简介:这份资源面向金融风险管理方向的学生与量化研究者,围绕VaR(在险价值)的五种计算思路展开,重点落在DCC-GARCH与Copula-GARCH两类建模方法上。内容涉及Clayton Copula结合t分布边际拟合收益率、DCC-GARCH刻画时变波动率与动态相关性,并据此估算投资组合的最大潜在损失,同时对照历史模拟法、参数法、蒙特卡洛模拟与风险因子模型等经典路径,帮助读者理解不同方法在尾部依赖与极端事件建模上的差异。压缩包共1个文件,为R语言源码脚本,体积约2KB,可直接在R环境中运行、修改与复现,适合作为课程作业或自学练手的参考实现。目前已有500人学习下载。对于希望把Copula与GARCH族模型落到代码层面、并对比多种VaR算法效果的读者,这份脚本能提供可运行的起点与清晰的建模脉络。
1. 五种 VaR 算法怎么选:从 DCC-GARCH 到 copula-GARCH 的落地判断
组合里有三只以上资产、持仓跨越牛熊、又不想把风险度量做成拍脑袋的拍板,这时候单一的历史模拟 VaR 就开始露怯。五种计算 VaR 的算法——历史模拟、方差-协方差、DCC-GARCH、copula-GARCH、以及边际 VaR 分解——本质上是在回答同一个问题:给定置信水平和持有期,明天最多亏多少。但它们的假设、数据要求和计算成本差得很远。DCC-GARCH 捕捉的是动态相关结构,copula-GARCH 进一步把边缘分布和相依结构拆开建模,marginal VaR 则回答“哪个头寸在贡献风险”。这套组合适合管理多资产组合、需要动态风险预算的从业者,不适合只有单资产、数据不足两年的场景。选错算法,回测里 VaR 突破次数会直接翻车。
2. 五种算法的数学骨架与选型理由
2.1 历史模拟与方差-协方差:基线为什么不能跳过
历史模拟法不假设分布,直接把过去 N 天的收益率排序取分位数。它的优点是直观、能捕捉厚尾,缺点是窗口长度决定一切:窗口太短,极端事件被平滑;窗口太长,旧 regime 污染当前风险。常见做法是取 500 个交易日,配合指数衰减权重。方差-协方差法假设收益率正态,VaR = μ + σ·Φ⁻¹(α)·√h。它快,但正态假设在 99% 置信水平下会系统性低估尾部风险。我一般把这两种作为基线,任何新算法都必须在这两个基线上证明自己有增量价值,否则就是过度工程。
2.2 DCC-GARCH:动态相关矩阵怎么估
DCC-GARCH 分两步:先对每个资产单独拟合 GARCH(1,1) 得到条件方差,再用标准化残差估计动态相关矩阵 Q_t。核心递推是 Q_t = (1-a-b)·Q̄ + a·ε_{t-1}ε_{t-1}' + b·Q_{t-1},其中 a 是冲击系数,b 是持续系数,a+b 必须小于 1 才平稳。典型参数 a≈0.05、b≈0.93,意味着相关性冲击衰减慢,适合日频组合。估完 Q_t 后,组合条件方差 w'D_t R_t D_t w,再开方乘分位数。这一步的坑在于:GARCH 边缘拟合不收敛时,DCC 第二步会放大误差。
2.3 copula-GARCH:把边缘和相依结构拆开
copula-GARCH 的思路是:每个资产的边缘分布用 GARCH 拟合,资产间的相依结构用 copula 函数描述。常用椭圆 copula(高斯、t)和 Archimedean copula(Clayton、Gumbel)。t-copula 能同时捕捉尾部相依和对称性,是金融组合里最稳的默认选择。Clayton 适合下尾相依强的组合,Gumbel 适合上尾。参数估计用两步法:先估边缘,再最大化 copula 似然。copula 的坑在于维度:超过 10 个资产,椭圆 copula 的相关矩阵参数就爆炸,这时候要考虑 vine copula 或因子 copula。
2.4 marginal VaR:风险贡献怎么拆
marginal VaR 不是独立算法,而是对组合 VaR 的分解。定义 MVaR_i = ∂VaR_p / ∂w_i,近似等于组合 VaR 乘以资产 i 的 beta 贡献。实操里用 component VaR 更稳:CVaR_i = w_i · MVaR_i,所有 CVaR_i 之和等于组合 VaR。这一步的价值在于风险预算:如果某资产 CVaR 占比远超其权重,说明它在偷偷放大组合风险。常见做法是每季度做一次边际分解,动态调仓。
2.5 五种算法的对比与选型表
| 算法 | 数据要求 | 计算成本 | 尾部捕捉 | 适用场景 |
|---|---|---|---|---|
| 历史模拟 | 500+ 日 | 低 | 中 | 单资产、快速基线 |
| 方差-协方差 | 250+ 日 | 极低 | 差 | 正态近似、日内 |
| DCC-GARCH | 750+ 日 | 中 | 中 | 多资产动态相关 |
| copula-GARCH | 1000+ 日 | 高 | 强 | 尾部相依、压力期 |
| marginal VaR | 依赖主模型 | 低 | 依赖主模型 | 风险预算、归因 |
选型逻辑:先跑历史模拟和方差-协方差做基线,如果回测突破率在 95% 置信下超过 7%,再上 DCC-GARCH;如果组合在危机期相关性飙升明显,上 copula-GARCH;最后用 marginal VaR 做归因。不要一上来就 copula,数据不够会估出玄学参数。
3. 用 Python 跑通 DCC-GARCH 与 copula-GARCH 的最小流程
3.1 环境与数据准备
pip install arch numpy pandas scipy statsmodels copulas数据用三只资产的日收益率,至少 750 个交易日。缺失值用前向填充,收益率用对数差分。
import numpy as np import pandas as pd from arch import arch_model # 假设 df 是 DataFrame,列是资产名,值是收盘价 returns = np.log(df / df.shift(1)).dropna() returns = returns.replace([np.inf, -np.inf], np.nan).dropna() print(returns.shape) # 应为 (750+, 3)逻辑说明:对数收益率比简单收益率更接近正态,且可加。参数说明:dropna 会损失首行,750 是 DCC 的最低要求,低于这个数 Q_t 递推不稳定。
3.2 拟合 GARCH 边缘并提取标准化残差
from arch import arch_model std_resid = pd.DataFrame(index=returns.index) cond_vol = pd.DataFrame(index=returns.index) for col in returns.columns: am = arch_model(returns[col] * 100, vol='Garch', p=1, q=1, dist='t') res = am.fit(disp='off') std_resid[col] = res.resid / res.conditional_volatility cond_vol[col] = res.conditional_volatility / 100 # 还原尺度 print(col, res.params['alpha[1]'], res.params['beta[1]'])逻辑说明:乘 100 是为了数值稳定,arch 包对小数收益率容易不收敛。dist='t' 捕捉厚尾。参数说明:alpha[1] 是冲击系数,beta[1] 是持续系数,两者之和接近 1 说明波动持续性强。如果 alpha+beta > 0.99,考虑 IGARCH 或降低频率。
3.3 DCC 动态相关矩阵递推
def dcc_estimate(std_resid, a=0.05, b=0.93): T, N = std_resid.shape Q_bar = np.cov(std_resid.T) Q = Q_bar.copy() R_list = [] for t in range(T): if t > 0: eps = std_resid.iloc[t-1].values.reshape(-1, 1) Q = (1 - a - b) * Q_bar + a * (eps @ eps.T) + b * Q D_inv = np.diag(1 / np.sqrt(np.diag(Q))) R = D_inv @ Q @ D_inv R_list.append(R) return R_list R_list = dcc_estimate(std_resid) print(R_list[-1]) # 最新相关矩阵逻辑说明:Q_bar 是无条件相关矩阵,递推里 a 和 b 是 DCC 参数。参数说明:a=0.05、b=0.93 是日频常用值,a+b=0.98 保证平稳。如果回测发现相关矩阵变化太慢,把 a 调到 0.08;如果太跳,把 b 调到 0.95。注意 R 必须做对角归一化,否则对角线不是 1。
3.4 copula-GARCH 的 t-copula 拟合
from scipy.stats import t from copulas.multivariate import GaussianMultivariate # 用标准化残差拟合 t-copula u = std_resid.apply(lambda x: t.cdf(x, df=5)) # 伪观测 copula = GaussianMultivariate() copula.fit(u) samples = copula.sample(10000) print(samples.head())逻辑说明:先把标准化残差转成均匀分布(伪观测),再拟合 copula。参数说明:t.cdf 的 df=5 是自由度,需要和 GARCH 的 t 分布自由度一致。GaussianMultivariate 是高斯 copula,换成 t-copula 需要指定 df。采样 10000 次用于蒙特卡洛 VaR。注意伪观测不能有 0 或 1,否则逆函数爆炸。
3.5 蒙特卡洛算组合 VaR 与 marginal VaR
weights = np.array([0.4, 0.35, 0.25]) n_sim = 10000 # 从 copula 采样,再逆变换回收益率 sim_u = copula.sample(n_sim).values sim_returns = np.zeros_like(sim_u) for i, col in enumerate(returns.columns): sim_returns[:, i] = t.ppf(sim_u[:, i], df=5) * cond_vol[col].iloc[-1] port_returns = sim_returns @ weights VaR_95 = -np.percentile(port_returns, 5) print('组合 VaR 95%:', VaR_95) # marginal VaR eps = 1e-4 mvar = [] for i in range(len(weights)): w_up = weights.copy(); w_up[i] += eps w_dn = weights.copy(); w_dn[i] -= eps var_up = -np.percentile(sim_returns @ w_up, 5) var_dn = -np.percentile(sim_returns @ w_dn, 5) mvar.append((var_up - var_dn) / (2 * eps)) print('marginal VaR:', mvar)逻辑说明:copula 采样得到相依结构,逆变换回收益率,再乘权重算组合损失。marginal VaR 用中心差分近似偏导。参数说明:n_sim=10000 是精度和速度的平衡,低于 5000 分位数抖动大。eps=1e-4 是差分步长,太大会非线性,太小会数值噪声。注意 cond_vol 要用最新一期的条件波动,不能用历史平均。
4. 避坑与排查:VaR 回测突破率超标的五个血泪经验
4.1 现象:95% VaR 回测突破率 12%
原因:GARCH 边缘拟合用了正态分布,忽略了厚尾。解决:把 dist 改成 't',并检查自由度参数是否小于 10。如果自由度大于 30,说明厚尾不明显,但金融数据里很少见。
4.2 现象:DCC 相关矩阵对角线不是 1
原因:Q 递推后没有做对角归一化。解决:每次递推后执行 D_inv @ Q @ D_inv,D_inv 是对角线 1/√Q_ii。这个步骤漏掉,R 就不是相关矩阵,组合方差会算错。
4.3 现象:copula 采样出现极端值
原因:伪观测里有 0 或 1,t.ppf 返回 inf。解决:对 u 做截断,u = np.clip(u, 1e-6, 1-1e-6)。另外检查 GARCH 残差是否标准化,没标准化会导致伪观测分布偏移。
4.4 现象:marginal VaR 之和远大于组合 VaR
原因:用了欧拉分解但权重没归一化,或者差分步长太大。解决:确认权重和为 1,eps 取 1e-5 到 1e-4。如果还不对,改用 component VaR 公式 CVaR_i = w_i · MVaR_i,并检查是否满足 ΣCVaR_i = VaR_p。
4.5 现象:回测通过但实盘亏损超 VaR
原因:VaR 是持有期 1 天的度量,实盘持仓超过 1 天,或者流动性差导致无法及时平仓。解决:把持有期调到 5 天或 10 天,用 √h 缩放,或者直接用重叠窗口重估。另外检查数据是否复权,未复权的价格在分红除权日会产生假跳空。
5. 进阶技巧:用滚动窗口和压力测试验证五种算法的边界
5.1 滚动窗口回测的代码骨架
window = 500 breaches = {m: 0 for m in ['hs', 'var_cov', 'dcc', 'copula']} for t in range(window, len(returns)): train = returns.iloc[t-window:t] test = returns.iloc[t] # 历史模拟 hs_var = -np.percentile(train @ weights, 5) if (test @ weights) < -hs_var: breaches['hs'] += 1 # 其他算法类似,略 print(breaches)逻辑说明:滚动窗口模拟实盘每天重估。参数说明:window=500 是历史模拟的常用窗口,DCC 和 copula 需要更长,可以设 750。breaches 统计突破次数,95% 置信下 1000 天允许 50 次左右,超过 70 次说明模型低估风险。
5.2 压力测试:把相关矩阵调到危机水平
DCC 和 copula 在危机期会低估相关性,因为递推里 b 太大导致调整慢。手动压力测试:把 R 的非对角元素统一调到 0.8,再算组合 VaR。如果压力 VaR 是正常 VaR 的 2 倍以上,说明组合分散化在危机期失效,需要降低杠杆或加对冲。
5.3 五种算法的验证清单
| 验证项 | 历史模拟 | 方差-协方差 | DCC-GARCH | copula-GARCH | marginal VaR |
|---|---|---|---|---|---|
| 回测突破率 | 必查 | 必查 | 必查 | 必查 | 不适用 |
| 参数平稳性 | 不适用 | 不适用 | a+b<1 | df>2 | 不适用 |
| 尾部拟合 | 分位数 | 差 | 中 | 强 | 依赖主模型 |
| 计算耗时 | 低 | 极低 | 中 | 高 | 低 |
我一般先跑历史模拟和方差-协方差,如果突破率超标,再上 DCC;如果 DCC 在危机期还是低估,再上 copula。marginal VaR 每季度做一次,用来调仓位。这套流程跑下来,最大的后悔药是数据不够长:750 天是底线,1000 天以上才稳。希望帮到你。
本文还有配套的精品资源,点击获取