样本内与样本外:那份没见过的数据,你只有一次机会
你花了一个周末,把一套规则里的那个周期参数从 10 试到 14,历史成绩从年化 30% 变成 62%。你很满意,准备上真钱。
这时候有人问你一句话:这个 14,是从哪一段数据里选出来的?
如果你的答案是「就是这五年啊」,那么恭喜,你手上那条 62% 的曲线,其实没有告诉你任何关于未来的事情。它只告诉了你一件已知的事——在这五年里,14 比 10 好。而这件事,是你自己刚刚翻遍所有可能之后挑出来的。
先说人话:它到底是件什么事
想象你桌上有两样东西:一本带答案的习题册,和一份密封着的模拟卷。
你把习题册刷了三十遍,每道题连选项位置都背下来了,闭着眼睛能考满分。这个满分能说明你会做题吗?显然不能,它只说明你记性不错。
真正能说明问题的,是那份密封的模拟卷。你拆开,做一遍,分数是多少就是多少——因为你事先没见过它,所以这个分数是干净的。
关键的地方在下一步。 假设你考砸了,于是你对着答案把错题都研究了一遍,回头又改了改自己的解题习惯,然后说「我再考一次这张卷子」。
这一次的分数,还有意义吗?没有了。因为这张卷子你已经见过、并且据此调整过自己了。它从「模拟卷」变成了「第二本习题册」,而且这个变化是永久的、不可逆的——你没办法让自己忘掉它。
这就是样本内与样本外的全部道理:
- 样本内(in-sample)=那本习题册。你在上面找规律、调参数、试各种想法,怎么折腾都行。
- 样本外(out-of-sample,也常被写成 OOS)=那份密封的卷子。它的全部价值,来自「你还没见过它」这一个事实。
- 而这个价值,每被使用一次就消耗一次,而且没有任何提示音。
再换个说法:样本外像一张刮刮卡。刮之前它有信息;刮开之后,它就只是一张纸了。
在投资里,这对你的决策意味着什么
「历史年化 60%」这句话,本身是没有信息量的
一条漂亮的资金曲线(就是那条「假装回到过去按规则走一遍」画出来的收益走势,这个动作叫回测),单独摆出来是不能判断好坏的。你必须追问两件事,而且它们必须分开回答:
- 这套规则的参数,是在哪一段数据上定下来的?
- 这条曲线,是在哪一段数据上算出来的?
如果这两段是同一段,或者有重叠,那这条曲线只是一份「事后总结」,不是一份「检验」。它约等于告诉你「过去五年里表现最好的那套参数,在过去五年里表现最好」——这是一句同义反复。
所以当别人给你看回测成绩时——不管是社群里晒的、课程里演示的,还是你自己做的——第一个该问的不是收益率多少,而是这两段数据是怎么划的。很多时候,对方回答不上来,或者答案是「用全部数据」。这一问就够了。
切数据不能随机切,只能按时间切
普通的机器学习里,人们常把数据随机打乱再分成两堆。在投资上,这么做是灾难。
原因很直白:如果你把 2023 年的一部分行情分给了「训练」,另一部分分给了「测试」,那你就是在拿 2023 年三月教会模型某件事,再去考它 2023 年二月——你用未来教了过去。这属于前视偏差,是量化里最会骗人的一类错误。
正确的切法只有一种方向:训练段在前,检验段在后,时间上严格不重叠。这条约束听起来简单,但它是所有金融数据划分的地基。
为什么中间还要夹一段
成熟的做法通常切成三段,而不是两段:
- 训练段:用来拟合,让模型或规则从这里学东西。
- 验证段:用来在若干个候选里挑一个。
- 测试段:最后看一眼,只看一次。
第三段之所以必要,是因为「挑选」这个动作本身就是一种拟合。你在十个候选里挑出验证段上表现最好的那一个,这个「最好」里必然含有运气成分——候选越多,运气的占比越大。如果你直接把验证段的成绩当作最终成绩报出去,那你报的是「十次里最好的一次」,而不是「一次的期望」。
所以要留出一段谁都没碰过的数据,专门用来接收这个坏消息。具体三段各该占多长、切点落在哪一天,用样本内外分割计算器把起止日期填进去就能排出来,顺便还能看出留给测试段的那截到底够不够长。
系统层:真实的量化系统是怎么切的
概念讲到这里就完整了。接下来看三个系统各自把它做成了什么样子——同一件事,落到工程里长得非常不同。
配置文件里的三段日期
qlib 的示例工作流配置里,数据集这一块最显眼的就是三行日期(以下取自其示例配置快照,日期属于示例值,不是什么推荐设置):
segments:
train: [2008-01-01, 2014-12-31]
valid: [2015-01-01, 2016-12-31]
test: [2017-01-01, 2020-08-01]
三段首尾相接、不重叠、严格按时间递增。这就是前面说的三段划分,被写成了一份必须填的配置——你想跑实验,就得先回答「哪段学、哪段挑、哪段考」。把一个纪律做成必填项,比写在文档里靠人自觉,效果完全不是一个量级。
真正精彩的是同一份配置里另外两行,很容易被跳过:
fit_start_time: 2008-01-01
fit_end_time: 2014-12-31
这两行管的不是模型,是数据预处理。做量化通常要先把原始数据「归一化」(把量纲不同的一堆数字统一缩放到可比的尺度上,比如都变成「距离平均值几个标准差」)。而缩放要用到平均值和离散程度这两个统计量——这两个数字是从哪一段数据上算出来的?
qlib 的处理器源码里,这个参数上方压着一行注释,语气强烈到不像技术文档:
# NOTE: correctly set the `fit_start_time` and `fit_end_time` is very important !!!
# `fit_end_time` **must not** include any information from the test data!!!
三个感叹号。为什么这么紧张?因为这是最容易被放过的一条泄漏路径。你会觉得「我只是把数据缩放了一下,又没拿它做预测」——可是那个平均值一旦是在包含测试段的全体数据上算的,测试段的信息就已经渗进训练里了。模型没有直接看到未来,但它看到了一把「按未来刻度校准过的尺子」。等到真实环境里,这把尺子不存在。
注意这两行日期和 train 段的日期是一致的。这不是巧合,是纪律。
同一套代码里还把处理器分成两类:训练时用的和推断时用的分开配置。原因也是同一个——有些处理动作(比如丢掉标签缺失的样本)只在训练时合理,如果照搬到检验阶段,等于偷偷帮检验样本做了筛选。
一个日期,把因子分成四种命运
Vibe-Trading 里有个专门做因子严格检验的模块,它的做法更直接:给一个日期字符串,把数据面板切成训练和测试两半,分别算显著性,然后给每个因子贴上四个标签之一。这四个标签的名字本身就是一堂课:
"confirmed_alive", # 在全样本和样本外都跑赢随机对照
"train_only", # 训练段跑赢,测试段挂了
"reversed_strict", # 信号方向反了
"noise", # 和随机没区别
请盯着 train_only 这个词看三秒。
一个成熟的检验系统,专门为「训练段好、样本外不好」这个结局造了一个名字。这说明什么?说明它太常见了,常见到必须单独归档、单独统计数量,而不是简单地扔进「失败」里了事。
更有意思的是它对「样本外反号」的处理。按直觉,全样本显著、样本外掉到零附近,和全样本显著、样本外反向显著,好像都算「没通过」。但这个模块把后者归到了 reversed_strict 而不是 train_only,注释里给的理由是:样本外反号是「训练段的超额收益是假的」这件事最强的证据,研究员需要看到这个区别。衰减到零可能只是信号变弱了;掉头反向说明你原来抓到的根本不是你以为的那个东西。
这个模块的另一半同样值得抄下来:它不满足于「和零比」。它要求把因子在同一股票池上随机打乱一遍,作为对照组,只有跑赢自己的随机版本才算数。文件开头解释了为什么——原来那套只和零比较的判据,会放行一大批实际上是靠共同的市场涨跌撑起来的因子。它引用的一份 A 股审计里,十几个单因子在原判据下每一个都能在某组参数下通过,但加上随机对照后只剩一个。
「在某组参数下通过」这七个字,正是本篇的主题:只要你允许自己反复试,几乎任何东西都能在历史上通过。
优化器每跑一轮,就消耗一点样本外
freqtrade 那边的角度不同。它的参数优化功能会把你的参数空间跑很多轮,文档里管一轮叫一个 epoch——每一轮就是换一组参数值,在同一段历史上重跑一遍回测,然后按你选的目标函数打分。你可以用命令行参数限定这段历史的范围。
它的文档里有一句大实话:换个随机种子多跑几轮,得到的最优结果多半就不一样了。
这句话该被读成一句警告。如果「最好的那组参数」会随着随机种子变,那所谓的「最好」里有相当一部分是抽签抽出来的。你在优化窗口上跑的轮数越多,挑出来的那一组就越贴合这段历史的噪音——这就是过拟合的生产过程,而样本外是唯一能戳破它的东西。
另一处细节也很值得警惕。文档专门有一节讲「优化完之后要回测验证结果对不对得上」,并强调要用和优化时完全相同的时间段和配置。这一步是必要的,但它检验的是「参数有没有正确落到策略里」,属于复现性检查——它和样本外检验是两件完全不同的事,名字却很像。见过不少人做完这一步就以为「我验证过了」,其实一步样本外都还没走。
文档里另有一条顺带的提醒同样是这个病根:只挑历史上赚钱的品种来做,会得到一个过度贴合的策略。挑品种和挑参数,本质是同一个动作。
「样本外只能用一次」:这条纪律为什么反直觉
前面铺的所有东西,都是为了这一节。
你每看一眼,就往里搬一点信息
假设你切好了数据,规规矩矩地在训练段调完参,拿到样本外一跑——不及格。
于是你回去改了改规则,再跑一次样本外。还是不行,再改,再跑。第五次,过了。
这第五次的成绩,不是样本外成绩。因为这套最终规则是在你看过样本外四次之后才成型的,样本外的信息已经通过你的大脑,一次一点地搬进了规则里。从统计上说,你做的是「在样本外上五选一」——而「选」,就是拟合。
真正让人难受的是这个过程完全不留痕迹:
- 代码里没有任何一行写着「我在样本外上调过参」。
- 回测报告和第一次跑出来的长得一模一样。
- 你没有作弊的感觉,每一步都觉得自己是在「改进策略」。
- 任何自动化检查工具都查不出来,因为可疑的东西根本不在文件里,在你的记忆里。
所以对付它的手段不可能是技术手段,只能是流程手段。
流程上怎么守
动手前把判据写下来。 在打开样本外之前,先白纸黑字写清楚:我要看哪几个指标、达到什么状态算通过、什么状态算不通过。写完再看。这一步的作用不是仪式感,是堵死事后找理由的空间——人在看到结果之后,编出一个「这次不算」的理由,只需要三秒钟。
看完就作废,不管结果如何。 通过了,这段数据的使命结束;没通过,它的使命同样结束。「我再微调一下就来考」这句话是可以被无限重复使用的,所以它不能算判据。
想再考一次,必须换新卷子。 只有两个来源:要么继续等,让时间产生真正没人见过的新数据;要么承认你剩下的干净数据变短了,把切点往前挪,然后接受一个更短、结论更弱的检验。第二种是有代价的,代价必须记在账上。
记录你一共考了几次。 哪怕你做不到只用一次——现实中很少有人做得到——至少要诚实地记下「这套东西看过样本外七次」。看过七次的策略和看过一次的策略,你对它的信心应该是天壤之别。这份记录本身,就是你未来定仓位时最重要的输入之一。
样本外没通过时,最危险的念头是「这段行情比较特殊」。 这句话在任何一段历史上都成立——每一段行情都有它特殊的地方。一个在所有情况下都成立的解释,不解释任何东西。真正的问题不是「这段特殊吗」,而是「未来的哪一段不特殊」。如果答案是没有,那你的规则依赖的就不是规律,是运气。
切法本身也会骗你:常见误用
切点位置的随机性被忽略了。 只切一刀,结论会严重依赖这一刀切在哪。牛市全给了训练段、熊市全落在检验段,和反过来切,能得出完全相反的结论。对付这个问题的通行做法是把切点不断往后滚动、反复检验,也就是滚动前进验证,那篇专门讲这件事。
样本外太短,结论没有分量。 前面那个因子检验模块有一道硬闸门:训练段和测试段各自的有效样本数不够,一律先判为「噪音」,连算都不算。一段只覆盖三个月、只包含个位数笔交易的样本外,通过与否都是抛硬币。样本外的长度不够,等于没有样本外。
同一段样本外被很多人、很多次共用了。 你自己只用了一次,很守规矩。但这段数据在网上被成千上万人用过,你正在检验的这个想法本身就来自那个圈子——那么对整个群体而言,它早就不是样本外了。这层更隐蔽的问题叫数据窥探,单独一篇讲。
把复现性检查当成样本外检查。 前面说过,「用一样的配置重跑一遍看数字对不对得上」是必要的工程步骤,但它一步都没有走出样本内。这两件事名字接近,混淆的代价却很大。
预处理阶段的悄悄泄漏。 归一化用的统计量、特征筛选用的相关性、异常值裁剪用的分位数——只要其中任何一个是在包含检验段的全体数据上算出来的,检验段就被污染了。模型再干净也没用。qlib 那三个感叹号就是钉在这里的。
数据源头已经带病。 如果你的历史数据里,财报被按报告期而不是公布日对齐、指数成分用的是今天的名单,那么你切成多少段都没意义——每一段都带着同样的病。切分解决的是「用哪段学、用哪段考」,解决不了「这段数据本身是不是当时真实存在的样子」。这一层属于前视偏差的范畴。
通过了样本外,不等于策略成立。 通过只意味着「暂时还没被证伪」。市场结构会变,交易成本会变,容量会变。样本外是一道必须过的门槛,不是一张证书。而且别忘了,收益要和一个像样的参照物比才有意义——单看绝对数字,很容易把大盘的普涨误认成自己的本事。
小结
- 样本内是你随便折腾的那本习题册,样本外是那份只能拆一次的密封卷子;后者的全部价值来自「你还没见过它」。
- 金融数据只能按时间切,训练在前检验在后,随机打乱切分等于用未来教过去。三段划分(学 / 挑 / 考)之所以要三段,是因为「挑」本身也是一种拟合。
- 泄漏不只发生在模型里:归一化用的均值、筛选用的相关性,只要沾到检验段就已经污染了检验段。
- 真实系统用三种方式落地这条纪律:把切分做成必填配置、给「只在训练段有效」单独造一个标签、在优化窗口上限定范围并坦白最优解带运气。
- 样本外每被看一次就消耗一次,而且不留任何痕迹——查不出来,只能靠流程:先写判据再看、看完即作废、想重来就得换新数据、老实记下看过几次。
- 「这段行情比较特殊」是一个在任何历史上都成立的借口,因此它不是判据。
一句话记住这篇:你自己出的卷子考再高分都不算数,而那份别人出的卷子,你只能考一次——考完偷看答案再重考,分数就不是你的了。
本文所引源码与配置均来自上述快照版本,是阅读代码得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。相关术语卡见回测与过拟合;回量化与 AI 投研概念库看其它概念各自在说什么;本站内容为投资教育,不构成任何投资建议,边界见免责声明。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- qlib 源码快照 79633dd(2026-07-23):examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml、qlib/data/dataset/processor.py、qlib/data/dataset/__init__.py、qlib/contrib/data/handler.py ↗
- Vibe-Trading 源码快照 3a752d5(2026-08-04):agent/src/factors/bench_runner_strict.py ↗
- freqtrade 源码快照 b3404c9(2026-08-18):docs/hyperopt.md、docs/utils.md ↗