超参优化:一个天生和过拟合纠缠在一起的功能
- 说得清参数优化工具的目标函数和你真正的目标之间差在哪里,以及为什么优化器越强这个差距越危险
- 认得出框架里那几处「防过拟合」的设计各自在对抗什么——换目标函数、样本外开关、结果全量列出、主动限制搜索精度、随机起点
- 说得出这些手段为什么只能缓解不能消除,并把这个功能的定位从「找最优参数」改成「看参数地形」
你打开这个功能的说明文档,第一段就给你泼了一盆冷水:这个过程会榨干你机器上所有的处理器核心,让笔记本的风扇吵得像架战斗机,而且跑完还是要花很久。
大多数人读到这句的第一反应是兴奋的:动静这么大,说明这是个重活;重活跑出来的东西,总该值点钱吧。
这一篇要说的,恰恰是这个反应错在哪。这台机器很卖力,但它卖力的方向,和你想去的方向,从一开始就不是同一个。 更别扭的是:它越卖力、算法越先进、跑得越快,这个方向偏差造成的损害就越大。
参数优化为什么会制造过拟合,过拟合:调参这个动作本身,就是一台噪音制造机 那篇已经把统计机制拆得很细了,你想搞懂「为什么」应该去读它。这一篇不重复推导,只处理另一个问题:一个真实的框架,明知道这个功能有毒,它是怎么给它加护栏的;这些护栏各自挡住了什么、挡不住什么。
先说人话:你让 HR 挑的那个人,不是你想招的那个人
你要招一个人,扔给招聘负责人一句话:从这一万份简历里,挑出过去三年业绩最好的那一个。
他照做了,而且做得极其漂亮——一万份全看完,排了序,把第一名端到你面前,附上一份完整的历史业绩单,数字确实亮眼。
问题出在哪?出在你说出口的那句话,和你心里真正想要的那件事,不是一回事。你想招的是「未来三年能干活的人」。可未来三年的数据不存在,谁手上都没有。 所以你只能退而求其次,拿「过去三年」当替身。
这个替身有一个致命性质:它是可以靠运气拿到的。 一万个人里,总有那么几个恰好赶上了风口、恰好接手了一个好项目、恰好躲过了一次裁员。他们的历史业绩单,和那些真有本事的人写出来的,长得一模一样。
到这一步,关键的推论出来了:
招聘负责人越称职,你越危险。
一个偷懒的负责人只翻了十份简历,他挑出来的那个多半是个正常人——因为运气极佳的人在十个里出现不了。而一个能把一万份全部看完、精确排序、绝不遗漏的负责人,一定会把那个运气最好的人顶到第一名。他没有失职,他把你交代的任务完成得完美无缺。是任务本身就是错的。
参数优化器就是这位负责人。它不知疲倦、绝不遗漏、还能用聪明的采样算法比你更快地找到那个最高点。它的能力全部用在了一个你其实不想要的目标上。
这对你的判断意味着什么
你想要的那个数,永远不会有数据
把上面的比喻还原成投资语言:你真正关心的量是「这套规则未来的表现」。这个量没有历史数据,将来也不会有——等它有了数据的时候,你已经用真钱把它验证完了。
所以任何优化工具,不管它多先进,能拿来打分的只有一样东西:这套参数在你给它的那段历史上表现如何。这不是工具的缺陷,这是这件事的物理边界。优化器优化的永远是替身指标,从来不是你的目标。
一旦接受这一点,几个结论就自动成立了:
第一,冠军的成绩天生虚高。 它不是这套规则的正常水平,它是「几千个成绩里的最大值」。最大值这个东西本身就偏高,这一点在 过拟合 里有完整的展开。
第二,工具不会告诉你这个成绩有多可信。 它给你的是一个排名和一个目标值,没有任何一栏写着「这个结果里有多少是运气」。排行榜的形式本身就在暗示「第一名最好」,而这恰恰是最不该被信任的那一行。
第三——这条最少人想到——工具的演进方向和你的利益是错位的。 优化工具的迭代目标是什么?更快收敛、更少的轮数找到更高的分。它每往前走一步,都是在把「历史最优」这件事做得更彻底。而你真正需要的「未来还能用」,从来不在它的评价体系里。这个功能每一次变强,都在往同一个错误方向变强。
顺便说一句:你在网上看到的那些「优化后历史年化 XX%」的截图,是这条错配链条的末端产物。别人把「我们优化过参数」当成卖点说给你听的时候,你该听出来的其实是一句警告——他刚刚承认了自己在同一段历史上试过很多遍。
系统层:框架给这个功能加了哪几道护栏
有意思的地方来了。freqtrade 的开发者显然清楚这个功能的毒性,所以在它周围放了好几处设计。挨个看,你会发现每一处都在对抗错配的某一个侧面。
护栏一:换靶子——目标函数是可以插拔的
优化器怎么判断「这一轮比上一轮好」?靠一个打分函数。这个函数在框架里不是写死的,而是可以换的,源码里内置了十来种,你也可以自己写。
几种典型的换法值得翻译一下:
- 有的按夏普比率打分——夏普是「每承担一份波动,能换回多少收益」的比值,它逼着优化器不能只看总收益,还得看这条曲线抖不抖。
- 有的按索提诺打分,跟夏普类似,但只算下跌方向的波动——因为往上的波动不是风险,是好事。
- 有的按卡玛比率打分,把收益跟最大回撤(历史上从高点到低点最惨的那一段跌幅)挂钩。
- 有一种做法很有想法:把每个交易品种各自的收益回撤比分开算,然后取里面最差的那个当分数。 它防的是一个很具体的骗局——一两个表现极好的品种,把整体指标撑得很漂亮,而那些表现糟糕的品种在总数里被稀释掉了,优化过程根本没在意它们。
- 还有一种是综合型的:同时看总收益、回撤、盈亏比、期望值(平均每笔交易的净期望收益)、胜率好几个维度,并且对交易笔数太少的结果直接扣分。
这一整排设计在做同一件事:让「历史上好看」变得更难被运气满足。 运气很容易把一个数字顶高,不太容易同时把五个数字顶高;容易在一两个品种上发威,不容易在全部品种上同时发威。这是实打实的改进。
那种「交易笔数太少就扣分」的做法,是这里面最扎实的一条。原因很朴素:样本量少是运气最容易得手的地方。 一个只做了七八笔交易的漂亮成绩,跟连着抛出七八个正面没有本质区别。把这条硬编进目标函数,等于替你挡掉了一整类假冠军。
但它挡不住什么? 换靶子改变的是「什么样的历史算好看」,没有改变「评分依据依然只有这一段历史」。你把靶子换了个形状,靶子还钉在同一堵墙上。
更麻烦的是第二层:你挑选目标函数这个动作,本身就是一次搜索。 按总收益跑出来不好看,换夏普跑一遍;夏普不行,换回撤口径再跑。你以为自己在「选择合适的评价标准」,实际上做的事情和调参数完全同构——这正是 数据窥探 讲的那个分母问题,只不过这次被搜索的是尺子而不是旋钮。
护栏二:样本外——框架给了工具,但不替你做
优化命令上有一个时间区间的开关,你可以指定「只用这一段历史来优化」。文档里把它放在「用一个较小的测试集来跑」这个标题下面,一句话带过。
就是这个不起眼的开关,撑起了整套缓解手段里唯一真正有效的那一个:把历史切成两段,前一段用来找参数,后一段一次都不碰,留着最后验一次。这条纪律的完整道理在 样本内与样本外 里讲得很透,把这场考试重复很多次的做法在 滚动前进验证 里。
现在请注意这里的不对称:
框架提供了这个开关,但它不强制、不提醒、不在你没用的时候报警。 你什么都不指定,它默认拿你给的全部数据跑优化,然后把冠军端给你,一句话都不会多说。默认路径,就是错配最严重的那条路径。
这几乎是所有工程工具的通病,但在这里后果格外重:最有效的那道护栏,是唯一一道完全靠你自觉的护栏。
还有一处更隐蔽的漏水点。假设你很规矩,切了段、优化完、拿后半段验了一次——结果不好看。你会怎么做?大概率是回去改改规则、重跑一轮优化,再拿后半段验一次。
那段样本外已经废了。 从你第一次看它开始,它就变成了另一本习题册,而这个变化没有任何提示音。工具这边的账本只记录它自己跑了多少轮,你按了多少次「重来」,它一个字都不记。 唯一的账本在你脑子里,而人脑对这种数字的记忆能力,大家都清楚。
护栏三:不只给冠军,给你整份名单
跑完之后,框架提供了把历次结果全部列出来、并按条件筛的子命令——按交易笔数筛掉样本太少的、按平均收益筛、按目标值筛、只看盈利的、只看历史最佳的。
这个功能的字面用途是「方便你从结果里挑」。但它真正的价值在另一处:它让你看得见分母。
那个扔硬币的比喻里,最要命的一步是把九百九十九个失败者请出房间。而这份完整名单,等于把他们又请了回来。你亲眼看到「有几千组参数被试过,其中绝大多数难看得很,只有排在最前面的那几组像样」——你对冠军的态度会跟只看到一行时完全不同。
配合着看还有一个更实用的用法:把冠军旁边那些参数相近的结果调出来,看它们塌不塌。 邻居都还行,你手上是一块能站人的地;只有冠军一枝独秀,那是一根针尖。这套判断法在 参数高原与参数尖峰 里有完整的体检清单。
但这是把双刃刀。 同一个筛选功能,你既可以用来看分布,也可以用来「筛到好看为止」——挑一组筛选条件,把不合心意的结果全过滤掉,剩下的那个当结论。这就又是一轮搜索。功能是中性的,取决于你是在看形状,还是在挑冠军。
护栏四:框架主动把自己变笨
这一处是整个工具里我觉得最诚实的设计。
对于那些取值连续的参数(比如止损放在多少个百分点),框架主动限制了它们的小数精度——不允许你把搜索细分到任意精细的程度。文档里给出的理由写得毫不含糊:比这更精细的取值,通常只会得到过拟合的结果。(具体限制到几位小数是个可改的设定,以你手上版本的文档为准。)
停下来品一品这件事的性质:一个以「找到最优」为职业的工具,主动砍掉了自己一部分寻优能力,因为它知道那一部分找到的「最优」是假的。这不是技术限制,这是价值判断被写进了代码。
跟它配套的还有一条提示。当搜索跑到一定程度,程序会开始反复报告「这个点之前已经评估过了」。文档解释得很直白:这说明你的搜索空间快被跑穿了,所有的组合基本都试过一遍,算法找不到没去过的地方了。
这条提示的言外之意值得琢磨——你的旋钮组合是有限的。 假设你只开放了一个旋钮、它只有十几种取值,那么跑到十几轮之后,后面所有的轮次都是在重复劳动。
而很多人看到这条提示的第一反应是:再加几个旋钮。
这个反应正好走反了。旋钮数量和组合数是乘起来的关系,每多开一个旋钮,噪音能藏身的角落就乘一次。你不是在给策略增加表达能力,你是在给运气增加藏身处。 搜索空间被跑穿其实是件好事——它意味着你的规则简单到可以被穷举,而简单本身就是抗过拟合的。
护栏五:随机起点,和一次免费的稳健性测试
搜索不是从零开始的,它先撒一小批随机组合作为起点,然后才让采样算法接手往下收敛。这批随机起点由一个种子控制,你可以指定它来复现同一次运行。
文档里跟着一句话,我认为是整份文档里最诚实的免责声明:用不同的随机种子多跑几次,很可能得到不一样的结果。
把这句话翻成人话:换个起点,就能换出一组不同的「最优参数」。 那么「最优」这两个字的含金量,你自己掂量。
但这句话同时也白送了你一个检验工具,而且是这套系统里最便宜的一个:换几个种子重跑,看几次跑出来的参数落在哪里。 几次结果扎堆在相近的区域,说明那一带可能真是一片平缓的高原;每次都跑到地图上完全不同的角落,说明你面对的是一片全是孤峰的地形——这种地形上,「最优」只是「这次运气抽到的那根针」的别名。
这个测试不需要你懂统计,也不需要额外的数据,只需要你愿意多跑几遍并且愿意接受一个可能很难看的答案。
为什么这些手段只能缓解,不能消除
把五道护栏摆在一起看,会发现它们有一个共同的作用位置:它们全都在改进「怎么从这段历史里挑」,而错配发生在「你只有这段历史」这件事上。
挑的过程可以做得越来越讲究,原料没变过。
往下还有三层,一层比一层难对付。
第一层:样本外也是历史。 那段你没看过的数据,唯一的特殊之处是「你还没看过」。它对抗的是你的偷看,不是市场的变化。一套规则在样本外表现良好,说明它至少不是纯粹为了拟合前半段而生的——这是一个真实的信息,但它离「未来能用」还差着一整个未来。
第二层:护栏本身也会被你搜索。 换目标函数、换切分点、换随机种子、换筛选条件——每一样「防过拟合的手段」,落到你手上都变成了一个新的、可以反复试的旋钮。防过拟合的工具,用法不对的时候,本身就是过拟合的新维度。 这是这一节最反直觉的一句话,但它成立得非常彻底:只要一个东西可以被反复试、并且你只保留最满意的那次结果,它就在生产过拟合,哪怕它的名字里带着「验证」两个字。
第三层,也是任何工具都碰不到的那一层:市场会变。 假设你完美地绕开了所有统计陷阱,假设你找到的那个规律在历史上货真价实——它仍然可能因为发现它的人越来越多而慢慢消失。这属于策略衰减的范畴,模型衰减 那篇讲了它长什么样。优化器连这一层的存在都不知道。
所以那个常见的问题——「有没有什么办法能优化参数又不过拟合」——本身就问错了。它假设过拟合是一个可以被技术手段解决掉的 bug。它不是 bug,它是这件事的形状。 你能做的只有把它压小,压到你愿意承担的程度,然后诚实地把剩下的那部分标在结论旁边。
那这个功能到底还用不用
用。但要把它的定位整个换掉。
不要拿它找最优参数,拿它画地形图。
同一次运行、同一堆输出,两种读法:
- 找最优的读法:跳到排行榜第一行,抄下那组参数,写进策略。
- 画地形的读法:把整份名单摊开,看成绩的分布长什么样,看冠军周围塌不塌,看换个种子重跑还落不落在同一片区域。
第二种读法产出的不是一组参数,而是一个判断:这套规则对参数敏不敏感。 这个判断比那组参数有价值得多,因为它回答的是「这个想法有没有一块能站人的地」,而不是「哪个点站着最舒服」。
配套的三条使用姿态:
先看分布再看冠军,顺序不能反。 一旦你先看了冠军那个漂亮数字,后面所有的判断都会被它拽着走——这是人性,不是纪律问题,所以要靠顺序来防。
优化之前就把最后一段历史锁进抽屉,并且只开一次。 不是跑完再想起来切,是动手之前就切好。开箱那一刻的结果是多少就是多少,不好看就是不好看,回去重跑再验的那个数字已经没有意义了。
给你自己记一本账。 工具的账本只记它跑了多少轮,不记你按了多少次重来、换了几个目标函数、挪了几次切分点。你的那本账才是真正决定结论可信度的分母,而它只存在于你的自觉里。
最后留一条比任何统计检验都实用的判断标准:如果一套规则必须靠优化才能变得好看,那它本来就不好看。 优化的合理位置,是在一个已经能大致站住的想法上做微调,而不是把一个不成立的想法调到成立。一个想法在参数随便取的时候都还行,优化只是让它更顺手——这种才值得继续;一个想法只有在某几个特定的数字上才活得下来,那你调出来的不是参数,是一个借口。
顺带说清两处边界:框架里那套专门用来检测回测本身有没有作弊的工具(比如策略偷看了未来数据),跟本篇讲的过拟合是两类不同的问题,放在 一个框架专门造工具来防的坑 里讲;而把机器学习整套接进这个系统之后,参数问题会变成什么样,见 FreqAI:机器学习长在一个真实交易系统里是什么样。
小结
- 优化器的目标是「历史上最好的参数」,你的目标是「未来还能用的参数」,这两件事之间隔着一整个不存在的数据集——这不是工具做得不够好,是这件事的物理边界。
- 优化器越强,错配越危险:一个能把所有组合都试完并精确排序的搜索,一定会把运气最好的那一组顶到第一名。
- 框架的五道护栏各有对手:换目标函数对付「单一维度的运气」,样本外开关对付「用同一段数据既找又验」,全量结果列表对付「看不见分母」,主动限制搜索精度对付「假精确」,随机起点则白送你一次稳健性测试。
- 它们全都作用在「怎么挑」上,而问题出在「只有历史」上;更麻烦的是每道护栏本身都能被你反复试,防过拟合的手段用错了就是过拟合的新维度。
- 把这个功能从「找最优参数」改成「画参数地形图」:先看分布再看冠军,事先锁好最后一段历史且只开一次,自己给自己记一本重跑次数的账。
一句话说完这篇:这台机器能替你在过去的一万条路里,找出走得最顺的那一条;但它没有任何办法知道明天该走哪条——它连「明天」这个词都没听说过。
本文基于开源仓库的源码与文档快照做概念讲解,未实际运行该程序,也不构成任何投资建议或软件使用建议;相关边界见免责声明。想系统看这一卷的其他内容,回到量化与 AI 投研卷。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- freqtrade 文档 docs/hyperopt.md(本地仓库快照 commit b3404c9,2026-08-18 读取):开篇对整个过程的描述与算力提示、损失函数一节的内置清单、时间区间参数一节、随机起点与可复现性一节、小数精度限制的注解、「这个点之前已经评估过了」一节 ↗
- freqtrade 文档 docs/utils.md(同一快照):跑完之后列出与筛选历次结果的子命令说明 ↗
- freqtrade 源码 freqtrade/optimize/hyperopt_loss/ 目录下的各个损失函数实现、freqtrade/optimize/hyperopt_epoch_filters.py(同一快照,仅作概念注解,未实际运行该程序) ↗