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:
jackyu66git
2026-05-03 20:17:35 +08:00
parent 02fac78c59
commit 89d92b6672
+27 -28
View File
@@ -258,39 +258,37 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti
} }
t.mu.Unlock() t.mu.Unlock()
go t.executeEntry(opp, store, notifier) return t.executeEntry(opp, store, notifier)
return true
} }
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() snap := store.GetAll()
exMap := snap[opp.Coin] exMap := snap[opp.Coin]
if exMap == nil { if exMap != nil {
return 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
} }
bgP := exMap[ExBitget] if opp.BuyEx == ExHyperLiquid && currBg <= currHl*0.999 {
hlP := exMap[ExHyperLiquid] return false
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
} }
pos := &ArbPosition{ pos := &ArbPosition{
@@ -337,13 +335,13 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
// Execute both legs // Execute both legs
if err := t.placeOrder(pos.LongLeg, "buy", store); err != "" { if err := t.placeOrder(pos.LongLeg, "buy", store); err != "" {
t.cleanup(pos.Coin) t.cleanup(pos.Coin)
return return false
} }
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
if err := t.placeOrder(pos.ShortLeg, "sell", store); err != "" { if err := t.placeOrder(pos.ShortLeg, "sell", store); err != "" {
t.closeLeg(pos.LongLeg) t.closeLeg(pos.LongLeg)
t.cleanup(pos.Coin) t.cleanup(pos.Coin)
return return false
} }
pos.LastScaleAt = time.Now() pos.LastScaleAt = time.Now()
@@ -374,6 +372,7 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier *
"time": time.Now().Format("15:04:05"), "time": time.Now().Format("15:04:05"),
}) })
} }
return true
} }
// checkScaleIn adds more position when spread widens further. // checkScaleIn adds more position when spread widens further.