# QMT HTTP API 将迅投 QMT(MiniQMT / 投研版)策略进程内的 `ContextInfo`、行情、财务与交易函数,封装为 JSON HTTP 服务,供外部程序远程调用。 源码:[`api/QMT_API.py`](../api/QMT_API.py) 完整接口清单、请求/响应字段与 curl 示例见 [api.md](./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.*`、`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. 接入步骤 1. 在 QMT 中配置 Python 策略,入口文件指向 `api/QMT_API.py`。 2. 准备数据目录,放入 `pass_codes.json`,例如: ```json ["000001.SZ", "600000.SH"] ``` 3. 设置环境变量 `QMT_ACCOUNT_ID`(以及你实际使用的数据目录变量,见已知问题)。 4. 启动策略。日志出现类似: ``` QMT HTTP Server 启动于 http://0.0.0.0:10086 (全部API已加载) ``` 5. 用任意 HTTP 客户端调用。所有业务接口默认需要鉴权: ```http X-Token: <与源码 TOKEN 一致> Content-Type: application/json ``` 快速探活(需 Token): ```bash curl -s -H "X-Token: QMTbyYanweidong" http://127.0.0.1:10086/api/context/period ``` 关闭服务: ```bash 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](./api.md))。 - 失败:`write_error` 统一为: ```json {"error": "", "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.md#1-兼容层--v2) | | `/api/holding` `/api/money/*` `/api/order/*` | 旧版买卖、资金、撤单、成交 | 同上 | | `/api/context/*` | 策略上下文属性 | [§2](./api.md#2-策略上下文-apicontext) | | `/api/data/*` | 行情、财务、期权、订阅 | [§3](./api.md#3-数据查询-apidata) | | `/api/check/*` | 停牌、板块、K 线判定 | [§4](./api.md#4-判定-apicheck) | | `/api/trade/*` | 股票/算法/期货下单、任务、账户查询 | [§5](./api.md#5-交易-apitrade) | | `/api/ext/*` | 扩展数据与因子引用 | [§6](./api.md#6-扩展引用-apiext) | | `/api/sys/*` | Python 版本、关停服务 | [§7](./api.md#7-系统-apisys) | 兼容层买卖是对 `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 数据目录变量不一致 ```python 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 参考](./api.md) - 迅投 QMT Python 策略官方函数手册(`passorder` 的 `opType` / `prType` 等枚举以官方文档为准;本仓库只记录本封装实际传入的值)