ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多资产风险度量:Copula-GARCH-EVT与蒙特卡洛CVaR

多资产风险度量:Copula-GARCH-EVT与蒙特卡洛CVaR 做市场风险管理的同行应该都有同感单资产的风险度量相对容易一旦组合里放上几只股票、几类资产问题就立刻变得棘手。资产之间不是独立的尾部风险还会“传染”——市场大跌的时候相关性往往同步上升正态假设下的VaR在这种场景下基本是摆设。这个项目做的就是一套完整的多资产市场风险解决方案用Copula把资产间的依赖结构拆出来用GARCH、EWMA和EqWMA分别做波动率预测用EVT处理极端尾部最后通过蒙特卡洛模拟把组合的CVaR算出来。整套流程在Matlab里实现适合做量化风控、金融工程研究或者毕业设计的读者参考。我会把每一段链路——从数据清洗到参数估计、从模拟到回测——的原理、代码和坑都掰开讲清楚。1. 这套组合方案到底在解决什么问题1.1 单一模型的两大短板先说最直观的问题。假设手里有5只股票构成一个组合要算未来一天的CVaR。如果只给每只股票单独套一个GARCH模型能拿到各自的波动率和条件分布但组合风险不是简单把单资产的VaR加总——因为资产之间有相关性而相关性在市场压力下会变化。更麻烦的是金融资产之间普遍存在尾部依赖平时相关性看起来也就0.3、0.4一旦市场暴跌相关性可能瞬间冲到0.8以上。这种“压力状态下联动增强”的现象用简单的皮尔逊相关系数矩阵完全描述不了。第二块短板在于边缘分布。GARCH模型如果假设标准化残差服从正态分布那么尾部衰减速度大约是e的负二次方量级而真实金融收益的尾部衰减要慢得多接近幂律衰减。这意味着正态GARCH模型会系统性低估极端亏损的概率。我在实际回测里见过不少案例模型说99%日VaR是-2.3%结果组合在一个月内跌了-3.5%而且这种超限不是偶发一年里能出现好多次。这说明不是参数没调好而是模型结构本身缺了厚尾刻画。Copula的作用就是把“单变量的边缘分布”和“多变量之间的依赖结构”分开建模。边缘部分用GARCH加EVT描述每只资产自己的波动聚集和厚尾特征依赖部分用Copula描述资产之间的联动模式。这种分解有Sklar定理做数学支撑在实操上也有一个非常实际的好处你可以随时替换边缘模型或者Copula类型不用把整条链路推翻重来。1.2 完整链路与Matlab工具箱分工整套项目的核心数据流可以概括成五个环节获取并清洗多资产日收益率序列统一交易日历对每个资产分别估计GARCH、EWMA、EqWMA波动率得到条件波动率序列和标准化残差用EVT对标准化残差的尾部做修正把厚尾特征吃进边缘分布估计Copula参数得到资产间依赖结构蒙特卡洛模拟生成未来收益场景按组合权重计算损益分布输出VaR和CVaR。这套链路在Matlab里实现最大的优势是工具箱齐全基本不需要自己造轮子。Econometrics Toolbox里的garch对象可以直接estimate和inferStatistics and Machine Learning Toolbox里有copulafit、copularnd、fitdist这些现成函数蒙特卡洛部分用矩阵运算几行就能写完。我整个项目跑下来核心代码加在一起大概三百行左右如果把回测框架也算进去五百行以内能搞定。这里顺便说一下Matlab版本的问题。我主力用的是R2022b以上的版本garch对象和copulafit的接口在近几个版本里都比较稳定。如果你用的版本比较旧比如R2016b之前注意garch的Distribution参数设置方式略有不同其他差异不大。另外做滚动回测时单线程跑会非常慢建议提前开启并行池后面我会专门讲这个问题。2. 三种波动率模型的原理与Matlab实现2.1 GARCH(1,1)标准配置与参数约束GARCH模型处理的是金融收益序列最典型的两个特征波动率聚集和均值回归。GARCH(1,1)的完整形式是r_t mu epsilon_t epsilon_t sigma_t * z_t sigma_t^2 omega alpha * epsilon_{t-1}^2 beta * sigma_{t-1}^2其中z_t是独立同分布的标准化残差可以先假设为正态分布或t分布。omega对应长期平均方差水平alpha是ARCH项系数捕捉最新冲击的影响beta是GARCH项系数决定波动率的持续性。参数需要满足omega 0、alpha 0、beta 0且alpha beta 1这样才能保证条件方差为正且过程平稳。金融数据里alpha beta通常接近1但小于1比如常见的估计结果是alpha在0.05到0.15之间beta在0.80到0.92之间两者之和在0.95以上。这说明波动率冲击会持续很长时间——今天的波动率大幅上升未来几十天都会保持高位。alpha beta越接近1波动率衰减越慢对应的“波动率聚集”现象越明显。在Matlab里标准做法是用Econometrics Toolbox的garch对象Mdl garch(GARCHLags, 1, ARCHLags, 1, Distribution, t); [EstMdl, EstParamCov, LogL, info] estimate(Mdl, retSeries);这里把Distribution设成t是因为金融残差普遍厚尾t分布的自由度参数估计出来通常在4到12之间。这个自由度后面会用到——它不仅是GARCH残差分布的关键参数也是判断是否需要EVT修正的重要参考。如果估计出来的自由度大于30说明数据尾部其实接近正态EVT的边际收益会小很多。估计完参数之后用infer函数提取条件波动率sig2 infer(EstMdl, retSeries);infer返回的是每个时间点的条件方差开根号就是条件波动率序列。这里有个容易忽略的细节Matlab会自动用模型隐含的无条件方差omega/(1-alpha-beta)初始化起始点的条件方差不需要自己额外处理。但如果你要手动复现递推过程记得第一期的条件方差要用样本方差或者无条件方差来初始化不然前几步的数值会偏。如果你没有Econometrics Toolbox也可以用fmincon手动做MLE估计。极大似然函数对t分布残差来说就是每一期标准化残差的对数密度之和加上一个对条件方差的对数惩罚项。手动实现的优势是能完全控制参数约束和初始值劣势是收敛速度慢、容易陷入局部最优。我建议有工具箱就用工具箱除非你要做定制化的变体模型。2.2 EWMA0.94背后的半衰期逻辑EWMA指数加权移动平均是RiskMetrics在二十世纪九十年代推广开来的波动率模型它的核心思想非常直观给历史收益按时间递衰减权重越近的观测对当前波动率影响越大。递推形式极为简洁sigma_t^2 lambda * sigma_{t-1}^2 (1 - lambda) * r_{t-1}^2lambda的取值在RiskMetrics的原始框架里固定为0.94这是从大量资产数据里调出来的经验常数。0.94意味着权重半衰期大约是h ln(0.5) / ln(0.94) ≈ 11.2天也就是说11个交易日之前的收益平方对当前波动率的影响已经衰减到一半。这个参数对日度数据来说非常合理——太长则波动率反应迟钝太短则噪声太大。Matlab里不需要专门工具箱一个简单循环就能实现lambda 0.94; T length(retSeries); sig2_ewma zeros(T, 1); sig2_ewma(1) var(retSeries(1:60)); for t 2:T sig2_ewma(t) lambda * sig2_ewma(t-1) (1 - lambda) * retSeries(t-1)^2; end这里有几个实操细节。第一初始化不要用零建议用前60天样本方差否则前几百期EWMA会从零开始慢慢爬升形成一条明显的“热身段”。第二公式里用的是r_{t-1}^2而不是epsilon_{t-1}^2意味着模型隐含假设均值为零。日度收益的均值确实小到可以忽略但如果你处理的是周度收益或者其他低频数据先把均值减掉会更稳妥。第三EWMA没有均值回归项波动率会一直跟着最新的冲击走——冲击来了波动率猛涨冲击过去之后回落速度也完全由lambda决定比较僵硬。市场急跌之后快速恢复平静的阶段EWMA往往会高估波动率。2.3 EqWMA等权重窗口的定位EqWMAEqual Weighted Moving Average是三个模型里最朴素的一个就是取最近N天的样本方差没有任何衰减权重sigma_t^2 (1/N) * sum_{i1}^{N} r_{t-i}^2Matlab一行就能算N 20; sig2_eqwma movvar(retSeries, N, Endpoint, discard);N的选取是EqWMA唯一的自由度这个选择直接决定模型行为。取20天波动率对近期冲击反应快但序列噪声大取60天曲线平滑但反应慢半拍。实际项目里我通常把N20和N60都算出来当作GARCH预测值的上下边界参考。如果GARCH的预测波动率长期跑出这个区间说明模型参数可能出了问题或者市场结构发生了切换。EqWMA的优势在稳健性和可解释性。它的估计不依赖任何分布假设也不需要优化求解谁都能复现。它的缺陷也很明显等权重忽略了“越近越重要”这个金融直觉方差估计的波动较大。在正常市场环境下EqWMA和GARCH的预测结果差距不大但在波动率突变的环境下EqWMA的响应速度取决于你对N的选择不如EWMA和GARCH灵活。2.4 统一对比与预测口径三个模型输出的是同一件事每个时点的条件波动率。要公平地对比它们不能直接看曲线形状而要用统一的预测误差口径。我建议用一步向前预测的MSE均方误差以实际收益平方作为真实波动率的代理T0 60; % 跳过初始化区间 mse_garch mean((retSeries(T0:end).^2 - sig2_garch(T0:end)).^2); mse_ewma mean((retSeries(T0:end).^2 - sig2_ewma(T0:end)).^2); mse_eqwma mean((retSeries(T0:end).^2 - sig2_eqwma(T0:end)).^2);这里有个必须注意的坑T0之前EqWMA的输出是NaNGARCH和EWMA虽然有值但属于“热身段”直接比较会把前60天的初始化效应带进结果。所以一定要统一从同一个起始点开始比较。除了MSE还可以比较负对数似然或者AIC/BIC。不过MSE直观、解释成本低适合做日常监控AIC/BIC更严谨适合做模型选型。我个人习惯是两套都算模型之间差异很小的时候就以AIC为准差异大的时候以回测的CVaR超限率为准——毕竟风控模型最终是要看预测极端损失能力的。多步预测方面GARCH可以在估计完EstMdl之后用simulate函数做前向递推[simRet, simVariance] simulate(EstMdl, numPeriods, NumPaths, Nsim);EWMA和EqWMA的多步递推其实就是在每期用上一步的条件方差和模拟收益更新写起来更麻烦。如果你的项目只做一步向前预测——像大多数日度风控流程那样——直接用infer最后一期的条件方差作为下一期的预测基础就行。注意GARCH的下一步预测方差公式是omega alpha * epsilon_T^2 beta * sigma_T^2其中epsilon_T是最后一期残差sigma_T是最后一期条件波动率别把残差和收益搞混。3. Copula依赖结构建模与风险因子处理3.1 Sklar定理与两阶段估计Copula的核心是Sklar定理一个多变量联合分布可以分解成K个边缘分布和一个Copula函数Copula完整刻画了变量之间的依赖结构。简单说如果你知道每只资产收益各自的分布又知道它们之间“怎么联动”你就能拼出联合分布。在Matlab里做Copula建模标准路径是两阶段的IFMInference for Margins方法。第一步估计每个资产自身的边缘分布。在纯静态模型里边缘分布可以是t分布、偏t分布或者经验分布在这个项目里边缘分布由波动率过程和标准化残差的分布共同构成——GARCH给出每期不同的条件方差标准化残差则决定分布的形态。第二步把边缘信息剥离出来得到标准化残差再用概率积分变换映射到[0,1]区间的均匀分布最后输入copulafit估计Copula参数。标准化残差的计算mu_hat EstMdl.Constant; % 估计出的均值项 z_t (retSeries - mu_hat) ./ sqrt(sig2_garch);然后做概率积分变换。这里千万不要直接把z_t输入copulafit——虽然copulafit也接受原始数据并自动做半参数边缘估计但那样你就丧失了对边缘分布的控制力后续没法嵌入EVT修正。我建议手动控制变换方式u tcdf(z_t, df); % 如果残差用t分布 % 或者用经验CDF避免分布假设 u (tiedrank(z_t) - 0.5) / length(z_t);两种方式各有适用场景。经验CDF的优点是灵活不依赖分布假设缺点是样本外尾部几乎没有外推能力——你观测到的最大亏损就是最大亏损模拟时永远不会生成比历史更大尾部的事件。参数化t分布的CDF尾部行为由自由度决定配合EVT修正后外推能力更好。我实际项目里默认用t分布CDF然后把EVT修正放在后面这样整条链路的逻辑是一致的。3.2 Gaussian与t-Copula的选择依据Copula家族里最常用的是Gaussian Copula和t-Copula。Gaussian Copula完全由相关矩阵R决定没有尾部依赖——两个变量同时出现极端事件的条件概率会随着阈值提高而趋近于零。t-Copula除了相关矩阵R还多一个自由度nu参数能产生非零的尾部依赖。尾部依赖系数可以由解析公式近似lambda 2 * tcdf(-sqrt((nu1)*(1-rho)/(1rho)), nu1)当nu很大时比如超过50t-Copula退化为Gaussian Copula当nu小比如5到10尾部联动会显著增强。金融市场里资产同时暴跌的场景太常见了所以t-Copula在绝大多数实证研究里都比Gaussian Copula拟合得好。Matlab里估计t-Copula[rho_t, nu_t] copulafit(t, U, Method, ML);这里U是K列、每列在[0,1]区间的均匀分布样本。Method用ML就是极大似然估计用ApproximateML会快一些——在样本量大或维度高时建议直接用ApproximateML两者结果差异很小。nu的初始值最好给个合理起点比如10。如果拟合出来的nu非常大超过100说明数据里没有显著的尾部依赖这时直接用Gaussian Copula即可省一个自由度参数反而是好事。另外t-Copula的自由度nu和GARCH残差t分布的自由度df是两个概念不要混淆。前者描述资产间的尾部联动强度后者描述单资产残差的厚尾程度。我见过有人把GARCH里的df直接拿来当Copula的nu用这是不对的——需要分别估计。3.3 风险因子选取与数据对齐要点这套框架里的“风险因子”可以理解成驱动组合损益的基础变量。项目里通常是资产收益率本身但也可以扩展成行业因子收益率、利率变动、汇率变动等。选因子时有一条经验因子之间最好有真实的经济联动关系否则Copula估计出来的依赖结构既不稳定也不可解释。比如把同行业的几只股票放一起Copula拟合出的尾部依赖通常很强这就有实际含义如果把股票和黄金放一起依赖结构可能随着市场环境漂移需要更长的样本窗口才能稳定。数据预处理是Copula阶段最容易被低估的环节。第一个坑是时间对齐——不同资产的交易日历可能不一致比如A股和港股的节假日不同。直接按行对齐会引入大量NaN处理办法是取共同交易日交集或者先把收益率重采样到统一日期轴。第二个坑是异常值——不要随便删除极端收益。Copula和EVT本身就是在建模这些极端值删掉它们等于扔掉最关键的尾部信息。第三个坑是样本量——t-Copula在高维度下需要大量数据K10个资产、500个交易日勉强够用维度再高建议把资产分组建模或者改用Vine Copula这种分层的结构否则相关矩阵的自由参数太多估计噪声会吃掉一切。4. EVT极值理论把尾部风险做实4.1 为什么GARCH残差的正态假设不够GARCH(1,1)加正态残差是风控领域十几年前的默认配置覆盖面很广但它的核心漏洞在于正态分布的尾部。正态分布的尾概率衰减速度是e^{-x^2/2}而真实金融收益的尾部衰减接近幂律x^{-alpha}。也就是说正态模型认为跌4个标准差的事件几乎不可能发生但真实市场里这种事情隔几年就会来一次。我自己的一个直观案例某只股票指数日收益的标准化残差里出现超过4倍标准差的样本占比大约是0.5%到1%而正态分布的理论概率是0.003%量级。差了三个数量级。直接后果就是如果只用正态GARCH算99%VaR你会发现超限次数显著高于理论上的一年2到3次。这不是模型“运气不好”而是分布假设系统性偏差。解决办法有两个方向。第一把标准化残差分布直接设成t分布也就是GARCH估计时的Distribution设为t这个方案简单有效能覆盖大部分厚尾。第二用EVT把尾部建模得更精细。EVT的优势在于它只关心超过某个阈值的极端观测用广义帕累托分布GPD去拟合这部分数据理论上对尾部行为的外推更准确。实际操作中我会两者结合GARCH残差分布先用t分布打底再对t分布仍然覆盖不到的极端尾部用GPD精细建模相当于给尾部再加一层保险。4.2 GPD模型与阈值选取方法广义帕累托分布GPD的累积分布函数是F(x) 1 - (1 xi * (x - u) / beta)^(-1/xi) xi不等于0 F(x) 1 - exp(-(x - u) / beta) xi等于0其中u是阈值beta是尺度参数xi是形状参数。xi大于0对应肥尾金融数据里通常就在这个区间xi小于0对应有界尾部xi等于0退化为指数尾。Pickands-Balkema-de Haan定理告诉我们在一定条件下超过高阈值的超出量分布会收敛到GPD这就是EVT处理尾部的方法论基础。阈值u的选取是EVT建模里最主观也最关键的一步。u选得太小非极值数据混进GPD拟合产生偏差u选得太大剩下的极值样本太少估计方差巨大。实操中有两个常用方法。第一是平均超额函数图Mean Excess Plot画出不同u下超出量均值随u变化的曲线找到曲线开始近似线性下降的起点那个位置就是合适的阈值。第二是直接取90%到95%分位数作为经验起点然后检查形状参数xi是否稳定——把阈值从90%逐步调到98%看xi和beta的变化如果参数在一个区间内基本稳定说明这个阈值区间是可信的。我在项目里通常取95%分位数作为默认阈值然后做一次稳定性检查。有一个值得关注的点如果xi明显超过0.5意味着分布的方差不存在或者一阶矩偏高这种情况下GPD的估计极不稳定需要回到数据本身看看是否存在异常的结构比如微结构噪声或者数据录入错误。4.3 分段分布构造与Matlab代码EVT修正的思路是构造一个分段分布中间区域用经验分布两尾用GPD。以标准化残差z为例左尾取低于5%分位数的部分右尾取高于95%分位数的部分% 拟合左尾GPD thresh_low quantile(z, 0.05); z_lower z(z thresh_low); exceed_low thresh_low - z_lower; % 转成超出量 pd_low fitdist(exceed_low, gp); % 拟合右尾GPD thresh_high quantile(z, 0.95); z_upper z(z thresh_high); exceed_high z_upper - thresh_high; pd_high fitdist(exceed_high, gp);注意fitdist的gp拟合的是超出量分布所以在输入前要把原始数据减去阈值。得到pd_low和pd_high之后你需要拼出一个完整的边缘CDF反函数供蒙特卡洛模拟的反变换使用。拼接时最关键的问题是保证连续性和概率质量守恒。一个简单可靠的做法是中间区间用经验CDF在阈值处的值做衔接。比如左尾GPD对应的概率区间是[0, p_low]其中p_low是经验CDF在u_low处的值中间区间是[p_low, p_high]右尾GPD对应的概率区间是[p_high, 1]。这样构造出来的反函数输入一个0到1之间的均匀随机数u如果u小于p_low就走左尾GPD反函数u在中间就走经验分布插值u大于p_high就走右尾GPD反函数。由于尾部的概率密度远高于正态假设模拟时生成的极端场景频率会更贴近真实市场。做这件事常用的辅助函数示意function x inv_edgeCDF(u, gridZ, gridF, thresh_low, thresh_high, pd_low, pd_high) p_low gridF(thresh_low); p_high gridF(thresh_high); x zeros(size(u)); idx_low u p_low; idx_mid u p_low u p_high; idx_high u p_high; x(idx_low) thresh_low - icdf(pd_low, 1 - u(idx_low)/p_low); x(idx_high) thresh_high icdf(pd_high, (u(idx_high)-p_high)/(1-p_high)); if any(idx_mid) x(idx_mid) interp1(gridF, gridZ, u(idx_mid), linear); end end这里的gridZ和gridF是中间区间内的一组网格用经验CDF的值和对应的z值构成插值表。有了这个反函数蒙特卡洛阶段就可以把Copula生成的均匀随机数转成带厚尾特征的标准化残差。5. 蒙特卡洛模拟与CVaR计算5.1 四步生成未来收益场景蒙特卡洛模拟的核心目标是把前面搭好的三块组件——波动率模型、边缘分布、Copula依赖结构——合成一个完整的未来收益场景生成器。具体分四步。第一步用Copula生成带依赖结构的均匀随机数U_sim copularnd(t, rho_t, nu_t, Nsim);这里Nsim是场景数。我的建议是至少5万如果要看99%置信度的CVaR5万都不算多——尾部样本只有500个左右CVaR的估计波动会比较大。我自己项目里默认用10万跑一次几百毫秒到几秒完全可接受。第二步把均匀随机数通过边缘CDF的反函数转成标准化残差Z_sim zeros(Nsim, K); for j 1:K Z_sim(:, j) inv_edgeCDF(U_sim(:, j), gridZ{j}, gridF{j}, ...); end这一步是把Copula的依赖结构“灌”回到每个资产的边缘分布里。注意for循环在K不大时没问题但如果K大且Nsim大可以考虑对每个资产的边缘反函数向量化减少循环开销。第三步乘以预测波动率。对每个资产j用前面选定的波动率模型GARCH、EWMA或EqWMA预测下一期波动率forecast_sigma(j)然后Ret_sim Z_sim .* forecast_sigma;如果需要模拟未来多天就要做步进式的波动率更新。GARCH可以用simulate函数一次性生成也可以手动递推每条路径的条件方差EWMA和EqWMA同样逐期更新。但要注意多步模拟里每一步的收益都依赖于上一步的波动率路径计算量会线性增长我建议先把单步预测做好再考虑扩展多步。第四步按组合权重计算组合损益。假设权重向量w是K行1列PortLoss -Ret_sim * w; % 组合亏损正数表示亏损这样得到的PortLoss是Nsim行1列的向量就是组合在未来一个周期内的损益分布模拟样本。注意负号的含义我习惯把亏损定义成正数这样VaR和CVaR的计算方向比较直观也方便跟风控系统的损失口径对接。5.2 CVaR计算口径与稳定性处理CVaR条件风险价值的定义是在给定置信水平alpha下超过VaR的损失的条件期望CVaR_alpha E[L | L VaR_alpha]Matlab里最直接的计算方式是对模拟损失排序后取尾部均值alpha 0.99; VaR quantile(PortLoss, alpha); CVaR mean(PortLoss(PortLoss VaR));但这样算有一个隐患quantile返回的VaR本身是模拟样本的某个排序位置当模拟次数不够多时VaR的估计噪声会传导到CVaR。更稳的做法是直接取排序后尾部固定比例的样本均值sortedLoss sort(PortLoss, ascend); n_tail max(1, round(Nsim * (1 - alpha))); tailLoss sortedLoss(end-n_tail1:end); CVaR mean(tailLoss);这样得到的CVaR是损失分布右尾那1%样本的均值不会因为VaR单点波动而跳来跳去。还有一个细节如果你对比GARCH、EWMA、EqWMA三个模型算出的CVaR会发现它们差异主要在尾部——EWMA在波动率刚突变后给出的CVaR会明显偏高GARCH因为有均值回归项极端冲击后的CVaR会缓慢回落EqWMA的表现取决于窗口N。把三个值放一起看能对模型不确定性有个直观感受。顺便提一个常用的风险归因视角。Copula模拟的优势在于你可以固定边缘分布不变只切换Copula参数看CVaR的变化有多大或者固定Copula不变只切换GARCH和EWMA看波动率模型的影响有多大。这种“控制变量”式的分析在实际汇报中非常有用能让领导或者客户直观理解风险的构成。5.3 回测验证与模型比较算出CVaR之后必须做回测否则整个模型就是自嗨。回测的标准做法是滚动窗口每天用截至当天过去500天的数据估计所有参数预测下一期波动率并计算CVaR然后与第二天实际收益对比统计实际亏损超过CVaR的次数占比。Matlab里滚动回测的基本骨架for t T0:T-1 window retSeries(t-499:t); % 估计GARCH、EWMA、EqWMA % 估计EVT、Copula % 蒙特卡洛模拟计算CVaR_t % 记录实际亏损是否超过CVaR_t end violationRatio mean(actualLoss CVaR_series);这个循环写起来不难真正麻烦的是性能。每步都要重新估计GARCH和Copula500个交易日就是500轮拟合单线程可能要跑几小时。我的建议是一是提前开启parpool并行池把每个时间窗口的估计和模拟丢给worker并行处理二是GARCH参数可以每5天或每10天重估一次中间几天只做infer和模拟Copula参数同理三是如果数据量支持优先用ApproximateML的Copula估计能省不少时间。回测结果怎么看在99%置信度下理论上超限率应该在1%附近。如果超限率明显高于1%比如超过2%说明模型低估了尾部风险优先检查EVT的阈值选择其次检查Copula是t还是Gaussian。如果超限率明显低于1%比如只有0.3%说明模型偏保守CVaR可能虚高这在某些监管场景里会浪费资本。回测还可以用Kupiec的失败率检验来做统计推断Matlab里自己写几行就能算p值但日常监控中我更依赖超限率加上超限天数的分布形态光看一个比例容易忽略尾部损失集中的问题。6. 踩坑记录与实战建议6.1 常见报错与处理速查这一节是我从实操中整理出来的问题表很多坑都是第一次跑完整链路时必然遇到的。问题现象可能原因排查思路copulafit报错“Data must be in [0,1]”概率积分变换出的U里有0或1用(tiedrank(u)-0.5)/n代替经验CDF或对极端值施加微小扰动GARCH估计不收敛初始值不合适或数据里有NaN用estimate的Display,off试多组起始值先清洗NaN和离群值GPD形状参数xi为负阈值选得过低GPD在有界数据上拟合不佳提高阈值到97%分位数再试检查数据是否有明确的上下界t-Copula自由度nu估计偏大大于100数据没有明显尾部依赖或样本太少改用Gaussian Copula对比用似然比或BIC判断蒙特卡洛CVaR在不同种子下波动大场景数太少把Nsim从1万提高到5万或10万重跑观察稳定性多资产日期对不齐矩阵里出现大量NaN交易日历不一致用intersect取共同交易日或统一插值到同一日历GARCH预测波动率长期低于EqWMA模型均值回归项太强或参数估计有问题检查alphabeta是否接近0.99必要时放宽参数约束重新估计回测超限率异常高EVT阈值太低或边缘分布没吃进厚尾检查GPD形状参数调整阈值做稳定性分析6.2 几条实操体会第一不要把“模型复杂”等同于“模型更好”。GARCH加EVT加t-Copula这套组合确实在真实数据上比单一GARCH正态模型更可靠但计算代价和参数不确定性也更大。如果组合资产之间的相关性本来就低Gaussian Copula和t-Copula给出的CVaR差异通常很小这时候硬上t-Copula纯粹是增加复杂度换不来实质改进。我一般会先跑一版Gaussian Copula做基线再看t-Copula是否显著改善似然值没有显著改善就维持简单版本。第二EVT阈值的选择对结果的影响非常大。我试过同一组数据阈值从90%提到95%CVaR的差异可能有10%到20%。所以一定不要一次性拍脑袋定阈值要做一个阈值敏感性分析从85%到98%每隔1个百分点跑一遍画出xi和CVaR随阈值变化的曲线找到稳定区间再取区间内的代表值。这个分析做一次大概几分钟但能让你对模型输出的信心完全不同。第三做多个波动率模型对比时务必统一预处理口径。这个坑我踩得最深。GARCH模型用的是原始收益序列EWMA用的是去均值后的收益结果对比CVaR时发现系统性偏差——折腾了很久才发现不是模型差异而是均值处理没对齐。建议把数据清洗、去均值、标准化这些步骤封装成一个统一的预处理函数所有模型都走同一个入口后面省下的调试时间远超写这个函数的时间。第四滚动回测一定要看参数稳定性。GARCH和Copula的参数在每个窗口重新估计本身带估计误差。你把每个窗口的alpha、beta、nu画出来看如果某个窗口出现明显突变多半不是市场结构变了而是那个窗口里混进了异常值。所以每个窗口开跑前做一个快速的数据质量检查比如校验日收益是否连续、有没有重复日期、有没有极端跳空这类检查能帮你避免大量无效调试。第五Copula模拟最有价值的用法不只是算一个CVaR数字而是做情景分析。你可以用模拟出的场景提取“所有资产同时下跌超过两个标准差”的子集看看这些场景在组合层面的损失分布是什么样的。这种分析对压力测试非常有帮助因为你不需要预设什么历史重演Copula加蒙特卡洛本身就是一种“看到所有可能尾部”的工具。最后再分享一个我做这个项目时养成的习惯每个阶段都单独保存中间结果。GARCH的条件方差、标准化残差、Copula参数、EVT拟合对象、蒙特卡洛模拟样本全部写成.mat文件存档。这样当模型某天出现奇怪的结果时可以直接回溯到出问题的环节而不是从零开始重新跑一遍。市场风险管理本质上是管理不确定性——这里的不确定性不仅来自市场也来自模型本身。一个能定位问题的流程比你用的具体模型是什么更重要。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进