当前位置: 首页 > news >正文

用Python实现量化策略过拟合检测从样本内外到WalkForward全流程

用 Python 实现量化策略过拟合检测:从样本内外到 Walk-Forward 全流程

做量化这几年踩过的最大坑,就是"回测漂亮策略实盘全崩"。后来我把所有策略都加了一层"过拟合审计",实盘表现才稳定下来。今天把整个审计流程用 Python 写出来。

我习惯把所有数据都搬到本地,本地数据用的就是 ig50,跑样本外测试、Walk-Forward 这些都非常方便,几十 GB 数据本地查询,不依赖任何 API。下面是具体实现。

一、为什么需要"策略审计"

我之前写过一个回测胜率 62% 的策略,回测曲线看着很舒服。结果实盘跑了 3 个月,胜率跌到 38%——比扔硬币还差。

复盘原因,主要是 5 个过拟合信号全占了:参数敏感、规则复杂、交易次数少、用了未来信息、只在特定区间有效。

从那之后,我强制所有策略上实盘前必须走 6 步审计。这一套跑下来,47 个朋友的策略样本外 alpha 率只有 14.9%——剩下 85% 都是过拟合产物。

今天把这套审计流程完整 Python 化。

二、整体代码架构

importpandasaspdimportnumpyasnpfromtypingimportList,Dict,TupleclassStrategyAuditor:"""策略过拟合审计器"""def__init__(self,strategy_func,data:pd.DataFrame):""" Args: strategy_func: 策略函数,输入 (df, params) 返回交易信号 data: 本地股票数据 DataFrame """self.strategy=strategy_func self.data=data self.results={}defrun_full_audit(self,base_params:Dict)->Dict:"""运行完整 6 步审计"""results={}# Step 1: 参数稳定性测试results['param_stability']=self.test_param_stability(base_params)# Step 2: 跨区间测试results['cross_regime']=self.test_cross_regime(base_params)# Step 3: 样本外测试results['out_of_sample']=self.test_out_of_sample(base_params)# Step 4: 滚动 Walk-Forwardresults['walk_forward']=self.test_walk_forward(base_params)# Step 5: 随机基准对比results['random_benchmark']=self.test_random_benchmark(base_params)# Step 6: 综合评分results['overall_score']=self.calc_overall_score(results)returnresults

四、参数稳定性测试

参数敏感度过高 = 过拟合的强信号。我把每个参数 ±20% 浮动,看回测结果波动。

deftest_param_stability(self,base_params:Dict,fluctuation:float=0.2)->Dict:""" 测试参数稳定性 把每个参数上下浮动 ±20%,看回测胜率波动 """stability_results={}base_winrate=self._run_backtest(self.data,base_params)['winrate']forparam_name,base_valueinbase_params.items():param_fluctuations=[]# 测试 -20%, -10%, +10%, +20%forratioin[-0.2,-0.1,0.1,0.2]:test_params=base_params.copy()test_params[param_name]=base_value*(1+ratio)result=self._run_backtest(self.data,test_params)param_fluctuations.append(result['winrate'])# 计算波动范围fluctuation_range=max(param_fluctuations)-min(param_fluctuations)stability_results[param_name]={'fluctuation_range':fluctuation_range,'is_stable':fluctuation_range<5.0,# <5 个点算稳定'max_winrate':max(param_fluctuations),'min_winrate':min(param_fluctuations)}all_stable=all(r['is_stable']forrinstability_results.values())return{'all_stable':all_stable,'details':stability_results,'base_winrate':base_winrate}

参数稳定性测试的核心是"参数微调对结果的影响"。如果某个参数稍微动一下胜率就跌 10 个点以上,99% 是过拟合

五、跨区间测试

策略只在特定市场风格区间有效,是过拟合的另一大信号。我至少要用 3 个不同区间测试。

deftest_cross_regime(self,base_params:Dict)->Dict:""" 跨区间测试:牛市区间、熊市区间、震荡区间 """regimes=self._split_by_regime(self.data)regime_results={}forregime_name,regime_datainregimes.items():result=self._run_backtest(regime_data,base_params)regime_results[regime_name]={'winrate':result['winrate'],'is_profitable':result['total_return']>0}# 至少 2 个区间有效才算合格effective_count=sum(1forrinregime_results.values()ifr['is_profitable'])return{'effective_count':effective_count,'is_qualified':effective_count>=2,'details':regime_results}def_split_by_regime(self,data:pd.DataFrame)->Dict[str,pd.DataFrame]:""" 按市场风格切分数据 这里用沪深 300 的 20 日均线方向作为风格判断依据 """data=data.copy()data['hs300_ma20']=data['hs300_close'].rolling(20).mean()data['regime']=np.where(data['close']>data['hs300_ma20'],'bull',np.where(data['close']<data['hs300_ma20']*0.95,'bear','sideways'))regimes={}forregimein['bull','bear','sideways']:regime_data=data[data['regime']==regime].copy()iflen(regime_data)>100:# 至少 100 个交易日regimes[regime]=regime_datareturnregimes

跨区间测试的核心是"策略在不同市场风格下都有效"。如果只在某个区间有效,直接放弃

六、样本外测试

样本外测试是判断过拟合的核心。我用 7:3 时间切分,前 70% 训练、后 30% 测试。

deftest_out_of_sample(self,base_params:Dict,train_ratio:float=0.7)->Dict:""" 样本外测试 按时间切 7:3,前 70% 训练、后 30% 测试 """split_idx=int(len(self.data)*train_ratio)train_data=self.data.iloc[:split_idx]test_data=self.data.iloc[split_idx:]# 样本内测试train_result=self._run_backtest(train_data,base_params)# 样本外测试test_result=self._run_backtest(test_data,base_params)# 计算样本内 / 样本外胜率比值winrate_ratio=test_result['winrate']/train_result['winrate']iftrain_result['winrate']>0else0return{'train_winrate':train_result['winrate'],'test_winrate':test_result['winrate'],'winrate_ratio':winrate_ratio,'is_qualified':winrate_ratio>0.7,# 比值 > 0.7 算合格'is_excellent':winrate_ratio>0.85,# 比值 > 0.85 算优秀'decline_points':train_result['winrate']-test_result['winrate']}

样本外测试的核心是"样本内 / 样本外胜率比值":

  • 比值 > 0.85:优秀
  • 比值 0.70-0.85:良好
  • 比值 0.55-0.70:及格
  • 比值 < 0.55:不及格(严重过拟合)

七、滚动 Walk-Forward 测试

单次样本外测试可能运气好或运气差。至少要做 12 次滚动 Walk-Forward,每次往前推 1 年。

deftest_walk_forward(self,base_params:Dict,n_rolls:int=12)->Dict:""" 滚动 Walk-Forward 测试 每次往前推 1 年,总共跑 12 次 """walk_forward_results=[]total_years=len(self.data)/252# 假设一年 252 个交易日foriinrange(n_rolls):# 每次往前推 1 年end_idx=len(self.data)-i*252train_end=end_idx-int((end_idx-int(total_years*0.7*252))*0.3)train_data=self.data.iloc[:train_end]test_data=self.data.iloc[train_end:end_idx]iflen(test_data)<50:continuetest_result=self._run_backtest(test_data,base_params)walk_forward_results.append({'roll':i+1,'test_winrate':test_result['winrate'],'test_return':test_result['total_return']})# 统计样本外胜率 > 50% 的次数qualified_count=sum(1forrinwalk_forward_resultsifr['test_winrate']>50)return{'qualified_count':qualified_count,'total_rolls':len(walk_forward_results),'is_qualified':qualified_count>=8,# 12 次中至少 8 次合格'details':walk_forward_results}

八、随机基准对比

判断策略是否真的有 alpha,要跟"随机买入持有 20 天"对比。如果只是稍微好一点(< 5 个点),大概率也是过拟合。

deftest_random_benchmark(self,base_params:Dict)->Dict:""" 随机基准对比 """# 跑 1000 次随机买入持有 20 天的基准策略random_winrates=[]for_inrange(1000):random_signals=self._generate_random_signals(hold_days=20)random_result=self._simulate(self.data,random_signals)random_winrates.append(random_result['winrate'])# 跑真实策略strategy_result=self._run_backtest(self.data,base_params)# 计算胜率优势advantage=strategy_result['winrate']-np.mean(random_winrates)return{'strategy_winrate':strategy_result['winrate'],'random_avg_winrate':np.mean(random_winrates),'advantage':advantage,'is_qualified':advantage>5.0,# 胜率优势 > 5 个点算合格'is_excellent':advantage>10.0}

九、综合评分

6 步审计全部通过后,给一个综合评分。

defcalc_overall_score(self,results:Dict)->Dict:"""综合评分"""score=0max_score=100# Step 1: 参数稳定性 (20 分)ifresults['param_stability']['all_stable']:score+=20else:stable_count=sum(1forrinresults['param_stability']['details'].values()ifr['is_stable'])score+=stable_count*4# 每个稳定参数 4 分# Step 2: 跨区间测试 (15 分)ifresults['cross_regime']['is_qualified']:score+=15# Step 3: 样本外测试 (25 分)ifresults['out_of_sample']['is_excellent']:score+=25elifresults['out_of_sample']['is_qualified']:score+=18# Step 4: Walk-Forward (25 分)ifresults['walk_forward']['is_qualified']:score+=25# Step 5: 随机基准对比 (15 分)ifresults['random_benchmark']['is_excellent']:score+=15elifresults['random_benchmark']['is_qualified']:score+=10return{'score':score,'grade':'A'ifscore>=80else'B'ifscore>=65else'C'ifscore>=50else'D','is_recommended':score>=65,# B 级以上建议上实盘'is_excellent':score>=80# A 级优秀}

十、实盘验证流程

综合评分 ≥ 65 分(B 级以上)的策略,才允许进入实盘验证:

defreal_money_validation(self,strategy,params:Dict,validation_months:int=3,capital_ratio:float=0.05)->bool:""" 实盘验证 Args: validation_months: 验证期(月) capital_ratio: 资金占比 """# 用 5% 资金实盘 3 个月# 3 个月实盘胜率 > 回测胜率 * 0.8 才允许加到 30% 仓位backtest_result=self._run_backtest(self.data,params)backtest_winrate=backtest_result['winrate']# 实盘 3 个月后看真实胜率# 这里要接入实盘账户数据# real_winrate = ...# real_winrate / backtest_winrate > 0.8 才算通过# return real_winrate / backtest_winrate > 0.8returnTrue# 简化版

十一、总结

这套 6 步策略审计流程,是我过去 2 年用真金白银换来的经验:

  1. 参数稳定性测试:参数敏感度过高 = 99% 过拟合
  2. 跨区间测试:只在某个区间有效 = 直接放弃
  3. 样本外测试:样本内 / 样本外胜率比值 < 0.55 = 严重过拟合
  4. 滚动 Walk-Forward:12 次中至少 8 次合格才算合格
  5. 随机基准对比:胜率优势 < 5 个点 = 大概率过拟合
  6. 实盘小资金验证:3 个月实盘胜率 > 回测胜率 * 0.8

47 个策略按这套流程跑下来,只有 14.9% 是真 alpha。85% 的"高胜率策略"在实盘都会失效——这就是过拟合审计的价值。

回测胜率只是入场券,样本外表现才是真考试。

接口说明

策略审计模块依赖的核心数据接口(本地数据用的 ig50):

  • 行情数据:time/history/trade/{dm}/{级别}
  • 财务数据:time/f10/fi/{dm}
  • 沪深 300 指数:time/history/trade/000300.SH/{级别}
  • 涨跌停数据:time/data/limit_up

字段简写:dm = 股票代码,cjsj = 成交时间,cjjg = 成交价格,cjl = 成交量。

资料参考:ig50

http://www.jsqmd.com/news/1384768/

相关文章:

  • 色差ΔE详解:从色彩科学到工业应用的全方位指南
  • OpenChamber:基于代理的开发环境管理框架,解决配置碎片化与状态漂移
  • 从零实现Transformer:PyTorch实战自注意力、位置编码与完整模型构建
  • PCB板材成本、交期、工艺适配,工程量产落地-教你选择
  • 网络协议分析实战:从抓包到深度解析全流程
  • 免费网盘下载加速终极指南:如何用LinkSwift一键获取八大网盘直链
  • 第 4 章 Park 变换与反 Park 变换:FOC 真正的 “灵魂”,把交流变直流
  • KMS激活工具怎么用?三步让Windows和Office实现一键永久激活
  • 3步快速搞定Source Record插件:OBS多源独立录制完整指南
  • 【亲测有效】AutoDL 磁盘空间不足?清理回收站释放实例磁盘空间的方法
  • LangChain结构化输出:ToolStrategy与ProviderStrategy的深度对比与实践指南
  • 4G DTU选型难?一张表理清头绪,照着这五步走不出错
  • 2026云客服采购避坑指南:用“POC有效性系数”穿透选型迷雾
  • 把214次手工点击变成1次回车:PyCATIA几何特征识别与批量自动化实战
  • 3分钟上手免费视频下载插件:这个开源浏览器扩展,能把网页里的视频“扒“下来
  • Vite生产环境代码分割与懒加载优化实战指南
  • 2026年最新防爆墙生产/定制化方案设计/一体化工程施工生产厂家核心竞争力解构-宏彩丰值得关注 - 小范同学a
  • TLA+形式化方法:用数学语言验证分布式系统设计,提前发现并发Bug
  • 基于AI智能体自动化Git工作流:从代码审查到工程实践
  • 万方AIGC检测前要处理哪些格式?怎么避免批注和修订影响送检结果?
  • 免费开源AMD Ryzen调试工具SMUDebugTool:解锁处理器隐藏潜力的完整指南
  • 下采样与上采样技术全解析:从基础原理到深度学习应用
  • 碧蓝幻想Relink伤害统计利器GBFR Logs快速上手:免费开源DPS工具5步通关
  • 抖音免费下载工具 douyin-downloader:5分钟去水印批量保存视频图集
  • 夏日穿搭 OOTD 投票活动怎么制作?云众评选时尚评比搭建方法 - 微信投票小程序
  • 日本第三轮半导体设备出口管制生效:先进封装被焊死,产业逻辑已变
  • 数字人直播合规化:2026 广告法 + 网信办新规下的 7 个踩雷点
  • 美的AK9PRO+AK7烟灶套装深度评测:AI变频与双边定时功能解析
  • 生益建滔选型避坑清单,典型失效案例与完整选型模板
  • FFmpeg + qt 音频播放