问题描述
解决方案
在 QMT 中编写指数增强策略,核心步骤包括:获取指数成分股、获取成分股权重、应用增强逻辑(调整权重)、归一化权重以及执行交易。
以下是针对您需求的详细解答和完整的策略代码示例。
核心 API 说明
- 获取指数成分股:使用
ContextInfo.get_sector(index_code)。 - 获取个股在指数中的权重:使用
ContextInfo.get_weight_in_index(index_code, stock_code)。- 注意:该函数返回的数值单位是 %(例如返回 1.5 代表 1.5%),而下单函数通常需要小数(0.015),计算时需注意转换。
- 下单执行:推荐使用
order_target_percent,它可以自动计算当前持仓与目标权重的差额进行买卖。
策略逻辑流程
- 初始化:设定基准指数(如沪深300)、账户信息和调仓周期。
- 获取数据:在调仓日,获取指数的所有成分股及其原始权重。
- 增强逻辑:
- 遍历成分股,根据您的自定义逻辑(如均线、因子得分等)计算调整系数。
- 例如:看好则权重乘以 1.2(超配),不看好则乘以 0.8(低配)。
- 权重归一化:调整后的权重之和可能不等于 1(100%),必须进行归一化处理,公式为:$w_{final} = w_{adjusted} / \sum w_{adjusted}$。
- 执行交易:遍历归一化后的权重,发送调仓指令。
完整的 Python 策略代码
以下代码展示了一个简单的指数增强框架。作为示例,增强逻辑设定为:如果股价在 20 日均线之上,则超配 20%;如果在均线之下,则低配 20%。 您可以将这部分替换为您自己的判断逻辑。
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
# 1. 设置策略参数
ContextInfo.index_code = '000300.SH' # 基准指数:沪深300
ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请替换为您的资金账号
ContextInfo.account_type = 'STOCK' # 账号类型:STOCK, FUTURE 等
ContextInfo.rebalance_period = 20 # 调仓周期:每20根K线调仓一次
# 设置账号(实盘/回测必须)
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设置股票池为指数成分股(用于回测数据下载)
# 注意:get_sector 获取的是当前成分股,回测中若需历史成分股需注意数据准确性
components = ContextInfo.get_sector(ContextInfo.index_code)
ContextInfo.set_universe(components)
def handlebar(ContextInfo):
# 1. 判断是否到达调仓周期
# 使用 barpos 判断,或者使用 timetag 判断日期
if ContextInfo.barpos % ContextInfo.rebalance_period != 0:
return
print(f"开始执行调仓,当前K线位置: {ContextInfo.barpos}")
# 2. 获取指数成分股
# get_sector 第二个参数传入当前时间戳,可尝试获取当时成分股(视数据源支持情况)
current_time = ContextInfo.get_bar_timetag(ContextInfo.barpos)
components = ContextInfo.get_sector(ContextInfo.index_code, current_time)
if not components:
print("未获取到成分股信息")
return
# 3. 获取增强所需的行情数据 (用于示例逻辑:计算均线)
# 获取过去20天的收盘价
market_data = ContextInfo.get_market_data_ex(
['close'],
components,
period='1d',
count=21,
dividend_type='front'
)
# 4. 计算目标权重
target_weights = {}
total_adjusted_weight = 0.0
# 临时存储调整后的权重
temp_weights = {}
for stock in components:
# 4.1 获取原始权重 (单位是 %,需要除以 100 转换为小数)
raw_weight_percent = ContextInfo.get_weight_in_index(ContextInfo.index_code, stock)
base_weight = raw_weight_percent / 100.0
# 如果获取不到权重(如新股或数据缺失),暂按0处理或跳过
if base_weight <= 0:
continue
# 4.2 应用增强逻辑 (自定义判断部分)
# 示例逻辑:收盘价 > 20日均线 -> 超配 1.2倍;否则 -> 低配 0.8倍
adjustment_factor = 1.0
if stock in market_data:
df = market_data[stock]
if len(df) >= 21:
close_prices = df['close']
current_price = close_prices.iloc[-1]
ma20 = close_prices.iloc[-21:-1].mean() # 前20日均线
if current_price > ma20:
adjustment_factor = 1.2 # 超配
else:
adjustment_factor = 0.8 # 低配
# 计算调整后权重
adj_weight = base_weight * adjustment_factor
temp_weights[stock] = adj_weight
total_adjusted_weight += adj_weight
# 5. 权重归一化 (Normalization)
# 确保所有股票的目标权重之和为 1 (100%)
if total_adjusted_weight > 0:
for stock, w in temp_weights.items():
target_weights[stock] = w / total_adjusted_weight
else:
print("权重计算异常,总权重为0")
return
# 6. 执行交易
# 遍历计算出的目标权重进行调仓
# 注意:这里只处理了在指数内的股票。如果需要卖出已经被剔除出指数的股票,
# 需要先获取当前持仓,将不在 target_weights 里的股票清仓。
# 6.1 先处理清仓逻辑 (不在目标列表里的持仓股票卖出)
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
for pos in positions:
stock_code = pos.m_strInstrumentID + '.' + pos.m_strExchangeID
if stock_code not in target_weights and pos.m_nVolume > 0:
order_target_percent(stock_code, 0, ContextInfo, ContextInfo.account_id)
print(f"卖出非成分股/被剔除股票: {stock_code}")
# 6.2 执行买入/调仓
for stock, weight in target_weights.items():
# order_target_percent 接受的 weight 是小数 (0.05 代表 5%)
order_target_percent(stock, weight, ContextInfo, ContextInfo.account_id)
print("调仓指令发送完毕")
关键点提示
- 权重归一化:这是指数增强策略中最容易被忽略的一步。因为您对某些股票进行了超配或低配,原始权重的总和不再是 100%。如果不进行归一化,可能会导致仓位过重(使用杠杆)或仓位过轻(资金闲置)。
- 数据获取:
get_weight_in_index获取的是静态数据,通常随行情包更新。在回测模式下,QMT 会尝试匹配历史权重,但建议在实盘前仔细核对数据源。 - 交易函数:
order_target_percent是最适合此类策略的函数,它会自动计算买卖数量。请确保在init中正确设置了ContextInfo.set_account。 - 异常处理:代码中加入了对数据缺失的处理(如获取不到 K 线数据时默认不调整系数),这在实际运行中非常重要,防止因个别股票停牌或数据缺失导致策略报错中断。
Q&A
Q: 如何将增强逻辑改为基于因子的打分?
A: 您可以在 init 中通过 ContextInfo.get_factor_data 预先获取因子数据,或者在 handlebar 中读取本地计算好的 CSV 文件。在遍历成分股时,根据因子值的大小来设定 adjustment_factor。
Q: get_weight_in_index 返回的是流通市值权重还是总市值权重?
A: 这取决于指数本身的编制规则。对于沪深300等主流指数,通常是分级靠档后的自由流通市值权重。QMT 返回的是交易所或指数公司公布的权重数据。
Q: 为什么代码第一行要加 # -*- coding: gbk -*-?
A: QMT 的 Python 编辑器和底层环境默认使用 GBK 编码。如果不加这一行,代码中的中文字符(注释或打印信息)可能会导致乱码或运行报错。