🚀 AI 一键生成 qmt 策略代码
立即体验

QMT 平台如何使用 cancel 函数撤单?(附超时自动撤单 Python 源码)

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

问题描述

qmt 撤单 API 教程:利用 cancel 函数实现超时自动撤单
如何在 QMT 策略中获取委托号并调用 cancel 函数把未成交的限价单撤销?

解决方案

QMT 撤单 API 教程:利用 cancel 函数实现超时自动撤单

在量化交易中,限价单(Limit Order)常常因为市场价格波动而无法立即成交。为了避免资金被长时间占用或防止错过更好的交易时机,我们通常需要实现超时自动撤单功能。本文将详细介绍如何在国信证券 QMT 极速策略交易系统中,利用 cancel 等相关 API 实现这一功能。


1. 核心撤单 API 介绍

在 QMT 中实现自动撤单,主要依赖以下四个 API 函数:

(1) 取消委托 cancel()

  • 用法cancel(orderId, accountId, accountType, ContextInfo)
  • 释义:向柜台发送撤单信号。
  • 参数
    • orderId (string):委托号(合同编号)。
    • accountId (string):资金账号。
    • accountType (string):账号类型,如 'STOCK'(股票)、'FUTURE'(期货)。
    • ContextInfo:策略运行环境对象。
  • 返回True(已发出撤单信号)或 False

(2) 获取最新委托号 get_last_order_id()

  • 用法get_last_order_id(accountID, strAccountType, strDatatype)
  • 释义:获取策略最新产生的委托号,用于后续跟踪。

(3) 查询委托是否可撤销 can_cancel_order()

  • 用法can_cancel_order(orderId, accountID, strAccountType)
  • 释义:在撤单前校验该委托当前状态是否允许撤单(如已成交、已撤销的订单不可再撤)。

(4) 根据委托号获取委托详情 get_value_by_order_id()

  • 用法get_value_by_order_id(orderId, accountID, strAccountType, strDatatype)
  • 释义:获取委托对象,可查询其成交数量 m_nVolumeTraded、委托状态 m_nOrderStatus 等属性。

2. 超时自动撤单的实现逻辑

  1. 下单并记录时间:使用 passorder 发送限价单,随后立即调用 get_last_order_id 获取该笔订单的委托号,并记录当前的时间戳。
  2. 盘中轮询检查:在 handlebar 或定时器中,检查未成交订单的等待时间。
  3. 超时判定与撤单:若等待时间超过设定阈值(例如 60 秒),且通过 can_cancel_order 确认订单可撤,则调用 cancel 函数执行撤单。

3. Python 策略源码示例

以下是一个完整的 QMT 超时自动撤单策略模板:

#encoding:gbk
import time

def init(ContextInfo):
    # 设定交易账号
    ContextInfo.accid = '6000000248' 
    ContextInfo.set_account(ContextInfo.accid)
    
    # 存储未成交订单信息的字典 { 委托号: 下单时间戳 }
    ContextInfo.pending_orders = {}
    # 设定超时时间(单位:秒)
    ContextInfo.timeout_limit = 60 

def handlebar(ContextInfo):
    # 仅在最后一根 Bar(最新行情)处理撤单逻辑
    if not ContextInfo.is_last_bar():
        return

    # 示例:满足某种条件时发送限价买入单(此处仅作演示,实际策略中需替换为您的信号)
    if ContextInfo.barpos == ContextInfo.time_tick_size - 1 and not ContextInfo.pending_orders:
        target_stock = '600000.SH'
        limit_price = 7.50  # 假设一个不易立即成交的限价
        
        # 发送限价买入单 (opType=23, orderType=1101)
        passorder(23, 1101, ContextInfo.accid, target_stock, 11, limit_price, 100, '超时撤单策略', 1, ContextInfo)
        
        # 立即获取刚刚生成的委托号
        order_id = get_last_order_id(ContextInfo.accid, 'STOCK', 'ORDER')
        if order_id and order_id != '-1':
            # 记录委托号及当前时间戳
            ContextInfo.pending_orders[order_id] = time.time()
            print(f'已发送限价单,委托号: {order_id},开始计时...')

    # --- 轮询检查超时订单 ---
    current_time = time.time()
    orders_to_remove = []

    for order_id, start_time in ContextInfo.pending_orders.items():
        # 计算已等待时间
        elapsed_time = current_time - start_time
        
        # 获取订单当前状态
        order_obj = get_value_by_order_id(order_id, ContextInfo.accid, 'STOCK', 'ORDER')
        
        if order_obj:
            # 如果订单已经全部成交 (ENTRUST_STATUS_SUCCEEDED = 56) 或已撤销,则无需再监控
            if order_obj.m_nOrderStatus in [54, 56]: 
                print(f'订单 {order_id} 已结束(已成/已撤),移出监控列表。')
                orders_to_remove.append(order_id)
                continue
            
            # 若超时且可撤销
            if elapsed_time > ContextInfo.timeout_limit:
                if can_cancel_order(order_id, ContextInfo.accid, 'STOCK'):
                    print(f'订单 {order_id} 已等待 {elapsed_time:.1f} 秒,触发超时撤单!')
                    cancel_result = cancel(order_id, ContextInfo.accid, 'STOCK', ContextInfo)
                    if cancel_result:
                        print(f'撤单信号发送成功。')
                    orders_to_remove.append(order_id)
                else:
                    print(f'订单 {order_id} 超时,但当前状态不可撤销。')

    # 移除已处理的订单记录
    for order_id in orders_to_remove:
        if order_id in ContextInfo.pending_orders:
            del ContextInfo.pending_orders[order_id]

4. 注意事项

  1. 实盘与回测区别cancelcan_cancel_order 等函数主要在实盘/模拟盘运行模式下生效。在历史 K 线回测中,限价单通常在当期 K 线结束时按收盘价撮合,撤单 API 无实际撮合意义。
  2. 账号绑定:必须在 init 中调用 ContextInfo.set_account() 绑定资金账号,否则无法正常接收交易主推和执行撤单。
  3. 状态码校验:在判断订单是否结束时,可以参考附录中的 enum_EEntrustStatus 委托状态码(如 54 代表已撤,56 代表已成)。