Fix entry latency: synchronous execution with scan-time prices
- Remove goroutine in TryEntry (executeEntry now synchronous) - Use opp.BuyPrice/SellPrice directly instead of re-reading from store - Keep lightweight direction sanity check (0.1% tolerance) - executeEntry returns bool for call chain consistency
This commit is contained in:
@@ -258,39 +258,37 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
go t.executeEntry(opp, store, notifier)
|
||||
return true
|
||||
return t.executeEntry(opp, store, notifier)
|
||||
}
|
||||
|
||||
func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *Notifier) {
|
||||
// executeEntry places both legs using the scan-time prices from ArbOpportunity.
|
||||
// Synchronous — runs in the scanner tick to avoid WS price movement between
|
||||
// detection and execution.
|
||||
func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *Notifier) bool {
|
||||
// Use scan-time prices directly to avoid WS jitter killing the entry
|
||||
bgP, hlP := opp.BuyPrice, opp.SellPrice
|
||||
if opp.BuyEx == ExHyperLiquid {
|
||||
bgP, hlP = opp.SellPrice, opp.BuyPrice
|
||||
}
|
||||
if bgP <= 0 || hlP <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Quick sanity check: spread direction hasn't completely reversed
|
||||
// Use a relaxed check (not full re-read) since WS prices move constantly
|
||||
snap := store.GetAll()
|
||||
exMap := snap[opp.Coin]
|
||||
if exMap == nil {
|
||||
return
|
||||
}
|
||||
bgP := exMap[ExBitget]
|
||||
hlP := exMap[ExHyperLiquid]
|
||||
if bgP <= 0 || hlP <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Issue #1: reProfit must use actual direction — CalcNetProfit auto-swaps on reversal!
|
||||
var reProfit float64
|
||||
if opp.BuyEx == ExBitget {
|
||||
reProfit = netProfit(bgP, hlP, makerFees[ExBitget], makerFees[ExHyperLiquid])
|
||||
} else {
|
||||
reProfit = netProfit(hlP, bgP, makerFees[ExHyperLiquid], makerFees[ExBitget])
|
||||
}
|
||||
if reProfit < t.cfg.TradeThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
// Issue #1: Verify spread direction hasn't flipped since scan
|
||||
if opp.BuyEx == ExBitget && hlP <= bgP {
|
||||
return // reversed: HL no longer more expensive than BG
|
||||
}
|
||||
if opp.BuyEx == ExHyperLiquid && bgP <= hlP {
|
||||
return // reversed: BG no longer more expensive than HL
|
||||
if exMap != nil {
|
||||
currBg := exMap[ExBitget]
|
||||
currHl := exMap[ExHyperLiquid]
|
||||
if currBg > 0 && currHl > 0 {
|
||||
if opp.BuyEx == ExBitget && currHl <= currBg*0.999 {
|
||||
return false // reversed beyond small tolerance
|
||||
}
|
||||
if opp.BuyEx == ExHyperLiquid && currBg <= currHl*0.999 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos := &ArbPosition{
|
||||
@@ -337,13 +335,13 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
||||
// Execute both legs
|
||||
if err := t.placeOrder(pos.LongLeg, "buy", store); err != "" {
|
||||
t.cleanup(pos.Coin)
|
||||
return
|
||||
return false
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if err := t.placeOrder(pos.ShortLeg, "sell", store); err != "" {
|
||||
t.closeLeg(pos.LongLeg)
|
||||
t.cleanup(pos.Coin)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
pos.LastScaleAt = time.Now()
|
||||
@@ -374,6 +372,7 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
|
||||
"time": time.Now().Format("15:04:05"),
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkScaleIn adds more position when spread widens further.
|
||||
|
||||
Reference in New Issue
Block a user