4、GARCH模型:标准GARCH(1,1)模型、模型参数解释、预测步骤与实战代码
波动率预测,说白了就是猜市场明天会疯到什么程度。我做了这么多年量化,见过太多人把精力花在预测价格方向上,结果被波动率一口吃掉。其实波动率本身是有规律的,GARCH模型就是用来捕捉这个规律的利器。
4.1 为什么需要GARCH?
先问个问题:你观察过金融时间序列吗?你会发现一个现象——大的波动后面往往跟着大的波动,小的波动后面跟着小的波动。这就是所谓的"波动率聚集"效应。
传统的线性模型假设方差是常数,这显然不符合现实。我记得刚入行时,用简单移动平均算波动率,结果回测时表现不错,实盘一跑就崩。为什么?因为移动平均给过去所有数据同样的权重,而实际上近期的波动信息更重要。
GARCH模型的出现解决了这个问题。它让方差随时间变化,并且依赖于过去的方差和过去的残差。说白了,就是让模型自己学会"波动是会传染的"。
核心思想:今天的波动率 = 长期平均波动率 + 昨天的波动冲击 + 昨天的波动率
4.2 标准GARCH(1,1)模型结构
GARCH(1,1)是最常用的形式。括号里的两个数字,第一个1表示ARCH项阶数,第二个1表示GARCH项阶数。我个人的习惯是,先从(1,1)开始试,90%的情况都够用。
模型由两部分组成:
均值方程:
r_t = μ + ε_t
ε_t = σ_t * z_t, z_t ~ N(0,1)
方差方程:
σ_t² = ω + α * ε_{t-1}² + β * σ_{t-1}²
这里每个参数都有明确的含义:
| 参数 | 含义 | 约束条件 |
|---|---|---|
| ω (Omega) | 长期平均方差的截距项 | ω > 0 |
| α (Alpha) | ARCH项系数,衡量新信息对波动的影响 | α ≥ 0 |
| β (Beta) | GARCH项系数,衡量波动持续性 | β ≥ 0 |
还有一个重要约束:α + β < 1。这个条件保证了模型是平稳的。如果α+β接近1,说明波动冲击衰减得很慢。我在处理比特币数据时遇到过α+β=0.98的情况,这意味着一次冲击的影响会持续很久很久。
经验之谈:α通常小于0.1,β通常在0.85-0.98之间。如果α很大,说明市场对新信息反应过度;如果β接近1,说明波动率有长记忆性。
4.3 预测步骤
用GARCH做预测,其实就三步。嗯,这里要注意,预测的是方差,不是价格。
- 第一步:估计模型参数
用历史数据通过最大似然估计(MLE)算出ω、α、β。说白了就是找一组参数,让当前数据出现的概率最大。
- 第二步:计算条件方差序列
用估计好的参数,从第一个样本开始递推计算每一天的σ_t²。这步会得到整个历史波动率序列。
- 第三步:向前预测
对于h步向前预测,公式是:
σ²_{t+h|t} = ω + (α+β) * σ²_{t+h-1|t}当h很大时,预测值会收敛到长期方差ω/(1-α-β)。
避坑指南:我曾经犯过一个错误——直接用预测的方差去算期权价格,结果发现预测值总是偏低。后来才意识到,GARCH预测的是条件方差,而期权定价需要的是实现波动率。两者有本质区别,千万别混用。
4.4 实战代码
下面我用Python演示完整的GARCH(1,1)建模流程。这里用的是arch库,我个人觉得比手动实现方便得多。
import numpy as np
import pandas as pd
import yfinance as yf
from arch import arch_model
import matplotlib.pyplot as plt
# 1. 获取数据
# 我用的是SPY,你也可以换成任何股票或指数
ticker = 'SPY'
data = yf.download(ticker, start='2020-01-01', end='2023-12-31')
returns = 100 * data['Adj Close'].pct_change().dropna()
# 2. 拟合GARCH(1,1)模型
# mean='Zero'表示均值方程中μ=0,对日频数据来说够用了
model = arch_model(returns, vol='Garch', p=1, q=1, mean='Zero')
result = model.fit(disp='off')
# 3. 查看参数
print(result.summary())
# 4. 提取条件波动率
conditional_vol = result.conditional_volatility
# 5. 向前预测5步
forecasts = result.forecast(horizon=5)
predicted_var = forecasts.variance.iloc[-1] # 最后一天的预测值
predicted_vol = np.sqrt(predicted_var)
print(f"预测的波动率(未来5天): {predicted_vol.values}")
# 6. 可视化
plt.figure(figsize=(12, 6))
plt.plot(returns.index, returns, alpha=0.5, label='日收益率')
plt.plot(conditional_vol.index, conditional_vol,
color='red', linewidth=2, label='条件波动率')
plt.title(f'{ticker} - GARCH(1,1)条件波动率')
plt.legend()
plt.show()
运行这段代码,你会看到红色的波动率曲线紧紧包裹着收益率序列。波动大的地方曲线就高,波动小的地方曲线就低。这就是GARCH的魅力——它能动态捕捉市场的"情绪"。
4.5 模型诊断
模型建好了,怎么知道好不好用?我一般看三个东西:
- 标准化残差:用ε_t / σ_t算出来,应该近似白噪声。如果还有明显的自相关,说明模型没捕捉全。
- Ljung-Box检验:检验残差平方的自相关性。p值大于0.05就算合格。
- 参数显著性:看p值,α和β的p值应该小于0.05。
一个实用的建议:别只看拟合效果,一定要做回测。我习惯用滚动窗口预测,然后比较预测波动率和实际实现波动率。如果预测偏差太大,说明模型参数不稳定,需要重新考虑数据窗口长度。
4.6 知识体系总览
下面这张图总结了GARCH(1,1)的核心逻辑。你看,从数据输入到预测输出,整个流程是闭环的。
你想想看,整个流程其实就一个递推公式。有了昨天的方差和昨天的冲击,就能算出今天的方差。这就是GARCH(1,1)最优雅的地方——简单,但足够强大。
最后说一句,GARCH模型不是万能的。它假设波动率是对称的,但实际市场中,利空消息往往比利好消息带来更大的波动。这就是后面要讲的EGARCH模型要解决的问题了。
公众号:蓝海资料掘金营,微信deep3321