🚀 AI 一键生成 qmt 策略代码
立即体验

如何在 QMT 中使用 ext_data_rank 扩展数据排名构建截面轮动策略?

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

问题描述

qmt 截面轮动:结合 ext_data_rank 与 K 线偏移量选股
如何利用QMT的ext_data_rank扩展数据排名函数构建截面轮动策略?

解决方案

QMT 截面轮动策略:结合 ext_data_rank 与 K 线偏移量选股

在量化交易中,截面轮动策略(Cross-sectional Rotation)是一种非常经典的策略。它通过在同一时间截面上,对股票池中的所有股票按照某种因子(如动量、成长性、技术指标等)进行排序,选择排名靠前的股票买入,排名靠后的股票卖出或空仓。

QMT 平台提供了强大的扩展数据引用函数 ext_data_rank,允许用户直接获取某个自定义扩展指标在全市场或指定品种中的实时/历史排名。结合 K 线偏移量(deviation),我们可以轻松回溯历史排名,构建稳健的截面轮动策略。


1. 核心 API 介绍:ext_data_rank

在 QMT 中,获取扩展数据排名的核心函数定义如下:

ext_data_rank(extdataname, stockcode, deviation, ContextInfo)

参数解析:

  • extdataname (string):扩展数据名称(需提前在 QMT 客户端的“扩展数据”中配置并计算,例如自定义的 myccimomentum)。
  • stockcode (string):股票代码,格式为 'code.market',例如 '600000.SH'
  • deviation (number):K 线偏移量。这是实现历史回溯和避免未来函数的关键:
    • 0:不偏移,代表当前 K 线对应的排名。
    • -1:向左偏移 1 根 K 线,代表上一根 K 线的排名(在实际交易信号触发中,通常使用 -1 或更早的值,以确保信号在当前 Bar 开始时是已知且确定的)。
    • N / -N:向右/向左偏移 N 根 K 线。
  • ContextInfo:Python 策略运行环境对象。

2. 截面轮动策略设计思路

  1. 初始化环境 (init):设定股票池(Universe)、交易账号,并设置调仓周期(如每周或每月)。
  2. 获取截面排名 (handlebar):在调仓日,遍历股票池中的所有股票,利用 ext_data_rank 获取它们在上一根 K 线(deviation=-1)的因子排名。
  3. 筛选标的:对排名进行升序或降序排序,筛选出排名最靠前(例如前 5 只)的股票作为目标持仓。
  4. 执行调仓
    • 卖出当前持仓中不在目标持仓列表中的股票。
    • 买入目标持仓中尚未持有的股票,并按比例均分资金。

3. 策略 Python 源码示例

以下是一个完整的 QMT 截面轮动策略模板。假设我们已经在 QMT 中创建并计算了一个名为 'my_momentum'(动量因子)的扩展数据:

#encoding:gbk
import pandas as pd

def init(ContextInfo):
    # 1. 设定股票池(以沪深300成分股为例)
    ContextInfo.stock_pool = ['600000.SH', '600036.SH', '600016.SH', '601398.SH', '600900.SH', '600030.SH', '600519.SH']
    ContextInfo.set_universe(ContextInfo.stock_pool)
    
    # 2. 设定交易账号
    ContextInfo.account = '6000000248'
    ContextInfo.set_account(ContextInfo.account)
    
    # 3. 策略参数设置
    ContextInfo.hold_count = 3  # 每次持仓 3 只股票
    ContextInfo.factor_name = 'my_momentum'  # 预先计算好的扩展数据名称
    
    # 记录上次调仓的 K 线索引,控制调仓频率(例如每 5 根 K 线调仓一次)
    ContextInfo.last_trade_bar = -1
    ContextInfo.trade_interval = 5 

def handlebar(ContextInfo):
    # 获取当前 K 线索引
    current_bar = ContextInfo.barpos
    
    # 控制调仓频率
    if ContextInfo.last_trade_bar != -1 and (current_bar - ContextInfo.last_trade_bar) < ContextInfo.trade_interval:
        return
        
    # 确保不是最后一根 K 线之前的数据不重复触发(回测模式下逐 Bar 运行)
    if not ContextInfo.is_last_bar():
        # 如果是回测,可以按区间运行;此处以最新 Bar 触发为例
        pass

    # 开始进行截面选股
    rank_dict = {}
    for stock in ContextInfo.stock_pool:
        # 使用 deviation = -1 获取上一个交易日的排名,防止引入未来数据
        rank_val = ext_data_rank(ContextInfo.factor_name, stock, -1, ContextInfo)
        if rank_val is not None and rank_val > 0:
            rank_dict[stock] = rank_val
            
    if not rank_dict:
        return
        
    # 按排名从小到大排序(假设排名越小越优,如第 1 名优于第 100 名)
    sorted_stocks = sorted(rank_dict.items(), key=lambda x: x[1])
    target_holdings = [stock for stock, rank in sorted_stocks[:ContextInfo.hold_count]]
    
    # 获取当前实际持仓
    position_info = get_trade_detail_data(ContextInfo.account, 'STOCK', 'POSITION')
    current_holdings = [pos.m_strInstrumentID for pos in position_info if pos.m_nVolume > 0]
    
    # 4. 执行调仓逻辑
    # 卖出不在目标持仓中的股票
    for stock in current_holdings:
        if stock not in target_holdings:
            # 股票卖出:24代表股票卖出,1101代表单股单账号股/手方式下单
            # 卖出所有持仓数量
            for pos in position_info:
                if pos.m_strInstrumentID == stock:
                    passorder(24, 1101, ContextInfo.account, stock, 5, -1, pos.m_nCanUseVolume, ContextInfo)
                    print(f"卖出不符合轮动条件的股票: {stock}")

    # 买入目标持仓中尚未持有的股票
    for stock in target_holdings:
        if stock not in current_holdings:
            # 目标比例下单:23代表股票买入,1113代表单股单账号总资产比例方式下单
            # 均分资金,每只股票分配 1 / hold_count 的资金比例
            target_percent = 1.0 / ContextInfo.hold_count
            passorder(23, 1113, ContextInfo.account, stock, 5, -1, target_percent, ContextInfo)
            print(f"买入轮动筛选出的优质股票: {stock},目标比例: {target_percent:.2%}")
            
    # 更新调仓时间
    ContextInfo.last_trade_bar = current_bar

4. 避坑指南与注意事项

  1. 必须提前下载并计算扩展数据:在使用 ext_data_rank 之前,必须确保在 QMT 客户端的“数据管理”或“扩展数据”中,已经下载了相关历史行情,并成功创建并计算了对应的扩展指标。否则函数将返回默认值或空值。
  2. 严格使用负偏移量(deviation = -1:在 handlebar 中获取排名时,如果使用 deviation = 0,在实盘中可能会因为当前 Bar 的数据未走完而导致排名频繁跳动;在回测中则可能引入未来函数。使用 -1 代表使用已收盘的确定性历史数据,更加安全稳健。
  3. 停牌股处理:在遍历股票池时,建议结合 ContextInfo.is_suspended_stock(stock) 剔除停牌股票,避免下单失败。