🚀 AI 一键生成 joinquant 策略代码

JoinQuant 高级风控:如何根据投资组合历史波动率动态调整仓位?

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

问题描述

请编写一个能根据整体投资组合的波动率来动态调整仓位大小的策略。

解决方案

这是一个基于**目标波动率(Target Volatility)**的动态仓位管理策略。

策略逻辑

该策略的核心思想是:当市场波动加剧时降低仓位以控制风险,当市场波动平缓时增加仓位以获取收益。

  1. 标的:以沪深300 ETF(510300.XSHG)为例。
  2. 波动率计算:使用过去 N 天(如20天)的日收益率标准差并年化。
  3. 仓位调整公式
    $$ \text{目标仓位} = \frac{\text{预设目标波动率}}{\text{当前实际波动率}} $$
  4. 风控限制:最大仓位限制为 100%(不加杠杆)。

策略代码

# -*- 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')
    
    # 设定股票交易手续费:买入万三,卖出万三加千一印花税,最低5元
    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5), type='stock')
    
    # --- 策略参数设置 ---
    # 操作标的:沪深300ETF
    g.security = '510300.XSHG'
    
    # 目标年化波动率 (例如 15%)
    g.target_volatility = 0.15
    
    # 计算波动率的回看窗口 (例如 20个交易日)
    g.lookback_window = 20
    
    # 每年交易日数量估算,用于年化计算
    g.annual_days = 252
    
    # 设定定时运行:每天收盘前10分钟运行
    run_daily(adjust_position, time='14:50')

def adjust_position(context):
    """
    每日调仓逻辑
    """
    security = g.security
    
    # 1. 获取历史收盘价数据
    # 我们需要 lookback_window + 1 天的价格来计算 lookback_window 个收益率
    hist_data = attribute_history(security, g.lookback_window + 1, '1d', ['close'])
    
    # 如果数据不足(例如刚上市),则不操作
    if len(hist_data) < g.lookback_window + 1:
        return

    # 2. 计算日收益率
    # pct_change() 计算 (今天-昨天)/昨天
    daily_returns = hist_data['close'].pct_change().dropna()
    
    # 3. 计算当前实现的年化波动率
    # 标准差 * sqrt(252)
    current_volatility = daily_returns.std() * np.sqrt(g.annual_days)
    
    # 防止波动率为0导致除零错误(极少见,但需防御)
    if current_volatility == 0:
        log.warn("当前波动率为0,跳过本次调仓")
        return

    # 4. 计算目标仓位比例
    # 公式:目标仓位 = 目标波动率 / 当前波动率
    target_weight = g.target_volatility / current_volatility
    
    # 5. 仓位限制
    # 如果计算出的仓位大于1(即需要加杠杆),这里限制为100%仓位(不融资)
    # 如果你希望策略更保守,可以设置更低的上限,比如 0.8
    if target_weight > 1.0:
        target_weight = 1.0
    elif target_weight < 0.0:
        target_weight = 0.0
        
    # 6. 执行交易
    # 获取当前账户总资产
    total_value = context.portfolio.total_value
    # 计算目标持仓价值
    target_value = total_value * target_weight
    
    # 打印日志方便回测观察
    log.info("日期: %s, 当前年化波动率: %.2f%%, 目标仓位: %.2f%%" % (
        context.current_dt.date(), 
        current_volatility * 100, 
        target_weight * 100
    ))
    
    # 调整标的至目标价值
    order_target_value(security, target_value)

代码关键点解析

  1. 数据获取 (attribute_history):

    • 我们获取了 g.lookback_window + 1 天的数据。这是因为计算收益率会损失一个数据点(第一天没有前一天的价格来计算涨跌幅)。
  2. 波动率计算:

    • daily_returns.std() 计算的是日波动率。
    • 乘以 np.sqrt(252) 是为了将其转换为年化波动率,以便与我们设定的 g.target_volatility (0.15,即15%) 进行比较。
  3. 仓位计算逻辑:

    • 假设目标波动率是 15%。
    • 如果当前市场很平稳,实际波动率只有 7.5%,那么 15% / 7.5% = 2.0。理论上应该上2倍杠杆,但代码中限制了 target_weight <= 1.0,所以会满仓。
    • 如果当前市场很动荡(如股灾期间),实际波动率达到 30%,那么 15% / 30% = 0.5。策略会自动减仓到 50%,持有更多现金避险。
  4. 交易执行 (order_target_value):

    • 这个函数非常方便,它会自动计算需要买入还是卖出多少金额,直接将持仓调整到我们计算出的 target_value

常见问题 (Q&A)

Q: 这个策略适合什么市场环境?
A: 适合长牛慢牛或震荡市。在暴跌来临初期,由于波动率迅速放大,策略会快速减仓,起到止损作用。但在单边暴涨行情中,如果波动率也随之放大,策略可能会过早减仓,导致跑输基准。

Q: 如何引入杠杆?
A: 如果你的账户支持融资融券,可以将代码中的 if target_weight > 1.0: target_weight = 1.0 这一行修改或删除,并确保 set_subportfolios 设置了融资融券账户类型。

Q: 为什么选择 20 天作为回看窗口?
A: 20天大约是一个月的交易日,是一个常用的短期趋势参数。窗口越短,对市场变化反应越快,但交易频率和滑点成本越高;窗口越长,信号越稳定,但对风险的反应会滞后。