From 29f2072e900f8a9be89e1650b49c04364f5928d8 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Sun, 3 May 2026 22:32:09 +0800 Subject: [PATCH] feat: async TryEntry with goroutine, non-blocking main loop - TryEntry now spawns a goroutine for order placement - Main loop continues at 50-250ms even during entry - 'entering' map prevents duplicate entries on same coin - Async cleanup of entering state on completion --- trader.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/trader.go b/trader.go index f7cf14a..1d79936 100644 --- a/trader.go +++ b/trader.go @@ -118,6 +118,7 @@ type Trader struct { db *db.DB mu sync.Mutex positions map[string]*ArbPosition // coin -> position + entering map[string]bool // coin -> being entered (async goroutine) lastTradeTime map[string]time.Time closedTrades []TradeRecord // history of closed trades (current session) @@ -160,6 +161,7 @@ func NewTrader(cfg *Config, database *db.DB) *Trader { bitget: bt, hyperliquid: hl, positions: make(map[string]*ArbPosition), + entering: make(map[string]bool), lastTradeTime: make(map[string]time.Time), } @@ -235,6 +237,8 @@ func (t *Trader) Tick(store *PriceStore, notifier *Notifier) { } // TryEntry opens initial position when threshold is met. +// Returns true if entry was accepted (async goroutine will place orders). +// Non-blocking — the main loop is not stalled by REST calls or the 300ms leg delay. func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Notifier) bool { if !t.IsConfigured() { return false @@ -252,13 +256,25 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti t.mu.Unlock() return false } + if t.entering[opp.Coin] { + t.mu.Unlock() + return false + } if lastTime, ok := t.lastTradeTime[opp.Coin]; ok && time.Since(lastTime) < 30*time.Second { t.mu.Unlock() return false } + t.entering[opp.Coin] = true t.mu.Unlock() - return t.executeEntry(opp, store, notifier) + // Async goroutine — placeOrder calls (REST or mock) don't block the main loop + go func() { + t.executeEntry(opp, store, notifier) + t.mu.Lock() + delete(t.entering, opp.Coin) + t.mu.Unlock() + }() + return true } // executeEntry places both legs using the scan-time prices from ArbOpportunity.