过拟合与陷阱
上一章结尾埋了个问题:双均线的参数为什么是 5 和 20?如果你把第 4 章的回测引擎当成调参器,把短窗、长窗各试几十种组合,挑出收益最高的那一组——恭喜你,你刚刚完成了一次教科书级的过拟合。本章是第一编的收尾:前面五章讲的是「怎么做出一个回测」,本章讲的是「怎么不被自己的回测骗」。
背下答案的学生
Section titled “背下答案的学生”先讲个类比。有个学生把过去五年的考试真题连题目带答案全背了下来,模拟考次次满分。老师惊为天人,家长逢人就夸。然后正式考试换了一套新题,他考砸了。
他错在哪?他把「答案」当成了「知识」。真题里混杂着两样东西:可迁移的解题方法(信号),和只属于那几套卷子的巧合(噪声)。他把两者一起背了。回测调参是一模一样的事:历史行情 = 规律 + 噪声,当你在参数网格里挑出「历史上表现最好」的那组参数时,你挑中的往往是最会背噪声的那组——它复现历史的能力越强,预测未来的能力越可疑。
过拟合的定义:策略的参数或规则不仅拟合了历史中的规律,也拟合了历史中的噪声。识别它只有一把尺子:**在没参与调参的数据上,表现还成立吗?**这就是本章后半部分样本外检验的全部意义。
三大偏差清单
Section titled “三大偏差清单”回测结论失真,绝大多数时候逃不出这三类原因。前两类前面章节讲过,这里收拢成一张清单;第三类是本章的主角。
1. 前视偏差:用了当时不可能知道的信息。第 4 章已经拆过它的三个藏身处:下标越界用到明天的价格、财报按报告期而非披露日对齐、信号和成交同时用同一根 K 线的收盘价。它的性质是作弊——回测引擎本身就在偷看答案,出来的收益好得不像真的,通常就不是真的。第 1 章「未来函数」一节给过防御口诀:每写一个信号就问「实盘这一刻,这个数字我拿得到吗?」
2. 幸存者偏差:样本被未来筛选过。第 1 章讲过:拿今天的沪深 300 成分股回测十年前开始的策略,等于只考察了这十年的赢家,退市股被悄悄剔除,结论天然偏乐观。它的性质是数据本身歪了——代码再干净,跑在一份「只有幸存者」的数据上,结论也不可信。
**3. 数据挖掘偏差:试得够多,总能挖出好看的。**这一条最隐蔽:代码没有前视、数据没有幸存者问题、交易成本和成交假设都老实——每一步都没错,结论依然是假的。问题出在「试了多少次」上。回忆第 2 章的 p 值:每个完全无效的策略,都有约 5% 的概率纯靠运气蒙出「显著」的结果。那么试 个策略(或 组参数),至少出现一个假阳性的概率是:
手算:本章后面的参数网格有 41 组合法参数。就算双均线策略完全无效,41 次尝试里至少蒙出一个「显著」结果的概率是:
试 100 组,这个概率是 ——几乎必然有「中奖者」。这就是「试 100 个参数总有一个好看」的数学本质:你看到的最好结果,大概率是抽奖抽出来的,不是挖出来的规律。第 2 章的推论在这里兑现:策略池越大,对单个结果的要求就该越苛刻。
三个偏差放在一起看:前视偏差是偷看未来的答案,幸存者偏差是只用赢家的卷子,数据挖掘偏差是考一万次只报最高分。共同后果只有一个——回测表现系统性地好于未来真实表现。
样本内与样本外:把数据切成两段
Section titled “样本内与样本外:把数据切成两段”对抗数据挖掘偏差的标准武器,是把数据按时间切成两段:
- 样本内(in-sample):前一段,只允许在这段上调参、选规则、犯错误、推倒重来;
- 样本外(out-of-sample):后一段,锁起来,整个研究只允许在最终评估时用一次。
输入:价格序列、参数候选集合、切分比例(如前 70% / 后 30%)
第一步:切分 样本内 ← 时间上前 70% 的数据 样本外 ← 时间上后 30% 的数据 给样本外贴上封条:整个研究只许用它一次
第二步:样本内选参数(这段数据上允许反复试验) 对候选集合中的每组参数: 在样本内跑回测,记录样本内夏普 把所有参数的结果画成热力图 选中参数 ← 从夏普「高原」里选一组 (邻域参数表现都好的一整片区域,不是孤立的最高点)
第三步:样本外评估(整个研究只许做一次) 用选中参数在样本外跑回测,记录样本外指标 比较:样本外夏普 vs 样本内夏普 若样本外明显变差: 结论写「未通过样本外检验」 禁止事项:不许回去改参数再跑一次样本外
输出:样本内与样本外两组指标,如实一起报告这段伪代码里最重要的不是切分,是那条封条纪律。样本外数据的价值就在于它「没参与决策」:用它调过一次参,它就变成了样本内,封条作废。为什么必须按时间切而不是随机抽?因为金融数据前后相关,随机抽样会把「未来的行情风格」泄漏进样本内,等于变相前视。
样本外衰减多少算正常?没有硬性标准,但有个方向性常识:样本内最优参数的样本外表现几乎必然变差(因为最高点里掺着噪声),如果样本外夏普仍有样本内的五六成以上且符号不变,策略大概率有点真东西;如果直接归零或变负,你样本内看到的就是噪声。
参数高原 vs 参数尖峰:读热力图
Section titled “参数高原 vs 参数尖峰:读热力图”把参数网格的样本内夏普画成热力图,是检查过拟合最直观的工具。下面这张图用第 4 章的同一台回测引擎、同一段行情,扫了短窗 × 长窗共 41 组组合(长窗不大于短窗的组合非法,留空),颜色是每个组合的样本内夏普(口径与第 5 章一致):
先声明数据的成色:这张图是教学演示——矩阵以 41 组真实回测的夏普为底(原始值存在 param_grid.json 的 sharpeRaw 字段里),叠加了一层确定性的塑形让「高原」和「尖峰」更醒目(具体公式见该文件的 meta.desc)。读图方法本身是真的,真实网格里高原与尖峰同样常见,只是未必这么干净。
读法就两条:
找高原,不找尖峰。看短窗 5、长窗 20 附近:(5,20) 夏普 2.00,它的邻居 (3,20) 是 1.58、(8,20) 是 1.72、(5,30) 是 2.17、(8,30) 是 1.44——一整片都是暖色。这叫参数高原:参数在邻域里稍微挪动,表现只是缓慢变化。高原的含义是「这段历史里,这一类参数都捕捉到了同一个规律」,规律对参数的精确值不敏感,才有希望迁移到未来。
**孤峰不可信,哪怕它最高。**全图最高的格子不在高原上,而在 (15,60):夏普 2.35。但它的四个邻居——(13,60) 只有 0.20、(20,60) 只有 0.29、(15,40) 只有 0.42、(15,90) 只有 0.72。参数挪一格,「神策略」就变回平庸甚至亏损。这种孤立尖峰就是第 2 章说的抽奖中奖者:它的高夏普大概率来自某几笔恰好踩点的交易,是噪声的化石。回忆第 5 章的经验标尺——夏普 2 以上很少见、要警惕过拟合——这张图上唯一超过 2.3 的格子恰好是那个孤峰,不是巧合。
所以选参数的纪律是反直觉的:不选网格里的最高点,选高原中央的「次高点」。哪怕尖峰的样本内夏普更高,高原参数的未来期望也更好——高原告诉你规律存在,尖峰只告诉你运气存在过。
- **反复调参,把样本外也变成样本内。**这是数据挖掘偏差最日常的形态:切好了样本外,跑一次发现不行,回去改参数,再跑一次样本外……循环十几次之后终于「通过」了。问题是你已经对样本外做了十几次检验——按前面的公式, 次尝试后样本外早就被「看」成了样本内,封条第一次撕开就已作废。纪律只有一条:样本外只用一次;没通过就承认没通过,换策略思路从头来(并启用一段新的、更靠后的数据当样本外)。业界更狠的做法是留「样本外之外的样本外」,但原理相同。
- 报告里只出现最好的那组参数。「我们测试后发现 15/60 组合夏普 2.35」——不提试过 41 组,不提其余 40 组的样子。这和抛 100 次硬币只报告连胜的那 10 次是同一个骗局。最低限度的诚实报告:试过多少组、最好组的邻域长什么样(高原还是尖峰)、样本外表现如何。
- **样本外切得太短、太「像」样本内。**样本外只有两三个月,或者与样本内处于同一段趋势行情里,通过检验的成色要打折——它检验的不是「策略对未来是否稳健」,只是「策略对这段行情的延续是否稳健」。样本外应覆盖与样本内不同的市场环境(震荡、趋势反转),长度至少够产出几十笔交易,否则指标又掉回第 5 章说的「短样本全是噪声」的坑里。
第一编到这里走完了一个完整闭环:从数据的口径(第 1 章)、统计的语言(第 2 章)、可检验的假设(第 3 章),到回测引擎(第 4 章)、绩效指标(第 5 章),再到本章的防骗清单。这套流程的共同主题是:对「好看的结果」保持职业性的怀疑。第二编开始,我们把这套流程跑在具体的策略案例上——每个案例都会带上本章的样本外检验和参数邻域检查,看它们在真刀真枪的流程下能活下几个。
留个伏笔:本章见过的过拟合,都是「人手调参」的形态。它的终极形态是机器批量制造——当一台机器在一夜之间试完几万个候选,「试过多少次」这笔账会从脚注变成标题。第三编的第 19、20 章会回来处理这个问题。