参数高原与参数尖峰:为什么「最好的那个参数」常常是最不能用的
你把一条均线的天数从 5 一路试到 60,跑完一看,第 23 格最亮眼:收益最高、回撤最小,曲线漂亮得让人想立刻上真钱。你很自然地把 23 写进了策略里。
先别急。回头看一眼 22 和 24。
如果 22 和 24 也差不多好,那你手里大概是一块能站人的地。如果 22 和 24 平平无奇甚至亏钱,只有 23 一枝独秀——那这个 23 多半不是市场的规律,是这段历史数据的一个巧合。而你刚刚把全部身家押在了这个巧合上。
先解释一个词:参数,就是你策略里那几个可以拧的旋钮——均线看几天、某个指标超过多少算超买、止损放多远、最多同时持几只。回测(把规则放到历史数据上模拟走一遍)里挑参数,本质就是在一堆旋钮位置里挑一个。而这篇要讲的是:挑的时候该看什么。
先说人话:高原和尖峰长什么样
想象你在调一个老式热水龙头。有的龙头,旋钮转过去一大截,水温都是舒服的温热——你闭着眼睛拧到大概位置就能洗澡。有的龙头,只有某一个极窄的角度是温的,偏一丝就冰、再偏一丝就烫。
两个龙头你都能找到「最佳角度」。但只有第一个是能用的。第二个哪怕今天让你找到了那个角度,明天手一抖、水压一变,它就不在那儿了。
高原就是第一个龙头,尖峰就是第二个。
再换个更扎心的说法。一份菜谱写「盐 3 克」,你放 2.8 克或 3.3 克,菜都好吃——这是一份真菜谱。另一份写「盐 3.7 克,多 0.2 克就难吃」,那它记录的多半不是烹饪规律,是那位厨师某一天的一次偶然。你按它做,做不出那道菜。
所以判断一个参数好不好,从来不是看它自己有多好看,而是看把它左右挪几格,结果塌不塌。旁边不塌,它就有可能在讲一件真事;旁边一塌,它讲的就是这段历史的私事。
在投资里,这意味着什么
你真正该盯的不是最高点,是它周围的坡
大多数人跑完参数优化,只看一个东西:排行榜第一行。系统也确实只把第一行推给你。但排行榜第一行恰恰是最容易被噪音顶上去的那一行——因为噪音本来就是往上顶得最猛的那种力量。
换个看法:把所有试过的参数和它们的成绩摊平铺开,你会看到一片起伏的地形。你要找的是一片连成一整块的、平缓的隆起,而不是平地上突然冒出来的一根针。哪怕高原的最高点比针尖低一截,也该选高原——因为你下个月要走的路,不在你已经量过的这张地图上。
一个直白的心理测试:如果告诉你「必须闭着眼睛在这个参数附近随机落一脚」,你敢落在哪片区域?敢落的那片就是高原。只有站在针尖上才活得下去的策略,你其实一直就站不稳。
尖峰不是你「找到」的,是搜索过程「造出来」的
这一点最反直觉,也最重要。
参数搜索的过程是:试一组、记一个分数,试一组、记一个分数……试上几千次,从里面挑最高的。问题在于,历史数据里本来就有大量随机的、不会重复的波动,任何一组参数的成绩里都掺着一份运气。你试的组合越多,「光靠运气就跑出一个漂亮分数」的组合出现的概率就越高。
换句话说,只要你搜得够久,你一定能找到一个成绩惊人的参数。找到它,不能证明策略有效,只能证明你搜得够久。这也是为什么参数越多越危险——每多一个旋钮,可能的组合数是乘上去的,噪音能藏身的角落也跟着乘上去。这件事在 过拟合:把噪音当成规律 那篇里有更系统的展开;而「一个人反复换想法去试」造成的同类污染,见 数据窥探。术语卡式的一句话版本在 过拟合。
高原之所以是好的信号,正是因为噪音很难制造出一整片连续平缓的地形。噪音擅长制造孤峰,不擅长制造高原。
尖峰的三个体检指标
一,邻域塌不塌。 上下各挪两三格,成绩掉一半以上,警报。
二,撑起它的交易有多少笔。 一个年化很高但只做了七八笔交易的结果,跟抛七八次硬币全是正面没有本质区别。样本太少的时候,任何统计数字都不作数,这是 大数定律 那篇讲的老问题在参数搜索里的翻版。
三,换一段时间还在不在。 把历史一切两半,前半段找出来的最优参数,在后半段还是不是那一片区域?如果最优点跳到了完全不相干的地方,说明你两次都只是在各自的噪音里挑点。这属于 样本内与样本外 的范畴,那里讲得更完整。
还有一个懒办法,管用得出奇:换个随机种子再搜一遍。 参数搜索本身通常带随机性,起点是随机撒的。如果两次搜出来的「最优参数」落在同一片区域,那片区域可能是真的;如果两次结果各跑各的,你找的从头到尾就是噪音。freqtrade 的文档在这件事上说得很直白——用不同的随机种子多跑几轮,很可能得到不一样的结果。要自己动手看一眼的时候,过拟合自查与参数高原工具可以把每个参数值和它对应的绩效两列贴进去,再设一下邻域半径,它会算出最优点周围掉得有多快。
系统层:三个系统各自怎么防着你掉进针尖
概念说完了,看看真实系统里这件事长什么样。有意思的是,它们防的角度完全不同。
| 视角 | 做法 | 一句话说清 |
|---|---|---|
| freqtrade · 搜索空间 | 参数空间强制离散化 | 把格子调粗,让你压根落不进针尖 |
| freqtrade · 结果查看 | 全量结果列表与筛选 | 让你看得到地形,而不只是冠军 |
| freqtrade · 目标函数 | 交易笔数惩罚项 | 样本不够的漂亮成绩,评分阶段就先打折 |
| qlib · 模型超参 | 验证集打分 + 叶子最小样本数 | 不许为了少数几个样本单独开一条规则 |
| Vibe-Trading · 验证 | 稳定性做成一等指标 | 只报均值不够,还要报波动和一致率 |
一 · 把格子调粗:让针尖无处落脚
freqtrade 定义可搜索参数时分了几类,其中处理小数的那一类值得单看。它默认限制小数位数,把连续区间切成有限的格点;文档里给的理由非常干脆:比这更精细的取值,通常只会得到过拟合的结果。它还提供了一个完全不限精度的版本,但文档标注为「很少使用」,因为那会造出一个近乎无限种可能的空间。
底层实现就是把小数位数换算成一个固定步长:
# 只允许二选一:给步长,或给小数位数
self.step = step or (1 / 10**decimals if decimals else 1)
(默认的小数位数是当前版本的默认值,可以配置。)
这个设计的哲学值得琢磨:它不是在帮你找更好的参数,是在拦着你找得太细。 参数格子越细,你越容易挖到一个只在那一格成立的巧合;把格子调粗,能被找到的就只剩下那些「一整片都成立」的区域。牺牲的是理论上的最优,换来的是找到的东西更可能是真的。
整数型参数更能说明格点的性质。文档举了个例子,一个取值范围只有十几格的整数参数,搜索几十轮之后就会开始反复提示「这个点之前已经算过了」——因为空间本来就被穷尽了。这句提示第一次见到会以为是错误,其实它在告诉你一件好事:你的参数空间是有限的、可以被走遍的。反过来说,如果你的空间大到永远走不完,你就永远不知道自己站的是高原还是孤峰。
二 · 让你看见地形,而不只是冠军
搜索完只看第一名,是所有毛病的源头。freqtrade 提供了打印全部结果的选项,以及事后重新列出、逐条查看某一轮结果的命令。更有意思的是它的结果筛选逻辑,允许你按「只看盈利的」「交易数不少于多少笔」「交易数不多于多少笔」这些条件过一遍:
if filteroptions["only_best"]:
epochs = [x for x in epochs if x["is_best"]]
if filteroptions["only_profitable"]:
epochs = [x for x in epochs if x["results_metrics"].get("profit_total", 0) > 0]
这些筛选条件的存在本身就是一句话:优化结果是一份需要被人工挑拣的清单,不是一个可以直接抄走的答案。 尤其是那个交易数下限——它默认就假设你会想把「笔数太少」的结果先扔掉。
三 · 在评分阶段就给样本不足的结果打折
更硬的一招是把这件事写进目标函数(打分规则,决定搜索朝哪个方向走)。freqtrade 的多指标目标函数里有一段专门算「交易笔数惩罚」:笔数低于某个目标值时,最终得分要乘上一个小于 1 的系数,笔数越少乘得越狠,但设了下限不至于归零。文件顶部的注释明确写着这个目标笔数是可调的。
它的效果是:一个「年化极高但只有寥寥几笔交易」的参数组合,在评分阶段就被压下去了,搜索算法根本不会往那个方向追。这跟事后筛选是两个层次——事后筛选靠你自觉,惩罚项让偏好长在系统里。
同一个文件里还有几个类似的系数,分别调节回撤、盈亏比、期望值、胜率各占多大分量。这透露了另一件事:「什么叫好」本身就是一个你要拍板的选择。 只盯着总收益去搜,搜出来的一定是最极端的那一组;把回撤和交易频率也放进目标里,搜索会自动倾向于更平缓的区域。
四 · 机器学习那边的同一件事
qlib 的超参搜索示例是这个概念在机器学习侧的镜像。它一次性把十几个模型超参(学习率、采样比例、正则强度、叶子数等)全交给搜索器去调,搜索目标写成一个函数:
model.fit(dataset, evals_result=evals_result)
return min(evals_result["valid"])
注意 valid——它优化的是验证集上的成绩,不是训练集上的。这是最基本也最容易被忽略的一道防线:如果你拿训练集打分,模型会毫无悬念地把训练集背下来,那个「最优」百分之百是尖峰。
搜索空间里还有两个参数特别值得点名:一个限制每片叶子最少要有多少条样本,另一个限制每个节点最少要有多少个子样本。翻译成人话就是——不许为了少数几个样本单独开一条规则。 这跟「不要相信只有七笔交易撑起来的参数」是同一句话,只是一个说给模型听,一个说给你听。同一个仓库里的梯度提升模型还内置了早停机制(当前默认轮数可配置),验证集不再改善就停手,也是同一个方向上的约束。
顺带一提,示例里叶子数的搜索范围从个位数一直开到上千,学习率从零开到一——这么大的空间意味着搜出来的最优点几乎必然需要邻域检验。空间越大,越不能只看冠军。
五 · 把「稳不稳」做成一个指标
Vibe-Trading 的验证模块换了个维度:它不检验参数的邻域,而是检验时间的邻域。它把回测区间切成若干段,每段单独算收益、夏普、最大回撤、胜率,最后不只报平均值,还报各段收益的标准差、各段夏普的标准差,以及「有多少段是赚钱的」这个一致率。
这个设计很值得抄进你自己的判断习惯。一个平均年化很高但只有一段赚钱、其余全亏的结果,和一个平均年化中等但每段都小赚的结果,在只看均值时长得一模一样;把波动和一致率摆出来,它们立刻分出了高下。高原思维的本质就是:别只报中心值,报出周围的样子。
同一个仓库的分析流程里还有一条更狠的体检:把最赚钱的几笔交易删掉,看策略是否还赚钱。如果删掉几笔就翻脸,那这套规则的收益是几次偶然事件贡献的,不是规则本身贡献的——这是「尖峰」在交易层面的另一副面孔。
高原不是免死金牌,它只是「不是巧合」的必要条件。 一片平缓且成绩平平的区域,仍然是不赚钱的策略;一片看起来平缓的区域,也可能只是因为你的参数根本没起作用(比如某个阈值设得太宽松,怎么调都不触发,成绩当然纹丝不动)。看到高原,下一个该问的问题是「这个参数到底有没有在影响结果」,而不是直接放行。
失效边界与常见误用
把一维扫描当成全貌。 你固定其它旋钮、只挪一个,看到的是这条线上的地形。真实空间是多维的:某个参数单看是高原,配上另一个参数的特定取值可能就是一道窄脊。参数之间有交互作用时,逐个体检会给你虚假的安全感。
用样本内数据找高原。 在同一段数据上又搜参数又验邻域,两件事用的是同一份噪音,高原也可能是噪音的形状。邻域检验必须和样本外检验配合使用,单独用它不够。
把「参数少」当成安全。 显式参数只是一部分。你换过多少个指标组合、试过多少种进出场逻辑、砍掉过多少个不好看的想法——这些都是没写进代码但真实存在的自由度。一个只有两个参数、却是你试了三十个想法之后剩下的策略,比一个有五个参数、一次就定型的策略更可疑。
为了追求高原而把参数范围设得极窄。 有人发现范围一窄,曲面自然就平了。那不是高原,那是你把地图裁得只剩山顶。检验邻域的前提是搜索范围本来就足够宽。
混淆「参数稳健」和「策略稳健」。 参数不敏感,说的是这套规则不依赖某个精确刻度;它不保证规则在新环境里还成立。市场结构变了,整片高原可能一起沉下去。这也是为什么均线类参数尤其容易骗人——均线(MA/EMA) 的取值本身就带着强烈的「过去这段行情节奏」的印记。
忘了回测本身还有别的坑。 就算参数稳健、样本外也过关,回测和实盘之间还隔着成本、滑点、成交假设那一整层。回测 这张卡和 量化与 AI 投研卷 里的其它篇目讲的正是这些别的坑。
小结
- 参数好不好,看的不是它自己的成绩,是把它左右挪几格之后成绩塌不塌。不塌是高原,塌了是尖峰。
- 尖峰不是被发现的,是被搜索过程制造的:试的组合越多,纯靠运气冒出一个漂亮结果的概率越高。所以「搜到了惊人的成绩」本身不是好消息。
- 三个不用写代码的体检动作:邻域左右各挪几格看塌不塌、看撑起它的交易有多少笔、换一段时间看最优区域还在不在。加一个懒办法——换个随机种子重搜,看最优点跑不跑。
- 系统里的对应做法各占一层:把参数空间调粗使针尖无处落脚、把全部结果而非冠军呈现出来、在目标函数里给样本不足的结果打折、用验证集而非训练集打分并限制每条规则的最少样本、把「各段之间稳不稳」做成显式指标。
- 高原是必要条件不是充分条件。平缓且平庸的区域仍然平庸;参数不敏感也可能只是因为它压根没起作用。
一句话记住这篇:一个只有拧到那个刻度才好使的东西,你迟早会拧偏。
本文所引源码与文档均来自上述快照版本,是阅读代码得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。回 量化与 AI 投研概念库 看其它概念各自在说什么;本站内容为投资教育,不构成任何投资建议,边界见 免责声明。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- freqtrade 源码快照 b3404c9(2026-08-18):docs/hyperopt.md、freqtrade/optimize/space/decimalspace.py、freqtrade/optimize/hyperopt_epoch_filters.py、freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py ↗
- qlib 源码快照 79633dd(2026-07-23):examples/hyperparameter/LightGBM/hyperparameter_158.py、examples/hyperparameter/LightGBM/Readme.md、qlib/contrib/model/gbdt.py ↗
- Vibe-Trading 源码快照 3a752d5(2026-08-04):agent/backtest/validation.py、agent/src/agent/context.py ↗