本文仅为技术分享,不构成投资建议。量化交易有风险,入市需谨慎。
2026 年 3 月,A 股再次上演"千股跌停"。我的一个量化策略在极端行情下单日回撤 12%,远超预期。
问题出在哪?
不是策略逻辑错了,而是风险计量方法太粗糙。我只用了简单的"历史最大回撤"作为风控阈值,却忽略了更科学的 VaR(风险价值)和 CVaR(条件风险价值)。
什么是 VaR 和 CVaR?
用生活化比喻:
- VaR(Value at Risk):就像"保险额度"——在 95% 的情况下,你最多亏这么多
- CVaR(Conditional Value at Risk):就像"极端情况下的平均损失"——如果真遇到那 5% 的极端情况,平均会亏多少
举个例子:
假设你的投资组合:
- VaR(95%) = 5 万元 → 95% 的把握,一天最多亏 5 万
- CVaR(95%) = 8 万元 → 但那 5% 极端情况下,平均会亏 8 万
只看 VaR 会怎样? 你可能在极端行情下,发现实际亏损远超预期。
这篇文章,我会用完整代码演示:
- 三种 VaR 计算方法(历史模拟法、参数法、蒙特卡洛法)
- CVaR 的计算与对比
- 真实股票数据回测
- 如何用 VaR/CVaR 优化仓位管理
一、为什么 VaR 和 CVaR 比"最大回撤"更科学?
先说结论:最大回撤是"后视镜",VaR/CVaR 是"预警雷达"。
1.1 最大回撤的局限性
最大回撤(Max Drawdown)计算的是历史最差情况:
# 错误示范:只用最大回撤做风控
def max_drawdown(returns):
"""计算历史最大回撤"""
cum_returns = (1 + returns).cumprod()
rolling_max = cum_returns.expanding().max()
drawdowns = cum_returns / rolling_max - 1
return drawdowns.min() # 返回历史最差值
# 问题:这是"后视镜",无法预测未来极端情况
问题在哪? 最大回撤只告诉你"过去最惨亏了多少",但无法回答:
- 明天最可能亏多少?
- 极端行情下(如黑天鹅)会亏多少?
- 我的仓位应该放多大?
1.2 VaR 和 CVaR 的优势
| 指标 | 回答的问题 | 适用场景 |
|---|---|---|
| 最大回撤 | 过去最惨亏多少 | 历史业绩评估 |
| VaR(95%) | 95% 情况下最多亏多少 | 日常风控阈值 |
| CVaR(95%) | 极端 5% 情况下平均亏多少 | 压力测试、极端风控 |
核心差异:VaR 和 CVaR 是概率分布思维,考虑了所有可能情况,而不是只看历史最差。
二、三种 VaR 计算方法详解
2.1 历史模拟法(最简单)
原理:直接用历史收益率的分位数作为 VaR。
import numpy as np
import pandas as pd
def var_historical(returns, confidence=0.95):
"""
历史模拟法计算 VaR
参数:
returns: 历史收益率序列(numpy array 或 pandas Series)
confidence: 置信水平,默认 95%
返回:
VaR 值(正数表示损失)
"""
# 取收益率的分位数(注意:分位数是负数表示亏损)
var = -np.percentile(returns, (1 - confidence) * 100)
return var
# 示例:计算某股票 95% 置信度下的 VaR
returns = np.random.randn(252) * 0.02 # 模拟 252 天收益率,年化波动率约 20%
var_95 = var_historical(returns, confidence=0.95)
print(f"VaR(95%) = {var_95:.4f} = {var_95*100:.2f}%")
优点:
- 简单直观,无需假设分布
- 计算速度快
缺点:
- 依赖历史数据质量
- 无法预测历史未出现过的极端情况
2.2 参数法(假设正态分布)
原理:假设收益率服从正态分布,用均值和标准差计算 VaR。
from scipy.stats import norm
def var_parametric(returns, confidence=0.95):
"""
参数法计算 VaR(假设正态分布)
参数:
returns: 历史收益率序列
confidence: 置信水平
返回:
VaR 值
"""
mu = np.mean(returns) # 均值
sigma = np.std(returns) # 标准差
# 正态分布的分位数
z = norm.ppf(1 - confidence)
# VaR = -(mu + z * sigma)
var = -(mu + z * sigma)
return var
# 示例
var_param = var_parametric(returns, confidence=0.95)
print(f"参数法 VaR(95%) = {var_param:.4f} = {var_param*100:.2f}%")
优点:
- 计算简单
- 有明确的统计学解释
缺点:
- 假设收益率服从正态分布(实际金融市场存在"肥尾")
- 低估极端风险
2.3 蒙特卡洛模拟法(最灵活)
原理:通过大量随机模拟生成未来可能的情景。
def var_monte_carlo(returns, confidence=0.95, n_simulations=10000):
"""
蒙特卡洛模拟法计算 VaR
参数:
returns: 历史收益率序列
confidence: 置信水平
n_simulations: 模拟次数
返回:
VaR 值
"""
mu = np.mean(returns)
sigma = np.std(returns)
# 生成随机收益率(假设正态分布)
simulated_returns = np.random.randn(n_simulations) * sigma + mu
# 计算 VaR
var = -np.percentile(simulated_returns, (1 - confidence) * 100)
return var
# 示例
var_mc = var_monte_carlo(returns, confidence=0.95, n_simulations=10000)
print(f"蒙特卡洛 VaR(95%) = {var_mc:.4f} = {var_mc*100:.2f}%")
优点:
- 灵活,可处理复杂分布
- 可加入更多假设(如波动率聚集)
缺点:
- 计算量大
- 结果依赖模拟次数
三、CVaR 计算:更极端的�风险度量
CVaR(条件风险价值):在损失超过 VaR 的情况下,平均会亏多少。
def cvar(returns, confidence=0.95):
"""
计算 CVaR(条件风险价值)
参数:
returns: 历史收益率序列
confidence: 置信水平
返回:
CVaR 值
"""
var_value = var_historical(returns, confidence)
# 找出所有超过 VaR 阈值的损失
tail_losses = returns[returns <= -var_value]
# 计算尾部平均损失
if len(tail_losses) == 0:
return var_value # 没有尾部数据时返回 VaR
cvar_value = -np.mean(tail_losses)
return cvar_value
# 示例
cvar_95 = cvar(returns, confidence=0.95)
print(f"CVaR(95%) = {cvar_95:.4f} = {cvar_95*100:.2f}%")
为什么 CVaR 更重要?
VaR 只告诉你"95% 情况下最多亏多少",但不告诉你那 5% 极端情况有多惨。CVaR 填补了这个空白。
四、实战:用真实股票数据回测
4.1 数据准备
import yfinance as yf
# 下载贵州茅台(600519.SS)历史数据
stock = yf.download('600519.SS', start='2023-01-01', end='2026-03-19')
returns = stock['Close'].pct_change().dropna()
print(f"数据量:{len(returns)} 天")
print(f"年化收益率:{returns.mean() * 252:.2%}")
print(f"年化波动率:{returns.std() * np.sqrt(252):.2%}")
4.2 三种方法对比
# 计算 VaR
var_hist = var_historical(returns, 0.95)
var_param = var_parametric(returns, 0.95)
var_mc = var_monte_carlo(returns, 0.95)
cvar_value = cvar(returns, 0.95)
# 对比表格
comparison = pd.DataFrame({
'方法': ['历史模拟法', '参数法', '蒙特卡洛法', 'CVaR'],
'VaR(95%)': [var_hist, var_param, var_mc, cvar_value],
'含义': ['历史分位数', '正态分布假设', '随机模拟', '尾部平均损失']
})
print(comparison.to_string(index=False))
典型输出:
方法 VaR(95%) 含义
历史模拟法 0.0234 历史分位数
参数法 0.0198 正态分布假设
蒙特卡洛法 0.0241 随机模拟
CVaR 0.0312 尾部平均损失
关键洞察:
- 参数法 VaR 最低(因为假设正态分布,低估极端风险)
- 历史模拟法和蒙特卡洛法接近
- CVaR 显著高于 VaR,说明极端情况下的损失会更大
五、如何用 VaR/CVaR 优化仓位管理?
5.1 基于 VaR 的仓位控制
def position_size_by_var(portfolio_value, var_95, max_loss_ratio=0.02):
"""
根据 VaR 计算合理仓位
参数:
portfolio_value: 总资金
var_95: 95% 置信度下的 VaR
max_loss_ratio: 最大可接受亏损比例(如 2%)
返回:
建议仓位(资金量)
"""
# 最大可承受损失金额
max_loss = portfolio_value * max_loss_ratio
# 根据 VaR 反推仓位
# 如果 VaR=3%,最大可承受损失 2%,则仓位 = 2%/3% = 67%
position_ratio = max_loss_ratio / var_95
return portfolio_value * position_ratio
# 示例:100 万资金,VaR=2.34%,最大可接受日亏损 2%
portfolio_value = 1_000_000
var_95 = 0.0234
position = position_size_by_var(portfolio_value, var_95)
print(f"建议仓位:{position/1_000_000:.1f}万 ({position/portfolio_value:.1%})")
5.2 动态调仓策略
# 滚动计算 VaR,动态调整仓位
rolling_var = returns.rolling(60).apply(lambda x: var_historical(x, 0.95))
rolling_var = rolling_var.shift(1) # 用昨天的 VaR 决定今天的仓位
# 假设每天根据 VaR 调整仓位
daily_position = 0.02 / rolling_var # 目标:日亏损不超过 2%
daily_position = daily_position.clip(0, 1) # 仓位限制在 0-100%
六、三种方法对比总结
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 历史模拟法 | 简单直观,无需假设 | 依赖历史数据质量 | 日常风控、快速估算 |
| 参数法 | 计算最快,有统计解释 | 假设正态分布,低估极端风险 | 初步筛选、组合优化 |
| 蒙特卡洛法 | 灵活,可处理复杂分布 | 计算慢,依赖模拟次数 | 压力测试、精细风控 |
| CVaR | 考虑尾部极端风险 | 计算复杂 | 极端风险对冲 |
我的建议:
- 日常监控用历史模拟法(快速)
- 组合优化用参数法(数学性质好)
- 压力测试用蒙特卡洛 + CVaR(全面)
写在最后
2026 年的量化交易,风控能力决定生存能力。
VaR 和 CVaR 不是万能药,但它们提供了更科学的风险视角:
- 不再依赖"后视镜"(最大回撤)
- 用概率思维思考风险
- 在极端行情前有更多准备
最后留个互动问题:你的量化策略用什么指标做风控?欢迎在评论区分享你的经验,我会挑选 3 位读者送出《量化风控实战手册》。
参考资料:
- Jorion, P. (2006). Value at Risk: The New Benchmark for Managing Financial Risk
- CSDN《Python 量化投资实践:基于蒙特卡洛模拟的投资组合风险建模》
- 各大量化平台文档
免责声明: 本文仅为技术分享,不构成投资建议。量化交易存在亏损风险,请谨慎决策。文中代码仅供学习参考,实盘使用需自行验证。
原创声明:本文为原创内容,转载请联系作者授权。