跳到主要内容

预测评估与陷阱

直觉

建好模型后必须诚实评估它到底预测得准不准。两个铁律:(1) 永远在样本外(未参与训练的数据)评估;(2) 先和朴素基准比——如果你的复杂模型连「用历史均值预测」都打不过,它就没有价值。

评估流程

  1. 划分训练集 / 测试集(时间顺序,不能打乱!);
  2. 在训练集拟合,对测试集预测
  3. RMSE(或 MAE)对比朴素基准;
  4. 残差诊断:好的模型,残差应是白噪声(没有剩余结构)。
RMSE=1nt(r^trt)2\text{RMSE} = \sqrt{\frac{1}{n}\sum_t (\hat r_t - r_t)^2}
「人话」解释:为什么必须样本外?

模型在训练集上总能「记住」数据(参数多到能拟合每个点),训练集 RMSE 可以任意小——但这叫过拟合,不等于预测力。 只有「没见过的测试集」上的表现才算数。这正是 模块 0.3 过拟合那一节的落地。

可运行案例:收益预测 vs 均值基准

用前段数据训练 AR(1),预测最后 252 天,对比「历史均值」基准——你会看到模型几乎打不过基准(收益难预测),但残差已是白噪声(模型没遗漏结构)。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import acf

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
ret = np.log(df['adj_close'] / df['adj_close'].shift(1)).dropna()

n_test = 252
train, test = ret.iloc[:-n_test], ret.iloc[-n_test:]

m = ARIMA(train, order=(1, 0, 0)).fit()
fc = np.asarray(m.forecast(n_test))

rmse_model = np.sqrt(np.mean((test.values - fc)**2))
rmse_mean  = np.sqrt(np.mean((test.values - train.mean())**2))   # 朴素基准: 历史均值
print(f"AR(1) 模型 RMSE = {rmse_model:.5f}")
print(f"历史均值基准 RMSE = {rmse_mean:.5f}")
print(f"→ 模型{'优于' if rmse_model < rmse_mean else '≈ 不优于'}基准(收益均值极难预测)")

resid = m.resid
ra = acf(resid, nlags=10, fft=True)
print(f"\n残差 ACF(1~10阶): {[round(v, 2) for v in ra[1:]]}")
print("(都在 ±0.05 内 → 近似白噪声 → 模型已提取可用的线性结构)")

plt.figure(figsize=(8, 3.2))
plt.plot(range(n_test), test.values, lw=0.9, label='真实收益(测试集)')
plt.plot(range(n_test), fc, color='crimson', lw=1.4, label='AR(1) 预测')
plt.axhline(train.mean(), color='gray', ls='--', lw=0.8, label='历史均值')
plt.title('样本外预测 vs 真实'); plt.legend(fontsize=8)
plt.tight_layout(); plt.show()

小结

  • 样本外评估 + 朴素基准是预测可信度的底线;
  • 收益均值难预测——模型常打不过「均值基准」;
  • 残差白噪声说明模型已榨干结构;残差还有自相关则模型仍有改进空间。

模块 6 完成。下一模块用统计模型做投资组合优化