21 KiB
21 KiB
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 曲线图