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:
@@ -118,6 +118,7 @@ type Trader struct {
|
|||||||
db *db.DB
|
db *db.DB
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
positions map[string]*ArbPosition // coin -> position
|
positions map[string]*ArbPosition // coin -> position
|
||||||
|
entering map[string]bool // coin -> being entered (async goroutine)
|
||||||
lastTradeTime map[string]time.Time
|
lastTradeTime map[string]time.Time
|
||||||
closedTrades []TradeRecord // history of closed trades (current session)
|
closedTrades []TradeRecord // history of closed trades (current session)
|
||||||
|
|
||||||
@@ -160,6 +161,7 @@ func NewTrader(cfg *Config, database *db.DB) *Trader {
|
|||||||
bitget: bt,
|
bitget: bt,
|
||||||
hyperliquid: hl,
|
hyperliquid: hl,
|
||||||
positions: make(map[string]*ArbPosition),
|
positions: make(map[string]*ArbPosition),
|
||||||
|
entering: make(map[string]bool),
|
||||||
lastTradeTime: make(map[string]time.Time),
|
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.
|
// 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 {
|
func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Notifier) bool {
|
||||||
if !t.IsConfigured() {
|
if !t.IsConfigured() {
|
||||||
return false
|
return false
|
||||||
@@ -252,13 +256,25 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti
|
|||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
return false
|
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 {
|
if lastTime, ok := t.lastTradeTime[opp.Coin]; ok && time.Since(lastTime) < 30*time.Second {
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
t.entering[opp.Coin] = true
|
||||||
t.mu.Unlock()
|
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.
|
// executeEntry places both legs using the scan-time prices from ArbOpportunity.
|
||||||
|
|||||||
Reference in New Issue
Block a user