4、压力测试指标计算:VaR、CVaR、最大回撤与夏普比率在压力期表现
好,咱们进入正题。压力测试跑完了,数据堆了一堆,接下来干什么?算指标。说白了,你得用几个硬核数字来回答一个问题:这套做市策略在极端行情下,到底扛不扛得住?
我个人习惯,压力测试指标主要看四个:VaR、CVaR、最大回撤、夏普比率。它们各自盯着风险的不同侧面。今天咱们一个一个拆开讲,顺便聊聊我在实战中踩过的坑。
4.1 在险价值(VaR)—— 最坏情况下的“底线”
VaR 是什么?简单说就是:在给定的置信水平和持有期内,资产组合可能的最大损失。比如“95%置信度下,日 VaR 是 50 万”,意思就是只有 5% 的概率,一天亏损会超过 50 万。
计算 VaR 的方法有好几种。做市场景下,我推荐用历史模拟法。为什么?因为它不需要假设收益率服从正态分布。你想想看,压力期哪有什么正态分布?全是肥尾。
将历史损益序列按升序排列,取第 (1-α)×N 个分位数对应的损益值。
代码实现也很直接:
import numpy as np
def calculate_var(returns, confidence_level=0.95):
"""
计算 VaR(历史模拟法)
:param returns: 收益率序列(numpy array)
:param confidence_level: 置信水平,默认 95%
:return: VaR 值(正数表示损失)
"""
sorted_returns = np.sort(returns)
index = int((1 - confidence_level) * len(sorted_returns))
var = -sorted_returns[index] # 取负值,方便理解
return var
# 示例:假设压力期收益率数据
stress_returns = np.random.normal(-0.002, 0.03, 1000) # 模拟数据
var_95 = calculate_var(stress_returns, 0.95)
print(f"95% VaR: {var_95:.4f}")
4.2 条件在险价值(CVaR)—— 亏起来到底有多疼?
VaR 有个致命缺陷:它只告诉你“最坏情况”的边界,但没告诉你一旦突破这个边界,损失会有多惨。这就是 CVaR 要解决的问题。
CVaR,也叫期望损失(Expected Shortfall),衡量的是超过 VaR 的那些损失的均值。说白了就是:当坏日子真的来了,平均每天亏多少?
CVaR = E[ Loss | Loss > VaR ]
即所有超过 VaR 的损失的算术平均值。
def calculate_cvar(returns, confidence_level=0.95):
"""
计算 CVaR(条件在险价值)
"""
sorted_returns = np.sort(returns)
index = int((1 - confidence_level) * len(sorted_returns))
# 取所有超过 VaR 的损失
tail_losses = -sorted_returns[:index]
cvar = np.mean(tail_losses)
return cvar
cvar_95 = calculate_cvar(stress_returns, 0.95)
print(f"95% CVaR: {cvar_95:.4f}")
我习惯把 VaR 和 CVaR 放在一起看。如果两者差距很大,说明尾部风险很重。举个例子:VaR=50万,CVaR=120万,这意味着一旦触发 VaR,平均亏损会达到 120 万——这个信息对做市商的资金准备至关重要。
4.3 最大回撤(Max Drawdown)—— 你能承受多久的浮亏?
最大回撤,衡量的是从净值峰值到后续谷底的最大跌幅。做市商尤其关注这个指标,因为它直接关系到你的资金占用和风控线。
压力期最大回撤往往比正常时期大 3-5 倍。我记得有一次做 BTC 永续合约的做市策略,正常时期最大回撤只有 2%,但 3.12 暴跌那天,最大回撤直接飙到 18%。
def calculate_max_drawdown(equity_curve):
"""
计算最大回撤
:param equity_curve: 净值曲线(numpy array)
:return: 最大回撤(百分比,正数)
"""
peak = np.maximum.accumulate(equity_curve)
drawdown = (peak - equity_curve) / peak
max_dd = np.max(drawdown)
return max_dd
# 模拟净值曲线
np.random.seed(42)
equity = 100 * np.exp(np.cumsum(np.random.normal(0.0005, 0.02, 500)))
max_dd = calculate_max_drawdown(equity)
print(f"最大回撤: {max_dd:.2%}")
4.4 夏普比率在压力期的“失真”问题
夏普比率 = (策略收益率 - 无风险利率) / 收益率标准差。正常时期,这个指标很好用。但压力期,它可能会骗人。
为什么会这样?因为夏普比率假设收益率是正态分布,且风险用标准差衡量就够了。但压力期有两个问题:
- 肥尾效应: 极端损失出现的概率远高于正态分布假设。夏普比率会低估尾部风险。
- 波动率聚集: 压力期波动率会突然飙升,导致分母变大,夏普比率反而可能下降,掩盖了策略的真实风险调整后收益。
我个人的做法是:压力期不要只看夏普比率,而是看调整后的夏普比率,或者用 Sortino 比率替代(只考虑下行波动率)。
def calculate_sortino_ratio(returns, risk_free_rate=0.02):
"""
计算 Sortino 比率(只考虑下行风险)
"""
excess_returns = returns - risk_free_rate / 252 # 日化
downside_returns = returns[returns < 0]
downside_std = np.std(downside_returns)
sortino = np.mean(excess_returns) / downside_std * np.sqrt(252) # 年化
return sortino
sortino = calculate_sortino_ratio(stress_returns)
print(f"压力期 Sortino 比率: {sortino:.2f}")
4.5 四个指标的综合解读框架
光算单个指标没用,你得把它们串起来看。我总结了一个四象限判断法:
| 指标组合 | 解读 | 应对建议 |
|---|---|---|
| VaR 低 + CVaR 低 + 回撤小 | 策略在压力期表现稳健 | 可以适当放大仓位 |
| VaR 低 + CVaR 高 + 回撤大 | 尾部风险集中,平时没事,一有事就是大事 | 必须加尾部对冲(如深度虚值期权) |
| VaR 高 + CVaR 高 + 回撤大 | 策略不适合当前市场环境 | 立即降低仓位,重新评估参数 |
| VaR 高 + CVaR 低 + 回撤小 | 少见,说明损失分布很均匀,没有极端值 | 检查数据是否有误,或者策略是否过度拟合 |
嗯,这里要注意:压力测试的指标不是算一次就完事了。我建议每季度重新跑一次压力测试,更新参数。因为市场结构在变,去年的压力场景,今年可能就不适用了。
4.6 本章知识体系总览
下面这张图,是我自己画的一个知识结构图,帮你把四个指标的关系理清楚:
最后说一句:指标只是工具,不是目的。真正重要的是,通过这些指标你能做出什么决策。是加仓、减仓、还是加对冲?这才是压力测试的价值所在。
公众号:蓝海资料掘金营,微信deep3321