26 KiB
QMT Bridge 整体设计方案(统一单端口 8610)
版本: v3(2026-08-19, 2026-08-26 整合环境调研/最终方案) 状态: 实施中(WS 单通道推送一期已完成,见设计变更记录)
一、设计目标
- 复刻旧桥接口:把
miniqmt_bridge/bridge.py(FastAPI, 8610)的 9 个接口 1:1 覆盖,旧脚本零改动 - 新增订阅与推送:HTTP 订阅(单票/全量)+ WS 推送(行情 + 交易结果)
- 统一单端口:HTTP + WebSocket 都走 8610(性能无影响,技术问题可解,见下)
- 跑在大 QMT 内:策略编辑器加载,Python 3.6.8,纯标准库零依赖
- 薄桥原则: 桥只做协议转换 + 透传,QMT 统一处理业务逻辑。不追求大并发时不做订阅合并/缓存/过滤等自研逻辑,订阅、回调、数据处理全部透传给大 QMT(官方:多策略订阅同品种计数不累加,即 QMT 已内置合并)
- 实施路径(2026-08-20 定): 最小骨架先行(
/health+/data/kline+/data/quote+ WS 连接/心跳),验证整条链路;订阅端点、交易接口、WS 推送后续增量添加
1.1 命名空间(2026-08-19 定稿)
| 命名空间 | 内容 | 官方依据 |
|---|---|---|
| data | 行情、K线、板块、日历、合约信息、快照、订阅 | xtquant.xtdata(行情中心) |
| trade | 持仓、资金、委托、成交、下单、撤单 | xtquant.xttrader(交易中心) |
| 通用 | 健康检查 | - |
新旧并存: 旧桥无前缀路径保留(内部转发),新接口用 /data/* /trade/*。
一之二、QMT 环境调研(设计依据,2026-08-19 实测)
整合自
QMT内置Python环境与pip扩展调研.md(已删除,内容并入本设计文档)。 QMT 安装路径:C:\Programs\GJQMT_BIG(桌面快捷方式指向bin.x64\XtItClient.exe)
本机两个 QMT 实例
C:\Programs\GJQMT |
C:\Programs\GJQMT_BIG(★ 桥运行目标) |
|
|---|---|---|
| 身份 | miniQMT 版(有 XtMiniQmt.exe + userdata_mini) | 大 QMT 完整版(纯交易端) |
| Python | 极简嵌入式(pythonw + python36.dll,无 site-packages) | 完整 3.6.8 + 101 包 |
| 桥 | ❌ 不在此跑(miniQMT 关停中) | ✅ 策略编辑器 + 桥都跑这里 |
内置 Python 环境(实测)
| 项 | 值 |
|---|---|
| 解释器 | C:\Programs\GJQMT_BIG\bin.x64\pythonw.exe(嵌入式,无 python.exe/pip.exe) |
| 版本 | Python 3.6.8 (Dec 19 2022) 64位 |
| 发行包 | 101 个(site-packages 实测) |
| pip | 21.0.1(内置,可导入) |
| threading | 实测可用(线程能起) |
| 调用方式 | pythonw.exe -c "..."(GUI 子系统,无 stdout/stderr,需重定向到文件/StringIO) |
已内置关键包(桥方案相关)
- 通信: redis 3.5.3 ✅、pyzmq 18.0.1 ✅、requests 2.24.0、tornado 6.0.2
- 科学计算: numpy 1.19.1、pandas 0.22.0、scipy 1.5.2
- ML: scikit-learn 0.23.2、tensorflow 1.8.0、torch 1.0.1、talib 0.4.17
- 数据库: pymongo 3.11.3、pymssql 2.1.4
pip 扩展能力(实测可用)
- 结论: 可以用 pip 安装外部包(pythonw 无控制台需先设 stdout/stderr 代理,否则报
'NoneType' object has no attribute 'write') - 实测:
pip.main(["install","--target",target,"flask==2.0.3"])→ exit 0,安装成功并可 import - 方式 A:
--target装到独立目录(推荐,不污染 QMT);方式 B: 直接装进 site-packages(需管理员) - ⚠️ 注意: 只能装支持 cp36 的包;SSL 旧,HTTPS 可能失败(用
--trusted-host或国内镜像);C 扩展包需 cp36 wheel
对本方案的意义
- 零依赖方案成立: 纯标准库即可实现桥(当前实现);redis/pyzmq 内置可作备选传输
- HTTP 桥可扩展: 可 pip 装 flask/websockets 等
- 线程实测可用: 多线程在 QMT 内 OK(线程池方案成立)
- 策略可上 ML: talib/tensorflow/torch 内置
二、总体架构
┌──────────────────── 大 QMT 进程(策略) ────────────────────┐
│ qmt_bridge.py (统一入口, 薄桥) │
│ ├─ init(ContextInfo) │
│ │ ├─ set_account / GIL 调优 │
│ │ ├─ 启动 8610 统一服务器(HTTP + WS 共端口) │
│ │ └─ run_time("adjust", 5ms) 注册定时器 │
│ │ │
│ ├─ HTTP server (ThreadingMixIn, 8610) │
│ │ ├─ /data/* 查询 + 订阅 │
│ │ ├─ /trade/* 交易查询 │
│ │ └─ Upgrade: websocket → WS 帧循环 (RFC6455) │
│ │ │
│ ├─ WS 推送 (单通道 /ws, 按 type 区分) │
│ │ ├─ 行情推送: type=whole (全量tick增量, 已实现) │
│ │ └─ 交易结果推送: type=trade_result (预留未实现) │
│ │ │
│ ├─ 订阅管理 (bridge_subscription.py, 单客户端) │
│ ├─ adjust() 定时器 → 下单/撤单队列执行 (passorder/cancel) │
│ └─ 回调: order_callback / deal_callback → 回写 + WS 推送 │
└────────────────────────────────────────────────────────────┘
│ 8610 (HTTP + WS 同端口)
▼
外部: sfgrid / monitor_signals / 策略脚本 / AI 助手
注: 调度周期由 v3 初稿的 500ms 更新为 5ms(见"二之二、架构决策与性能实测")。
二之二、架构决策与性能实测(2026-08-20 定稿,整合自最终方案)
架构决策:自研 HTTP 桥(线程池 + 高频调度),不用 big_convert
经过多轮实测,最终确定:在 QMT 策略内跑自研 HTTP 桥,TS 客户端直接 fetch。
为什么不用 big_convert:
- big_convert 的 ZMQ 方案成熟,但要写 TS ZMQ library(协议复杂,2-4 周)
- HTTP 方案 TS 直接 fetch,零协议成本
- 关键突破:发现"线程池 + 高频 run_time 调度"能让 QMT 内 HTTP 变快
性能实测数据(5ms 调度 + 8 线程池)
| 调度周期 | 平均延迟 |
|---|---|
| 500ms(默认) | 500-2500ms ❌ |
| 50ms | 62.6ms |
| 5ms | 28.2ms(最快 7ms) ✅ |
| 接口 | 速度 | 真实数据 |
|---|---|---|
| /health | 24-57ms | - |
| /data/quote | 29-34ms | ✅ 真实行情 |
| /data/kline | 正常 | ✅ 真实K线 |
关键技术发现(QMT 环境特性)
- 线程创建慢,复用快 → 用线程池(预建 8 线程)
- run_time 调度周期决定线程调度频率 → 周期越短,线程响应越快
- 500ms 周期 → 线程每 500ms 才被调度(慢)
- 5ms 周期 → 线程每 5ms 被调度(快)
- 但周期太小有风险(big_convert 警告):adjust 热循环占 GIL,饿死后台线程
- 5ms = 200次/秒,远低于危险的 2500次/秒,实测稳定
- 行情读取线程安全(后台线程直接调 get_full_tick 快)
- 交易查询需要主线程(实测: get_trade_detail_data 在 HTTP 线程报
'NoneType' object has no attribute 'request_id'→ 用策略线程缓存/队列方案)
最终架构
TS 客户端(DSH 插件)
│ HTTP fetch(简单)
▼
QMT 策略内 HTTP 桥
├─ 线程池 8 线程(预建,复用)
├─ run_time 5ms 调度
├─ 单端口 8610
└─ data/trade 接口 + WS 单通道推送
│ ContextInfo/passorder
▼
大QMT
三、单端口(8610)方案分析
3.1 原理:WebSocket 握手就是 HTTP
客户端: GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xxx
服务端: 请求头含 Upgrade: websocket → 回 101 → 进入 WS 帧循环
否则 → 普通 HTTP 处理
3.2 性能影响:无
| 维度 | 分析 |
|---|---|
| 连接模型 | HTTP 已是 ThreadingMixIn(每连接线程);WS 也是每连接线程 → 合并后线程模型不变 |
| 握手成本 | WS 握手 = 一次 HTTP 请求,同成本 |
| 分流开销 | Upgrade 头判断是 O(1) |
| GIL | 同进程,合并不改变竞争;已 setswitchinterval(0.001) |
| 吞吐 | 单端口 accept + 双 handler,与双端口等价 |
3.3 潜在技术问题与解法
| 问题 | 影响 | 解法 |
|---|---|---|
| HTTP 短连接 vs WS 长连接超时差异 | HTTP server 的 socket 配置可能误伤 WS | WS 升级后 socket 单独 settimeout(2)(_client_loop 已有) |
| 鉴权统一 | WS 握手也能带 header | 统一走 HTTP 层 token 检查(Upgrade 前) |
| 连接泄漏 | WS 断开要清理 | _mark_dead 已有(引用计数拆除) |
3.4 结论:单端口可行 ✅
四、HTTP 接口(8610,复刻旧桥 9 接口 + data/trade 前缀)
详细规格见
旧桥接口规格对照.md;此处列结构。 命名空间: 大 QMT/xtquant 官方即分 data(行情) / trade(交易)两套,故接口加前缀。
4.0 命名空间设计(官方依据)
| 命名空间 | 内容 | 依据 |
|---|---|---|
| data | 行情、K线、板块、日历、合约信息、快照、订阅 | 官方 xtquant.xtdata(行情中心) |
| trade | 持仓、资金、委托、成交、下单、撤单 | 官方 xtquant.xttrader(交易中心) |
| 通用 | 健康检查 | - |
新旧并存策略: 旧桥无前缀路径(/kline 等)保留,内部转发到新前缀;新接口统一用 /data/* /trade/*。
data 命名空间(行情/数据)
| 方法 | 新前缀路径 | 旧兼容路径 | 底层大QMT函数 |
|---|---|---|---|
| GET | /data/kline | /kline | ContextInfo.get_market_data_ex |
| GET | /data/kline/batch | /kline/batch | 同上(多代码) |
| POST | /data/kline/download | /kline/download | 全局 download_history_data |
| GET | /data/instrument | /instrument | ContextInfo.get_instrument_detail |
| GET | /data/calendar/trading_dates | /calendar/trading_dates | ContextInfo.get_trading_dates |
| GET | /data/sectors | /sectors | ContextInfo.get_sector_list |
| GET | /data/sectors/stocks | /sectors/stocks | ContextInfo.get_stock_list_in_sector |
| GET | /data/stocks/delisted | /stocks/delisted | 板块差集 + 本地目录 + instrument |
| GET | /data/quote | /quote | ContextInfo.get_full_tick(实时快照) |
trade 命名空间(交易)
| 方法 | 路径 | 底层大QMT函数 |
|---|---|---|
| GET | /trade/positions | get_trade_detail_data(ACCOUNT,'position') |
| GET | /trade/asset | get_trade_detail_data(ACCOUNT,'account') |
| GET | /trade/orders | get_trade_detail_data(ACCOUNT,'order') |
| GET | /trade/trades | get_trade_detail_data(ACCOUNT,'deal') |
| POST | /trade/order | passorder(队列→adjust) |
| POST | /trade/cancel | cancel(队列→adjust) |
| GET | /trade/order/status | ORDER_RESULTS 查询(轮询兜底,推荐用 WS 收结果) |
订阅端点(HTTP,2026-08-19 定稿)
| 方法 | 路径 | body | 返回 |
|---|---|---|---|
| POST | /data/subscribe | {"code":"600519.SH","period":"1d","client_id":"..."} |
{"ok":true,"sub_id":123} |
| POST | /data/subscribe_whole | {"codes":["SH","SZ"],"client_id":"..."} |
{"ok":true,"sub_id":124} |
| POST | /data/unsubscribe | {"sub_id":123} |
{"ok":true} |
订阅是 HTTP 请求-响应;行情推送走 WS(需先建立 WS 连接并带同 client_id)。详见第五节。
通用
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health | 健康检查 |
错误格式统一: {"detail": "..."} + 400/404/500/503(与旧桥一致)
4.1 K 线周期合成规则(官方,直接影响 /kline 实现)
基础周期(实际存储): tick、1m、5m、1d
合成周期:
3m← 由 1m 合成10m, 15m, 30m, 60m, 2h, 3h, 4h← 由 5m 合成2d, 3d, 5d, 1w(周), 1mon(月), 1q(季), 1hy(半年), 1y(年)← 由 1d 合成
三条规则:
- 取合成周期历史 → 必须先下载基础周期(如取 15m 先下载 5m)
- 取实时 → 可直接订阅原始周期
- 基础+合成混用 → 只下载基础周期一次(5m+15m 只需下载 5m)
/kline 实现映射(旧桥已遵循):
| 请求周期 | 底层动作 |
|---|---|
| tick/1m/5m/1d | 直接 download + get_market_data_ex |
| 3m | 先下载 1m → 取 3m |
| 10m/15m/30m/60m/2h/3h/4h | 先下载 5m → 取合成周期 |
| 1w/1mon/1q/1hy/1y | 先下载 1d → 取合成周期 |
五、订阅与推送(HTTP 订阅 + WS 回调推送)
5.0 架构原则(2026-08-19 定稿):按协议特性划分
不按 data/trade 划分协议,而是按"请求-响应 vs 推送"划分:
| 协议 | 职责 | 承载 |
|---|---|---|
| HTTP(请求-响应) | 所有主动操作:查询、订阅/退订、下单请求、下载 | /data/* /trade/* 全部查询 + 订阅端点 |
| WebSocket(双向长连接) | 所有异步回调/推送:行情、交易结果 | 行情 tick、trade_result、心跳 |
理由:
- 订阅是"一次性动作 + 明确返回",天然适合 HTTP(返回订阅号、幂等重试)
- WS 连接专一化:客户端只收推送,不用发请求(除心跳),连接管理简单
- 交易结果异步推送(替代 HTTP 轮询 order/status)
- 符合薄桥原则:桥只做"HTTP 请求→QMT 调用"和"QMT 回调→WS 推送"
HTTP (8610) WebSocket (8610 同端口)
├─ POST /data/subscribe 订阅单票 ─► 行情推送 {type:"quote",...}
├─ POST /data/subscribe_whole 全量 ──► 行情推送 {type:"whole",...}
├─ POST /data/unsubscribe 退订
├─ GET /data/kline 查询K线
├─ POST /trade/order 下单 ────► 交易结果 {type:"trade_result",rid,...}
├─ POST /trade/cancel 撤单 ────► 交易结果 {type:"trade_result",rid,...}
└─ ... 其余查询/操作 只有:行情推送 + 交易结果 + 心跳
5.1 订阅关联机制(HTTP 订阅 ↔ WS 推送)
核心问题: HTTP 订阅后,行情推给哪个 WS 连接?
采用方案:client_id 绑定
1. 客户端建立 WS 连接,握手带 client_id(或首个消息声明):
ws://host:8610/ws?client_id=my-app-1 (或 X-Client-Id 头)
2. 客户端 HTTP 订阅时带 client_id:
POST /data/subscribe body: {"code":"600519.SH","period":"1d","client_id":"my-app-1"}
3. 桥记录: 订阅(sub_id) → client_id → WS 连接
QMT 回调 → 查订阅表 → 按 client_id 找 WS 连接 → 推送
关联表:
_SUBS[key] = {qmt_subid, refcount, clients: {client_id}}
_WS_CONN[conn] = {client_id, alive, subs: set(key)}
- 同一 client_id 可多个 WS 连接(负载/重连),推送广播到该 client_id 的所有连接
- 无 WS 连接时订阅仍生效(数据缓存),连接建立后补推? (MVP: 不补推,客户端重连后重新订阅)
5.2 HTTP 订阅端点
| 方法 | 路径 | body | 返回 |
|---|---|---|---|
| POST | /data/subscribe |
{"code":"600519.SH","period":"1d","client_id":"..."} |
{"ok":true,"sub_id":123} |
| POST | /data/subscribe_whole |
{"codes":["SH","SZ"],"client_id":"..."} |
{"ok":true,"sub_id":124} |
| POST | /data/unsubscribe |
{"sub_id":123} 或 {"code":"...","period":"...","client_id":"..."} |
{"ok":true} |
- 单票订阅透传 →
ContextInfo.subscribe_quote(code, period, cb) - 全量订阅透传 →
ContextInfo.subscribe_whole_quote(codes, cb) - 返回
sub_id(QMT 订阅号或桥生成),用于退订 - 错误:
{"detail":"..."}+ 400/404/503
5.3 单票订阅 vs 全量订阅(QMT 官方定义)
前提确认(实盘验证): 大 QMT 的 ContextInfo 有订阅逻辑,与 miniQMT 同名但形态不同。
依据 xtquant_big_convert 实盘验证(quote_subscription_manager.py 第 48-51 行):
"Verified against the real environment:
ContextInfo.subscribe_whole_quote(code_list, callback)returns an int subscription id (<0on failure) and pushes INCREMENTAL{code: tick}batches on a dedicated quote thread;ContextInfo.unsubscribe_quote(sub_id)cancels it."
| 方法 | 大 QMT 行为 |
|---|---|
ContextInfo.subscribe_whole_quote(codes, cb) |
全量订阅,返回 int 订阅号,失败 <0 |
ContextInfo.subscribe_quote(code, period, cb) |
单票订阅(官方文档确认) |
ContextInfo.unsubscribe_quote(sub_id) |
反订阅 |
ContextInfo.get_full_tick(codes) |
取当前快照(全推增量前的 prime) |
大 QMT vs miniQMT:
| miniQMT(xtdata) | 大 QMT(ContextInfo) | |
|---|---|---|
| 调用 | xtdata.subscribe_quote(...) |
ContextInfo.subscribe_quote(...) |
| 位置 | 外部 Python | 策略进程内 |
| 回调线程 | 独立行情线程 | 专用 quote 线程(不阻塞主线程) |
| 连接 | 连 miniQMT 58610 | 无需连接,策略内直接用 |
| 单票订阅 | 全量订阅 | |
|---|---|---|
| QMT 函数 | ContextInfo.subscribe_quote(code, period, cb) |
ContextInfo.subscribe_whole_quote(code_list, cb) |
| 数据 | 指定周期 K 线(分笔/1m/5m/1d) | 分笔快照(最新值,无历史) |
| 推送时机 | 该股行情更新 | 增量推送有变化的品种 |
| 五档盘口 | 有(视行情权限) | 默认无,只有最新价(需开全推行情级别) |
| 数量限制 | 非VIP ~300 个(按周期累加计数) | 不占单股订阅数,无品种上限 |
| 数据形态 | {code: DataFrame}(K线) |
{code: {lastPrice, volume, amount, ...}}(tick dict) |
| 典型场景 | 网格策略盯指定票 | 全市场监控 / 异动扫描 |
重要官方细节:
- 单票订阅同一品种不同周期累加计数(如订阅 1m+5m+1d 算 3 个)
- 多策略订阅同一品种计数不累加(QMT 内部去重)
- 全推数据服务器对交易所数据即时转发,打包增量下发
- 全推默认无五档(需修改行情源的全推行情级别);若要五档需客户端确认权限
5.4 WS 推送协议(服务端 → 客户端)
客户端建立 WS 连接后,主要收推送(除心跳外不发请求):
// 行情推送 - 单票(HTTP 订阅后)
{"type":"quote","code":"600519.SH","data":{"close":[1500.0,1510.0],"volume":[...],"_index":["20260818","20260819"]}}
// 行情推送 - 全量(HTTP 订阅后,增量tick)
{"type":"whole","data":{"600519.SH":{"lastPrice":1500.0,"volume":12345,"amount":...,"askPrice":[...],"bidPrice":[...]}}}
// 交易结果推送(HTTP 下单后,替代轮询)
{"type":"trade_result","rid":"r123","status":"submitted","code":"600519.SH","opType":23,"volume":100}
{"type":"trade_result","rid":"r123","status":"order_update","orderSysId":"SYS1","orderStatus":50,"volumeTraded":50}
{"type":"trade_result","rid":"r123","status":"dealt","price":1500.0,"volume":100,"amount":150000.0}
{"type":"trade_result","rid":"r123","status":"order_error","error":"..."}
// 心跳响应
{"type":"pong","ts":...}
客户端 → 服务端(仅心跳,可选):
{"action":"ping"}
5.5 服务端实现
┌─ 单票订阅(subscribe_quote) ──► QMT回调 → {type:"quote"} 推送
HTTP POST /data/subscribe ──► 订阅表
└─ 全量订阅(subscribe_whole_quote) ─► QMT回调 → {type:"whole"} 推送
HTTP POST /trade/order ──► 队列 → adjust → passorder
└─ order_callback/deal_callback → {type:"trade_result"} 推送
订阅表(桥内状态):
_SUBS[key] = {qmt_subid, refcount, clients: {client_id}}
key:
- 单票: ("quote", code.upper(), period)
- 全量: ("whole", tuple(sorted(codes.upper())))
QMT 回调 → WS 推送转换:
- 单票回调
data = {code: DataFrame}→{"type":"quote","code":code,"data":{col:[vals],"_index":[...]}} - 全量回调
data = {code: tick_dict}→{"type":"whole","data":{code: tick_dict}} - 交易回调(ORDER_RESULTS 更新后)→
{"type":"trade_result","rid":remark,...}推给该 rid 关联的 client_id
分发:
- QMT 回调 → 按订阅表找 client_id 集合 → 推给该 client_id 的所有 WS 连接
- 无 WS 连接在线时:行情丢弃(增量);交易结果存 ORDER_RESULTS(可 HTTP 查)
5.6 边界与注意
- 订阅上限: 单票超 ~300 时拒绝新订阅,HTTP 返回
{"detail":"sub failed/limit"}+ 503 - 全量五档: 默认只有最新价;若客户端要五档,需 QMT 行情源开启全推行情级别(文档提示)
- 初次订阅耗时: 单票订阅"初次订阅耗时长",客户端应容忍订阅后首次数据延迟
- 周期: 单票仅 4 种基本周期(tick/1m/5m/1d)+ L2(有权限);全量只有分笔
- 并发推送: QMT 回调可能频繁触发,推送用短帧、不阻塞回调;广播失败(客户端断开)静默清理
- 订阅后先推快照(全量订阅必须,实盘验证): 全推回调是增量的,客户端订阅后第一次要先
get_full_tick([codes])拿当前快照,再进入增量推送(xtquant_big_convert 实盘验证了此设计)
5.7 分时以上 K 线订阅的处理(重要设计决策)
依据 xtquant_big_convert 实盘实现: 分时以上 K 线(1m/5m/1d)不做 QMT 推送订阅,而是轮询 get_market_data_ex。
源码证据(xtquant_compat.py subscribe_quote):
- K 线周期的"订阅"回调 = 拉一次
get_market_data_ex历史 - 服务端
save_quote_subscription只登记状态,不建立 K 线推送通道 - 客户端靠自己的定时器反复拉最新 K 线
原因:
- K 线是聚合数据,1m 一分钟一根、1d 一天一根,轮询成本极低
- QMT 单票 subscribe_quote 有 ~300 上限,不值得为 K 线占配额
- 全推只有分笔,给不了 K 线
我们的桥对应设计:
| 数据 | 方式 | 通道 |
|---|---|---|
| 分笔/tick(实时) | QMT 全推真推送 | WS(HTTP 订阅后推送) |
| 1m/5m/1d K线 | 轮询 get_market_data_ex |
HTTP /kline(客户端主动拉) |
| K线推送式(可选) | 桥内定时器轮询 → 推 WS | WS (type=kline, 桥内周期拉取) |
决策: MVP 阶段 K 线走 HTTP /kline 轮询(简单、不占配额);若客户端需要推送式 K 线,再加桥内周期轮询推送(第 3 行)。
六、模块结构(薄桥版)
决策: 保持单文件(qmt_bridge.py),因为:
- QMT 策略编辑器加载单文件最简单(一个文件一个策略)
- 薄桥逻辑简单,拆多个文件反而增加 QMT 目录同步负担
- 后续需要再拆(WS 复杂化时)
qmt_bridge.py 统一入口 + HTTP handler + WS 订阅 + 下单队列(薄桥,全部透传)
6.1 单文件内部结构
qmt_bridge.py
├─ 配置区 PORT/ACCOUNT/ACCOUNT_TYPE/TOKEN/WS...
├─ QMT 生命周期 init / handlebar / adjust / stop
├─ HTTP handler BaseHTTPRequestHandler
│ ├─ /data/* 行情查询 + 订阅端点(旧路径兼容转发)
│ ├─ /trade/* 交易查询/下单
│ └─ Upgrade: websocket → WS 逻辑
├─ WS 推送 握手/帧/心跳(只收心跳,专注推送行情 + trade_result)
├─ 订阅表 HTTP 订阅 → 关联 client_id → QMT subscribe(透传)
├─ 下单队列 adjust() 里执行 passorder/cancel(唯一非透传: 下单必须策略线程)
└─ 回调 order_callback / deal_callback(回写 ORDER_RESULTS + WS 推送)
6.2 薄桥下"唯一需要桥自己逻辑"的地方
| 功能 | 是否透传 | 说明 |
|---|---|---|
| 行情查询(/data/*) | ✅ 透传 | 直接调 ContextInfo |
| 订阅(HTTP) | ✅ 透传 | 直接 subscribe_quote/whole_quote,按 client_id 记录关联 |
| WS 推送 | ⚠️ 桥内关联 | QMT 回调 → 查订阅表 → 推给 client_id 的 WS 连接 |
| 持仓/资金/委托/成交查询(/trade/*) | ✅ 透传 | 直接 get_trade_detail_data |
| 下单/撤单 | ⚠️ 队列 | passorder 必须在策略线程(adjust)执行,HTTP 线程不能直调(官方约束) |
| 状态回写 | ⚠️ 回调+推送 | passorder 无返回,靠 order_callback/deal_callback 回写 + WS trade_result 推送 |
六之二、实现计划(薄桥 MVP)
Phase 1: 最小骨架(2026-08-20 定,本次实现)
- 单文件
qmt_bridge.py:HTTP handler +/health+/data/kline+/data/quote - WS 连接打通(Upgrade + 帧循环 + 心跳,暂不推送内容)
- 旧路径兼容(
/kline→/data/kline,/quote→/data/quote) - 本地 mock 测试(最小骨架 3 接口 + WS 连接)
Phase 1.5: 增量(骨架验证后)
- data 其余查询接口(/kline/batch /kline/download /instrument /calendar /sectors /sectors/stocks /stocks/delisted)
- trade 查询接口(/positions /asset /orders /trades)
- 下单/撤单队列 + adjust + 回调回写
Phase 1.6: 订阅与推送(骨架 + 查询稳定后)
- HTTP 订阅端点(/data/subscribe /subscribe_whole /unsubscribe)+ client_id 关联
- WS 行情推送(type=quote / type=whole)
- WS 交易结果推送(type=trade_result)
Phase 2: 真实环境验证
- 部署到 QMT(GJQMT_BIG),跑通待实测项
- 模拟盘验证下单/撤单/回报
Phase 3: 扩展(按需)
- 交易类接口细化(两融/期权/新股)
- 推送式 K 线(桥内轮询)
- Redis/MQ 中转(海量客户端时)
七、端口决策记录
| 方案 | 结论 | 理由 |
|---|---|---|
| 双端口(8610 HTTP + 8611 WS) | 备选 | 实现简单,但多开一个端口 |
| 单端口(8610 统一) | 选定 | 无性能影响,技术问题可解,部署/安全/客户端更简单 |
八、待实测项(真实 QMT)
ContextInfo.get_market_data_ex返回结构确认(index=时间? columns=字段?)download_history_data全局函数是否可用/stocks/delisted的大QMT 本地数据目录结构get_sector_list是否原生(还是 fallback)- WS 单端口升级在 ThreadingMixIn 下是否正常
- HTTP 订阅后,
subscribe_quote/subscribe_whole_quote回调能否正确触达(真实行情环境) - 下单后
order_callback/deal_callback是否实盘模式才触发(决定 trade_result 推送的可靠性)
九、设计变更记录
| 日期 | 变更 |
|---|---|
| 2026-08-19 | v3 定稿:薄桥 + data/trade 前缀 + 单端口 8610 |
| 2026-08-19 | 协议划分变更: HTTP 负责请求/订阅/下单,WS 只做推送(行情 + 交易结果)。订阅从 WS 移入 HTTP,新增 /data/subscribe 等端点;WS 新增 trade_result 推送,替代 order/status 轮询 |
| 2026-08-20 | 架构决策: 自研 HTTP 桥(线程池 8 线程 + run_time 5ms 调度),不用 big_convert;整合自最终方案.md(详见二之二) |
| 2026-08-26 | WS 单通道推送(一期)完成: 单通道 /ws + 全量tick订阅(type=whole),订阅/退订走 HTTP(/data/subscribe,/data/unsubscribe),新增 /data/tick 透传 get_full_tick;详情见 docs/迭代记录/WS单通道推送设计.md |