问题描述
解决方案
这是一个基于 QMT (Quantitative Trading Platform) API 编写的自动投资组合再平衡策略。
策略逻辑说明
该策略旨在实现一个经典的定期等权重再平衡(Equal-Weight Rebalancing)逻辑:
- 标的池:选取一组固定的股票(示例中选取了白酒、金融等行业的龙头股)。
- 目标权重:策略目标是让持仓中每只股票的市值保持相等(例如 5 只股票,每只占总资产的 20%)。
- 再平衡频率:按月进行。每当月份发生变化时(即每个月的第一天交易日),触发调仓。
- 交易执行:使用
order_target_percent函数。该函数会自动计算需要买入或卖出的数量,将仓位调整至目标比例。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 1. 设置资金账号 (请替换为您真实的资金账号)
# 格式如: '6000000248'
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设置股票池 (示例:贵州茅台, 五粮液, 平安银行, 招商银行, 宁德时代)
# 用户可根据需求修改此列表
ContextInfo.stock_list = ['600519.SH', '000858.SZ', '000001.SZ', '600036.SH', '300750.SZ']
ContextInfo.set_universe(ContextInfo.stock_list)
# 3. 设置再平衡参数
# 记录上一次再平衡的月份,用于判断是否跨月
ContextInfo.last_rebalance_month = -1
# 4. 设置回测参数 (仅回测模式有效)
# 资金 100万
ContextInfo.capital = 1000000
# 费率设置:万分之三
ContextInfo.set_commission(0, [0.001, 0.001, 0.0003, 0.0003, 0.0003, 5])
# 滑点设置:0.01元
ContextInfo.set_slippage(1, 0.01)
print("策略初始化完成,标的池数量: {}".format(len(ContextInfo.stock_list)))
def handlebar(ContextInfo):
"""
行情事件函数,每根K线运行一次
"""
# 获取当前K线的时间戳
timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
# 将时间戳转换为 datetime 格式的字符串
current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
# 提取当前月份 (格式 YYYYMM)
current_month = int(current_date_str[0:6])
# 判断是否需要跳过 (例如未到最后一根K线,或者非交易时间)
# 在回测模式下,通常在日线周期的每一根Bar执行
# --- 再平衡逻辑判断 ---
# 如果当前月份与上一次记录的月份不同,说明进入了新的月份,执行再平衡
if current_month != ContextInfo.last_rebalance_month:
print(">>> 检测到月份变化,执行再平衡。当前日期: {}".format(current_date_str))
# 执行调仓函数
rebalance(ContextInfo)
# 更新记录的月份
ContextInfo.last_rebalance_month = current_month
def rebalance(ContextInfo):
"""
执行再平衡的核心逻辑
"""
stock_count = len(ContextInfo.stock_list)
if stock_count == 0:
return
# 计算每只股票的目标权重 (等权重)
# 如果有5只股票,每只权重为 1/5 = 0.2 (20%)
target_weight = 1.0 / stock_count
print("开始调仓,目标每只股票持仓权重: {:.2%}".format(target_weight))
# 遍历股票池中的每一只股票进行调整
for stock in ContextInfo.stock_list:
# 获取当前标的名称,用于日志输出
stock_name = ContextInfo.get_stock_name(stock)
# 使用 order_target_percent 进行下单
# 该函数会自动计算:(总资产 * 目标比例 - 当前持仓市值) / 股价
# 正数买入,负数卖出
# 注意:order_target_percent 依赖于账户的总资产数据
# 参数说明:
# stock: 代码
# target_weight: 目标比例 (0.2 表示 20%)
# order_type: 1101 (单股、单账号、普通、股/手方式下单) - 这里虽然是target_percent,但底层通常映射到具体的下单指令
# price: -1 (使用默认价格,通常是市价或最新价,取决于交易设置)
# ContextInfo: 上下文
# account_id: 账号
# 注意:QMT的 order_target_percent 封装在 Python API 中,
# 它会自动处理买卖方向。
order_target_percent(stock, target_weight, "LATEST", 0.0, ContextInfo, ContextInfo.account_id)
print("已发送调仓指令: {} ({}), 目标权重: {:.2%}".format(stock, stock_name, target_weight))
print("本次再平衡指令发送完毕。")
代码关键点解析
-
# -*- coding: gbk -*-:- QMT 的 Python 编辑器默认使用 GBK 编码,这行代码必须放在第一行,否则中文注释会导致乱码或报错。
-
init函数:ContextInfo.set_account: 必须设置真实的资金账号,否则无法获取持仓和资金信息,order_target_percent也无法正确计算。ContextInfo.stock_list: 这里定义了投资组合的范围。
-
handlebar函数:- 这是策略的驱动引擎。我们通过
timetag_to_datetime获取当前 K 线的时间。 - 跨月判断逻辑:通过比较
current_month和last_rebalance_month。如果不同,说明这是新月份的第一个交易 K 线,触发调仓。
- 这是策略的驱动引擎。我们通过
-
rebalance函数:- 核心算法:
target_weight = 1.0 / len(stock_list)。这是最简单的等权重模型。 order_target_percent: 这是 QMT 提供的便捷交易函数。- 它会自动读取当前账户的总资产(现金+持仓市值)。
- 它会计算目标持仓市值 = 总资产 *
target_weight。 - 它会对比当前持仓,自动发出买入或卖出指令,将仓位调整到目标水平。
- 核心算法:
如何在 QMT 中运行
- 打开 QMT 客户端,进入【模型研究】或【策略交易】界面。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴到编辑器中。
- 修改账号:将
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'中的'YOUR_ACCOUNT_ID'替换为您实际的资金账号。 - 回测:
- 在右侧设置回测区间(例如最近一年)。
- 周期选择“日线”。
- 点击“回测”按钮查看净值曲线。
- 实盘/模拟盘:
- 确保 QMT 交易端已登录。
- 将策略加载到运行单元,点击“运行”。
常见问题 (Q&A)
Q: 如何将等权重改为按市值加权?
A: 您需要在 rebalance 函数中获取每只股票的总市值,计算总市值之和,然后计算每只股票的权重 weight = 个股总市值 / 所有股票总市值,最后将这个动态的 weight 传给 order_target_percent。
Q: 为什么回测时没有交易?
A: 请检查:1. ContextInfo.stock_list 中的股票在回测区间内是否已上市;2. 是否下载了对应的历史日线数据;3. 资金账号设置是否符合回测虚拟账号规则(回测模式下通常不需要真实账号,但代码逻辑中引用了 account_id,回测引擎会模拟一个)。
Q: order_target_percent 下单失败怎么办?
A: 可能是因为资金不足、股票停牌或跌停无法卖出。在实盘中,建议配合 get_trade_detail_data 查询委托状态,或者在 rebalance 前增加风控判断(如检查股票状态 ContextInfo.is_suspended_stock)。