ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Matlab Copula函数实战:从参数拟合到蒙特卡洛模拟

Matlab Copula函数实战:从参数拟合到蒙特卡洛模拟 Matlab里做多元变量分析很多人第一反应就是算相关系数矩阵但真到做风险度量、可靠性分析或者金融资产组合模拟的时候光有相关系数是远远不够的。举个最常见的例子股票和债券在平时可能表现得很独立一旦市场暴跌相关性会突然飙升这种“尾部联动”用传统的Pearson相关系数根本抓不住。 Copula函数就是专门解决这类问题的工具而Matlab里恰好有一套非常成熟的内置函数从参数估计到蒙特卡洛模拟都能直接调用。这篇文章我会从理论直觉讲起再带你把Matlab实现Copula的完整流程走一遍包括数据准备、参数估计、模型选型、模拟预测和常见报错排查代码直接能跑原理也尽量讲透。这篇指南适合对概率统计有基本了解、但没接触过Copula的读者也适合已经查过一些资料、但被各种公式劝退的人。我尽量用“人话”把Sklar定理、尾部相关性这些概念讲清楚再给出一套能直接在金融数据或工程数据上复用的Matlab代码流程你照着抄就能用。1. Copula到底解决什么问题先建立直觉再说公式1.1 为什么不能只算相关系数先看一个实际场景。你要分析两只股票收益率的联动关系Excel里跑一个CORREL得出0.3然后呢这个0.3能告诉你两只股票同时暴跌的概率吗不能。因为它只度量了线性相关而且对分布形态很敏感——如果数据里出现极端值相关系数会被拉得不成样子。更麻烦的是金融数据普遍存在“非线性、厚尾、非对称”的特征。比如两只股票在正常行情下可能几乎不相关但市场出现极端下跌时它们会同时跳水这种“平时各走各的、危机时抱团”的结构相关系数完全无法刻画。Copula的思路是把变量的边缘分布每个变量自己的分布形态和变量之间的相依结构它们如何联动拆开来看。这个拆分的理论依据就是Sklar定理对于任意联合分布函数F(x1, x2)都存在一个Copula函数C使得 F(x1, x2) C(F1(x1), F2(x2))。反过来如果F1、F2是边缘分布函数C是某个Copula那么C(F1(x1), F2(x2))一定是一个有效的联合分布。用大白话说Copula就是连接边缘分布和联合分布的那座桥它只管“相依结构”不管“各自长什么样”。这正是它的强大之处——你可以先单独拟合每只股票的收益率分布比如用t分布再单独决定“它们怎么联动”用哪个Copula互不干扰。1.2 常用Copula族和选型直觉Matlab内置了几种主流Copula分别是Gaussian高斯、t学生t、Clayton、Frank、Gumbel。它们的区别主要体现在两个维度对称性和尾部相关性。Copula类型对称性下尾相关上尾相关典型适用场景Gaussian对称无趋近0无弱尾相关、近似正态的多元数据t对称有且上下相等有金融资产收益存在对称厚尾联动Clayton非对称强无下尾联动明显如系统性风险爆发Gumbel非对称无强上尾联动明显如牛市同涨、可靠性系统Frank对称无无整体中等相关但尾部不突出选型别死记硬背。我的经验是先画散点图看数据在极端区域的聚集情况。如果左下角两个变量同时取小值比右下角明显更密优先试Clayton如果右上角更密试Gumbel如果两端都密而且对称试t。如果看不出来就把几种都拟合一遍用AIC/BIC选后面会给代码。有个细节要注意Matlab里Gumbel写的是单参数Gumbel Copula它只能刻画上尾相关做不了下尾。Clayton正好相反只能刻画下尾。你要是觉得数据两头都有尾巴直接上t Copula基本不会出大错。1.3 Matlab里现成的工具不只是copulafitMatlab统计学工具箱里跟Copula相关的函数其实有十来个但大多数人只知道copulafit。这里先把全家桶列出来做到心里有数。copulafit拟合Copula参数支持Gaussian、t、Clayton、Frank、Gumbelcopularnd从指定Copula生成随机数做蒙特卡洛模拟就靠它copulapdf/copulacdf计算Copula的概率密度和累积分布函数copulastat计算Copula对应的Kendall秩相关系数或尾部相关系数copulafit的Method选项ML精确极大似然和ApproximateML两步近似更快ecdf经验累积分布函数常用于把原始数据变换成均匀分布按我个人的习惯用copulafit做参数估计是主路但有的时候内置函数不够用——比如你要用混合Copula、带变结构的Copula或者自定义一个Copula那就得自己写似然函数用fmincon或fminsearch优化。这部分后面会展开讲别急。2. 动手前的准备先造一份“已知答案”的数据2.1 确认工具箱和版本做Copula分析核心依赖是Statistics and Machine Learning Toolbox。你可以在Matlab里直接跑一行命令检查ver(stats) exist(copulafit, file)如果第一个命令报错或输出为空说明没装统计工具箱第二个命令如果返回0也是同样的问题。我的建议是R2018b之后的版本都行太老的版本对ApproximateML的支持可能有差异。要是公司电脑不方便装新版先确认有没有统计工具箱没有的话只能自己写实现文章后面的手动MLE部分就是干这个用的。另外建议把随机数种子固定下来这样你的结果可以被复现也方便调试rng(42)2.2 从Copula生成模拟数据先有真相再验证代码我个人非常推荐这个流程先从一个已知的Copula和已知参数生成模拟数据然后在模拟数据上跑你的估计流程看看能不能还原出真实参数。这样做一遍你对代码是否正确心里就有底了。下面这段代码生成两个变量它们都服从t分布并且用t Copula连接自由度nu5相关系数rho0.7rng(42) N 2000; rho 0.7; nu 5; % 第一步从t Copula生成均匀分布的相依结构 U copularnd(t, [1 rho; rho 1], nu, N); % 第二步用边缘分布的逆CDF变成实际变量这里两个边缘都假设为t分布 T1 tinv(U(:,1), 4); % 边缘1自由度4的t分布 T2 tinv(U(:,2), 6); % 边缘2自由度6的t分布 % 画图感受一下 scatter(T1, T2, 10, filled); xlabel(变量1); ylabel(变量2); title(t Copula t边缘 生成的模拟数据);你看整个流程分两步先生成均匀分布U再用逆CDF把U变成目标分布。这也是Copula的核心思路——相依结构在均匀分布层面完成然后套上各自的边缘分布。这里要特别强调一点copularnd(t, ..., nu, N)里的nu是Copula的自由度它和边缘分布的自由度完全无关。上面例子里Copula的自由度是5两个边缘分布的自由度分别是4和6互不影响。很多人一开始会把它们搞混导致后面参数估计的结果看起来怪怪的。2.3 数据预处理的坑先让数据变成“干净的U”如果用真实数据第一步不是拟合Copula而是清洗数据。以金融数据为例我踩过不少坑这里挑重点说。首先要用收益率而不是价格。价格序列一般是非平稳的直接拿来算相关会得到严重虚高的结果。最基础的处理是log(price_t / price_{t-1})也就是对数收益率。其次如果有明显的波动率聚集现象ARCH效应最好先用一个GARCH(1,1)模型把波动率过滤掉用标准化残差来做Copula分析。否则边缘分布里会混入时变方差Copula拟合出的参数会失真。这一步很多教材不写但实际做风险模型时几乎绕不开。最后也是最关键的一步把边缘分布变成均匀分布U。Copula函数的输入严格来说必须是[0,1]区间上的均匀分布变量。怎么得到它用概率积分变换先估计每个变量的边缘分布CDF然后把原始数据代入CDF得到的就是U。Matlab里最简单的做法是经验分布U1 ecdf(returns1); % 注意ecdf返回的是结构体 U1 U1(:,2); % 第二列是经验CDF值但直接用经验分布有个问题它会把尾部数据压得很平而Copula分析恰恰最关注尾部这会导致尾部参数的估计偏差。如果你事先知道数据大致服从什么分布比如t分布我更推荐先用fitdist拟合参数再用cdf做变换pd1 fitdist(returns1, tLocationScale); pd2 fitdist(returns2, tLocationScale); U1 cdf(pd1, returns1); U2 cdf(pd2, returns2); U [U1, U2];这里用tLocationScale带位置和尺度参数的t分布比用t分布更稳因为金融数据的均值不一定为0标准差也不一定是1。实际项目里我几乎都这么处理。3. 核心实现拟合、选型、模拟一条龙3.1 最省事的路径copulafit一把梭数据变成U之后拟合Copula参数就是一行代码的事。以t Copula为例[rho_t, nu_t] copulafit(t, U, Method, ML);输出rho_t是一个2x2的相关矩阵nu_t是估计出的自由度。自由度越大尾部相关性越弱趋向于Gaussian Copula自由度越小尾部联动越强。如果需要对比不同Copula族可以写个循环统一用负对数似然作为比较指标families {Gaussian, t, Clayton, Frank, Gumbel}; results table(); for i 1:length(families) fam families{i}; if strcmp(fam, t) [~, nu_fit] copulafit(t, U, Method, ML); % 拟合后算负对数似然 [~, negloglik] copulafit(t, U, Method, ML); % 这里用占位具体见下文 else [~, negloglik] copulafit(fam, U); end results.Family(i) {fam}; results.NegLogLik(i) negloglik; end disp(results)别直接照抄上面这段copulafit返回的是参数不一定直接返回负对数似然。更可靠的写法是拟合完参数后再用copulapdf自己算对数似然。我提供一个封装好的函数复制就能用function negloglik calc_negloglik(family, U, params) % 计算指定Copula的负对数似然 c copulapdf(family, U, params{:}); c max(c, 1e-12); % 防止log(0) negloglik -sum(log(c)); end然后分别拟合、分别调用% Gaussian rho_gau copulafit(Gaussian, U); nll_gau calc_negloglik(Gaussian, U, {rho_gau}); % t [rho_t, nu_t] copulafit(t, U, Method, ML); nll_t calc_negloglik(t, U, {rho_t, nu_t}); % Clayton alpha_clay copulafit(Clayton, U); nll_clay calc_negloglik(Clayton, U, {alpha_clay}); % Frank alpha_frank copulafit(Frank, U); nll_frank calc_negloglik(Frank, U, {alpha_frank}); % Gumbel alpha_gumbel copulafit(Gumbel, U); nll_gumbel calc_negloglik(Gumbel, U, {alpha_gumbel});这样就能在同一个尺度下比较不同Copula族的拟合优度了。代码思路很简单但能解决很多人“拟合完了不知道怎么比较”的困惑。注意copulafit对t Copula的自由度nu是有上下限约束的默认搜索范围大概是2到50。如果你觉得真实自由度在1到2之间尾部极厚需要手动指定Tail相关选项。看官方文档doc copulafit里面写得很清楚。3.2 手动写极大似然估计真正掌握原理的必经之路有的人可能觉得Matlab既然有copulafit为什么还要自己手写答案很简单内置函数只支持那五种Copula你要做混合Copula、变结构Copula、或者带协变量的Copula就必须自己写似然函数。而且手动实现一次MLE会让你对Copula的理解上一个大台阶后面排查问题也能更快定位。t Copula的密度函数长这样2维情况C(u,v) t_{nu,rho}( t^{-1}{nu}(u), t^{-1}{nu}(v) )实际计算通常用对数形式。下面给一个完整可跑的例子用fminsearch估计t Copula的参数rng(1) % 生成模拟数据 true_rho 0.5; true_nu 6; U_true copularnd(t, [1 true_rho; true_rho 1], true_nu, 1000); % 定义t Copula的负对数似然函数 negloglik_t (x) -sum(log(copulapdf(t, U_true, ... {[1 x(1); x(1) 1], 2 x(2)}))); % 用2x保证nu2 % 初始值 x0 [0.3, 5]; % 优化 options optimset(Display, iter, MaxFunEvals, 5000); x_opt fminsearch(negloglik_t, x0, options); fprintf(估计的rho: %.4f (真实值 %.4f)\n, x_opt(1), true_rho); fprintf(估计的nu: %.4f (真实值 %.4f)\n, 2 x_opt(2), true_nu);这里我把nu从2开始做平移变换2 x(2)是为了保证优化过程中自由度始终大于2。很多新手直接拿nu去优化结果跑到极值、矩阵不正定就会报错。这种参数化技巧看起来不起眼但能避免一大半数值问题。另外初始值的选择也很关键。我的建议是先用copulafit跑一次把结果作为手动优化的初值这样手写版本基本都能收敛还能顺便对照验证。3.3 模型选择AIC/BIC不是万能的但比瞎猜强拟合完几种Copula之后怎么选我一般先看负对数似然然后计算AIC和BICAIC 2k - 2logLBIC k*log(n) - 2logL其中k是参数个数Gaussian在2维下是1个相关参数t是2个Clayton/Frank/Gumbel都是1个n是样本量logL是最大化对数似然值。n size(U, 1); params_count struct(Gaussian, 1, t, 2, Clayton, 1, Frank, 1, Gumbel, 1); nll_values [nll_gau, nll_t, nll_clay, nll_frank, nll_gumbel]; family_names {Gaussian, t, Clayton, Frank, Gumbel}; for i 1:length(family_names) k params_count.(family_names{i}); aic 2*k - 2*(-nll_values(i)); % 注意nll_values是负对数似然 bic k*log(n) - 2*(-nll_values(i)); fprintf(%s: AIC %.4f, BIC %.4f\n, family_names{i}, aic, bic); end别迷信AIC/BIC一定选对。Copula的选型本质上是对“尾部行为”的假设不同族之间的差异在小样本下可能很不显著。我通常会把AIC/BIC排名第一和第二的Copula都跑一遍下游模拟看看风险指标差异大不大如果结果稳健就选参数更简单的那个——奥卡姆剃刀原则在这里很实用。另一个更直观的检验方法是“经验Copula对比”。把数据的经验联合分布和理论Copula的CDF在同一组网格点上做差算平均绝对误差% 计算经验Copula通过秩变换 Urank tiedrank(U) / (n 1); % 在某几个点比较简单起见算全体点的均差 grid_u (1:10)/11; diff_sum 0; count 0; for i 1:length(grid_u) for j 1:length(grid_u) emp_cdf mean(Urank(:,1) grid_u(i) Urank(:,2) grid_u(j)); theo_cdf copulacdf(t, [grid_u(i), grid_u(j)], rho_t, nu_t); diff_sum diff_sum abs(emp_cdf - theo_cdf); count count 1; end end mae diff_sum / count; fprintf(拟合优度MAE: %.4f\n, mae);这个MAE越小说明拟合越好。虽然不如专门的Goodness-of-Fit检验严谨但作为快速判断完全够用。3.4 把模型用起来蒙特卡洛模拟与风险指标拟合完Copula不是终点大部分实际项目还要用它做模拟。比如金融风控里最常见的场景给定当前持仓模拟未来一天的组合收益算VaR或ESExpected Shortfall。完整流程是用拟合好的Copula生成均匀分布随机数U_new把U_new通过各边缘分布的逆CDF转成模拟收益率用模拟收益率计算组合收益取分位数作为VaR代码示例% 假设我们已经拟合好了边缘分布 pd1, pd2 和 t Copula的参数 rho_t, nu_t M 100000; % 模拟次数 U_sim copularnd(t, rho_t, nu_t, M); % 转成资产收益率 sim_ret1 icdf(pd1, U_sim(:,1)); sim_ret2 icdf(pd2, U_sim(:,2)); % 组合收益率等权重 portfolio_ret 0.5 * sim_ret1 0.5 * sim_ret2; % 95% VaR负号表示损失 VaR_95 -quantile(portfolio_ret, 0.05); ES_95 -mean(portfolio_ret(portfolio_ret -VaR_95)); fprintf(95%% VaR: %.4f\n, VaR_95); fprintf(95%% ES: %.4f\n, ES_95);顺带说一句copularnd(t, rho_t, nu_t, M)里的rho_t是拟合得到的相关矩阵注意它必须是一个正定矩阵否则Matlab会报错。如果你自己修改过相关矩阵先用nearestSPD这类函数修复一下正定性。另外可以用copulastat直接计算尾部相关系数用来评估模型是否抓住了数据的尾部特征tail_coef copulastat(t, rho_t, nu_t); fprintf(t Copula的理论尾部相关系数: %.4f\n, tail_coef(1,2));这里返回的其实是上下尾相等的尾部相关系数。如果你用Clayton返回的就是下尾相关系数用Gumbel返回的就是上尾相关系数。这个数字可以帮你理解Copula参数的实际含义——比如Clayton的alpha2对应的下尾相关系数大约是多少心里有个数。4. 排查与经验常见报错、坑点和工具箱外延4.1 常见报错速查表这一节是给我自己备忘用的也分享给被各种报错折磨过的朋友。报错信息常见原因解决方案Undefined function copulafit未安装Statistics Toolbox检查工具箱或用ver(stats)确认X must be a matrix of data输入数据有NaN或Inf用isnan、isinf检查并清洗RHO must be a correlation matrix相关矩阵非正定用nearestSPD修复或检查数据相关性The tail parameter must be...t Copula自由度超出边界改用copulafit的Bounds选项或重新参数化优化不收敛初始值太差或数据量过小用copulafit结果做初值或增加迭代次数遇到NaN问题先说清楚copulafit不接受数据里有NaN哪怕只缺一个值整个矩阵都会罢工。最简单的处理是U(any(isnan(U),2),:) [];但如果你做的是时间序列删除行会导致后续序列错位这时建议用fillmissing做插值或者用EM算法别图省事直接删。4.2 我自己踩过的几个坑第一个坑边缘分布拟合不当后面的Copula全白做。有次我图省事直接用ecdf把数据变成U结果因为样本里有几个极端异常值经验CDF在尾部几乎是平的Clayton的alpha被估得奇高。后来换成参数法拟合tLocationScale问题立刻解决。如果你不确定边缘分布形态至少用核密度估计的CDF过渡一下别直接用经验分布。第二个坑自由度估计不稳定。t Copula的nu参数在小样本下极不稳定经常出现估计值直接撞到搜索边界比如50。这不是代码错了而是数据本身提供的信息不足。解决办法是设定合理的先验范围比如把nu限制在3到30之间或者干脆用ApproximateML方法它的稳定性比精确ML好一些。第三个坑蒙特卡洛模拟忘了固定随机种子。Copula模拟本身是随机过程你不设rng前后两次跑出来的VaR会有差异这在团队协作或写报告时很尴尬。我习惯在脚本开头固定rng(42)或者在模拟前用rng(default)重置这样至少保证每个人的结果一致。第四个坑二维数据好办高维数据难做。到5维以上时相关矩阵的参数估计需要样本量随维度指数增长。我见过有人拿60个月的数据直接拟合7维t Copula结果相关矩阵里有大量噪声模拟结果惨不忍睹。这时候要么降维比如用PCA降维后做Copula要么改用基于秩相关矩阵的两步法能稳不少。4.3 扩展方向动态Copula、混合Copula与贝叶斯估计如果内置Copula满足不了需求还有几个延展方向值得研究。第一个是动态Copula。金融数据的相依结构很少是常数尤其是危机期间相关系数会急剧上升。这类问题可以用滚动窗口拟合Copula参数或者用DCC-GARCH先提取动态相关再和Copula结合。Matlab里需要自己循环调用copulafit模板前面给了往循环里套就行。第二个是混合Copula。有时候单一Copula不够用比如数据下尾和上尾都有相关但强度不同t Copula假设对称尾部就不太合适。可以构造一个Clayton和Gumbel的线性组合C_mix w * C_clayton (1-w) * C_gumbel。这时copulafit没法直接估计需要自己写对数似然函数用fmincon优化注意权重w要限制在[0,1]之间同时可以用logit变换保证。第三个是贝叶斯估计。如果你对参数有很强的先验知识比如行业经验表明相关性在0.4到0.6之间用贝叶斯方法把先验信息加进去更合理。Matlab的Statistics Toolbox有slicesample可以做简单的MCMC采样但过程相对繁琐新手慎入。我个人的建议是先别急着上高级模型把标准Copula的分析流程走通理解每一步在做什么再去扩展。很多实际项目里t Copula配上好的边缘分布模型结果已经比传统相关系数方法好太多了。5. 写在最后的实操心得做Copula分析这几年我最深的体会是模型不是越复杂越好关键是数据预处理和边缘分布的选择这两个环节决定了Copula分析的下限而Copula本身只负责上限。很多人一上来就纠结选哪个Copula族却忽略了原始数据里的异常值、非平稳性和异方差导致后续所有分析都建立在流沙上。另一个想强调的点是Matlab内置函数确实方便但它是一个“黑箱”。如果你只满足于调copulafit、跑通一个demo那一旦遇到真实数据里的各种噪音和结构变化你会发现自己完全没有排查问题的抓手。建议至少手动实现一次MLE亲眼看一次对数似然函数是怎么随参数变化的这样以后再看到报错就不会慌。最后再分享一个小技巧在做完Copula拟合之后强烈建议画一张“模拟数据 vs 原始数据”的对比散点图坐标轴范围保持一致。如果两张图的整体形态、特别是尾部的密集程度比较接近说明你的模型基本抓住了数据结构如果模拟图看起来过于均匀或者尾部过于极端那就要回头检查边缘分布或者Copula族的选择了。这种可视化检查比任何统计量都直观也是我每次建模后必做的一步。
返回列表