🚀 AI 一键生成 qmt 策略代码

QMT 交易函数 passorder 中的 userOrderId 有什么用?(附订单追踪代码示例)

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

问题描述

我在一个策略里有多种开仓逻辑,下单后如何区分和追踪具体是哪种逻辑触发的委托?

解决方案

在 QMT 中,区分和追踪不同开仓逻辑触发的委托,最直接且有效的方法是利用下单函数 passorder 中的 userOrderId 参数。

核心原理

  1. 下单时打标签:在调用 passorder 函数时,将具体的逻辑名称(如 "MACD_Open" 或 "Boll_Breakout")赋值给 userOrderId 参数。
  2. 查询时读标签:在委托回报(order_callback)、成交回报(deal_callback)或查询交易明细(get_trade_detail_data)时,读取返回对象中的 m_strRemark 属性。QMT 底层会将 userOrderId 的内容透传到 m_strRemark 中。

代码实现示例

以下代码展示了如何在一个策略中定义两种不同的开仓逻辑(均线策略和随机入场),并通过 userOrderId 进行区分,同时在回调函数中打印追踪。

# -*- coding: gbk -*-
import random

def init(ContextInfo):
    # 设置资金账号
    ContextInfo.accid = '6000000000' # 请替换为您的真实资金账号
    ContextInfo.set_account(ContextInfo.accid)
    
    # 定义股票池
    ContextInfo.stock = '600000.SH'
    ContextInfo.set_universe([ContextInfo.stock])
    
    # 策略运行参数
    ContextInfo.strategy_name = "MultiLogicStrategy"

def handlebar(ContextInfo):
    # 获取当前K线位置
    index = ContextInfo.barpos
    # 获取当前时间
    timetag = ContextInfo.get_bar_timetag(index)
    
    # 跳过历史K线,只在最新K线或实时行情运行(模拟实盘场景)
    if not ContextInfo.is_last_bar():
        return

    # --- 逻辑 A: 均线策略 (示例) ---
    # 获取收盘价
    close_data = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock], count=21, period='1d')
    if close_data is not None and not close_data.empty:
        closes = close_data['close']
        if len(closes) >= 20:
            ma5 = closes[-5:].mean()
            ma20 = closes[-20:].mean()
            current_price = closes[-1]
            
            # 假设金叉开仓
            if ma5 > ma20:
                # 【关键点】在这里定义逻辑标签
                logic_tag = "Logic_MA_Cross" 
                
                print(f"触发 {logic_tag} 开仓信号")
                passorder(
                    23,                 # opType: 23-买入
                    1101,               # orderType: 1101-单股单账号
                    ContextInfo.accid,  # accountId
                    ContextInfo.stock,  # orderCode
                    5,                  # prType: 5-最新价
                    -1,                 # price: 最新价模式下无效
                    100,                # volume: 100股
                    ContextInfo.strategy_name, # strategyName
                    1,                  # quickTrade: 1-立即下单
                    logic_tag,          # userOrderId: 【这里传入逻辑标签】
                    ContextInfo         # ContextInfo
                )

    # --- 逻辑 B: 随机策略 (示例) ---
    # 仅作演示,模拟另一种完全不同的触发条件
    rand_val = random.random()
    if rand_val > 0.95:
        # 【关键点】定义不同的逻辑标签
        logic_tag = "Logic_Random_Entry"
        
        print(f"触发 {logic_tag} 开仓信号")
        passorder(
            23, 
            1101, 
            ContextInfo.accid, 
            ContextInfo.stock, 
            5, 
            -1, 
            100, 
            ContextInfo.strategy_name, 
            1, 
            logic_tag,          # userOrderId: 【传入不同的标签】
            ContextInfo
        )

# 委托回报回调:追踪下单结果
def order_callback(ContextInfo, orderInfo):
    # orderInfo 是委托对象
    # m_strRemark 对应下单时的 userOrderId
    source_logic = orderInfo.m_strRemark
    
    print("=" * 30)
    print(f"收到委托回报")
    print(f"委托编号: {orderInfo.m_strOrderSysID}")
    print(f"证券代码: {orderInfo.m_strInstrumentID}")
    print(f"触发逻辑(Remark): {source_logic}")  # 这里会打印 "Logic_MA_Cross" 或 "Logic_Random_Entry"
    print(f"委托状态: {orderInfo.m_nOrderStatus}")
    print("=" * 30)

    # 可以在这里根据不同的 source_logic 做进一步处理
    if source_logic == "Logic_MA_Cross":
        # 记录均线策略的委托状态...
        pass
    elif source_logic == "Logic_Random_Entry":
        # 记录随机策略的委托状态...
        pass

# 成交回报回调
def deal_callback(ContextInfo, dealInfo):
    # dealInfo 是成交对象
    # m_strRemark 同样对应下单时的 userOrderId
    source_logic = dealInfo.m_strRemark
    
    print("=" * 30)
    print(f"收到成交回报")
    print(f"成交编号: {dealInfo.m_strTradeID}")
    print(f"触发逻辑(Remark): {source_logic}")
    print(f"成交价格: {dealInfo.m_dPrice}")
    print("=" * 30)

关键参数说明

passorder 函数中,参数的顺序是固定的(除非使用关键字参数,但 QMT 文档通常推荐位置参数)。userOrderId 通常位于第 10 个参数位置(视具体重载而定,但在 Python API 中通常如下):

passorder(
    opType, 
    orderType, 
    accountid, 
    orderCode, 
    prType, 
    modelprice, 
    volume, 
    strategyName,  # 策略名称(用于区分不同策略文件)
    quickTrade,    # 快速交易标志(通常设为1或2)
    userOrderId,   # 【核心】用户自定义ID,即备注信息
    ContextInfo
)
  • strategyName: 通常用于区分整个策略文件。虽然也可以用来区分逻辑,但建议保持为策略名称,方便在交易终端按策略筛选。
  • userOrderId: 这是最适合用来做“逻辑标签”的字段。它是一个字符串,你可以传入任何标识符。
  • m_strRemark: 在 order_callbackdeal_callback 返回的对象中,这个属性存储的就是你下单时传入的 userOrderId

如何查询历史委托的触发逻辑?

如果你需要在盘后分析或在 handlebar 中查询之前的委托是哪个逻辑触发的,可以使用 get_trade_detail_data

def check_orders(ContextInfo):
    # 获取当天的所有委托
    orders = get_trade_detail_data(ContextInfo.accid, 'stock', 'order')
    
    for order in orders:
        # 读取备注
        logic = order.m_strRemark
        status = order.m_nOrderStatus
        
        if logic == "Logic_MA_Cross":
            print(f"发现均线策略委托,状态: {status}")
        elif logic == "Logic_Random_Entry":
            print(f"发现随机策略委托,状态: {status}")

Q&A

Q: userOrderId 有长度限制吗?
A: 是的,通常建议不要过长,保持在简短的字符串(如 32 个字符以内)是安全的,例如 "MA_Buy" 或 "RSI_Sell"。

Q: 如果我不填 strategyNamequickTrade,直接传 userOrderId 可以吗?
A: 不可以。因为 Python 的位置参数机制,如果你想传第 10 个参数,必须把第 8 和第 9 个参数填上。通常 strategyName 填策略名或空字符串,quickTrade 填 1(立即下单)或 0。

Q: 这个方法在回测中有效吗?
A: 在回测模式下,passorderuserOrderId 参数通常会被忽略或不完全支持回调中的透传,因为回测主要关注净值计算。这个功能主要用于实盘模拟盘的信号追踪与风控。

Q: 可以在 userOrderId 中传递复杂信息吗?
A: 可以传递 JSON 字符串,例如 "{'logic':'MA', 'id':101}",然后在回调中解析这个字符串,从而携带更多元数据。