- New SpreadWindowTracker in types.go watches BG↔HL spread for all
tracked coins, both directions
- Logs duration when spread stays above trade threshold then converges
- Wired into main loop after each scan tick
- Filters sub-100ms windows as noise
- Click any trade row in history table to open detail modal
- Modal shows 6 sections: 概览, 时间, 价差, 手续费, 多仓, 空仓
- Entries and exits displayed with 6 decimal precision
- Fee entry/exit and total fee displayed
- Open/close timestamps with full date-time format
- Duration, scale count, total amount, exit reason
- Orders sub-table if available
- Escape key and overlay click to close
- Add snapMu RWMutex + positionsSnapshot to Trader
- RefreshSnapshot() called from main loop after Tick() — acquires
t.mu briefly, stores deep copy under snapMu
- ReadSnapshot() returns snapshot copy under snapMu.RLock — never
touches t.mu, zero contention with trading path
- Dashboard + handleStatus + hourly summary + status log all
use ReadSnapshot() instead of GetPositionsCopy()
- Trading path (Tick/TryEntry/executeEntry/checkExit/checkScaleIn)
never blocked by display reads
- Snapshot is at most 1 tick behind live state — acceptable delay
- 🔴 Data race: Add GetPositionsCopy() returning deep copies (no shared
ArbPosition pointers). Use it in dashboard broadcastLoop + handleStatus.
- 🟡 Scale-in PnL: Track LongEntryPrices/ShortEntryPrices on ArbPosition,
compute weighted average (harmonic mean) at exit for accurate PnL.
- 🟢 CalcNetProfit: Delete dead code from exchange/helpers.go.
- 🟢 HL nonce: Add sync.Mutex around lastNonce++ (thread safety).
- 🟢 Hourly check: Change from 5-second window to minute window.
- 🟢 ExitPrice: Test mode closeLeg already handled by checkExit.
P3-1: Scan optimization — only BG↔HL (50+ pair combos → 2)
P3-2: Real-time spread chart — spreadHistory ring buffer +
/api/spread-history endpoint + Chart.js spread chart
P3-3: Live position PnL — positions SSE now includes
estimated current profit/loss + current spread
P3-4: Real-time trade events — trader.OnTradeEvent callback
fires SSE 'trade_open' / 'trade_close' immediately
P3-5: Connection status monitoring — tracks last update time
per exchange, broadcast via stats.connections + /api/connections
Frontend: spread chart card, PnL column in positions,
connection status dots in stats bar,
green/red border flash on trade events
B#1 — sigCh shared across goroutines, SIGINT unreliable
→ context.WithCancel: main loop cancels ctx on SIGINT,
4 WS goroutines select on ctx.Done() instead of shared sigCh
B#3 — restoreOpenPositions missing LastScaleAt
→ Set LastScaleAt = tr.OpenedAt on restore so scale-in cooldown works
B#4 — dYdX heartbeat goroutine leaks on reconnect
→ Added stopHeartbeat chan + heartbeatMu mutex; close old channel
before spawning new heartbeat goroutine
B#5 — GetBitgetSize fmt.Sprintf rounds up, may exceed amountUSD
→ Added math.Floor(sz*multiplier)/multiplier before format to round
DOWN to nearest valid step size for every coin
B#6 — netProfit and CalcNetProfit duplicate formula
→ scanner.go netProfit now delegates to exchange.CalcNetProfit
B#7 — Aevo Run callback only 2 params, incompatible with startExchange
→ Changed to 4-arg callback func(coin, price, bid, ask) with bid=ask=0
B#8 — parseFloat uses fmt.Sscanf (slow, locale-sensitive)
→ Replaced with strconv.ParseFloat
B#9 — dYdX receives hlSymbols instead of its own symbol list
→ Added dydxSymbols var, built from c.HL like other exchanges