回测结果太好,可能是回测本身在骗你

年化 80%、最大回撤 5% 的回测曲线第一眼很美,第二眼该起疑了。本文梳理四类最常见的回测自欺机制,并给出一份上线前的自检清单。

一句话版本:回测收益 = 策略真实收益 + 回测偏差。后者的量级经常比前者大,而且它永远是正的——这正是回测曲线总是很好看的原因。

为什么「太好」本身就是警报

一份合规的、无偏的回测,其样本外表现通常低于样本内。如果你的回测满足下面任意一条,请优先怀疑回测,而不是庆祝策略:

  • 年化收益高得像印钞机,回撤却小得像货币基金;
  • 参数稍微改动 5%,绩效指标剧烈波动;
  • 用全市场历史数据选出来的「最优参数」,只在这一段历史里最优。

下面四类机制,覆盖了我见过 80% 的「假回测」。

陷阱一:未来函数(前视偏差)

定义:在时刻 T 的决策里,用到了 T 时刻尚不可知的信息。

最常见的三种形态:

# ❌ 1. 用当天收盘价决策、按当天收盘价成交
signal = data.close[t] > ma(t)          # 收盘后才知道
buy(price=data.close[t])                # 却按收盘价成交

# ❌ 2. 用了"全样本"计算的指标
z = (x - x.mean()) / x.std()            # mean/std 含未来数据

# ❌ 3. 财报数据没有对齐公告日
use(report_data=quarterly_revenue)      # 4 月才公告的一季报,1 月就用上了

修法:决策输入只用 t-1 及之前的数据;所有统计量做滚动窗口;财务数据按 point-in-time(公告日)对齐,而不是按报告期对齐。

陷阱二:幸存者偏差

定义:样本里只有「活到最后」的标的,退市、停牌、被剔除的都消失了。

用今天的成分股名单去回测过去十年,等于用后视镜选股——你提前知道了谁会活下来。

修法:

  • 使用历史时点的成分股名单(point-in-time universe),而不是今天的名单;
  • 把退市标的保留在数据集里,用退市前的真实价格结算;
  • 如果数据源不提供历史名单,至少在文中明确声明这是已知偏差,并估计它的量级。

陷阱三:参数挖掘与隐性过拟合

当你在 500 组参数里挑出最好的一组时,你做的不是优化,是多重检验——哪怕全是随机数据,500 组里也必然有一组看起来像圣杯。

一个快速自检:

现象可能病因
最优参数在网格边缘搜索空间没边界感,多半在拟合噪声
参数改动 ±10%,绩效腰斩适应度曲面太陡,样本内幻觉
换一段历史,最优参数就变你找到的不是规律,是巧合

修法:

  1. walk-forward 滚动验证:只用「当时可用」的数据选参数,滚动到下一段评估;
  2. 参数平原优先于参数尖峰:选一大片「都还行」的区域,不选孤峰;
  3. Deflated Sharpe Ratio / 多重检验校正:把「试了多少组」计入显著性,试得越多,要求越严。

陷阱四:样本内幻觉

全样本调参 + 全样本报告绩效,等于考试前拿到了答案再考试。

最低标准:切出一段从未参与任何决策的时间段(含参数选择、规则修改、指标筛选)作为最终测试集。碰过它一次,它就不再是测试集。

上线前自检清单

把下面这份清单固化成脚本,跑通全绿再看收益曲线:

  • 决策输入是否只含 T 时刻已知信息?(滚动统计、滞后信号)
  • 财务/基本面数据是否按公告日对齐?(point-in-time)
  • 股票池是否保留了历史时点的退市/剔除记录?
  • 参数选择过程是否与最终评估数据隔离?
  • 是否报告了「尝试过的参数组数」并做多重检验校正?
  • 样本外绩效是否单独列示(而不是和样本内混在一起)?
  • 交易成本、滑点、涨跌停无法成交是否建模?

写在最后

回测的作用是证伪,不是证明。一条好看的回测曲线只能说明「这个策略在过去的数据上没有被击毙」,不能说明它未来可用。

先怀疑回测,再怀疑市场,最后才轮到怀疑策略——这个顺序,能帮你省下很多真金白银的学费。


本文讨论的是回测工程方法,不构成投资建议。任何策略的实盘表现都可能与回测存在显著差异。