1. 先搞清楚过拟合在期货量化里意味着什么
做期货量化的人,很少有谁没碰过“参数优化”这个环节。把均线周期一拖,止损线位数一拉,点一次回测,年化从 8% 变成 35%,谁看了都心动。我在做螺纹钢、橡胶这类品种的日内 CTA 和隔夜波段策略时,也做过太多这样“顺手”的优化。结果毫无例外:参数换一个区间就失灵,换一个品种就失效,甚至同一段数据多往后推两天,绩效曲线就连续回撤。
这个问题的核心,不是“参数优化”本身可恶,而是我们对它的使用方式太粗暴了。参数优化本质上是把策略的参数空间扫一遍,寻找让历史表现最好的组合。问题在于,历史数据里的信息分成两类:一类是相对稳定的市场规律,一类是纯粹的噪声。参数优化算法不区分这两类信息,它只会找一个在历史上最“贴脸”的参数组合。噪声被大量记忆下来,就成了过拟合。
很多朋友一听“过拟合”就以为只有那些跑深度学习、几百个因子的团队才会遇到。这是误解。哪怕你只有一个双均线系统,两个参数,一样可以过得严严实实。因为过拟合不完全取决于参数数量,而取决于你给参数留了多少“自由发挥”的空间。观察一次历史行情里均线金叉后顺利拉出大阳线,于是你就把均线周期微调成恰好能抓住那一次行情的数值;止损设在恰好离最低点差两个 tick 的位置;你觉得自己在优化策略,其实是在做一笔事后诸葛的模拟。
1.1 过拟合不是“参数多”的专利
我见过不少新手逻辑:我的策略只有两三个参数,肯定不会有过拟合问题吧?这个直觉是错的。
过拟合的成因是“复杂度的自由度超过数据有效信息的支撑能力”。哪怕参数很少,只要你在选参时看的是同一段历史数据的多次试错,每一次试错都在“偷看答案”。看十次你就对那段行情形成了极强的记忆。参数多的策略只是让这件事更容易发生,但真正引发质变的因素,是你对同一段数据进行了多少次切片式的“优化借鉴”。
举个例子。你用某一年的螺纹钢行情调均线周期,从 10 到 60 每隔 1 测试,一共 50 次,选到最漂亮的一组。请问:这个过程中的每一次回测,是不是都在消耗你对这批数据的新鲜感?是。即便你的模型只有一条均线和一个固定止损,你也等于在这段数据上做了 50 次模式匹配,选出了一个最巧合的答案。换一段行情,大概率就不是那么回事了。
这个道理我用了一个很生活化的比喻理解它:期末考试的题目你反复背了十遍,连每个选项的陷阱都能背出来,你觉得自己考得很好,但换成一套新题目,你会发现自己对知识点的理解并没有那么牢。参数优化和考试背诵的差别只在于:优化过程不带心理负担,看起来很理性,实际上比盲目背诵更危险。
1.2 过拟合在期货策略中的具体表现
过拟合的实盘症状有迹可循,不用等真正的实盘亏损才去排查。我总结过自己在品种和策略里反复见到的几种信号:
- 参数敏感度极高:周期参数从 20 改到 19,绩效从 30% 年化变成 -12%;改到 21 又变成 +28%。这种陡峭的“参数悬崖”极大概率是过拟合。
- 单品种特化:某个参数组合在焦煤上特别猛,但你放到热卷、铁矿、甲醇上一试全崩。这通常意味着你只是记住了焦煤某段行情的形状。
- 样本内和样本外差异巨大:前面三年曲线漂亮,后面两年无论怎么回放都明显衰减,甚至亏损。
- 换月、交割、复权方式不同,绩效剧烈变化。真实可用的策略,对数据细节应该有稳定性,除非你交易的就是跨期价差。
- 优化出的止损位带有“无意义精度”,比如止损 17 个 tick、止盈 53 个 tick。市场不会精确到这种“恰好”的程度。这类参数是典型的噪声记忆。
以上这些表现,大多数不是交易者故意犯错,而是优化流程里没有引入“对抗过拟合”的机制。后面章节讲的就是怎么把这种对抗机制加进日常流程。
1.3 从源头分解:数据、逻辑、执行三层防线
面对过拟合,不能只靠“我少调几次参数”这种意志力。有效的做法是从三个层面设计防线:
第一层是数据层面。入场前就把训练集和验证集分开,并且坚持验证集只使用一次。很多人回测喜欢反反复复在完整数据集上“看效果”,这等于把验证和训练揉在一锅粥里,再也说不清楚哪个是真实表现。
第二层是逻辑层面。策略要有经济学或市场微观层面的逻辑基础。我常问自己:我凭什么觉得这个规律存在?如果答案只是“回测里出现这个信号后往往涨”,那可靠性很低。如果答案能落到“节假日前后资金谨慎、持仓等待节后方向选择”这类可理解的行为逻辑上,参数即便失效,你也知道怎么迭代逻辑。
第三层是执行层面。回测模型要尽量包含滑点、手续费、涨跌停约束、仓位限制。把成本手续写得太理想,优化算法会发现你的策略大量收益来自成交价完美跳过障碍——这不是策略优势,是回测缺陷。执行层面的失真会量产“操作性过拟合”。
这三层防线,每一层都是独立的。单靠任何一层都不够,但一起做,能拒绝掉绝大部分“好看但不中用”的参数组合。
2. 参数优化常用方法盘点:网格、随机与贝叶斯优化
接下来说具体优化的“器”。我在期货策略上跑过的参数优化方式主要就三种:网格搜索、随机搜索、贝叶斯优化。它们的目标一致,但计算开销、搜索效率、使用场景完全不同。
2.1 网格搜索:最有直觉但最容易迷失
网格搜索就是把参数空间按步长切分,计算每个组合的表现。比如双均线策略,快线 5 到 50,慢线 20 到 200,步长 5,一共就有 10×37 等于 370 组组合。加上止损、仓位规则,组合数量很快就膨胀。
网格搜索最大的优点是简单、可解释,而且能输出一张完整的“参数平面”,方便你观察绩效在参数空间中的分布。这一点对防过拟合很有价值,后面我会专门讲参数平面分析。但它有两个代价:一是计算耗时随维度指数增长;二是它容易让人把“选定一组最优”当作终点,忽略邻域参数的表现。
我自己不会完全抛弃网格,但会把网格当作三维可视化工具来用,而不是当作参数决策器。先画图看全局,再决定该往哪个区域深入。
2.2 随机搜索与贝叶斯优化:什么时候用
当参数维度上升到 4 个以上,网格组合很容易破万组。这时候该用随机搜索或贝叶斯优化。
随机搜索的思路很简单:在参数空间内随机采样,比如跑 2000 组,观察绩效分布,挑比较好的区域继续采样。它比网格更有优势的地方在于,在“参数数量多而有效区间窄”的场景里,随机采样更容易碰到那些不一样的可能性。网格搜索如果步长选得不好,可能恰好跳过全部有效参数。
贝叶斯优化更聪明:它会根据之前测试的结果,用一个概率模型估计“参数空间各处的潜在效果”,然后优先探索那些既有希望、又存在不确定性的区域。对于回测慢的策略,这是效率最高的方案之一。期货策略的回测如果包含分钟级数据和复杂的撮合逻辑,几分钟才能跑一组参数,网格完全不现实,贝叶斯优化可以帮你把有效探索次数降到几百组以内。
我需要提醒一点:不要因为贝叶斯优化听起来高深就迷信它。它给出的一组“最优参数”同样可能是过拟合尖峰,因为它只是在效率更高地搜索历史表现最好的点。你需要做的事和网格搜索一样:检查参数的邻域是否稳定、样本外是否有效。
2.3 从我实盘角度出发,优化方式的选型建议
我的建议不复杂,照着用就行:
- 参数只有 2 到 3 个,且回测速度快,优先网格搜索,同时生成参数平面图做敏感性分析。
- 参数 3 到 5 个,回测速度中等,用随机搜索先摸分布,再用局部网格精修。
- 参数 5 个以上,或者单次回测超过 1 分钟,用贝叶斯优化,但必须配合样本外验证。
- 无论用哪种优化方式,最终交付的参数必须通过“平坦区域检验”和“时间外样本检验”,否则一律不用。
还有一个容易忽略的问题:不要只做一次优化就完事。我习惯按滚动窗口做多次优化,观察“最优参数”随时间是否会漂移。如果最优参数在近五年内每年都不一样,说明策略对参数依赖太强,本身就很不健康。这个点放到第五部分详细展示。
3. 避免过拟合的五个核心技巧
这一节是整篇文章的灵魂。我按下述五个技巧在实践中逐一使用,它们共同抵御了绝大多数无效优化。
3.1 时间滚动样本内外验证:别做“一次性拟合”
最常见的错误,是把全部历史数据丢进去优化,再在同样的数据上评估绩效。那是典型的“开卷考试”。正确的做法是,把数据按时间切开,用一段数据训练参数,用另一段数据检验参数。
以我的习惯为例:想要测试一个日内突破策略在最近几年的表现,我不会把 2019 到 2024 年全部塞进优化器。我会先用 2019 到 2022 年做参数扫描,选定一组参数,然后把 2023 到 2024 年当作从未看过的样本外数据,只跑一次验证。如果样本外绩效显著低于样本内,这组参数就不合格。
更进一步的做法是滚动前推验证,也叫 walk-forward。它的流程是:在时间点 t 训练,在 t+1 段验证;然后把训练窗口往前推,再训练,再验证。这样的好处是,能观察到一个策略在不同市场环境下的参数稳定性。我一般把数据切成 3 到 4 段,每段训练、验证循环一次,取验证结果的平均值作为策略的有效表现,而不是单看某一次验证曲线的最高峰。
这里有一个非常现实的操作细节:验证集只能使用一次。如果你看完样本外表现不好,马上调整参数,再重新跑一次验证,那你已经“污染”了验证集。第二次的样本外表现不再是独立检验,它已经携带了你的主观矫正。所以一旦开始验证,就要忍住“再调一下”的手痒;若确实要迭代,就把验证集再往后推移,引入真正新鲜的数据。
3.2 参数平面分析:挑平稳高原,而不是孤峰
网格搜索不仅是为了找出“最大值”,更关键的是看绩效平面长什么样。
我举一个自己 2022 年处理过的例子。策略是常见的唐奇安通道突破,参数包括入场周期 N 和止损倍数 M。画出的绩效热力图显示:在 N 介于 20 到 45、M 介于 1.5 到 2.5 的区域,年化收益呈现出不太平滑的连接带,整体差异不超过 5 个百分点。但当你把 N 调到 38、M 调到 2.1 时,会出现一根孤立的尖峰,年化瞬间高出其他组合 18 个百分点。那根尖峰就是过拟合的最典型标记。
为什么?因为真实市场规律通常是缓慢渐变的:相邻参数表现差异不大,说明策略对参数选择不敏感,任何一组邻域参数都能抓到类似行情。而孤立尖峰的出现,意味着只有那一组参数恰好匹配了历史上某几个极端行情的细节,这种匹配很难在未来再现。
操作上,我会在参数数组上选出“表现较好的平坦区域”,然后取该区域的中心点或稳健点,而不是取最大值。如果平坦区域内年化收益都在 18% 到 22% 之间,那取 18% 那组参数是合理的;孤峰 40% 反而不该用。
用代码表达也很简单,选择合适的稳健性统计量,而不是 max:
import numpy as np import pandas as pd # 假设 flat_region 是根据参数平面筛选出的一组候选组合 # 每个元素是 (param_dict, annual_return, max_drawdown) candidates = [ {"fast": 20, "slow": 120, "ret": 0.19, "mdd": 0.12}, {"fast": 22, "slow": 125, "ret": 0.20, "mdd": 0.13}, {"fast": 25, "slow": 130, "ret": 0.21, "mdd": 0.14}, {"fast": 38, "slow": 210, "ret": 0.42, "mdd": 0.11}, # 孤峰 ] # 策略:按绩效排序,但排除孤峰 candidates_sorted = sorted(candidates, key=lambda x: x["ret"], reverse=True) # 孤峰识别:观察它与次优之间的收益差,以及邻域连续性 # 如果最大收益与第二名的差异远大于第二名到第三名的差异,先标记为可疑 ret_gap_1 = candidates_sorted[0]["ret"] - candidates_sorted[1]["ret"] ret_gap_2 = candidates_sorted[1]["ret"] - candidates_sorted[2]["ret"] print(f"最高降次高: {ret_gap_1:.2%}, 次高降第三: {ret_gap_2:.2%}") # 实际选择时宁可要“稳健平庸”,不要“尖峰惊艳” robust_choice = candidates_sorted[2] # 举例:跳过孤峰,选平坦区域内的稳健组合 print(robust_choice)这组代码不是为了直接跑出答案,而是提醒你现在很多现成框架会默认给你“全参数空间最优解”,你需要在业务判断层面拒绝它。
3.3 多品种多周期稳健性:你的秘密武器
单品种的样本外表现都可靠吗?不一定。因为一个品种的历史行情也可能存在偶然段。比如某个品种在某几年走了特别清晰的趋势,你针对这个品种调出来的参数,放到别的品种或别的周期上就没用了。
我的习惯是:任何参数组合都必须同时通过多品种和多周期的复验。以螺纹钢、热卷、铁矿石、焦炭、甲醇这几个流动性较好的品种为例,策略在螺纹钢参数优化后,我会直接把同一组参数放到其他品种上不做任何调整地回测。如果收益大幅度衰减甚至亏损,这组参数极可能是螺纹钢特化形态。
多品种复验不需要每个品种都赚钱,但至少要做到“不出现无法解释的巨大亏损”。如果某个品种产生异常回撤,要分清究竟是策略逻辑不适用该品种,还是优化过程过拟合了螺纹钢。这个区别并不难判断:把参数平原画出来,如果螺纹钢的参数平面有明显孤峰,而其他品种参数平面平淡,那就是特化。
多周期也一样。一个趋势策略如果在小时线上有效,那至少在 30 分钟和日线上不能变成一个彻底亏损的“反向指标”。跨周期一致性不代表直接用同一组参数,而是指逻辑的普适性。如果策略只能在某一个特定周期上有效,其他周期全部亏掉,那要高度怀疑是噪声拟合。
品种和周期的复验有一个附带收益:你会被迫让策略保持简洁。因为每增加一个规则和参数,都会降低它多品种适用的概率。这种“被迫整洁”本身就是防止过拟合的方法。
3.4 用惩罚项约束参数精度
在优化目标中加入惩罚项,压制“高精度参数”和“极端参数”,是另一种有效的防过拟合手段。
我用这样一个思路:策略优化的目标函数不再是单一年化收益最大值,而是“扣除了参数复杂度代价后的净收益”。
常见做法包括:
- 参数精度惩罚:对止损参数限制为 5、10、15、20 这样的整数档位,避免出现 17、23 这类精度,因为那样等于让模型记住了行情细节。
- 参数数量惩罚:参数越多,惩罚越大;每增加一个有实际影响的参数,要求它带来显著的绩效提升,否则不采用。
- 样本外惩罚:把样本外表现作为负向参考,如果样本外和样本内的绩效差距过大,整体评分下调。
我常用一个简单的目标函数,结合夏普比率、最大回撤和参数稳定性:
def fitness(annual_return, sharpe, max_drawdown, param_count, y_margin=0.5): # 参数数量多,减分更多;同时限制回撤 complexity_penalty = 0.02 * param_count score = 0.5 * annual_return + 0.3 * sharpe - 0.4 * max_drawdown - complexity_penalty return score这只是一个示例,权重你完全可以根据风险偏好调整。重点不在公式,而在“参数数量、参数精度必须付出代价”的意识。否则优化算法自然会把所有有利条件统统用上,到头来你得到的是一个记忆库。
3.5 数据泄漏与未来函数检查:这是血泪教训
程序员出身的朋友经常会在处理 K 线数据时,无意中掺入未来信息。比如用当天的最高价去判断当天入场信号,这等于作弊。更隐蔽的是:用全局归一化后的指标去生成信号,归一化参数本身用到了未来数据。
我用过一个经典案例:某个策略在优化后回测绩效优秀,但仔细排查后发现,技术指标计算时我用了全样本的均值与标准差进行标准化。这个标准化参数看起来只是个数据处理细节,却把未来几年的统计信息带进了每一个信号。幸好及时发现,否则这份“策略”上实盘就是稳定亏损。
未来函数的排查,我总结出几个习惯:
- 写回测时,所有指标只能使用截至当前 K 线收盘的数据。
- 如果用滚动窗口计算指标,必须在循环内逐步更新窗口,不能在整段数据上预先算好。
- 对涉及归一化的指标,用历史滚动窗口的均值、标准差,而不是全样本的。
- 逐根 K 线用手工推演几个样本,核对信号发生的时间点是否晚于价格确认,这在调试时比任何自动化测试都直观。
还有一类数据泄漏来自“幸存者偏差”:做商品期货往往还好,因为品种存在退市少,但如果你做的是股票或指数成分股的历史回测,就要注意当时的成分股和现在的成分股不同。期货上的数据泄漏主要来自换月拼接:主力合约切换时的价格跳变如果处理不当,会给策略带来虚拟的跳空盈利。处理主力合约连续时,要么用复权价,要么在回测撮合中明确禁止在换月瞬间以“无缝成交”方式平仓。
4. 期货交易成本与微观结构约束下的参数优化
期货和股票与加密货币不同。期货有保证金、换月、涨跌停、夜盘、不同合约流动性和差异化手续费。参数优化如果忽略这些,等于在一张没有重力的小图上做飞行测试。
4.1 滑点、手续费、冲击成本怎么折算进优化
在参数优化的初始阶段,就要把交易成本写入回测并纳入参数评分。滑点尤其重要:很多短周期策略对价格敏感,优化的参数往往极度依赖“入场价就是最高点之后瞬间反转”等难以实现的成交质量。
我自己处理成本的方式是分档:
- 保守档:每手单边计 1 个 tick 滑点加上手续费。例如螺纹钢一个 tick 是 1 元/吨,一手 10 吨,那么单边成本就是 10 元滑点加合约手续费。
- 严厉档:每手单边计 2 个 tick 滑点,并把手续费按期货公司默认标准加一倍。窄止损策略必须过严厉档测试。
操作时,不要把“滑点”放在优化结束后手动减一下,而是直接在回测引擎撮合成交时按不利方向滑点,并在参数评估函数里加入交易成本。这样优化器会自动避开那些高频次交易、低单笔期望的“伪盈利”区域。
4.2 涨跌停、跳空、换月对参数选择的影响
商品期货有涨跌停制度,连续涨跌停时持仓无法按理想价位平仓。这会导致回测中的“按收盘价平仓”完全不现实。参数优化若偏好设置紧止损,又没有模拟涨跌停约束,很容易高估风险。
跳空是另一个大坑。你在夜盘开盘跳空高开超过止损位,回测模型可能默认以开盘价和止损价中较优价格成交,而实盘只能吃到开盘价之后的市场深度。为了稳健,我在回测引擎里默认:止损失效时,按下一根真实成交 K 线的开盘价并叠加不利滑点成交。这样做会让很多“漂亮参数”现出原形,但换来的真实性极其宝贵。
换月不处理好的后果也很严重。主力合约切换时,旧合约流动性和新合约不同,且价差跳变不是真实可交易的价差。如果回测直接采用主力合约价格序列,换月当天的剧烈价差可能被策略误判为信号,产生虚假盈利。我建议用复权方式拼接连续合约,并在实盘交易规则里设定换月移仓窗口,不要在切换瞬间无成本平仓。
4.3 为什么“完美回测”往往靠不可交易价格实现
很多参数优化得到的高绩效,仔细拆开看,收益往往集中在少数几笔“极端完美成交”的交易上。比如某一天触发了入场信号,成交在极低点;另一天触发了离场信号,成交在极高点。这种回测里的交易区间和时间,充满了“刚刚好”的巧合。
我处理这个问题时有一个习惯:单独统计每笔交易的滑点敏感性影响。做法很简单,按市价模拟时,把入场和离场分别加上 1 个 tick、2 个 tick、3 个 tick 的不利滑点,重新计算收益。如果策略的最终净值对单个 tick 的滑点极其敏感,那说明策略其实是在和交易成本比赛,而不是在和市场规律比赛。绝大多数有效策略,至少应该能承受 2 个 tick 的滑点变动而不改变盈亏结构。
4.4 一套我常用的成本惩罚公式
我习惯把所有交易成本折算成“单边费用比例”,然后在优化评分中扣除。这样做的好处是,不同品种、不同手数之间可以横向对比。
def cost_adjusted_return(gross_return, trade_count, avg_slippage_ticks, tick_value, contract_value, fee_per_lot, lots=1): # gross_return 为未计成本的毛利比例 # trade_count 为总交易次数 # avg_slippage_ticks 为平均每边滑点 tick 数 # tick_value 为最小变动价位对应的盈亏额 # contract_value 为单手合约价值 # fee_per_lot 为单边手续费 # 单边合计成本:滑点 + 手续费 avg_slippage_cost = avg_slippage_ticks * tick_value * lots total_cost_per_trade = (avg_slippage_cost + fee_per_lot * lots) * 2 # 双边 total_cost_ratio = total_cost_per_trade * trade_count / contract_value net_return = gross_return - total_cost_ratio return net_return这套思路不是唯一的,但它的价值在于把“优化目标”和“真实可交易性”绑定在一起。参数优化一旦把成本纳入目标,就不会再去选择那些依赖高频低质交易的参数区。
5. 完整实操记录:一个双均线通道策略的优化全过程
我会用一个非常简单的策略示例,完整展示我如何把上面所有技巧串起来。这个策略不带任何圣杯属性,只是方便说明流程。
5.1 策略需求定义与数据准备
假设策略逻辑如下:当快线上穿慢线且价格创出过去 N 日新高时做多,当快线下穿慢线或价格创出过去 N 日新低时平多。反手同理做空。这个策略实际上是一个简单的双均线加通道过滤。
为了演示参数优化,我保留三个参数:
- fast:快线周期
- slow:慢线周期
- channel:通道突破周期
数据使用某商品期货主力连续日线,时间范围 2018 年 1 月到 2024 年 12 月。我把 2018-2022 作为训练优化期,2023-2024 作为样本外验证期。
这里有一个关键习惯:所有数据在优化前只做最基本的复权与拼接,不允许在训练阶段看到验证期数据。我甚至会把验证期数据从工作目录中暂时移走,防止自己在调试中无意间瞟到。
5.2 网格优化代码与参数平面计算
我先把参数空间跑一遍,得到完整的绩效矩阵。
import numpy as np import pandas as pd def run_backtest(df, fast, slow, channel, cost_per_trade=0.001): # 极简示意:返回年化收益和最大回撤 # 实际回测请使用完整撮合逻辑 df = df.copy() df["fast"] = df["close"].rolling(fast).mean() df["slow"] = df["close"].rolling(slow).mean() df["high_n"] = df["high"].rolling(channel).max().shift(1) df["low_n"] = df["low"].rolling(channel).min().shift(1) position = 0 equity = [0.0] for i in range(1, len(df)): # 信号判断省略,核心是展示参数扫描结构 pass return 0.15, 0.10 # 示例返回 fast_range = range(10, 51, 5) slow_range = range(60, 201, 10) channel_range = range(10, 41, 5) results = [] for fast in fast_range: for slow in slow_range: if fast >= slow: continue for channel in channel_range: ann_ret, mdd = run_backtest(train_df, fast, slow, channel) results.append((fast, slow, channel, ann_ret, mdd))跑完之后,我会把结果转成一个三维结构,对“fast 和 slow”两个维度做切片,画热力图,观察绩效的分布。代码只是最粗略的骨架,但流程的核心是:扫描完所有组合后,我不要只看最大值,而是把参数平面画出来,观察哪里是“高原”。
5.3 样本外与多品种复验
扫描结果中,训练期里最优的一组参数可能是 fast=15,slow=80,channel=20,年化 34%。但参数平面显示它的邻域极不稳定,从 fast=15 改成 10 后年化掉到 8%,改成 20 后掉到 11%。与此同时,另一个区域 fast=25,slow=120,channel=30 到 35 之间的年化收益都稳定在 18% 到 22% 区间。
按照 3.2 的方法,我会放弃 34% 的孤峰,选择 20% 左右的高原区域。
然后把这组“高原参数”放到样本外数据 2023-2024 上验证。验证结果若仍能保持 16% 到 20% 的年化且回撤可控,这组参数才算初步通过。
下一步是多品种复验。我会在热卷、铁矿、甲醇、PTA、塑料等多个品种上使用同一组参数,不做任何重新优化。如果大部分品种能保持正收益,少数品种亏损但在合理范围,那策略逻辑的可复制性较强;如果只有原品种盈利,其他全部亏损,直接淘汰。
5.4 最终参数的选择思路
最终参数我不会选验证期中表现最好的那一组,而是选“验证期中位数表现靠前且邻域稳定”的组合。更简单地:
- 把参数平面上年化收益处于前 20% 的区域全部标记出来。
- 再计算这些区域中每个参数组合的邻域标准差,选邻域标准差较小的组合。
- 最后通过样本外和多品种复验的那一组,才作为实盘候选。
这个流程听起来保守,但保护作用极强。我过去的经验是:过度追求训练期最优参数,实盘基本都在半年到一年内出现异常回撤;而通过“高原检测”和“多品种复验”选出的参数,往往能存活更久。
6. 常见问题排查与避坑速查表
最后把我在实盘和回测中踩过、见过的坑集中盘点一下。这些问题很典型,希望你能跳过。
6.1 回测很好、实盘崩盘的三个根源
第一根:成本失真。回测时滑点设为零,手续费设成“象征性”水平。结果实盘里策略实际收益被成本磨损掉大半。检查方法:把滑点调成 2 到 3 个 tick 再跑一遍,如果绩效崩塌,成本就是根因。
第二根:信号生成依赖完美成交。比如用 K 线收盘价确认信号,但假设在同一根 K 线的收盘价成交。实盘中信号确认和成交之间存在延迟,尤其在快速行情里延迟会导致成交价显著偏离。检查方法:把信号确认推迟到下一根 K 线开盘价,且开盘价叠加不利滑点,再看绩效。
第三根:过拟合。训练期最优参数是孤峰,参数平面周围一片“毒区”,到了实盘遇到不同的波动率状态后,策略立刻失效。检查方法:画出参数热力图,看看所选参数是不是处于连续高原,而不是独立尖峰。
6.2 参数敏感性发现的“雷区”怎么处理
你可能会在处理参数平面时发现某个区域有“如果参数略变,最大回撤瞬间放大”的现象。即使收益还不错,这也是雷区。回撤放大意味着策略对参数极其敏感,可能只差几个单位就会触发一连串不顺畅的交易。
处理方式:把该区域排除,哪怕它收益再高。因为未来的参数漂移和数据噪声是必然的,你需要的是容错性,不是历史最优。
另一种雷区是“优化参数分布在边界位置”。比如 slow 周期的可测范围是 60 到 240,如果最优参数集中在 60 附近,说明你可能没有把空间探索完整,甚至真实有效参数可能在 60 以内。这时候应该扩展参数范围重新扫描,而不是直接接受边界解。
6.3 避坑速查表:参数优化的自检清单
| 检查项 | 错误做法 | 正确做法 |
|---|---|---|
| 训练/验证数据 | 整段历史同时优化和评价 | 严格切分训练期与验证期,验证期只使用一次 |
| 参数选择 | 直接选绩效最大值 | 选参数平原/高原区域内的稳健组合 |
| 参数精度 | 保留 17、23、36 这类任意精度 | 步长设为整数、5 或 10 倍数的可解释档位 |
| 交易成本 | 优化结束后统一减成本 | 在回测撮合和参数评分阶段就计入成本 |
| 未来函数 | 全样本标准化、未来数据参与指标计算 | 严格滚动窗口,逐根 K 线核验 |
| 多品种验证 | 只测单一品种 | 同一组参数,在多个相关性不高的品种上复验 |
| 换月处理 | 直接拼接主力合约价格 | 正确复权或指定移仓窗口 |
| 多周期验证 | 只测单一周期 | 至少再验证一个相邻周期的稳定性 |
| 参数数量 | 往策略里堆过滤条件 | 每个新参数必须带来显著且稳健的收益提升否则删除 |
| 样本外结果 | 不好就调参再试 | 验证集只使用一次,重新迭代必须引入新数据 |
这套自检清单我打印出来贴在工位上。每跑一轮优化就逐项对照,能拦住绝大多数由手痒导致的“优化悲剧”。
我自己的体感是:参数优化有用的前提,是你根本不指望找到“最优参数”,而是找到“最不容易失效的窄参数区”。真正靠前的业绩来自策略逻辑和风控执行,而不是某组精密校准的数字。每次回测跑完,多问自己一句:这个参数换一个品种、再换一段行情,还能站得住脚吗?问多了,手自然就不敢乱动了。