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
This commit is contained in:
jackyu66git
2026-05-03 22:32:09 +08:00
parent 915b316ca7
commit 29f2072e90
+17 -1
View File
@@ -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.