问题描述
解决方案
这是一个基于**净利润增长率(Net Profit Growth Rate)**的选股策略设计与实现。
策略逻辑分析
- 核心指标:使用 QMT 内置因子库中的
Growth.NetProfitGrowRate(净利润增长率)。 - 股票池:为保证流动性和数据质量,本示例使用 沪深300 成分股作为基础股票池。
- 选股逻辑:
- 每隔固定周期(例如 20 个交易日,约一个月)进行一次调仓。
- 获取股票池中所有股票的最新净利润增长率数据。
- 剔除数据缺失的股票。
- 按照增长率从大到小排序。
- 选取增长率最高的 10只 股票。
- 交易逻辑:
- 卖出:平仓当前持仓中不在新选出的 10 只股票名单中的股票。
- 买入:对新选出的 10 只股票进行等权重买入(即每只股票占用总资金的 1/10)。
QMT 策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置账号(请替换为您的实际资金账号)
# ContextInfo.set_account('您的资金账号')
# 2. 策略参数设置
ContextInfo.target_num = 10 # 持仓股票数量
ContextInfo.rebalance_period = 20 # 调仓周期(交易日)
ContextInfo.days_counter = 0 # 计数器
# 3. 设置股票池:这里使用沪深300作为选股范围
# 注意:get_stock_list_in_sector 获取的是当前成分股,回测时建议使用 get_sector 获取历史成分股
# 这里为了演示方便,使用当前沪深300成分股
ContextInfo.stock_pool = ContextInfo.get_stock_list_in_sector('沪深300')
ContextInfo.set_universe(ContextInfo.stock_pool)
print("策略初始化完成,股票池大小:", len(ContextInfo.stock_pool))
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 1. 判断是否到达调仓周期
if ContextInfo.days_counter % ContextInfo.rebalance_period != 0:
ContextInfo.days_counter += 1
return
# 重置计数器并增加
ContextInfo.days_counter += 1
print(f"=== 开始调仓: {ContextInfo.barpos} ===")
# 2. 获取当前时间
# get_bar_timetag 返回的是毫秒时间戳
timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
current_date = timetag_to_datetime(timetag, '%Y%m%d')
# 3. 获取因子数据:净利润增长率
# 因子名称:Growth.NetProfitGrowRate
factor_name = 'Growth.NetProfitGrowRate'
stock_list = ContextInfo.stock_pool
# 获取因子数据,返回的是 pandas DataFrame 或 Series
# 注意:get_factor_data 的时间参数是闭区间
factor_data = ContextInfo.get_factor_data(
[factor_name],
stock_list,
current_date,
current_date
)
# 4. 数据处理与选股
if factor_data is None or len(factor_data) == 0:
print("未获取到因子数据,跳过本次调仓")
return
# 将数据转换为 DataFrame 方便处理
# get_factor_data 返回结构通常是 {stock: {date: value}} 或者 DataFrame
# 在单日查询下,通常需要整理格式
df_factor = pd.DataFrame(factor_data)
# 如果返回的是空或者全NaN,跳过
if df_factor.empty:
print("因子数据为空")
return
# 提取特定因子列,并去除空值
# 注意:根据返回数据的结构,可能需要转置或索引调整,这里假设返回的是以股票代码为索引的结构
if factor_name in df_factor.columns:
series_factor = df_factor[factor_name].dropna()
else:
# 尝试处理多重索引或不同返回格式的情况
# 这种情况下通常直接取 values 即可,视具体版本数据结构而定
# 这里做一个通用的处理尝试
try:
series_factor = df_factor.iloc[:, 0].dropna()
except:
print("数据格式解析失败")
return
# 排序:按净利润增长率从大到小排序
sorted_stocks = series_factor.sort_values(ascending=False)
# 选取前 N 只股票
target_stocks = sorted_stocks.head(ContextInfo.target_num).index.tolist()
print(f"选中股票: {target_stocks}")
# 5. 交易执行
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'position')
current_holdings = [obj.m_strInstrumentID for obj in positions]
# 卖出不在目标列表中的股票
for stock in current_holdings:
if stock not in target_stocks:
# 目标价值设为0即为清仓
order_target_value(stock, 0, ContextInfo, ContextInfo.accid)
print(f"卖出平仓: {stock}")
# 买入目标股票
# 计算每只股票的目标市值(总资产 / 目标数量)
# 获取账户总资产
account_info = ContextInfo.get_trade_detail_data(ContextInfo.accid, 'stock', 'account')
if len(account_info) > 0:
total_asset = account_info[0].m_dBalance
target_value_per_stock = total_asset / ContextInfo.target_num
for stock in target_stocks:
# 使用 order_target_value 自动调整仓位到目标市值
order_target_value(stock, target_value_per_stock, ContextInfo, ContextInfo.accid)
print(f"买入/调仓: {stock}, 目标市值: {target_value_per_stock}")
else:
print("未获取到账户资产信息,无法下单")
代码关键点解析
-
数据获取 (
get_factor_data):- 我们使用了
Growth.NetProfitGrowRate这个因子。QMT 的因子库非常丰富,直接调用因子比自己去查财报原始数据(get_financial_data)并计算同比增长率要方便且准确得多。 - 该函数返回的数据通常是
pandas格式,非常适合进行排序和筛选。
- 我们使用了
-
时间处理:
- QMT 的
barpos对应的时间是毫秒级时间戳,必须使用timetag_to_datetime转换为YYYYMMDD字符串格式,才能作为参数传给数据获取接口。
- QMT 的
-
交易执行 (
order_target_value):- 使用
order_target_value是最稳健的调仓方式。 - 如果持有该股票且市值低于目标,它会自动买入差额。
- 如果持有该股票且市值高于目标(例如股价大涨),它会自动卖出部分止盈。
- 如果未持有,它会买入。
- 如果目标价值为 0,它会清仓。
- 使用
-
容错处理:
- 代码中加入了对数据为空 (
dropna,emptycheck) 的判断,防止因为某些股票停牌或财报数据缺失导致策略报错停止。
- 代码中加入了对数据为空 (
如何运行
- 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 重要:在
init函数中,取消注释ContextInfo.set_account并填入您的真实或模拟资金账号。 - 点击“编译”检查是否有语法错误。
- 选择回测区间(建议包含财报发布期,如 4月、8月、10月),设置初始资金。
- 点击“运行”或“回测”。
Q&A
Q: 为什么使用 get_factor_data 而不是 get_financial_data?
A: get_financial_data 获取的是原始财报数据(如“净利润”绝对值),你需要自己写代码去获取去年的数据并计算增长率 (今年-去年)/去年。而 get_factor_data 直接提供了计算好的因子(如 Growth.NetProfitGrowRate),处理了除权、财报调整等复杂情况,开发效率更高。
Q: 如何查看还有哪些因子可用?
A: 在 QMT 的帮助文档中,查找“多因子数据接口”或“股票因子库”章节(通常在附录 5.2),可以看到如 Valuation_and_Market_Cap.PE (市盈率), Profitability_and_Earning Quality.ROE (净资产收益率) 等大量因子。
Q: 策略回测时没有交易是怎么回事?
A: 1. 检查 ContextInfo.set_account 是否设置正确。2. 检查回测时间段内是否有数据(如果是节假日或数据未下载,可能取不到因子数据)。3. 确保在“数据管理”中下载了对应的“多因子数据”和“日线数据”。