V5.2 开发文档(完整版)
套利引擎V5.2 — Bug修复 + 策略优化 + 8信号源 + 策略配置化 + AB测试
版本:V5.2 | 状态:待开发 | 负责人:露露
创建:2026-03-01 | 前置:V5.1 tag v5.1 commit d8ad879
V5.1-hotfix:commits 45bad25 → 4b841bc(P0修复已上线)
| 指标 | 数值 |
|---|
| 总交易 | 181笔 |
| 总盈亏 | +37.07R |
| 每笔平均 | +0.20R |
| 初始资金 | $10,000 |
| 当前余额 | ~$17,414 |
| 档位 | 笔数 | 胜率 | 总PnL | 每笔平均 | 结论 |
|---|
| 85+ (heavy) | 53 | 73.6% | +15.00R | +0.28R | ✅ 优质 |
| 80-84 | 81 | 65.4% | +4.11R | +0.05R | ⚠️ 平庸 |
| 75-79 | 41 | 78.0% | +17.05R | +0.42R | ✅ 最佳 |
| 70-74 | 6 | 50.0% | -2.65R | -0.44R | ❌ 亏钱 |
| 币种 | SL距离% | 仓位价值 | 隐含杠杆 | 手续费/R |
|---|
| BTC | 0.43% | $46,000 | 4.6x | 0.23R |
| ETH | 0.56% | $36,000 | 3.6x | 0.18R |
| XRP | 0.44% | $45,000 | 4.5x | 0.05R |
| SOL | 0.58% | $34,000 | 3.4x | 0.04R |
手续费公式:fee_R = 2 × 0.05% × position_value / risk_usd
BTC盈亏比问题:TP2净利+0.89R vs SL净亏-1.23R,盈亏比仅0.72,需55%以上胜率才保本。
| Bug | 影响 | 修复Commit |
|---|
| pnl_r虚高2倍 | 统计数据全部失真 | 45bad25 |
| 冷却期阻断反向平仓 | 反向信号无法关仓 | 45bad25 |
| 分区月份Bug | 月底数据写入失败 | 45bad25 |
| SL/TP用市价不是限价 | SL超过1R | 2f9dce4 |
| 浮盈没算半仓 | 持仓盈亏虚高 | 4b841bc |
- 修复所有已知Bug(Claude Code审阅 + 实际使用发现的)
- FR+清算加入评分(8信号源完整版)
- 开仓阈值提到75分(砍掉70-74垃圾信号)
- 策略配置化框架(一套代码多份配置)
- AB测试(V5.1 vs V5.2并行对比)
- 24小时warmup(消除冷启动)
- 55%胜率必须盈利:盈亏比至少0.82:1
- 无限趋近实盘:模拟盘和实盘逻辑完全一致
- 数据驱动:所有决策基于数据,不拍脑袋
| ID | 优先级 | 文件 | 问题 | 修复方案 |
|---|
| P0-3 | P1 | signal_engine.py:285 | 开仓价用30分VWAP而非实时价 | price = win_fast.trades[-1][2] |
| P0-4 | P2 | signal_engine+paper_monitor | 双进程并发写paper_trades | SELECT FOR UPDATE SKIP LOCKED |
| P1-2 | P2 | signal_engine.py:143-162 | 浮点精度漂移(buy_vol/sell_vol) | 每10000次trim从deque重算sums |
| P1-3 | P1 | market_data_collector.py:51 | 单连接无重连 | 改用db.get_sync_conn()连接池 |
| P1-4 | P3 | db.py:36-43 | 连接池初始化线程不安全 | threading.Lock双重检查 |
| P2-1 | P2 | market_data_collector.py:112 | XRP/SOL coinbase_premium KeyError | if symbol not in pair_map: return |
| P2-3 | P2 | agg_trades_collector.py:77 | flush_buffer每秒调ensure_partitions | 移到定时任务(每小时一次) |
| P2-4 | P3 | liquidation_collector.py:127 | elif条件冗余 | 改为else |
| P2-5 | P2 | signal_engine.py:209 | atr_percentile @property有写副作用 | 拆成update_atr_history()方法 |
| P2-6 | P2 | main.py:554 | 1R=$200硬编码 | 从paper_config.json动态读取 |
| P3-1 | P2 | auth.py:15 | JWT密钥硬编码默认值 | 启动时强制校验JWT_SECRET环境变量 |
| P3-2 | P3 | main.py:17 | CORS allow_origins=["*"] | 限制为https://arb.zhouyangclaw.com |
| P3-3 | P3 | auth.py:316 | refresh token刷新非原子 | UPDATE...WHERE revoked=0 RETURNING |
| P3-4 | P3 | auth.py:292 | 登录无频率限制 | slowapi或Redis计数器 |
| NEW-1 | P1 | signal_engine.py:664 | 冷启动warmup只有4小时 | 分批加载24小时,加载完再出信号 |
| ID | 优先级 | 文件 | 问题 | 修复方案 |
|---|
| FE-P1-1 | P1 | lib/auth.tsx:113 | 并发401多次refresh竞态 | 单例Promise + _refreshPromise |
| FE-P1-2 | P1 | lib/auth.tsx:127 | 刷新失败AuthContext未同步 | window.dispatchEvent("auth:session-expired") |
| FE-P1-3 | P1 | 所有页面 | catch{}静默吞掉API错误 | 每个组件加error state + 红色提示 |
| FE-P1-4 | P2 | paper/page.tsx:119 | LatestSignals串行4请求 | Promise.allSettled并行 |
| FE-P2-1 | P3 | app/page.tsx:52 | MiniKChart每30秒销毁重建 | 只更新data不重建chart |
| FE-P2-3 | P2 | paper/page.tsx:20 | ControlPanel非admin可见 | 校验isAdmin,非admin隐藏 |
| FE-P2-4 | P1 | paper/page.tsx:181 | WebSocket无断线重连 | 指数退避重连 + 断线提示 |
| FE-P2-5 | P2 | paper/page.tsx:217 | 1R=$200前端硬编码 | 从/api/paper/config读取 |
| FE-P2-6 | P2 | signals/page.tsx:101 | 5秒轮询5分钟数据 | 改为300秒间隔 |
| FE-P2-8 | P3 | paper/signals | 大量any类型 | 定义TypeScript interface |
| FE-P3-1 | P3 | lib/auth.tsx:33 | Token存localStorage | 评估httpOnly cookie方案 |
| FE-P3-3 | P3 | app/page.tsx:144 | Promise.all任一失败全丢 | 改Promise.allSettled |
| # | 信号源 | 层级 | 当前 | V5.2 |
|---|
| 1 | CVD三轨(fast/mid) | 方向层 | ✅ 评分中 | 保持 |
| 2 | P99大单流 | 方向层 | ✅ 评分中 | 保持 |
| 3 | CVD加速度 | 方向层 | ✅ 评分中 | 保持 |
| 4 | 多空比+大户持仓比 | 拥挤层 | ✅ 评分中 | 保持 |
| 5 | OI变化率 | 环境层 | ✅ 评分中 | 保持 |
| 6 | Coinbase Premium | 辅助层 | ✅ 评分中 | 保持 |
| 7 | 资金费率(FR) | 拥挤层 | ⬜ 仅采集 | ✅ 加入评分 |
| 8 | 清算数据 | 确认层 | ⬜ 仅采集 | ✅ 加入评分 |
# 资金费率 → 拥挤层加分
# FR > 0.03% = 多头过度拥挤 → SHORT加分
# FR < -0.03% = 空头过度拥挤 → LONG加分
# |FR| > 0.1% = 极端值 → 反向强信号
funding_rate = self.market_indicators.get("funding_rate")
if funding_rate is not None:
if direction == "LONG" and funding_rate < -0.0003:
fr_score = 5 # 空头拥挤,做多有利
elif direction == "SHORT" and funding_rate > 0.0003:
fr_score = 5 # 多头拥挤,做空有利
elif direction == "LONG" and funding_rate > 0.001:
fr_score = -5 # 多头极度拥挤,做多危险(减分)
elif direction == "SHORT" and funding_rate < -0.001:
fr_score = -5 # 空头极度拥挤,做空危险
else:
fr_score = 0
# 大额清算 → 确认层加分
# 大额空单清算 = 空头被清洗 → SHORT可能结束,LONG加分
# 大额多单清算 = 多头被清洗 → LONG可能结束,SHORT加分
# 5分钟内清算量 > 阈值 = 趋势加速信号
liq_long = self.market_indicators.get("long_liq_usd", 0)
liq_short = self.market_indicators.get("short_liq_usd", 0)
if direction == "LONG" and liq_short > 500000:
liq_score = 5 # 大量空单被清算,趋势确认
elif direction == "SHORT" and liq_long > 500000:
liq_score = 5
else:
liq_score = 0
| 层级 | V5.1权重 | V5.2权重 | 变化 |
|---|
| 方向层 | 45+5 | 40+5 | -5 |
| 拥挤层 | 20 | 25(+FR 5分) | +5 |
| 环境层 | 15 | 15 | 不变 |
| 确认层 | 15 | 20(+清算 5分) | +5 |
| 辅助层 | 5 | 5 | 不变 |
| 总计 | 100+5 | 105+5 | +5 |
注:总分超过100不影响,阈值按绝对分数判断(75/80/85)
| V5.1 | V5.2 | 原因 |
|---|
| 最低开仓分 | 60分 | 75分 | 70-74档位50%胜率,扣手续费亏钱 |
| light档 | 60-74 | 取消 | 数据证明低分信号无价值 |
| standard档 | 75-84 | 75-84 | 保持 |
| heavy档 | 85+ | 85+ | 保持 |
| 参数 | V5.1(方案A) | V5.2候选(方案B) |
|---|
| SL | 2.0 × risk_atr | 3.0 × risk_atr |
| TP1 | 1.5 × risk_atr | 2.0 × risk_atr |
| TP2 | 3.0 × risk_atr | 4.0 × risk_atr |
| BTC手续费占比 | 0.23R | 0.15R |
| TP2净利 | +0.89R | +0.97R |
| SL净亏 | -1.23R | -1.15R |
| 盈亏比 | 0.72 | 0.84 |
| 保本胜率 | 58% | 54% |
一个signal-engine进程,支持多套策略配置并行。
// strategies/v51.json
{
"name": "v51_baseline",
"threshold": 75,
"weights": {
"direction": 45,
"crowding": 20,
"environment": 15,
"confirmation": 15,
"auxiliary": 5
},
"tp_sl": {
"sl_multiplier": 2.0,
"tp1_multiplier": 1.5,
"tp2_multiplier": 3.0
},
"signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"]
}
// strategies/v52.json
{
"name": "v52_8signals",
"threshold": 75,
"weights": {
"direction": 40,
"crowding": 25,
"environment": 15,
"confirmation": 20,
"auxiliary": 5
},
"tp_sl": {
"sl_multiplier": 3.0,
"tp1_multiplier": 2.0,
"tp2_multiplier": 4.0
},
"signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium", "funding_rate", "liquidation"]
}
ALTER TABLE paper_trades ADD COLUMN strategy VARCHAR(32) DEFAULT 'v51_baseline';
signal_engine.py
├── evaluate_signal(state, strategy="v51") → score_A, signal_A
├── evaluate_signal(state, strategy="v52") → score_B, signal_B
│
├── paper_open_trade(strategy="v51", ...) → paper_trades.strategy='v51'
└── paper_open_trade(strategy="v52", ...) → paper_trades.strategy='v52'
- V5.1:$10,000虚拟资金,独立统计
- V5.2:$10,000虚拟资金,独立统计
| 指标 | V5.1 | V5.2 |
|---|
| 总笔数 | ? | ? |
| 胜率 | ? | ? |
| 每笔平均R | ? | ? |
| 最大回撤 | ? | ? |
| 盈亏比 | ? | ? |
| PF | ? | ? |
- 两周(目标每策略100+笔)
- 结束后选表现更好的上实盘
signal-engine启动时只加载4小时数据,P99大单需要24小时窗口。
def main():
states = {sym: SymbolState(sym) for sym in SYMBOLS}
# 分批加载24小时(每批100万条,避免OOM)
for sym, state in states.items():
load_historical_chunked(state, 24 * 3600 * 1000)
logger.info(f"[{sym}] warmup complete")
logger.info("=== 全部币种warmup完成,开始出信号 ===")
# 这之后才开始评估循环
- 4币种24小时约2000-3000万条
- 加载时间1-3分钟
- 启动后信号质量从第一笔就是100%
| Commit | 内容 |
|---|
45bad25 | P0-1: 反向信号绕过冷却 |
45bad25 | P0-2: pnl_r统一(exit-entry)/risk_distance |
45bad25 | P1-1: 分区月份+UTC修复 |
2f9dce4 | TP/SL改限价单模式(趋近实盘) |
4b841bc | 前端浮盈半仓计算 |
d351949 | 历史pnl_r修正脚本 |
8b73500 | Auth从SQLite迁移PG |
9528d69 | recent_large_trades去重 |
| 阶段 | 时间 | 内容 | 产出 |
|---|
| Phase 1 | Day 1-2 | Bug修复(P1全部 + P2重要的) | 代码+测试 |
| Phase 2 | Day 2-3 | FR+清算评分逻辑 | signal_engine.py |
| Phase 3 | Day 3-4 | 策略配置化框架 | strategies/*.json + 代码 |
| Phase 4 | Day 4-5 | AB测试机制 | paper_trades.strategy字段 |
| Phase 5 | Day 5 | 24h warmup | signal_engine.py |
| Phase 6 | Day 6-7 | 前端Bug修复 + 策略对比页面 | 前端代码 |
| 部署 | Day 7 | 全部写好测好,一次性部署 | 减少重启 |
- aggTrades:7039万条(BTC 23天 + ETH 3天 + XRP/SOL 3天)
- signal_indicators:2.7万+条
- paper_trades:181笔
- market_indicators:5400+条
- liquidations:3600+条
- AB测试:每策略至少100笔 → 两周
- 权重优化(Phase 1统计分析):300+笔
- 回归分析:500+笔
- ML优化:1000+笔
- 手续费是盈亏关键 — BTC手续费0.23R/笔,必须控制交易频率
- 样本量不足 — 当前181笔,按分数段分析可能不稳定
- 过拟合风险 — 两周一次微调,每次±10-20%,不做大改
- AB测试期间 — 两套策略共享最大持仓4个,需要分配
- 24h warmup — 启动时间变长,需要告知运维(小周)
- 策略配置化 — 改动signal_engine核心代码,必须充分测试
本文档为V5.2开发的完整参考,开发过程中持续更新。
商业机密:策略细节不对外暴露。