🚀 AI 一键生成 qmt 策略代码

QMT 因子选股:基于净利润增长率 (`NetProfitGrowRate`) 的策略实现

qmt | 发布时间: 2025/12/22
以下内容由 EasyQuant 生成。

问题描述

怎么写一个基于净利润增长率的选股策略?

解决方案

这是一个基于**净利润增长率(Net Profit Growth Rate)**的选股策略设计与实现。

策略逻辑分析

  1. 核心指标:使用 QMT 内置因子库中的 Growth.NetProfitGrowRate(净利润增长率)。
  2. 股票池:为保证流动性和数据质量,本示例使用 沪深300 成分股作为基础股票池。
  3. 选股逻辑
    • 每隔固定周期(例如 20 个交易日,约一个月)进行一次调仓。
    • 获取股票池中所有股票的最新净利润增长率数据。
    • 剔除数据缺失的股票。
    • 按照增长率从大到小排序。
    • 选取增长率最高的 10只 股票。
  4. 交易逻辑
    • 卖出:平仓当前持仓中不在新选出的 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("未获取到账户资产信息,无法下单")

代码关键点解析

  1. 数据获取 (get_factor_data):

    • 我们使用了 Growth.NetProfitGrowRate 这个因子。QMT 的因子库非常丰富,直接调用因子比自己去查财报原始数据(get_financial_data)并计算同比增长率要方便且准确得多。
    • 该函数返回的数据通常是 pandas 格式,非常适合进行排序和筛选。
  2. 时间处理:

    • QMT 的 barpos 对应的时间是毫秒级时间戳,必须使用 timetag_to_datetime 转换为 YYYYMMDD 字符串格式,才能作为参数传给数据获取接口。
  3. 交易执行 (order_target_value):

    • 使用 order_target_value 是最稳健的调仓方式。
    • 如果持有该股票且市值低于目标,它会自动买入差额。
    • 如果持有该股票且市值高于目标(例如股价大涨),它会自动卖出部分止盈。
    • 如果未持有,它会买入。
    • 如果目标价值为 0,它会清仓。
  4. 容错处理:

    • 代码中加入了对数据为空 (dropna, empty check) 的判断,防止因为某些股票停牌或财报数据缺失导致策略报错停止。

如何运行

  1. 打开 QMT 客户端,进入“模型研究”或“策略编辑器”。
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 重要:在 init 函数中,取消注释 ContextInfo.set_account 并填入您的真实或模拟资金账号。
  5. 点击“编译”检查是否有语法错误。
  6. 选择回测区间(建议包含财报发布期,如 4月、8月、10月),设置初始资金。
  7. 点击“运行”或“回测”。

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. 确保在“数据管理”中下载了对应的“多因子数据”和“日线数据”。