Walk-Forward 与样本内外验证
直觉
防止过拟合最有效的工程手段是样本外验证:只在训练集上调参,在从不参与调参的测试集上检验。Walk-Forward 更进一步——滚动地「用过去选参、交易未来」,模拟策略在实盘中不断重新适应的过程,是稳健性检验的金标准。
样本内外划分
- 在 IS 上扫参数、选最优;
- 用该参数在 OOS 上跑一次,报告结果;
- 永远不要反过来在 OOS 上挑参数(那是选择偏差,等于作弊)。
Walk-Forward
把时间切成连续窗口:第 段用「之前的数据」选参,交易「下一段」,拼接所有 OOS 段得到真实可得的累计表现。
「人话」解释:Walk-Forward 为什么可信?
它模拟了「你当时只能看到那些数据」的现实——每一步都只用过去、交易未来,没有任何事后信息。 拼接起来的曲线,是你这个策略真能实现的历史表现,不像单次 IS 拟合那样乐观。 如果 Walk-Forward 夏普还过得去,策略才算「稳健」而非过拟合。
可运行案例:IS 选参 → OOS 检验 + Walk-Forward
import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
close = df['adj_close']
n = len(close); split = int(n * 0.7)
is_p, oos_p = close.iloc[:split], close.iloc[split:]
grid = [(5,20),(10,30),(20,60),(20,120),(40,120)]
def sig(p, f, s):
raw = (p.rolling(f).mean() > p.rolling(s).mean()).astype(float)
return raw.replace(0, np.nan).ffill().fillna(0)
def sh(p, f, s):
return quant.sharpe(quant.vector_backtest(p, sig(p,f,s), cost_bps=2.0, freq=252)['returns'], freq=252)
# 1) IS 选最优参数, 看 OOS 是否守得住
best = max(grid, key=lambda fs: sh(is_p, *fs))
print(f"IS 最优参数 {best}: IS 夏普={sh(is_p,*best):.3f} OOS 夏普={sh(oos_p,*best):.3f} (通常下降)")
# 2) 选择偏差: 作弊地在 OOS 选最优(高估真实能力)
cheat = max(grid, key=lambda fs: sh(oos_p, *fs))
print(f"OOS 作弊最优 {cheat}: 夏普={sh(oos_p,*cheat):.3f} ← 用未来选参, 不可信")
# 3) Walk-Forward: 用过去 3 年选参, 交易下 1 年
w, step = 252*3, 252
seg_sharpe = []
for start in range(w, n - step, step):
train = close.iloc[start-w:start]
test = close.iloc[start:start+step]
fs = max(grid, key=lambda x: sh(train, *x))
seg_sharpe.append(sh(test, *fs))
print(f"\nWalk-Forward 各年 OOS 夏普: {[round(x,2) for x in seg_sharpe]}")
print(f"均值={np.mean(seg_sharpe):.3f} 正比例={np.mean([x>0 for x in seg_sharpe]):.0%}")
print("→ 不依赖某次幸运选参, 才是稳健策略。")
小结
- 样本外验证:只在 IS 调参,OOS 只评一次;
- 选择偏差:在 OOS 上挑参数 = 作弊,会高估;
- Walk-Forward:滚动「用过去选参、交易未来」,是稳健性金标准。
模块 8 完成,M5(模块 5–8)全部交付。下一批 M6 将进入机器学习、风控与 Capstone。
小测验
1. 在测试集(OOS)上反过来挑最优参数,属于?
2. Walk-Forward 每一步是怎么做的?
3. 为什么 Walk-Forward 比「单次全样本拟合」更可信?