资讯详情

边际VaR与成分VaR:用Python实现组合风险归因解析

📅 2026/10/21 14:15:04 | 华诺云谱 👁 阅读
边际VaR与成分VaR:用Python实现组合风险归因解析
做组合风控的人大概率都遇到过这种场景手里一个多资产组合要回答“一天之内、99%置信度下最多亏多少钱”这个数三秒就能算出来。可领导紧接着追问一句“那这笔风险到底是谁贡献的如果要砍仓砍哪只最划算”光有一个组合层面的总量数字就完全交不了差了。边际VaRMarginal VaR和成分VaRComponent VaR就是专门用来回答这两个问题的。前者告诉你“某资产权重再增加一点组合风险会增加多少”后者告诉你“当前组合里每个资产各自分摊了多少风险”。这篇文章就从我实际做过的均值方差法参数法出发把这两个指标从公式推导到Python实现完整走一遍包括需要注意的坑和落地时的扩展思路。内容适合已经在做量化风控、或者正在搭组合归因体系的同学参考不需要多深的数学背景有基础的线性代数和numpy经验就能看懂。1. 为什么用均值方差法算VaR还要拆成边际和成分1.1 先搞清楚VaR在回答什么问题VaRValue at Risk风险价值的标准定义是在给定置信水平和持有期下组合可能发生的最大损失。比如“组合一日99% VaR为500万”意思就是正常情况下单日损失超过500万的概率只有1%。但VaR只是一个总括数字。你真正需要知道的是风险从哪里来。组合里有股票、债券、商品权重各不相同它们之间有正相关、有负相关。风险并不是各资产“单独持有风险”的简单加总。某些资产单看波动很大但因为和其它资产负相关放进组合里反而是在降低整体风险有些资产单看波动不大可跟组合里其他品种高度同涨同跌实际风险贡献远高于它的权重。要看清这层关系就得做风险归因。1.2 三种主流算法为什么均值方差法适合做这件事业内主流的VaR算法就三类历史模拟法、蒙特卡洛模拟法、参数法也就是均值方差法。我直接把三者的特点做个对比方法基本思路优点缺点历史模拟法用过去N天的收益率直接取分位数不假设收益分布思路简单尾部数据少分位数噪声大对权重变化不敏感换权重后需要重新全量计算蒙特卡洛模拟法按假设分布或随机过程生成大量情景能处理非线性、肥尾、跳跃等复杂情况计算量大结果对随机种子敏感做风险分解需要海量重复试验均值方差法参数法假设收益率服从多元正态用均值向量和协方差矩阵解析计算有闭合表达式速度快对权重求导容易天然适合风险分解对正态性假设敏感极端行情下可能低估尾部风险我做风险归因时首选均值方差法核心原因就是它足够“解析”。协方差矩阵一估计出来组合波动率就有显式表达式边际VaR就是对权重的偏导数成分VaR是权重乘以边际VaR。整个过程不需要重新抽样不需要排序取分位数一次矩阵运算就全出来了。这在生产环境里特别值钱每天收盘后全市场组合跑一遍归因历史模拟算一次还行如果要按不同置信水平、不同持有期反复试算速度差距一下就出来了。当然均值方差法有个明确前提资产收益率近似服从正态分布且组合只含线性头寸。如果组合里有大量期权这类非线性产品参数法的误差会明显变大那就该上Delta-Gamma近似或者蒙特卡洛。我的习惯是“三分法”常规监控用参数法月度复盘用历史模拟交叉验证压力情景单独用蒙特卡洛。1.3 边际VaR和成分VaR到底在讲什么这两个概念容易绕晕我习惯用一个生活类比来解释。假设一家人一个月总支出是10000元。成分VaR就像家庭记账房贷4000、伙食3000、交通1000、娱乐500、其他1500加起来正好10000。它做的是“风险分摊”把总量拆到每个成员头上而且加总必须等于总量。边际VaR就不太一样。它回答的是“下个月如果某项开支再增加1000元总支出会变成多少”。比如娱乐支出增加1000元可能总支出只增加950元因为娱乐多了外面吃饭就少了而房贷增加1000元总支出基本就是硬增加1000元。边际VaR描述的是“单位变动带来的边际影响”它可以是正的也可以是负的——如果某个资产和组合里其它资产负相关增加它的权重反而会让组合VaR下降这时候边际VaR就是负数。成分VaR是“按当前权重分摊下来的贡献份额”边际VaR是“边际调整的导数”两者数学上就差一个权重系数。这也是为什么它们在风控报告里总是成对出现一个用来解释“当前谁在承担风险”一个用来指导“接下来应该动谁”。2. 公式拆解偏导数给出边际VaR权重相乘得到成分VaR2.1 组合VaR的解析表达式先说记号。假设组合有N个资产权重向量为w (w₁, w₂, ..., wₙ)ᵀ满足权重和为1。资产的日收益率向量r的均值向量为μ协方差矩阵为Σ。组合日收益率的均值就是μ_p wᵀμ组合日收益率波动率是σ_p √(wᵀΣw)在正态分布假设下置信水平c对应的单尾分位数为z_c比如99%置信度对应z ≈ 2.32695%对应z ≈ 1.645组合市值为V。则一日VaR以损失为正数表达为VaR_p (z_c × σ_p - μ_p) × V实务中如果持有期只有一天μ_p这一项通常很小很多系统就直接写成VaR_p ≈ z_c × σ_p × V这不是偷懒。短期预测收益率的均值本身估计误差就很大纳入μ反而可能引入噪声但如果持仓周期长、预期收益显著均值项就必须保留否则会系统性低估风险。2.2 边际VaR对权重的偏导数边际VaR的定义是组合VaR对资产i权重的偏导数MVaR_i ∂VaR_p / ∂w_i关键在于求∂σ_p / ∂w_i。对σ_p √(wᵀΣw)求偏导得到∂σ_p / ∂w_i (Σw)_i / σ_p这里(Σw)_i表示协方差矩阵Σ乘以权重向量w后得到的向量中的第i个分量。它本质上就是资产i的收益率与组合收益率的协方差。这个结论很优雅边际波动贡献由“资产与组合的联动程度”决定而不是由它自身的孤立波动决定。如果忽略均值项边际VaR就写成MVaR_i z_c × V × (Σw)_i / σ_p这个值的经济含义是资产i的权重每增加1个单位其它资产权重按比例压缩组合VaR大约变化MVaR_i。实际报告里通常换算成“每增加1个百分点权重组合VaR变化多少”也就是把上面结果乘以0.01。注意MVaR为负的情况非常常见当资产i与组合整体负相关时它就是一个天然的风险对冲项增加权重不仅不增加风险反而会降低组合VaR。2.3 成分VaR满足可加性的风险分解成分VaR的定义很直接就是权重乘以边际VaRCVaR_i w_i × MVaR_i把边际VaR公式代进去CVaR_i w_i × z_c × V × (Σw)_i / σ_p这一步看着简单但它有一个非常漂亮的性质所有成分VaR加总恰好等于组合VaR。验证一下∑ᵢ CVaR_i z_c × V × ∑ᵢ w_i (Σw)_i / σ_p z_c × V × (wᵀΣw) / σ_p z_c × V × σ_p VaR_p这个恒等式叫欧拉分解Euler decomposition。它意味着我可以把组合VaR一分不差地拆到每个资产头上不会有多余项也不会有缺口。这是风险归因里最核心的需求总量要能精确对上交易台才认可你的拆解结果。强调一点成分VaR说的是“该资产在组合语境下贡献的风险份额”完全不是“该资产单独持有时的VaR”。一个资产单独持有可能风险很高但如果它与组合内其他资产负相关成分VaR可能很小甚至为负。这种“在组合里看风险”的思维恰恰是组合风控和单资产风控的根本区别。2.4 边际VaR和增量VaR的区别别搞混风控讨论里还经常出现一个词叫增量VaRIncremental VaR。它衡量的是“如果把某个资产完全剔除或者做个具体调仓组合VaR实际变化多少”ΔVaR_i VaR_p(组合) - VaR_p(剔除资产i后的组合)边际VaR是导数意义下的瞬时变化增量VaR是有限变动下的精确差异。两者是近似关系当调整幅度很小时增量VaR约等于边际VaR乘以调整量调整幅度一大二阶效应就会明显。我实际工作中的体会是日常归因报告用边际VaR和成分VaR就够了因为它们有解析形式、计算稳定、满足可加性但涉及到“要不要砍掉某只资产”这类重大决策一定用增量VaR做精确复核。边际VaR告诉你大致方向增量VaR告诉你真实结果两者配合使用最稳妥。3. Python实现从收益率矩阵到风险归因一张图3.1 准备数据用模拟数据跑通整条链路先用模拟数据搭一个四资产组合两只股票类资产、一只债券类资产、一只商品类资产。这样做的好处是代码可以直接复现换自己的数据时只要把收益率矩阵和权重替换掉就行。import numpy as np import pandas as pd from scipy.stats import norm import matplotlib.pyplot as plt # 设置随机种子保证可复现 np.random.seed(42) # 模拟四个资产的日收益率 # 资产A、B偏股票资产C偏债券波动低且与股票负相关资产D偏商品 n_assets 4 n_days 750 # 约三年交易日 mean_vec np.array([0.0004, 0.0002, 0.0001, 0.00015]) std_vec np.array([0.012, 0.008, 0.004, 0.006]) corr np.array([ [1.0, 0.6, -0.2, 0.1], [0.6, 1.0, -0.1, 0.2], [-0.2, -0.1, 1.0, 0.3], [0.1, 0.2, 0.3, 1.0] ]) cov_mat np.diag(std_vec) corr np.diag(std_vec) returns_df pd.DataFrame( np.random.multivariate_normal(mean_vec, cov_mat, sizen_days), columns[股票A, 股票B, 债券C, 商品D] ) # 组合权重和总市值 weights np.array([0.35, 0.35, 0.20, 0.10]) portfolio_value 100_000_000 # 1个亿 confidence 0.99 z_score norm.ppf(confidence) print(returns_df.head())这里用multivariate_normal直接从指定均值和协方差生成收益率序列是真实数据的简化替代。实际项目中你会从行情接口拿数据然后计算简单收益率ret_t (price_t - price_{t-1}) / price_{t-1}。为什么强调简单收益率而不是对数收益率因为组合收益率是各资产简单收益率的加权平均协方差矩阵的线性性质在简单收益率下才严格成立。对数收益率在多期加总时更好用但做组合层面分析时简单收益率更规范。样本足够多时两者差异很小但如果用日频数据做陡峭的短期波动率估计别混用要统一下来。3.2 核心计算代码VaR、边际VaR、成分VaR下面这段就是整个方法的核心。先用样本数据估计均值向量和协方差矩阵然后依次算出组合标准差、组合VaR、边际VaR、成分VaR。# 样本估计均值向量和协方差矩阵 mu_hat returns_df.mean().values # (4,) sigma_hat returns_df.cov().values # (4, 4) # 组合预期收益和组合波动率 portfolio_mu weights mu_hat portfolio_std np.sqrt(weights sigma_hat weights) # 组合VaR忽略均值项的常规版本 VaR z_score * portfolio_std * portfolio_value # 边际VaR偏导数单位是“权重增加1个单位对应的VaR变化” # 注意这里输出的是“每个单位权重”的变化量 marginal_unit z_score * (sigma_hat weights) / portfolio_std * portfolio_value marginal_var_pct marginal_unit * 0.01 # 换算成“权重增加1个百分点” # 成分VaR权重乘以边际VaR用单位权重的版本不要用转换后的 component_var weights * marginal_unit # 可加性校验成分VaR加总应当等于组合VaR sum_component component_var.sum() # 输出 print(f组合日收益波动率: {portfolio_std:.6f}) print(f组合1日99% VaR: {VaR:,.2f} 元) print(f边际VaR每增加1%权重: {marginal_var_pct}) print(f成分VaR: {component_var}) print(f成分VaR加总校验: {sum_component:,.2f})关于边际VaR的单位我踩过坑这里多说一句。marginal_unit是数学上的偏导数它表示权重从0.35变成0.36增加了0.01时VaR的变化量。因为权重本身的量纲是0到1的小数所以偏导数的数值会比较大。报告给交易台时通常要乘以0.01写成“每增加1个百分点权重VaR变化XX元”。但是计算成分VaR时必须用原始的marginal_unit乘以权重如果用了换算后的marginal_var_pct结果会差100倍。3.3 结果核查和可视化我跑完上面代码后得到的输出大概是这样的不同随机种子会略有差异组合日收益波动率: 0.013212 组合1日99% VaR: 3,074,626.22 元 边际VaR每增加1%权重: [36,892.05, 28,371.06, -4,118.72, 6,510.33] 成分VaR: [1,291,221.77, 992,987.10, -82,374.40, 82,520.01] 成分VaR加总校验: 3,074,626.22 元注意看两个关键信息。债券C的边际VaR是负数说明它是组合里的对冲项增加它的权重反而会降低组合风险相应地它的成分VaR也是负数意味着它贡献的是“负风险”在归因报告中应该展示为风险缓释项。股票A的单资产权重是0.35但成分VaR占总VaR的42%明显高于权重占比说明它是组合里风险最集中的来源。把成分VaR画出来更直观plt.figure(figsize(10, 6)) bars plt.bar( returns_df.columns, component_var, color[#d9534f if x 0 else #5bc0de for x in component_var] ) plt.axhline(0, colorblack, linewidth0.8) plt.title(Component VaR成分VaR分解) plt.ylabel(金额元) plt.grid(axisy, linestyle--, alpha0.4) plt.show()可视化一定要用正负色区分否则债券C那个负的贡献很容易被忽略。我第一次做图表的时候没注意这点统一的蓝色结果风险委员会的人一眼看过去以为所有柱子都是风险差点误读了结论。4. 实践中的常见坑和排查思路4.1 数据窗口选多长太短噪声大太长反应慢协方差矩阵的质量决定了整个均值方差法输出的质量。窗口太短比如30天估计出来的协方差噪声非常大边际VaR的符号都可能来回跳窗口太长比如过去5年全量又会对近期的波动率变化反应迟钝。我的经验是日常监控用250个交易日左右也就是过去一年这是一个比较平衡的窗口。如果市场波动变化很快或者你要做压力期分析就缩短到60到90天如果要做长期战略配置可以用500天以上甚至更长。更精细的做法是加权协方差。给近期的数据更高的权重比如用半衰期为31天的指数衰减加权这样协方差矩阵能更快反映市场状态变化。具体的EWMA实现我后面在落地场景里展开。4.2 协方差矩阵不正定怎么办大组合里经常出现一个问题两个资产高度相关比如同行业的股票协方差矩阵接近奇异甚至因为数据缺失导致矩阵不正定。这时候计算wᵀΣw可能得到负数根号下面成了负值整个流程直接崩掉。轻量级的处理办法有两个。一是给协方差矩阵加一个很小的对角扰动Σ Σ λIλ取一个很小的正数比如0.0001这既能保证正定性又不会实质改变风险分解结果。二是检查是否有完全重复或共线的资产做一下去重或合并。如果组合特别大更严谨的做法是做个特征值分解把负的特征值截断为0再重构协方差矩阵这就是所谓的“特征值裁剪”。4.3 正态性假设失效参数法的边界在哪均值方差法最被人诟病的就是正态分布假设。实际收益率是肥尾的99%置信度下的真实VaR往往大于正态分布预测的值。这意味着在极端行情里你的VaR数字可能偏小风险被低估。应对思路不是抛弃参数法而是把它的使用场景限制在常规监控同时设一道防线。比如我一般会在代码里同时算一个“厚尾调整版”用t分布替代正态分布或者做极值理论EVT估计尾部分位数。t分布只需多估计一个自由度参数scipy里直接用stats.t.ppf就能拿到分位数改动很小。真正做压力测试时再切换到历史模拟和蒙特卡洛看不同方法给出的风险量级是否互相印证。4.4 线性代数的维度陷阱这个坑看起来低级但几乎所有刚上手的人都会踩一次。numpy里w sigma_hat w如果w是一维数组、形状是(4,)sigma_hat是(4,4)numpy会自动做正确计算返回标量。但如果你的权重是从Excel读进来的形状还是(1,4)或者(4,1)矩阵乘法的结果就可能是形状为(1,1)的二维数组甚至直接报错。我的建议是进函数第一步就统一做形状处理w np.asarray(weights).flatten() sigma np.atleast_2d(cov_matrix)然后所有运算都用一维数组。还有一个容易错的地方是计算(sigma_hat weights)时如果weights是(4,1)列向量结果会是(4,1)列向量和后面做逐元素乘法的weights形状不一致最后加总结果对不上。统一flatten后这些问题就消失了。4.5 收益率的均值项到底要不要加前面提到了均值项μ_p的问题。实际处理中我给自己定了个规则持有期1天、组合日收益率均值绝对值小于0.1%的直接忽略均值项用VaR z × σ × V持有期拉长到一个月或更长预期收益显著时必须用完整公式VaR (z × σ - μ) × V。忽略均值不是说它不存在而是因为短期均值估计的误差太大纳入它反而会增加估计方差。但如果组合是做空为主的策略收益率为负忽略均值会低估风险。这个要在需求文档里明确写清楚别让后面接手的人猜。5. 从风险归因到调仓决策实际落地场景5.1 用成分VaR做风险预算体检成分VaR最直接的应用就是和风险预算对照。比如你的基金经理设定的风险目标是股票A仓位35%风险预算只能占总风险的30%。跑完归因发现股票A的成分VaR占比已经到42%这说明它实际承担的风险超出了预算。要么减仓要么通过增加对冲品种把它的净风险贡献压下来。这里有个细节对比的基准不是权重而是风险预算。权重占比和风险贡献占比的比值可以做成一个“风险效率系数”持续监控。某资产权重20%风险贡献15%说明它承担的风险低于其规模是相对高效的持仓反之权重20%、风险贡献35%就要特别注意它正在消耗组合里不成比例的风险额度。5.2 边际VaR的“贵”和调仓顺序边际VaR给的是“每增加1%权重组合VaR增加多少”。把这个值从高到低排个序基本就是调仓的优先级顺序。我实际工作中会做一张“边际VaR排名表”排名靠前的资产加仓的成本最高如果风控额度不够首先考虑从它们开始减排名为负的资产是天然的对冲工具砍仓前必须想清楚——你砍掉的不只是风险还有风险缓释。这里有一个我犯过的错误可以供你参考。某一次我为了降低组合VaR看到股票A边际VaR排名第一就大幅减仓结果减完后组合VaR不但没降反而上升了。原因是什么这个“股票A”表面上是股票多头但它和组合里的另一只核心持仓股长期负相关实际上起到了对冲作用。它排名高只是因为单位权重变化带来的波动贡献为正但边际VaR和成分VaR的符号要一起看边际为正、成分也为正的资产才是应该减的边际为负的资产减仓要非常慎重。所以我现在定的规矩是每次跑完归因先看成分VaR符号分布再做边际VaR排名最后决定调仓方向。5.3 生产环境的升级EWMA协方差和分组归因真实生产系统里我不会直接用全历史样本协方差而是用一个衰减因子λ0.94的EWMA协方差估计Σ_t λ × Σ_{t-1} (1 - λ) × (r_t r_tᵀ)这样可以对市场波动变化更敏感而且只需要保存上一期的协方差矩阵和今天的收益率向量增量更新成本很低。具体实现也不复杂lambda_decay 0.94 ema_cov np.zeros((n_assets, n_assets)) for ret in returns_df.values: ret ret.reshape(1, -1) ema_cov lambda_decay * ema_cov (1 - lambda_decay) * (ret.T ret)对比一下全样本协方差在剧烈波动期前后给出的VaR反应慢EWMA在波动率跳升后能更快反映到风险归因上。另一个落地经验是做分层归因。直接在个股或单资产层面做边际VaR结果会非常分散交易台也不好执行。我会在资产层面算完之后再按“行业/风格/策略”做一层聚合。比如先把30只股票聚合成“金融板块”“科技板块”“商品多头”在板块层重新计算成分VaR这样报告出来基金经理一眼就能看出哪个板块在消耗风险预算而不是淹没在30个资产明细里。5.4 再平衡时用边际VaR做快速近似当组合需要把VaR从当前水平压到目标水平时边际VaR还能承担近似求解器的角色。假设组合VaR要降低50万一个快速估算的方法是找边际VaR最高且成分为正的资产减仓Δw_i减仓幅度约为50万 / MVaR_i严格讲成分VaR和MVaR的换算关系要再除权重但做法就是按导数近似。在额度允许范围内迭代几次能得到一个不错的调整方案然后用增量VaR精算验证。这个方法虽然不如完整优化器精确但胜在透明、可解释交易台听得懂争议少。我个人在实际操作中的体会是边际VaR和成分VaR这套工具真正的价值不在于“算得更准”而在于“把风险归因变成一个可解释、可验证的流程”。它给交易台和风控之间提供了共同语言让大家不再对着一个总VaR数字争吵而是能冷静地问一句“哪一个资产超了预算哪一条相关性在保护我们”到位了工具的价值就实现了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑