QMT HTTP API
将迅投 QMT(MiniQMT / 投研版)策略进程内的 ContextInfo、行情、财务与交易函数,封装为 JSON HTTP 服务,供外部程序远程调用。
完整接口清单、请求/响应字段与 curl 示例见 api.md。
1. 它是什么
QMT_API.py 不是可独立 python QMT_API.py 启动的普通脚本。它是一份 QMT Python 策略:
- QMT 加载策略后调用
init(ContextInfo)。 init绑定资金账号、加载股票池、创建 TornadoApplication,并在当前进程里listen+IOLoop.start()。- 此后外部 HTTP 客户端通过
X-Token鉴权,调用本机(或同网段)上的 REST 接口。 - 接口内部再转调 QMT 内置对象:
ContextInfo.*、passorder、get_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_data、get_universe等)- 交易:
passorder、algo_passorder、smart_algo_passorder、order_*、buy_open/sell_open等 - 查询:
get_trade_detail_data、get_value_by_order_id、can_cancel_order、cancel等 - 其它:
get_open_date、timetag_to_datetime、ext_data、get_etf_info等
3. 配置
源码顶部与 init() 使用的配置如下。
| 名称 | 来源 | 默认值 | 说明 |
|---|---|---|---|
QMT_ACCOUNT_ID |
环境变量 | '' |
资金账号,写入 ContextInfo.accountID 并 set_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. 接入步骤
- 在 QMT 中配置 Python 策略,入口文件指向
api/QMT_API.py。 - 准备数据目录,放入
pass_codes.json,例如:
["000001.SZ", "600000.SH"]
- 设置环境变量
QMT_ACCOUNT_ID(以及你实际使用的数据目录变量,见已知问题)。 - 启动策略。日志出现类似:
QMT HTTP Server 启动于 http://0.0.0.0:10086 (全部API已加载)
- 用任意 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)。
- POST:Body 必须是 合法 JSON 对象。多数 POST 一上来就
json.loads(self.request.body),空 Body 会直接异常。 - 多标的字段(如
stock_code、stocks、stock_list、fieldList)一般为 逗号分隔字符串,服务端再split(',')+strip()。
5.3 响应
- 默认
Content-Type: application/json; charset=utf-8。 - 成功:各接口自定义 JSON(见 api.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/buy→passorder(23, 1101, ...)(买入)POST /api/order/sell→passorder(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_DIR,init / 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 策略官方函数手册(
passorder的opType/prType等枚举以官方文档为准;本仓库只记录本封装实际传入的值)