Files
big-qmt/docs/api.md
2026-09-07 21:01:36 +08:00

374 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QMT REST API 参考
根据 [`api/qmt_rest_new.py`](../api/qmt_rest_new.py) 的路由、Handler 和格式化函数重新生成。更新日期2026-09-07。启动说明见 [README.md](README.md)。
## 1. 公共约定
- Base URL`http://<host>:10086`,监听 `0.0.0.0`
- 所有已注册接口均要求 `X-Token: <服务端 TOKEN>`,没有免鉴权接口。
- POST 请求体为 JSON 对象,使用 `Content-Type: application/json`
- 账户取自服务端 `QMT_ACCOUNT_ID`,账户类型固定为 `stock`,请求不能切换账户。
- 成功响应没有统一包装,具体形状见各接口。
- 资产、持仓、委托、成交字段为小写下划线;下单请求仍使用驼峰字段;行情及原始查询保留 QMT 字段。
- 字段类型按 QMT 属性约定列出;除资产金额四舍五入外,格式化函数不强制类型转换、不填默认值、不转换枚举和日期。
- 枚举含义、原始日期格式、行情和其他透传结果由 QMT 决定,本文件不扩展源码未定义的契约。
## 2. 接口清单
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/api/context/info` | 策略上下文 |
| GET | `/api/get/{handler_type}` | 单证券查询 |
| GET | `/api/portfolio` | 聚合资产、持仓、委托 |
| GET | `/api/portfolio/assets` | 资产 |
| GET | `/api/portfolio/positions` | 持仓 |
| GET | `/api/portfolio/order` | 委托数组 |
| GET | `/api/portfolio/deal` | 成交数组 |
| GET | `/api/portfolio/org/{handler_type}` | 原始记录 |
| POST | `/api/data/full_tick` | 实时行情 |
| POST | `/api/trade/passorder` | 下单 |
| POST | `/api/trade/cancel_by_id` | 撤单 |
| POST | `/api/trade/ipo_data` | 发行数据 |
| GET | `/api/sys/python_version` | Python 版本 |
## 3. 策略上下文
`GET /api/context/info`,无参数,返回以下对象字段:
| 字段 | 来源 |
| --- | --- |
| `period` | `ContextInfo.period` |
| `barpos` | `ContextInfo.barpos` |
| `time_tick_size` | `ContextInfo.time_tick_size` |
| `stockcode` | `ContextInfo.stockcode` |
| `dividend_type` | `ContextInfo.dividend_type` |
| `market` | `ContextInfo.market` |
| `do_back_test` | `ContextInfo.do_back_test` |
| `benchmark` | `ContextInfo.benchmark` |
| `capital` | `ContextInfo.capital` |
| `timetag` | `ContextInfo.timetag` |
| `universe` | `ContextInfo.get_universe()` |
属性按原值输出;`stockcode` 是此接口的实际字段名。
## 4. 单证券查询
`GET /api/get/{handler_type}?stock_code=600000.SH`
`stock_code` 为必填 Query 字符串,去除首尾空格后不能为空,整体传入底层函数,不拆分逗号。
| handler_type | 调用 |
| --- | --- |
| `stock_name` | `ContextInfo.get_stock_name(stock_code)` |
| `open_date` | `ContextInfo.get_open_date(stock_code)` |
| `last_volume` | `ContextInfo.get_last_volume(stock_code)` |
| `total_share` | `ContextInfo.get_total_share(stock_code)` |
| `svol` | `ContextInfo.get_svol(stock_code)` |
| `bvol` | `ContextInfo.get_bvol(stock_code)` |
| `divid_factors` | `ContextInfo.get_divid_factors(stock_code)` |
| `etf_info` | 全局 `get_etf_info(stock_code)` |
| `etf_iopv` | 全局 `get_etf_iopv(stock_code)` |
| `instrumentdetail` | `ContextInfo.get_instrumentdetail(stock_code)` |
| `his_st_data` | `ContextInfo.get_his_st_data(stock_code)` |
响应示例:
```json
{"stock_code":"600000.SH","ref":"示例证券名称"}
```
`ref` 透传底层结果,可为字符串、数值、对象、数组或 null无法直接序列化的值通过 `str()` 转换。缺失或空代码返回 HTTP 500不支持的路径类型通常因路由不匹配返回 404。
## 5. 账户查询
以下接口均无请求参数。
### 5.1 聚合快照
`GET /api/portfolio`
依次调用 `get_trade_detail_data(account_id, 'stock', 'account' / 'position' / 'order')`,分别格式化为以下三个字段:
```json
{
"assets":{"total":100000.0,"available":30000.0},
"positions":{},
"orders":[]
}
```
不包含成交。三次底层查询顺序执行,不保证同一时刻的原子快照;资产为空时整个请求失败。
### 5.2 资产
`GET /api/portfolio/assets`,查询 `account`,仅使用第一条记录。
| 字段 | JSON 类型 | QMT 来源 / 转换 |
| --- | --- | --- |
| `total` | number | `m_dBalanceround(..., 2)` |
| `available` | number | `m_dAvailableround(..., 2)` |
```json
{"total":100000.0,"available":30000.0}
```
`total` 来源为账户余额字段,客户端作为总资产使用;`available` 为可用资金。无账户数据返回 HTTP 500。四舍五入不保证 JSON 文本固定显示两位小数。
### 5.3 持仓
`GET /api/portfolio/positions`,查询 `position`
返回以证券代码为 key 的对象,每个 value 包含下表全部字段。空结果为 `{}`;同一代码多条记录以后面的覆盖前面的,不合并数量。
| 字段 | JSON 类型 | QMT 来源 / 转换 |
| --- | --- | --- |
| `stock_code` | string | `m_strInstrumentID + "." + m_strExchangeID` |
| `stock_name` | string | `m_strInstrumentName` |
| `direction` | integer | `m_nDirection` |
| `volume` | integer | `m_nVolume` |
| `open_price` | number | `m_dOpenPrice` |
| `open_cost` | number | `m_dOpenCost` |
| `float_profit` | number | `m_dFloatProfit` |
| `market_value` | number | `m_dMarketValue` |
| `stock_holder` | string | `m_strStockHolder` |
| `frozen_volume` | integer | `m_nFrozenVolume` |
| `can_use_volume` | integer | `m_nCanUseVolume` |
| `on_road_volume` | integer | `m_nOnRoadVolume` |
| `yesterday_volume` | integer | `m_nYesterdayVolume` |
| `last_price` | number | `m_dLastPrice` |
| `profit_rate` | number | `m_dProfitRate` |
| `future_trade_type` | integer | `m_eFutureTradeType` |
| `expire_date` | string | `m_strExpireDate` |
`volume` 为持仓数量,`can_use_volume` 为可用数量,`frozen_volume` 为冻结数量,`on_road_volume` 为在途数量,`yesterday_volume` 为昨日数量。`open_price``open_cost``float_profit``market_value` 分别保留开仓价、开仓成本、浮动盈亏、市值原值。`profit_rate` 不乘以 100也不换算单位。
外层示意:`{"600000.SH": {上述持仓字段}}`
### 5.4 委托
`GET /api/portfolio/order`,查询 `order`,返回对象数组,空结果为 `[]`
| 字段 | JSON 类型 | QMT 来源 / 转换 |
| --- | --- | --- |
| `stock_code` | string | `m_strInstrumentID + "." + m_strExchangeID` |
| `order_sys_id` | string | `m_strOrderSysID` |
| `ref` | integer | `m_nRef` |
| `order_ref` | string | `m_strOrderRef` |
| `direction` | integer | `m_nDirection` |
| `offset_flag` | integer | `m_nOffsetFlag` |
| `limit_price` | number | `m_dLimitPrice` |
| `volume_total_original` | integer | `m_nVolumeTotalOriginal` |
| `volume_traded` | integer | `m_nVolumeTraded` |
| `volume_total` | integer | `m_nVolumeTotal` |
| `traded_price` | number | `m_dTradedPrice` |
| `trade_amount` | number | `m_dTradeAmount` |
| `insert_date` | string | `m_strInsertDate` |
| `insert_time` | string | `m_strInsertTime` |
| `remark` | string | `m_strRemark` |
| `order_status` | integer | `m_nOrderStatus` |
`order_sys_id` 为柜台系统订单号,撤单时使用。`volume_total_original` 为原始委托数量,`volume_traded` 为已成交数量,`volume_total` 为剩余数量。日期、时间、备注及状态枚举保持原值。
服务端不按状态过滤,不排序或分页,不从备注中派生 `local_order_id``side``created_at` 等字段。
### 5.5 成交
`GET /api/portfolio/deal`,查询 `deal`,返回对象数组,空结果为 `[]`
| 字段 | JSON 类型 | QMT 来源 / 转换 |
| --- | --- | --- |
| `stock_code` | string | `m_strInstrumentID + "." + m_strExchangeID` |
| `order_sys_id` | string | `m_strOrderSysID` |
| `ref` | integer | `m_nRef` |
| `order_ref` | string | `m_strOrderRef` |
| `direction` | integer | `m_nDirection` |
| `offset_flag` | integer | `m_nOffsetFlag` |
| `price` | number | `m_dPrice` |
| `volume` | integer | `m_nVolume` |
| `trade_amount` | number | `m_dTradeAmount` |
| `trade_date` | string | `m_strTradeDate` |
| `trade_time` | string | `m_strTradeTime` |
| `remark` | string | `m_strRemark` |
| `close_profit` | number | `m_dCloseProfit` |
`price``volume``trade_amount` 分别为成交价格、数量、金额;`close_profit` 为平仓盈亏。记录逐条输出,不按订单合并、不去重、不分页,也不接受日期过滤。`order_sys_id` 是订单号,不应假定其唯一标识一条成交记录。
### 5.6 原始记录
`GET /api/portfolio/org/{handler_type}`,支持 `account``order``deal``position`
调用 `get_trade_detail_data(account_id, 'stock', handler_type)`,遍历每条记录的公开、非 callable 属性,响应为 `{"data":[记录对象]}`,空结果为:
```json
{"data":[]}
```
保留 `m_strInstrumentID` 等原始属性名,不使用前述格式化函数,不保证固定字段集合,也没有 `default=str` 序列化兜底。
## 6. 实时行情
`POST /api/data/full_tick`
| 字段 | 类型 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `stocks` | array of string | 否 | `[]` | 原样传给 `ContextInfo.get_full_tick(stocks)` |
```json
{"stocks":["600000.SH","000001.SZ"]}
```
响应直接使用 QMT 行情结果,通常为证券代码到行情对象的映射;内部字段不改名、不裁剪。无法直接序列化的值转为字符串。允许空数组,但具体查询范围由 QMT 决定。底层返回空值时返回 HTTP 500。
## 7. 提交委托
`POST /api/trade/passorder`
| 字段 | 类型 | 必填 | 默认 | 服务端处理 |
| --- | --- | --- | --- | --- |
| `opType` | integer | 是 | — | `int()` |
| `orderType` | integer | 否 | `1101` | `int()` |
| `stockCode` | string | 是 | — | 直接传入 |
| `prType` | integer | 否 | `11` | `int()` |
| `price` | number | 是 | — | `float()` |
| `volume` | integer | 是 | — | `int()` |
| `quickTrade` | integer | 否 | `2` | `int()` |
| `strategyName` | string | 否 | `""` | `str(...).strip()` |
| `orderId` | string | 否 | `""` | `str(...).strip()`,本地订单标识 |
底层参数顺序:
```python
passorder(opType, orderType, account_id, stockCode, prType, price,
volume, strategyName, quickTrade, orderId, ContextInfo)
```
请求结构示例(实际提交会调用交易函数):
```json
{
"opType":23,
"orderType":1101,
"stockCode":"600000.SH",
"prType":11,
"price":10.0,
"volume":100,
"quickTrade":2,
"strategyName":"trend",
"orderId":"trend-BUY-example"
}
```
响应示例:
```json
{
"status":"success",
"opType":23,
"stockCode":"600000.SH",
"strategy_name":"trend",
"local_order_id":"trend-BUY-example",
"order_ref":"None"
}
```
`order_ref``str(passorder返回值)`;示例对应底层返回 `None`,不保证提供柜台系统订单号。它与查询接口中来源于 `m_strOrderRef` 的同名字段来源不同。
`success` 仅表示底层调用未抛异常,不代表已经成交或最终报单成功。服务端未按 `orderId` 做幂等去重,请求超时不能证明未提交。
缺字段、JSON 解析失败及捕获到的类型 / 数值转换错误返回 HTTP 400底层普通异常返回 502底层 `HTTPError` 原样传播。封装未检查价格、数量正负、代码非空或枚举有效性。表中为 REST 服务默认值SDK 显式传参可能不同。
## 8. 撤单
`POST /api/trade/cancel_by_id`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `order_id` | string | 是 | 柜台系统订单号,即查询的 `order_sys_id`,不是本地 `orderId` |
```json
{"order_id":"示例柜台订单号"}
```
先调用 `can_cancel_order(order_id, account_id, 'stock')`,可撤时调用 `cancel(order_id, account_id, 'stock', ContextInfo)`
成功响应:
```json
{"status":"success","order_id":"示例柜台订单号"}
```
不可撤时响应仍为 HTTP 200
```json
{
"status":"failed",
"order_id":"示例柜台订单号",
"message":"Order does not exist or cannot currently be canceled"
}
```
`cancel()` 返回值严格为 `False`,返回 `{"status":"failed","order_id":"示例柜台订单号"}`,无 `message`,仍为 HTTP 200其他返回值均标记 `success`。最终结果应再查询委托确认。缺失或空字符串订单号返回 400QMT 普通调用异常返回 500。
## 9. 发行数据
`POST /api/trade/ipo_data`
| 字段 | 类型 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `type` | string | 否 | `"STOCK"` | 原样传给全局 `get_ipo_data(type)`,不转换大小写或校验枚举 |
```json
{"type":"STOCK"}
```
响应直接序列化底层结果,不包装或重命名字段,具体结构由 QMT 提供。使用默认参数也需发送 `{}`
## 10. Python 版本
`GET /api/sys/python_version`,无参数。
```json
{
"python_version":"<sys.version>",
"python_version_info":{
"major":3,
"minor":11,
"micro":0,
"releaselevel":"final",
"serial":0
}
}
```
值仅为示例,实际取自服务进程的 `sys.version``sys.version_info`
## 11. 错误响应
匹配到 `BaseHandler` 的接口默认声明 `Content-Type: application/json; charset=utf-8`,但 `write_error()` 实际输出 `self._reason` 原始文本,**不是 JSON 错误对象**。客户端应先检查 HTTP 状态,错误正文按文本处理。
| 场景 | HTTP 状态 | 行为 |
| --- | --- | --- |
| Token 缺失或不匹配 | 500 | 未显式设置 reason正文通常为 `Internal Server Error` |
| 单证券查询缺代码 / 无资产 / 无行情 | 500 | 通常为默认错误原因文本 |
| `safe_call()` 中普通异常 | 500 | `QMT: <函数名> call failed.` |
| 下单参数解析 / 转换异常 | 400 | `Invalid order parameters: ...` |
| 下单底层普通异常 | 502 | `QMT order submission failed` |
| 撤单订单号缺失或为空 | 400 | 通常为 `Bad Request` |
| 不可撤或底层明确返回 False | 200 | JSON 中 `status=failed` |
| 路由不存在 | 404 | Tornado 默认错误处理,不保证上述格式 |
| 方法不支持 | 405 | 已匹配 Handler 的错误处理 |
除下单外POST 未统一捕获 JSON 解析错误,非法 JSON 可能返回 500非对象 JSON 也未统一校验。`safe_call()` 遇到底层 `HTTPError` 原样传播。
## 12. 只读调用示例
PowerShell 示例,不提交交易:
```powershell
$apiBase = 'http://127.0.0.1:10086'
$apiHeaders = @{ 'X-Token' = '<服务端 TOKEN>' }
Invoke-RestMethod -Uri "$apiBase/api/portfolio" -Headers $apiHeaders
Invoke-RestMethod -Uri "$apiBase/api/get/stock_name?stock_code=600000.SH" -Headers $apiHeaders
Invoke-RestMethod -Method Post -Uri "$apiBase/api/data/full_tick" -Headers $apiHeaders -ContentType 'application/json' -Body '{"stocks":["600000.SH"]}'
```