问题描述
解决方案
这是一个基于**目标波动率(Target Volatility)**的动态仓位管理策略。
策略逻辑
该策略的核心思想是:当市场波动加剧时降低仓位以控制风险,当市场波动平缓时增加仓位以获取收益。
- 标的:以沪深300 ETF(510300.XSHG)为例。
- 波动率计算:使用过去 N 天(如20天)的日收益率标准差并年化。
- 仓位调整公式:
$$ \text{目标仓位} = \frac{\text{预设目标波动率}}{\text{当前实际波动率}} $$ - 风控限制:最大仓位限制为 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)
代码关键点解析
-
数据获取 (
attribute_history):- 我们获取了
g.lookback_window + 1天的数据。这是因为计算收益率会损失一个数据点(第一天没有前一天的价格来计算涨跌幅)。
- 我们获取了
-
波动率计算:
daily_returns.std()计算的是日波动率。- 乘以
np.sqrt(252)是为了将其转换为年化波动率,以便与我们设定的g.target_volatility(0.15,即15%) 进行比较。
-
仓位计算逻辑:
- 假设目标波动率是 15%。
- 如果当前市场很平稳,实际波动率只有 7.5%,那么
15% / 7.5% = 2.0。理论上应该上2倍杠杆,但代码中限制了target_weight <= 1.0,所以会满仓。 - 如果当前市场很动荡(如股灾期间),实际波动率达到 30%,那么
15% / 30% = 0.5。策略会自动减仓到 50%,持有更多现金避险。
-
交易执行 (
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天大约是一个月的交易日,是一个常用的短期趋势参数。窗口越短,对市场变化反应越快,但交易频率和滑点成本越高;窗口越长,信号越稳定,但对风险的反应会滞后。