第10章:VaR与CVaR计算:参数法、历史模拟法、蒙特卡洛法、回测与验证

做市商最怕什么?怕黑天鹅,怕流动性瞬间枯竭,怕自己扛不住波动被穿仓。

我入行那会儿,带我的老交易员跟我说过一句话,我一直记着:「你赚的钱,都是市场暂时借给你的。风控没做好,连本带利都得还回去。」

VaR(在险价值)和CVaR(条件在险价值),就是用来量化这笔「借款」到底有多大的工具。说白了,它们回答一个问题:最坏情况下,我一天最多亏多少?

今天咱们就把三种主流计算方法——参数法、历史模拟法、蒙特卡洛法,以及怎么回测验证,一次性讲透。

核心观点:VaR告诉你「最坏情况的下限」,CVaR告诉你「一旦突破下限,平均会亏多少」。做市商必须同时关注这两个指标,缺一不可。

VaR与CVaR计算 三大计算方法 参数法(方差-协方差) 历史模拟法 蒙特卡洛模拟法 假设正态分布 无需分布假设 可自定义分布 回测与验证 Kupiec失败率检验 Christoffersen独立性检验 CVaR回测(超越VaR)

一、参数法(方差-协方差法)

参数法是最古老、最经典的方法。它假设收益率服从正态分布,然后直接用均值和标准差算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。

基本步骤:

  1. 估计收益率的分布参数(均值、波动率、相关性等)
  2. 生成N条随机路径(通常N≥10000)
  3. 对每条路径计算组合损益
  4. 排序后取分位数
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是你的「雷达」,回测是你的「校准仪」。两者配合好了,才能在风浪中稳稳航行。


交易系统化学习资料 微信Strategy888888