4. 市场风险模型(上):波动率建模与相关性建模
各位同学,欢迎来到第四章。这一章我们聊点硬核的——波动率和相关性。说实话,这两个东西是做市风控的命根子。你想想看,如果连资产价格的波动幅度和资产之间的联动关系都搞不清楚,那你的风控模型基本就是在裸奔。
我个人习惯把市场风险拆成两块:单资产的风险和多资产之间的风险。单资产看波动率,多资产看相关性。今天我们先啃下这两块硬骨头。
4.1 波动率建模:GARCH系列
波动率这东西,说白了就是资产价格的"脾气"。有的资产温顺得像只猫,有的暴躁得像头狮子。而且最要命的是——波动率会聚集。什么意思?就是大涨大跌之后往往跟着更大的波动,平静期之后还是平静。
我第一次做跨境做市的时候,就吃过这个亏。当时用简单历史波动率去算保证金,结果遇到 Brexit 事件,波动率瞬间翻了三倍,保证金直接穿仓。嗯,从那以后我再也不敢用静态波动率了。
4.1.1 GARCH(1,1) 模型
GARCH 模型的核心思想很简单:今天的波动率 = 昨天的波动率 + 昨天的冲击。用数学表达就是:
σ²_t = ω + α * ε²_{t-1} + β * σ²_{t-1}
其中:
- σ²_t:今天的条件方差
- ω:长期平均方差
- ε²_{t-1}:昨天的市场冲击(残差平方)
- σ²_{t-1}:昨天的方差
- α + β < 1:保证模型平稳
我个人习惯用 Python 的 arch 包来拟合。给你看个实际代码:
import arch
import numpy as np
import pandas as pd
# 假设 returns 是日收益率序列
returns = pd.Series(...)
# 拟合 GARCH(1,1)
model = arch.arch_model(returns, vol='Garch', p=1, q=1)
result = model.fit(update_freq=5)
# 查看参数
print(result.params)
# 输出类似:omega=0.0001, alpha=0.08, beta=0.91
# 预测未来10天的波动率
forecast = result.forecast(horizon=10)
vol_forecast = np.sqrt(forecast.variance.values[-1, :])
4.1.2 已实现波动率
GARCH 用的是日频数据,但高频数据里其实藏着更多信息。已实现波动率(Realized Volatility)就是利用日内高频数据来估计波动率。
公式很简单:
RV_t = Σ r²_{t,i}
其中 r_{t,i} 是第 t 天第 i 个五分钟收益率
为什么用五分钟?太短了有微观结构噪声,太长了又丢失信息。五分钟是个业界公认的 sweet spot。
我记得有一次做 EUR/USD 的做市模型,用 GARCH 预测的波动率总是偏小,导致报价太激进。后来换成已实现波动率 + HAR-RV 模型,效果立竿见影。
# 计算已实现波动率
def compute_rv(price_series, freq='5T'):
# 重采样到5分钟
resampled = price_series.resample(freq).last()
# 计算对数收益率
log_ret = np.log(resampled / resampled.shift(1))
# 计算已实现方差
rv = (log_ret ** 2).sum()
return np.sqrt(rv * 252) # 年化
4.2 相关性建模:DCC-GARCH 与 Copula
单资产的风险好办,但做市商手里通常握着几十个资产。这些资产之间会互相影响——美元涨了,黄金可能跌;股市崩了,避险货币可能涨。所以,相关性建模比波动率建模更关键。
4.2.1 DCC-GARCH 模型
DCC-GARCH(Dynamic Conditional Correlation)是 Engle 在2002年提出的。它的核心思想是:相关性不是固定的,它会随时间变化。
模型分两步走:
- 第一步:对每个资产单独拟合 GARCH,得到标准化残差
- 第二步:用这些标准化残差来估计动态相关性
from arch import arch_model
import numpy as np
# 假设有两个资产的收益率序列
returns_1 = pd.Series(...)
returns_2 = pd.Series(...)
# 第一步:分别拟合 GARCH
model_1 = arch_model(returns_1, vol='Garch', p=1, q=1).fit(disp='off')
model_2 = arch_model(returns_2, vol='Garch', p=1, q=1).fit(disp='off')
# 提取标准化残差
std_resid_1 = model_1.resid / model_1.conditional_volatility
std_resid_2 = model_2.resid / model_2.conditional_volatility
# 第二步:估计动态相关性
# 这里用滚动窗口计算
window = 60
corr_series = []
for i in range(window, len(std_resid_1)):
corr = np.corrcoef(std_resid_1[i-window:i],
std_resid_2[i-window:i])[0,1]
corr_series.append(corr)
4.2.2 Copula 模型
DCC-GARCH 有个局限——它假设资产之间的依赖关系是线性的、对称的。但现实世界不是这样的。比如:
- 尾部依赖:市场暴跌时,资产一起跌的概率远大于一起涨的概率
- 非对称依赖:熊市里的相关性比牛市里高得多
Copula 就是来解决这个问题的。它把联合分布拆成两部分:边缘分布(每个资产自己的分布)和依赖结构(Copula 函数)。
常用的 Copula 有几种:
| Copula 类型 | 特点 | 适用场景 |
|---|---|---|
| Gaussian Copula | 对称、无尾部依赖 | 正常市场环境 |
| t-Copula | 对称、有尾部依赖 | 有极端事件的市场 |
| Clayton Copula | 下尾依赖强 | 熊市、危机时期 |
| Gumbel Copula | 上尾依赖强 | 牛市、泡沫时期 |
# 使用 Python 的 copulae 库
from copulae import ClaytonCopula
import numpy as np
# 假设有两个资产的标准化残差
u = np.column_stack([std_resid_1, std_resid_2])
# 拟合 Clayton Copula
cop = ClaytonCopula()
cop.fit(u)
# 查看参数
print(cop.params)
# theta 越大,下尾依赖越强
# 模拟联合分布
simulated = cop.random(10000)
4.3 实战中的坑与经验
讲了这么多理论,最后分享几个我在实战中踩过的坑:
- 数据频率不匹配:别用日频数据算波动率,然后用分钟频数据算相关性。频率不一致会导致模型自相矛盾。
- 参数稳定性:GARCH 参数不是一成不变的。我习惯每三个月重新拟合一次,遇到重大事件(比如央行议息)会手动触发重拟合。
- 相关性不等于因果关系:两个资产相关性高,不代表一个涨另一个就一定涨。做对冲策略时一定要注意这个。
- 计算效率:如果你有50个资产,DCC-GARCH 要算1225对相关性。我建议用 PCA 降维,先提取主要因子,再对因子做相关性建模。
好了,这一章的内容就到这里。波动率和相关性是市场风险模型的基石,下一章我们会在此基础上搭建完整的 VaR 和压力测试框架。记住:模型越复杂,不代表越准确。有时候一个简单的 GARCH(1,1) + 滚动相关性,比那些花里胡哨的深度学习模型靠谱得多。