问题描述
解决方案
这是一个基于**目标波动率(Target Volatility)**进行仓位管理的策略示例。
策略逻辑
- 核心思想:当市场波动率(风险)变大时,降低仓位;当市场波动率变小时,增加仓位。
- 计算公式:
- 当前波动率 = 过去 N 天收益率的标准差 $\times \sqrt{252}$(年化)。
- 目标仓位比例 = 目标波动率 / 当前波动率。
- 限制:通常将最大仓位限制在 100%(1.0),防止加杠杆。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数
"""
# 1. 设定交易标的(例如:沪深300ETF)
ContextInfo.stock_code = '510300.SH'
# 2. 设定股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设定资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 4. 策略参数设置
ContextInfo.target_volatility = 0.15 # 目标年化波动率 (例如 15%)
ContextInfo.lookback_window = 20 # 回看窗口 (过去20个交易日)
ContextInfo.max_position = 1.0 # 最大仓位限制 (100%)
print("策略初始化完成:目标波动率={:.2%}, 回看窗口={}天".format(
ContextInfo.target_volatility, ContextInfo.lookback_window))
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前K线索引
index = ContextInfo.barpos
# 确保有足够的历史数据进行计算
# 我们需要 lookback_window + 1 个数据来计算 lookback_window 个收益率
if index < ContextInfo.lookback_window + 1:
return
# 1. 获取历史行情数据 (使用 get_market_data_ex 接口)
# 获取收盘价,数量为回看窗口+1,前复权
market_data = ContextInfo.get_market_data_ex(
['close'],
[ContextInfo.stock_code],
period='1d',
count=ContextInfo.lookback_window + 1,
dividend_type='front',
subscribe=True
)
if ContextInfo.stock_code not in market_data:
return
df = market_data[ContextInfo.stock_code]
# 2. 计算收益率
# pct_change() 计算日收益率
df['returns'] = df['close'].pct_change()
# 去除第一个NaN值
returns_series = df['returns'].dropna()
# 3. 计算当前年化波动率
# std() 计算标准差,乘以 sqrt(252) 进行年化
if len(returns_series) < ContextInfo.lookback_window:
return # 数据不足时不交易
current_volatility = returns_series.std() * np.sqrt(252)
# 防止波动率为0导致除零错误
if current_volatility == 0:
print("当前波动率为0,跳过计算")
return
# 4. 计算目标仓位
# 公式:目标仓位 = 目标波动率 / 当前波动率
target_percent = ContextInfo.target_volatility / current_volatility
# 限制最大仓位不超过设定值 (例如 1.0)
target_percent = min(target_percent, ContextInfo.max_position)
# 5. 执行交易
# 使用 order_target_percent 自动调整仓位到目标比例
# 注意:回测模式下直接生效,实盘模式下需要确保账号设置正确
# 获取当前时间用于日志
timetag = ContextInfo.get_bar_timetag(index)
import datetime
date_str = datetime.datetime.fromtimestamp(timetag / 1000).strftime('%Y-%m-%d')
print(f"日期: {date_str} | 当前波动率: {current_volatility:.2%} | 目标仓位: {target_percent:.2%}")
# 发送交易指令
order_target_percent(ContextInfo.stock_code, target_percent, ContextInfo, ContextInfo.account_id)
代码详解
-
init函数:ContextInfo.target_volatility = 0.15:这是策略的核心锚点。如果市场当前波动率是 30%,为了维持 15% 的风险敞口,策略会将仓位降至 50% (0.15/0.30)。ContextInfo.lookback_window = 20:计算波动率所使用的时间跨度,通常取 20 日(一个月)或 60 日(一季度)。
-
handlebar函数:- 数据获取:使用
get_market_data_ex获取历史收盘价。注意count设置为lookback_window + 1,因为计算收益率会损失一个数据点。 - 波动率计算:
pct_change()计算每日涨跌幅。std()计算标准差。np.sqrt(252)将日波动率转化为年化波动率(假设一年有 252 个交易日)。
- 仓位计算:简单的除法逻辑
Target / Current。加入了min(..., 1.0)确保不会因为波动率极低而产生融资买入(除非您希望加杠杆)。 - 交易执行:
order_target_percent是一个非常方便的函数,它会自动计算当前持仓和目标持仓的差额,并自动发出买入或卖出指令。
- 数据获取:使用
注意事项
- 资金账号:代码中的
'YOUR_ACCOUNT_ID'必须替换为您在 QMT 交易界面看到的实际资金账号。 - 运行频率:此策略通常在日线周期上运行。如果在分钟线上运行,需要调整波动率的年化系数(例如分钟线需乘以 $\sqrt{252 \times 240}$)。
- 交易成本:频繁调整仓位会产生手续费。在实盘中,通常会增加一个阈值(Buffer),例如:只有当目标仓位与当前仓位的偏差超过 5% 时才进行调仓,以减少磨损。
Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更清晰(字典包含 DataFrame),且支持更多参数配置,处理多股数据时更高效。
Q: 如何避免频繁调仓导致的手续费过高?
A: 可以在 order_target_percent 之前加入逻辑判断。例如获取当前持仓比例,计算 abs(目标比例 - 当前比例),只有当差值大于 0.05 (5%) 时才触发下单函数。
Q: 这个策略适合什么品种?
A: 波动率目标策略(Target Volatility)非常适合指数ETF(如沪深300、中证500)或CTA策略。对于个股,由于存在特质性风险(如暴雷),单纯依赖波动率调整仓位可能不够安全。