258 lines
9.5 KiB
Markdown
258 lines
9.5 KiB
Markdown
# 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": "<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.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` 等枚举以官方文档为准;本仓库只记录本封装实际传入的值)
|