🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中利用 API 每日自动更新股票池?

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

问题描述

请帮我写一个动态筛选股票池的脚本

解决方案

这是一个基于 QMT 平台的动态选股策略脚本。

策略逻辑说明

该脚本实现了以下筛选逻辑:

  1. 股票池范围:以 沪深300 成分股为基础池。
  2. 剔除垃圾股:剔除 *ST、ST 股票,剔除 停牌 股票。
  3. 基本面筛选:筛选市盈率(PE)在 0 到 30 之间的股票。
  4. 技术面筛选:筛选当前收盘价 大于 20日均线 的股票(多头趋势)。
  5. 执行频率:在日线级别上,每个交易日运行一次筛选,并更新系统的股票池 (ContextInfo.set_universe)。

QMT 策略代码

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

def init(ContextInfo):
    """
    初始化函数
    """
    print("策略初始化...")
    # 设置基准,这里设为沪深300
    ContextInfo.benchmark = '000300.SH'
    # 设定初始股票池为空,后续动态更新
    ContextInfo.set_universe([])
    
    # 定义筛选参数
    ContextInfo.target_sector = '沪深300' # 基础板块
    ContextInfo.pe_min = 0               # 最小市盈率
    ContextInfo.pe_max = 30              # 最大市盈率
    ContextInfo.ma_period = 20           # 均线周期

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 为了避免盘中每个Tick都计算,我们只在K线生成的新Bar时计算
    # 如果是回测模式,每根K线都会触发;如果是实盘,建议配合定时器或is_new_bar使用
    if not ContextInfo.is_new_bar():
        return

    # 获取当前K线的时间
    current_date = timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y%m%d')
    print(f"当前日期: {current_date},开始执行选股...")

    # 1. 获取基础股票池 (沪深300)
    stock_list = ContextInfo.get_stock_list_in_sector(ContextInfo.target_sector)
    if not stock_list:
        print("未获取到板块成分股")
        return

    # 2. 剔除 ST 和 停牌股
    valid_stocks = []
    for code in stock_list:
        # 剔除停牌
        if ContextInfo.is_suspended_stock(code):
            continue
        
        # 剔除 ST / *ST
        name = ContextInfo.get_stock_name(code)
        if 'ST' in name:
            continue
            
        valid_stocks.append(code)

    if not valid_stocks:
        print("剔除ST和停牌后无剩余股票")
        return

    # 3. 获取基本面数据 (PE - 市盈率)
    # 使用 get_factor_data 获取因子数据
    # 字段: Valuation_and_Market_Cap.PE (市盈率)
    factor_fields = ['Valuation_and_Market_Cap.PE']
    
    # 注意:get_factor_data 获取的是时间序列数据,我们需要取最后一天
    # 这里简单处理,start和end都设为当前日期
    factor_data = ContextInfo.get_factor_data(
        factor_fields, 
        valid_stocks, 
        current_date, 
        current_date
    )
    
    # 筛选 PE 符合条件的股票
    fundamental_select = []
    
    # 处理返回的数据格式,通常是 Dict 或 DataFrame
    # 如果返回的是 Dict {code: df}
    if isinstance(factor_data, dict):
        for code in valid_stocks:
            if code in factor_data:
                df = factor_data[code]
                if not df.empty:
                    pe_val = df['Valuation_and_Market_Cap.PE'].iloc[-1]
                    if ContextInfo.pe_min < pe_val < ContextInfo.pe_max:
                        fundamental_select.append(code)
    # 如果返回的是 DataFrame (视版本和参数而定)
    elif isinstance(factor_data, pd.DataFrame):
         # 这里假设是 Panel 转成的 DF 或者直接是 DF,具体需根据实盘调试
         # 为保证稳健性,建议使用上面的 dict 遍历方式,或者打印 type(factor_data) 确认
         pass

    if not fundamental_select:
        print("基本面筛选后无股票")
        return

    # 4. 获取技术面数据 (收盘价 和 均线)
    # 获取过去 ma_period + 5 天的数据,确保能计算出 MA
    market_data = ContextInfo.get_market_data_ex(
        ['close'], 
        fundamental_select, 
        period='1d', 
        count=ContextInfo.ma_period + 1,
        dividend_type='front' # 前复权
    )

    final_pool = []
    
    for code in fundamental_select:
        if code in market_data:
            df_price = market_data[code]
            if len(df_price) >= ContextInfo.ma_period:
                # 计算 MA20
                ma_value = df_price['close'].rolling(window=ContextInfo.ma_period).mean().iloc[-1]
                current_price = df_price['close'].iloc[-1]
                
                # 筛选:收盘价 > 20日均线
                if current_price > ma_value:
                    final_pool.append(code)

    # 5. 更新股票池
    print(f"选股完成,共选中 {len(final_pool)} 只股票")
    if len(final_pool) > 0:
        print(f"部分选中股票示例: {final_pool[:5]}")
        
    # 将选出的股票设定为当前的 ContextInfo 股票池
    # 后续的交易函数可以直接对这个池子进行操作
    ContextInfo.set_universe(final_pool)

    # 可选:在这里直接根据 final_pool 进行买卖操作
    # rebalance_portfolio(ContextInfo, final_pool) 

代码关键点解析

  1. # -*- coding: gbk -*-:

    • QMT 的 Python 编辑器默认编码通常是 GBK,加上这行可以防止中文注释乱码。
  2. ContextInfo.get_stock_list_in_sector:

    • 用于获取板块成分股,支持 '沪深300'、'中证500'、'上证50' 等,也可以是用户自定义的板块名称。
  3. ContextInfo.get_factor_data:

    • 这是 QMT 获取基本面因子的核心接口。
    • Valuation_and_Market_Cap.PE 是市盈率因子。如果需要市净率可以使用 Valuation_and_Market_Cap.PB
    • 注意:使用此接口前,请确保在 QMT 客户端的【数据管理】中下载了对应的【多因子数据】。
  4. ContextInfo.get_market_data_ex:

    • 这是获取行情数据的高效接口。
    • count=ContextInfo.ma_period + 1:为了计算 20 日均线,我们需要至少 20 根 K 线的数据。
    • dividend_type='front':技术分析通常使用前复权数据,以消除分红配股对价格走势的干扰。
  5. ContextInfo.set_universe(final_pool):

    • 这一步将筛选出的股票列表更新到策略环境中。如果你在图表上运行,这不会改变图表显示的股票,但会改变策略内部逻辑(如 get_universe())获取到的列表。

如何使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略交易】。
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 重要:在运行或回测前,请务必在【数据管理】中补充下载历史行情数据多因子数据
  5. 点击【运行】或【回测】。

Q&A

Q: 如何修改筛选条件,例如改成筛选市净率(PB)小于 2 的股票?
A: 修改 init 函数中的参数,并更改 get_factor_data 的字段。

  1. init 中添加 ContextInfo.pb_max = 2
  2. factor_fields 改为 ['Valuation_and_Market_Cap.PB']
  3. 在筛选逻辑中判断 df['Valuation_and_Market_Cap.PB'].iloc[-1] < ContextInfo.pb_max

Q: 为什么 get_factor_data 返回的数据是空的?
A: 这通常是因为本地没有下载多因子数据。请在 QMT 客户端顶部菜单栏找到【数据】->【数据管理】,选择【多因子数据】,勾选对应的市场和时间段进行下载补充。

Q: 这个脚本会自动买入选中的股票吗?
A: 目前的代码只负责筛选(生成股票池)。如果需要自动交易,需要在 handlebar 函数的最后添加交易逻辑,例如遍历 final_pool 调用 passorderorder_target_value 函数进行下单。