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

qmt 多因子截面选股:利用 ext_data_rank 实现因子动态排序与调仓指南

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

问题描述

请根据以下 SEO 主题生成完整页面内容:

Title: qmt 多因子截面选股:利用 ext_data_rank 实现因子动态排序与调仓

Question: 请提供一个在 qmt 中利用 ext_data_rank 计算自定义因子全市场排名并按百分比截面选股的示例。

解决方案

QMT 多因子截面选股:利用 ext_data_rank 实现因子动态排序与调仓

在多因子量化选股策略中,获取因子在全市场的截面相对排名是构建多因子模型和因子组合的核心步骤。QMT(迅投量化)平台提供了强大的扩展数据排名接口 ext_data_rank(),可高效计算全市场截面排名。


1. ext_data_rank 函数解析

ext_data_rank 用于获取引用的扩展数据(如自定义指标、因子)在所有品种中的实时排名。

语法说明:

ext_data_rank(extdataname, stockcode, deviation, ContextInfo)
  • extdataname: string,扩展数据名称(须先在客户端系统中创建或引入该扩展数据)。
  • stockcode: string,品种代码(带市场后缀),例如 '600000.SH'
  • deviation: number,K线偏移量,0 表示当前Bar不偏移,N 表示向右偏移,-N 表示向左偏移。
  • ContextInfo: pythonObj,策略上下文全局环境对象。
  • 返回值: number,该品种在该扩展数据指标下的全市场数值排名(如第 1 名、第 10 名等)。

2. 截面选股策略思路

  1. 定义全市场股票池:使用 ContextInfo.get_stock_list_in_sector('沪深300')全A股 板块。
  2. 计算因子排名与相对分位数:通过 ext_data_rank 遍历股票池中的每只股票,获取排名,并计算相对百分比分位数:
    $$\text{Rank Percentile} = \frac{\text{Rank}}{\text{Total Stocks}}$$
  3. 截面筛选:筛选出因子排名在前 10%(或指定比例)的股票作为目标调仓池。
  4. 定时调仓:在 handlebar 或结合 run_time 定时器进行周期性调仓,使用 passorder 进行一键目标调整。

3. QMT 策略完整源码示例

以下示例展示了如何在 QMT 中利用 ext_data_rank 进行截面筛选并按固定调仓周期进行买卖:

#coding:gbk
import pandas as pd

def init(ContextInfo):
    # 1. 设定资金账号与股票池
    ContextInfo.accountID = '6000000001'
    ContextInfo.set_account(ContextInfo.accountID)
    
    # 设定基准与交易参数
    ContextInfo.start = '2023-01-01 09:30:00'
    ContextInfo.end = '2023-12-31 15:00:00'
    ContextInfo.capital = 1000000
    
    # 设定扩展数据名称(请确保客户端已存在该扩展数据,例如 'mycci' 或自定义因子)
    ContextInfo.factor_name = 'mycci'
    
    # 选股分位数比例:选取全市场排名前 10% 的股票
    ContextInfo.top_percent = 0.10
    
    # 调仓周期控制(例如每20个Bar调仓一次)
    ContextInfo.trade_period = 20
    ContextInfo.hold_count = 10  # 最终持仓目标股票数量

def handlebar(ContextInfo):
    # 判断调仓周期
    if ContextInfo.barpos % ContextInfo.trade_period != 0:
        return
        
    # 2. 获取股票池成份股列表(以沪深300为例)
    stock_pool = ContextInfo.get_stock_list_in_sector('沪深300')
    total_count = len(stock_pool)
    if total_count == 0:
        return
        
    # 3. 遍历股票池,利用 ext_data_rank 计算全市场排名
    rank_dict = {}
    for stock in stock_pool:
        # 排除停牌股
        if ContextInfo.is_suspended_stock(stock):
            continue
            
        # 获取扩展数据排名
        rank = ext_data_rank(ContextInfo.factor_name, stock, 0, ContextInfo)
        if rank > 0:
            rank_dict[stock] = rank
            
    if not rank_dict:
        return
        
    # 4. 转化为 DataFrame 进行百分比截面筛选
    df_rank = pd.DataFrame(list(rank_dict.items()), columns=['stock', 'rank'])
    df_rank.sort_values(by='rank', ascending=True, inplace=True)  # 升序,数值越小排名越靠前
    
    # 截取前 top_percent 的股票
    cutoff_idx = int(len(df_rank) * ContextInfo.top_percent)
    selected_stocks = df_rank.head(cutoff_idx)['stock'].tolist()[:ContextInfo.hold_count]
    
    print(f"Barpos: {ContextInfo.barpos} | 截选目标持仓股票: {selected_stocks}")
    
    # 5. 执行组合调仓:采用目标比例下单方式 (orderType=1113,比例分配)
    weight_per_stock = 1.0 / len(selected_stocks) if selected_stocks else 0
    
    # 调仓:卖出不在目标池中的持仓
    positions = get_trade_detail_data(ContextInfo.accountID, 'STOCK', 'POSITION')
    current_holding = [pos.m_strInstrumentID for pos in positions if pos.m_nVolume > 0]
    
    for stock in current_holding:
        if stock not in selected_stocks:
            # 卖出平仓
            passorder(24, 1101, ContextInfo.accountID, stock, 5, -1, pos.m_nVolume, '多因子平仓', 1, ContextInfo)
            
    # 买入目标选股池股票
    for stock in selected_stocks:
        # 利用可用资金的固定比例(orderType=1123)或总资产比例买入
        passorder(23, 1101, ContextInfo.accountID, stock, 5, -1, int(weight_per_stock * 100), '多因子开仓', 1, ContextInfo)

4. 关键注意事项

  1. 扩展数据预先计算:使用 ext_data_rank 前,需确保客户端【扩展数据管理】中已配置好对应名称的扩展数据,并完成了历史数据刷新。
  2. 实盘与回测差异:回测模式下,需提前下载并补充好回测时间段内的完整行情与指标数据;实盘运行时,ext_data_rank 会基于实时推送截面重新计算排名。
  3. 过滤停牌与新股:在截面排序前,务必通过 ContextInfo.is_suspended_stock() 剔除停牌股票,避免无效信号下单。