回测调完参数,要留一段没碰过的数据再考一次

在回测工具里把均线长度从 5 试到 200,把止损从 1% 试到 10%,挑出收益最高的那一组,再把这一组在同一段历史上跑出来的收益当成"这个策略能赚多少"。这一步里同一段数据干了两件事:先用来挑参数,又用来给挑出来的参数打分。这样打出来的分数会偏高。
改法是把数据按时间切开。前一段只用来调参,后一段在调参期间完全不碰,等参数定死之后只跑一次。后一段的结果才接近"换一段没见过的行情会怎样"。切的时候要按时间先后切,不能打乱;后一段的结果出来以后,也不能再回头改参数。
样本内、样本外怎么分:调参用过的那段,和从没参与调参的那段
样本内(in-sample)指参与了调参的那段数据,样本外(out-of-sample)指调参全程没用过的那段。拿做题来比:样本内是对过答案的练习册,样本外是没见过的考卷。练习册上的题再做一遍拿高分,说明不了会不会做新题。
scikit-learn 用户指南的交叉验证一章开头讲的就是这件事:在同一批数据上学参数、又在这批数据上测试,是方法上的错误。一个只会把见过的答案原样背出来的模型能拿满分,对没见过的数据却什么都预测不了。这种情况叫过拟合(overfitting),通行的应对是留出一部分数据当测试集。
放到交易工具里,"参数"是均线长度、网格间距、止损百分比、信号阈值这些能改的数。"调参"的范围比点一下"参数优化"按钮要宽:手动改一次、看一眼收益、再改一次,和让软件一口气跑几百组,做的是同一件事,只是慢一些。在几个指标里挑一个留下、在几个周期里挑一个留下,同样算调。凡是"看了这段数据的结果再做的选择",都让这段数据变成了样本内。
为什么在同一段数据上挑出来的最优参数,收益数字会偏高
先看一个和交易无关的例子。100 个人每人抛 10 次硬币,硬币是公平的,没有人有任何本事。单独一个人抛出 8 次以上正面的机会是 56/1024,约 5.5%,大概每 18 个人里有一个。100 个人里至少有一个人抛出 8 次以上的机会就超过 99% 了。把这个人挑出来,说他"正面率 80%",数字是真的,可是让他再抛 10 次,预期还是 5 次。
调参时发生的是同一件事。每一组参数在一段历史上的收益,可以看成两部分加在一起:这组参数本身的好坏,加上它和这段行情里某几根 K 线碰巧合拍的运气。挑收益最大的那一组,挑中的是"好坏加运气"最大的那一组,运气那一部分被一起挑了进来。试的组数越多,最大值里运气占的份量越大。换一段行情,运气那部分不会跟着过去。
过拟合的几种表现里,有两条和这里直接相关(QuantConnect 研究指南的归纳):跑很多次测试只留下显著的结果,以及在不更换测试数据的情况下反复微调参数。对同一个想法每多跑一次回测,就离过拟合近一步。
这不等于挑出来的参数一定没用。它说的是:样本内的那个数字里掺了多少运气,只看样本内看不出来,要拿没参与挑选的数据再量一次。看别人给的回测报告时,"试过多少组参数"该怎么问,写在 回测夏普比率多少算好 那篇里讲回测区间和调参次数的一节。
回测数据为什么不能随机打乱了再切成两份
做数据分析的通用办法是把样本随机分成两堆,一堆训练一堆测试。K 线数据这么做,调参时会用到未来的数据。
随机分的结果可能是 2025 年 3 月的 K 线在调参那一堆,2025 年 2 月的 K 线在考试那一堆。参数是看过 3 月之后调出来的,再拿去考 2 月,等于先知道后面发生了什么。实盘里永远只有过去的数据。对按时间排列的数据,别的交叉验证方法不合适,因为会变成用未来的数据训练、用过去的数据评估(scikit-learn 的 TimeSeriesSplit 说明页)。
相邻的 K 线还长得像。时间序列里时间上挨着的观测彼此相关,普通的切分方法默认样本之间互相独立,用在这里会让训练部分和测试部分之间带上不该有的相关性,估出来的成绩不可靠。翻成交易的话:周二下午的行情和周二上午差不了多少,上午在调参堆里、下午在考试堆里,这场考试接近开卷。
所以切法只有一个方向:切点之前的归调参,切点之后的归考试。TimeSeriesSplit 还有一个 gap 参数,作用是在每个训练段末尾、测试段开始之前空出若干条数据不用。回测里可以借用这个做法:策略单笔持仓最长能拿几天,就在两段之间空出几天,免得一笔在前一段开仓的单子平在后一段里,两段的账搅在一起。
留出一段、滚动前推、多段交叉:三种切法各怎么做,各适合什么情况
留出和多段交叉的定义在 scikit-learn 用户指南的交叉验证一章和 TimeSeriesSplit 说明页里,滚动前推的在 QuantConnect、Freqtrade 的在线文档里,都是 2026 年 10 月的版本。
留出一段是最省事的:整段数据按时间切一刀,前面调参,后面参数定死后跑一次。代价是只有一次考试成绩,这次成绩高低和后一段刚好是什么行情关系很大;另外切出去的那段不能拿来调参,数据本来就短的话,前一段会更短。
滚动前推(walk-forward)把"调参、往前跑一段"重复很多轮。QuantConnect 文档给滚动前推优化下的定义是:定期调整策略的参数,让某个目标在最近一段时间窗口上最优,选出来的参数一直用到下一次优化。拿 Freqtrade 的机器学习模块 FreqAI 回测说明里的数字来看:训练窗口 30 天,训练完回测紧接着的 7 天,然后窗口往前挪一周,新一轮用最近 30 天重新训练(里面包含上一轮拿来回测的那 7 天);时间范围设成 20210501 到 20210701,跑完一共训练 8 个模型,因为这段范围是 8 周。
每一轮往前跑的那一段,用的都是只看过它之前数据的参数。把各轮往前跑的部分接起来,整条曲线都是样本外的。这种切法模拟的是"实盘里每隔一段时间重新调一次参数"的用法,打算这么用的策略才值得这么测。多久重调一次要自己权衡:调得勤,参数贴着最近的行情;隔得久,运行更快,过拟合的机会也小一些。
多段交叉在通用数据分析里指 K 折:把数据分成 k 段,每一段轮流当一次验证段,其余 k-1 段合起来训练。直接用在 K 线上,轮到中间某段当验证段时,它后面的数据在训练那一边,又成了用未来调参。TimeSeriesSplit 是 K 折按时间改过的版本:第 k 轮拿前 k 段训练、第 k+1 段测试,后一轮的训练段总是包含前一轮的训练段。拿 6 条数据切 3 轮来说:第一轮用第 0 到 2 条训练、第 3 条测试,第二轮用第 0 到 3 条训练、第 4 条测试,第三轮用第 0 到 4 条训练、第 5 条测试。它和上面滚动前推例子的差别在训练窗口:这里从头累积、越来越长,那个例子是固定 30 天往前挪。想限制训练段长度,有 max_train_size 这个参数。
| 切法 | 调参用哪段 | 考试用哪段 | 适合的情况 |
|---|---|---|---|
| 留出一段 | 切点之前的全部 | 切点之后,跑一次 | 工具只能设起止日期;参数调好后打算长期不动 |
| 滚动前推 | 每一轮最近的一个窗口 | 紧接窗口的下一小段,各轮接起来 | 实盘打算定期重调参数;数据够长,工具支持或自己会写循环 |
| 按时间的多段交叉 | 从开头累积到当前轮 | 当前轮之后的那一段 | 想要好几次考试成绩,看成绩之间差多远 |
不写代码的最小做法:把回测日期分成两段,后一段只跑一次
只要回测工具能设起止日期,留出一段就能做。
- 找到起止日期的设置。图形界面的工具里找开始、结束两个日期框。命令行工具是一个参数,比如 Freqtrade 的
--timerange,三种写法在它的回测文档里都有:起止都写(20260131-20260301)、只写起点加短横(20260131-,某天之后)、短横加终点(-20260131,某天之前)。 - 看任何结果之前先定切点,并且写下来。比如手上的数据是 2023 年 1 月到 2026 年 6 月,切点定在 2025 年 7 月 1 日。后一段要长到能成交出一批单子,只有三五笔交易的考试看不出什么。
- 只在前一段调参。回测的结束日期填切点,怎么试都行,顺手记下一共试了多少组。
- 参数定死,把日期换成后一段,跑一次。手续费、滑点、本金这些设置和前一段保持一样。
- 两段结果并排放。
两段时间长度不一样,总收益不能直接比,先各自换算成月均或年化。然后逐项对:最大回撤各是多少,交易笔数按月算差多少,盈利的单子占几成。再看报告里同期行情自身的涨跌(Freqtrade 的回测汇总里有 Market change 这一项),前一段是大涨、后一段是横盘的话,两段收益本来就不在同一个起点上。
后一段比前一段掉得厉害,先能确定的一点是:前一段那个数字不能当作对以后的预期。至于原因,有两种可能混在一起。一种是前一段的好成绩主要来自挑选,也就是前面抛硬币那回事;另一种是后一段的行情状态和前一段差得远,参数在新状态下不合用。跑一次分不清这两种,但对要不要拿真钱去跑来说,结论是一样的。
后一段和前一段差不多,甚至更好,也只是一次考试的成绩。它让"前一段全靠运气"这个解释变得不太站得住,还不足以证明以后都这样。
有一个动作容易和样本外检验弄混。Freqtrade 的参数优化文档建议,把优化出来的参数写进策略后再回测一遍,并且用和优化时相同的时间范围,这样交易笔数和利润才对得上。这一步查的是参数有没有填对,用的还是样本内的数据。
看了样本外结果又回去改参数,那一段就不再是样本外
后一段的数据只有在"没影响过任何选择"的时候才算样本外。下面这几种做法,每一种看上去都只是小调整,效果都是把后一段变成了调参数据。
· 后一段结果不好,回去把止损或周期改一改,再跑后一段,直到好看为止。
· 准备了五个策略,全部拿后一段跑一遍,留下成绩最好的那个。
· 先把整段 K 线图看了一遍,再决定切点放哪,顺手把一段大跌划到了调参那边。
· 后一段跑完觉得成绩不理想,换一个切点重新来。
参数可以一直调到在测试集上表现最优,测试集的信息就这样"漏"进了模型,得到的指标不再反映它在新数据上的表现。办法是再多留一份:训练集上训练,验证集上比较和挑选,觉得可以了,最后在测试集上评估一次。
换成回测就是切三段。第一段调参;第二段用来在几组候选参数、几个候选策略之间做取舍,可以看很多次;第三段封起来,全部定下来以后跑一次。第三段跑完如果不满意,老实的选择只有两个:放弃这个想法,或者改了想法以后承认第三段已经用掉,等新的数据攒出来再考。
靠自觉不去看很难,更稳的办法是让自己看不到。QuantConnect 给团队管理员提供了一个设置,强制所有回测的结束日期停在当前日期的若干个月之前,留出的这几个月等开发结束后再用来验证。个人可以照这个思路做:下载数据时只下到切点为止,后一段等参数定死那天再下。
还有一段数据是谁都没法提前看的:今天之后的行情。参数定死后开模拟盘跑几周,拿到的是不可能被偷看的样本外结果。回测永远替代不了用模拟模式(dry-run)实际跑一段,Freqtrade 的回测文档里就有这句话。
接着读:样本外检验查的是参数,回测信号本身到了实盘会不会挪位置,要另外查,方法在 指标重绘:回测里的信号,实盘为什么会消失;参数定下来以后打算用图表警报接自动下单的,信号会在哪一段丢,见 警报响了却没下单怎么查。