9.8 Walk-forward 滚动训练测试入门
课前导读
本节目标
学完能解决什么困惑:为什么一次完整回测不够,还要把历史切成多轮“先训练、再考试”。
适合谁学:已经理解回测指标、成本、参数扫描,并希望降低过拟合风险的读者。
难度:中级。
用时:约 45 分钟。
上一节我们知道,参数不能只看一个最高点。
但即使找到了看起来稳定的参数平台,也还不能马上放心。
因为整段历史回测可能仍然把未来的信息混进了过去的选择。
Walk-forward,就是把历史拆成一段段时间。
每一轮都先用过去资料练习,再用随后一段资料考试。
它的核心纪律是:不能用未来调过去。
知识正文
1. 类比:先练习再考试,多轮滚动
想象一个学生准备考试。
他不能先看完整套期末答案,再回头说自己“平时训练很有效”。
合理做法是:先用前几章内容练习,然后做一套没见过的小测。
做完后,再把新学到的内容加入训练范围,继续准备下一次小测。
Walk-forward 的思路就是这样。
每一轮都有明确的训练期和测试期。
训练期用来选择参数、检查规则和确定策略版本。
测试期用来模拟“策略发布后”的真实表现。
如果策略在多轮测试期都没有完全失控,我们才更有理由相信它不是只会背历史答案。
关键直觉
回测不是让策略参加一场已经知道答案的大考,而是让它经历多轮没见过的小考。
2. 术语:训练期、测试期、滚动窗口
训练期,就是允许你研究、调参、筛选规则的历史区间。
在训练期里,你可以做参数扫描,可以比较指标,也可以决定某个策略版本是否保留。
测试期,就是训练完成后,不再改规则,直接把策略放到下一段历史上检查。
测试期越像未来,越能暴露策略离开训练样本后的真实压力。
Walk-forward,常翻译为滚动前推验证。
它不是一次切分,而是多次重复“训练、测试、向前移动”的流程。
滚动窗口,指每次训练只使用最近一段固定长度的历史。
例如用过去 2 年训练,用接下来 6 个月测试,然后整体向前滚动 6 个月。
扩展窗口,指训练期从早期开始,随着时间不断变长。
例如先用 2018 到 2019 年训练,测试 2020 年上半年。
下一轮用 2018 到 2020 年上半年训练,测试 2020 年下半年。
两种方法没有绝对高低。
滚动窗口更强调近期市场状态。
扩展窗口更强调积累更多样本。
新手先理解纪律:测试期不能参与训练期的选择。
3. 为什么普通回测还不够
一段 2018 到 2024 年的完整回测,看起来很完整。
但如果你用这整段历史选出了参数,再说策略在 2018 到 2024 年表现很好,就存在问题。
因为 2024 年的信息也参与了你对 2018 年规则的选择。
这在现实中不可能发生。
站在 2018 年,你不知道 2024 年会发生什么。
站在 2020 年,你也不知道后面几年某个风格会不会突然失效。
Walk-forward 把这件事强制写进流程。
每一轮都要求你只用当时以前的数据做决定。
然后用当时之后的一小段数据检验。
4. A 股案例:均线参数的滚动验证
假设我们研究一个简单的 A 股趋势过滤策略。
股票池是流动性较好的沪深主板样本。
规则是:收盘价站上 N 日均线后观察买入,跌破 N 日均线后卖出。
交易成本包含佣金、印花税、过户费和滑点。
我们准备在训练期里从 15、20、25、30 四个均线参数里选择一个。
然后在测试期里固定使用这个参数。
| 轮次 | 训练期 | 测试期 | 训练期选出的参数 | 测试期表现简评 |
|---|---|---|---|---|
| 1 | 2018-2019 | 2020 上半年 | 20 | 收益可接受,回撤较大 |
| 2 | 2018-2020 上半年 | 2020 下半年 | 25 | 收益较好,交易次数适中 |
| 3 | 2018-2020 | 2021 上半年 | 20 | 收益一般,但未严重失控 |
| 4 | 2019-2021 上半年 | 2021 下半年 | 30 | 回撤扩大,需要记录原因 |
| 5 | 2020-2021 | 2022 上半年 | 25 | 测试期表现偏弱 |
这张表比单次回测更有教学价值。
它告诉你策略不是每一轮都赚钱。
它也告诉你参数会随着训练期变化。
更重要的是,它把“当时能知道什么”和“后来发生什么”分开了。
如果测试期多轮都很差,说明策略可能只在训练数据里漂亮。
如果测试期有赚有亏,但回撤、交易次数和失效原因都能解释,策略才有继续研究的价值。
5. Walk-forward 的标准流程
先选定总样本区间。
再决定训练期长度和测试期长度。
接着在每一轮训练期里做参数扫描。
然后把选出的参数锁定,放进下一段测试期。
最后把所有测试期结果拼接起来,形成一条“样本外资金曲线”。
graph TD
A["准备历史数据"] --> B["划分训练期与测试期"]
B --> C["训练期内选择参数"]
C --> D["锁定规则"]
D --> E["测试期检验"]
E --> F["窗口向前滚动"]
F --> C
E --> G["汇总所有测试期结果"]这里有两个词很重要。
第一个是“锁定”。
进入测试期后,不能看到结果不好就回头改参数。
第二个是“汇总”。
不要只挑一轮最漂亮的测试期展示。
要把所有测试期结果放在同一张表里。
6. 训练期和测试期该多长
没有万能长度。
长度要和策略节奏匹配。
如果策略一周就交易很多次,训练期可以相对短一些,但要注意成本和噪声。
如果策略基于月线或季度财务数据,训练期太短就可能没有足够样本。
对日线 A 股新手策略,可以先尝试:
| 策略节奏 | 训练期示例 | 测试期示例 | 直觉解释 |
|---|---|---|---|
| 较短线 | 1 年 | 3 个月 | 快速检查近期适应性 |
| 波段 | 2 年 | 6 个月 | 兼顾样本和市场变化 |
| 中期趋势 | 3 年 | 1 年 | 更重视跨环境表现 |
这些只是学习起点。
真正写报告时,要说明为什么这样切。
不能因为某种切法结果好,就事后宣布它最合理。
7. 不能用未来调过去
这是 Walk-forward 的红线。
假设你用 2022 年的测试结果发现 25 日均线不好。
你不能回头把 2021 年训练期的选择改成 20 日。
因为站在 2021 年末,你还不知道 2022 年会怎样。
同样,你不能用完整样本里表现最好的股票池,回头用于早期测试。
也不能用后来才披露的财务数据,提前放进训练期。
在 A 股里,公告、财报、停复牌、成份股调整,都有明确可见时间。
回测流程必须尊重这些时间。
风险提醒
只要测试期结果参与了规则修改,这一轮测试就不再是真正的测试,而变成了新的训练。
8. 一个短小的伪代码示例
下面代码不追求完整运行,只展示 Walk-forward 的思路。
windows = [
("2018-01-01", "2019-12-31", "2020-01-01", "2020-06-30"),
("2018-07-01", "2020-06-30", "2020-07-01", "2020-12-31"),
("2019-01-01", "2020-12-31", "2021-01-01", "2021-06-30"),
]
test_results = []
for train_start, train_end, test_start, test_end in windows:
train_data = data.loc[train_start:train_end]
test_data = data.loc[test_start:test_end]
best_param = choose_param_on_train(train_data, candidates=[15, 20, 25, 30])
result = run_backtest(test_data, ma_days=best_param, cost_model=cost_model)
test_results.append(result)读这段代码时,请抓住三点。
第一,训练数据和测试数据按时间切开。
第二,参数只在训练数据里选择。
第三,测试数据只负责检验,不负责反向修改本轮规则。
9. 专业老师视角:Walk-forward 检查的是学习流程
Walk-forward 不是神秘算法。
它检查的是你的研究流程是否诚实。
如果你每一轮都能清楚说明训练期做了什么,测试期发生了什么,策略为什么通过或失败,说明你的研究开始可复查。
如果你只展示最终拼起来的漂亮曲线,却不展示每轮选择过程,就少了最关键的证据。
老师看 Walk-forward 报告,最关心三件事。
第一,切分是否提前规定。
第二,测试期是否独立。
第三,失败轮次是否被诚实记录。
10. 投资者视角:测试期亏损不是自动否定
很多新手看到某一轮测试期亏损,就立刻认为策略没用。
这也太急。
任何策略都有不适合的市场。
关键是亏损是否在可承受范围内。
最大回撤是否突破规则边界。
交易次数是否异常增加。
亏损是否集中在涨跌停、停牌或流动性不足的环境。
如果亏损符合策略已知风险,说明它只是经历了逆风。
如果亏损来自规则没有覆盖的漏洞,才需要暂停研究或重写规则。
11. 量化开发视角:每一轮都要可追踪
Walk-forward 很容易因为文件混乱而失真。
建议每轮记录一个表。
| 字段 | 需要记录的内容 |
|---|---|
| 轮次编号 | 例如 WF-01、WF-02 |
| 训练期 | 起止日期 |
| 测试期 | 起止日期 |
| 候选参数 | 本轮允许选择哪些参数 |
| 选中参数 | 训练期最终锁定的参数 |
| 成本假设 | 佣金、印花税、滑点 |
| 测试期指标 | 收益、回撤、交易次数 |
| 备注 | 异常行情、停牌、涨跌停等 |
不要把每轮文件随手命名为 final、final2、final_new。
量化研究最怕的不是结果不好,而是结果好却没人能复现。
12. 常见失败信号
第一,训练期很好,测试期连续很差。
这通常说明训练期参数把历史细节学得太死。
第二,参数每一轮跳来跳去,且没有投资解释。
这说明策略可能没有稳定结构。
第三,测试期交易次数突然暴增。
这可能是行情噪声、成本估计不足或规则过于敏感。
第四,只有某一轮测试期特别好,整体靠它撑起结果。
这说明收益可能高度依赖单一市场环境。
这些信号都不是“立刻放弃”的命令。
它们是要求你回到规则和数据,重新检查原因。
交互实验室
本节的互动练习,是亲手设计一个 Walk-forward 切分表。
不用真实外部数据。
只要把时间逻辑写清楚,就已经完成了关键训练。
练习 A:设计滚动窗口
假设你有 2018 年到 2024 年的日线数据。
策略节奏是中期趋势。
请先采用“2 年训练 + 6 个月测试”的方案。
| 轮次 | 训练期 | 测试期 | 本轮是否能看见测试期数据 |
|---|---|---|---|
| 1 | 2018-2019 | 2020 上半年 | 不能 |
| 2 | 2018 下半年-2020 上半年 | 2020 下半年 | 不能 |
| 3 | 2019-2020 | 2021 上半年 | 不能 |
| 4 | 2019 下半年-2021 上半年 | 2021 下半年 | 不能 |
请继续补完后续轮次。
补完后检查:每一轮测试期是否都在训练期之后?
练习 B:判断是否偷看未来
请判断下面做法是否合格。
| 做法 | 是否合格 | 原因 |
|---|---|---|
| 用 2018-2019 训练,测试 2020 上半年 | 合格 | 时间顺序正确 |
| 看完 2020 测试结果后,回头重选 2018-2019 参数 | 不合格 | 测试期参与了训练 |
| 用 2021 年才披露的财务数据判断 2020 年买点 | 不合格 | 数据当时不可见 |
| 每轮都记录选中参数和失败原因 | 合格 | 便于复查 |
练习 C:写一段 Walk-forward 结论
请套用下面模板。
本策略共完成 ______ 轮 Walk-forward。训练期长度为 ______,测试期长度为 ______。测试期中有 ______ 轮收益为正,最大回撤最深的一轮出现在 ______。当前结论是 ______,下一步需要检查 ______。
示例答案:
本策略共完成 8 轮 Walk-forward。训练期长度为 2 年,测试期长度为 6 个月。测试期中有 5 轮收益为正,最大回撤最深的一轮出现在 2022 上半年。当前结论是策略具备一定趋势环境适应性,但在震荡下跌阶段压力较大,下一步需要检查参数平台和成本敏感性。
核心复盘
知识清单:
[ ] 我知道 Walk-forward 是多轮“训练期选择、测试期检验”的流程。
[ ] 我能区分训练期、测试期、滚动窗口和扩展窗口。
[ ] 我知道测试期不能反向参与本轮参数选择。
[ ] 我会把每轮选中的参数、成本假设和测试期指标记录下来。
[ ] 我理解测试期亏损不一定否定策略,但必须解释风险来源。
[ ] 我知道所有测试期结果都要展示,不能只挑好看的轮次。
金句总结
Walk-forward 的价值,不是让曲线更漂亮,而是逼策略在没见过的时间里接受考试。