# Dashboard Design ## 1. 目录结构 ``` exchange-monitor-go/ ├── main.go # 入口:启动 engine + web server ├── config.go # 配置加载(不变) ├── types.go # 公共类型(不变) │ ├── engine/ # 核心交易引擎(从 main.go 拆分) │ ├── engine.go # Engine 结构体:组合所有模块 │ ├── scanner.go # 价差扫描(从 scanner.go 移入) │ ├── trader.go # 交易执行(从 trader.go 移入) │ ├── notifier.go # Telegram 通知(从 notifier.go 移入) │ └── portfolio.go # 资金管理 + PnL 聚合 │ ├── exchange/ # 交易所连接(不变) │ └── ... │ ├── db/ # SQLite 持久化层(新增) │ ├── db.go # DB 初始化、迁移 │ ├── trade_repo.go # 交易记录 CRUD │ ├── order_repo.go # 订单明细 CRUD │ └── config_repo.go # 配置快照 │ ├── web/ # Web 仪表盘(新增) │ ├── server.go # HTTP 服务器 + 路由 │ ├── handler_dashboard.go # 页面渲染 │ ├── handler_api.go # REST API │ ├── handler_sse.go # SSE 实时推送 │ ├── static/ # 前端静态资源(go:embed) │ │ ├── index.html │ │ ├── app.js │ │ └── style.css │ └── ws_monitor.go # WS 状态监控 │ ├── risk/ # 风控层(新增) │ └── risk.go # 风控规则引擎 │ ├── persistence.md # 本设计文档 └── ... ``` ## 2. 数据模型 (SQLite) ``` ┌─────────────────────────────────────────────────────┐ │ trades │ ├──────────────┬──────────┬───────────────────────────┤ │ id │ INTEGER │ PRIMARY KEY AUTOINCREMENT │ │ coin │ TEXT │ NOT NULL │ │ direction │ TEXT │ BG->HL / HL->BG │ │ status │ TEXT │ open / closed │ │ entry_spread │ REAL │ 进场价差 % │ │ exit_spread │ REAL │ 出场价差 % │ │ long_ex │ TEXT │ 多腿交易所 │ │ short_ex │ TEXT │ 空腿交易所 │ │ long_entry │ REAL │ 多腿进场价 │ │ long_exit │ REAL │ 多腿出场价 │ │ short_entry │ REAL │ 空腿进场价 │ │ short_exit │ REAL │ 空腿出场价 │ │ long_pnl │ REAL │ 多腿 PnL % │ │ short_pnl │ REAL │ 空腿 PnL % │ │ fee_entry │ REAL │ 开仓手续费 % │ │ fee_exit │ REAL │ 平仓手续费 % │ │ net_pnl │ REAL │ 净利 % │ │ amount_usd │ REAL │ 总金额 $ │ │ scale_count │ INTEGER │ 加仓次数 │ │ exit_reason │ TEXT │ 止盈/止损/超时 │ │ convergence │ TEXT │ 收敛/发散/持平 │ │ opened_at │ DATETIME │ │ │ closed_at │ DATETIME │ │ └──────────────┴──────────┴───────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ orders (每腿一条) │ ├──────────────┬──────────┬───────────────────────────┤ │ id │ INTEGER │ │ │ trade_id │ INTEGER │ FK → trades.id │ │ leg │ TEXT │ long / short │ │ type │ TEXT │ entry / exit / scale │ │ exchange │ TEXT │ │ │ side │ TEXT │ buy / sell │ │ price │ REAL │ 成交价 │ │ size │ REAL │ 数量 │ │ fee │ REAL │ 手续费 │ │ order_id │ TEXT │ 交易所订单 ID │ │ status │ TEXT │ filled / cancelled │ │ created_at │ DATETIME │ │ └──────────────┴──────────┴───────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ price_snapshots │ ├──────────────┬──────────┬───────────────────────────┤ │ id │ INTEGER │ │ │ coin │ TEXT │ │ │ exchange │ TEXT │ │ │ price │ REAL │ │ │ bid │ REAL │ │ │ ask │ REAL │ │ │ spread_basis │ REAL │ bid-ask spread % │ │ recorded_at │ DATETIME │ │ └──────────────┴──────────┴───────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ config_snapshots │ ├──────────────┬──────────┬───────────────────────────┤ │ id │ INTEGER │ │ │ key │ TEXT │ 参数名 │ │ value │ TEXT │ 参数值 │ │ changed_at │ DATETIME │ │ │ changed_by │ TEXT │ web / cli │ └──────────────┴──────────┴───────────────────────────┘ ``` ## 3. REST API ``` Base URL: http://localhost:8080/api/v1 ┌────────┬────────────────────────┬────────────────────────────┐ │ Method │ Path │ 说明 │ ├────────┼────────────────────────┼────────────────────────────┤ │ GET │ /api/v1/stats/summary │ 总览指标 │ │ GET │ /api/v1/stats/coins │ 各币种明细 │ │ GET │ /api/v1/stats/pnl │ PnL 曲线(按天/时) │ │ GET │ /api/v1/stats/daily │ 每日统计 │ ├────────┼────────────────────────┼────────────────────────────┤ │ GET │ /api/v1/trades │ 交易列表(分页) │ │ GET │ /api/v1/trades/:id │ 单笔交易详情 + 订单明细 │ │ GET │ /api/v1/trades/active │ 当前持仓 │ ├────────┼────────────────────────┼────────────────────────────┤ │ GET │ /api/v1/exchanges │ 交易所连接状态 │ │ GET │ /api/v1/prices │ 所有币种实时价差 │ ├────────┼────────────────────────┼────────────────────────────┤ │ GET │ /api/v1/config │ 当前配置 │ │ PUT │ /api/v1/config │ 更新配置 │ ├────────┼────────────────────────┼────────────────────────────┤ │ GET │ /api/v1/status │ 系统运行状态(uptime等) │ │ POST │ /api/v1/action/restart │ 重启扫描器 │ └────────┴────────────────────────┴────────────────────────────┘ GET /api/v1/stats/summary 响应: { "total_trades": 387, "total_pnl_pct": 4.27, "total_pnl_usd": 0.85, "win_rate": 56.5, "avg_pnl_pct": 0.011, "max_drawdown": -2.1, "active_positions": 3, "running_time": "13h 22m", "exchanges_connected": 4, "mode": "simulation" } GET /api/v1/stats/coins 响应: [ { "coin": "ONDO", "trades": 115, "pnl_pct": 3.11, "win_rate": 56.5, "avg_pnl": 0.027, "best_trade": 0.18, "worst_trade": -0.05, "long_pct": 94, "short_pct": 6, "active": true }, ... ] GET /api/v1/trades?page=1&limit=20&coin=ONDO 响应: { "trades": [ { "id": 1, "coin": "ONDO", "direction": "BG->HL", "entry_spread": 0.17, "exit_spread": 0.01, "net_pnl": 0.10, "duration": "52s", "opened_at": "2026-05-03T15:42:00+08:00", "scale_count": 0 } ], "total": 115, "page": 1 } ``` ## 4. SSE (Server-Sent Events) 实时推送 ``` Endpoint: GET /api/v1/stream ────────────── 连接建立 ──────────────→ ←── event: snapshot ── 全量数据推送 ── { prices: {...}, positions: [...], summary: {...} } ←── event: price_update ── 价差变化 ── (每 500ms) { coin: "ONDO", spread: 0.15, bg: 0.28, hl: 0.2815 } ←── event: trade_opened ── 新开仓 ── { id: 42, coin: "ONDO", direction: "BG->HL", spread: 0.17, ... } ←── event: trade_closed ── 平仓 ── { id: 42, net_pnl: 0.10, exit_spread: 0.01, ... } ←── event: exchange_status ── WS 状态变化 ── { exchange: "Bitget", connected: true, latency_ms: 120 } ←── event: alert ── 系统告警 ── { level: "warn", message: "WS reconnected", ... } ``` ## 5. 前端页面布局 ``` ┌──────────────────────────────────────────────────────┐ │ [logo] 套利机器人仪表盘 [模拟/实盘] [设置] │ ├──────────────────────────────────────────────────────┤ │ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐ │ │ │总交易 │ │总净利 │ │胜率 │ │当前持仓│ │运行时间 │ │ │ │ 387 │ │+4.27% │ │56.5% │ │ 3 │ │ 13h22m │ │ │ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘ │ ├──────────────────────────────────────────────────────┤ │ Tab: [📈 概览] [📋 交易记录] [⚙️ 配置] [🔌 连接] │ ├──────────────────────────────────────────────────────┤ │ │ │ Tab: 概览 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 价差实时折线图(可切换币种) │ │ │ │ ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ │ │ │ │ ──── 0.1% 阈值线 ──── │ │ │ │ ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ │ │ │ │ [ONDO] [WIF] [OP] [DOGE] [ARB] [LINK] │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ ┌────┬──────┬──────┬──────┬──────┬──────┬───────┐ │ │ │币种 │方向 │持仓$ │价差% │已持 │浮动% │ P&L$ │ │ │ ├────┼──────┼──────┼──────┼──────┼──────┼───────┤ │ │ │ONDO│BG→HL │$5 │0.15 │12s │+0.10 │+0.005 │ │ │ │WIF │BG→HL │$10 │0.08 │2m30s │+0.22 │+0.022 │ │ │ │OP │HL→BG │$5 │0.22 │1m │+0.05 │+0.003 │ │ │ └────┴──────┴──────┴──────┴──────┴──────┴───────┘ │ │ │ │ 最近成交 │ │ 15:42:23 ONDO BG→HL 入场0.17% 出场0.01% +0.10% │ │ 15:41:55 WIF BG→HL 入场0.15% 出场0.02% +0.08% │ │ 15:41:30 OP HL→BG 入场0.22% 出场0.01% +0.15% │ │ │ │ Tab: 交易记录 │ │ ┌────┬──────┬──────┬──────┬──────┬──────┬──────┬───┐ │ │ │时间│币种 │方向 │入场 │出场 │净利% │持仓 │详情│ │ │ ├────┼──────┼──────┼──────┼──────┼──────┼──────┼───┤ │ │ │... │ │ │ │ │ │ │ >│ │ │ └────┴──────┴──────┴──────┴──────┴──────┴──────┴───┘ │ │ [上一页] [1/23] [下一页] │ │ │ │ Tab: 配置 │ │ 阈值: [0.1% ] 每腿金额: [$5 ] 模式: ○模拟 │ │ 冷却时间: [30000]ms 最大持仓: [3 ] ●实盘 │ │ [保存配置] │ │ │ │ Tab: 连接 │ │ ┌──────────┬──────────┬───────┬──────────┐ │ │ │交易所 │状态 │延迟 │最后更新 │ │ │ ├──────────┼──────────┼───────┼──────────┤ │ │ │Bitget │● 已连接 │120ms │15:42:23 │ │ │ │HL │● 已连接 │85ms │15:42:23 │ │ │ │Binance │● 已连接 │90ms │15:42:22 │ │ │ │dYdX │⚠ 重连中 │-- │15:41:48 │ │ │ └──────────┴──────────┴───────┴──────────┘ │ └──────────────────────────────────────────────────────┘ ``` ## 6. 前端技术选型 ``` 框架: 无框架,纯 HTML + CSS + vanilla JS 原因:零构建步骤,单文件嵌入 图表: Chart.js (CDN https://cdn.jsdelivr.net/npm/chart.js) 原因:轻量、灵活、CDN 无需 npm 实时通: EventSource (浏览器原生 SSE) 原因:比 WebSocket 简单,自动重连 UI: 纯 CSS Grid + Flexbox 深色主题(适合交易屏长时间看) 体积: < 300KB 总大小(含 Chart.js CDN) ``` ## 7. Web Server 设计 (Go) ``` // web/server.go package web type Server struct { engine *engine.Engine db *db.DB mux *http.ServeMux sse *SSEHub // SSE 连接管理器 } // SSEHub 管理所有 SSE 客户端连接 type SSEHub struct { clients map[chan SSEEvent]struct{} register chan chan SSEEvent unregister chan chan SSEEvent broadcast chan SSEEvent } // 从 Engine 接收事件并广播 func (h *SSEHub) Broadcast(eventType string, data interface{}) ``` ## 8. 数据流 ### 三层数据分层 ``` ┌──────────────────────────────────────────────┐ │ HOT (内存 only, 500ms) │ │ PriceStore: 6币×4所 实时价 │ │ Trader.positions: 当前持仓 │ │ Scanner: 扫描结果 │ │ 不落盘,重启丢失,但重连 WS 秒恢复 │ ├──────────────────────────────────────────────┤ │ WARM (SQLite, 事件驱动) │ │ 平仓 → INSERT trades │ │ 每腿成交 → INSERT orders │ │ 配置修改 → INSERT/UPSERT config_snapshots │ │ WS 重连 → INSERT exchange_events(可选) │ │ 写入频率: < 1次/秒 │ ├──────────────────────────────────────────────┤ │ COLD (时序方案待定, 未来) │ │ 价格存档:每分钟采样 × 6币 × 4所 │ │ 日产量: ~34,560行 → 可存 SQLite 也可用 │ │ TimescaleDB / InfluxDB (如果要做回测平台) │ └──────────────────────────────────────────────┘ ``` ### 实时数据 → 网页 ``` WS 数据流 (500ms): ┌─────────┐ price ┌──────────┐ SSE push ┌─────────┐ │ Exchange│──────────►│ Engine │─────────────►│ Browser │ │ WS │ │ (HOT层) │ │(实时更新)│ └─────────┘ └────┬─────┘ └─────────┘ │ 仅事件写入 ┌─────▼──────┐ │ SQLite │ │ (WARM层) │ └────────────┘ API 请求 (读 WARM 层): ┌─────────┐ GET /api/... ┌──────────┐ SQL ┌────────┐ │ Browser │────────────────►│ Server │──────────►│ SQLite │ │ (页面) │◄────────────────│(REST API)│◄──────────┘ │ └─────────┘ JSON └──────────┘ ``` ## 9. 实现顺序 ``` Phase 1 — 基础设施 1. db/ 包:SQLite 初始化 + schema 迁移 2. 程序启动时保存交易记录到 SQLite 3. 重启时从 SQLite 恢复历史数据 Phase 2 — Web Server 1. web/server.go:路由 + SSE Hub 2. REST API:summary, trades, prices, config 3. 前端 index.html:概览页(指标卡片 + 当前持仓 + 最近成交) Phase 3 — 实时 1. SSE stream:价格、持仓、交易实时推送 2. Chart.js 实时价差折线图 Phase 4 — 完善 1. 交易记录页(分页、筛选、详情弹窗) 2. 配置页(在线修改参数) 3. 连接状态页 4. PnL 曲线图 ```