
本篇是「量化工程实战进阶」系列第 48 讲。上一讲#47我们给因子做了体检——用 IC 分析和分层回测判断它到底是信号还是噪声。本讲我们把镜头对准策略本身当你看到一个回测曲线完美、夏普惊人、回撤极小的策略时怎么判断它是真有边缘还是只是把历史噪声背了下来这一讲给你两套反过拟合的武器——样本外检验 / Walk-Forward 与参数稳定性分析全程用魔码行情 API 零 SDK 纯 HTTP 接入代码已本地跑通。一、痛点开场那条太美的曲线往往是陷阱做量化的人几乎都经历过这一幕花几小时回测得到一条几乎不回撤的完美净值曲线胜率惊人夏普超过 3。你信心满满上实盘三周之后账户绿了 8%策略连续六周亏损你才意识到——回测记住的不是市场规律而是这一段历史数据的噪声。这就是过拟合curve-fitting / over-optimization。过拟合之所以致命是因为它看起来毫无破绽。几个最典型的红旗回测夏普超过 3全球顶尖量化基金的实盘夏普通常也只有 1~2.5。一个回测夏普 4、5 的策略几乎可以断定是过拟合或数据偏差前视偏差、幸存者偏差。最大回撤低于 5% 胜率超过 75%如果三者同时出现策略极可能过拟合。这些数字在系统化实盘里几乎不存在。最优参数很怪移动平均周期出现 137 这种非整数感的数值而不是 50、100 这种有解释意义的整数往往是暴力搜索拟合噪声的产物。换个时间窗口策略就崩把回测窗口挪几个月绩效断崖式下跌说明它只适配这一段历史。社区里一个被反复印证的结论“优化参数每增加一个过拟合的表面积就乘以一倍一个 15 个参数、跑过 1 万次组合的策略无论回测多漂亮几乎一定过拟合。”换句话说对抗过拟合的本质是克制——克制参数数量、克制规则堆叠、克制看到结果再改规则的冲动。本篇教你用工程化手段把看起来好和真的好区分开。二、本文你将得到什么一个数据接入层用魔码日线 API零 SDK 纯 HTTP真实拉取标的演示接入方式。一套鲁棒检验框架单因子模型的样本内 / 样本外 IC 对比以及 5 折滚动 Walk-Forward 检验参数稳定性。一个过拟合反例21 因子暴力拟合在少样本 vs 多特征下的样本内 R² 虚高、样本外 R² 崩塌的完整演示。一份完整可运行代码已验证覆盖接入→构造真实因子与幌子因子→鲁棒检验→过拟合对照。六条抗过拟合铁律定义先于优化、参数最少化、样本外只测一次、跨市场/跨状态验证、蒙特卡洛重排、定期再优化但别太勤。三、第一步用魔码日线接入和前几讲一致因子与策略研究的第一步是把数据弄干净。这里用魔码行情 API 零 SDK 纯 HTTP 拉取日线无需任何客户端 SDKimportrequests BASEhttps://api.momaapi.comLICENCETEST-API-TOKEN-MOMA-836089C22111# 演示证书请换成你自己的正式证书deffetch_moma_daily(code,st,et):urlf{BASE}/hsstock/history/{code}/d/n/{LICENCE}?st{st}et{et}rowsrequests.get(url,timeout20).json()ifnotisinstance(rows,list):raiseRuntimeError(f接口返回异常{rows})returnrows魔码日线字段稳定o/h/l/c/v/a/pc/sf复权口径统一意味着你在不同时间拉同一段历史因子值与策略输入是可复现的——这恰恰是做样本内 / 样本外严格分离的前提。如果你的数据源本身字段会漂移那么样本外检验从根上就不成立。注演示证书返回固定样本无法做真实全市场截面下方演示用截面 panel 为固定随机种子生成的演示数据含一个真实因子 20 个纯噪声幌子因子仅用于演示算法逻辑非真实行情。真实证书可直接拉真实截面把同样代码跑在真实数据上。四、第二步鲁棒检验——样本内 / 样本外 IC 与 Walk-Forward对抗过拟合的第一道闸门是把数据严格分成两段**样本内In-Sample, IS**用于构造与调参**样本外Out-of-Sample, OOS**预留出来只测一次。如果 OOS 表现显著差于 IS策略就是过拟合的。黄金法则参数定稿前绝不看 OOS看了之后若想改参数必须从头重来。比单次切分更严谨的是Walk-Forward滚动前向检验把数据切成连续窗口每个窗口上在 IS 段优化参数、在紧接着的 OOS 段不加修改地测试再把所有 OOS 段拼接得到一条模拟实盘的净值。它的核心产出有两个效率比Efficiency Ratio平均 OOS 表现 / 平均 IS 表现。高于 80% 说明参数稳健60%~80% 有预测力但需监控低于 60% 直接丢弃不管 IS 多漂亮。参数稳定性如果各窗口选出的最优参数在数值上剧烈跳变说明策略不稳定、大概率是过拟合。下面用 #47 讲过的 IC 工具做鲁棒检验。我们构造一个含真实因子的截面未来收益由单一真实因子线性驱动这是真边缘另有 20 个纯噪声幌子因子不进入真实模型importnumpyasnpdefrankdata(x):ordernp.argsort(x,kindmergesort)ranksnp.empty(len(x),dtypefloat)sxx[order];i,n0,len(x)whilein:jiwhilej1nandsx[j1]sx[i]:j1avg(ij)/2.01.0forkinrange(i,j1):ranks[order[k]]avg ij1returnranksdefspearman_ic(score,fwd_ret):mask~np.isnan(score)~np.isnan(fwd_ret)s,rscore[mask],fwd_ret[mask]iflen(s)30:returnnp.nan rs,rrrankdata(s),rankdata(r)rs-rs.mean();rr-rr.mean()denomnp.sqrt((rs**2).sum()*(rr**2).sum())returnfloat((rs*rr).sum()/denom)ifdenom0elsenp.nandefbuild_panel(n_stocks500,n_dates120,n_spurious20,seed20260912):rngnp.random.default_rng(seed)frng.standard_normal((n_dates,n_stocks))# 真实因子有边缘grng.standard_normal((n_dates,n_spurious,n_stocks))# 20 个幌子因子纯噪声ret0.05*frng.standard_normal((n_dates,n_stocks))# 真实模型仅 f 有预测力returnf,g,ret鲁棒检验只使用真实因子f算 IS 段与 OOS 段的截面 IC再用 5 折滚动看 IC 是否稳定。五、第三步过拟合反例——21 因子暴力拟合过拟合最容易发生在参数数 ≈ 样本数比例偏高时。我们故意构造一个少样本 vs 多特征的拟合场景在一个 15 期 × 25 只 375 个样本的小块上用含 20 个噪声因子的 21 因子模型做最小二乘拟合。噪声因子在 IS 段被记住R² 被虚抬而在 OOS 大块上这些系数毫无意义R² 直接崩到 0 甚至为负。defdesign(g_block,f_block,ret_block):T,K,Ng_block.shape Xnp.column_stack([f_block.reshape(-1)][g_block[:,k,:].reshape(-1)forkinrange(K)])returnX,ret_block.reshape(-1)defr2(X,y,coef):predX coef ss_res((y-pred)**2).mean()ss_tot((y-y.mean())**2).mean()return1.0-ss_res/ss_totifss_tot0else0.0对照实验同时给出同一小块只拟合真实因子1 因子本应有的 R²让你直观看到噪声因子把 IS R² 虚抬了多少。六、完整可运行代码已本地跑通# -*- coding: utf-8 -*-importrequestsimportnumpyasnp BASEhttps://api.momaapi.comLICENCETEST-API-TOKEN-MOMA-836089C22111# 演示证书请换成你自己的正式证书deffetch_moma_daily(code,st,et):urlf{BASE}/hsstock/history/{code}/d/n/{LICENCE}?st{st}et{et}rowsrequests.get(url,timeout20).json()ifnotisinstance(rows,list):raiseRuntimeError(f接口返回异常{rows})returnrowsdefrankdata(x):ordernp.argsort(x,kindmergesort)ranksnp.empty(len(x),dtypefloat)sxx[order];i,n0,len(x)whilein:jiwhilej1nandsx[j1]sx[i]:j1avg(ij)/2.01.0forkinrange(i,j1):ranks[order[k]]avg ij1returnranksdefspearman_ic(score,fwd_ret):mask~np.isnan(score)~np.isnan(fwd_ret)s,rscore[mask],fwd_ret[mask]iflen(s)30:returnnp.nan rs,rrrankdata(s),rankdata(r)rs-rs.mean();rr-rr.mean()denomnp.sqrt((rs**2).sum()*(rr**2).sum())returnfloat((rs*rr).sum()/denom)ifdenom0elsenp.nandefdesign(g_block,f_block,ret_block):T,K,Ng_block.shape Xnp.column_stack([f_block.reshape(-1)][g_block[:,k,:].reshape(-1)forkinrange(K)])returnX,ret_block.reshape(-1)defr2(X,y,coef):predX coef ss_res((y-pred)**2).mean()ss_tot((y-y.mean())**2).mean()return1.0-ss_res/ss_totifss_tot0else0.0defbuild_panel(n_stocks500,n_dates120,n_spurious20,seed20260912):rngnp.random.default_rng(seed)frng.standard_normal((n_dates,n_stocks))grng.standard_normal((n_dates,n_spurious,n_stocks))ret0.05*frng.standard_normal((n_dates,n_stocks))returnf,g,retdefmain():realfetch_moma_daily(600519,20250101,20250901)print(真实接口返回 rows,len(real),演示证书固定样本)f,g,retbuild_panel()n_datesf.shape[0]is_endint(n_dates*0.7)ic_isnp.nanmean([spearman_ic(f[:is_end][t],ret[:is_end][t])fortinrange(is_end)])ic_oosnp.nanmean([spearman_ic(f[is_end:][t],ret[is_end:][t])fortinrange(n_dates-is_end)])print(f鲁棒(1因子): IS-IC{ic_is:.4f}OOS-IC{ic_oos:.4f}效率比{ic_oos/ic_is:.2%})# 5 折滚动folds,step5,n_dates//5fis[]forkinrange(folds):ak*step;baint(step*0.7);castepifcn_datesorba:continuefis.append(np.nanmean([spearman_ic(f[a:b][t],ret[a:b][t])fortinrange(b-a)]))print(f 各折IC{np.round(fis,4).tolist()}跨折IC标准差{np.std(fis):.4f})# 过拟合反例sd,sn15,25Xis,yisdesign(g[:sd,:,:sn],f[:sd,:,:sn],ret[:sd,:,:sn])Xos,yosdesign(g[sd:],f[sd:],ret[sd:])coef,*_np.linalg.lstsq(Xis,yis,rcondNone)r2_isr2(Xis,yis,coef)r2_oosr2(Xos,yos,coef)coef1,*_np.linalg.lstsq(Xis[:,:1],yis,rcondNone)r2_is_1fr2(Xis[:,:1],yis,coef1)print(f过拟合(21因子): IS-R²{r2_is:.4f}(1因子仅{r2_is_1f:.4f}) OOS-R²{r2_oos:.4f}效率比{r2_oos/r2_is:.2%})if__name____main__:main()真实运行输出节选演示证书接入层已验证、panel 为固定种子演示数据真实接口返回 rows 50 演示证书固定样本 鲁棒(1因子): IS-IC0.0400 OOS-IC0.0446 效率比111.49% 各折IC[0.0396, 0.0319, 0.0415, 0.0579, 0.0471] 跨折IC标准差0.0086 过拟合(21因子): IS-R²0.0900(1因子仅-0.0018) OOS-R²-0.0945 效率比-105.05%结果非常说明问题鲁棒单因子模型 IS-IC 与 OOS-IC 几乎一致效率比 111%跨折 IC 标准差仅 0.0086极其稳定——它抓到的是真实边缘样本外能复现。而 21 因子暴力拟合IS-R² 被噪声因子虚抬到 0.09注意只用真实因子的 1 因子模型在同一小块 IS-R² 仅为 -0.0018可一到 OOS 大块R² 直接跌到 -0.0945效率比 -105%——典型的样本内完美、样本外崩塌。这条曲线如果拿去实盘亏损是大概率事件。文中数据为演示用合成截面仅供演示算法逻辑非实时行情生产环境请使用你自己的魔码正式证书并以官方文档与实时返回为准。七、六个抗过拟合铁律定义先于优化。跑任何优化前先写下策略逻辑和每个参数的经济含义。一个没有理由的参数就是过拟合的直接入口。参数最少化奥卡姆剃刀。同等性能下永远选参数更少的策略。经验上每个自由参数至少需要约 30 笔成交来支撑5 个以内参数最稳妥。样本外只测一次。IS 定稿前绝不看 OOS看了之后若想改参数必须从头重来否则检验作废。跨市场 / 跨状态验证。一个只在 2023-2025 牛市的策略和只在某一指数上有效的策略都应视为可疑。真边缘应在趋势、震荡、高波动多种状态下都站得住。蒙特卡洛重排互补手段。把历史成交序列随机打乱上千次若你的真实净值排不进前 5%说明优势可能来自运气而非真实边缘。定期再优化但别太勤。日线级策略每 2~3 个月再优化一次足够更频繁本身就是一种过拟合。八、小结与下篇预告过拟合的本质不是模型太复杂而是模型在记忆而非学习。对抗它的工程手段并不神秘严格分离样本内 / 样本外、用 Walk-Forward 看效率比与参数稳定性、控制自由参数数量、跨状态验证。当你学会用这些工具对一条完美曲线保持怀疑你就已经绕开了绝大多数量化新手会踩的第一个大坑。下一篇#49我们把视野拉到更上游的数据组织层多资产组合数据底座A股 / ETF / 指数统一接入——如何用一个统一接口把不同资产类别的行情收拢成可交叉分析的数据资产。文中接口调用使用演示证书返回数据为样本示例生产环境请使用你自己的魔码正式证书并以官方文档与实时返回为准。更多量化工程实战内容欢迎访问魔码官方技术博客https://www.momaapi.com/blog/