Commit Graph
53 Commits
Author SHA1 Message Date
jackyu66git 0288dc8284 feat: 手续费改为逐笔USD累算 + Vite React前端 + system_orders表 + README 2026-05-04 04:34:27 +08:00
jackyu66git 15a4684208 chore: 增强start.sh, 支持--clean清库/--rebuild强制编译 2026-05-04 02:03:17 +08:00
jackyu66git d1e6c965cb fix: blacklistCoin 计算退出PnL字段, 避免DB写入零值
blacklistCoin 之前只设 Status=close_failed 就跑了, 没给
ExitDiffPct/ExitNetPnl/ExitLongPnl/ExitTotalFees 等字段赋值。
retryClose 写 TradeRecord 时全为零, 导致DB出现 PnL=0 的假记录。

修复: blacklistCoin 现在接收当前价格, 用 checkExit 相同方式
计算 PnL/价差收敛标签并存入 pos 字段。
2026-05-04 02:00:04 +08:00
jackyu66git dd15c6fe43 fix: 移除多余的netPnl和反转检查, 收敛即退出(0.02%) 2026-05-04 01:57:56 +08:00
jackyu66git 1c0618583c fix: 收敛退出放宽到0.02%+净利为正, 恢复到0.02%即退不亏钱 2026-05-04 01:55:22 +08:00
jackyu66git 51b8ee0c8a chore: 添加.gitignore, 移除日志文件跟踪 2026-05-04 01:51:17 +08:00
jackyu66git 21a3f9a962 feat: 所有参数移至config.json, 重构退出逻辑, 清理遗留接口
- 将所有硬编码参数迁移到 config.json (手续费率、止盈/止损阈值、
  超时、腿间隔、加仓步进等)
- 退出条件: 净利 >= take_profit_pct 止盈, 价差 <= 0 平仓
- 删除 Binance/dYdX 遗留代码
- 更新 README 文档
- Dashboard: 双交易所价格表、黑名单UI、按币名排序持仓
- Bitget WS: 文本ping保活
- 数据库: 重置, 无历史仓位
2026-05-04 01:46:17 +08:00
jackyu66git 2ed6ffc747 fix: Bitget WS keepalive — send text ping, not WebSocket PingMessage
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
2026-05-04 00:33:03 +08:00
jackyu66git e04a165d69 fix: start.sh detects embedded web file changes; rebuild on JS/CSS change 2026-05-04 00:09:49 +08:00
jackyu66git 004ff50325 web: spread highlight threshold 0.1% → 0.2% 2026-05-04 00:07:32 +08:00
jackyu66git b8c73852be fix: BG taker fee 0.06% → 0.03% 2026-05-04 00:04:14 +08:00
jackyu66git 02ca8e248c fix: trade_threshold 0.25 → 0.05 (net profit %) 2026-05-03 23:55:40 +08:00
jackyu66git 5d6d9352f3 fix: use taker fees (BG 0.06%, HL 0.045%) + threshold 0.25%
All IOC market orders incur taker fees, not maker fees. Previous
makerFees (BG 0.02%, HL 0.015%) caused 0.10% threshold trades to
actually lose 0.11% per round trip.

Changes:
- scanner.go: makerFees → takerFees (BG 0.060, HL 0.045)
- dashboard.go: makerFees → takerFees
- types.go: makerFees → takerFees
- trader.go: makerFees → takerFees
- config.json: trade_threshold 0.1 → 0.25
2026-05-03 23:34:01 +08:00
jackyu66git 68c0a342a6 fix: retryClose saves trade record and persists to DB on success 2026-05-03 23:19:15 +08:00
jackyu66git cf6548c37f fix: 4 bugfixes for production readiness
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)
2026-05-03 23:13:44 +08:00
jackyu66git cdcf0acea6 docs: update README for async entry, entering status, 50ms scan
- Architecture diagram: async goroutine entry path
- Trading Logic: async entry, entering status protection
- Config note: floor rounding on both exchanges
2026-05-03 23:00:18 +08:00
jackyu66git b55ff111a4 perf: scan interval 50ms fixed
HL allMids pushes ~200ms, scan at 50ms catches every update
within 1 tick. Async entry goroutine means no blocking.
2026-05-03 22:47:44 +08:00
jackyu66git 721abc02a9 fix: async entry race condition — 'entering' status prevents premature 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.
2026-05-03 22:40:48 +08:00
jackyu66git 29f2072e90 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
2026-05-03 22:32:09 +08:00
jackyu66git 915b316ca7 fix: HL size floor, random scan jitter 50-250ms
- GetHLSize: change rounding from Sprintf (round-to-nearest)
  to math.Floor (floor), consistent with GetBitgetSize
- Scan interval: fixed 200ms → random 50-250ms to avoid
  lock-step with HyperLiquid's ~200ms allMids push cycle
- README: update architecture diagram, trading logic, config note
2026-05-03 22:25:01 +08:00
jackyu66git 6a4b046742 config.json: add trade_enabled=true 2026-05-03 21:19:07 +08:00
jackyu66git e301c5aa4c fix config.json boolean fields (test_mode, trade_enabled) not applying
loadJSONConfig only checked != 0 for floats, missing bool overrides.
Added def.X = cfg.X || def.X for both booleans.
2026-05-03 21:16:45 +08:00
jackyu66git 327f2505dd sync README: update arch diagram and structure for stats separation 2026-05-03 21:14:49 +08:00
jackyu66git 32f06b57d6 move DetailedStats calc out of trader.go into dashboard.go
- 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
2026-05-03 21:13:11 +08:00
jackyu66git 1e4a3f3b37 Add detailed PnL and duration stats to dashboard 2026-05-03 21:03:40 +08:00
jackyu66git a89a1f6cd8 Configurable scan interval via SCAN_INTERVAL_MS env var
- Add SCAN_INTERVAL_MS env var support in config.go (code default 500ms)
- Set to 200ms in .env for tighter opportunity detection
2026-05-03 20:55:09 +08:00
jackyu66git de6463530d Update README: add start.sh, fix Quick Start
- 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
2026-05-03 20:31:19 +08:00
jackyu66git 35d9f7661b Update README: spread window monitoring, synchronous entry
- Add SpreadWindowTracker to architecture diagram
- Document synchronous entry logic (no goroutine, scan-time prices)
- Add Spread Window Monitoring section with log example
2026-05-03 20:30:47 +08:00
jackyu66git 5cfb6afa55 Fix SpreadWindow: use netProfit() for fee accuracy, track real peak
- 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
2026-05-03 20:28:27 +08:00
jackyu66git 8ae85750b5 Add SpreadWindowTracker to measure opportunity duration
- 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
2026-05-03 20:24:36 +08:00
jackyu66git 89d92b6672 Fix entry latency: synchronous execution with scan-time prices
- 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
2026-05-03 20:17:35 +08:00
jackyu66git 02fac78c59 Update README to reflect current architecture and features
- Fix architecture diagram (ScanBGHL, correct data flow)
- Add configuration table with all env vars
- Document exit logic (convergence, reversal, timeout)
- Add Web Dashboard, Notifications, DB, Signals sections
- Add project file tree
2026-05-03 19:56:35 +08:00
jackyu66git c89a3b7333 fix: loadTrades before chart init + try-catch for CDN failure
- Reorder init(): loadTrades() runs first, charts second
- Wrap chart init in try-catch so UI survives CDN issues
- Update start.sh to detect changes in embedded .html/.js/.css
2026-05-03 19:42:06 +08:00
jackyu66git 8284458b9c chore: startup script with port detection and auto-cleanup
./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
2026-05-03 19:38:18 +08:00
jackyu66git e319b73331 fix: reversal = take profit, not stop loss
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.
2026-05-03 19:24:51 +08:00
jackyu66git db52152ef9 fix: stop-loss on spread reversal + current spread sign (#3)
- 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.
2026-05-03 19:22:35 +08:00
jackyu66git 57344d33f7 fix(dashboard): current spread sign for HL→BG positions
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.
2026-05-03 19:11:34 +08:00
jackyu66git 505137bcb0 feat(web): trade detail modal with prices, fees, timestamps
- 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
2026-05-03 18:59:28 +08:00
jackyu66git 02b74f1ec0 decouple display from trading: snapMu + RefreshSnapshot/ReadSnapshot
- 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
2026-05-03 18:35:47 +08:00
jackyu66git b08d8490fc fix: data race, scale-in PnL, nonce mutex, dead code, hourly check
- 🔴 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.
2026-05-03 18:31:14 +08:00
jackyu66git ab48e207a5 Fix 3 arbitrage logic issues from code review
Issue #1 (critical): reProfit false positive on price reversal
  executeEntry used CalcNetProfit which auto-swaps prices when
  hlP < bgP. If prices flipped between scan and execution, reProfit
  reported positive even when direction was now wrong.
  Fix: use netProfit() with explicit direction + verify spread
  direction hasn't flipped (hlP <= bgP prevents BG->HL when
  HL is no longer more expensive).

Issue #2 (medium): Scale-in was paper-only, didn't place orders
  Now actually places additional orders on both legs via new
  placeOrderAt(). Test mode uses mock fills. Live mode sends
  real API orders. AmountUSD properly tracks total deployed
  capital. Partial fill handled gracefully (don't close main leg).

Issue #3 (minor): closeLeg missing ExitTime on mock mode
2026-05-03 18:19:25 +08:00
jackyu66git 931855e1f5 Cleanup: remove dead code after P3 refactor 2026-05-03 18:13:11 +08:00
jackyu66git beb3611778 Phase 3: Real-time enhancements
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
2026-05-03 18:05:19 +08:00
jackyu66git da561325d7 Revert B#6: netProfit must NOT delegate to CalcNetProfit
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.
2026-05-03 17:55:39 +08:00
jackyu66git 277c34c3bd Remove Aevo exchange (retired)
- Delete exchange/aevo.go (AevoWS, aevoTickerMsg, aevoTickerData, etc.)
- scanner.go: remove ExAevo constant, fee rates, scan pair entries, shortName mapping
- main.go: remove aevoSymbols collection loop and TrackedSymbol usage
2026-05-03 17:50:42 +08:00
jackyu66git eb74495470 Fix 8 bugs from code review
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
2026-05-03 17:48:06 +08:00
jackyu66git c2489614a7 Phase 2: Web dashboard with SSE real-time push
- dashboard.go: SSE hub + HTTP server + price history ring buffer
- static.go: //go:embed for static files
- web/static/index.html: Full dashboard HTML (6 panels)
- web/static/app.js: SSE client, Chart.js price chart, live table updates
- web/static/style.css: GitHub-style dark theme
- main.go: Start dashboard on :8888 + wire price recording + scan results

Dashboard features:
  - Real-time price table (6 coins × 4 exchanges)
  - Arbitrage opportunities table
  - Open positions view
  - Historical trades table (from SQLite)
  - Chart.js price chart with coin/exchange selector
  - Stats summary (total/converged/diverged/flat)
  - 🚫 Zero external Go dependencies (Chart.js loaded from CDN)
2026-05-03 17:38:26 +08:00
jackyu66git 0c1eaedcb6 Fix: remove DB from git tracking, add to gitignore 2026-05-03 17:28:30 +08:00
jackyu66git b09314f317 Phase 1: SQLite persistence layer
- Add modernc.org/sqlite (pure Go, no CGO)
- db/ package: trades, orders, config_log tables + CRUD
- Trade persistence: every closed trade saved to SQLite
- Restart recovery: open positions restored from DB
- Automatic migration on startup
2026-05-03 17:28:08 +08:00
jackyu66git 189fc0d9b6 Add HOT/WARM/COLD data tier to design doc 2026-05-03 17:16:01 +08:00