3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化交易中,截面轮动策略(Cross-sectional Rotation)是一种非常经典的策略。它通过在同一时间截面上,对股票池中的所有股票按照某种因子(如动量、成长性、技术指标等)进行排序,选择排名靠前的股票买入,排名靠后的股票卖出或空仓。
QMT 平台提供了强大的扩展数据引用函数 ext_data_rank,允许用户直接获取某个自定义扩展指标在全市场或指定品种中的实时/历史排名。结合 K 线偏移量(deviation),我们可以轻松回溯历史排名,构建稳健的截面轮动策略。
ext_data_rank在 QMT 中,获取扩展数据排名的核心函数定义如下:
ext_data_rank(extdataname, stockcode, deviation, ContextInfo)
extdataname (string):扩展数据名称(需提前在 QMT 客户端的“扩展数据”中配置并计算,例如自定义的 mycci 或 momentum)。stockcode (string):股票代码,格式为 'code.market',例如 '600000.SH'。deviation (number):K 线偏移量。这是实现历史回溯和避免未来函数的关键:
0:不偏移,代表当前 K 线对应的排名。-1:向左偏移 1 根 K 线,代表上一根 K 线的排名(在实际交易信号触发中,通常使用 -1 或更早的值,以确保信号在当前 Bar 开始时是已知且确定的)。N / -N:向右/向左偏移 N 根 K 线。ContextInfo:Python 策略运行环境对象。init):设定股票池(Universe)、交易账号,并设置调仓周期(如每周或每月)。handlebar):在调仓日,遍历股票池中的所有股票,利用 ext_data_rank 获取它们在上一根 K 线(deviation=-1)的因子排名。以下是一个完整的 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
ext_data_rank 之前,必须确保在 QMT 客户端的“数据管理”或“扩展数据”中,已经下载了相关历史行情,并成功创建并计算了对应的扩展指标。否则函数将返回默认值或空值。deviation = -1):在 handlebar 中获取排名时,如果使用 deviation = 0,在实盘中可能会因为当前 Bar 的数据未走完而导致排名频繁跳动;在回测中则可能引入未来函数。使用 -1 代表使用已收盘的确定性历史数据,更加安全稳健。ContextInfo.is_suspended_stock(stock) 剔除停牌股票,避免下单失败。