问题描述
解决方案
这是一个基于 QMT 平台的投资组合再平衡策略(Portfolio Rebalancing Strategy)。
策略逻辑
- 目标设定:在
init中设定一个目标投资组合及其对应的权重(例如:60% 股票A,40% 股票B)。 - 定期检查:在
handlebar中,根据设定的周期(如每月第一个交易日)触发调仓逻辑。 - 执行调仓:使用 QMT 提供的
order_target_percent函数。该函数会自动计算当前持仓价值与目标价值的差额,并自动下单买入或卖出,使该标的占总资产的比例达到预设值。
策略代码
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
"""
初始化函数,设定账户、股票池、目标权重和调仓周期
"""
# 1. 设置资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.account_type = 'STOCK' # 账号类型:STOCK-股票,FUTURE-期货
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设定目标投资组合及权重 (权重之和建议为 1.0)
# 示例:贵州茅台 40%, 宁德时代 30%, 五粮液 30%
ContextInfo.target_weights = {
'600519.SH': 0.4,
'300750.SZ': 0.3,
'000858.SZ': 0.3
}
# 3. 设置股票池 (用于回测时下载数据)
ContextInfo.set_universe(list(ContextInfo.target_weights.keys()))
# 4. 策略运行参数
ContextInfo.rebalance_period = 'monthly' # 调仓周期: 'monthly' (月度) 或 'weekly' (周度)
ContextInfo.last_rebalance_date = None # 记录上次调仓日期
print("策略初始化完成,目标组合权重:", ContextInfo.target_weights)
def handlebar(ContextInfo):
"""
行情驱动函数,每根K线运行一次
"""
# 获取当前K线的时间
index = ContextInfo.barpos
timetag = ContextInfo.get_bar_timetag(index)
current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
# 检查是否需要调仓
if not check_rebalance_condition(ContextInfo, current_date_str):
return
print(f"[{current_date_str}] 触发再平衡调仓...")
# 执行调仓逻辑
rebalance_portfolio(ContextInfo)
# 更新上次调仓时间
ContextInfo.last_rebalance_date = current_date_str
def check_rebalance_condition(ContextInfo, current_date_str):
"""
判断是否满足调仓时间条件
"""
# 如果是第一天运行,直接调仓
if ContextInfo.last_rebalance_date is None:
return True
last_date = ContextInfo.last_rebalance_date
# 月度调仓:判断当前月份是否与上次调仓月份不同
if ContextInfo.rebalance_period == 'monthly':
current_month = current_date_str[0:6] # 取 YYYYMM
last_month = last_date[0:6]
if current_month != last_month:
return True
# 周度调仓:这里简单判断是否跨周(实际应用可结合 datetime 库判断周几)
# 为了演示简单,此处仅实现月度逻辑,如需周度可扩展
return False
def rebalance_portfolio(ContextInfo):
"""
执行核心调仓逻辑
"""
# 1. 获取当前账户的总资产 (用于计算目标持仓市值,但在 order_target_percent 中通常自动处理)
# 注意:order_target_percent 是基于账户总资产(现金+市值)计算比例
# 2. 遍历目标权重字典进行调仓
for stock_code, target_weight in ContextInfo.target_weights.items():
# 获取当前标的最新价,用于日志输出或检查停牌
last_price = ContextInfo.get_market_data_ex(
['close'], [stock_code], period=ContextInfo.period, count=1
)
if stock_code in last_price and not last_price[stock_code].empty:
# 使用 order_target_percent 函数
# 该函数会自动计算:(总资产 * target_weight) / 股价 = 目标股数
# 然后自动下单买入或卖出差额
order_target_percent(
stock_code,
target_weight,
ContextInfo,
ContextInfo.account_id
)
print(f" -> 调整 {stock_code} 仓位至目标权重: {target_weight*100}%")
else:
print(f" -> 警告: 无法获取 {stock_code} 行情,跳过调仓")
# 3. (可选) 清理不在目标列表中的持仓
# 如果您希望卖出所有不在 target_weights 中的股票,需要获取当前持仓并遍历
# 此处为简易版,仅处理 target_weights 中定义的股票
代码关键点解析
-
order_target_percent:- 这是 QMT 中实现再平衡最核心的函数。
- 功能:它不需要你手动计算“我要买多少股”或“我要卖多少股”。你只需要告诉它“我希望这只股票占我总资产的 30%”,函数内部会自动获取你的总资产、当前股价、当前持仓,然后计算差值并下单。
- 注意:回测模式下,它基于回测虚拟资金计算;实盘模式下,它基于绑定的资金账号的实际总资产计算。
-
调仓时间控制 (
check_rebalance_condition):- 在
handlebar中,我们通过比较当前 K 线日期的月份和上一次调仓日期的月份。 - 如果月份发生了变化(例如从 4 月变成了 5 月),则意味着这是新月份的第一个交易日(或第一个 K 线),触发调仓。
- 在
-
数据获取:
- 使用了
get_market_data_ex来简单检查行情是否存在,防止对停牌或无数据的股票下单导致报错。
- 使用了
如何使用
- 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 修改账号:将
YOUR_ACCOUNT_ID替换为您真实的资金账号。 - 修改组合:在
ContextInfo.target_weights中修改您想要的股票代码和权重。 - 运行/回测:
- 回测:设置好回测区间、初始资金和费率,点击“回测”。
- 实盘:请务必先在模拟账户中测试无误后,再谨慎用于实盘。
常见问题 (Q&A)
Q: 如果我想把某只股票彻底清仓怎么办?
A: 在 ContextInfo.target_weights 中将该股票的权重设置为 0.0,策略在下次调仓时会调用 order_target_percent(code, 0, ...),从而卖出该股票的所有持仓。
Q: order_target_percent 是按什么价格下单的?
A: 默认情况下,QMT 的交易函数在回测中通常以当前 K 线的收盘价(或次日开盘价,取决于回测机制设置)成交。在实盘中,如果不指定 style 和 price 参数,通常默认以最新价(LATEST)下单。如果需要指定限价或对手价,可以在函数中增加参数,例如:order_target_percent(..., style='COMPETE', ...) 使用对手价。
Q: 为什么回测时第一天就买入了?
A: 代码逻辑中 if ContextInfo.last_rebalance_date is None: return True 确保了策略在开始运行的第一天就会进行一次建仓,将现金转换为股票组合。