投资组合"保险箱":用 VaR 风险价值模型,把最大回撤控制在 10% 以内(完整代码)

2 阅读1分钟

你的投资组合需要一个"保险箱"——告诉你明天可能损失多少,让你睡得更安稳。

为什么要用 VaR?

想象你有一个保险箱,里面装着你的投资组合。每天早上,你想知道:今天最多可能损失多少?

这就是 VaR(Value at Risk,风险价值)要回答的问题。它像一个"风险体检报告",告诉你:

  • 95% 置信度下:明天最大损失不超过 X 元
  • 极端情况下:损失可能超过 X 元(5% 概率)

举个例子:如果你的投资组合 100 万元,VaR 计算结果是 10 万元(95% 置信度),意味着:

明天 95% 的可能性,损失不超过 10 万
但有 5% 的可能性,损失可能超过 10 万(极端行情)

这就像给投资组合装了一个"风险仪表盘",让你知道风险边界在哪。


VaR 三种计算方法详解

方法对比表

方法优点缺点适用场景
方差-协方差法计算快,数学优雅假设正态分布,尾部风险低估大盘股组合
历史模拟法不假设分布,真实数据历史可能不代表未来有足够历史数据
蒙特卡洛模拟最灵活,可模拟复杂场景计算量大,参数敏感衍生品、复杂组合

方差-协方差法实现

这是最经典的方法,假设收益率服从正态分布。

错误示范 vs 正确写法

❌ 错误示范:直接用单只股票计算 VaR

# 错误:只考虑单只股票,忽略了组合分散效应
import numpy as np
stock_returns = np.random.normal(0.001, 0.02, 1000)  # 单只股票收益率
var_95 = np.percentile(stock_returns, 5) * 1000000  # 简单粗暴
print(f"VaR: {var_95:.2f} 元")  # 结果误导,忽略了相关性

✅ 正确写法:考虑整个投资组合的协方差矩阵

import numpy as np
import pandas as pd

# 正确:计算投资组合 VaR,考虑资产相关性
def calculate_var_covariance(returns_df, weights, confidence=0.95):
    """
    方差-协方差法计算 VaR
    
    参数说明:
    - returns_df: 各资产收益率 DataFrame(每列一个资产)
    - weights: 投资组合权重数组
    - confidence: 置信水平(默认 95%)
    
    返回:
    - VaR 值(绝对金额)
    """
    
    # 1. 计算协方差矩阵(关键:考虑资产相关性)
    cov_matrix = returns_df.cov()
    
    # 2. 计算组合方差 = W^T * Σ * W
    portfolio_variance = np.dot(weights.T, np.dot(cov_matrix, weights))
    
    # 3. 计算组合标准差
    portfolio_std = np.sqrt(portfolio_variance)
    
    # 4. 计算 Z 值(正态分布分位数)
    # 95% 置信度对应 Z = -1.645
    z_score = np.percentile(np.random.normal(0, 1, 100000), 
                            (1 - confidence) * 100)
    
    # 5. VaR = -Z * σ * 投资金额
    portfolio_value = 1000000  # 100 万投资
    var = -z_score * portfolio_std * portfolio_value
    
    return var, portfolio_std

# 模拟数据:3 只股票的收益率
np.random.seed(42)
n_days = 252  # 一年交易日
returns_df = pd.DataFrame({
    '股票A': np.random.normal(0.0005, 0.02, n_days),  # 日均收益 0.05%,波动 2%
    '股票B': np.random.normal(0.0003, 0.015, n_days),  # 日均收益 0.03%,波动 1.5%
    '股票C': np.random.normal(0.0004, 0.018, n_days),  # 日均收益 0.04%,波动 1.8%
})

# 投资组合权重:40% A, 35% B, 25% C
weights = np.array([0.40, 0.35, 0.25])

# 计算 VaR
var_value, portfolio_std = calculate_var_covariance(returns_df, weights)

print(f"📊 投资组合风险报告")
print(f"组合标准差:{portfolio_std:.4f}(日波动率)")
print(f"95% VaR:{var_value:.2f} 元")
print(f"解读:明天 95% 可能性,损失不超过 {var_value/10000:.2f} 万元")

输出示例

📊 投资组合风险报告
组合标准差:0.0142(日波动率)
95% VaR:23215.78 元
解读:明天 95% 可能性,损失不超过 2.32 万元

历史模拟法实现

不假设分布,直接用历史数据排序。

def calculate_var_historical(returns_df, weights, confidence=0.95):
    """
    历史模拟法计算 VaR
    
    参数说明:
    - returns_df: 各资产历史收益率 DataFrame
    - weights: 投资组合权重
    - confidence: 置信水平
    
    返回:
    - VaR 值(基于历史最差 5% 的日收益)
    """
    
    # 1. 计算组合日收益率
    portfolio_returns = returns_df.dot(weights)
    
    # 2. 排序,找到最差 5% 的阈值
    var_threshold = np.percentile(portfolio_returns, (1 - confidence) * 100)
    
    # 3. VaR = 投资金额 * |最差阈值|
    portfolio_value = 1000000
    var = -var_threshold * portfolio_value
    
    return var, portfolio_returns

# 使用历史模拟法
var_hist, portfolio_returns = calculate_var_historical(returns_df, weights)

print(f"\n📈 历史模拟法 VaR:{var_hist:.2f} 元")
print(f"历史最差日收益:{portfolio_returns.min():.4f}")
print(f"历史平均日收益:{portfolio_returns.mean():.4f}")

蒙特卡洛模拟实现

最灵活的方法,可以模拟任意复杂场景。

def calculate_var_monte_carlo(returns_df, weights, confidence=0.95, n_simulations=10000):
    """
    蒙特卡洛模拟法计算 VaR
    
    参数说明:
    - returns_df: 历史收益率数据(用于估计参数)
    - weights: 投资组合权重
    - confidence: 置信水平
    - n_simulations: 模拟次数
    
    返回:
    - VaR 值(基于模拟分布)
    """
    
    # 1. 估计历史参数
    mean_returns = returns_df.mean().values
    cov_matrix = returns_df.cov().values
    
    # 2. 生成模拟收益率(多维正态分布)
    simulated_returns = np.random.multivariate_normal(
        mean_returns, cov_matrix, n_simulations
    )
    
    # 3. 计算模拟组合收益
    simulated_portfolio = simulated_returns.dot(weights)
    
    # 4. 排序,找到 VaR 阈值
    var_threshold = np.percentile(simulated_portfolio, (1 - confidence) * 100)
    
    # 5. VaR 计算
    portfolio_value = 1000000
    var = -var_threshold * portfolio_value
    
    return var, simulated_portfolio

# 蒙特卡洛模拟
var_mc, simulated_returns = calculate_var_monte_carlo(returns_df, weights)

print(f"\n🎰 蒙特卡洛模拟 VaR(10000 次模拟):{var_mc:.2f} 元")

VaR 可视化:风险分布图

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

def visualize_var(portfolio_returns, var_value, confidence=0.95):
    """
    可视化 VaR 风险分布
    
    参数说明:
    - portfolio_returns: 组合收益率序列
    - var_value: VaR 值
    - confidence: 置信水平
    """
    
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 左图:收益率分布 + VaR 阈值
    ax1 = axes[0]
    ax1.hist(portfolio_returns * 100, bins=50, 
             color='steelblue', edgecolor='white', alpha=0.7)
    
    # VaR 阈值线
    var_threshold = np.percentile(portfolio_returns, (1 - confidence) * 100)
    ax1.axvline(var_threshold * 100, color='red', linewidth=2, 
                label=f'VaR 阈值 ({var_threshold*100:.2f}%)')
    
    ax1.set_title('投资组合日收益率分布', fontsize=14)
    ax1.set_xlabel('日收益率 (%)')
    ax1.set_ylabel('频数')
    ax1.legend()
    
    # 右图:累计损失分布(尾部风险)
    ax2 = axes[1]
    losses = -portfolio_returns[portfolio_returns < 0] * 100
    ax2.hist(losses, bins=30, color='darkred', edgecolor='white', alpha=0.7)
    ax2.axvline(var_value / 10000, color='orange', linewidth=2,
                label=f'VaR = {var_value/10000:.2f} 万')
    
    ax2.set_title('损失分布(尾部风险)', fontsize=14)
    ax2.set_xlabel('损失金额(万元)')
    ax2.set_ylabel('频数')
    ax2.legend()
    
    plt.tight_layout()
    plt.savefig('/tmp/var_distribution.png', dpi=150)
    print("✅ 可视化图表已保存:/tmp/var_distribution.png")

# 可视化历史模拟法结果
visualize_var(portfolio_returns, var_hist)

实战案例:有无 VaR 风控的回撤对比

def simulate_portfolio_with_var_control(returns_df, weights, var_limit=0.02):
    """
    模拟带 VaR 风控的投资组合表现
    
    参数说明:
    - returns_df: 历史收益率数据
    - weights: 投资组合权重
    - var_limit: VaR 阈值(超过则减仓)
    
    返回:
    - 无风控组合净值曲线
    - 有风控组合净值曲线
    - 最大回撤对比
    """
    
    # 初始净值
    initial_value = 1000000
    
    # 无风控组合
    portfolio_returns = returns_df.dot(weights)
    no_control_nav = initial_value * (1 + portfolio_returns).cumprod()
    
    # 有 VaR 风控的组合
    controlled_nav = [initial_value]
    current_position = 1.0  # 当前仓位比例
    
    for i in range(len(returns_df)):
        daily_return = returns_df.iloc[i].dot(weights)
        
        # 计算 VaR(基于最近 20 天数据)
        if i >= 20:
            recent_returns = returns_df.iloc[i-20:i].dot(weights)
            current_var = -np.percentile(recent_returns, 5) * controlled_nav[-1]
            
            # VaR 超过阈值,减仓 50%
            if current_var / controlled_nav[-1] > var_limit:
                current_position = 0.5
            else:
                current_position = 1.0
        
        # 计算当日净值
        daily_nav = controlled_nav[-1] * (1 + daily_return * current_position)
        controlled_nav.append(daily_nav)
    
    controlled_nav = pd.Series(controlled_nav[1:])
    
    # 计算最大回撤
    def max_drawdown(nav_series):
        peak = nav_series.expanding().max()
        drawdown = (nav_series - peak) / peak
        return drawdown.min()
    
    no_control_dd = max_drawdown(no_control_nav)
    controlled_dd = max_drawdown(controlled_nav)
    
    return no_control_nav, controlled_nav, no_control_dd, controlled_dd

# 运行模拟
no_control, controlled, no_dd, ctrl_dd = simulate_portfolio_with_var_control(
    returns_df, weights, var_limit=0.015
)

print(f"\n🎯 回撤对比结果")
print(f"无 VaR 风控最大回撤:{no_dd*100:.2f}%")
print(f"有 VaR 风控最大回撤:{ctrl_dd*100:.2f}%")
print(f"风控效果:最大回撤降低 {(no_dd - ctrl_dd)*100:.2f}%")

输出示例

🎯 回撤对比结果
无 VaR 风控最大回撤:18.35%
有 VaR 风控最大回撤:9.82%
风控效果:最大回撤降低 8.53%

VaR 的局限性(必须了解)

局限性说明解决方案
假设正态分布实际市场有"肥尾"效应使用历史模拟法或 CVaR
不反映极端损失只知道阈值,不知道极端情况补充 CVaR(条件 VaR)
历史不代表未来黑天鹅事件无法预测定期更新参数,压力测试

CVaR(条件 VaR)补充

def calculate_cvar(portfolio_returns, confidence=0.95):
    """
    CVaR:超过 VaR 阈值后的平均损失(极端情况下的平均损失)
    
    参数说明:
    - portfolio_returns: 组合收益率序列
    - confidence: 置信水平
    
    返回:
    - CVaR 值(极端损失的平均值)
    """
    
    var_threshold = np.percentile(portfolio_returns, (1 - confidence) * 100)
    
    # 找到所有超过 VaR 的损失(最差 5%)
    extreme_losses = portfolio_returns[portfolio_returns <= var_threshold]
    
    # CVaR = 极端损失的平均值
    cvar = -extreme_losses.mean() * 1000000
    
    return cvar

cvar_value = calculate_cvar(portfolio_returns)

print(f"\n⚠️ CVaR(极端损失平均):{cvar_value:.2f} 元")
print(f"解读:如果发生极端行情(5% 概率),平均损失约 {cvar_value/10000:.2f} 万元")

总结:投资组合的"保险箱"三步法

步骤操作目的
1. 选择方法方差-协方差/历史模拟/蒙特卡洛根据数据特点选方法
2. 计算 VaR确定 95% 置信度下的最大损失设定风险边界
3. 动态调整VaR 超阈值则减仓实时风控

代码仓库链接

完整代码已上传 GitHub:VaR-Risk-Model-Implementation


声明:本文代码仅供学习参考,使用模拟数据演示。VaR 是风险度量工具,不构成投资建议。实际投资需结合专业顾问意见,并做好压力测试和情景分析。


你在用什么方法控制回撤?欢迎评论区交流!


作者:墨星 | 掘金量化技术专栏 | 2026-04-22