🚀 AI 一键生成 joinquant 策略代码

JoinQuant 风险控制:历史模拟法计算投资组合 VaR(在险价值)

joinquant | 发布时间: 2025/12/19
以下内容由 EasyQuant 生成。

问题描述

怎样用历史模拟法计算投资组合的 VaR?

解决方案

历史模拟法计算投资组合 VaR

历史模拟法(Historical Simulation)是一种计算风险价值(Value at Risk, VaR)的非参数方法。它的核心假设是**“历史会重演”**。

该方法不假设资产收益率服从正态分布,而是利用投资组合中各项资产的当前权重,配合这些资产在过去一段时间的历史收益率,计算出投资组合在历史每一天的假设收益率,从而构建出一个经验分布,最后根据置信度选取对应的分位数作为 VaR。

以下是在 JoinQuant 聚宽平台上实现该算法的详细步骤和完整代码。

算法逻辑步骤

  1. 获取当前持仓:确定当前投资组合中持有的标的及其市值权重。
  2. 获取历史数据:获取这些标的在过去 N 天(例如 250 天)的历史收盘价。
  3. 计算历史收益率:计算每个标的在历史区间内的日收益率。
  4. 构建组合模拟收益:假设当前持仓权重在历史上保持不变,计算组合在历史每一天的模拟收益率。
    • 公式:$R_{p,t} = \sum (w_i \times R_{i,t})$
    • 其中 $w_i$ 是资产 $i$ 的当前权重,$R_{i,t}$ 是资产 $i$ 在历史第 $t$ 天的收益率。
  5. 计算 VaR:将组合模拟收益率从小到大排序,根据置信度(如 95% 或 99%)找到对应的分位数(Quantile),该分位数即为 VaR(收益率形式),再乘以当前总资产即可得到金额形式的 VaR。

JoinQuant 策略代码实现

以下是一个完整的策略示例。该策略会在每天收盘后计算当前持仓在 95% 置信度下的单日 VaR。

# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd

def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 设定日志级别
    log.set_level('order', 'error')
    
    # 设定VaR计算参数
    g.lookback_days = 250  # 回溯历史天数(约一年)
    g.confidence_level = 0.95 # 置信度 95%
    
    # 运行函数(为了演示,我们在开盘买入一些股票,收盘计算VaR)
    run_daily(market_open, time='09:30')
    run_daily(calculate_portfolio_var, time='15:30')

def market_open(context):
    # 示例:构建一个简单的多股票组合
    # 如果没有持仓,则买入
    if len(context.portfolio.positions) == 0:
        # 买入平安银行、万科A、贵州茅台作为测试组合
        stocks = ['000001.XSHE', '000002.XSHE', '600519.XSHG']
        # 资金三等分买入
        cash_per_stock = context.portfolio.available_cash / len(stocks)
        for stock in stocks:
            order_value(stock, cash_per_stock)

def calculate_portfolio_var(context):
    """
    使用历史模拟法计算投资组合的 VaR
    """
    # 1. 获取当前持仓标的及市值
    positions = context.portfolio.positions
    if not positions:
        log.info("当前无持仓,VaR 为 0")
        return

    security_list = list(positions.keys())
    total_value = context.portfolio.total_value
    
    # 计算当前各资产的权重
    # weights 字典: {stock_code: weight}
    weights = {}
    for stock in security_list:
        # 权重 = 个股持仓价值 / 组合总价值
        weights[stock] = positions[stock].value / total_value
    
    # 2. 获取历史价格数据
    # 多取1天数据以计算收益率,因为pct_change会损失第一天数据
    hist_data = history(g.lookback_days + 1, '1d', 'close', security_list)
    
    # 3. 计算单资产历史日收益率
    # df_returns 的列是股票代码,行是日期
    df_returns = hist_data.pct_change().dropna()
    
    if df_returns.empty:
        log.warn("历史数据不足,无法计算 VaR")
        return

    # 4. 计算组合的历史模拟收益率序列
    # 假设当前权重在历史上一直保持不变
    # 公式:组合日收益 = sum(个股日收益 * 个股当前权重)
    
    # 创建一个全为0的序列来存储组合收益
    portfolio_hist_returns = pd.Series(0.0, index=df_returns.index)
    
    for stock in security_list:
        if stock in df_returns.columns:
            w = weights[stock]
            r = df_returns[stock]
            # 累加加权收益
            portfolio_hist_returns += r * w
            
    # 5. 计算 VaR (分位数法)
    # 如果置信度是 95%,我们需要找左尾 5% 的分位数
    alpha = 1 - g.confidence_level
    
    # 使用 numpy 的 percentile 函数计算分位数
    # portfolio_hist_returns 是一系列收益率,如 [-0.02, 0.01, 0.03 ...]
    # 我们寻找最差的那部分收益率
    var_return = np.percentile(portfolio_hist_returns, alpha * 100)
    
    # 计算金额 VaR
    # VaR 通常表示为正数,代表最大潜在损失金额
    var_value = abs(var_return) * total_value
    
    # 打印结果
    log.info("=" * 30)
    log.info("【VaR计算报告 - 历史模拟法】")
    log.info("回溯天数: %d 天" % g.lookback_days)
    log.info("置信度: %.1f%%" % (g.confidence_level * 100))
    log.info("当前组合总市值: %.2f" % total_value)
    log.info("VaR (收益率形式): %.4f%%" % (var_return * 100))
    log.info("VaR (金额形式): %.2f 元" % var_value)
    log.info("这意味着在正常的市场条件下,")
    log.info("明天该组合损失超过 %.2f 元的概率仅为 %.1f%%" % (var_value, alpha * 100))
    log.info("=" * 30)
    
    # 也可以使用 record 在回测图中画出 VaR 曲线
    record(VaR_Value=var_value)

代码关键点解析

  1. history 函数的使用

    • 我们使用 history(g.lookback_days + 1, '1d', 'close', security_list) 获取数据。
    • 注意:必须多取 1 天数据,因为计算收益率(pct_change)时,第一天的数据会变成 NaN 并被丢弃。
  2. 权重的处理

    • 历史模拟法的核心在于**“当前权重,历史收益”**。我们不是去回测历史上每一天真实的持仓权重,而是假设“如果我们过去一年一直持有现在的这个组合,每天会赚赔多少钱”。
    • 代码中 weights[stock] = positions[stock].value / total_value 计算了当前的静态权重。
  3. 组合收益率计算

    • 利用 Pandas 的向量化运算特性,portfolio_hist_returns += r * w 可以高效地计算出组合在历史每一天的假设收益率。
  4. 分位数计算 (np.percentile)

    • alpha = 1 - 0.95 = 0.05
    • np.percentile(data, 5) 会找到数据中从小到大排序后位于 5% 位置的数值。因为是损失,这个值通常是负数(例如 -0.03 表示亏损 3%)。
    • 最终报告 VaR 时,通常取绝对值表示“最大潜在损失金额”。

Q&A

Q1: 为什么使用历史模拟法而不是方差-协方差法?
A1: 历史模拟法不需要假设收益率服从正态分布,能够更好地捕捉市场中的“肥尾”现象(即极端行情发生的概率比正态分布预测的要高)。此外,它不需要计算复杂的协方差矩阵,对于包含非线性资产(如期权)或非正态分布资产的组合更有效。

Q2: 回溯天数(Lookback Window)设置多少合适?
A2: 通常设置为 250 天(约一年)或 500 天(约两年)。天数太短(如 30 天)会导致样本量不足,分位数估计不准确;天数太长(如 10 年)则可能包含过时的市场结构信息,无法反映当前的市场波动特征。

Q3: 如何处理停牌股票?
A3: 在上述代码中,history 函数获取停牌股票的价格通常是前一交易日的收盘价,因此计算出的收益率为 0。这在一定程度上低估了风险(因为复牌后可能补跌)。更严谨的做法是使用行业指数收益率替代,或者在数据预处理阶段剔除停牌过久的标的。

Q4: 这个代码可以在回测和模拟交易中使用吗?
A4: 是的。该代码使用了 run_daily 和标准的 JoinQuant API,既可以在回测中验证风险指标的有效性,也可以在模拟交易中用于每日风险监控。