🚀 AI 一键生成 qmt 策略代码

QMT 指数增强策略开发:get_weight_in_index 函数实战指南

qmt | 发布时间: 2025/12/8
以下内容由 EasyQuant 生成。

问题描述

我想写一个指数增强策略,如何获取指数里每只股票的权重,然后根据我自己的判断进行超配或者低配?

解决方案

在 QMT 中编写指数增强策略,核心步骤包括:获取指数成分股、获取成分股权重、应用增强逻辑(调整权重)、归一化权重以及执行交易。

以下是针对您需求的详细解答和完整的策略代码示例。

核心 API 说明

  1. 获取指数成分股:使用 ContextInfo.get_sector(index_code)
  2. 获取个股在指数中的权重:使用 ContextInfo.get_weight_in_index(index_code, stock_code)
    • 注意:该函数返回的数值单位是 %(例如返回 1.5 代表 1.5%),而下单函数通常需要小数(0.015),计算时需注意转换。
  3. 下单执行:推荐使用 order_target_percent,它可以自动计算当前持仓与目标权重的差额进行买卖。

策略逻辑流程

  1. 初始化:设定基准指数(如沪深300)、账户信息和调仓周期。
  2. 获取数据:在调仓日,获取指数的所有成分股及其原始权重。
  3. 增强逻辑
    • 遍历成分股,根据您的自定义逻辑(如均线、因子得分等)计算调整系数。
    • 例如:看好则权重乘以 1.2(超配),不看好则乘以 0.8(低配)。
  4. 权重归一化:调整后的权重之和可能不等于 1(100%),必须进行归一化处理,公式为:$w_{final} = w_{adjusted} / \sum w_{adjusted}$。
  5. 执行交易:遍历归一化后的权重,发送调仓指令。

完整的 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("调仓指令发送完毕")

关键点提示

  1. 权重归一化:这是指数增强策略中最容易被忽略的一步。因为您对某些股票进行了超配或低配,原始权重的总和不再是 100%。如果不进行归一化,可能会导致仓位过重(使用杠杆)或仓位过轻(资金闲置)。
  2. 数据获取get_weight_in_index 获取的是静态数据,通常随行情包更新。在回测模式下,QMT 会尝试匹配历史权重,但建议在实盘前仔细核对数据源。
  3. 交易函数order_target_percent 是最适合此类策略的函数,它会自动计算买卖数量。请确保在 init 中正确设置了 ContextInfo.set_account
  4. 异常处理:代码中加入了对数据缺失的处理(如获取不到 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 编码。如果不加这一行,代码中的中文字符(注释或打印信息)可能会导致乱码或运行报错。