🚀 AI 一键生成 qmt 策略代码

QMT 策略实战:如何优雅地处理订单部分成交的情况?

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

问题描述

我的策略下单后,如果只成交了一部分该怎么办?如何让程序知道并处理这种情况?

解决方案

在 QMT 量化交易平台中,处理“部成”(部分成交)的情况是实盘策略中非常重要的一环。如果一笔委托只成交了一部分,剩余部分可能会因为价格变动而无法成交,或者一直挂在买一/卖一位置暴露风险。

要让程序知道并处理这种情况,最推荐的方式是使用 交易回报实时主推函数 (order_callback)

核心机制

  1. 如何知道(检测):
    通过 order_callback(ContextInfo, orderInfo) 回调函数,QMT 会在委托状态发生变化时自动调用该函数。你需要检查 orderInfo 对象中的 m_nOrderStatus(委托状态)和成交量字段。

    • 状态码 55 (ENTRUST_STATUS_PART_SUCC):表示“部成”,即部分成交,剩余部分仍在挂单中。
    • 状态码 52 (ENTRUST_STATUS_PARTSUCC_CANCEL):表示“部成待撤”或“部成已撤”,即部分成交后,剩余部分被撤单了(这是最终状态)。
  2. 如何处理(逻辑):
    一旦检测到状态为 55(部成),你可以根据策略逻辑选择:

    • 继续等待:不做操作,等待剩余部分成交。
    • 撤单:调用 cancel() 函数撤销剩余未成交部分。
    • 追单:撤单后,以更激进的价格(如对手价)重新报单剩余数量。

代码实现

以下是一个完整的策略示例。该代码展示了如何通过 order_callback 实时监控委托,一旦发现“部分成交”且剩余部分未成交,程序会打印日志,并演示如何执行撤单操作(防止剩余部分长期挂单)。

# -*- coding: gbk -*-
import time

def init(ContextInfo):
    # 1. 必须设置资金账号,否则无法接收回报
    # 请将下面的账号替换为您的真实资金账号
    ContextInfo.accID = '6000000000' 
    ContextInfo.accType = 'STOCK' # 账号类型:STOCK股票,FUTURE期货
    ContextInfo.set_account(ContextInfo.accID)
    
    print("策略初始化完成,已绑定账号监控交易回报")

def handlebar(ContextInfo):
    # 示例:在最后一根K线发单(仅作演示触发,实际逻辑由您的策略决定)
    if not ContextInfo.is_last_bar():
        return
    
    # 这里可以写您的下单逻辑,例如:
    # passorder(23, 1101, ContextInfo.accID, '600000.SH', 5, -1, 100, ContextInfo)
    pass

def order_callback(ContextInfo, orderInfo):
    """
    委托状态变化回调函数
    当委托状态变为“部成”时,触发处理逻辑
    """
    # 55 代表 ENTRUST_STATUS_PART_SUCC (部成/部分成交,剩余部分仍在挂单)
    if orderInfo.m_nOrderStatus == 55:
        print("=" * 30)
        print(f"【检测到部分成交】 委托号: {orderInfo.m_strOrderSysID}")
        print(f"标的: {orderInfo.m_strInstrumentID}")
        
        # 获取成交量和剩余量
        traded_vol = orderInfo.m_nVolumeTraded      # 已成交数量
        total_vol = orderInfo.m_nVolumeTotalOriginal # 原始委托数量
        remain_vol = orderInfo.m_nVolumeTotal       # 剩余未成交数量 (股票中通常 = 总量 - 成交)
        
        print(f"原始委托: {total_vol}, 已成交: {traded_vol}, 剩余: {remain_vol}")
        
        # --- 处理逻辑区域 ---
        
        # 策略 1: 激进处理 - 只要出现部成,立即撤销剩余部分(防止价格偏离后一直挂单)
        # 注意:实际使用时建议加一些判断,比如判断剩余量是否过小,或者判断时间间隔
        
        print(f"正在对剩余 {remain_vol} 股进行撤单处理...")
        
        # 调用撤单函数
        # 参数: orderId, accountId, accountType, ContextInfo
        cancel_result = cancel(orderInfo.m_strOrderSysID, ContextInfo.accID, ContextInfo.accType, ContextInfo)
        
        if cancel_result:
            print("撤单指令发送成功")
            # 如果需要追单,可以在这里记录信息,在下一轮 handlebar 或通过延时任务重新下单
        else:
            print("撤单指令发送失败 (可能已完全成交或已撤单)")
            
    # 56 代表 ENTRUST_STATUS_SUCCEEDED (全部成交)
    elif orderInfo.m_nOrderStatus == 56:
        print(f"【全部成交】 委托号: {orderInfo.m_strOrderSysID},标的: {orderInfo.m_strInstrumentID}")

    # 52 代表 ENTRUST_STATUS_PARTSUCC_CANCEL (部成已撤)
    elif orderInfo.m_nOrderStatus == 52:
        print(f"【部成已撤】 委托号: {orderInfo.m_strOrderSysID},剩余部分已撤销,流程结束。")

关键字段说明

orderInfo 对象中,处理部分成交时需要关注以下属性:

  1. m_nOrderStatus (委托状态):

    • 55: 部分成交(关键状态,意味着还有剩余部分在撮合队列中)。
    • 52: 部成已撤(最终状态,意味着部分成交了,剩下的被撤销了)。
    • 56: 全部成交。
  2. m_nVolumeTraded: 已经成交的数量。

  3. m_nVolumeTotal: 委托剩余量(即挂在交易所还未成交的量)。

进阶处理建议

如果您的策略比较复杂,仅仅撤单可能不够,您可能需要**“撤单后追单”**。由于 order_callback 是异步推送的,直接在回调里发单可能会有逻辑风险(例如死循环:下单->部成->撤单->追单->部成...)。

建议的处理流程是:

  1. order_callback 中发现部成。
  2. 记录该笔交易的 ID 和未成交数量到全局变量(如 ContextInfo.pending_orders)。
  3. 发出 cancel() 撤单指令。
  4. order_callback 中等待状态变为 52 (部成已撤)54 (已撤),确认撤单成功。
  5. handlebar 或定时器中,检查撤单成功的记录,针对剩余数量,以最新价格(如对手价或涨停价)重新发起一笔新的委托。

Q&A

Q: 为什么我的 order_callback 没有被触发?
A: 请检查 init 函数中是否调用了 ContextInfo.set_account(accountID)。只有绑定了具体的资金账号,QMT 才会向策略推送该账号的交易回报。此外,请确保策略处于“实盘”或“模拟交易”模式,回测模式下回报机制可能有所不同(通常回测假设流动性充足,较少出现部成)。

Q: m_nVolumeTotal 在股票和期货中含义一样吗?
A: 基本一致,但在 QMT 的定义中,m_nVolumeTotal 通常指当前剩余的挂单量。即 m_nVolumeTotal = m_nVolumeTotalOriginal - m_nVolumeTraded - m_dCancelAmount(已撤量)。在处理部成逻辑时,判断 m_nVolumeTotal > 0 是确认是否还有挂单的关键。

Q: 如果我不撤单,剩余部分会怎么样?
A: 如果不撤单,剩余部分会继续挂在交易所的订单簿上,等待市场价格波动触及您的委托价。如果当天收盘仍未成交,交易所会在收盘后自动撤销该委托。