halo 的技术博客

返回

模型风险与回测过拟合检测:让回测结果值得信任#

每一个做过量化回测的人,都经历过这样的时刻:屏幕上一条净值曲线一路向北,年化收益 30%、夏普 2.5、最大回撤不到 5%,你激动得差点把键盘拍碎。然后你上实盘——三个月亏掉 15%,曲线比心电图还平。

这不是你一个人遇到的问题。学界有个著名估计:已发表的技术分析规则里,约 95% 的「显著」结果在样本外无法复现。问题往往不在市场,而在我们衡量策略的方式。本文把「模型风险(Model Risk)」与「回测过拟合(Backtest Overfitting)」拆开讲清楚,并给出可立即落地的检测工具。

一、模型风险到底是什么#

模型风险指:你用来做决策的模型,与真实的数据生成过程(DGP)之间存在偏差,而这个偏差会系统地损害收益或放大风险。它有三个来源:

  1. 模型误设(Model Misspecification):用了错误的函数形式。比如用线性回归去拟合明显非线性的市场状态切换。
  2. 参数不确定性(Parameter Uncertainty):模型结构对,但估计出的参数只是「恰好拟合了历史噪声」。
  3. 实现风险(Implementation Risk):前视偏差(look-ahead)、幸存者偏差、复权错误、滑点漏算——代码层面的 bug 比数学错误更致命。

回测过拟合是模型风险中最常见、也最隐蔽的一类,特指第 2 种:你在历史数据上反复调参、反复筛选,直到曲线「完美」,但拟合进去的主要是噪声。

二、过拟合的第一道裂缝:样本内 vs 样本外#

最直观的过拟合信号,是样本内(In-Sample)表现与样本外(Out-of-Sample)表现的巨大鸿沟。一个复杂度过高的模型(比如几十个因子的非线性叠加、或者网格搜索了上千组参数),会在样本内把噪声也「学」进去,于是样本内净值一路高歌,一旦换一段没见过的数据,立刻原形毕露。

过拟合示意:样本内拟合优度 vs 样本外表现崩塌

判断经验很简单:如果样本外夏普不到样本内的一半,基本可以判定过拟合。更严格的做法是要求样本外表现在统计上仍显著为正(这正是下一节去膨胀夏普比率要解决的)。

import numpy as np

def insample_vs_oos_gap(sr_insample, sr_oos):
    """计算样本内/外夏普比率的衰减比,>0.5 视为可疑"""
    ratio = sr_oos / sr_insample
    return ratio

# 假设网格搜索后得到样本内 SR=2.6, 样本外 SR=0.8
print(f"衰减比 = {insample_vs_oos_gap(2.6, 0.8):.2f}  (>0.5 才相对安全)")
python

除了「调参过度」,还有一类更隐蔽的过拟合来自实现层面——前视偏差(Look-Ahead Bias)。最典型的错误是:用包含当天收盘价的字段去生成「当天」的信号,却在开盘就成交。比如下面这段看似无害的代码,其实已经把未来信息泄漏了进去:

# ❌ 错误:用当日收盘价计算信号,却在同一根 K 线当天成交 -> 前视偏差
# 这里 rolling mean 用到了 close[t],而收益又用了未来信息
bad_signal = (df["close"].rolling(20).mean() > df["close"]).astype(int)

# ✅ 正确:信号只用 t-1 及之前的信息,成交用 t 日收益
df["signal"] = (df["close"].shift(1).rolling(20).mean() > df["close"].shift(1)).astype(int)
df["ret"] = df["close"].pct_change()  # t 日收益,信号严格基于 t-1 及以前
python

幸存者偏差(Survivorship Bias) 是另一类实现型过拟合:只在回测里保留「现在还活着的股票」,会自动剔除当年退市的垃圾股,历史收益被人为抬高 15%~30%。任何个股回测都必须用 point-in-time 的「当时真实上市池」,而非当前指数成分。这两类 bug 不会让曲线变难看,反而会让你误以为策略更好——所以它们比数学错误更危险。

三、多重检验:你试得越多,假阳性越必然#

过拟合的本质是多重检验(Multiple Testing)。每多跑一次参数组合、多试一个因子、多切一次时间段,你就在做一次「显著性检验」。单次检验犯第一类错误(假阳性)的概率是 α(通常取 5%),但当你独立跑 N 次时,至少出现一次假阳性的概率是:

P=1(1α)NP = 1 - (1-\alpha)^N

多重检验陷阱:试验越多,至少一次假阳性越接近必然

这意味着:如果你系统地扫了 100 组参数,即使策略本身毫无预测力,也有约 99.4% 的概率至少出现一组「看起来显著」。你以为找到了圣杯,其实只是概率的必然产物。这正是「策略挖掘机」最容易踩的坑——把数据挖掘的副产品当成 alpha。

alpha = 0.05
for n in [10, 50, 100, 500, 1000]:
    p_fp = 1 - (1 - alpha) ** n
    print(f"N={n:4d}  至少一次假阳性概率 = {p_fp:.3f}")
# N=58 时概率已突破 95%
python

四、去膨胀夏普比率(Deflated Sharpe Ratio)#

既然「高夏普」可能只是多重检验的假象,Bailey & López de Prado 提出了去膨胀夏普比率(Deflated Sharpe Ratio, DSR):给定一个观测到的夏普比率,它在「原假设(策略无真实 alpha + 经历过多次检验)」下成立的概率到底有多高?

核心思想:把观测夏普和「经过 V 次检验、样本量 T、偏度 S、超额峰度 K」后的原假设分布做比较。我们可以用蒙特卡洛近似其形状——在原假设下,夏普比率近似服从均值为 0、标准差为 1/T1/\sqrt{T} 的正态分布(T 为样本期数)。

去膨胀夏普比率:观测 SR 在原假设下的分位

实践忠告:DSR 的可信度高度依赖你如实申报自己试过多少组合(V)。隐瞒搜索次数,等于自己骗自己。

五、Walk-Forward:把「未来」留给验证#

防御过拟合最朴素也最有效的工程手段是 Walk-Forward 分析(滚动样本外验证):把历史切成多段,每段只用前半段训练/调参,后半段做样本外测试,像这样滚动前进——

Walk-Forward 验证:滚动训练 / 样本外测试窗口

如果每一段样本外的表现都稳定为正且接近样本内,策略才值得信任。任何只在「最后一次全样本」上才好看的结果,都是红旗。

六、一份可落地的「防过拟合」清单#

把上面几节浓缩成回测上线前的 checklist:

  1. 先定假设,再跑数据:因子逻辑必须来自经济直觉或文献,禁止「先画曲线再编故事」。
  2. 划分清晰:训练/验证/测试三段严格隔离,测试集只用一次。
  3. 申报搜索次数 V:网格搜索、因子筛选的规模必须记录,用于 DSR 修正。
  4. 看样本外衰减比:OOS 夏普 < 0.5×IS 夏普,直接否决。
  5. Walk-Forward 稳定性:多个 OOS 窗口收益方向需一致。
  6. 压力测试:在 2015 股灾、2018 去杠杆、2020 新冠等极端段单独看表现。
  7. 成本敏感度:把手续费、滑点、冲击成本加进去再算一遍,很多策略在「零成本」下才好看。

七、从回测到实盘:小步快跑的上线流程#

过拟合检测做完,也别直接全仓上。再漂亮的 OOS 曲线,也只是「历史没崩」。建议分三步把模型风险继续压低:

  1. 纸面交易(Paper Trading) 1–3 个月,验证信号在工程链路里能准时生成、成交与风控逻辑正确,重点排查数据延迟与复权错误。
  2. 小资金实盘 跑 3–6 个月,对比实盘与样本外回测的偏差,重点关注滑点、成交率、拒单——这些在回测里常被理想化。
  3. 分批加仓 只有实盘表现与 OOS 回测在统计上一致,才逐步放大仓位。

这个阶段暴露的问题(延迟、漏单、复权错误)往往比策略本身更致命,值得投入同等精力。模型风险管理的本质,不是追求一条更陡的曲线,而是把「未知」控制在一个你能承受的区间内

结语#

模型风险不会因为你用了更复杂的模型而消失,反而常常因为复杂度的提升而放大。回测的价值不在于证明策略能赚钱,而在于用尽可能严酷的方式试图证伪它——能在多重检验、样本外衰减、Walk-Forward 和成本压力之下都站得住的策略,才配得上你的真金白银。下一次看到那条漂亮的净值曲线,先问一句:这是 alpha,还是噪声的代价?

模型风险与回测过拟合检测:让回测结果值得信任
https://blog.halo26812.eu.org/blog/model-risk-backtest-overfitting
Author halo
Published at 2026年7月10日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨