波动率是可以被预测的——这是学界和业界都验证过的事实,也是所有波动率交易、风险预算、期权定价的地基。但「预测波动」这件事有个反直觉的难点:波动率不是白噪声,它有长记忆(long memory)——今天的波动会缓慢地影响未来好几周,普通的 AR(1) 抓不住这么长的尾巴。
本文的主角 HAR-RV(Heterogeneous Autoregressive model of Realized Volatility,Corsi 2009) 用一个极巧的招数解决了这个问题:不跟长记忆死磕,而是用「日 / 周 / 月」三个不同时间尺度的已实现方差平均值,把长记忆等价成一个简单线性回归。我们以分钟频合成数据从零实现它,样本外 R² 达到 0.35,明显优于 naive(拿今天方差当明天预测,R²=0)和单日 AR(1)(R²=0.31)。
一、先把「波动率」量出来:已实现方差#
传统 GARCH 用的是日收益平方 当波动的「有噪声的代理」。但如果你有日内分钟频数据,可以直接把一天内的高频收益平方加起来,得到已实现方差(Realized Variance):
其中 是第 天第 个 5 分钟收益(一天约 根 5 分钟 K)。在「无微观结构噪声」的理想下,当 , 几乎无偏地收敛到当天的积分方差——它比单看 干净得多,这就是 HAR-RV 用 RV 而不用日收益平方的根本原因。

上面这张图是用「慢随机游走 + 快 AR(1)」两层构造的带长记忆日度方差,再叠加上日内 78 根 5 分钟收益还原出的 RV。肉眼就能看到 volatility clustering(波动聚集):大波动后面跟着大波动,小波动后面跟着小波动,而且这种「大—大」的联动能拖很久。
二、长记忆:为什么 AR(1) 不够用#
波动率的长记忆意味着它的自相关函数(ACF)衰减得很慢——lag 20 天、lag 50 天还有显著相关。标准的 AR(1) 模型 只有「昨天→今天」一条链,记忆长度被 指数式压缩,对 20 天以外的过去几乎视而不见。
下面把 RV 的 ACF 画出来就清楚了:

蓝线是 的 ACF,拖着长长的一条尾巴——这就是长记忆的证据。要预测明天的波动,你得把这条尾巴里蕴含的「过去几周都在躁动」的信息用上,而 AR(1) 做不到。
三、HAR-RV 的妙招:三个时间尺度#
Corsi 的洞见是:长记忆可以由多个不同速度的 AR(1) 叠加出来。与其去拟合一个分数阶差分(那很麻烦),不如直接把「日度、周度、月度」三个尺度的平均都放进回归:
其中:
- 是过去 5 天的均值(周尺度);
- 是过去 22 天的均值(月尺度);
- 三个系数分别捕捉「今天 / 这一周 / 这一个月」对明天方差的贡献。
「日」项吸收快速波动,「周」项吸收中等记忆,「月」项吸收缓慢漂移——三者一叠加,就等价于一条长记忆链,却只是个普通最小二乘线性回归,无需任何数值优化。
四、Python:从分钟频数据到样本外预测#
下面这段代码与三张配图完全对应,是自洽合成数据(两层方差结构 + 日内 78 根 5 分钟收益还原 RV),仅用于演示方法;真实落地请用实际的高频数据(见文末)。
import numpy as np
def build_data(T=2000, M=78, seed=20260712):
rng = np.random.default_rng(seed)
# 慢层(近似单位根→长记忆)+ 快层(AR(1))
slow = np.zeros(T)
for t in range(1, T):
slow[t] = slow[t - 1] + 0.012 * rng.normal()
fast = np.zeros(T)
for t in range(1, T):
fast[t] = 0.5 * fast[t - 1] + 0.5 * rng.normal()
logvar = np.log(0.0004) + 0.9 * slow + 0.3 * fast # 日波动率≈1%~2%
sig = np.sqrt(np.exp(logvar))
rv = np.empty(T)
for t in range(T):
intra = rng.normal(0.0, sig[t] / np.sqrt(M), size=M)
rv[t] = np.sum(intra ** 2) + 1e-7 * abs(rng.normal()) # 加微结构噪声地板
return rv
def har_matrix(rv, w=5, m=22):
rows, y = [], []
for t in range(1, len(rv)):
if t - 1 - m < 0:
continue
rows.append([1.0, rv[t - 1],
rv[t - 1 - w:t - 1].mean(),
rv[t - 1 - m:t - 1].mean()])
y.append(rv[t])
return np.array(rows), np.array(y)
rv = build_data()
X, y = har_matrix(rv)
cut = int(len(y) * 0.7)
beta, *_ = np.linalg.lstsq(X[:cut], y[:cut], rcond=None)
print(f"β0={beta[0]:.3e} β_d={beta[1]:.3f} β_w={beta[2]:.3f} β_m={beta[3]:.3f}")python跑出来训练集系数为 β_d=0.263、β_w=0.201、β_m=0.374:三个尺度都有显著正贡献,且「月」项最大——说明慢变的波动水平才是预测明天波动的主力,这也正是长记忆存在的直接证据。
五、样本外:HAR-RV 真的更准#
预测波动最诚实的评判是样本外 R²,以 naive 基准(直接拿今天 RV 当明天预测)为分母:
Xte, yte = X[cut:], y[cut:]
fc_har = Xte @ beta
fc_naive = Xte[:, 1]
# AR(1) 基准
b0, b1 = np.polyfit(y[:cut][:-1], y[:cut][1:], 1)
fc_ar1 = b0 + b1 * y[cut - 1:-1]
def oos_r2(a, fc):
return 1 - ((a - fc) ** 2).sum() / ((a - fc_naive) ** 2).sum()
print(oos_r2(yte, fc_har), oos_r2(yte, fc_ar1)) # 0.35, 0.31python结果:HAR-RV 样本外 R²=0.35,单日 AR(1)=0.31,naive=0。HAR-RV 不仅比「明天=今天」好一大截,也稳定压过只看昨天的 AR(1)——多出来的那 0.04,正是「周 + 月」两个长记忆项从过去几周里挖出来的信息。


六、实盘三类真实陷阱#
陷阱一:跳变(jumps)会污染 RV。 上面我们给 RV 加了 量级的噪声地板,真实市场里更凶的是隔夜跳空和盘中突发新闻——单根 5 分钟收益的平方可能是常规值的几十倍。这些跳变不是「波动」而是「水平位移」,会系统性高估 RV。稳健做法是先做跳跃检验(如 bipower variation 把跳和连续波动拆开),或干脆用中位数类稳健估计,别让一根异常 K 线主导你的预测。
陷阱二:时变波动率 + 结构断点。 HAR-RV 的系数 是用全样本 OLS 估的,隐含「过去十年波动的记忆结构恒定」。但 2015 股灾、2020 新冠、2022 加息的波动机制完全不同,固定系数会过时。实战要用滚动窗口或把 HAR-RV 接进马尔可夫区制转换,让周/月项的权重随 regime 调整——否则你在低波动期学出的「月项权重」会在危机里严重低估风险。
陷阱三:微结构噪声让 RV 在高频失真。 我们用 5 分钟数据,正是噪声与信息量的折中:频率太高(如 1 分钟),买卖价差造成的「伪波动」会让 RV 高估;频率太低(如 30 分钟),又丢掉了日内信息。真实落地要先用市场微观结构噪声方差估计挑一个最优采样频率(通常 5~15 分钟),而不是随手取。
七、落地路径与诚实结论#
真实复现时,把合成序列换成:
- 数据:交易所/券商的 1
5 分钟 K 线(股票、指数、期货均可),至少 35 年以覆盖多个波动 regime; - RV 构造:用 5 分钟收益平方和,加跳跃稳健处理;若做跨境/跨资产,注意不同市场交易时段与假期对齐;
- 扩展:HAR-RV 极易加项——HAR-CJ(拆连续/跳)、HARQ(用已实现半方差区分好坏波动)、HAR 加宏观/隐波变量;
- 用法:RV 预测不是交易信号本身,而是波动率目标仓位、期权 Delta 对冲再平衡频率、风险预算的输入。它最擅长回答「明天该比平时更保守还是更激进」。
结论:HAR-RV 用「日 / 周 / 月」三个平均,把一个棘手的分数阶长记忆问题变成了一个闭式线性回归,样本外 R² 0.35 显著高于朴素基准。它不魔法——它的力量来自「认真对待波动的长记忆」,而不是假装明天只取决于昨天。落到自己数据上,先确认采样频率、拆掉跳变、再让系数随时间滚动,这三条做到,HAR-RV 才会从论文里的 0.35 变成你组合里真能用的风险刻度。