Added: ALGO, PYTH, SAND, ADA, HBAR, IOTA, MINA, FET, WLD,
SUI, APT, NEAR, FIL, DYDX, JUP, TIA, MOVE
Removed: pixel and VET (fake 195% spreads on testnet)
Also updated GetHLSize() to handle szDecimals for all new coins.
HL testnet USDC lives in the spot account, not the perp clearinghouse.
GetBalance() was calling UserState() (perp clearinghouseState), which
returned /usr/bin/bash. Switched to SpotUserState() and parse USDC.total - USDC.hold.
Also cleaned up .gitignore to exclude .env, binary, logs, data/.
- config.json: test_mode=false, ready for sim/testnet trading
- trader.go: auto-stop after 5 real trades, exchange response logging,
Stop()/Start() API, shuttingDown flag for graceful stop
- dashboard.go: POST /api/stop + POST /api/start endpoints,
trading status in SSE stats
- exchange/hyperliquid.go: switch HL WS to testnet endpoint
- exchange/hyperliquid_trade.go: switch REST to testnet endpoint,
support base64 + 32-byte EVM private keys
- main.go: listen on trader.StopCh (graceful, no process exit)
- scanner.go: trim TrackedCoins to only 6 core coins (DOGE/LINK/ONDO/OP/WIF/ARB)
- .gitignore: ignore main binary
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
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
CalcNetProfit (helpers.go) has internal price-swap logic — when
price2 < price1 it swaps buy/sell sides. ScanArbWithFees relies
on netProfit being a pure strict-direction calculation (callers
try both directions via addPair). Delegation caused double-swap:
both netProfit calls in addPair returned positive profit, but
direction1's struct reported wrong exchange pair, leading to
potential loss-making trades.
Keep both formulas as independent implementations with explicit
comments warning against future merging attempts.
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