踩坑复盘
回测实盘路径分叉
回测、模拟盘、实盘的信号 / 风控 / 因子筛选存在三套独立实现,导致结果不可复现。#31 事故档案,One Codebase 不变量的直接驱动。
层级 全局(回测 ↔ 执行) 严重度 致命 关联 #31 · commit 226fef2 · engine.py signal_generator.py run_daily.py
每条路径单独验证均无问题,但三条路径合在一起时,回测与实盘产生系统性不一致。
现象
同一个策略,回测净值漂亮,模拟盘却跑不出来——而且每次想复现都对不上。换个入口跑,信号、持仓、绩效全都不一样。系统对外宣称是一套策略,内部其实是三套。
定位
把回测、模拟盘、实盘三条路径并排摊开,发现它们各自实现了一份:
- 信号生成——截面排序、Top N 选择的代码各写了一遍;
- 风控——单票上限 / 总仓上限 / 止损的判定逻辑不一致;
- 因子筛选——IC/ICIR 的口径与去冗余规则有出入。
三份实现随各自迭代逐渐漂移,一个 shift 或一个边界条件的差异即可导致截然不同的结果。
根因
违反了系统的第二条不变量——One Codebase。只要产生交易决策的逻辑存在第二份拷贝,回测就不再能有效预测实盘表现。「所见即所得」原则一旦被破坏,所有回测指标均丧失参考价值。
修复
强制收敛到唯一真相源:
- 回测与实盘都调用同一个
SignalGenerator产生目标持仓; - 同一个
RiskManager施加风控; - 回测
_shift_signals在截面维度对每个 symbol 做shift(1),保证 T 日只用 T-1 信息——实盘则天然在收盘后才跑,时间隔离对齐。
此后「回测 → 模拟 → 实盘」是同一段代码喂不同数据,而不是三段代码假装一致。
教训:回测的正确做法是让生产代码处理历史数据,而非另写一套模拟器。每多一份决策逻辑的拷贝,就多一个回测与实盘分叉的风险点。可复现性需要通过架构约束来保证,而非仅靠测试验证。
关联
- 手册:4. 截面回测引擎 · 5. 交易执行层 ADS
- 直接后果:止损形同虚设——分叉路径里那份没人测的风控。