🚀 AI 一键生成 qmt 策略代码

QMT 平台如何构建一个简单的多因子选股模型?

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

问题描述

如何编写一个结合估值和动量因子的多因子选股模型?

解决方案

这是一个结合了**估值因子(PE)动量因子(20日涨跌幅)**的多因子选股策略。

策略逻辑说明

  1. 选股范围:沪深300成分股。
  2. 因子选择
    • 估值因子:市盈率(PE),使用 QMT 内置因子 Valuation_and_Market_Cap.PE。逻辑是低估值优先(PE越小越好)。
    • 动量因子:20日价格动量,使用 QMT 内置因子 Trend.REVS20。逻辑是动量效应(过去20日涨幅越高越好)。
  3. 打分机制
    • 对股票池内的股票按 PE 从小到大排名。
    • 对股票池内的股票按 动量 从大到小排名。
    • 将两个排名相加得到总分,总分越低代表综合表现越好。
  4. 调仓周期:按月调仓(每月月初运行一次)。
  5. 交易执行:选取总分排名前 20 的股票,等权重买入。不在名单中的持仓股票卖出。

QMT 策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 策略参数设置
    ContextInfo.target_num = 20       # 持仓股票数量
    ContextInfo.benchmark = '000300.SH' # 股票池基准(沪深300)
    ContextInfo.account_id = '600000248' # 请修改为您的资金账号
    
    # 2. 设置账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 3. 设置股票池
    # 获取沪深300成分股
    stock_list = ContextInfo.get_sector(ContextInfo.benchmark)
    ContextInfo.set_universe(stock_list)
    
    # 4. 调仓控制变量
    ContextInfo.last_month = -1 # 用于记录上一次调仓的月份

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 1. 获取当前时间
    # get_bar_timetag 返回的是毫秒时间戳
    timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
    # 转换为日期字符串 YYYYMMDD
    current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
    # 获取当前月份
    current_month = int(current_date_str[4:6])
    
    # 2. 判断是否换月(简单的月度调仓逻辑)
    if current_month == ContextInfo.last_month:
        return
    
    # 更新月份记录
    ContextInfo.last_month = current_month
    print(f'触发调仓,当前日期: {current_date_str}')

    # 3. 获取因子数据
    # 使用 QMT 内置的多因子库
    # Valuation_and_Market_Cap.PE : 市盈率
    # Trend.REVS20 : 20日价格动量
    factor_fields = ['Valuation_and_Market_Cap.PE', 'Trend.REVS20']
    stock_list = ContextInfo.get_universe()
    
    # 获取当天的因子数据
    # 注意:get_factor_data 返回的数据格式取决于参数。
    # 传入股票列表和单日时间,返回 DataFrame (Index=股票代码, Columns=因子名)
    df_factors = ContextInfo.get_factor_data(
        factor_fields, 
        stock_list, 
        current_date_str, 
        current_date_str
    )
    
    if df_factors is None or df_factors.empty:
        print("未获取到因子数据,跳过本次调仓")
        return

    # 4. 数据清洗与打分
    # 去除空值
    df_factors.dropna(inplace=True)
    
    # 过滤掉 PE 为负(亏损)的股票,通常估值策略只看正PE
    df_factors = df_factors[df_factors['Valuation_and_Market_Cap.PE'] > 0]
    
    if df_factors.empty:
        return

    # 计算排名 (Rank)
    # PE: 升序排名 (值越小排名越靠前,rank值越小)
    df_factors['rank_pe'] = df_factors['Valuation_and_Market_Cap.PE'].rank(ascending=True)
    
    # 动量: 降序排名 (值越大排名越靠前,rank值越小)
    df_factors['rank_mom'] = df_factors['Trend.REVS20'].rank(ascending=False)
    
    # 计算综合得分 (等权重相加)
    df_factors['score'] = df_factors['rank_pe'] + df_factors['rank_mom']
    
    # 按得分升序排列 (分数越低越好)
    df_factors.sort_values(by='score', ascending=True, inplace=True)
    
    # 选取前 N 只股票
    target_list = df_factors.head(ContextInfo.target_num).index.tolist()
    print(f"选股结果: {target_list}")

    # 5. 交易执行
    # 计算单只股票的目标权重 (等权重)
    if len(target_list) > 0:
        target_weight = 1.0 / len(target_list)
    else:
        target_weight = 0.0

    # 获取当前持仓
    positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
    holding_list = [obj.m_strInstrumentID for obj in positions]
    
    # 卖出逻辑:不在目标列表中的股票,权重设为0(清仓)
    for code in holding_list:
        if code not in target_list:
            order_target_percent(code, 0, ContextInfo, ContextInfo.account_id)
    
    # 买入/调仓逻辑:在目标列表中的股票,调整至目标权重
    for code in target_list:
        order_target_percent(code, target_weight, ContextInfo, ContextInfo.account_id)

代码关键点解析

  1. get_factor_data 接口

    • 这是 QMT 获取基本面和技术面因子的核心接口。
    • Valuation_and_Market_Cap.PE 对应市盈率。
    • Trend.REVS20 对应20日收益率(动量)。
    • 该接口直接返回 pandas DataFrame,非常适合进行向量化计算(如 rank 排序)。
  2. 打分逻辑

    • 使用 rank() 函数将绝对数值转换为相对排名。这消除了不同因子量纲(PE是几十倍,动量是百分比)不一致的问题。
    • PE 越低越好,所以 ascending=True
    • 动量越高越好,所以 ascending=False
  3. 交易执行

    • 使用 order_target_percent 函数。这是一个非常方便的高级交易函数,它会自动计算当前持仓与目标仓位的差额,并发出相应的买入或卖出指令。例如,如果目标权重是 5%,当前持有 2%,它会自动买入 3%。
  4. 数据清洗

    • 代码中增加了 df_factors.dropna()PE > 0 的过滤,这是量化实盘中防止脏数据导致报错或买入亏损股的必要步骤。

常见问题 (Q&A)

Q: 如何查看 QMT 支持的所有因子名称?
A: 在 QMT 软件界面的“帮助”文档中,或者在策略编辑器中查看“多因子库”部分。文档附录通常有详细的因子表名和字段名对照表(如 Valuation_and_Market_Cap 表下的 PE)。

Q: 为什么使用 order_target_percent 而不是 passorder
A: passorder 是底层的下单接口,需要自己计算买卖数量。对于选股策略,我们通常关注的是“仓位占比”,order_target_percent 封装了计算逻辑,能自动处理买卖方向和数量,代码更简洁且不易出错。

Q: 这个策略可以在回测和实盘中直接运行吗?
A: 可以。但请注意:

  1. 账号ID:代码中的 ContextInfo.account_id 需要修改为你实际的资金账号。
  2. 数据下载:运行前请确保在 QMT 的“数据管理”中下载了对应的历史行情数据和多因子数据。如果没有下载多因子数据,get_factor_data 将无法返回结果。