From 721abc02a9497d4083cd63999903fa0c5244780f Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Sun, 3 May 2026 22:40:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20async=20entry=20race=20condition=20?= =?UTF-8?q?=E2=80=94=20'entering'=20status=20prevents=20premature=20exit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During async executeEntry goroutine, the 300ms sleep between legs left the position vulnerable to checkExit() from the main loop. Added intermediate Status='entering' that checkExit/checkScaleIn skip ('entering' != 'open'), switched to 'open' only after both legs are placed. --- trader.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trader.go b/trader.go index 1d79936..e968714 100644 --- a/trader.go +++ b/trader.go @@ -47,7 +47,7 @@ type ArbPosition struct { LastScaleAt time.Time // when we last scaled in StartedAt time.Time ExitedAt time.Time - Status string // "open", "closed" + Status string // "entering", "open", "closed" RealizedPnl float64 ErrorLog string @@ -311,7 +311,7 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier * Coin: opp.Coin, AmountUSD: t.cfg.TradeAmountUSD, StartedAt: time.Now(), - Status: "open", + Status: "entering", // prevent checkExit/checkScaleIn during leg placement ScaleLevels: 0, } @@ -361,6 +361,7 @@ func (t *Trader) executeEntry(opp *ArbOpportunity, store *PriceStore, notifier * } pos.LastScaleAt = time.Now() + pos.Status = "open" // both legs placed, ready for Tick/exit logic log.Printf("[Trader] %s: Opened %s | Long %s @ %.2f Short %s @ %.2f | $%.0f", pos.Coin, pos.Direction, pos.LongLeg.Exchange, pos.LongLeg.EntryPrice,