Bitget v2 WS requires a text message "ping" every 30s, not a WebSocket
PingMessage control frame (opcode 0x9). Using the wrong ping type caused
a silent failure: connection stays up and subscription succeeds, but NO
ticker data is pushed — zero errors, zero reconnection logs.
Changes:
- connector.go: Add TextPing bool flag, send text 'ping' when set
- bitget.go: Set TextPing=true, handle text 'pong', add debug logging
- scanner.go: Expand to 179 overlapping Bitget+HL coins
1. GetHLSize: add minimum size checks (prevent size=0 when price > amount)
2. executeEntry: orphan position warning when leg1 close also fails after leg2
3. checkExit: keep position as close_failed on failure, retryClose on each Tick
4. persistTrade: sync instead of goroutine (prevent data loss on exit)
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.
- 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
- Remove DetailedStats struct and GetDetailedStats() method from trader.go
- Add calcDetailedStats() standalone pure function in dashboard.go
- Dashboard calls d.trader.GetClosedTrades() + calcDetailedStats()
- Trading logic now has zero display-oriented calculations
- Add start.sh to Quick Start section and project file tree
- Remove broken cp .env.example reference (no such file)
- Annotate .env with config categories
- Replace manual spread-fee calc with netProfit() call — matches
scanner's exact fee model (round-trip 0.07%)
- Each direction gets correct buy/sell fee pairing
- Add PeakNet field to record true max during window, not close-time value
- Log peak net with +/- sign instead of approx symbol
- 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
- Remove goroutine in TryEntry (executeEntry now synchronous)
- Use opp.BuyPrice/SellPrice directly instead of re-reading from store
- Keep lightweight direction sanity check (0.1% tolerance)
- executeEntry returns bool for call chain consistency
./start.sh:
1. Detect if port 8888 is in use → kill gracefully, force if needed
2. Verify port free, rebuild if source changed, start in background
3. Wait up to 10s for readiness confirmation
HL→BG position: long HL, short BG.
When bgP and hlP cross over (spread reverses):
- Long HL: price went up → profit
- Short BG: price went down → profit
Both legs profit simultaneously. Reversal is MAX profit moment.
- Add explicit stop-loss when diffPct < -0.02 (价差反转,止损平仓)
instead of relying on the convergence threshold to catch reversals
- Dashboard currentSpread now direction-aware for HL→BG positions
- Trade detail modal with clickable rows (previous commit partial)
Before: reversals would exit via with wrong reason
'价差收敛'. After: dedicated < -0.02 check with correct reason.
Bug: current spread always computed as (hl-bg)/bg regardless of
position direction. For HL→BG positions, spread should be (bg-hl)/hl
to match entry spread convention.
Fix: check LongLeg.Exchange — if HL→BG, use (bg-hl)/hl instead.
- 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.