做组合级别的市场风险计量,最绕不开的模型组合就是“波动率估计 + 相关结构建模 + 尾部风险度量”。单一资产的VaR好算,历史模拟、参数法都能凑合,但一旦要算组合的CVaR,问题就来了:资产之间的相关结构怎么定?极端行情下相关性往往不是常数,而是明显上升的,这正是Copula模型发挥核心作用的地方。我这次把一个完整的Matlab框架整理出来,覆盖GARCH、EWMA、EqWMA三种波动率估计方式,再通过EVT处理残差尾部,用t-Copula刻画资产之间的联合分布,最后用蒙特卡洛模拟生成情景并计算组合CVaR。整个流程做下来,基本就是一套能从数据直接出风险数字的生产管线。
这套内容适合三类人看:一是刚接触金融工程、想搞明白“VaR/CVaR背后到底怎么算”的学生;二是做量化风控、需要落实内部模型法的从业者;三是想在Matlab里迅速搭一套可复现风险计量框架的工程师。我会把每个环节的原理、参数选择、Matlab实现和避坑要点都讲明白,代码也会给到可以直接跑起来改的程度。
1. 项目概述与整体设计思路
1.1 为什么要用“波动率模型 + Copula + EVT + Monte Carlo”这套组合
先把项目要解决的问题理清楚。假设手上有5只股票的日收益率,要算整个组合未来一天的CVaR。用最简单的历史模拟法,直接把过去250天的组合收益率排序取分位数,理论上也能出数。但这里有几个致命问题:
第一,历史样本里极端事件太少。250个交易日里可能一个超过99%分位数的极端损失都没有,直接用分位数外推等于默认未来不会出现比样本更差的行情。金融时间序列的厚尾特征决定了,用正态分布或普通历史分位数都会明显低估尾部风险。
第二,组合的联合分布不等于边际分布的简单叠加。单看每只股票,收益率各自的分布可能都符合t分布或偏t分布,但股票之间到底怎么联动?极端情况下是更容易齐涨齐跌还是分化?这需要用Copula把边际分布和依赖结构拆开建模。
第三,波动率是时变的。用无条件方差算VaR,遇到波动率聚集期很容易系统性低估风险。GARCH、EWMA、EqWMA就是用来捕获条件异方差性的工具。
所以整体设计思路很清晰:第一步,对每只资产的收益率分别建立波动率模型,得到标准化残差;第二步,对标准化残差用EVT拟合尾部;第三步,把标准化残差做概率积分变换,用Copula拟合资产间的依赖结构;第四步,用蒙特卡洛模拟生成大量联合收益率情景;第五步,把情景映射到组合损益上,计算CVaR。这条链路把市场风险计量的几个核心环节全部串起来了。
1.2 技术路线与模型选型对比
项目里我选了三种波动率模型做对比,它们各自适用于不同场景,放在同一个管线里也能互为验证。
| 模型 | 模型形式 | 核心参数 | 特点 | 适用场景 |
|---|---|---|---|---|
| GARCH(1,1) | σ_t² = ω + α·ε_{t-1}² + β·σ_{t-1}² | ω、α、β | 波动率均值回归,能刻画聚类效应 | 中长期风险管理、监管资本计量 |
| EWMA | σ_t² = λ·σ_{t-1}² + (1-λ)·r_{t-1}² | λ | 指数衰减,对近期冲击敏感 | RiskMetrics风格的高频盯市风险 |
| EqWMA | σ_t² = (1/N)·Σ_{i=1到N} r_{t-i}² | 窗口长度N | 最简单直接,等权平均 | 快速粗估、数据探索阶段 |
选GARCH作为基准模型,因为它在金融领域有最成熟的理论支撑,且具备多步预测能力。EWMA本质上是GARCH的一个特例——没有均值回归项,α+β=1,所以波动率过程不收敛到长期均值,这在预测长周期时会有问题,但短期预测非常灵敏。EqWMA是三个模型里最容易实现的,滚动窗口内取收益平方的均值即可,适合做基线对照。
我建议在项目里把三者同时跑一遍,不要只依赖某一个模型。GARCH输出的条件方差可以作为后续蒙特卡洛模拟的基准,EWMA可以作为检验GARCH参数是否合理的参考,EqWMA则用来验证长期波动率水平有没有明显偏离。
1.3 项目模块划分
整个Matlab项目我按四个模块组织,逻辑上相互独立、数据上层层传递:
- 数据预处理模块:读取价格数据、计算对数收益率、对齐日期、过滤缺失值。
- 波动率与边际分布模块:估计GARCH/EWMA/EqWMA,提取条件波动率,得到标准化残差;对标准化残差拟合t分布或GPD尾部。
- 依赖结构模块:概率积分变换得到均匀边际,拟合Copula参数,得到联合分布模型。
- 风险度量模块:基于Copula抽样生成蒙特卡洛情景,映射到组合损益,计算VaR与CVaR,并做回测验证。
这样的模块化设计方便调试和扩展。比如想把GARCH换成EGARCH或者GJR-GARCH,不需要动其他模块的代码;想把Gaussian Copula换成Clayton Copula,也只需要改一个函数。Matlab里Econometrics Toolbox负责GARCH估计,Statistics and Machine Learning Toolbox负责Copula和GPD拟合,工具箱之间的函数接口天然兼容,这是用Matlab做这套模型的一个优势。
2. 核心模型原理解析
2.1 波动率估计:GARCH、EWMA与EqWMA的细节
GARCH(1,1)是金融时间序列建模的默认起点。数学上它假设条件方差σ_t² 取决于三部分:常数项ω、上一期残差平方ε_{t-1}²、上一期条件方差σ_{t-1}²。参数α代表对市场冲击的敏感度,β代表波动率自身的持续性。α+β度量了波动率冲击的持久性,这个值越接近1,说明一次冲击对波动率的影响消退得越慢。
一个经常被忽略的细节是GARCH模型隐含的无条件方差:σ² = ω / (1 - α - β)。这可以用于检验模型估计结果是否合理。如果样本期内年化波动率大约是20%,日频无条件方差大约在(0.2 / √252)² ≈ 0.000158,那么GARCH估计出来的ω/(1-α-β)应该落在同一量级,否则模型设定可能有问题。
GARCH的优势在于可以做多步预测。Matlab的forecast函数可以直接输出未来h天的条件方差预测。对10天VaR这种监管口径,需要把未来多日的波动率预测累加,GARCH的均值回归特性会在预测中发挥作用——预测期限越长,条件方差越趋向无条件方差,这符合直觉,因为长期来看波动率不可能一直高或一直低。
EWMA模型有一个很关键的性质:它可以写成递归形式,而且参数λ在RiskMetrics框架里被固定为0.94。这代表每一天的权重是前一天的0.94倍,所以信息半衰期大约是 ln(0.5)/ln(0.94) ≈ 11.2天。换句话说,大约11天后一个冲击对当前波动率的影响衰减到一半。这个特性让EWMA对近期市场变化反应很快,但代价是长期预测中波动率会持续高昂而不会均值回归。
EqWMA是最朴素的滚动窗口法:σ_t² = 算数平均过去N天的收益平方。窗口N的选择是一个偏差和方差之间的权衡——N太小,估计噪声大;N太大,对波动率突变反应迟钝。项目里我一般取60天和250天两个值,分别对应短期和长期波动率水平,用来做GARCH结果的交叉验证。需要注意EqWMA有较严重的“幽灵效应”:一个大波动在窗口期结束后突然从均值中消失,会造成波动率断崖式下跳。
2.2 EVT极值理论:把尾部估计从“没样本”变成“有模型”
标准化残差的尾部密度直接决定了CVaR的估计准确度。通常我们用t分布拟合标准化残差已经比正态分布好很多,但t分布仍然是一种参数化的全局拟合——它用同一个分布函数去拟合所有分位数。问题在于,我们真正关切的99%分位数附近,样本数据其实非常稀少,全局拟合的结果在尾部可能偏差较大。
EVT的POT(Peaks Over Threshold)方法专门解决这个问题。它只对超过某个阈值u的样本建模,这些超限量的分布可以近似为广义帕累托分布(GPD)。GPD的累积分布函数是:
F(y) = 1 - (1 + ξ·y / β)^(-1/ξ),其中y = x - u
这里ξ是形状参数,β是尺度参数。ξ>0对应厚尾分布,ξ=0退化为指数分布,ξ<0对应有界尾。金融收益的标准化残差一般是ξ>0的情形,说明尾部比指数分布还要厚。
阈值u的选择是整个EVT过程里最需要经验和判断的一步。阈值太低,进入尾部拟合的样本太多,GPD的渐近性质不成立;阈值太高,样本太少,参数估计方差太大。实操中我通常用平均超额函数图(Mean Excess Plot)辅助选择,找到一个使平均超额函数接近线性的起始阈值,一般让尾部样本占比控制在10%左右。
EVT和t分布并不互斥。合理的做法是:中间部分用t分布拟合,尾部(比如超过90%或95%分位数)用GPD拟合。这种半参数方法在VaR/CVaR估计中兼顾了稳定性和灵活性。
2.3 Copula:把边际分布和依赖结构分开建模
Copula的核心思想源自Sklar定理:任何多元联合分布都可以分解为边际分布和依赖结构两部分,而依赖结构由一个Copula函数完全刻画。在Matlab里的表达就是:C(u₁, u₂) = F(F₁⁻¹(u₁), F₂⁻¹(u₂)),其中u是对边际分布做概率积分变换后得到的均匀变量。
为什么Copula对金融组合这么重要?因为资产的边际分布建模相对容易,难的是联合分布的“联动方式”。Gaussian Copula是最常见的选择,但它有一个致命缺陷:尾部渐进独立,即在极端事件发生时,变量之间不倾向于同时取极值。这和金融危机中资产相关性急剧上升的实证观察相悖。
t-Copula在这一点上明显胜出,它引入了自由度参数ν,能够刻画尾部相依。t-Copula的上下尾相关系数相等,这意味着它允许资产在极跌行情中齐跌。从风险管理的保守性来说,用t-Copula比Gaussian Copula更安全,尤其是持有股票多头组合时。
拟合Copula参数在Matlab里非常简单,两步走就可以完成。第一步估计各资产边际分布的参数,得到u序列;第二步用copulafit估计Copula的相关矩阵和自由度。数学上这对应IFM(Inference Functions for Margins)方法,虽然不是严格极大似然,但实践效果很好且效率远高于联合极大似然。
2.4 CVaR与蒙特卡洛模拟:从联合分布到风险数字
CVaR,也叫Expected Shortfall(ES),定义是损失超过VaR条件下的期望损失。设置信水平α=97.5%,VaR是损益分布的α分位数,CVaR就是这α分位数右边尾部所有损失的平均值。相比VaR,CVaR满足次可加性,组合分散化后CVaR一定会下降,这是它作为风险度量的一个数学优势。
蒙特卡洛模拟在这里的角色是“从联合分布到损益分布的桥梁”。原因很直接:通过Copula抽样生成大量未来收益率情景,每个情景对应一个组合损益,把所有损益排序后取尾部均值就是CVaR。这个过程处理组合中线性或非线性头寸都可行,不像参数法需要解析公式。
置信水平的选择需要说明一下。传统VaR是99%,但2016年后巴塞尔协议II的修订稿把预期损失ES作为内部模型法的核心指标,标准是97.5%的ES。实际项目中我会同时输出99% VaR和97.5% CVaR,既满足常用对标需求,又满足监管口径。
3. Matlab实操:从数据到CVaR的完整实现
3.1 数据准备与预处理
项目前期,数据质量直接决定了后面的模型是否可靠。我用Matalb里的readtable读取多资产价格数据,配合timetable做日期对齐。核心步骤是:先取对数价格,再差分得到对数收益率。对数收益率的优点是它消除了价格水平的影响,且多期收益率可以直接相加。
数据清洗有几个容易被忽略的坑。如果某只股票在某一天停牌,价格不变,对应的收益率为0——这在日频数据里会很自然地拉低波动率。处理方式不是简单删除那一天的0收益,而是看同行业指数当天波动是否显著。如果市场剧烈波动而个股收益为0,应当标记为停牌并填充。更简单的做法是用前后五天的收益中位数替代,或者干脆删除该交易日同时影响所有资产的样本。
价格时间序列的对齐也值得注意。不同市场节假日安排不同,某资产在某一天可能没有交易数据,而其他资产有。用MATLAB的synchronize函数把多只资产对齐到同一个日期网格,并明确缺失值的填充策略,这是后续所有步骤的前提。
3.2 波动率模型估计的代码实现
GARCH(1,1)估计用Econometrics Toolbox直接可以完成。需要注意一点,estimate函数对初始值比较敏感,建议先观察样本方差,把它作为ω初始值的参考,α初始值设0.1、β设0.85,这样通常可以顺利收敛。代码结构如下:
returns = tick2ret(price); % 对数收益率 Mdl = garch(1,1); EstMdl = estimate(Mdl, returns, 'Display', 'off'); [condVar, ~] = infer(EstMdl, returns); condSigma = sqrt(condVar); stdResid = returns ./ condSigma;EWMA和EqWMA的实现不需要工具箱,手写一个循环就行。EWMA的递推公式对初值的依赖很低,λ=0.94时大约迭代20天就会消除初值影响。EqWMA用movmean取窗口均值即可:
lambda = 0.94; ewmaVar = zeros(size(returns)); ewmaVar(1) = var(returns(1:20)); % 用前20天方差初始化 for t = 2:length(returns) ewmaVar(t) = lambda * ewmaVar(t-1) + (1-lambda) * returns(t-1)^2; end eqwmaVar = movmean(returns.^2, 60); % 60日等权窗口我通常会画一个对比图,把GARCH条件波动率、EWMA和EqWMA的波动率曲线叠加在一起。如果三条曲线的长期均值水平差异不大,说明模型设定基本合理;差异明显时,优先检查是否存在结构性断点或者极端异常值。
3.3 标准化残差的EVT尾部拟合
标准化残差z_t = r_t / σ_t,理论上应该近似独立同分布。先用t-location-scale分布拟合标准化残差的中段,再对尾部用GPD拟合。Matlab里可以直接调用gpfit来做GPD参数估计:
[tailIdx, u] = findTailThreshold(stdResid, 0.1); % 取上尾10%样本 [xi, beta] = gpfit(stdResid(tailIdx) - u);这里的阈值u选在约90%分位数。实际操作中有一点很关键:EVT的GPD是对超限部分建模,也就是标准化残差减去阈值u之后的部分,gpfit的输入应当是正数超限。标准残差的方向需要区分上尾和下尾,做多头组合风险管理时重点关心下尾(高亏损侧)。对下尾数据集的处理方式是把残差取负后再做GPD拟合,这样超限点的含义是“亏损超过阈值的幅度”。
3.4 Copula拟合与蒙特卡洛情景生成
对每只资产的标准化残差,先拟合其边缘分布,并对每个观测做概率积分变换,得到均匀变量u。这一步的目的是把各资产的边缘分布信息完全剥离,只保留资产间的依赖信息。Matlab代码:
u = tcdf(stdResid, nu); % nu是t分布自由度,也可用ecdf做非参数变换 [rho, nuCopula] = copulafit('t', u);参数rho是Copula的相关关系矩阵,nuCopula是t-Copula的自由度。自由度参数如果估计出来很大,比如超过30,说明数据的尾部相依特征接近高斯Copula,但出于保守性考虑仍然保留t-Copula通常没有坏处。
模拟未来一天的情景时,用copularnd从拟合好的t-Copula中抽出N个均匀随机向量,再通过各边际分布的反函数转换成标准化残差,乘以预测的条件波动率,得到每只资产的模拟收益率。如果要做10天情景,需要对波动率做累加预测,最简单的方法是直接把GARCH的10天预测方差加总。
simU = copularnd('t', rho, nuCopula, N); simStdResid = tinv(simU, nu); simReturns = simStdResid .* repmat(sigmaForecast, 1, numAssets); portfReturns = simReturns * w'; % w是组合权重向量这里的N建议至少5万,在蒙特卡洛模拟里,尾部风险估计的置信区间宽度大致反比于样本量的平方根。N=5万是兼顾计算速度与精度的选择,如果机器性能不错,完全可以拉高到20万。
3.5 CVaR计算与回测验证
有了组合损益的蒙特卡洛样本,VaR和CVaR计算就很直接了:
alpha = 0.975; varLevel = quantile(portfReturns, 1 - alpha); cvarLevel = mean(portfReturns(portfReturns <= varLevel));注意代码里对“亏损”的处理习惯:如果是用收益率表示,亏损就是负收益,取左尾;如果是用金额损失表示,就是正值损失,取右尾。方向不要搞反,这是新手最容易犯的错误。
回测部分,我会用滚动窗口的方式跑一段历史区间,每天重新估计模型参数并输出次日VaR/CVaR预测,然后把实际损益与预测进行比较。最简单的检验是看实际亏损超过VaR的天数占比是否在置信水平范围内。这个占比过高说明模型低估风险,占比过低说明模型过于保守、会过度消耗资本。
4. 常见问题与排查技巧
4.1 GARCH估计不收敛或参数异常怎么办
GARCH估计用MLE时,优化器偶尔会跑到参数空间的边界,比如β接近1,这通常是数据期内存在极端异常值导致对数似然函数对β方向不敏感。我的处理顺序是:先检查是否有收益率绝对值超过5个标准差的观测,如果有,看看是否数据错误;确认数据没问题后再手动设置初始值,或者把约束条件改为α+β≤0.999以保证平稳性。如果还是不收敛,换一种分布假设,比如从正态改为t分布,往往可以改善收敛性。
4.2 概率积分变换后的u序列不像均匀分布怎么办
如果某资产的标准化残差边缘模型拟合不当,变换后的u会有明显的偏斜或堆积在0或1附近,这会直接污染Copula的估计。出现这个情况时,先检查标准化残差本身的形态:如果偏度明显,考虑用skew-t分布;如果峰度极大,检查是否EVT尾部没有衔接好。一个实用的小技巧是用ecdf做非参数变换,直接用经验累积分布函数把残差映射到[0,1],可以暂时绕开边际分布设定误差对Copula估计的影响。
4.3 Copula自由度参数很大或很小分别说明什么
自由度ν很大(超过30)说明数据实际上比较接近高斯Copula,尾部相依微弱;ν很小(低于5)说明尾部相依很强,极端情况下资产会高度联动。实际中这个估计结果可能受样本区间影响很大。比如样本期包含一次明显的市场暴跌,ν就会显著变小。我建议在报告中同时展示两个场景下的CVaR:一个用估计出的ν,一个手动设置低自由度(比如ν=3)做压力情景,这样可以看到尾部相依假设对资本计量的敏感程度。
4.4 蒙特卡洛模拟结果波动大或速度慢
蒙特卡洛的数值稳定性问题基本集中在抽样层。如果每次运行得到的结果相差很大,首先增加N;其次固定随机数种子(rng(42))保证结果可复现;再次可以用对偶变量法(antithetic variates)——每次抽样时同时生成u的镜像1-u,再求两者组合损益的平均,这样可以显著降低方差而不增加太多计算量。如果担心尾部估计太靠运气,还可以用重要性抽样聚焦在尾部区域,但这会增加实现复杂度,项目初期不建议上。
4.5 CVaR结果合理性速查表
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| CVaR < VaR | 计算时方向搞反或符号处理错误 | 检查左尾/右尾取值,确认损失方向统一 |
| CVaR明显高于单资产CVaR之和 | 相关性设定过高 | 检查Copula的rho矩阵是否异常膨胀 |
| CVaR随时间剧烈波动 | 蒙特卡洛抽样误差大 | 增大N、固定随机种子、检查波动率模型是否稳定 |
| 回测中损失超VaR次数过多 | 波动率模型低估当前风险 | 尝试EWMA或降低GARCH的β估计值 |
| 回测中损失超VaR次数过少 | 模型过于保守 | 检查是否使用了过低自由度的t-Copula |
这套流程我反复跑过很多次,最深的体会是:模型之间的配合比单个模型的选择更重要。GARCH给EVT提供近似独立的标准化残差,EVT给Copula提供准确的尾部边际,Copula给蒙特卡洛提供可靠的依赖结构——任何一个环节出问题,最终CVaR都会失真。对于刚开始上手的人,我建议不要一上来就追求模型有多复杂,先把单资产的GARCH+EVT跑通,再扩展到两资产的Copula,最后才升级到多资产组合。每一步都验证中间结果,才不会在最终输出里面对一个不知道为什么对、也不知道为什么错的风险数字。
最后分享一个小技巧:所有参数估计步骤的随机种子和阈值选择都写死在配置脚本里,这样每一次跑出来的结果都能精确复现。不光是GARCH初始值,还有蒙特卡洛的抽样种子,甚至EVT的阈值百分比。风险管理最怕的就是“结果不可回溯”,参数写死在代码里其实是在保护你自己。