尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

量化策略防过拟合:样本内外划分实战指南

量化策略防过拟合:样本内外划分实战指南 你有没有过这种体验策略在历史回测里年化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(dropTrue) 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 工程层面的防范技术总结在策略工程层面有几种反过拟合的技术手段值得用起来。一是正则化约束给参数变更添加惩罚项让优化器宁可选一个“表现略差但参数不那么极端”的解二是多策略集成把多个逻辑独立的策略组合在一起分散单一策略的过拟合风险三是参数白名单机制只允许参数落在有经济逻辑支撑的范围内而不是完全交给网格搜索头脑风暴。这些手段说到底是“物理隔离过拟合”的辅助手段核心防线还是样本内外划分这条纪律。一整套流程走下来你会发现过拟合不是靠某个魔法函数消除的而是靠一套严谨的验证流程去发现并过滤掉的。我个人做策略研究这么多年最深刻的体会就是策略研究里最难的不是写出漂亮的代码而是忍住“再调一次参就完美了”的冲动。每一次你觉得“就差一点点了”其实都是在过拟合的边缘疯狂试探。样本内外划分解决不了所有问题但它至少给了你一道清晰的安全线——训练集随便你怎么折腾测试集只要跑一次超了线就果断放弃。这个规则够简单也够用。希望看到这里的你也能在自己的策略研究流程里把这道线画出来。
返回列表