过拟合:调参这个动作本身,就是一台噪音制造机
你写了一套规则,跑了一遍历史,成绩一般。于是你把均线周期从 20 改成 22,好了一点;再改成 23,又好了一点。止损从 5% 挪到 4.6%,再好一点。后来你嫌手动太慢,干脆写了个循环让机器替你试——两千组参数跑了一整夜,早上醒来,屏幕上躺着那组「最优参数」,历史曲线好看得让人想立刻上钱。
这时候大多数人心里想的是:我终于把它调好了。
但你其实没有在「调好它」。你在做的事情,翻译成统计语言是这样的:拿同一段历史,反复考同一套规则两千次,然后只把考得最好的那一次留下来。 至于这个「最好」有多少来自规律、多少来自这段历史里恰好长成那样的偶然,程序不会告诉你,因为它压根没有区分这两者的能力。
过拟合的基本含义——把历史里的偶然噪音当成规律背了下来——术语卡一句话就说完了,怎么避免过拟合那篇也把成因和方法清单讲得很全。这一篇只补它没细讲的那半边:优化这个动作本身是怎么源源不断生产过拟合的,以及跑完之后你能用哪几种统计手段,从结果堆里把这股味道闻出来。
先说人话:一屋子人扔硬币,总有人连中十次
找一千个人来,每人扔十次硬币,全押正面。按概率算,这一千人里大概会有一个人十次全中。
你把这个人请上台,介绍说:这位先生扔硬币的准确率是 100%。台下会笑。可如果你把前面那九百九十九个人藏起来,只让大家看这一位——没人笑得出来,所有人都会觉得他有点东西。
参数寻优就是这么回事,而且更彻底:你不是在一千个人里挑天才,是让程序把一千个人全试了一遍,再把冠军留给你看。 那九百九十九个失败者被自动丢进了日志文件,你看到的只有冠军的成绩单。
这里要拆掉一个很常见的误会:问题不在「优化」这个词,也不在你用了多先进的算法。问题出在最大值这个东西天生就偏高。你试一次,成绩好,那可能是本事;你试一万次挑出最好看的那一次,那个数字的性质已经变了——它不是这套规则的期望成绩,它是「一万个成绩里的最大值」。哪怕这一万套规则全都毫无道理,最大值也照样会很好看。
投资层:这件事怎么落到你身上
你的试验次数,没人替你记账
先接受一个不太舒服的等价关系:你手动改二十次条件,和让程序跑两千轮,是同一类操作,只是次数不同、账本有没有记而已。
程序至少还会把每一轮都写进结果文件;你自己在编辑器里反复改、改到曲线顺眼为止的那些次数,没有任何地方记录。等你回头讲这套规则的时候,你说的是「我这套方法历史年化 40%」,而不是「我试了大约三十种版本,其中最好的那个历史年化 40%」。这两句话的可信度差着量级,可它们描述的是同一件事。
搜索不只发生在参数这根轴上
这是最容易漏掉的一层。除了调数字,下面这些动作全都在给历史增加「碰运气」的机会:
- 换品种:这套规则在 A 板块不灵,换 B 板块试试,换 C 板块试试——挑出表现最好的那个市场,这是一次搜索。
- 换时间段:起点从五年前挪到三年前,成绩好看多了——这也是一次搜索,而且是最隐蔽的一种,因为它长得像「选择合适的样本」。
- 换评价口径:按总收益不好看,按夏普看还行;按夏普不行,按回撤比又能说得过去——挑一个能把结论撑起来的口径,同样是搜索。
- 挑别人的策略:你在网上翻了十套公开策略,选了历史成绩最高的那套。这一步你自己没调任何参数,但你踩在了别人已经筛过一轮的结果之上,又筛了一轮。
它们共用一个结构:在一堆候选里挑出历史上最好看的那个。参数只是候选的一种形式。
所以你真正该记的那个数
从这里可以引出一条可操作的习惯:任何时候你要拿一个历史成绩说事,先在旁边写上为了得到这个成绩,你一共试过多少种版本。这个数字不是备注,它是成绩的一部分。试了 3 次得到的年化 20%,和试了 3000 次得到的年化 40%,前者更值得信。
系统层:优化器长什么样,它在哪些地方替你放大了问题
参数寻优 = 把回测跑很多遍
freqtrade 的参数寻优文档在开头就把这件事说穿了:它和回测一样需要历史数据,因为这个过程就是用不同的参数组合,把回测跑很多很多遍。搜索先从一小批随机组合起步,之后交给采样器(当前用的是 optuna 里的某个采样器,具体是哪个会随版本变),往「让损失函数变小」的方向收敛。
请盯住这个目标:损失函数算的是这段历史上的成绩。 优化器唯一知道的事情就是这个数字的高低,它没有任何机制去判断某一次提升来自真规律还是来自噪音。你让它优化历史成绩,它就一路啃过去,能啃到的规律和能啃到的噪音,它一视同仁。
文档里还有一句挺诚实的自嘲,说这个搜索会烧光你所有 CPU 核、让笔记本听起来像战斗机,而且依然很慢。它的言外之意值得品:慢,是因为它在穷尽地找那个最高点。而「找到这段历史的最高点」和「找到规律」,从来就不是同一件事。
自由度不只看参数个数,还看参数的精度
这一条是我认为最值得单独拎出来的增量。
文档在几处搜索空间的说明里都重复了同一条注解:小数类参数被限制到三位小数(这是当前的默认设定,可以通过覆盖预定义空间来改)。给出的理由非常直接——比这更精细的取值,通常只会得到过拟合的结果。
想清楚这句话的分量。同样是「止损」这一个参数,个数没变:
- 允许它在 3% 和 4% 之间二选一,你给了历史 2 个选择;
- 允许它在 3.000% 到 4.000% 之间按千分之一取值,你给了历史 1001 个选择。
参数还是一个,自由度涨了三个数量级。所以「参数越少越稳健」这条常见建议只说对了一半——**决定自由度的是候选组合的总量,参数个数只是其中一个乘数,取值粒度是另一个。**一个粒度极细的三参数策略,可能比一个粗粒度的八参数策略更能贴合噪音。除了把格子调粗,还有一条路是从模型那一侧主动往回收自由度,让它没那么容易把噪音背下来,正则化与泛化讲的就是这一手。
空间被穷尽时,程序会主动喊出来
有个信号特别有教学价值。文档专门用一节讲这条提示:The objective has been evaluated at this point before.——意思是这个点之前已经算过了。出现这句话,说明搜索空间里的组合基本被试遍,采样器找不到新的点了。
文档给的例子干净得像教科书:
buy_ema_short = IntParameter(5, 20, default=10, space="buy", optimize=True)
# 这是该空间里唯一的参数
这一个参数只有 5 到 20 共 15 个可能取值。你要是跑上千轮,绝大多数轮次都在重复劳动。
把这个事实倒过来读,你会得到一把衡量风险的粗糙尺子:试验次数相对于搜索空间的比例。
- 空间只有 15 个点、你跑满 15 轮:这不是优化,这是穷举。所谓「最优」,就是这 15 个数里的最大值,没有任何统计意义上的发现。
- 空间有上亿个点、你跑了两千轮:你连表面都没刮干净,但你交出去的那组参数,仍然是两千次里的最大值。
两种情形都不构成「找到了规律」。这也解释了一个反直觉的现象:加大轮数往往不会让策略变得更稳,只会让你更深地挖进这段历史的地形里。
目标函数本身也是一根可以调的轴
freqtrade 内置了十几个损失函数:只认利润的、认夏普的、认日频夏普的、认索提诺(只罚下行波动)的、认最大回撤的、认卡玛(收益比最大回撤)的、认利润与回撤组合的、多指标综合的……
每一个单独看都合理,它们只是不同的评价角度。真正的问题在于用法:如果你换一个损失函数重跑一遍,再挑「哪个损失函数跑出来的结果最好看」,你就在参数搜索之上又叠了一层搜索。 你的试验次数悄悄乘了一个系数,而这一层几乎没人会记进账本——因为在感觉上,换损失函数像是「换了个更合适的尺子」,不像「又试了一次」。
事后筛选,是同一件事的另一副面孔
搜索跑完还有一堆过滤器可以用:只看有盈利的轮次、只看交易笔数落在某个区间的、只看平均持仓时长合适的、只看目标值达标的。这些筛选作为分析工具没问题,但每用一次,你就在结果堆里又挑了一次。
上游文档里有一处提醒把这层说得非常直白。回测结果可以导出一份「表现好的品种清单」,直接粘回配置里去。文档紧跟着挂了个警告:只保留赢的品种,会得到一个过拟合的策略,在未来数据上不会好用。
值得注意的是,这个动作里一个参数都没调。挑的东西从「数字」变成了「名单」,可它和调参属于同一类操作——在一堆候选里,按历史成绩挑赢家。
怎么从结果里把过拟合读出来
怎么避免过拟合那篇给的是流程性办法(切样本、滚动检验、少用参数、逻辑先行)。下面这几种是另一个场景:你已经跑完了一大堆结果,怎么从结果本身闻出味道。
一、别只看最优点,看整个分布
优化器会把每一轮的成绩都留下来。把它们全铺开看,重点不是冠军多高,而是冠军站在什么样的地形上:
- 冠军孤零零地顶在分布最上面,和第二名、和中位数拉开一大截 —— 这更像一个异常值。异常值在一次搜索里出现,几乎是必然事件,不是发现。
- 排名靠前的一批轮次成绩接近,而且它们的参数取值彼此相邻 —— 这更像找到了一片真实存在的地形。
这就是「参数高原与参数尖峰」在优化结果层面的样子,参数高原与参数尖峰那篇把这个判据讲得更细。
二、换随机种子重跑,看最优参数漂多远
文档提到搜索的起点是一批随机组合,随机状态由一个可指定的参数控制,指定同一个值可以复现同一批起点;而它也明确写了另一半——用不同随机状态多跑几轮,很可能得到不同的结果。
这句话可以直接当检验工具用。同一套规则、同一段数据、只换随机种子,跑三次,然后看:
- 三次给出的「最优参数」是不是落在相近的区域?
- 如果三组参数差得很远,而每一组的历史成绩又都很好看,那结论很清楚:这段历史里到处是局部高点,你挑中哪一个纯看运气。 这时候不管你把哪一组搬上实盘,本质上都是在赌一个随机数。
这个检验的好处是几乎不需要额外理解成本,代价只是多跑几遍。想看看自己这轮搜出来的最优点是坐在一片高原上还是一根尖峰上,把整张参数扫描结果贴进过拟合自查与参数高原工具,它会算出邻域衰减幅度和参数高原指数。
三、把目标函数改成「盯最差的那一块」
freqtrade 里有个损失函数的设计思路值得单独拿出来说。它不算整体成绩,而是按每个交易品种分别算「利润 ÷ 回撤」,然后返回其中最差的那个当作优化目标。注释里写明了动机:防止一两个表现好的品种把整体指标撑起来,而表现差的那些既没有被体现出来,也就从来没被优化过。
这是把统计思路直接写进了目标函数。既然过拟合的一个典型表现是「靠个别样本撑场面」,那就让目标函数只认最弱的那一环——一个能让最差品种也过得去的参数组合,比一个靠明星品种拉平均的参数组合,可信度高得多。
同一个思路的另一个变体是对样本量施加惩罚:某个综合型损失函数会给交易笔数偏少的轮次直接扣分(惩罚的力度做成了模块顶部的可调常量)。道理很朴素——二十笔交易的高胜率什么都证明不了,成绩必须建立在足够的样本上才有讨论价值。
四、给成绩配一个区间,而不是一个点
Vibe-Trading 的验证模块里有一项叫 bootstrap 夏普置信区间:从已完成的交易里有放回地反复抽样,每次重算一遍夏普,最后给出的是一个区间,而不是一个数。
这个做法的价值在于它换了问法。「这套规则夏普 2.1」和「这套规则的夏普大致落在某个区间里,2.1 只是其中一个点」,是两句完全不同的话。样本越少、单笔盈亏波动越大,区间就越宽。而当这个区间宽到把零也包进去的时候,你手上那个漂亮的点估计,就没有立场支撑任何决策了——它和「什么都没有」在统计上区分不开。
同一个模块里还有把成交顺序打乱做显著性检验、把回测切成若干段看一致性的工具,前者在蒙特卡洛检验里另有篇幅。
五、三段切分,并且认清中间那段已经脏了
qlib 的示例配置把数据集切成三段而不是两段:训练段、验证段、测试段。模型在训练段上学,配套的梯度提升模型封装里带早停机制——靠验证段上的指标判断什么时候该停下来,测试段留到最后。
值得琢磨的是中间那段的地位。验证段没有参与训练,但它参与了选择:它决定了训练在第几轮停住。而只要参与了选择,它就不再是干净的样本外了。很多人把验证段上的成绩当成「样本外表现」拿去汇报,这是个非常安静的错误——安静到几乎没人会指出来。
这条可以推广成一句通则:任何一段数据,只要它的结果反过来影响了你的下一个动作,它就已经进了样本内。 这条线具体怎么划,样本内与样本外那篇专门讲。
「我做过样本外检验」这句话,要追问一句:做过几次。 如果样本外不达标、你回头改了规则、又用同一段数据重测——哪怕只重测了两次,那段数据已经在参与选择了,它变成了第二个样本内。样本外数据的可用次数是一种一次性资源,用一次少一次,而且没有任何提示告诉你它已经用完了。
失效边界与常见误用
「我用的是更聪明的搜索算法,所以不容易过拟合。」 正好反了。更聪明的搜索器意味着它更快地找到这段历史上的最高点,其中当然包括噪音堆出来的最高点。搜索效率提高,过拟合的速度也一起提高。先进算法替你省的是电费和时间,不是风险。
「我参数很少,所以安全。」 参数个数只是自由度的一个乘数。取值精度、试过的品种数、换过的时间段、换过的评价口径、事后用过的筛选条件,每一项都在往总量上乘。把这些全乘完,你会发现「三个参数」这个说法安慰性远大于实际意义。
「历史上跑得不好,说明我没过拟合。」 过拟合的反面不是亏损,是泛化。一套在历史上也不赚钱的规则,只是同时又差又过拟合罢了——两件事可以并存。
把统计检验当通行证。 分布看了、种子换了、区间算了、目标函数也改成盯最差的了,全都过关——这只说明「没查出来」。过拟合不像程序 bug 那样有明确的存在证明,它只有程度,没有有无。任何用历史数据挑出来的东西,都带着一定量的过拟合,问题永远是「有多少」,不是「有没有」。
和前视偏差混为一谈。 前视是「用了当时拿不到的信息」,过拟合是「贴合了拿得到的信息里的偶然部分」。两者都会让历史成绩虚高,但查法和补救方式完全不同。想看这几类回测毛病各自的分界,可以回 量化与 AI 投研卷 顺着读一遍。
小结
- 参数寻优的本质,是拿同一段历史反复考同一套规则,然后只留下考得最好的那次。最大值天生偏高,这和你用不用优化器无关——手动调参只是没记账而已。
- 自由度不只看参数个数:取值精度、试过的品种、换过的时间段、换过的目标函数、事后的筛选条件,每一样都在给历史多一次碰运气的机会。
- 优化器的目标函数算的是历史成绩,它没有任何机制区分规律和噪音;搜索器越聪明,找到历史最高点越快,过拟合也越快。
- 从结果里读过拟合的几种办法:看整个成绩分布而不是冠军一点、换随机种子看最优参数漂多远、把目标函数改成盯最差的那一块并惩罚样本过少、给成绩配区间而不是点估计、三段切分并承认验证段已经参与了选择。
- 这些手段全部只能降低概率、缩小程度,没有一种能证明清白。
一句话记住这篇:试一万次挑出最好看的那一次,你挑到的多半是运气,不是本事。
本文所引源码与文档均来自上述快照版本,是阅读代码与文档得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。回 量化与 AI 投研概念库 看其它概念各自在说什么;本站内容为投资教育,不构成任何投资建议,边界见 免责声明。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- freqtrade 源码快照 b3404c9(2026-08-18):docs/hyperopt.md、docs/utils.md、freqtrade/optimize/hyperopt/hyperopt.py、freqtrade/optimize/hyperopt_epoch_filters.py、freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_per_pair.py、freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py ↗
- qlib 源码快照 79633dd(2026-07-23):examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml、qlib/contrib/model/gbdt.py ↗
- Vibe-Trading 源码快照 3a752d5(2026-08-04):agent/backtest/validation.py ↗