← 返回目录
p5.5动手⏱ 约 17 分钟

滚动前推验证:每年重新优化,不如一开始不动

12 折 walk-forward 实测——每年重选参数年化 -0.45%,固定 5/20 不动 0.13%,买入持有 3.84%

🔎 最后验证 2026-07📚 来源:12 折滚动前推(训练 3 年/测试 1 年)于 2026-07-31 在 ECS 实测,输出见 code/outputs/stdout.txt🧰 qlab.engine(本课自研)、pandas 3.0.5、Python 3.12.13
为什么学这个

p5.4 只做了一次样本外检验。一次不够——一次切分只有一个结果,而你还是会忍不住调到它好看为止。

所以把它做成流水线:训练 3 年 → 选参 → 参数在测试窗内完全冻结 → 测试 1 年 → 整体前移 1 年 → 重复。

12 折,测试窗合计 3024 个交易日。把 12 段测试窗首尾相接:

A  滚动前推(每期选单点最优)   年化 -0.45%   最大回撤 -49.5%   夏普 0.026
B  滚动前推(每期选平原最优)   年化 -0.06%   最大回撤 -36.1%   夏普 0.056
C  固定参数 5/20,从头到尾不动  年化  0.13%   最大回撤 -41.7%   夏普 0.075
D  买入持有                  年化  3.84%   最大回撤 -46.7%   夏普 0.283

每年勤勤恳恳重新优化参数的 A,跑输了从头到尾没动过的 C。

而 12 折里,每折选出来的参数有 11 个不同取值——它每年都在跳。

💡 打个比方

你每年根据去年的天气,重新决定今年种什么。

邻居十年不换,一直种同一种作物。

十年下来,他收成比你好。

不是因为他聪明,是因为 "去年的天气"里,能指导今年的信息本来就很少——而你每年都当它是全部信息。

一、切分方式

标的沪深300 指数,4023 个交易日
训练窗3 年(756 个交易日)
测试窗1 年(252 个交易日),参数在窗内完全冻结
前移步长1 年
折数12
覆盖2013-02-19 ~ 2025-07-28
参数网格fast 220(步长 2)× slow 20160(步长 20)= 80 组

两种选参方式对照跑:单点最优(训练窗内年化最高)与平原最优(p5.4 定义的平原分最高)。

测试窗回测时带上最长均线所需的历史做预热,但只统计测试窗内的收益——否则前 160 天信号是空的,等于白送一段空仓。

二、逐折结果

测试窗起单点参数训练内%测试外%平原参数训练内%测试外%固定5/20%买入持有%
12013-02-196/807.20-18.4314/1004.95-19.58-12.51-19.13
22014-03-074/80-0.3862.904/20-5.5742.2047.9077.78
32015-03-192/8017.011.984/14016.27-10.69-8.18-17.51
42016-03-296/6019.93-1.276/6019.93-1.277.1512.23
52017-04-1220/4025.772.3712/16019.535.756.757.19
62018-04-2318/1605.245.3818/1605.245.38-2.86-1.22
72019-05-0814/808.35-15.3110/807.79-11.12-11.847.91
82020-05-202/203.926.9520/601.7920.826.9935.91
92021-06-0216/1608.76-10.506/1007.66-10.55-8.82-19.73
102022-06-1716/1604.41-7.6418/1602.40-7.04-3.43-11.35
112023-06-3016/404.17-5.0710/20-0.711.00-9.93-9.67
122024-07-1518/40-0.20-7.1014/160-4.12-1.463.3719.06

第 4 折和第 6 折,两种选参方式选出了同一组参数——这种巧合一年发生一次左右,不是常态。

三、三个数,一起读

① 训练内 → 测试外的衰减

选参方式训练窗内平均测试窗外平均衰减
单点最优8.68%1.19%-7.50 pp
平原最优6.26%1.12%-5.14 pp

平原选参的衰减更小(-5.14 vs -7.50)——p5.4 那个启发式在 12 折上重复出来了,不是单次巧合。

② 但拼起来是负的

上面写"测试窗外平均 1.19%",而拼接后的年化是 -0.45%。这不矛盾:

前者是 12 个分年收益率的算术平均,后者是复利。 第 2 折那个 +62.90% 把算术平均拉高了,但复利不吃这一套——p3.1 讲过的算术均值与几何均值之差,在这里正面撞上来了。

凡是用"平均每年赚 X%"描述一段实际持有经历的,先问一句:是算术平均还是复利? 本例两者差了 1.64 个百分点,方向还是相反的。

③ 谁也没跑赢什么都不做

方案年化%最大回撤%夏普
A 滚动前推(单点最优)-0.45-49.50.026
B 滚动前推(平原最优)-0.06-36.10.056
C 固定参数 5/200.13-41.70.075
D 买入持有3.84-46.70.283
⚠️ 避坑

A 跑输 C:每年重新优化,不如一开始定死不动。

这句话值得停下来想一想,因为它推翻的是一个非常自然的直觉:"市场在变,参数当然要跟着调。"

实测说的是:你以为你在跟踪市场变化,实际上你在跟踪噪声。

证据在参数序列里:

单点最优每折选出的参数:
(6,80) (4,80) (2,80) (6,60) (20,40) (18,160) (14,80) (2,20) (16,160) (16,160) (16,40) (18,40)
→ 12 折里 11 个不同取值

如果训练窗真的在测量某种稳定的市场结构,参数不该每年都跳到完全不同的地方。

平原选参也是 11/12——它减小了衰减和回撤,但没有让参数变稳定

🔧 动手做:诚实验证之后,策略还剩多少?(6 分钟)

滚动前推是这门课最接近"你当年真这么做"的验证:训练窗选参 → 测试窗冻结 → 前移 → 重复。跑 python walk_forward.py

你会看到(实测):滚动前推(平原选参)拼起来的净值,年化 -0.06%;而同期买入持有 3.84%。平原参数训练内 6.26% → 测试外只剩 1.12%。

想明白:一旦不许偷看未来、参数一进测试窗就冻结,这条双均线策略输给了什么都不做。这不是策略没调好——是大多数看着能赚的策略,经不起这一关。这门课到这里没给你一个赚钱的策略,给的是这台照妖镜。

四、那滚动前推还有什么用?

它没能让策略变好。但它让你提前知道了这件事。

这就是它的全部价值:

一次样本外检验滚动前推
样本外结果个数1 个12 个
能否看出参数稳定性不能能(11/12 个不同取值)
能否算样本外的回撤路径只有一段12 段拼接,-49.5%
被"调到好看为止"的空间小(每调一次要重跑全部 12 折)

它是一个证伪工具,不是一个提升工具。

而它在本例里给出的答案是明确的:这个策略族在这段样本上,经不起这个检验。

需要说清楚的边界:本节只检验了双均线 + 网格选参这一种组合,在沪深300 指数这一个标的上。 "滚动前推之后仍然有效"的策略是存在的——但你必须真的跑一遍才知道自己手上这个是不是。

五、报告规范

一份合格的滚动前推报告至少包含:

  1. 切分参数:训练窗、测试窗、步长、折数(缺一项就无法复现)
  2. 逐折表,不只有汇总——汇总会藏住第 2 折那种一枝独秀
  3. 拼接净值的复利年化 + 最大回撤,而不是分年收益的算术平均
  4. 每折选出的参数列表——参数漂移是最诚实的过拟合指标
  5. 至少一个"什么都不做"的对照(固定参数 / 买入持有)
❓ 测验
某策略的滚动前推报告写:「10 折样本外,平均年化 6.2%」。你最该先要哪个数?
✏️ 填空
12 折滚动前推里,单点最优选参共选出 ___ 个不同的参数组合——参数漂移是最诚实的过拟合指标。

📌 免责:本课为技术教学,标的为沪深300 指数,所有回测为历史模拟,不构成投资建议。

✅ 小结

三件事:

  1. 滚动前推 = 12 个样本外结果,不是 1 个。训练 3 年 → 选参 → 冻结 → 测试 1 年 → 前移;实测 12 折,测试窗合计 3024 个交易日
  2. 每年重新优化(-0.45%)跑输从头不动(0.13%),两者都跑输买入持有(3.84%)。参数序列 12 折里有 11 个不同取值——你以为在跟踪市场变化,实际在跟踪噪声
  3. 平原选参确实更稳:衰减 -5.14 pp vs -7.50 pp,最大回撤 -36.1% vs -49.5%。p5.4 的启发式在 12 折上重复出来了——但它没有让参数变稳定(同样 11/12)

一个必须记住的口径差:测试窗外算术平均 +1.19%,拼接复利 -0.45%,差 1.64 个百分点且方向相反(p3.1)。凡是用"平均每年赚 X%"描述实际持有经历的,先问是算术还是复利。

滚动前推是证伪工具,不是提升工具。它没让策略变好,但它让你提前知道了。

下一节继续这条线:即使拿到了一条样本外净值曲线,它也只是一次实现。蒙特卡洛与自助法回答的是——如果历史重演一次,结果的范围有多宽?

下一节 → 蒙特卡洛与自助法:给结论配一个置信区间
🔎 来源与核验· 4 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「12 折滚动前推(沪深300,训练 3 年/测试 1 年/步长 1 年,网格 fast 2~20 步长 2 × slow 20~160 步长 20)拼接结果:单点最优年化 -0.45%/回撤 -49.5%/夏普 0.026;平原最优 -0.06%/-36.1%/0.056;固定 5/20 0.13%/-41.7%/0.075;买入持有 3.84%/-46.7%/0.283」
📚 本节 code/walk_forward.py,2026-07-31 于 ECS 实测,输出见 code/outputs/stdout.txt✓ 已核验 2026-07
「训练内到测试外的衰减:单点最优 8.68% → 1.19%(-7.50 pp);平原最优 6.26% → 1.12%(-5.14 pp);跑赢买入持有的折数占比 50% 与 42%」
📚 同上实测✓ 已核验 2026-07
「参数漂移:单点最优 12 折选出 11 个不同参数组合,平原最优同样 11 个」
📚 同上实测✓ 已核验 2026-07
「口径差:测试窗外分年收益算术平均 +1.19%,而 12 段测试窗拼接后的复利年化为 -0.45%」
📚 同上实测(第 2 折测试外 +62.90% 是算术均值被拉高的主因)✓ 已核验 2026-07
结论仅针对本样本、本标的、本参数网格与双均线族;不外推到其他策略族或标的。
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p5.6
蒙特卡洛与自助法:给结论配一个区间
继续读下一节 →