3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化投资中,截面轮动策略(Cross-sectional Rotation)是一种非常经典的策略。它通过在同一时间截面上,对全市场或特定板块的股票按某种指标(如动量、估值、技术指标等)进行排序,选择排名靠前的标的买入,排名靠后的标的卖出或不持有。
QMT 平台提供了强大的扩展数据引用函数 ext_data_rank(),能够直接获取指定自定义指标在全市场所有品种中的实时或历史截面排名。本文将详细介绍其工作原理,并给出一个基于该函数的截面轮动策略实现方案。
ext_data_rank在 QMT 中,获取扩展数据排名的核心函数为 ext_data_rank:
ext_data_rank(extdataname, stockcode, deviation, ContextInfo)
extdataname (string):自定义扩展数据的名称(例如在客户端中导入或计算好的 'mycci' 或其他因子名称)。stockcode (string):股票代码,格式为 'code.market',例如 '600000.SH'。deviation (number):K 线偏移量。0 表示当前 K 线,-1 表示上一个 K 线。ContextInfo:策略运行环境对象。number,代表该股票的指标值在当前截面所有品种中的绝对排名(或百分比排名,取决于扩展数据的生成设置)。init 函数中设置股票池(Universe)、交易账号,并定义调仓周期(如每周或每月)。handlebar 中,遍历股票池中的所有股票,调用 ext_data_rank 获取它们在当前 Bar 上的自定义指标排名。以下是一个完整的 QMT 截面轮动策略源码。该策略假设您已经在 QMT 客户端中创建并计算了名为 'mycci' 的扩展数据:
#coding:gbk
import pandas as pd
def init(ContextInfo):
# 1. 设定基础股票池(例如沪深300成分股)
# 实际运行前请确保已下载对应历史数据
ContextInfo.stock_list = ['600000.SH', '600016.SH', '600019.SH', '600030.SH', '600036.SH', '600050.SH', '600104.SH', '600196.SH', '600276.SH', '600309.SH']
ContextInfo.set_universe(ContextInfo.stock_list)
# 2. 设定交易账号
ContextInfo.account_id = '6000000248' # 请替换为您的实际资金账号
ContextInfo.set_account(ContextInfo.account_id)
# 3. 设定策略参数
ContextInfo.hold_count = 3 # 每次轮动持有的股票数量
ContextInfo.ext_data_name = 'mycci' # 预先在QMT中算好的扩展数据名称
# 记录上次调仓时间,用于控制调仓频率(例如每5个Bar轮动一次)
ContextInfo.last_trade_bar = -1
ContextInfo.trade_interval = 5
def handlebar(ContextInfo):
# 获取当前Bar的索引
bar_index = ContextInfo.barpos
# 控制调仓频率
if ContextInfo.last_trade_bar != -1 and (bar_index - ContextInfo.last_trade_bar) < ContextInfo.trade_interval:
return
# 确保不是历史无数据Bar
if bar_index < 10:
return
# 1. 获取截面排名数据
rank_dict = {}
for stock in ContextInfo.stock_list:
try:
# 获取该股票在当前截面的排名值
rank_val = ext_data_rank(ContextInfo.ext_data_name, stock, 0, ContextInfo)
if rank_val is not None and not pd.isna(rank_val):
rank_dict[stock] = rank_val
except Exception as e:
print(f"获取 {stock} 排名失败: {e}")
if not rank_dict:
return
# 2. 对排名进行排序(假设排名数值越小越好,即升序排列)
sorted_stocks = sorted(rank_dict.items(), key=lambda x: x[1])
target_holdings = [stock for stock, rank in sorted_stocks[:ContextInfo.hold_count]]
print(f"当前Bar位置: {bar_index}, 筛选出的目标持仓: {target_holdings}")
# 3. 获取当前账户的实际持仓
current_positions = []
pos_objects = get_trade_detail_data(ContextInfo.account_id, 'STOCK', 'POSITION')
for pos in pos_objects:
if pos.m_nVolume > 0:
current_positions.append(pos.m_strInstrumentID)
# 4. 执行调仓交易
# 4.1 卖出不在目标持仓中的股票
for stock in current_positions:
if stock not in target_holdings:
print(f"卖出不符合轮动条件的股票: {stock}")
# 使用指定目标比例交易,调仓到0%持仓(即卖出)
order_target_percent(stock, 0.0, 'LATEST', ContextInfo, ContextInfo.account_id)
# 4.2 买入目标持仓股票(等权重分配)
target_percent = 1.0 / ContextInfo.hold_count
for stock in target_holdings:
print(f"买入/调整目标轮动股票: {stock}, 目标比例: {target_percent:.2%}")
order_target_percent(stock, target_percent, 'LATEST', ContextInfo, ContextInfo.account_id)
# 更新上次调仓的Bar位置
ContextInfo.last_trade_bar = bar_index
ext_data_rank 前,必须确保客户端的“数据管理”中已经补充了完整的历史行情数据,并且您自定义的扩展数据(如 'mycci')已经计算完毕。如果本地没有该扩展数据,函数将无法返回有效值。ContextInfo.is_suspended_stock() 剔除停牌股,避免因无法交易导致调仓失败。