☰
量化策略防过拟合:样本内外划分实战指南
2026/9/30 11:55:25 网站建设 项目流程

你有没有过这种体验:策略在历史回测里年化70%,最大回撤不到5%,曲线平滑得像教科书案例,结果一上实盘就连续打脸,亏到怀疑自己是不是Python写错了?我敢说,十个做量化的人里至少有八个栽在同一个坑里——过拟合。而防过拟合最基础也最有效的一招,就是做好样本内外划分。

这不是什么高深理论,却是一道实打实的分水岭。市面上讲量化策略的教程很多,但大部分都在教你怎么把回测曲线做得好看,很少跟你讲清楚“为什么回测好不代表实盘好”。这篇文章专门聊这个:什么是样本内外划分、为什么要分、怎么分才科学、以及怎么用Python落地这套流程。如果你正在写python量化交易策略代码,或者准备把自己的策略接入无限易量化策略这类终端做实盘,那这篇文章值得你花十分钟看完。

1. 过拟合的真相:你背下了答案,却没学会解题

1.1 策略过拟合到底是什么意思

先拆个概念。量化策略的过程,本质上是“从历史数据里寻找规律,然后用这个规律去预测未来”。但历史数据是有限的,里面不光有规律,还有大量噪音。过拟合就是指:你的模型把“噪音”也当成“规律”学进去了,导致它在训练数据(样本内)上表现极好,一遇到新数据(样本外)就原形毕露。

我举一个特别直白的例子。你拿十个点拟合一条曲线,用一阶直线拟合,误差可能比较大;用十阶多项式拟合,能做到每一个点都精准穿过,训练误差几乎为零——但这条曲线扭曲得毫无意义,你拿它去预测第十一个点,结果会离谱到天上去。量化策略的过拟合就是同一个道理:你不断往模型里加参数、调阈值、改逻辑,直到它在历史数据上“完美”地复现了每一段涨跌,但这份“完美”只是对历史噪音的精确记忆,不是对未来规律的洞察。

用数学一点的表达:任何一个模型的预测误差都可以拆成三部分——偏差、方差和不可约噪声。过拟合对应的就是方差过大:你的策略对训练数据极其敏感,样本稍微变一点点,策略表现就天翻地覆。这不是策略稳定,而是策略“病得不轻”。

1.2 为什么回测好不代表实盘好

很多新手想不明白一件事:我明明用的是历史真实数据,回测结果也是真金白银算出来的,凭什么说它不靠谱?

关键在于:历史数据只有一条路径,而未来有无数种可能。你在回测里看到的“优秀表现”,可能只是无数偶然因素叠加的结果。比如某一年某只股票恰好走出了符合你策略形态的行情,你在回测中捕捉到了这段利润;但未来这种行情还会不会出现、出现的频率高不高,历史数据给不了你任何保证。

还有一层更隐蔽的原因:市场本身在变化。你从历史数据中学到的规律,可能在当时是成立的,但参与市场的人会变、交易机制会变、资金偏好会变。一个在2018年有效的策略,到2025年可能已经完全失效。这不是策略写错了,而是市场进化了。所以,光靠一段历史数据去验证策略,这个行为本身就存在天然缺陷。

1.3 样本内外划分解决什么问题

样本内外划分的核心思想很简单:把历史数据切成互不相干的两部分,一部分当“训练集”,一部分当“考试卷”。你在训练集上设计策略、调参数,然后把定好的策略直接拿到考试卷上跑一遍,看它能不能及格。如果能,说明策略大概率学到了真实规律;如果训练集考100分、考试卷考20分,那不用怀疑,铁定过拟合。

这个思路跟机器学习训练模型完全一致。但量化领域有个特殊之处:金融时间序列不能像图片分类那样把数据随机打乱,因为时间顺序本身就是信息——昨天的价格会影响今天的价格,今天的交易会改变明天的持仓。所以量化里的样本内外划分,必须严格尊重时间先后顺序,这也是这个领域里最容易踩雷的地方。

2. 样本内外划分的三种主流方法

2.1 静态划分:最简单,也最容易用错

静态划分是最基础的做法:把全部数据按时间顺序切一块。比如你有十年日线数据,前七年作为样本内(训练集),后三年作为样本外(测试集)。在样本内反复优化策略参数,确定最优参数后,在样本外做一次“终测”。

这个方法好理解、好实现,但有两个使用前提,很多人会忽略。

第一个前提:样本外的数据绝对不能反复“偷看”。我见过不少人的所谓“样本外测试”,其实是先跑一遍样本外,发现表现不佳,再回去调整参数,然后再跑一遍样本外,直到样本外也好看了为止。这本质上还是过拟合,只是换了个地方过拟合——你把样本外信息也融入参数选择了,这个样本外就不再是真正的“未知数据”。

第二个前提:样本内的数据要足够长,覆盖足够多的市场状态。七年数据听起来很长,但如果这七年里只有一波趋势行情、其余时间全是震荡,那你的策略很可能只学会了一种玩法。样本外一旦遇到完全不同的行情状态,照样崩给你看。

2.2 Walk-Forward滚动验证:最接近实盘的做法

静态划分相当于“一次大考定终身”,而Walk-Forward(滚动前移验证)则更像“月考”:每次都只用最近一段数据来训练,然后对紧接着下一段数据做验证,验证完就把窗口往前滚动一步,再训练、再验证。这样循环下去,整个过程会产生一连串连续的样本外预测结果,这些结果拼接起来,就是策略最接近真实盘中表现的“模拟考核成绩”。

我实际做下来,这个方法的优势非常明显。首先,它能持续检验策略在不同时间段的有效性,不像静态划分只验证一次;其次,它天然模拟了实盘中的“滚动调参”过程——你在实盘里隔一段时间也会根据最近数据调整参数,Walk-Forward就是把这个过程规范化了。缺点是计算量大,耗时相对较长,但这点成本跟实盘亏损比起来实在不算什么。

2.3 按市场状态划分:机构里常用的进阶玩法

除了按时间划分,还有一种是按市场波动状态划分样本内外。比如把样本内定为趋势较为明确的单边行情年份,把样本外定为震荡行情或大幅度回撤行情年份。这么做是为了故意“刁难”策略:如果策略在一种行情下训练、在另一种行情下依然能稳定发挥,才说明它具备真正的市场适应能力。

这种方法对做商品期货CTA或股票择时策略的人特别有价值,因为这些市场的风格切换特别剧烈。你也不妨在划分样本内外的时候,刻意观察一下你的样本外区间到底处于什么行情状态——如果样本外恰好和样本内是同一类行情,那么即使测试通过了,也说明不了太多问题。

3. 用Python落地:样本内外划分实操

3.1 第一步:数据准备与基础划分

先写个最基础的按时间切分。假设你从tushare或者聚宽导出日线数据存在df里:

import pandas as pd import numpy as np # 按时间顺序排序并确保索引为日期 df = df.sort_values('date').reset_index(drop=True) df['date'] = pd.to_datetime(df['date']) df = df.set_index('date') # 静态划分:前70%样本内,后30%样本外 split_point = int(len(df) * 0.7) train_df = df.iloc[:split_point] test_df = df.iloc[split_point:] print(f"样本内区间: {train_df.index[0]} -> {train_df.index[-1]}, 共{len(train_df)}根K线") print(f"样本外区间: {test_df.index[0]} -> {test_df.index[-1]}, 共{len(test_df)}根K线")

这里有个细节值得注意:划分比例不是硬性的70/30。如果数据量不够大、策略波动周期太长,30%的样本外可能只有区区几百根K线,根本测不出统计意义。我的习惯是:样本外至少得有500根以上的交易K线,并且尽量覆盖一次完整的涨跌周期。如果做不到,优先考虑增加历史数据长度,而不是压缩样本外比例。

3.2 第二步:Walk-Forward滚动验证框架实现

静态划分是起点,真正有价值的是滚动验证。下面是一个简化的框架:

def walk_forward_validation(data, train_window, test_window, optimize_func, backtest_func): """ data: 全部行情数据 train_window: 每次用于训练的行数 test_window: 每次用于验证的行数 optimize_func: 输入训练数据,返回最优参数 backtest_func: 输入测试数据和参数,返回绩效指标 """ oos_results = [] params_history = [] start = 0 while start + train_window + test_window <= len(data): train_data = data.iloc[start: start + train_window] test_data = data.iloc[start + train_window: start + train_window + test_window] # 在样本内训练数据上寻参 best_params = optimize_func(train_data) # 在样本外验证数据上固定参数回测 oos_result = backtest_func(test_data, best_params) oos_results.append(oos_result) params_history.append(best_params) # 窗口前移 start += test_window # 拼接所有样本外结果,计算汇总绩效 oos_metrics = aggregate_results(oos_results) return oos_metrics, params_history

这个框架看起来简单,但里面藏着两个关键决策。

第一,训练窗口和测试窗口的比例要合理。我常用的是训练窗口约2-3年日线数据、测试窗口3-6个月,这样既能保证训练数据充足,又能在不太长的时间内完成一次滚动验证。窗口太短,训练不够充分;窗口太长,滚动次数少,统计意义不足。

第二,优化函数输出参数时,要做到“看不到测试数据”。这是整个Walk-Forward框架的灵魂——如果优化函数内部不小心用了全量数据,哪怕是算了一个全量均值,都等于提前“偷看”了样本外信息,整个验证就失去意义了。这是我每次写代码都会反复检查的点。

3.3 第三步:用净值曲线和绩效指标判断过拟合

跑完样本内外验证之后,怎么判断策略到底有没有过拟合?直接对比几张表。

先看一个典型的过拟合策略长什么样:

绩效指标样本内(训练)样本外(测试)
年化收益率68%5%
最大回撤6%32%
夏普比率3.20.4
胜率61%48%
盈亏比2.11.1

样本内美如画,样本外惨不忍睹。这种策略基本可以直接扔进垃圾桶,任何形式的参数调整都不值得挽救——因为这说明你的策略逻辑本身没有捕捉到稳定规律,之前的高收益完全是拟合出来的巧合。

再来看一个相对健康的策略:

绩效指标样本内(训练)样本外(测试)
年化收益率32%26%
最大回撤9%12%
夏普比率1.81.4
胜率55%53%
盈亏比1.61.5

样本外比样本内差一些,这是正常的,毕竟实盘环境永远比历史环境更残酷。但如果样本外的表现能保持在样本内的一半以上,而且最大回撤和夏普没有出现断崖式恶化,这个策略就具备了上实盘的初步条件。

还有一个我特别常用的观察方法:看样本外的净值曲线形状。如果样本外净值曲线是平稳上升的,哪怕斜率平缓一点,都说明策略逻辑在持续起作用;如果净值曲线是一步一个台阶然后突然跳水,或者长时间横盘后剧烈波动,那大概率你抓住的规律只在特定阶段有效。

3.4 第四步:参数敏感性分析,一眼识破过拟合

识别过拟合还有一个非常直观的手段:参数高原测试。做法很简单——把你策略里最核心的一个或两个参数,在样本内做网格扫描,画出收益热力图。你会看到两种截然不同的形态。

健康的策略,在最优参数附近会有一个“高原”,也就是参数小幅偏移时,绩效变化不大,整体呈现平滑过渡;过拟合的策略,在最优参数附近则是一个“尖峰”,参数稍微动一点点,绩效就剧烈掉下来。尖峰意味着你的策略对参数极其敏感,这个优解纯粹是钻了数据的空子,没有任何稳定性可言。

我通常会在优化完参数之后,顺手画一张这样的热力图,存下来当策略档案的一部分。这不仅能帮你识别过拟合,还能在后续实盘调参时提供参考——一个真正稳健的参数点,应该落在高原的中心,而不是尖峰的最顶端。

4. 从研究到实盘的最后一公里

4.1 回测环境与实盘环境的天然差异

就算你的策略通过了严格的样本外验证,也不能直接把回测代码扔进实盘账户,中间还隔着一条宽阔的“河流”。

回测环境里,你用的是历史K线,成交价是当根K线的开盘价或收盘价,手续费和滑点都是理想化的估算值;实盘环境里,你的订单要经过信号生成、指令发送、柜台撮合、行情延迟等一系列环节,最终的成交价往往和信号触发时的价格有偏差。尤其是高频或日内策略,滑点的杀伤力会远超你的想象。

我见过太多策略在回测里赚钱、上实盘就亏损的案例,原因不是策略逻辑错了,而是回测假设和实盘现实差距太大。所以,如果你打算走实盘,我强烈建议先把策略对接到一个量化交易终端或券商模拟盘环境,跑一段时间真实的模拟盘。像无限易这类量化终端,都支持内置模拟交易功能,能真实反映信号触发、下单延迟和滑点情况。模拟盘跑得稳,实盘才敢上。

4.2 实盘阶段还需要监控什么

策略上线之后,不代表万事大吉。接下来要做的是持续监控策略的“实盘表现 vs 预期表现”是否一致。

我在实际跟踪中发现,最有效的监控指标其实是策略的实时信号与回测逻辑是否完全一致。很多看起来是“策略失效”的亏损,实际原因是程序bug、数据源断流、复权方式不一致、涨跌停无法成交等工程层面的问题。把这些问题排查干净,再去谈策略本身是否失效,才是客观的归因。

至于什么时候该淘汰一个策略,我的经验是:实盘表现连续低于样本外预期一段时间,并且回撤超过了样本外最大回撤的1.5倍以上,同时找不到合理的市场归因时,这个策略就该进“观察池”了。与其抱着一个失效策略死扛,不如把资金调配到更稳健的策略上。

4.3 实盘调参的红线:参数禁区

最后说一个我踩过无数次坑之后才彻底想明白的原则:样本外的参数,一旦定下来,就不要轻易修改。

很多人在实盘亏损后,第一反应是“是不是参数该调了”,然后开始根据实盘最近几周的行情重新优化参数。这个动作危险性极高——因为实盘最近的行情也是一种“样本内”,你对它做优化,本质上又是在进行样本外数据窥探,只会让策略陷入“不断追逐最近行情”的恶性循环。

我给自己定了一条规矩:除非市场结构发生了根本性变化(比如交易规则变更、标的退市、涨跌停制度调整),否则实盘阶段只监控,不调参。如果策略连续多次触发止损且逻辑失效,那就直接下线,而不是通过调参去“挽救”它。真正赚钱的策略往往不是参数最优的策略,而是参数“足够稳健”的策略——这句话我建议你抄下来贴在屏幕旁边。

5. 常见过拟合问题与排查清单

5.1 数据泄漏:最容易犯、却最难发现的错误

数据泄漏是量化策略过拟合里最隐蔽的一类问题。它不一定是你有意作弊,而往往是代码逻辑细节里埋下的雷。常见的几种泄漏包括:

  • 未来函数:在回测中使用了未来数据,比如用当天的收盘价信号在当天开盘价成交,或者用未来的财务数据参与当前评分。
  • 前视偏差:计算指标时不小心使用了包含未来N根K线的滚动窗口,导致当前信号里“提前”包含了未来信息。
  • 幸存者偏差:选取股票池时只用了当前还在上市的公司,忽略了过去退市的公司,导致回测结果虚高。

排查这类问题,我有一套笨但很有效的方法:单步回放。拿实际某一天的数据,手动推演策略在当天的信号和成交逻辑,看看它是否在那一刻能够合理获取到所有需要的数据。只要你有任何一步需要“看看明天才能知道的东西”,那就有泄漏。

5.2 数据窥探与参数多重检验

还有一个常见的过拟合方式叫做数据窥探。比如你一共跑了500组参数,其中某一组参数在回测中表现极好。但你想过没有,即便500组参数全是随机数,也会有那么一组碰巧表现不错的——这是纯粹的统计学偶然。

很多新手在优化时容易陷入“参数越多越好、网格越密越好”的误区,但事实上你每多尝试一组参数,就是在多消耗一次“运气”。这也是为什么在机器学习中要做训练集、验证集、测试集三分离的原因——在验证集上选参数,在真正陌生的测试集上做最终评估。

我建议你在策略研究阶段就限制参数组合的总数。比如策略有三个核心参数,每个参数建议只试5个候选值,也就是最多125次组合。如果超过这个数量还找不到理想参数,说明策略逻辑本身的天花板很低,再多的参数尝试也救不了它——与其疯狂调参,不如回头审视策略逻辑本身。

5.3 净利润幻觉:只看单利不看回撤曲线

最后一个误区也特别典型:只看累计净利润和年化收益率,完全忽略回撤曲线和净值修复时间。

有朋友拿着一个年化50%的策略来找我,说统计上样本内外都很稳定。我打开净值曲线一看,前三年一直在水下趴着,最后一年突然拔地而起,最大回撤60%——这种策略的“高年化”毫无意义,因为绝大多数人在水下那三年早就止损离场了,根本等不到最后的爆发。

判断一个策略是否值得上实盘,我至少会看五个维度:年化收益、最大回撤、夏普比率、盈亏比、净值创新高的时间占比。前四个大家都懂,最后一个是说:这只策略在多少比例的交易日里处于“历史新高”状态?健康趋势策略通常在60%以上,震荡策略可能低一些,但至少不应该长期趴在水下不出来。

5.4 工程层面的防范技术总结

在策略工程层面,有几种反过拟合的技术手段值得用起来。一是正则化约束:给参数变更添加惩罚项,让优化器宁可选一个“表现略差但参数不那么极端”的解;二是多策略集成:把多个逻辑独立的策略组合在一起,分散单一策略的过拟合风险;三是参数白名单机制:只允许参数落在有经济逻辑支撑的范围内,而不是完全交给网格搜索头脑风暴。

这些手段说到底是“物理隔离过拟合”的辅助手段,核心防线还是样本内外划分这条纪律。一整套流程走下来,你会发现:过拟合不是靠某个魔法函数消除的,而是靠一套严谨的验证流程去发现并过滤掉的。

我个人做策略研究这么多年,最深刻的体会就是:策略研究里最难的不是写出漂亮的代码,而是忍住“再调一次参就完美了”的冲动。每一次你觉得“就差一点点了”,其实都是在过拟合的边缘疯狂试探。样本内外划分解决不了所有问题,但它至少给了你一道清晰的安全线——训练集随便你怎么折腾,测试集只要跑一次,超了线,就果断放弃。这个规则够简单,也够用。希望看到这里的你,也能在自己的策略研究流程里把这道线画出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询