3、波动率预测模型:GARCH模型、EWMA模型、波动率锥的构建与应用
做利率债做市,最怕什么?不是方向看错,而是波动率突然放大,你手里的头寸像坐过山车一样。我做了这么多年交易,有个深刻的体会:预测波动率,比预测方向更重要。方向错了可以止损,波动率没算对,仓位管理和对冲全都会乱套。
这一章,我们就来聊聊怎么预测波动率。我会重点讲三个工具:GARCH模型、EWMA模型,还有波动率锥。这三个东西,说白了就是帮你回答一个问题——“明天市场会动多大?”
核心逻辑:波动率预测不是算命,而是用历史数据去推断未来的“震荡幅度”。做市商靠的是双边报价赚差价,波动率一旦超出预期,你的报价可能被瞬间吃掉,或者你根本报不出有竞争力的价格。
3.1 GARCH模型:捕捉波动率的“记忆效应”
GARCH模型,全称是广义自回归条件异方差模型。名字听着吓人,其实核心思想很简单:今天的波动率,跟昨天的波动率有关,也跟昨天的“意外”有关。
我刚开始用GARCH的时候,总觉得它太学术。后来在实盘里跑了一次,发现它确实能抓到一些规律——比如市场大跌之后,波动率往往会持续高企一段时间。这就是所谓的“波动率聚集”现象。
3.1.1 模型公式
标准的GARCH(1,1)模型长这样:
σ²(t) = ω + α * ε²(t-1) + β * σ²(t-1)
其中:
- σ²(t):今天的条件方差(波动率的平方)
- ω:长期平均方差(常数项)
- ε²(t-1):昨天的“冲击”或“残差”平方
- σ²(t-1):昨天的条件方差
- α + β < 1:平稳性条件,保证波动率不会发散
说白了,这个模型就是在说:今天的波动率 = 长期均值 + 昨天冲击的影响 + 昨天波动率的影响。
我的经验:α和β的取值很有意思。α越大,说明市场对新鲜冲击反应越剧烈;β越大,说明波动率的“记忆”越长。我在国债期货上跑出来的参数,α通常在0.05-0.1之间,β在0.85-0.95之间。这意味着利率债的波动率有很强的持续性,一次冲击的影响会持续好几天。
3.1.2 Python实现
用Python实现GARCH模型,我推荐用arch库。下面是一个完整的示例:
import numpy as np
import pandas as pd
from arch import arch_model
import matplotlib.pyplot as plt
# 假设我们有一列收益率数据
# returns = pd.Series(...)
# 拟合GARCH(1,1)模型
model = arch_model(returns, vol='Garch', p=1, q=1)
result = model.fit(disp='off')
# 查看参数
print(result.params)
# 预测未来5天的波动率
forecast = result.forecast(horizon=5)
predicted_vol = np.sqrt(forecast.variance.values[-1, :])
# 可视化
plt.plot(np.sqrt(result.conditional_volatility), label='条件波动率')
plt.plot(predicted_vol, label='预测波动率', linestyle='--')
plt.legend()
plt.show()
注意:GARCH模型对数据频率很敏感。我建议用日频数据,不要用分钟级数据去拟合,否则参数估计会很不稳定。另外,记得先做ARCH效应检验——如果数据本身没有波动率聚集现象,用GARCH就是白费力气。
3.2 EWMA模型:简单粗暴但有效
EWMA,指数加权移动平均。这个模型比GARCH简单得多,但我在实盘里用得反而更多。为什么?因为它计算快、参数少、不容易过拟合。
EWMA的核心思想是:越近的数据,权重越大;越远的数据,权重指数衰减。
3.2.1 公式与实现
σ²(t) = λ * σ²(t-1) + (1 - λ) * r²(t-1)
其中:
- λ:衰减因子,通常取0.94(日频数据)或0.97(周频数据)
- r²(t-1):昨天的收益率平方
你看,这个公式比GARCH少了一个参数。我个人的习惯是,做市报价的波动率调整用EWMA,因为快;做风险管理的压力测试用GARCH,因为准。
避坑指南:我曾经在2017年用EWMA做国债期货的波动率预测,λ设成了0.99。结果市场突然波动,模型反应慢了两天,差点出大问题。后来我改成0.94,虽然短期波动大了点,但至少能跟上市场节奏。记住:做市交易,宁可反应过度,不要反应迟钝。
3.2.2 代码示例
def ewma_vol(returns, lambda_=0.94):
"""
计算EWMA波动率
"""
vol = np.zeros_like(returns)
vol[0] = np.var(returns[:20]) # 初始值用前20天方差
for i in range(1, len(returns)):
vol[i] = lambda_ * vol[i-1] + (1 - lambda_) * returns[i-1]**2
return np.sqrt(vol)
# 使用
vol_series = ewma_vol(returns, lambda_=0.94)
3.3 波动率锥:多周期视角下的“正常范围”
波动率锥,是我个人最喜欢的工具。它不预测具体的波动率数值,而是告诉你:在当前市场环境下,不同持有期的波动率处于什么历史分位。
你想想看,做市交易最怕什么?不是波动率大,而是波动率超出预期。波动率锥就是帮你建立这个“预期”的。
3.3.1 构建方法
构建波动率锥的步骤很简单:
- 计算不同持有期(1天、5天、20天、60天等)的历史波动率
- 对每个持有期,计算波动率的分位数(10%、25%、50%、75%、90%)
- 把这些分位数连成线,就形成了“锥”的形状
| 持有期 | 10%分位 | 25%分位 | 50%分位 | 75%分位 | 90%分位 |
|---|---|---|---|---|---|
| 1天 | 0.12% | 0.18% | 0.25% | 0.35% | 0.48% |
| 5天 | 0.28% | 0.38% | 0.52% | 0.68% | 0.85% |
| 20天 | 0.55% | 0.72% | 0.90% | 1.10% | 1.35% |
| 60天 | 0.80% | 1.00% | 1.25% | 1.55% | 1.90% |
怎么用?假设当前1天期波动率是0.30%,落在50%-75%分位之间。这说明市场处于正常偏高的状态。但如果1天期波动率突然跳到0.50%,超过了90%分位——嗯,这时候你要小心了,市场可能进入了异常状态。我一般会在这个时候收紧报价价差,或者降低持仓规模。
3.3.2 Python实现
def volatility_cone(returns, periods=[1, 5, 20, 60], quantiles=[0.1, 0.25, 0.5, 0.75, 0.9]):
"""
构建波动率锥
"""
cone = {}
for period in periods:
# 计算滚动波动率
rolling_vol = returns.rolling(period).std() * np.sqrt(252/period)
# 计算分位数
cone[period] = rolling_vol.quantile(quantiles).values
return pd.DataFrame(cone, index=quantiles)
# 使用
cone_df = volatility_cone(returns)
print(cone_df)
3.4 三个模型的对比与选择
说了这么多,到底该用哪个?我个人的建议是:
- 做市报价调整:用EWMA。快,参数少,适合高频调整。
- 风险管理:用GARCH。准,能捕捉波动率的动态特征。
- 情景分析:用波动率锥。直观,告诉你当前处于什么历史位置。
我的实战组合:每天早上开盘前,我会先看波动率锥,判断市场处于什么状态。然后用EWMA计算一个实时波动率,用于调整报价价差。最后用GARCH做一个未来5天的预测,用于决定今天的仓位规模。三个模型各司其职,配合起来效果很好。
嗯,这一章的内容就到这里。波动率预测不是万能的,但没有波动率预测是万万不能的。尤其是在利率债做市这种高频、低利差的环境里,算准波动率,就等于算准了利润。