问题描述
解决方案
这是一个基于 日均换手率(Turnover Rate) 的选股策略。
策略逻辑
- 股票池:以“沪深300”成分股为例(可根据需要修改为全A或其他板块)。
- 选股指标:计算过去 N 个交易日(例如 5 天)的平均换手率。
- 排序筛选:选取平均换手率最高的 M 只股票(例如 5 只)。
- 交易执行:
- 卖出:持仓中不在新选出的股票列表中的股票,全部卖出。
- 买入/调仓:对新选出的股票进行等权重买入(每只股票占用总资金的 1/M)。
- 运行频率:日线级别,每日开盘或收盘进行检测和交易。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
# 1. 设置策略参数
ContextInfo.holding_num = 5 # 持仓股票数量
ContextInfo.lookback_days = 5 # 计算过去多少天的平均换手率
ContextInfo.index_code = '000300.SH' # 股票池:沪深300
# 2. 设置账号(请替换为您自己的资金账号)
# 格式如:'6000000248',如果是回测模式,这里会自动使用虚拟账号
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 3. 设置回测参数(仅回测有效,实盘请在界面设置)
# 费率设置:万三
ContextInfo.set_commission(0, [0.0003, 0.0003, 0.0003, 0.0003, 0, 5])
# 滑点设置:0.02元
ContextInfo.set_slippage(1, 0.02)
def handlebar(ContextInfo):
# 仅在日线周期的每根K线结束或新K线开始时运行
# 这里我们选择在每根K线的第一个tick(即开盘)进行判断和交易
if not ContextInfo.is_new_bar():
return
# 1. 获取当前时间
barpos = ContextInfo.barpos
timetag = ContextInfo.get_bar_timetag(barpos)
current_date = timetag_to_datetime(timetag, '%Y%m%d')
print(f'当前交易日期: {current_date}')
# 2. 获取股票池(沪深300成分股)
# 注意:get_sector 获取的是当前成分股,回测中若需历史成分股需用 get_sector(code, timetag)
stock_list = ContextInfo.get_sector(ContextInfo.index_code, timetag)
if not stock_list:
print("未获取到成分股信息")
return
# 3. 获取交易日列表,用于确定计算换手率的时间窗口
# 获取包括当前日期在内的过去 lookback_days + 5 天的交易日,确保数据足够
# 我们需要用过去的数据来决策,所以截止日期是“昨天”
trading_dates = ContextInfo.get_trading_dates(stock_list[0], '', current_date, ContextInfo.lookback_days + 1, '1d')
if len(trading_dates) < ContextInfo.lookback_days + 1:
print("历史数据不足,跳过")
return
# 取过去N天(不包含今天,因为今天要根据过去的数据下单)
start_date = trading_dates[0]
end_date = trading_dates[-2] # 昨天的日期
# 4. 获取换手率数据
# get_turnover_rate 返回的是 DataFrame,Index为日期,Columns为股票代码
df_turnover = ContextInfo.get_turnover_rate(stock_list, start_date, end_date)
if df_turnover.empty:
print("未获取到换手率数据")
return
# 5. 计算平均换手率并排序
# 计算每只股票的均值
mean_turnover = df_turnover.mean()
# 降序排列
sorted_stocks = mean_turnover.sort_values(ascending=False)
# 取前 N 只
target_stocks = sorted_stocks.head(ContextInfo.holding_num).index.tolist()
print(f"今日选股: {target_stocks}")
# 6. 交易执行
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holdings = [obj.m_strInstrumentID for obj in positions if obj.m_nVolume > 0]
# 6.1 卖出不在目标列表中的股票
for stock in current_holdings:
if stock not in target_stocks:
# 目标比例设为0,即清仓
order_target_percent(stock, 0, 'fix', -1, ContextInfo, ContextInfo.account_id)
print(f"卖出: {stock}")
# 6.2 买入/调整目标股票
# 等权重分配,每只股票目标仓位 = 1 / 持仓数量
target_weight = 1.0 / ContextInfo.holding_num
for stock in target_stocks:
# 使用 order_target_percent 自动调整仓位到目标比例
# 价格类型 'fix' 和价格 -1 在 order_target_percent 中通常代表以市价或最新价成交(视具体回测撮合设置)
# 实盘中建议根据盘口价格指定,这里演示简化处理
order_target_percent(stock, target_weight, 'fix', -1, ContextInfo, ContextInfo.account_id)
print(f"买入/调仓: {stock} 目标权重: {target_weight}")
代码关键点解析
-
数据获取 (
get_turnover_rate):- QMT 提供了直接获取换手率的接口
ContextInfo.get_turnover_rate,这比手动用成交量除以流通股本更方便且准确。 - 注意:使用此接口前,请务必在 QMT 客户端的【数据管理】中下载补充好对应的日线行情数据和财务数据。
- QMT 提供了直接获取换手率的接口
-
时间窗口处理:
- 策略逻辑通常是“利用过去 N 天的数据,在今天开盘进行交易”。
- 代码中使用
ContextInfo.get_trading_dates获取交易日历,取trading_dates[-2]作为数据截止日期(即昨天),避免用到今天的未来数据(Look-ahead bias)。
-
交易函数 (
order_target_percent):- 这是一个非常方便的高级交易函数。它会自动计算当前持仓与目标仓位的差额,并执行买入或卖出操作。
- 例如:如果目标权重是 20%,当前持仓已经是 20%,则不操作;如果是 0%,则买入;如果是 30%,则卖出多余部分。
-
股票池 (
get_sector):- 使用了
000300.SH(沪深300) 作为基础池。传入timetag参数是为了在回测时获取当时的历史成分股,防止幸存者偏差。
- 使用了
如何运行
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略,将上述代码复制进去。
- 修改
init函数中的ContextInfo.account_id为您的真实资金账号(如果是模拟/回测,QMT会自动分配,但建议显式设置)。 - 补充数据:确保下载了沪深300成分股的日线数据。
- 点击【回测】查看历史表现,或在【模型交易】中加载进行实盘/模拟盘运行。
常见问题与建议
- 数据缺失:如果打印出“未获取到换手率数据”,请检查是否在数据管理中下载了对应品种的日线数据。
- 停牌处理:
get_turnover_rate返回的数据中,停牌期间通常为 NaN 或 0。mean()计算时会自动忽略 NaN,但如果某只股票长期停牌,换手率为 0,自然会被排序过滤掉,符合策略逻辑。 - 实盘注意:实盘运行时,建议结合
ContextInfo.get_market_data_ex获取实时行情来指定更精确的委托价格(如对手价、买一卖一价),而不是简单的使用默认价格。
Q&A
Q: 如何将股票池改为全市场股票?
A: 可以将 ContextInfo.index_code 修改为 '沪深A股',并使用 ContextInfo.get_stock_list_in_sector('沪深A股') 来获取全市场代码。但请注意,全市场计算量较大,回测速度会变慢。
Q: 为什么使用 order_target_percent 而不是 order_lots?
A: order_target_percent 更适合这种定期调仓的策略。它能自动根据当前总资产计算需要买入多少股,并且自动处理卖出逻辑(比如从持仓 1000 股调整到 0 股),大大简化了代码逻辑。
Q: 换手率数据需要额外下载吗?
A: 是的。QMT 的基础行情数据通常包含开高低收和成交量,但换手率可能涉及财务数据(流通股本)。建议在【数据管理】中同时补充【日线数据】和【财务数据】。