4、库存风险度量:VaR(在险价值)计算、CVaR(条件在险价值)计算、压力测试场景设计

做市商最怕什么?不是单边行情,而是你不知道自己到底扛了多少风险。

我见过太多交易员,账面浮盈时意气风发,结果一个黑天鹅事件直接爆仓。说白了,库存管理如果没有风险度量,就像闭着眼睛开车——你敢开,我不敢坐。

这一章,我们聊聊库存风险的三个核心工具:VaRCVaR压力测试。这三个东西,是我每天必看的指标。

库存风险度量 VaR 在险价值 CVaR 条件在险价值 压力测试 参数法 历史模拟法 尾部损失均值 蒙特卡洛模拟 历史情景 假设情景

4.1 VaR:在险价值计算

VaR 是什么?简单说就是:在给定的置信水平和持有期内,你的库存可能面临的最大损失

举个例子:95% 置信水平下,日 VaR 为 10 万。意思是,100 天里只有 5 天亏损会超过 10 万。嗯,听起来挺美好,但实际用起来坑不少。

VaR 的三种计算方法:

  1. 参数法(方差-协方差法):假设收益率服从正态分布。计算快,但现实数据哪有那么乖?
  2. 历史模拟法:直接用过去 N 天的收益率排序,取第 5% 分位数。不假设分布,但历史会重演吗?
  3. 蒙特卡洛模拟法:随机生成大量路径,模拟未来价格走势。精度高,但计算量大。

我个人习惯用历史模拟法做日常监控。为什么?因为它简单、直观,而且不需要假设分布。我在项目中遇到过一件事:用参数法算出来的 VaR 只有 5 万,但历史模拟法显示是 12 万。后来一查,那段时间市场波动率异常,正态假设根本不成立。

# Python 示例:历史模拟法计算 VaR
import numpy as np
import pandas as pd

def calculate_var_historical(returns, confidence_level=0.95):
    """
    计算历史模拟 VaR
    :param returns: 收益率序列 (numpy array 或 pandas Series)
    :param confidence_level: 置信水平,默认 95%
    :return: VaR 值(正数表示损失)
    """
    # 排序收益率
    sorted_returns = np.sort(returns)
    # 找到分位数索引
    index = int((1 - confidence_level) * len(sorted_returns))
    # VaR 就是该分位数的绝对值
    var = abs(sorted_returns[index])
    return var

# 示例数据
np.random.seed(42)
daily_returns = np.random.normal(0, 0.02, 1000)  # 模拟 1000 天收益率
var_95 = calculate_var_historical(daily_returns, 0.95)
print(f"95% 置信水平下的日 VaR: {var_95:.4f} 或 {var_95*100:.2f}%")

我的小技巧: 计算 VaR 时,别只用最近 250 个交易日。我一般会同时看 60 天、250 天和 500 天的 VaR。如果三个值差异很大,说明市场结构可能发生了变化,这时候要警惕。

4.2 CVaR:条件在险价值计算

VaR 有个致命缺陷:它只告诉你「最坏情况下的门槛」,但没告诉你「一旦跨过这个门槛,到底会亏多少」。

举个例子:两个策略的 VaR 都是 10 万。但策略 A 的尾部损失均值是 12 万,策略 B 是 30 万。你选哪个?

这就是 CVaR 的价值——它计算的是超过 VaR 那部分损失的期望值。说白了,就是「一旦出事,平均会亏多少」。

CVaR 的核心公式:

CVaR_α = E[ Loss | Loss > VaR_α ]

其中 α 是置信水平,Loss 是损失变量。

我在做期权做市时,特别喜欢用 CVaR。因为期权尾部风险大,VaR 根本不够用。有一次,一个深度虚值期权持仓,VaR 显示只有 2 万,但 CVaR 算出来是 18 万。后来市场果然出现剧烈波动,那个期权价格翻了 10 倍。嗯,CVaR 救了我一命。

# Python 示例:计算 CVaR
def calculate_cvar_historical(returns, confidence_level=0.95):
    """
    计算历史模拟 CVaR
    :param returns: 收益率序列
    :param confidence_level: 置信水平
    :return: CVaR 值(正数表示损失)
    """
    sorted_returns = np.sort(returns)
    # 找到 VaR 的分位数索引
    index = int((1 - confidence_level) * len(sorted_returns))
    # VaR 值
    var = abs(sorted_returns[index])
    # 取所有超过 VaR 的损失
    tail_losses = sorted_returns[:index]
    # CVaR 是这些损失的均值
    cvar = abs(np.mean(tail_losses))
    return cvar, var

cvar_95, var_95 = calculate_cvar_historical(daily_returns, 0.95)
print(f"95% VaR: {var_95:.4f}, 95% CVaR: {cvar_95:.4f}")
print(f"CVaR / VaR 比率: {cvar_95/var_95:.2f}")  # 这个比率越大,尾部风险越高

避坑指南: 我曾经犯过一个错误——直接用 CVaR 替代 VaR 做风控限额。结果发现 CVaR 对异常值太敏感,一个极端数据就能让 CVaR 翻倍。后来我改用「截尾 CVaR」,去掉最极端的 1% 数据再计算,效果稳定多了。

4.3 压力测试场景设计

VaR 和 CVaR 都是基于历史数据或假设分布的。但市场有时候不讲道理——比如 2020 年 3 月的流动性危机,或者 2023 年 3 月的硅谷银行事件。这些极端情况,VaR 根本算不出来。

压力测试就是用来补这个漏洞的。它不问你「大概率会亏多少」,而是问「如果发生这种极端情况,你会不会死」。

4.3.1 历史情景法

直接复制历史上发生过的极端事件。比如:

  • 2008 年金融危机:标普 500 单月跌 20%,波动率飙升 300%
  • 2015 年瑞郎黑天鹅:EUR/CHF 瞬间暴跌 30%
  • 2020 年新冠危机:美股熔断,信用利差急剧走阔

我习惯维护一个「历史情景库」,里面存了 20 多个经典危机事件的价格变动和波动率变动数据。每次调仓前,我都会跑一遍这些情景。

4.3.2 假设情景法

自己设计极端但合理的场景。比如:

  • 利率突变:美联储突然加息 100 个基点
  • 流动性枯竭:买卖价差扩大 10 倍,成交量萎缩 90%
  • 相关性破裂:原本负相关的资产突然同涨同跌

压力测试的核心步骤:

  1. 定义情景:明确价格、波动率、相关性等变量的变动幅度
  2. 重估库存:用新参数重新计算所有持仓的市值
  3. 计算损益:对比当前市值和压力情景下的市值
  4. 评估影响:看是否触及风控限额,是否需要减仓
# Python 示例:压力测试场景模拟
def stress_test_portfolio(positions, price_changes, vol_changes):
    """
    压力测试模拟
    :param positions: 持仓字典 {资产名: {数量, 当前价格, 波动率}}
    :param price_changes: 价格变动幅度 {资产名: 变动比例}
    :param vol_changes: 波动率变动幅度 {资产名: 变动比例}
    :return: 压力测试结果
    """
    total_pnl = 0
    results = {}
    
    for asset, pos in positions.items():
        # 价格变动
        new_price = pos['current_price'] * (1 + price_changes.get(asset, 0))
        # 波动率变动
        new_vol = pos['volatility'] * (1 + vol_changes.get(asset, 0))
        # 损益
        pnl = pos['quantity'] * (new_price - pos['current_price'])
        total_pnl += pnl
        results[asset] = {
            'pnl': pnl,
            'new_price': new_price,
            'new_vol': new_vol
        }
    
    results['total_pnl'] = total_pnl
    return results

# 示例:模拟 2008 年危机情景
positions = {
    'BTC': {'quantity': 10, 'current_price': 50000, 'volatility': 0.5},
    'ETH': {'quantity': 100, 'current_price': 3000, 'volatility': 0.6}
}

crisis_scenario = {
    'price_changes': {'BTC': -0.4, 'ETH': -0.5},  # 跌 40% 和 50%
    'vol_changes': {'BTC': 2.0, 'ETH': 2.5}        # 波动率翻 2-2.5 倍
}

result = stress_test_portfolio(positions, crisis_scenario['price_changes'], crisis_scenario['vol_changes'])
print(f"压力测试总损益: ${result['total_pnl']:,.0f}")
for asset, data in result.items():
    if asset != 'total_pnl':
        print(f"{asset}: 损益 ${data['pnl']:,.0f}, 新价格 ${data['new_price']:,.0f}")

我的经验: 压力测试不是做一次就完事的。我每周五下午都会跑一遍压力测试,看看周末持仓是否安全。另外,我建议你设计一个「最坏情景」——把所有你能想到的坏情况都加在一起。虽然概率极低,但真发生了,至少你知道自己会不会爆仓。

4.4 三者如何配合使用

VaR、CVaR 和压力测试,不是互相替代的关系,而是互补的。

指标 回答的问题 适用场景 局限性
VaR 正常市场下最多亏多少? 日常风控、限额设定 忽略尾部风险
CVaR 一旦出事,平均亏多少? 尾部风险管理、策略比较 对异常值敏感
压力测试 极端情况下会不会死? 极端事件准备、资本规划 情景设计主观性强

我个人习惯的流程是:每天开盘前看 VaR,盘中监控 CVaR,每周做一次压力测试。三者结合,基本能覆盖大部分风险场景。

最后提醒一句: 任何风险模型都有假设和局限。VaR 算出来是 10 万,不代表你永远不会亏 20 万。模型是工具,不是真理。做市商的核心竞争力,是知道什么时候该相信模型,什么时候该相信直觉。


公众号:蓝海资料掘金营,微信deep3321