Files
big-qmt/docs/README.md
2026-08-25 16:40:18 +08:00

9.5 KiB
Raw Blame History

QMT HTTP API

将迅投 QMTMiniQMT / 投研版)策略进程内的 ContextInfo、行情、财务与交易函数,封装为 JSON HTTP 服务,供外部程序远程调用。

源码:api/QMT_API.py

完整接口清单、请求/响应字段与 curl 示例见 api.md


1. 它是什么

QMT_API.py 不是可独立 python QMT_API.py 启动的普通脚本。它是一份 QMT Python 策略:

  • QMT 加载策略后调用 init(ContextInfo)
  • init 绑定资金账号、加载股票池、创建 Tornado Application,并在当前进程里 listen + IOLoop.start()
  • 此后外部 HTTP 客户端通过 X-Token 鉴权,调用本机(或同网段)上的 REST 接口。
  • 接口内部再转调 QMT 内置对象:ContextInfo.*passorderget_trade_detail_data 等。

因此:服务生命周期 = 策略生命周期。策略停止HTTP 一并停止。

外部程序  --HTTP JSON-->  Tornado (0.0.0.0:10086)
                                |
                                v
                         QMT 策略进程
                         ContextInfo / 交易账户

2. 运行环境

要求
宿主 迅投 QMT需启用 Python 策略)
解释器 QMT 自带的 Python源码文件编码为 GBK
第三方库 tornado(需在 QMT Python 环境中可用)
标准库 json / os / datetime / pathlib / logging / locale
操作系统 源码调用 locale.setlocale(locale.LC_CTYPE, 'chinese'),面向 Windows 中文环境

QMT 内置符号(由策略宿主注入,源码中未 import

  • ContextInfo 及其方法(get_market_dataget_universe 等)
  • 交易:passorderalgo_passordersmart_algo_passorderorder_*buy_open / sell_open
  • 查询:get_trade_detail_dataget_value_by_order_idcan_cancel_ordercancel
  • 其它:get_open_datetimetag_to_datetimeext_dataget_etf_info

3. 配置

源码顶部与 init() 使用的配置如下。

名称 来源 默认值 说明
QMT_ACCOUNT_ID 环境变量 '' 资金账号,写入 ContextInfo.accountIDset_account
QMT_DATA_DIR 环境变量 D:\qmt_strategy_data 意图上的数据目录;见下方「已知问题」
TOKEN 源码硬编码 QMTbyYanweidong HTTP 鉴权口令,请求头 X-Token 必须与之相等
PORT 源码硬编码 10086 监听端口;绑定地址为 0.0.0.0

启动时还会读取:

{数据目录}/pass_codes.json

内容须为 JSON 数组(股票代码列表),用于 ContextInfo.set_universe(...)。该文件缺失或无法解析会导致 init 失败HTTP 服务起不来。


4. 接入步骤

  1. 在 QMT 中配置 Python 策略,入口文件指向 api/QMT_API.py
  2. 准备数据目录,放入 pass_codes.json,例如:
["000001.SZ", "600000.SH"]
  1. 设置环境变量 QMT_ACCOUNT_ID(以及你实际使用的数据目录变量,见已知问题)。
  2. 启动策略。日志出现类似:
QMT HTTP Server 启动于 http://0.0.0.0:10086 (全部API已加载)
  1. 用任意 HTTP 客户端调用。所有业务接口默认需要鉴权:
X-Token: <与源码 TOKEN 一致>
Content-Type: application/json

快速探活(需 Token

curl -s -H "X-Token: QMTbyYanweidong" http://127.0.0.1:10086/api/context/period

关闭服务:

curl -s -X POST -H "X-Token: QMTbyYanweidong" http://127.0.0.1:10086/api/sys/shutdown

ShutdownHandler 会在响应后再 IOLoop.stop()Tornado 事件循环退出。


5. 鉴权与协议约定

5.1 鉴权

BaseHandler.prepare()

  • 请求头 X-Token 必须等于源码中的 TOKEN
  • 否则抛出 HTTPError(401, "认证失败token 无效或缺失")
  • 源码定义了 @no_auth 装饰器,但 没有任何 Handler 使用它,包括 /api/sys/python_version/api/sys/shutdown

5.2 请求

  • GET无 Body参数都在路径中本服务 GET 接口目前均无 Query
  • POSTBody 必须是 合法 JSON 对象。多数 POST 一上来就 json.loads(self.request.body),空 Body 会直接异常。
  • 多标的字段(如 stock_codestocksstock_listfieldList)一般为 逗号分隔字符串,服务端再 split(',') + strip()

5.3 响应

  • 默认 Content-Type: application/json; charset=utf-8
  • 成功:各接口自定义 JSONapi.md)。
  • 失败:write_error 统一为:
{"error": "<reason>", "status_code": 401}

常见状态码:

场景
400 缺参、下单参数不合法
401 Token 缺失或错误
500 QMT 调用失败(部分接口在 safe_call 返回 None 后主动抛出)

safe_call 会吞掉底层异常并打日志,返回 None。调用方看到的可能是 null 字段,也可能是 500取决于该 Handler 有没有对 None 再处理。

5.4 HTTP 方法习惯

  • 只读、无参的 Context / 判定 / 系统信息:多数为 GET
  • 带 JSON Body 的查询与全部交易: POST
  • 同一资源没有 REST 语义上的 PUT/PATCH/DELETE。

6. 接口分组

路由在 make_app() 中注册,当前约 100+ 条。按前缀划分:

前缀 用途 文档
/api/v2/* 持仓 / 资产(与旧接口共用 Handler api.md §1
/api/holding /api/money/* /api/order/* 旧版买卖、资金、撤单、成交 同上
/api/context/* 策略上下文属性 §2
/api/data/* 行情、财务、期权、订阅 §3
/api/check/* 停牌、板块、K 线判定 §4
/api/trade/* 股票/算法/期货下单、任务、账户查询 §5
/api/ext/* 扩展数据与因子引用 §6
/api/sys/* Python 版本、关停服务 §7

兼容层买卖是对 passorder 的薄封装:

  • POST /api/order/buypassorder(23, 1101, ...)(买入)
  • POST /api/order/sellpassorder(24, 1101, ...)(卖出)
  • 完整下单请用 POST /api/trade/passorder(可自定义 opType / orderType / prType / quickTrade

账户查询里的 account 字段默认 "stock",也会传到 get_trade_detail_data 的账户类型参数。


7. 回调与落盘(当前未挂接)

源码后半定义了主推回调,用于把账户/委托/成交/持仓写成 JSON 文件:

函数 意图文件名
account_callback acount_%s.json(拼写为 acount
order_callback order_%s.json
deal_callback deal_%s.json
position_callback position_%s.json
orderError_callback print

init() 没有 调用 ContextInfo 的回调注册接口,因此这些函数默认不会执行。即便注册,write_json 本身也存在未定义变量问题(见下节),落盘路径目前不可靠。


8. 源码审视(使用前必读)

以下为对照 QMT_API.py 的事实,不是「建议优化清单」。接入前应按此理解行为边界。

8.1 数据目录变量不一致

DATA_DIR = os.environ.get('QMT_DATA_DIR', 'D:\\qmt_strategy_data')
# ...
Path(QMT_DATA_DIR) / "pass_codes.json"

环境变量读入的是 DATA_DIRinit / write_json 使用的是 从未赋值的 QMT_DATA_DIR。在普通 Python 里会 NameError。若你的 QMT 环境没有额外注入同名全局量,策略会在启动阶段失败。

8.2 write_json 不可用

  • 使用未定义的 current.strftime(应为 now)。
  • order_id 无默认值,但 account_callback / position_callback 只传了两个参数。
  • file_key 模板与实参个数不一定匹配。

8.3 Token 硬编码且监听全网卡

TOKEN 写死在源码里;listen(..., address='0.0.0.0') 对所有网卡开放。任何能打到 10086 且知道 Token 的客户端都可以下单、撤单、关停服务。不要把该端口暴露到公网。

8.4 错误被吞掉

safe_call 捕获全部异常后返回 None。部分查询接口仍会把 null 当成功响应返回调用方不易区分「没数据」和「QMT 抛错」。

8.5 编码

文件头 # -*- coding: gbk -*-。用 UTF-8 无 BOM 保存可能在 QMT 中出现中文注释/字符串解码问题。

8.6 规则撤单语义很窄

POST /api/order/cancel_order 不是「按委托号撤单」,而是:

  • 股票代码(代码.市场)完全匹配,且
  • m_nVolumeTotal + m_nVolumeTraded == volume,且
  • can_cancel_order 为真

才发出 cancel。按委托号查询/判断请用 /api/trade/value_by_order_id/api/trade/can_cancel_order。源码里没有单独的「按 orderId 撤单」HTTP 封装(全部撤单走 /api/order/cancel_all)。


9. 仓库结构

big-qmt/
├── api/
│   └── QMT_API.py      # QMT 策略 + HTTP 服务(唯一实现)
├── docs/
│   ├── README.md       # 本文件:架构、接入、约定、风险
│   └── api.md          # 全量 HTTP 接口说明
└── README.md           # 仓库占位

10. 相关文档

  • HTTP API 参考
  • 迅投 QMT Python 策略官方函数手册(passorderopType / prType 等枚举以官方文档为准;本仓库只记录本封装实际传入的值)