第10章:VaR与CVaR计算:参数法、历史模拟法、蒙特卡洛法、回测与验证
做市商最怕什么?怕黑天鹅,怕流动性瞬间枯竭,怕自己扛不住波动被穿仓。
我入行那会儿,带我的老交易员跟我说过一句话,我一直记着:「你赚的钱,都是市场暂时借给你的。风控没做好,连本带利都得还回去。」
VaR(在险价值)和CVaR(条件在险价值),就是用来量化这笔「借款」到底有多大的工具。说白了,它们回答一个问题:最坏情况下,我一天最多亏多少?
今天咱们就把三种主流计算方法——参数法、历史模拟法、蒙特卡洛法,以及怎么回测验证,一次性讲透。
核心观点:VaR告诉你「最坏情况的下限」,CVaR告诉你「一旦突破下限,平均会亏多少」。做市商必须同时关注这两个指标,缺一不可。
一、参数法(方差-协方差法)
参数法是最古老、最经典的方法。它假设收益率服从正态分布,然后直接用均值和标准差算VaR。
公式很简单:
VaR(95%) = μ - 1.645 × σ
VaR(99%) = μ - 2.326 × σ
其中μ是均值,σ是标准差,1.645和2.326是标准正态分布在95%和99%置信水平下的分位数。
CVaR的计算也类似,它衡量的是尾部损失的期望值:
CVaR(95%) = μ - σ × φ(1.645) / 0.05
φ是标准正态分布的概率密度函数。
我的经验:参数法计算快,适合高频场景。但有个大坑——金融市场收益率通常有厚尾特征,正态分布假设会严重低估尾部风险。我在做比特币做市时吃过这个亏,VaR算出来每天只亏2%,结果某天暴跌直接亏了8%。
Python实现示例:
import numpy as np
from scipy import stats
def parametric_var_cvar(returns, confidence=0.95):
mu = np.mean(returns)
sigma = np.std(returns, ddof=1)
z = stats.norm.ppf(1 - confidence)
var = mu - z * sigma
# CVaR计算
phi = stats.norm.pdf(z)
cvar = mu - sigma * phi / (1 - confidence)
return var, cvar
二、历史模拟法
这个方法不假设任何分布。它直接用过去N天的收益率数据,排序后取分位数。
比如你有1000天的历史收益率,要算95% VaR,就把收益率从小到大排序,取第50个(1000×5%)最差的收益率。
CVaR就是所有超过VaR的收益率的平均值。
def historical_var_cvar(returns, confidence=0.95):
sorted_returns = np.sort(returns)
index = int(len(returns) * (1 - confidence))
var = sorted_returns[index]
cvar = np.mean(sorted_returns[:index])
return var, cvar
优点:简单直观,没有分布假设,能捕捉到历史中的极端事件。
缺点:依赖历史数据长度,如果市场结构变了,历史数据可能失效。比如2020年之前的数据对2022年的市场参考价值有限。
我个人习惯用历史模拟法做日常监控。它计算快,而且容易向非技术背景的同事解释。「你看,过去一年最差的5%的交易日,平均亏这么多」——这话一说,谁都听得懂。
三、蒙特卡洛模拟法
蒙特卡洛法是最灵活、也最耗计算资源的方法。它通过随机模拟生成大量可能的收益率路径,然后统计VaR和CVaR。
基本步骤:
- 估计收益率的分布参数(均值、波动率、相关性等)
- 生成N条随机路径(通常N≥10000)
- 对每条路径计算组合损益
- 排序后取分位数
def monte_carlo_var_cvar(mu, sigma, n_sim=10000, horizon=1, confidence=0.95):
# 模拟收益率
np.random.seed(42)
sim_returns = np.random.normal(mu, sigma, (n_sim, horizon))
# 计算累积收益率
cum_returns = np.sum(sim_returns, axis=1)
sorted_returns = np.sort(cum_returns)
index = int(n_sim * (1 - confidence))
var = sorted_returns[index]
cvar = np.mean(sorted_returns[:index])
return var, cvar
注意:蒙特卡洛法虽然强大,但容易「过拟合」。我见过有人用100万个模拟路径,结果模型参数稍微调一下,VaR就变了20%。记住:模拟精度取决于输入参数的质量,而不是模拟次数。
四、回测与验证
模型建好了,怎么知道它准不准?回测就是答案。
回测的核心逻辑:用历史数据检验模型预测的VaR是否与实际亏损一致。
Kupiec失败率检验:
如果VaR(95%)是准确的,那么实际亏损超过VaR的天数应该占总天数的5%左右。Kupiec检验就是看这个比例是否显著偏离预期。
def kupiec_test(actual_losses, var_estimates, confidence=0.95):
# 统计失败次数
failures = np.sum(actual_losses < var_estimates)
n = len(actual_losses)
p = 1 - confidence
# LR统计量
from scipy.stats import chi2
lr = -2 * np.log((1-p)**(n-failures) * p**failures) + \
2 * np.log((1-failures/n)**(n-failures) * (failures/n)**failures)
p_value = 1 - chi2.cdf(lr, 1)
return failures/n, p_value
Christoffersen独立性检验:
这个检验更严格。它不光看失败率,还看失败是否「扎堆」出现。如果失败集中在某几天,说明模型在市场波动剧烈时失效了。
避坑指南:我曾经用一套VaR模型跑了半年回测,失败率完美符合预期。但仔细一看,所有失败都发生在同一个月——那月市场暴跌。这说明模型在正常市场下还行,极端行情下完全失效。所以一定要做独立性检验。
CVaR的回测:
CVaR的回测比VaR复杂。一个常用方法是:当实际亏损超过VaR时,比较实际亏损与CVaR的差距。如果差距持续偏大,说明CVaR被低估了。
def cvar_backtest(actual_losses, var_estimates, cvar_estimates):
# 只关注VaR失败的情况
exceedances = actual_losses[actual_losses < var_estimates]
if len(exceedances) == 0:
return "无VaR失败,无法检验CVaR"
# 计算实际尾部均值与CVaR的偏差
actual_tail_mean = np.mean(exceedances)
deviation = (actual_tail_mean - cvar_estimates) / abs(cvar_estimates)
return deviation
五、三种方法对比
| 维度 | 参数法 | 历史模拟法 | 蒙特卡洛法 |
|---|---|---|---|
| 计算速度 | 极快 | 快 | 慢 |
| 分布假设 | 正态分布 | 无 | 可自定义 |
| 尾部风险捕捉 | 弱 | 中等 | 强 |
| 实现难度 | 低 | 低 | 高 |
| 适用场景 | 高频、低延迟 | 日常监控 | 压力测试、复杂组合 |
嗯,说到这儿,我想强调一点:没有完美的模型,只有合适的模型。我在实际项目中,通常同时跑三种方法,取最保守的结果作为风控阈值。虽然会牺牲一些资金效率,但安全第一。
最后,回测不是终点。模型需要持续监控,市场结构变了就要重新校准。我每个月都会跑一次全量回测,看看模型有没有「漂移」。一旦发现异常,立即调整参数。
做市商的风控,说白了就是跟不确定性打交道。VaR和CVaR是你的「雷达」,回测是你的「校准仪」。两者配合好了,才能在风浪中稳稳航行。