QMT 单线程指南(一):多策略怎么排队、该不该把策略写在一起

一、前情:miniQMT 被禁用之后,迁移到大 QMT

这一轮监管防外接改造,券商陆续禁用 / 收紧 miniQMT(xtquant 本地外接)通道,原来跑在自己 Python 环境里的那批策略,正在成批迁回大 QMT 客户端里跑。如果你也是迁过来的,先对齐一个最大的心智模型差异:


miniQMT(xtquant 外接) 大 QMT(客户端内策略)
你的代码跑在哪 你自己的 Python 进程 客户端内的唯一一条 Python 线程
多线程 / 多进程 自己进程里随你开 不可用,客户端架构约束
一个策略卡住 只影响你自己这个进程 同一客户端上所有策略一起停
等待类写法(sleep / 轮询等) 自己的进程自己负责 禁止,必须改写为状态机

迁移动作本身不难(行情、交易接口的语义大体能对上),难的是换脑:过去你操心的是自己进程里的并发安全,现在要操心的是”我的回调会不会把公共跑道堵死”。本文就围绕这条线程,把机制、坑位、写法纪律一次讲透。

二、为什么你必须搞懂”单线程”

三个真实场景,撞过的人都懂:

  1. 三个策略同时挂实盘,其中一个里写了 sleep 等成交——几分钟后发现另外两个策略也不动了。不是行情断了,是那条被 sleep 占住的线程是大家共用的。
  2. 下单函数刚调用完,立刻查资金和委托——什么都没查到,以为程序出 bug 了。其实是交易回报走的是另一条通道,你的线程根本没参与。
  3. 想给策略加 threading 提速——加不上,也不允许加。这不是 Python 语言的问题,是客户端的架构决定。

这三个场景指向同一件事:大 QMT 的 Python 策略层,只有一条线程。理解它,后面的每个设计决策才有依据。

三、原理:一条线程,三个入口

先看两条官方原文(来源:QMT 官方知识库):

出处 原文要点
使用须知 § 关于线程和进程 QMT 中 python 无法使用多线程和多进程,而且所有策略都在同一线程中执行,策略中应尽量避免阻塞类写法
常见问题 § 下单与回报相关 任意一个策略阻塞线程(死循环 / sleep / 加锁)会导致所有策略的执行被阻塞,所以不能在策略里写等待操作

大 QMT 提供两大类、三种运行机制,它们不是三条线程,而是三个往同一条线程里投递回调的入口:

机制 分类 触发源 盘中触发频率
handlebar 事件驱动 主图品种每个新分笔到达 股票 tick 约 3 秒一次
subscribe 回调 事件驱动 订阅品种新分笔到达 随行情推送
run_time 定时器 定时任务 固定时间间隔

画成图就是:

1
2
3
4
5
6
7
8
9
10
11
12
[主图品种 tick]      [订阅品种 tick]      [定时器到点]
│ │ │
▼ ▼ ▼
handlebar subscribe 回调 run_time 回调
└────────┬─────────┴──────────┬───────┘
▼
┌────────────────────┐
│ 唯一的 Python 线程 │ ← 所有策略、所有回调都在这里串行执行
└────────────────────┘
│ passorder 异步发出,调用后立刻返回
▼
客户端后台缓存 ← 柜台推送刷新(有主推 50ms / 无主推 1-6 秒)

串行、不可抢占:任一回调正在跑,其余回调只能在队列里等。

为什么下单不会把这条线程堵死

交易接口是异步的(常见问题 § 下单与回报相关):

  • passorder(quickTrade=2) 调用后立刻发出委托就返回,不等委托回报,也不阻塞 python 线程;
  • 委托 / 成交 / 持仓 / 资金状态由客户端后台维护本地缓存,get_trade_detail_data 和交易回调函数读的都是缓存,不是实时查柜台;
  • 缓存由柜台推送刷新——有交易主推的柜台 50ms 一次,没有主推的 1-6 秒一次(具体咨询自己的服务提供商)。

直接后果:卖出委托后立刻查询,查不到这笔委托、可用资金也不会变多。这不是 bug,是缓存延迟。所以实盘要用全局字典自建”待报状态机”,未确认前暂停该品种后续报单,防超单。

ContextInfo 深拷贝回滚——单线程逐 K 线模型的副产品

盘中每个分笔都触发一次 handlebar,QMT 为了模拟”K 线走完才生效”:

  1. 每次 handlebar 调用前对 ContextInfo 做一次深拷贝;
  2. 下一个分笔若不是新 K 线的首个分笔,ContextInfo 回滚为那份深拷贝;
  3. 只有 K 线结束分笔的修改才真正保存。

文档点明的两个副作用:每个分笔都深拷贝会拖慢策略运行;ContextInfo 只适合配 quickTrade=0,立刻下单(quickTrade=2)的状态必须存普通全局变量。这条机制在后面 Q5 讨论”合并策略”时还会用到。

四、被问爆的七个问题

Q1:大 QMT 真的就是单线程的吗?

是。而且注意:它不是一个可以切换的”模式”,没有”单线程模式 / 多线程模式”这个选项——只要你是正常登录的大 QMT 客户端(非极简形态),你写的每个策略都被放进同一条线程里跑,这是唯一的内置执行形态。

Q2:能变成多线程吗?

不能,而且升级 Python 版本也改变不了这一点。

原因要说透:threading / multiprocessing 在 QMT 策略里不可用,这不是 Python 语言的限制(Python 本身当然支持多线程),而是客户端架构的约束——所有策略的回调都由同一个宿主线程调用,客户端没有给 Python 层开第二条线程的口子。所以:

  • 你升级到 Python 3.12,线程模型原样不动,别指望版本升级带来并行;
  • 单线程带来的”不能等待”问题,官方用两层异步设计来补偿:下单异步(调用即返回)、回报后台缓存刷新。

Q3:内置 Python 能升级到 3.12 吗?

能,但不是”你”去升级,而是客户端版本升级带来的。

事实链:

  • QMT 官方知识库主体(快速开始、常见问题)目前仍写”内置 3.6 版本 Python”,示例代码基于 Python 3.6 规范;
  • 迅投官方社区 2026-10-15 的官方回复明确:将 QMT 的内置 Python 从 3.6.8 升级到 3.12,2.1.32 及以上版本的客户端支持该功能,具体应联系你服务提供商,问是否支持升级到 3.12。

三个注意点:

  1. 以你自己客户端的实际为准。券商版的更新节奏各不相同,看「设置 → 模型设置」指向的 bin.x64 目录、「我的 → 下载 Python 库」装出来的版本,才是你真实的运行环境;
  2. 升级客户端后先回归老策略。老代码基于 3.6 编写,3.12 下个别早已废弃的写法可能报错——先在模拟信号模式把每个策略完整跑一遍再上实盘;
  3. 再强调一次:3.12 不会带来多线程。版本解决的是语法与生态(新库、新语法),并发模型纹丝不动。

Q4:多个策略同时开启,到底是怎么个执行顺序?

Q5:该不该把所有策略写进一个策略里?

Q6:is_last_bar 到底该怎么用?实盘第一行怎么写?

Q7:单线程下怎么让策略跑得更快?

五、由此倒推出的编程纪律

六、几个容易做错的设计点

七、落地三步走

八、常见问题排查表

九、免责声明(请务必完整阅读)

未显示内容请前往微信公众号阅读:


赞赏作者

赞赏码

本页面赞赏完全自愿,属于读者无偿鼓励,不提供任何商品、服务、答疑、内容解锁等对价。您可随意选择是否赞赏以及赞赏金额。

微信二维码 Share