如何避免策略过拟合

为什么漂亮的回测可能什么都不代表——以及如何分辨真正的优势和曲线拟合的假象。

最后更新 2026-09
要点总结
  • 过拟合是策略把历史的噪声记下来,不是学到规则:回测看起来出色,实盘却和随机没有差别。
  • 扫 100 组参数,就算策略完全没有优势,在 5% 的显著性水平下纯靠概率也会有大约 5 组跑出正的 Sharpe。
  • 不要挑峰值,要挑高原:Blave Agent 的 find_plateau 取 3×3 邻域平均 Sharpe 最高的格子,不是单点最大值。
  • 扫描范围从指标自己的分布长出来:取 p5 与 p95 当两端、算术中点当分界,入场阈值扫上半段、出场阈值扫下半段。
  • 五个过拟合信号:孤立峰值、样本外大幅下滑、交易笔数不到 30–50 笔、绩效只来自单一市场环境、为了「修掉」亏损期而加参数。

你扫完参数,挑了 Sharpe 最高的那一组,回测曲线漂亮得不像话。真的跑起来之后,它的表现跟随机没有差别。中间发生的事有个名字。

什么是过拟合

当一个策略的参数被精确调整到能够完美符合历史数据时,它实际上是在记忆过去,而不是学习可以泛化的规则。回测看起来出色,实盘却表现很差——甚至和随机没有差别。

过拟合通常不是有意为之的。每当你做了以下事情,它就自然发生:

  • 扫描大量参数组合,然后挑 Sharpe 最高的那个
  • 看到回测结果后修改策略,再用同一段数据重新测试
  • 针对图表上看到的亏损区间,特意加入条件来「避开」它
  • 回测期间过短,某个参数组合纯粹靠运气超越其他组合
核心问题: 每一段回测数据都同时包含真实的规律和噪声。一个足够复杂的策略永远能够拟合噪声。但噪声不会在未来重演。

参数扫描的陷阱

假设你扫描了 100 个参数组合。就算你的策略完全没有优势,在 5% 的显著性水平下,单纯靠概率就有大约 5 个组合会产生正的 Sharpe。如果你挑出最好的那个并汇报为策略绩效,你是在把噪声当信号。

这个问题随着参数数量增加而恶化。两个参数做 20×20 的网格有 400 个组合;四个参数做 10×10×10×10 有 10,000 个。每增加一个维度,撞上幸运峰值的概率就翻倍。

经验法则: 策略每多一个自由参数,回测需要的交易笔数就要多大约 10 倍,结果才具有统计意义。两个参数已经需要相当多的交易。四个参数所需的回测长度通常难以达到。

峰值 vs 高原

避免过拟合最重要的实务技巧是:不要挑峰值。

看一张参数扫描的 Sharpe 热力图。绝对最佳的格子可能显示 Sharpe 1.8。往左移一格:0.4。往上移一格:0.3。这个峰值很脆弱——它之所以存在,是因为那个特定的参数组合刚好符合了训练数据里几个特定的市场事件。

正确的做法是找高原(Plateau):在许多邻近参数组合上 Sharpe 都持续偏高的区域。如果一个 3×3 邻域的 Sharpe 平均都在 1.2 左右,这才是稳健的信号——优势存在于一个参数范围之内,不只在单一的精确值。

# Blave Agent 的 find_plateau:
# 选的是邻域(3×3 窗口)平均 Sharpe 最高的格子,
# 而不是单点最大值。
best_idx = argmax(neighborhood_average_sharpe)

这就是为什么参数扫描脚本——每支策略自己文件夹里的 scan.py——用 find_plateau 而不是直接取最佳 Sharpe。位于平滑高原上的参数组合,远比位于孤立峰值的组合更可能泛化到未来。

样本内 vs 样本外

衡量策略真实绩效的唯一有效方式,是在它从未「见过」的数据上测试。如果你用 2020–2023 的数据优化参数,然后在 2024 的数据上评估,2024 的绩效就是真正的样本外估计。

如果你用 2020–2024 的数据优化,再汇报 2020–2024 的绩效:那是样本内。即使你并没有刻意过拟合,优化过程本身已经隐性地使用了 2020–2024 的结果来选择参数。汇报的 Sharpe 包含了后见之明的好处。

方法有效?说明
用全部历史数据优化,汇报全部历史绩效✗样本内——必然过拟合
用前 70% 优化,汇报后 30% 绩效✓简单训练/测试分割——有效但有限
滚动前推:用滚动窗口优化,汇报下一段✓✓最真实——和实盘的方式相同

从指标分布决定扫描范围

另一个常见的过拟合陷阱是把扫描范围设得太广,包含在历史上几乎从未出现过的极端参数值,让某个异常市场期的偶然事件拉高那个参数的 Sharpe。

Blave Agent 对阈值型策略采用不同的做法:从指标本身的分布来决定扫描范围。先取指标历史分布的 p5 与 p95 当两端,再用两者的算术中点(不是中位数)当分界;入场阈值的候选值落在上半段(中点到 p95),出场阈值的候选值落在下半段(p5 到中点)。这让两个阈值都落在有足够历史数据的范围内,不会把历史上只出现过两次的极端值当作候选。

你的策略可能过拟合的五个信号

1

Sharpe 热力图上的孤立峰值。 最佳参数组合的邻近格子 Sharpe 大幅下跌。往任何方向移动一格就掉 0.5 以上。优势不稳健,只是局部的。

2

回测 Sharpe 在样本外大幅下滑。 训练期 Sharpe 2.0,测试期 Sharpe 0.2。落差越大,策略越过拟合。

3

交易笔数太少。 三年回测只有 12 笔交易,几乎没有统计效力。几笔幸运的交易就能纯靠概率制造 Sharpe 2.0。需要至少 30–50 笔完整交易,回测数字才有意义。

4

绩效完全集中在单一市场环境。 如果回测绩效完全来自某一段牛市或熊市,其他时期都是横盘或亏损,策略可能只是学会了那个特定的市场环境,而不是一个普遍的规则。

5

为了「修掉」亏损期而加入参数。 在图表上看到某段回撤后,特意加入一个条件来避开它,然后在同样的数据上重测——这是最典型的过拟合操作,因为未来不会再有一模一样的亏损期等你去避开。

几个参数算太多

没有通用的上限,但有一个实用原则:每一个额外的自由参数都应该有先验的理由存在于策略逻辑中。不是因为加了它能让数据拟合得更好,而是因为它代表了你所利用的市场机制的一个真实维度。

一个有两个参数、代表有意义概念的策略(例如快速信号窗口和慢速确认窗口),比一个有六个参数、其中几个是为了「修掉」图表上特定亏损期而加入的策略,更有说服力。

这些做法救不了什么

  • 高原选择法处理的是「你挑到的那一格是不是运气」,它不处理「这个策略到底有没有优势」。用分布决定扫描范围也一样——它只是不让你去测历史上几乎没发生过的极端值,不会让一条没有优势的规则变成有优势。
  • 上面那张样本内/样本外的表是概念层的判断。产品层要补一句:Blave 上没有「留 30% 当测试集」这个设定,唯一的样本外工具是工作页第五分页的 rolling walk-forward,两个输入框填的是天数。它验的东西跟简单切分不一样,见〈样本外验证怎么做:为什么这里没有「留 30% 当测试集」〉。
  • 还有一个问题这篇没有回答:就算样本外也过了,这个成绩是不是运气?那是显著性检验的工作,见〈回测赚的是实力还是运气:MCPT p 值怎么看〉。样本外和显著性问的不是同一件事,两个工具也不保证会给你一样的答案。