# 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://: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_dBalance,round(..., 2)` | | `available` | number | `m_dAvailable,round(..., 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`。最终结果应再查询委托确认。缺失或空字符串订单号返回 400,QMT 普通调用异常返回 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":"", "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"]}' ```