🚀 AI 一键生成 qmt 策略代码

QMT 新手指南:Python 策略编程的 5 个常见陷阱及解决方法

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

问题描述

我刚开始学写真仓策略,有哪些常见的编程错误需要注意避免?

解决方案

在QMT平台上从回测转向真仓(实盘)交易时,环境的差异会导致许多在回测中不易察觉的问题暴露出来。以下是针对QMT Python API整理的常见编程错误及避免方法,涵盖了信号处理、下单逻辑、数据获取和环境配置四个维度。

QMT 真仓策略开发:常见编程错误与避坑指南

1. 信号闪烁与“未来函数”陷阱

在实盘中,最致命的错误是信号闪烁(Repainting)。这是指在当前K线尚未走完时,根据盘中临时价格发出了信号,但K线收盘后信号消失,导致实盘买入但回测中没有该笔交易。

错误示例:直接使用当前Bar的收盘价

handlebar 函数中,对于最后一根K线(实时K线),close 价格是随 Tick 变动的。

# 错误写法
def handlebar(ContextInfo):
    # 获取当前K线数据
    close = ContextInfo.get_market_data(['close'])
    # 如果盘中价格突破,立即下单
    if close > ma20: 
        passorder(...) 

后果:盘中价格瞬间突破MA20触发买入,但收盘回落跌破MA20。你的持仓还在,但策略逻辑上不该持有。

正确做法:

  1. 使用上一根K线判断:只依据已完成的K线(barpos - 1)做决策。
  2. 固定时间下单:如果必须做日内,配合 ContextInfo.get_tick_timetag() 判断时间。
  3. 利用 is_last_baris_new_bar
# 正确写法:在下一根K线的起始时刻,基于上一根K线的收盘价下单
def handlebar(ContextInfo):
    # 如果不是最新的一根K线,或者是最新K线但不是新的一根(即盘中Tick),则跳过
    if not ContextInfo.is_last_bar():
        return
    
    # 只有在实盘中,新K线生成的第一个Tick才执行逻辑(避免盘中重复计算)
    # 注意:is_new_bar() 在回测和实盘表现略有不同,需结合具体逻辑
    if ContextInfo.is_new_bar():
        # 获取历史数据(不包含当前正在跳动的K线)
        history_data = ContextInfo.get_market_data_ex(..., count=2) 
        # 使用 history_data.iloc[-2] (上一根) 进行判断

2. 下单逻辑与重复交易(Order Spamming)

QMT的 handlebar 在实盘模式下,最后一根K线每收到一个Tick(分笔数据)就会运行一次。如果不加控制,策略会在几秒钟内把资金全部打光。

错误示例:未检查持仓或未控制频率

# 错误写法
def handlebar(ContextInfo):
    # 只要条件满足,每个Tick都会触发一次下单
    if condition:
        passorder(23, 1101, account, code, 5, -1, 100, ContextInfo)

正确做法:

  1. 检查持仓:下单前必须查询 get_trade_detail_data 确认当前没有持仓。
  2. 全局变量控制:使用 ContextInfo 存储状态标记。
# 正确写法
def init(ContextInfo):
    ContextInfo.has_ordered = False # 初始化标记

def handlebar(ContextInfo):
    # 每日开盘重置标记(示例逻辑)
    if is_new_day(ContextInfo): 
        ContextInfo.has_ordered = False

    if condition and not ContextInfo.has_ordered:
        passorder(...)
        ContextInfo.has_ordered = True # 标记已下单

3. 数据获取接口的返回值类型混淆

QMT的 get_market_data 接口非常灵活,但也是新手噩梦。根据传入参数的不同(单股vs多股,单时间点vs时间序列),它返回的数据类型会在 pandas.Seriespandas.DataFramepandas.Panel 之间变化。

常见错误:

假设返回的是 DataFrame,结果只传了一只股票导致返回了 Series,代码报错 AttributeError

建议做法:

  1. 优先使用 get_market_data_ex:该接口返回格式更统一(字典形式 {code: DataFrame}),不易出错。
  2. 明确参数:始终清楚自己是在请求一只股票还是一篮子股票。
# 推荐写法
def handlebar(ContextInfo):
    # 获取多只股票数据,返回字典
    data_map = ContextInfo.get_market_data_ex(
        ['close'], 
        ['600000.SH', '000001.SZ'], 
        period='1d', 
        count=10
    )
    
    # 即使只传一只股票,也建议按列表传,处理方式保持一致
    df_600000 = data_map['600000.SH'] # 这是一个DataFrame

4. 忽略停牌与涨跌停

在回测中,QMT可能会自动处理停牌(取决于 skip_paused 参数),但在真仓交易中,向停牌或跌停的股票发送卖单,或者向涨停的股票发送买单,会导致废单或无法成交。

避坑指南:

  1. 判断停牌:使用 ContextInfo.is_suspended_stock(code)
  2. 判断涨跌停:获取当天的 UpStopPriceDownStopPrice(通过 get_instrumentdetail 或行情数据),下单价格不要超过限制。
def check_validity(ContextInfo, code, price):
    # 检查停牌
    if ContextInfo.is_suspended_stock(code):
        return False
    
    # 检查涨跌停
    detail = ContextInfo.get_instrumentdetail(code)
    if price >= detail['UpStopPrice'] or price <= detail['DownStopPrice']:
        # 根据策略逻辑决定是否追涨杀跌,或者避开
        pass
    return True

5. 编码与全局变量作用域

编码问题

QMT底层是C++,Python脚本文件默认编码格式要求严格。

  • 必须在文件第一行加上 # -*- coding: gbk -*-#coding:gbk。如果不加,中文注释会导致编译错误或乱码。

变量作用域

不要使用 Python 原生的 global 关键字来跨函数传递策略状态(如持仓标记、计数器)。QMT 的运行机制可能会重置脚本环境。

  • 必须将全局变量挂载到 ContextInfo 对象上。
# 错误
my_counter = 0
def handlebar(ContextInfo):
    global my_counter
    my_counter += 1

# 正确
def init(ContextInfo):
    ContextInfo.my_counter = 0

def handlebar(ContextInfo):
    ContextInfo.my_counter += 1

6. 账户ID与交易品种后缀

  • 账户IDpassorder 函数需要传入准确的资金账号字符串。新手常犯错误是传了数字类型而不是字符串,或者弄错了账号类型(股票 vs 期货)。
  • 代码后缀:QMT严格区分市场后缀。
    • 上海:.SH (如 600000.SH)
    • 深圳:.SZ (如 000001.SZ)
    • 注意:不要漏掉后缀,也不要用小写(虽然部分接口兼容,但建议统一大写)。

Q&A: 常见疑问解答

Q: 为什么我的策略在回测很完美,实盘却不交易?
A:

  1. 检查 init 中是否调用了 ContextInfo.set_account(account_id) 绑定账号。
  2. 检查 passorder 中的 quickTrade 参数。回测通常默认在K线结束时撮合,实盘如果需要在信号触发瞬间下单,需设置 quickTrade=1(立即下单)或 2(即使是历史K线也下单,慎用)。
  3. 检查是否处于非交易时间或股票停牌。

Q: 如何在盘中获取最新的实时价格(Tick数据)?
A: 使用 ContextInfo.get_full_tick([code])。它返回一个字典,包含最新的买一卖一、最新价、成交量等详细盘口数据。不要用 get_market_data 去取高频切片,效率较低。

Q: passorder 下单后,如何确认成交了?
A: 不要假设下单即成交。必须通过 order_callback (委托回报) 和 deal_callback (成交回报) 回调函数来接收通知,或者在 handlebar 中轮询 get_trade_detail_data 查询委托状态。

Q: 策略报错 "name 'ContextInfo' is not defined" 是怎么回事?
A: 这是一个常见的误解。ContextInfo 是QMT平台回调 inithandlebar 时自动传入的参数。你不能在这些函数之外(例如模块层级)直接使用 ContextInfo。所有的逻辑都必须包含在平台调用的函数内部。