Files
qmt_bridge/docs/项目规范.md

17 KiB

QMT Bridge 项目规范

版本: 1.1 (2026-08-26) 用途: 新会话快速参考。接手本项目前请先读本文件 + README.md + docs/设计/整体设计方案_v3.md。 本文件沉淀了历次会话验证过的技术决策与约束,是项目的"宪法"。


一、项目定位

大 QMT 薄桥:跑在国金 QMT(GJQMT_BIG)策略进程内的 HTTP + WebSocket 服务,把 QMT 的行情/交易能力暴露为 RESTful 接口和 WS 推送,供外部系统(sfgrid 网格策略、监控脚本、AI 助手)调用。

  • 环境: QMT 内置 Python 3.6.8,纯标准库零依赖(不能装第三方包)
  • 端口: 8610(HTTP + WS 共端口)
  • 账号: 从 C:\Programs\GJQMT_BIG\python\bridge_local_config.pyACCOUNT_ID
  • 外部消费者: sfgrid(C:\Users\Docker\Development\sfgrid,当前走 miniQMT SDK,计划迁移到桥)

二、项目结构规范

qmt_bridge/
├── README.md                  # 项目说明(入口文档)
├── docs/
│   ├── 项目规范.md            # 本文件
│   ├── api_spec/openapi.yaml  # 接口规范(唯一事实来源,中文,主动维护)
│   ├── 设计/整体设计方案_v3.md # 主设计文档(唯一设计依据)
│   ├── 研究/                  # 调研类文档(xtquant_big_convert调研、官方接口核实)
│   ├── 迭代记录/              # 迭代完成记录(WS单通道推送设计等)
│   ├── 桥接口清单与sfgrid满足度对照.md  # sfgrid 需求 vs 桥能力
│   └── ...
├── src/                       # 桥源码(部署时复制到 QMT python 目录)
│   ├── bridge_main.py         # QMT 生命周期入口
│   ├── bridge_http_server.py  # HTTP server + 路由(含 /mcp 路由)
│   ├── bridge_ws_server.py    # WebSocket(心跳 + 推送 broadcast_all)
│   ├── bridge_data_adapter.py # QMT API 透传适配层
│   ├── bridge_subscription.py # 订阅管理(snowflake sub_id + QMT 订阅回调→WS 推送)
│   ├── bridge_mcp_server.py   # MCP 端点(JSON-RPC tools,POST /mcp)
│   ├── bridge_util.py         # 共享状态 + 工具
│   └── qmt_strategy_entry.py  # QMT 策略入口(粘贴进 QMT 编辑器,含热重载)
├── tools/
│   ├── push_apifox.py         # 生成 openapi.json + Apifox 手动导入指引
│   ├── fetch_apifox_doc.ps1   # 沙箱外抓取 Apifox 文档(UAC 提权)
│   └── fetch_qmt_docs.ps1     # 沙箱外抓取 QMT 官方文档(UAC 提权)
├── tests/
│   ├── test_trade.py          # 交易接口 mock 测试
│   ├── test_subscription.py   # 订阅/退订/tick mock 测试
│   ├── test_ws_push.py        # WS 推送链路 mock 测试
│   ├── test_ws_real.py        # 真实 QMT WS 推送验证
│   └── test_sub_no_ws.py      # 无 WS 连接时订阅行为测试
├── reference/                 # 参考材料(xtquant_big_convert 等,只读)
└── deploy_elevated.bat        # 部署脚本(自动提权)

命名规范

  • 桥模块: bridge_*.py(部署目标),入口: qmt_strategy_entry.py(永不改,只转发)
  • 文档: docs/ 下中文命名,api_spec/ 放接口规范,设计/ 放设计文档,研究/ 放调研文档,迭代记录/ 放迭代完成记录
  • 诊断/一次性脚本: 用完即删,不留 diag_*.py 垃圾

三、设计约束(硬性,踩过坑)

3.1 源码编码约束 ⚠️ 最重要

  • 源码文件头 # coding:gbk,内容必须纯 ASCII(0-127)
  • 不允许在 .py 源码里写 UTF-8 中文字符(QMT Python 3.6 按 GBK 解析,UTF-8 中文会 SyntaxError)
  • 需要中文时用 \uXXXX 转义(运行时输出正确)
  • 检查方法: 统计文件非 ASCII 字节,必须为 0

3.2 QMT 函数线程约束

函数 能否在 HTTP worker 线程调用 处理方案
get_trade_detail_data 不能(报 NoneType.request_id) 策略线程缓存(adjust 刷新,HTTP 读缓存)
passorder / cancel 不能 adjust 队列(下单请求入队,adjust 执行)
download_history_data 可以(实测) 直接调用
get_market_data_ex / get_full_tick / get_instrument_detail / get_trading_dates 可以 直接调用
subscribe_whole_quote 可以(实测,2026-08-26 验证) 直接调用,回调→WS 推送
get_trade_detail_data(acct, type, 'ORDER', strategy_name) 可以(带第4参数实测 OK) 服务端过滤

核心原则: 任何 QMT 函数若在 HTTP 线程调用报错,一律改为"策略线程执行 + 缓存/队列"模式。

3.3 大 QMT 与 miniQMT 字段差异

  • 大 QMT get_trade_detail_data('ORDER') 返回对象没有 m_strStrategyName/m_strRemark 字段(miniQMT 有)
  • 原因: 策略名/备注是下单终端本地属性,跨终端不可见(参考项目 C8 约束)
  • 结论: 按策略名过滤必须用服务端参数(get_trade_detail_data 第 4 参数),不能靠字段
  • m_strInstrumentID 不带后缀(如 600719,不是 600719.SH)
  • get_full_tick 返回 {code: tick_dict},tick 字段含 timetag/lastPrice/open/high/low/amount/volume/pvolume/stockStatus 等

3.4 账号机制(单账号,重要结论)

  • QMT 一个策略实例 = 一个资金账号(界面选定账号后注入全局变量 account,策略内引用)
  • ContextInfo.set_account(account) 在 init 中调用: 设定账号 + 订阅交易主推回调(order/deal/account 回调的前提;init 后调用不再订阅)
  • passorder 的 account 传空时,用最后一次 set_account 的账号
  • 查询函数 get_trade_detail_data(accountID, type, dtype, strategyName)accountID 必填
  • 多账户支持: 官方机制是每个策略实例绑一个账号 → 要支持多账号 = 部署多个桥实例(多个策略,各选账号,各用不同端口 8610/8618...)
  • 结论: 桥本身天然单账号,HTTP 接口的 account/account_type 参数已移除(冗余)
    • 2026-08-21 已清理: get_trade_data/_submit_trade_request/_drain_trade_requests/_api_trade 的 account + account_type 参数全部删除
    • account/account_type 均为桥绑定配置(bridge_local_config.py),HTTP 接口不暴露
    • 新接口设计不需要账号参数;如需多账号,部署多桥实例而非加参数

3.5 K 线数据规则

  • 查询前必须先下载(download_history_data),否则返回残缺数据(OHLC 全同/volume=0)
  • 合成周期需下载基础周期: 3m←1m; 10m~4h←5m; 2d~1y←1d
  • get_market_data_ex 的 fields 参数传完整字段列表(["open","high","low","close","volume","amount"]),传 [] 只返回 close
  • DataFrame 取值用 iat[i,j](位置),不用 loc(大 QMT index 可能非唯一)

3.6 WS 订阅与推送(单通道,2026-08-26 新增)

  • 单通道 /ws,消息按 type 区分(whole 全量tick、pong 心跳)
  • 订阅/退订走 HTTP: POST /data/subscribe(type 在 body)、POST /data/unsubscribe(sub_id)
  • sub_id: snowflake 类唯一 ID(时间戳 << 12 | 序列号)
  • 无 client_id(单客户端设计): 推送发到唯一 WS 连接
  • 推送过滤: 订阅了什么类型才推什么;没订阅的不推
  • 不做 prime 推送: 客户端需要快照时调 GET /data/tick(透传 get_full_tick)
  • 订阅但无 WS 连接: QMT 推送继续,数据被静默丢弃(不缓冲/补推)
  • 详细设计见 docs/迭代记录/WS单通道推送设计.md

3.7 接口设计规范

  • 错误格式统一: {"detail": "..."} + 4xx/5xx
  • 返回格式: 查询类 {"ok": true, "data": [...]},K线 {"code","period","count","data"}
  • fields 参数: 不传=默认完整 OHLCV,传了=按白名单过滤(不要默认 close)
  • 旧路径兼容: /kline/data/kline 等前缀别名保留
  • 必填参数校验: code 等必填参数在代码转换前先校验原始值非空,返回 400 {"detail":"code required"}
    • 踩坑: _code_to_full("") 返回 ".SH",会绕过 if not code 校验 → 必须校验原始参数

3.8 MCP 端点(2026-08-27 新增)

  • POST /mcp: MCP(Model Context Protocol)端点,JSON-RPC 2.0 应用协议,与 HTTP/WS 同端口 8610
  • 为什么桥内嵌: QMT 内置 Python 3.6.8 纯标准库,官方 mcp SDK 要求 Python ≥ 3.10;桥手写 JSON-RPC(仅 json/socket/threading),复用现有 adapter
  • 实现范围: initialize / notifications/initialized / ping / tools/list / tools/call,暂不做 resources/prompts/SSE 流式
  • 工具与 HTTP 1:1: 9 个工具(qmt_kline/quote/tick/instrument/trading_dates/positions/asset/orders/trades),直接复用 bridge_data_adapter → QMT 线程约束与 HTTP 完全一致
  • 响应规则: JSON-RPC 错误走 HTTP 200(规范要求);notification 走 HTTP 202 空 body;非法 body 走 400 {"detail":...}
  • 鉴权/CORS: 鉴权复用 TOKEN(X-Token 头);响应带 CORS 头,OPTIONS /mcp 返回 204 预检
  • 热重载: qmt_strategy_entry.py_BRIDGE_MODULES 已含 bridge_mcp_server
  • 详细设计见 docs/迭代记录/MCP服务.md

四、部署规范

4.1 部署流程

  1. 修改 src/ 下代码
  2. 编译 + ASCII 检查(见下文验证清单)
  3. 复制到 C:\Programs\GJQMT_BIG\python\(可用 deploy_elevated.bat 或直接 Copy-Item + danger-full-access)
  4. QMT 策略编辑器: 停止 → 运行(热重载生效,不用重启 QMT)
    • ⚠️ 若端口被残留占用(监听但不响应),需重启 QMT 进程彻底释放

4.2 热重载机制

  • qmt_strategy_entry.py 顶部 _reload_bridge(): 重跑时先 stop 旧桥(释放端口) → 清 sys.modules 缓存 → 重新 import
  • 前提: QMT 编辑器里粘贴的是新版 qmt_strategy_entry.py 内容(不是旧版转发器)
  • QMT 进程不退出,模块会被缓存;不清理则改代码不生效
  • 新模块加入时需同步更新 _BRIDGE_MODULES 热重载列表(如 bridge_subscription)

4.3 部署验证清单

1. python -c "import py_compile; py_compile.compile(f, doraise=True)"  # 编译
2. 统计非 ASCII 字节 = 0                                              # 编码
3. python tests/test_trade.py 等 mock 测试                             # 本地 mock
4. QMT 停止→运行 → curl 各端点                                         # 真实验证

4.4 沙箱/权限

  • 当前会话(pwsh)默认 workspace-write:不能写 C:\Programs\(连 cmd.exe 都拒绝)
  • 需要写 QMT 目录时用 sandbox_permissions: danger-full-access(会弹审批,用户批准)
  • 外网访问(api.apifox.com 等)同样需要 danger-full-access
  • 沙箱 DNS 白名单限制部分域名 → 可用UAC 提权脚本在沙箱外执行(如 tools/fetch_*.ps1)

五、接口文档规范

5.1 唯一事实来源

  • docs/api_spec/openapi.yaml 是接口规范的唯一权威,中文,主动维护
  • 每次改接口 → 同步更新 YAML + 重新生成 openapi.json

5.2 桥提供 OpenAPI 端点(2026-08-25 新增)

  • 桥提供 GET /openapi.yamlGET /openapi.json(从 QMT 部署目录读取)
  • 用途: Apifox URL 同步(Apifox 主动拉取)
  • 更新 openapi 文件后需部署到 QMT python 目录(桥每次请求时读文件,无需重启)

5.3 导入 Apifox

  • 项目: QMT_HTTP_BRIDGE (ID 8742354)
  • 手动导入: Apifox → 导入数据 → OpenAPI/Swagger → 文件导入 docs/api_spec/openapi.yaml
  • 或 URL 导入: http://<桥IP>:8610/openapi.yaml(需 Apifox 能访问到桥,可能要内网穿透)
  • 开放 API 自动推送未打通: POST /v1/projects/8742354/import-openapi
    • 沙箱网络代理对 Python urllib 伪造 201 空响应(text/html + 0 字节,请求未真正到达)
    • PowerShell 直连返回 422(body 格式未确认,错误体为空)
    • 结论: 以手动导入 / URL 同步为可靠路径; 自动推送留待网络环境/格式确认后再打通

5.4 OpenAPI 内容规范

  • info.title 用中文("QMT桥接服务接口")
  • 每个接口: 中文 summary + 中文 description + 参数表 + 实测响应示例
  • 参数默认值必须与代码一致(如 fields 默认 OHLCV)
  • 示例数据用真实实测值(600519/600900 等)

六、关键已知信息(避免重复踩坑)

主题 结论
涨跌停价 ContextInfo.get_instrument_detailUpStopPrice/DownStopPrice,不在 get_full_tick 里
交易日历 大 QMT 签名 get_trading_dates(stockcode, start, end, count, period);实现兼容 (SH,start,end,count) → (start,end) → (SH,start,end)
账号机制 一个策略实例=一个账号;set_account 订阅回调;桥单账号,HTTP account 参数冗余
市场活跃判断 sfgrid 用"120秒无行情"看门狗;桥可用 trading_dates + 时间判断
passorder 参数 第10参数 userOrderId = 订单 m_strRemark(sfgrid 用它存 "类型,网格,代码")
sfgrid 策略名 固定 "SFGRID";remark 格式 "{类型},{网格索引},{股票代码}"
订单状态码 54=已撤, 57=废单; status=active 排除这两个
下单必须带 strategyName + userOrderId(remark),否则无法回查归属
全量tick订阅 subscribe_whole_quote 增量推送 {code: tick_dict};订阅走 HTTP,推送走 WS /ws
参考项目 reference/xtquant_big_convert 有大量实盘验证过的坑(务必参考)

七、待办/路线图

  • /trade/order 下单(passorder, 队列→adjust, 透传 strategyName + userOrderId)
  • /trade/cancel 撤单(cancel, 队列→adjust)
  • /trade/order/status 下单状态查询
  • WS 推送 trade_result(账号交易通知: 成交/订单/持仓/资金,预留命名未实现)
  • /trade/orders 按 remark 过滤(暂缓,依赖下单后 remark 可见)
  • MCP qmt_order / qmt_cancel(依赖交易接口迭代)
  • MCP SSE 流式传输(长连接占线程池 worker,参考 WS 处理;默认不做)
  • sfgrid 迁移到桥(替换 qmt_real.py 的 SDK 直连)

八、会话历史关键决策(留痕)

日期 决策
2026-08-20 trade 查询线程问题修复: get_trade_detail_data 不能在 HTTP 线程调 → 策略线程缓存
2026-08-20 新增 /data/instrument(涨跌停价,get_instrument_detail)
2026-08-20 新增 /data/calendar/trading_dates
2026-08-20 K线修复: 先下载后查询 + fields 完整列表 + iat 取值 + 默认完整 OHLCV
2026-08-21 热重载: qmt_strategy_entry 清模块缓存 + 释放端口,改代码只需停止→运行
2026-08-21 /trade/orders 过滤: code(客户端) + status(客户端) + strategy_name(服务端第4参数)
2026-08-21 移除代码内 OpenAPI/Swagger,改用 docs/api_spec/openapi.yaml + Apifox 导入
2026-08-21 Apifox 自动推送未打通(沙箱代理伪造201/PowerShell 422),定为手动导入;文档已验证中文接口可导入
2026-08-21 code 必填校验修复: 转换前先校验原始参数(kline/quote/instrument 缺 code 返回400)
2026-08-21 trading_dates 签名修复: 大QMT 需 (SH,start,end,count),兼容多种签名
2026-08-21 账号机制确认: QMT 一个策略实例=一个账号,桥单账号,HTTP account 参数冗余
2026-08-21 移除 HTTP 接口的 account/account_type 冗余参数,OpenAPI 同步更新
2026-08-25 桥新增 /openapi.yaml + /openapi.json 端点(供 Apifox URL 同步)
2026-08-26 WS 单通道推送一期完成: 单通道 /ws + 全量tick订阅(whole),HTTP 订阅/退订,/data/tick 透传
2026-08-26 桥端口迁移: 8617 → 8610(残留监听问题,需重启 QMT 进程释放)
2026-08-26 持仓查询接口增强: GET /trade/positions 返回全部持仓(语义化字段 stock_code/volume/available/avg_price/market_value/profit + summary),不做过滤;详见 docs/迭代记录/持仓查询接口.md
2026-08-27 MCP 端点一期完成: POST /mcp(JSON-RPC 2.0,initialize/ping/tools/list/tools/call),9 个工具与 HTTP 接口 1:1,复用 adapter(QMT 线程约束一致);详见 docs/迭代记录/MCP服务.md

九、迭代规范

9.1 迭代记录管理

每一次迭代的设计文档/记录都放在 docs/迭代记录/ 目录下,并在 docs/迭代记录/index.md 中登记索引。

9.2 迭代记录流程

  1. 迭代启动: 讨论设计(遵循"讨论什么记什么"原则,未讨论的不写死)
  2. 设计定稿: 迭代设计文档标记为"设计定稿",移入 docs/迭代记录/
  3. 实现完成: 设计文档状态改为" 已完成",补充实现记录
  4. 登记索引: 在 docs/迭代记录/index.md 添加一行: 迭代名、日期、状态、关联文档

9.3 迭代记录文档模板

每次迭代在 docs/迭代记录/ 下新建 <迭代名>.md,建议结构:

# <迭代名>

> 版本: x.y (日期)
> 状态: 讨论中 / 设计定稿 / ✅ 已完成
> 关联: docs/设计/整体设计方案_v3.md 相关章节
> 本次迭代: <一句话范围>

## 一、已确认的设计决策
## 二、实现记录(完成后补)
## 三、待讨论事项(讨论中保留)

9.4 索引表(index.md)

docs/迭代记录/index.md 维护迭代索引表,每迭代一行:

迭代 日期 状态 关联文档
迭代名 起止日期 已完成 / 讨论中 文档链接