DCC-GARCH 动态相关:让资产间相关系数随时间自行演化
相关性从来不是常数:危机来临时它齐刷刷跳升,而 252 天滚动相关要 248 天才承认这件事。Engle(2002)的 DCC-GARCH 把问题拆成两步:先用单变量 GARCH 各自过滤波动率,再在标准化残差上用一行递推 Q_t = (1−a−b)Q̄ + a·ε_{t−1}ε'_{t−1} + b·Q_{t−1} 驱动相关演化。本文两资产模拟实测:相关从 0.3 跳到 0.85 的危机段,DCC 86 天收敛到位(滚动窗口 248 天),全样本追踪 MAE 0.109 优于滚动(0.112)和 EWMA(0.143)。1% VaR 回测更能说明问题:危机段滚动窗口击穿率 2.33%(超标 2.3 倍),DCC 0.33%——差异全部集中在最需要风控的时段。QML 估计出 a=0.033、b=0.964,a+b=0.997 的高持续性正是金融相关的典型指纹。诚实边界:两步 QML 是一致但非有效估计、N 大时 Q̄ 本身要收缩、DCC 假设所有资产对共享同一组 a/b。
前面几篇讲协方差,我们先后解决了采样误差(收缩)、噪声特征值(谱裁剪)和时效性(EWMA)。但 EWMA 那篇留了一个尾巴:它用同一个 λ 同时管波动率和相关性。这在工程上很别扭——波动率是快变量(几天内就能翻倍),相关性是慢变量(regime 级别的漂移),一个衰减速率伺候两位主子,必然顾此失彼。
Engle(2002)的 DCC-GARCH(Dynamic Conditional Correlation)就是冲着这个别扭来的:把波动率和相关性拆开建模,各配各的动力学。这篇文章用一个两资产的可控实验把它从头到尾跑一遍。
先上本文实验里最重要的三组数字。模拟市场:真实相关沿慢正弦漂移,中途插入一段危机(相关从 0.3 跳升至 0.85 持续 250 天),资产各自服从 GARCH(1,1):
| 估计器 | 危机段收敛到 90% 所需天数 | 全样本追踪 MAE |
|---|---|---|
| DCC(1,1)(QML 估计) | 86 天 | 0.109 |
| EWMA λ=0.94 | 31 天 | 0.143 |
| 252 天滚动相关 | 248 天 | 0.112 |
注意这个格局:EWMA 反应最快但整体误差最大(太神经质,平静期跟着噪声乱抖);滚动窗口整体误差尚可但危机段迟钝到接近失效;DCC 两头都占——因为它的参数是从数据里估出来的,不是拍脑袋定的。
一、为什么滚动相关和 EWMA 都不够#
滚动相关的问题在 EWMA 那篇已经说透了:等权窗口要把旧数据全部换血才承认世界变了。这里不重复。
EWMA 的问题更微妙。RiskMetrics 式的 EWMA 相关是:
然后 。它有两个结构性缺陷:
- λ 是拍的,不是估的。0.94 是 1996 年 J.P. Morgan 为日频波动率定的经验值,没有任何理由相信它同时适合相关性。相关性通常比波动率持续得多——你需要更长的记忆,也就是更接近 1 的衰减率。
- 原始收益直接进递推,波动率和相关性纠缠。波动率飙升的日子, 的量级本身就大,EWMA 分不清这是”相关性上升”还是”波动率放大”。危机时观察到的”相关性跳升”有一部分其实是波动率污染。
DCC 对这两点各开一刀。
二、DCC 的两步手术#
第一步:单变量 GARCH 各自过滤波动率#
对每个资产 单独拟合 GARCH(1,1):
然后计算标准化残差 。这一步把波动率动态从数据里”洗掉”——洗完之后 的条件方差恒为 1,剩下的信息只有相关结构。这就解决了上面的缺陷 2。
第二步:在标准化残差上驱动相关演化#
定义伪相关矩阵 的递推:
其中 是标准化残差的无条件相关矩阵(长期锚点), 控制对最新观测的反应速度, 控制记忆长度。真正的相关矩阵通过归一化得到:
这个结构眼熟吗?它就是 GARCH(1,1) 的矩阵版——均值回复到 ,新息驱动靠 ,惯性靠 。和 EWMA 的关键区别有两个:
- 、 用极大似然从数据估计,不是拍的(解决缺陷 1);
- 项提供均值回复——危机过后相关性会自己走回长期水平,EWMA 没有这个锚,会永远漂。
最终协方差矩阵组装回来:,其中 。
核心代码#
两步都可以用 QML(准极大似然)分开估计,第二步的负对数似然只依赖 两个参数:
import numpy as np
from scipy.optimize import minimize
def garch_nll(params, x):
"""单变量 GARCH(1,1) 负对数似然(QML)"""
w, a, b = params
if w <= 0 or a < 0 or b < 0 or a + b >= 0.999:
return 1e10
h, nll = np.var(x), 0.0
for xi in x:
nll += 0.5 * (np.log(h) + xi * xi / h)
h = w + a * xi * xi + b * h
return nll
def dcc_nll(params, eps, Qbar):
"""DCC(1,1) 第二步负对数似然(两资产版)"""
a, b = params
if a < 0 or b < 0 or a + b >= 0.999:
return 1e10
Q, nll = Qbar.copy(), 0.0
for e in eps: # eps: 标准化残差 (T, 2)
d = 1 / np.sqrt(np.diag(Q))
R = Q * np.outer(d, d) # 归一化成相关矩阵
det = R[0,0]*R[1,1] - R[0,1]**2
quad = (R[1,1]*e[0]**2 - 2*R[0,1]*e[0]*e[1] + R[0,0]*e[1]**2) / det
nll += 0.5 * (np.log(det) + quad - e @ e)
Q = (1 - a - b) * Qbar + a * np.outer(e, e) + b * Q
return nll
# 第一步:各资产单独拟合 GARCH,取标准化残差
p1 = minimize(garch_nll, [0.05, 0.08, 0.88], args=(r[:,0],),
method='Nelder-Mead').x
h1 = garch_filter(r[:,0], p1) # 条件方差序列
eps = r / np.sqrt(np.column_stack([h1, h2]))
# 第二步:标准化残差上估计 (a, b)
Qbar = np.corrcoef(eps.T)
a_hat, b_hat = minimize(dcc_nll, [0.05, 0.90], args=(eps, Qbar),
method='Nelder-Mead').xpython在本文的模拟数据上,QML 估出 a=0.033、b=0.964,a+b=0.997。这个”接近 1 但不等于 1”的持续性是金融相关的典型指纹:相关性变化很慢(高 b),但确实在变(a+b<1,有均值回复)。
![]()
图里能清楚看到三种性格:滚动相关(红)在危机开始后慢吞吞爬坡、危机结束后又慢吞吞下坡,两头都慢半年;EWMA(绿)反应快但全程高频抖动;DCC(蓝)在危机段果断爬升、平静期贴着真实值走。

上半图验证第一步:GARCH(1,1) 拟合的条件波动率与真实条件波动率几乎重合。下半图是第二步的输出——注意 DCC 相关是在波动率已被洗掉的标准化残差上估计的,危机段波动率放大不会污染相关估计。
三、VaR 回测:差异集中在最贵的地方#
追踪误差是学术指标,风控实践看 VaR 击穿率。对 50/50 组合做 1% VaR 回测(组合波动率由各估计器的完整协方差预测给出,正态分位数 2.326):
| 时段 | 滚动 252 天 | DCC-GARCH | 目标 |
|---|---|---|---|
| 全样本 | 1.65% | 1.33% | 1% |
| 危机段(ρ=0.85) | 2.33% | 0.33% | 1% |
| 平静段 | 1.54% | 1.49% | 1% |

这张表比全样本平均值诚实得多。平静段两者半斤八两——相关性不怎么动的时候,你用什么估计器都差不多。全部差异集中在危机段:滚动窗口击穿率 2.33%,超标 2.3 倍,因为它还揣着危机前 ρ=0.3 的旧地图,系统性低估了组合波动率(相关跳升时分散化收益蒸发,组合波动率跳升,但滚动估计没跟上);DCC 的 VaR 线在右图里肉眼可见地跟着跳下去了,击穿率反而降到 0.33%(偏保守,因为危机段 GARCH 波动率也在快速上调,双重响应)。
这和 EWMA 那篇的结论一脉相承:迟钝估计器的错误是顺周期的——最需要风控收紧的时候它最松。DCC 把这个缺陷从波动率和相关性两个维度同时修掉。
四、a 和 b 各管什么#
直觉上: 是”今天的新息掺多少”, 是”昨天的判断留多少”, 是总记忆长度(越接近 1 记忆越长), 是往长期锚点 回拉的力度。

左图对比四组手工参数: 大(如 a=0.20, b=0.70)的曲线在危机开始时窜得快,但平静期抖成心电图; 小(a=0.02, b=0.95)的曲线平滑但爬坡慢。右图是各组的追踪 MAE——QML 估出来的 (0.033, 0.964) 基本落在手工网格的最优区域。这就是 DCC 相对 EWMA 的核心工程价值:你不需要替相关性猜一个衰减率,数据自己会说。
顺带一个有用的对照:如果把 DCC 递推里的 项去掉、令 、,它就退化成 EWMA 相关。所以 EWMA 是 DCC 的”无均值回复 + 参数拍死”特例——和”EWMA 是 GARCH(1,1) 的退化特例”完全平行的关系。
五、诚实边界#
两步 QML 是一致估计,但不是有效估计。 分步估计忽略了波动率参数误差向相关层的传播,标准误会偏乐观。点估计(用来做风控预测)没问题,但如果你要对 a、b 做假设检验,需要修正协方差矩阵(Engle & Sheppard 2001 给了方法)。
N 大时 本身就是灾难。 DCC 第二步只有 2 个参数,看起来对维度免疫——但 是 N×N 的无条件相关矩阵,它的估计误差就是前面几篇讲的采样误差问题。N=100 时正确做法是先对 做收缩或谱裁剪,再进 DCC 递推。DCC 解决的是时变性,不解决维度诅咒——和 EWMA 的局限一模一样,两类工具永远是串联不是替代。
所有资产对共享同一组 (a, b)。 这是 DCC 可扩展性的来源,也是它最强的假设:股票对股票、股票对债券、债券对商品的相关动力学被迫同速。现实里股债相关的 regime 切换和股票板块间的相关演化节奏可以差很远。对本文的两资产实验这不是问题,多资产大盘子里这是已知的模型误设(分块 DCC、DECO 等变体就是冲这个来的)。
日线 QML 的似然循环是 Python for 循环。 T=2500 的两资产例子秒级完成,但 N 稍大或做滚动重估时会慢。生产代码请向量化或用 Numba/Cython——递推结构决定了时间维无法向量化,但截面维可以。
六、下一步#
到这里,协方差估计的”四大件”齐了:收缩管采样误差、谱裁剪管噪声特征值、EWMA/GARCH 管波动率时变、DCC 管相关时变。实务里它们是流水线不是选择题:先波动率过滤,再收缩 ,再 DCC 演化——每一层修一种病。
下一篇会换一个角度:不修估计量,直接在优化端加约束——范数约束组合优化,用权重范数上限间接实现收缩效果。
本文实验代码基于 numpy/scipy 从零实现两步 QML,未使用 arch 等现成库——理解递推结构比调包重要。模拟市场参数:GARCH(1,1) ω=(0.05, 0.08)、α=(0.08, 0.10)、β=(0.90, 0.87),真实相关为慢正弦(0.15~0.45)叠加危机跳升段(0.85,250 天),T=2500。