diff --git a/trader.go b/trader.go index b900fe2..f7cf14a 100644 --- a/trader.go +++ b/trader.go @@ -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.