问题描述
解决方案
在 QMT 中,区分和追踪不同开仓逻辑触发的委托,最直接且有效的方法是利用下单函数 passorder 中的 userOrderId 参数。
核心原理
- 下单时打标签:在调用
passorder函数时,将具体的逻辑名称(如 "MACD_Open" 或 "Boll_Breakout")赋值给userOrderId参数。 - 查询时读标签:在委托回报(
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_callback和deal_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: 如果我不填 strategyName 和 quickTrade,直接传 userOrderId 可以吗?
A: 不可以。因为 Python 的位置参数机制,如果你想传第 10 个参数,必须把第 8 和第 9 个参数填上。通常 strategyName 填策略名或空字符串,quickTrade 填 1(立即下单)或 0。
Q: 这个方法在回测中有效吗?
A: 在回测模式下,passorder 的 userOrderId 参数通常会被忽略或不完全支持回调中的透传,因为回测主要关注净值计算。这个功能主要用于实盘和模拟盘的信号追踪与风控。
Q: 可以在 userOrderId 中传递复杂信息吗?
A: 可以传递 JSON 字符串,例如 "{'logic':'MA', 'id':101}",然后在回调中解析这个字符串,从而携带更多元数据。