NOTE · 技术笔记

walk-forward 重跑差点毁掉一批证据:一个漏掉的预处理步骤

这篇文章讲我系统里一次真实的"跑批事故":walk-forward 验证的代码改了重跑,结果和基准差出一大截(219 笔 vs 272 笔成交)——差点把一批实验结论全部污染。整个过程里有三个可复制的动作:先对账、再冻结、后硬校验。每步都配真实数字和代码。

先报真实数字:train 与 test 的差距有多大

同一个策略、同一段行情、同一份数据(2026-02-02 ~ 2026-09-30,161 个交易日):

  • 样本内(train,2026-02-02 ~ 05-29,用于校准):区间收益 +103.95%,最大回撤 −12.03%,139 笔,胜率 52.5%;
  • 样本外(test,2026-06-01 ~ 09-30,校准后冻结跑):区间收益 +15.73%,最大回撤 −25.93%,133 笔,胜率 46.6%。

样本内收益是样本外的六倍多、回撤只有一半。如果这个差距不动摇你"拿样本内报数"的冲动,那后面的事故也吓不到你——真正的问题在于:test 数字一旦被污染,你自己都不会知道。

事故现场:重跑之后,数字对不上

背景:我给执行引擎做了一次内核升级(profile 驱动,v2)。升级后按纪律要求"重跑 walk-forward 并与旧基准逐值比对"。第一次比对结果:

  • 基准(旧链):272 笔成交、区间收益 +133.81%、回撤 −25.93%;
  • 重跑(v2):219 笔成交,收益曲线形态都对不上。

53 笔的缺口。第一反应是"v2 内核有 bug"。但止损点在这里:我没有直接去改 v2 内核,而是先把两边产物逐日拉齐对比——发现差异从第一天就出现,且集中在"体制分支"上:本该走"弱市分支"的日子走了"强市分支"。

顺着分支查下去,根因浮出水面:重跑脚本漏调了一个预处理步骤(体制图预计算 prepare_regime)。体制图不预计算,分支判定就退化为常量 → 所有日期的强弱判断全错 → 选出的候选自然对不上(219 vs 272)。

这里最值得记的不是 bug 本身,而是:如果没有"重跑必须与基准逐值比对"这条规矩,219 笔的版本会安安静静地成为新一版"结论"——毕竟它不报错、不崩溃、数字看起来也合理。

修复:把"对账"升级为硬校验(可复现)

修掉漏调之后,我做了三件事把这类事故钉死:

1. 对账阈值写进代码:重跑完成自动与基准比对,笔数全等 + 收益/回撤偏差 ≤ 0.01%(写死,不可配置):

# 重跑产物 vs 基准:不一致即拒绝入库(而不是写 warning)
assert new["trade_count"] == base["trade_count"]
assert abs(new["return_pct"] - base["return_pct"]) <= 0.01
assert abs(new["max_drawdown_pct"] - base["max_drawdown_pct"]) <= 0.01

2. 失败即拒绝入链:校验不通过,产物不入证据链(写不进实验台账,也就不可能被后续报告引用)——"错了但要先发出去再说"在结构上不可能发生。

3. 内核版本分账:重跑产物标注所用内核版本(这里是 profile-exec-v2.0.0),与旧基准的版本并列可查——"哪个数字出自哪个引擎"永远可回答。

修复后的最终对账:+133.805737% / −25.930672% / 272 笔,逐值精确到 1e-6——比"看起来一样"强得多,因为它是可断言的。

三个可复制的动作

无论你用什么回测框架,这三件事值得照抄:

1. 先对账,再归因:数字对不上时,先拉齐两边产物找第一个分叉点,不要直接改代码——这次如果先改了内核,大概率引入第二个 bug,而且再也不知道原来那个对不对。

2. 重跑必须有基准:任何"代码变更后重跑",都要有旧产物作为基准硬校验(笔数全等是最便宜的第一道)。没有基准的重跑 = 没有验收。

3. 校验不通过就不许流动:产物要么带全部校验通过标记入库,要么被拒绝——不存在"带瑕疵流转,后面注意点"。

样本外的纪律清单(每天用)

1. 切数据一刀两段:train 在前、test 在后,不重叠;

2. 参数只在 train 上定,test 跑完之前不改任何东西;

3. 想调参?作废本次验证,从 test 之后的新数据重开一段;

4. 三段数字(train / test / 全期)并列汇报,回撤与收益一起看;

5. 试验基数 N 如实计数,失败的实验也记录在案——防止"多试几次凑出一个好结果"。


本站内容为个人研究记录与方法分享,模拟,不构成投资建议;据此操作风险自负。


Joyous 的技术笔记 · 沪ICP备2026053770号


隐私最小化:不设 cookie、无表单、无第三方脚本;访问统计仅取服务端日志(≤30 天,仅聚合),不导出 IP。