🚀 AI 一键生成 joinquant 策略代码

如何在 JoinQuant 策略中计算投资组合的在险价值(VaR)?

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

问题描述

请展示如何在策略中计算投资组合的 VaR

解决方案

在 JoinQuant 聚宽平台上计算投资组合的 VaR (Value at Risk,在险价值),最常用且适合回测环境的方法是历史模拟法 (Historical Simulation)

这种方法的核心逻辑是:假设当前持仓的权重在过去一段时间内保持不变,计算该组合在历史上的每日收益率分布,然后根据置信度(如 95% 或 99%)找到对应的分位数(Quantile),从而估算未来一日的最大潜在损失。

核心步骤

  1. 获取持仓信息:从 context.portfolio.positions 获取当前持有的标的和对应的市值权重。
  2. 获取历史行情:使用 historyattribute_history 获取持仓标的过去 N 天(如 250 天)的收盘价。
  3. 计算历史收益率:计算每只股票的日收益率。
  4. 构建组合历史收益率:利用当前权重和个股历史收益率,合成“假设当前组合在过去表现”的收益率序列。
  5. 计算 VaR:使用 numpy.percentile 计算指定置信度下的分位数,并乘以当前总资产。

策略代码示例

以下是一个完整的策略代码,包含了一个简单的买入逻辑(用于建立仓位)和一个专门计算 VaR 的函数 calculate_portfolio_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)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 定义全局变量:VaR计算的历史窗口长度(例如过去250个交易日)
    g.lookback_days = 250
    # 定义全局变量:置信度(例如95%)
    g.confidence_level = 0.95
    
    # 每天收盘后计算当天的VaR
    run_daily(calculate_portfolio_var, 'after_trading_end')
    # 每天开盘时进行简单的交易(为了演示持仓)
    run_daily(trade_func, 'open')

def trade_func(context):
    # 简单的示例交易逻辑:买入几只股票构建组合
    target_stocks = ['000001.XSHE', '600000.XSHG', '000002.XSHE']
    for stock in target_stocks:
        # 均仓买入
        order_target_value(stock, context.portfolio.total_value / len(target_stocks))

def calculate_portfolio_var(context):
    """
    计算投资组合的 VaR (历史模拟法)
    """
    # 1. 获取当前持仓的标的列表
    positions = context.portfolio.positions
    security_list = list(positions.keys())
    
    # 如果没有持仓,VaR 为 0
    if len(security_list) == 0:
        log.info("当前无持仓,VaR 为 0")
        return

    # 2. 获取持仓标的的历史收盘价数据
    # 获取过去 g.lookback_days + 1 天的数据,以计算 g.lookback_days 个收益率
    hist_data = history(g.lookback_days + 1, '1d', 'close', security_list, df=True)
    
    # 3. 计算个股的日收益率
    # pct_change() 计算 (今天-昨天)/昨天,dropna() 去除第一行空值
    stock_returns = hist_data.pct_change().dropna()
    
    # 4. 计算当前各标的的权重
    # 注意:这里假设计算的是“当前持仓”在“历史市场环境”下的风险
    total_value = context.portfolio.total_value
    weights = []
    valid_securities = []
    
    for stock in security_list:
        # 确保该股票在历史数据中有收益率记录(防止停牌或新股导致数据缺失)
        if stock in stock_returns.columns:
            # 权重 = 个股持仓市值 / 总资产
            weight = positions[stock].value / total_value
            weights.append(weight)
            valid_securities.append(stock)
    
    # 转换为 numpy 数组方便计算
    weights = np.array(weights)
    
    # 仅保留有效股票的收益率数据
    stock_returns = stock_returns[valid_securities]
    
    # 5. 计算组合的历史模拟收益率序列
    # 矩阵乘法:(T x N) dot (N x 1) = (T x 1)
    # 得到的是假设每天都持有当前权重的组合,在过去每一天的收益率
    portfolio_hist_returns = stock_returns.dot(weights)
    
    # 6. 计算 VaR (百分比形式)
    # 如果置信度是 95%,我们需要找左尾 5% 的分位数
    alpha = 1 - g.confidence_level
    var_percent = np.percentile(portfolio_hist_returns, alpha * 100)
    
    # 7. 计算 VaR (金额形式)
    # VaR 通常表示为正数,代表潜在损失金额
    var_value = abs(var_percent * total_value)
    
    # 打印和记录结果
    log.info("=" * 30)
    log.info(f"日期: {context.previous_date}")
    log.info(f"置信度: {g.confidence_level*100}%")
    log.info(f"历史窗口: {g.lookback_days} 天")
    log.info(f"组合 VaR (百分比): {var_percent:.4%}")
    log.info(f"组合 VaR (金额): {var_value:.2f} 元")
    log.info("=" * 30)
    
    # 可以在回测图表中画出 VaR 曲线
    record(VaR=var_value)

代码逻辑详解

  1. 数据获取 (history):
    我们使用 history 函数获取持仓股票过去 N 天的收盘价。这里需要获取 g.lookback_days + 1 天的数据,因为计算收益率会损失一天的数据。

  2. 权重计算:
    VaR 的核心在于评估当前持仓面临的风险。因此,我们使用 context.portfolio.positions[stock].value / context.portfolio.total_value 来计算当前每只股票占总资产的比例。

  3. 矩阵运算 (dot):
    stock_returns 是一个 DataFrame,每一列是一只股票的历史收益率序列。weights 是一个向量。通过点积运算 (dot),我们可以快速算出组合在历史上每一天的假设收益率。

    • 公式:$R_{portfolio, t} = \sum_{i=1}^{N} (w_i \times R_{i, t})$
  4. 分位数计算 (np.percentile):
    历史模拟法的关键在于找到收益率分布的左尾。

    • 对于 95% 的置信度,意味着有 5% 的概率损失会超过 VaR。
    • 因此我们计算历史收益率序列的第 5 百分位数(5th percentile)。
  5. 结果解读:
    如果计算出的 var_percent 是 -0.03 (即 -3%),且当前总资产为 1,000,000 元,则 var_value 为 30,000 元。这意味着在正常的市场波动下,我们有 95% 的把握认为明天的损失不会超过 30,000 元。

Q&A

Q: 为什么使用历史模拟法而不是方差-协方差法?
A: 在量化回测平台中,历史模拟法实现最简单且不需要假设收益率服从正态分布(金融数据通常具有尖峰肥尾特性,不符合正态分布)。方差-协方差法需要计算协方差矩阵,对于持仓较多的组合计算量大且对分布假设敏感。

Q: 如何提高 VaR 计算的准确性?
A:

  1. 增加历史窗口:将 g.lookback_days 从 250 增加到 500 或更多,可以包含更多的极端行情样本。
  2. 使用指数加权:给近期的历史数据更高的权重(EWMA),因为近期的波动率更能反映当前市场状态。
  3. 蒙特卡洛模拟:如果包含期权等非线性资产,历史模拟法可能失效,需要使用蒙特卡洛模拟,但在 JoinQuant 普通策略中计算成本较高。

Q: record(VaR=var_value) 有什么用?
A: record 函数会将计算出的 VaR 数值绘制在回测结果的收益率曲线下方。这样你可以直观地看到随着持仓变化和市场波动,你的风险敞口是如何变化的。