问题描述
解决方案
这是一个非常经典的指数增强或指数复制策略。在 QMT 中实现该策略主要涉及以下三个核心步骤:
- 获取成分股:使用
get_sector获取沪深300当天的成分股列表。 - 获取权重:使用
get_weight_in_index获取每只股票在指数中的权重。 - 交易执行:使用
order_target_percent根据权重自动调整仓位(该函数会自动计算买入或卖出的股数,非常适合此类策略)。
策略实现代码
以下是完整的 Python 策略代码。请在 QMT 的策略编辑器中新建一个 Python 策略,并将代码复制进去。
注意:
- 代码第一行必须包含
# -*- coding: gbk -*-以防止中文乱码。 - 请将
YOUR_ACCOUNT_ID替换为你实际的资金账号。 - 建议将策略运行周期设置为 “日线” (1d)。
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
"""
初始化函数,策略启动时执行一次
"""
# 1. 设置资金账号 (请替换为您的真实账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设置目标指数:沪深300
ContextInfo.index_code = '000300.SH'
print("策略初始化完成,目标指数:沪深300")
def handlebar(ContextInfo):
"""
K线执行函数,每根K线执行一次
建议在日线周期下运行
"""
# 获取当前K线的时间戳
index = ContextInfo.barpos
realtime = ContextInfo.get_bar_timetag(index)
# 1. 获取沪深300当天的成分股列表
# get_sector 获取指定板块在指定时间的成分股
stock_list = ContextInfo.get_sector(ContextInfo.index_code, realtime)
if not stock_list:
print(f"未获取到 {ContextInfo.index_code} 的成分股数据,请检查数据下载情况。")
return
print(f"当前日期检测到 {len(stock_list)} 只成分股,开始执行调仓...")
# 2. 遍历成分股,获取权重并下单
# 注意:为了防止持仓中有被剔除出指数的股票,建议先获取当前持仓进行对比(此处为简化逻辑,直接按目标权重下单)
# order_target_percent 会自动处理:如果没持仓则买入,如果持仓过多则卖出,如果持仓不足则补仓
for stock in stock_list:
# 获取个股在指数中的绝对权重 (返回值为百分比,例如 1.5 代表 1.5%)
weight = ContextInfo.get_weight_in_index(ContextInfo.index_code, stock)
# 过滤无效权重
if weight <= 0:
continue
# order_target_percent 接受的参数是小数 (例如 1.5% 需要转换为 0.015)
target_percent = weight / 100.0
# 执行目标比例下单
# 注意:实盘中建议添加 try-except 块或检查股票停牌状态
order_target_percent(stock, target_percent, ContextInfo, ContextInfo.account_id)
# 3. 清理非成分股持仓 (可选逻辑)
# 如果您希望严格复制指数,需要卖出那些已经不在沪深300里的持仓股票
clean_non_index_stocks(ContextInfo, stock_list)
def clean_non_index_stocks(ContextInfo, index_stocks):
"""
辅助函数:卖出不在指数成分股列表中的持仓
"""
# 获取当前持仓信息
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
for obj in positions:
stock_code = obj.m_strInstrumentID
# 如果当前持仓的股票不在当天的指数成分股列表中,且持仓量大于0
if stock_code not in index_stocks and obj.m_nVolume > 0:
print(f"{stock_code} 已被剔除出指数,执行清仓。")
# 将目标仓位设为 0,即卖出所有持仓
order_target_percent(stock_code, 0, ContextInfo, ContextInfo.account_id)
代码关键点解析
-
get_sector(sector, realtime):- 这是获取成分股的核心函数。传入
'000300.SH'和当前时间戳,它会返回该时间点该指数包含的所有股票代码列表。这保证了策略能自动适应指数的定期调整。
- 这是获取成分股的核心函数。传入
-
get_weight_in_index(indexcode, stockcode):- 获取某只股票在指数中的权重。
- 注意单位:该函数返回的是百分比数值(如
5.0代表 5%),而下单函数order_target_percent需要的是小数(如0.05),所以在代码中进行了/ 100.0的处理。
-
order_target_percent(stockcode, percent, ...):- 这是最方便的调仓函数。你不需要计算要买多少股,只需要告诉它“我希望这只股票占我总资产的多少比例”。
- 如果当前没货,它会买入;如果当前仓位不够,它会加仓;如果当前仓位超了,它会卖出。
-
clean_non_index_stocks函数:- 这是一个增强逻辑。因为
order_target_percent只处理我们传入列表中的股票。如果某只股票昨天还在沪深300里,今天被剔除了,主循环不会遍历到它,它就会一直留在账户里。这个辅助函数用于检测并卖出这些被剔除的股票。
- 这是一个增强逻辑。因为
运行前准备
- 补充数据:
- 在 QMT 界面上方菜单栏点击 “数据管理” -> “数据下载”。
- 确保下载了 “板块数据” 和 “权重数据”(通常包含在基础数据或财务数据中),否则
get_sector和get_weight_in_index可能取不到历史数据。
- 设置周期:
- 在策略编辑界面的右侧或上方,将“默认周期”设置为 “日线”。
- 回测设置:
- 设置好初始资金(建议设置大一点,因为沪深300成分股多,资金太小可能很多股票买不够一手)。
Q&A
Q: 为什么回测时有些股票没买入?
A: 可能是资金不足。沪深300有300只股票,部分高价股(如贵州茅台)一手需要十几万资金。如果权重计算出的金额不足买入一手(100股),系统会自动忽略该笔交易。
Q: 这个策略每天都会全仓买卖吗?
A: 不会。order_target_percent 是智能调仓。如果某只股票昨天的权重是 1.0%,今天还是 1.0%,且股价波动导致市值占比变化不大,它可能不会产生交易,或者只进行微调。只有权重发生变化或股价大幅波动导致持仓比例偏离目标时,才会触发买卖。
Q: 如何避免每天频繁微调带来的手续费?
A: 可以在下单前增加一个阈值判断。例如:if abs(current_percent - target_percent) > 0.001: order_target_percent(...),即只有当目标比例和当前比例偏差超过 0.1% 时才交易。