🚀 AI 一键生成 qmt 策略代码

QMT 量化选股:如何利用 get_last_volume API 构建流动性策略?

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

问题描述

在做选股策略时,如何用代码过滤掉那些盘子太小或者成交不活跃的股票?

解决方案

在 QMT 中过滤“小盘股”和“成交不活跃股”是量化策略中非常基础且重要的风控步骤。这通常涉及到两个核心指标的计算:

  1. 流通市值(Circulating Market Cap):用于衡量盘子大小。
    • 公式:流通市值 = 最新收盘价 × 流通股本
  2. 成交额(Turnover Amount):用于衡量活跃度。
    • 通常使用过去 N 天(如 20 天)的日均成交额来平滑单日的异常波动。

核心思路

  1. 获取数据:使用 get_market_data_ex 批量获取股票池的收盘价和成交额历史数据。
  2. 获取股本:使用 get_last_volume 获取流通股本(注意:回测严谨模式下建议用财务数据接口 get_financial_data 获取历史股本,实盘或简单示例可用 get_last_volume)。
  3. 计算与过滤:遍历股票,计算市值和均额,剔除不满足阈值的标的。

代码实现

以下是一个完整的策略示例代码。它定义了一个过滤函数,并在 handlebar 中调用,筛选出符合条件的股票。

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

def init(ContextInfo):
    # 1. 设置过滤阈值
    # 最小流通市值:例如 50亿 (单位:元)
    ContextInfo.min_mkt_cap = 50 * 10000 * 10000
    # 最小日均成交额:例如 3000万 (单位:元)
    ContextInfo.min_avg_amount = 3000 * 10000
    
    # 2. 设定基础股票池(这里以沪深300成分股为例,实际可设为全A)
    # 注意:实际运行时建议在界面设置股票池,或者用 get_stock_list_in_sector('沪深A股')
    ContextInfo.stock_pool = ContextInfo.get_stock_list_in_sector('沪深300')
    
    print("策略初始化完成,阈值设置:市值>{}亿, 日均成交>{}万".format(
        ContextInfo.min_mkt_cap/1e8, ContextInfo.min_avg_amount/1e4))

def handlebar(ContextInfo):
    # 为了演示,我们只在最后一根K线(如果是实盘就是最新时刻)运行过滤逻辑
    if not ContextInfo.is_last_bar():
        return

    # 获取当前时间
    current_date = ContextInfo.get_bar_timetag(ContextInfo.barpos)
    
    # 执行过滤函数
    selected_stocks = filter_stocks(ContextInfo, ContextInfo.stock_pool)
    
    print("=" * 30)
    print(f"原始股票数量: {len(ContextInfo.stock_pool)}")
    print(f"过滤后股票数量: {len(selected_stocks)}")
    print(f"部分入选股票: {selected_stocks[:5]}")
    print("=" * 30)
    
    # 在这里可以继续执行后续的择时或下单逻辑
    # ...

def filter_stocks(ContextInfo, stock_list):
    """
    股票过滤核心函数
    :param stock_list: 待筛选的股票列表
    :return: 筛选后的股票列表
    """
    filtered_list = []
    
    # 1. 批量获取行情数据
    # 获取过去20天的数据,用于计算日均成交额
    # fields: close(收盘价), amount(成交额)
    data_map = ContextInfo.get_market_data_ex(
        ['close', 'amount'],
        stock_list,
        period='1d',
        count=20,
        subscribe=False
    )
    
    for stock in stock_list:
        if stock not in data_map:
            continue
            
        df = data_map[stock]
        if df is None or df.empty:
            continue
            
        # 2. 计算活跃度指标 (20日均成交额)
        # 如果数据不足20天(如新股),取现有数据的均值
        avg_amount = df['amount'].mean()
        
        # 如果成交额不达标,直接跳过
        if avg_amount < ContextInfo.min_avg_amount:
            continue
            
        # 3. 计算市值指标
        # 获取最新收盘价
        last_close = df['close'].iloc[-1]
        
        # 获取流通股本 (单位:股)
        # get_last_volume 获取的是最新流通股本。
        # 注意:如果是做非常严谨的历史回测,建议使用 get_financial_data 获取历史时刻的股本,
        # 避免因股本变动(如送转股)导致的未来函数。实盘使用 get_last_volume 即可。
        circulating_shares = ContextInfo.get_last_volume(stock)
        
        # 计算流通市值
        mkt_cap = last_close * circulating_shares
        
        # 如果市值不达标,跳过
        if mkt_cap < ContextInfo.min_mkt_cap:
            continue
            
        # 4. 通过筛选
        filtered_list.append(stock)
        
    return filtered_list

代码关键点解析

  1. ContextInfo.get_market_data_ex:

    • 这是获取行情数据的核心接口。我们请求了 ['close', 'amount'] 两个字段。
    • count=20 表示获取过去 20 根 K 线的数据,这样我们可以计算 df['amount'].mean() 来得到 20 日均成交额,这比只看当天的成交额更能反映股票的真实流动性。
  2. ContextInfo.get_last_volume(stock):

    • 该函数返回指定股票的最新流通股本(单位:股)。
    • 市值计算公式流通市值 = 收盘价 * 流通股本
  3. 逻辑判断:

    • if avg_amount < ContextInfo.min_avg_amount: 剔除成交低迷的股票。
    • if mkt_cap < ContextInfo.min_mkt_cap: 剔除小盘股。

进阶提示:关于回测中的“未来函数”

在上述代码中,get_last_volume 获取的是当前最新的股本。如果你在 2020 年的数据上回测,但使用的是 2023 年的股本数据,可能会导致计算出的市值不准确(因为期间可能有增发、送转)。

严谨的回测写法应使用财务数据接口获取历史股本:

def get_historical_mkt_cap(ContextInfo, stock, current_price, timetag):
    # 获取股本结构中的 'circulating_capital' (已上市流通A股)
    # 注意:财务数据返回单位通常是 '股' 或 '万股',需根据实际数据确认,QMT通常返回基本单位
    field_list = ['CAPITALSTRUCTURE.circulating_capital']
    
    # 将时间戳转为 YYYYMMDD 字符串
    import datetime
    date_str = datetime.datetime.fromtimestamp(timetag/1000).strftime('%Y%m%d')
    
    # 获取财务数据
    fin_data = ContextInfo.get_financial_data(field_list, [stock], date_str, date_str)
    
    if fin_data is not None and not fin_data.empty:
        # 假设返回的是 DataFrame
        if isinstance(fin_data, pd.DataFrame):
             # 根据返回结构取值,通常需要处理一下索引
             try:
                 shares = fin_data.iloc[0][0]
                 return shares * current_price
             except:
                 return 0
    return 0

注:实盘交易或粗略筛选时,使用 get_last_volume 效率更高且代码更简洁。


Q&A

Q: 为什么要用 20 日均成交额而不是当天的成交额?
A: 单日的成交额容易受到突发事件、停牌复牌或市场情绪的干扰。使用 20 日(约一个月)的平均值可以更平滑地反映股票的真实流动性水平,避免误杀或误选。

Q: get_last_volume 返回的单位是什么?
A: 返回的是“股”数。例如某股票流通股本为 10 亿股,该函数返回 1,000,000,000。计算市值时直接乘以股价即可得到以“元”为单位的市值。

Q: 这段代码可以直接放在回测里跑吗?
A: 可以。代码中包含了 # -*- coding: gbk -*- 声明,且使用了标准的 QMT API。只需确保你在 QMT 客户端中已经下载了对应的历史行情数据(日线数据)。