这是一份写给 QMT(迅投极速策略交易系统)实盘用户 的下单错误排查指南。
全文分四块:先分清”出错”有四种形态(很多人把四件事混成一件事)、再讲回调机制与关键字段(为什么下单函数没有返回值、错误从哪里冒出来)、然后给局部代码片段(怎么接错误回调、怎么识别废单、怎么建委托台账)、最后给错误对照表与免责声明。
片段基于 QMT 内置 Python 3.6,面向实盘策略。
一、这份指南给你什么
先按你的情况挑路径:
| 你是谁 | 建议路径 |
|---|---|
| 策略能跑但偶尔没下出去单 | 从 §2 读起,对号入座 |
| 想知道错误信息从哪来 | 直奔 §3 机制 + §4 回调字段 |
| 已经在接回调但读不懂字段 | §4 状态表 + §5 片段 3 |
| 见过”废单”两个字但不知道去哪找原因 | §4 的 m_strCancelInfo + §6 对照表 |
要解决的是实盘里最常见的一段空白:
策略写了、信号也出来了,但单子就是没进去,或者进去了变成了废单——我从哪里知道原因?
二、先分清:下单”出错”其实是四种不同的形态
这是全文最关键的一张表。不同的错误形态,由不同的机制暴露,你不能指望一个回调把所有问题都兜住:
| 形态 | 现象 | 什么时候发生 | 谁会发现它 |
|---|---|---|---|
| A. 策略层根本没发单 | 有信号但没有任何委托,也没有任何报错 | 模拟模式运行(只显示信号);编辑器界面运行(不产生真实委托);quickTrade=0 且主图是日线及以上周期;非交易时段 |
没有任何回调——只能靠你自己核对业务逻辑 |
| B. 参数/前置校验失败 | 客户端消息区出现报错,委托从未产生 | 参数组合不合法(如 prType=11 指定价但价格非法)、行情级别不足导致对手价取不到 |
orderError_callback(ContextInfo, orderArgs, errMsg) |
| C. 报出后被拒或成为废单 | 委托出现在委托列表里,但状态是废单 | 价格超出涨跌幅/价格笼子、数量超可用、证券状态异常等 | order_callback 里读 m_nOrderStatus(57=废单)+ m_strCancelInfo(废单原因) |
| D. 报出去了但没成交 | 状态长期停在”已报”、”部成”,或被撤 | 挂单价格偏离、流动性不足、你自己撤单 | order_callback / deal_callback,属于正常业务不是错误 |
一句话记忆:A 靠自查、B 靠
orderError_callback、C 靠委托状态 + 废单原因、D 靠委托/成交回调的字段变化。
很多人第一次写错误处理,只在 orderError_callback 里打了日志,结果真出问题时一行都没打印——因为那次错误属于 C 类,压根不走这个回调。
三、机制:为什么”下单函数”没有返回值可判断
理解这一点,错误处理的设计就顺了。QMT 的交易接口是异步的:
- 调用即返回。
passorder发出委托后立刻返回,不等待回报,也不阻塞 Python 线程。所以没有”成功/失败”的返回值给你if——判断必须靠回调。 - 回调读的是客户端本地缓存。
get_trade_detail_data与四种交易回调(委托/成交/持仓/资金),都是从客户端本地缓存读取或触发,不是每次去问柜台。缓存由后台定期接收柜台推送刷新:有交易主推的柜台约 50ms 一次,没有的约 1~6 秒一次。- 由此推出一条非常实用的结论:卖出委托后立刻查询,很可能查不到这笔委托,可用资金也不会立刻变多。这不是 bug。
- 所有策略共用一个线程。 任意一个策略阻塞线程(
sleep、死循环、加锁等待)会拖住所有策略。所以答案是明确的:不要在下单后写sleep等待结果,用回调或定时核对代替。 - 回调只在实盘模式生效,且需要先在
init里调用ContextInfo.set_account(account)。模拟模式、编辑器界面都不产生真实委托,也就没有这些回调可看。
顺带说一个容易误判的工具:get_last_order_id 能取”最新委托或成交的委托号”,但知识库明确提示——下单后需要一段不确定的时间才能查到本次委托号,而且如果这次委托废单,你查到的会是上一次成功下单的委托号。所以它不能用来判断”本次下单是否成功”,只能作为辅助。
四、三个主推回调与关键字段
4.1 谁负责什么
| 回调 | 触发时机 | 主要用途 |
|---|---|---|
orderError_callback(ContextInfo, orderArgs, errMsg) |
账号下单异常时 | 抓参数级错误:把 errMsg 和 orderArgs 一起落盘 |
order_callback(ContextInfo, orderInfo) |
账号委托状态变化时 | 抓废单/拒绝:读 m_nOrderStatus 与 m_strCancelInfo |
deal_callback(ContextInfo, dealInfo) |
账号成交时 | 确认成交价量,更新台账 |
position_callback / account_callback / task_callback |
持仓 / 资金账号 / 任务状态变化时 | 对账、资金占用确认、任务型下单的状态跟踪 |
4.2 orderError_callback 的两个参数
orderArgs(下单参数对象 PassorderArguments)——就是把你那次 passorder 的参数原样还给你:
| 字段 | 类型 | 含义 |
|---|---|---|
opType |
int | 操作类型(股票买入 23 / 卖出 24) |
orderType |
int | 下单方式(如 1101 按数量) |
accountID |
string | 资金账号 |
orderCode |
string | 交易代码(注意格式为 SZ000001 这类”市场+代码”) |
prType |
int | 报价类型(11 指定价 / 5 最新价 / 14 对手价 等) |
modelPrice |
float | 下单价格 |
modelVolume |
int | 下单量(手数或股数) |
strategyName |
string | 策略名_&&&_投资备注 |
errMsg 是字符串形式的错误描述。典型实测输出(知识库示例,已脱敏):
1 | [函数交易] 函数: passorder, 证券 [SZ000001] 指定价 无效, 无法下单! |
看这句话就能定位:prType=11 走指定价,但价格非法。errMsg + orderArgs 组合起来,绝大多数参数级错误一眼可判。
4.3 order_callback 里必须认识的字段
| 字段 | 含义 | 用法 |
|---|---|---|
m_nOrderStatus |
委托状态(见下表) | 57 就是废单,重点盯它 |
m_strCancelInfo |
废单原因 | 状态为废单时来这里读原因 |
m_nOrderSubmitStatus |
报单状态 | 52 = 报单已经被拒绝 |
m_nErrorID / m_strErrorMsg |
状态 ID / 状态信息 | 兜底参考 |
m_nVolumeTotalOriginal / m_nVolumeTraded / m_nVolumeTotal |
原始委托量 / 已成交量 / 剩余量 | 判断部成 |
m_strOrderRef / m_strOrderSysID |
内部委托号 / 合同编号(委托号) | 与台账、券商流水对账 |
m_strRemark |
投资备注 | 与下单时 userOrderId 一致,是你独有的幂等键 |
m_strInsertDate / m_strInsertTime |
委托日期 / 时间 | 排查时序问题 |
m_strExchangeName / m_strInstrumentName |
交易所 / 证券名称 | 日志可读性 |
委托状态(EEntrustStatus)速查:
| 值 | 含义 | 值 | 含义 |
|---|---|---|---|
| 49 | 待报 | 54 | 已撤 |
| 50 | 已报(已报出到柜台,待成交) | 55 | 部成(已有部分成交) |
| 51 | 已报待撤 | 56 | 已成 |
| 52 | 部成待撤 | 57 | 废单(不符合报单条件被打回,原因看废单原因字段) |
| 53 | 部撤 |
报单状态(EEntrustSubmitStatus)速查:48 已提交 / 49 撤单已提交 / 50 修改已提交 / 51 已经接受 / 52 报单已经被拒绝 / 53 撤单被拒绝 / 54 改单被拒绝。
五、局部代码片段:怎么接、怎么判、怎么兜
文件编码提醒:QMT 内置 Python 要求源文件 GBK 编码,首行
# coding:gbk。
片段 1:回调生效的两个前提
1 | # coding:gbk |
片段 2:把 orderError_callback 变成可排查的日志
要点:不要 print 整个对象(里面含账号、账号 key 等敏感字段),只取你需要的字段,并对账号做掩码。
1 | def _mask(s): |
片段 3:在 order_callback 里识别废单与拒绝
1 | STATUS_JUNK = 57 # 废单 |
片段 4:台账思路——用”投资备注”当幂等键
错误处理真正的价值不在打印日志,而在防止错误导致的下一次错误。知识库给出的标准做法,思路是三步:
- 下单时给每一笔委托一个唯一的投资备注(
passorder的userOrderId参数),记为台账的key。 - 台账里记状态:下单后默认置为**”待报”**。
- 收到
order_callback/deal_callback后更新状态;若某品种存在”待报”状态的委托,暂停该品种的后续报单,防止超单。
骨架如下(片段,非完整实现):
1 | ORDER_LEDGER = {} # { 投资备注: 状态 } —— 用普通全局变量保存,不要塞进 ContextInfo |
配合片段 3,废单或被拒时把台账里对应备注置为 "失败",才算把闭环补上。
片段 5:回调没来怎么办
交易接口是异步的,回报可能延迟(本地缓存 50ms16 秒刷新一次),所以需要兜底核对而不是干等:
1 | # 思路:定时(例如每分钟)用 get_trade_detail_data 拉当日委托做核对 |
⚠️ 别用 get_last_order_id 判断本次下单是否成功——废单时它返回的是上一次成功下单的委托号,会把失败误判成成功。
六、常见错误 → 原因对照表
| 现象 / 提示 | 最可能原因 | 处理方向 |
|---|---|---|
指定价 无效, 无法下单! |
prType=11(指定价)但价格非法/未填 |
补合法价格,或改用 prType=5/14 |
对手价无效,无法下单! |
行情源的全推行情级别不足,取不到对手价 | 按客户端提示调整行情级别设置 |
| 有信号、无委托、无报错 | 模拟模式运行;编辑器界面运行;quickTrade=0 且主图是日线及以上周期 |
切实盘模式;核对 quickTrade 取值与运行位置 |
| 有实盘信号但找不到委托 | 参数被本地校验拦下 | 看客户端消息区报错,按 orderError_callback 的 errMsg 修改参数 |
| 委托状态为 57 废单 | 不符合报单条件(价格笼子、数量、证券状态等) | 读 m_strCancelInfo 定位具体原因 |
| 卖出后立刻查不到委托、可用资金没变多 | 本地缓存尚未刷新(1~6 秒) | 不要在策略里 sleep 等,用回调/定时核对 |
| 同一状态收到多次推送 | 状态变化即可触发推送,日志里常见重复行 | 业务处理做幂等(以委托号+状态去重) |
quickTrade 取值场景速记(来自知识库口径):
| 场景 | 取值 |
|---|---|
handlebar 逐 K 线,K 线结束生效 |
0(默认) |
handlebar 盘中触发立刻下单 |
1(有信号闪烁风险,需自己处理) |
定时器 / init / after_init / 行情与交易回调内下单 |
2(否则容易漏单) |
⚠️ quickTrade=2 在历史 Bar 上也会触发下单,回测/重放/重连场景极易产生意外委托,通常不建议用它;仅在明确需要”回调内立即下单”等场景使用,并须先模拟验证。
⚠️ 实盘风险提示:上述片段涉及真实下单逻辑。请先在 QMT「模拟信号模式」或「模拟柜台」中验证回调与台账逻辑无误后,再切换至实盘账号。股票交易受 2% 价格笼子限制,委托数量超过可用数量会产生废单。
七、免责声明(请务必完整阅读)
阅读本文,即视为你已完整阅读、理解并同意以下全部条款。
1. 性质声明:本文及文中示例代码片段仅供编程学习与技术研究使用,不构成任何投资建议、荐股意见或收益承诺。 证券市场有风险,任何交易决策及其后果均由使用者独立作出并独立承担。
2. 模拟测试要求(重要): 在将本文思路或代码片段用于实盘环境之前,你必须在 QMT「模拟信号模式」或「模拟柜台」中进行充分测试,验证回调触发、委托台账、异常处理均符合预期。本文作者/发布者无法也不承诺对你的特定环境进行测试。未经充分模拟验证直接接入实盘所导致的一切后果,由使用者自行承担全部责任。
3. 字段与口径说明: 文中回调字段、枚举值、错误提示均依据 QMT 官方文档知识库整理;不同券商版本、不同行情权限下的表现可能存在差异,最终以你本地客户端实际输出和券商柜台结果为准。委托状态、废单原因的解释权在交易所与券商,本文不构成对任何具体委托结果的承诺。
4. 数据与隐私提示: 交易回调对象包含资金账号、账号 key、委托号、成交号等敏感信息。请勿将回调原始对象直接 print、写入日志、截图或提交到工单;如需排查,请先对敏感字段做掩码处理。
5. 无担保声明: 本文内容与示例代码按”现状”提供,不附带任何明示或默示的担保。作者/发布者不保证示例代码无缺陷、不中断、不出错,也不保证读者自行实现的版本能达到预期效果。
6. 责任限制: 在任何情况下,作者/发布者均不对因使用或无法使用本文内容、示例代码而产生的任何直接、间接、附带、特殊、惩罚性损失(包括但不限于交易亏损、机会损失、数据丢失、停机损失)承担责任。即使作者/发布者已被告知此类损失的可能性。
7. 内容定性声明确认: 本文及其中的示例代码属于计算机编程技术内容,不构成证券投资咨询或投资顾问服务。作者不提供代客理财、代办交易、跟单操作、个股推荐或买卖时机建议等任何服务。
8. 版权声明: 本文版权归作者所有。本文免费分享,欢迎个人学习使用,也欢迎注明出处后自由转发;但未经书面授权,不得用于商业培训、付费社群、二次售卖,或以原创名义转载。
9. 合规使用: 使用者承诺遵守国家法律法规及所在券商的相关规定,不得将本文内容用于任何违法违规用途。
八、写在最后
交易错误处理的分水岭,不是”我会不会写回调”,而是你知不知道错误会从哪几个地方冒出来:
- 没发出去的单,回调永远不会告诉你(A 类);
- 参数错了,去找
orderError_callback的errMsg(B 类); - 报出去被打回,去找委托状态的 57 和
m_strCancelInfo(C 类); - 报出去没成交,那是业务,靠委托/成交回调更新台账(D 类)。
把这两类回调接上、给每笔委托一个唯一备注、再补一次定时核对——你的策略就从”下单靠运气”变成”下单有账可查”。
⚠️ 最后再强调一次:任何代码在实盘使用前,请务必在模拟模式下充分测试。 市场有风险,交易需谨慎。