From 9778d54f3d1b4a59174b0b47b051dc5ab758efca Mon Sep 17 00:00:00 2001 From: yanweidong Date: Mon, 7 Sep 2026 21:01:36 +0800 Subject: [PATCH] fix bug --- api/qmt_rest_new.py | 2 +- docs/README.md | 266 +-------- docs/api.md | 1389 +++++++++---------------------------------- 3 files changed, 301 insertions(+), 1356 deletions(-) diff --git a/api/qmt_rest_new.py b/api/qmt_rest_new.py index cd2888d..3a3577a 100644 --- a/api/qmt_rest_new.py +++ b/api/qmt_rest_new.py @@ -255,7 +255,7 @@ class PassorderHandler(BaseHandler): raise except Exception as e: logger.exception("passorder failed") - raise HTTPError(502, reason="QMT order submission failed") from e + raise HTTPError(500, reason="QMT order submission failed") from e self.write_json({ "status": "success", diff --git a/docs/README.md b/docs/README.md index 4a46fe5..e6be05a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,257 +1,43 @@ -# QMT HTTP API +# QMT REST API 文档 -将迅投 QMT(MiniQMT / 投研版)策略进程内的 `ContextInfo`、行情、财务与交易函数,封装为 JSON HTTP 服务,供外部程序远程调用。 +当前实现:[`api/qmt_rest_new.py`](../api/qmt_rest_new.py)。完整接口、请求参数、返回字段及错误行为见 [API 参考](api.md)。更新日期:2026-09-07。 -源码:[`api/QMT_API.py`](../api/QMT_API.py) +## 运行方式 -完整接口清单、请求/响应字段与 curl 示例见 [api.md](./api.md)。 +文件运行在 QMT Python 策略宿主中,由宿主调用 `init(ContextInfo)` 并提供交易和查询内置函数,不能作为普通独立 HTTP 脚本启动。 ---- +启动流程: -## 1. 它是什么 +1. 检查账户和数据目录配置非空,调用 `ContextInfo.set_account()`。 +2. 从 `PASS_CODES_URL` 获取股票池,响应的 `data` 必须是数组。 +3. 合并远端股票代码与账户当前持仓,去重后设置 `ContextInfo.set_universe()`。 +4. 启动 Tornado,监听 `0.0.0.0:10086`。 -`QMT_API.py` **不是**可独立 `python QMT_API.py` 启动的普通脚本。它是一份 QMT Python 策略: +远端请求超时为 10 秒,股票池请求、解析或持仓查询失败会阻止服务启动。当前不读取本地 `pass_codes.json`。 -- 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) | +| `ACCOUNT_ID` | 环境变量 `QMT_ACCOUNT_ID` | 默认空,必须配置 | +| `DATA_DIR` | 环境变量 `QMT_DATA_DIR` | 默认 `D:\qmt_strategy_data`;当前只检查非空并记录日志,不读写目录 | +| `TOKEN` | 源码常量 | 请求头 `X-Token` 必须与其一致 | +| `PORT` | 源码常量 | `10086` | +| `PASS_CODES_URL` | 源码常量 | 远端股票池地址,见实现文件 | -兼容层买卖是对 `passorder` 的薄封装: +源码编码为 UTF-8,依赖 `tornado`。启动时设置 `locale.LC_CTYPE` 为 `chinese`,需要环境支持该 locale。 -- `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` 的账户类型参数。 +在 QMT 中配置账户环境变量并加载策略,启动后可用 PowerShell 查询: ---- - -## 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" +```powershell +$apiHeaders = @{ 'X-Token' = '<服务端 TOKEN>' } +Invoke-RestMethod -Uri 'http://127.0.0.1:10086/api/context/info' -Headers $apiHeaders ``` -环境变量读入的是 `DATA_DIR`,`init` / `write_json` 使用的是 **从未赋值的** `QMT_DATA_DIR`。在普通 Python 里会 `NameError`。若你的 QMT 环境没有额外注入同名全局量,策略会在启动阶段失败。 +当前没有 `/api/v2` 前缀,也没有 HTTP 关停路由。服务运行在策略进程中。 -### 8.2 `write_json` 不可用 +资产、持仓、委托、成交使用小写下划线字段;行情和原始查询保留 QMT 字段;下单请求仍用驼峰字段。 -- 使用未定义的 `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` 等枚举以官方文档为准;本仓库只记录本封装实际传入的值) +鉴权失败目前返回 HTTP 500,接口错误正文通常是文本,撤单失败可能返回 HTTP 200 并标记 `status=failed`。详见 [API 参考](api.md)。 diff --git a/docs/api.md b/docs/api.md index e8074b1..0110bae 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,1214 +1,373 @@ -# HTTP API 参考 +# QMT REST API 参考 -本文档根据 [`api/QMT_API.py`](../api/QMT_API.py) 的 Handler 与 `make_app()` 路由逐条整理。架构、鉴权、启动方式见 [README.md](./README.md)。 +根据 [`api/qmt_rest_new.py`](../api/qmt_rest_new.py) 的路由、Handler 和格式化函数重新生成。更新日期:2026-09-07。启动说明见 [README.md](README.md)。 -**公共约定** +## 1. 公共约定 -- Base URL:`http://:10086`(`PORT=10086`,绑定 `0.0.0.0`) -- 鉴权:所有已注册接口均需请求头 `X-Token: `(源码当前值为 `QMTbyYanweidong`) -- POST Body:JSON 对象。未特别说明的字段均可省略并走源码默认值 -- 多值字符串:逗号分隔,例如 `"000001.SZ,600000.SH"` -- 失败响应:`{"error": "", "status_code": }` -- 「对应 QMT」列标明封装的原函数或属性;账户类调用会自动填入 `self.acc()`(即 `init` 时的 `ACCOUNT_ID`) +- Base URL:`http://:10086`,监听 `0.0.0.0`。 +- 所有已注册接口均要求 `X-Token: <服务端 TOKEN>`,没有免鉴权接口。 +- POST 请求体为 JSON 对象,使用 `Content-Type: application/json`。 +- 账户取自服务端 `QMT_ACCOUNT_ID`,账户类型固定为 `stock`,请求不能切换账户。 +- 成功响应没有统一包装,具体形状见各接口。 +- 资产、持仓、委托、成交字段为小写下划线;下单请求仍使用驼峰字段;行情及原始查询保留 QMT 字段。 +- 字段类型按 QMT 属性约定列出;除资产金额四舍五入外,格式化函数不强制类型转换、不填默认值、不转换枚举和日期。 +- 枚举含义、原始日期格式、行情和其他透传结果由 QMT 决定,本文件不扩展源码未定义的契约。 -文中 curl 的 `$TOKEN` 请自行替换。 +## 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. 策略上下文 -1. [兼容层 / v2](#1-兼容层--v2) -2. [策略上下文 `/api/context`](#2-策略上下文-apicontext) -3. [数据查询 `/api/data`](#3-数据查询-apidata) -4. [判定 `/api/check`](#4-判定-apicheck) -5. [交易 `/api/trade`](#5-交易-apitrade) -6. [扩展引用 `/api/ext`](#6-扩展引用-apiext) -7. [系统 `/api/sys`](#7-系统-apisys) +`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()` | -## 1. 兼容层 / v2 +属性按原值输出;`stockcode` 是此接口的实际字段名。 -旧客户端路径与 `/api/v2/*` 共用同一 Handler。查询类 POST 即使只用默认值,也需要传 `{}`。 +## 4. 单证券查询 -### 1.1 持仓列表 +`GET /api/get/{handler_type}?stock_code=600000.SH` -- **POST** `/api/holding` -- **POST** `/api/v2/positions` -- 对应 QMT:`get_trade_detail_data(accountId, account, 'position')` +`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)` | -| 字段 | 类型 | 默认 | 说明 | -| --- | --- | --- | --- | -| `account` | string | `"stock"` | 账户类型,传入 QMT | +响应示例: -**响应**:对象,key 为 `InstrumentID.ExchangeID`。 +```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 { - "600000.SH": { - "StockCode": "600000.SH", - "StockName": "...", - "Direction": "...", - "Volume": 0, - "OpenPrice": 0, - "FloatProfit": 0, - "MarketValue": 0, - "StockHolder": "...", - "FrozenVolume": 0, - "CanUseVolume": 0, - "OnRoadVolume": 0, - "YesterdayVolume": 0, - "LastPrice": 0, - "ProfitRate": 0, - "FutureTradeType": "...", - "ExpireDate": "..." - } + "assets":{"total":100000.0,"available":30000.0}, + "positions":{}, + "orders":[] } ``` -无持仓时为 `{}`。QMT 调用失败时 `safe_call` 返回 `None`,按空列表处理。 +不包含成交。三次底层查询顺序执行,不保证同一时刻的原子快照;资产为空时整个请求失败。 -```bash -curl -s -X POST -H "X-Token: $TOKEN" -H "Content-Type: application/json" \ - -d "{\"account\":\"stock\"}" http://127.0.0.1:10086/api/v2/positions -``` +### 5.2 资产 -### 1.2 资产(总资产 + 可用) +`GET /api/portfolio/assets`,查询 `account`,仅使用第一条记录。 -- **POST** `/api/v2/assets` -- 对应 QMT:`get_trade_detail_data(..., 'account')`,取第一条 - -**请求**:`account` 默认 `"stock"`。 - -**响应** +| 字段 | JSON 类型 | QMT 来源 / 转换 | +| --- | --- | --- | +| `total` | number | `m_dBalance,round(..., 2)` | +| `available` | number | `m_dAvailable,round(..., 2)` | ```json -{"total": 0.0, "available": 0.0} +{"total":100000.0,"available":30000.0} ``` -金额来自 `m_dBalance` / `m_dAvailable`,四舍五入到 2 位。无数据时 **500** `资金数据获取失败`。 +`total` 来源为账户余额字段,客户端作为总资产使用;`available` 为可用资金。无账户数据返回 HTTP 500。四舍五入不保证 JSON 文本固定显示两位小数。 -### 1.3 总资产 +### 5.3 持仓 -- **POST** `/api/money/total` +`GET /api/portfolio/positions`,查询 `position`。 -**响应**:`{"total_money": 0.0}`。无数据 500。 +返回以证券代码为 key 的对象,每个 value 包含下表全部字段。空结果为 `{}`;同一代码多条记录以后面的覆盖前面的,不合并数量。 -### 1.4 可用资金 +| 字段 | 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` | -- **POST** `/api/money/available` +`volume` 为持仓数量,`can_use_volume` 为可用数量,`frozen_volume` 为冻结数量,`on_road_volume` 为在途数量,`yesterday_volume` 为昨日数量。`open_price`、`open_cost`、`float_profit`、`market_value` 分别保留开仓价、开仓成本、浮动盈亏、市值原值。`profit_rate` 不乘以 100,也不换算单位。 -**响应**:`{"available_money": 0.0}`。无数据 500。 +外层示意:`{"600000.SH": {上述持仓字段}}`。 -### 1.5 简化买入 +### 5.4 委托 -- **POST** `/api/order/buy` -- 对应 QMT:`passorder(23, 1101, acc, stock, prType, price, volume, 'qmt', 2, ctx)` +`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` | 字段 | 类型 | 必填 | 默认 | 说明 | | --- | --- | --- | --- | --- | -| `stock` | string | 是 | | 代码,如 `600000.SH` | -| `price` | number | 是 | | 价格 | -| `volume` | int | 是 | | 数量 | -| `prType` | int | 否 | `11` | 报价类型 | - -**响应** +| `stocks` | array of string | 否 | `[]` | 原样传给 `ContextInfo.get_full_tick(stocks)` | ```json -{"status": "success", "action": "buy", "stock": "600000.SH", "order_ref": "..."} +{"stocks":["600000.SH","000001.SZ"]} ``` -`order_ref` 在 QMT 返回空时为 `"unknown"`。异常 **400** `下单失败: ...`。 +响应直接使用 QMT 行情结果,通常为证券代码到行情对象的映射;内部字段不改名、不裁剪。无法直接序列化的值转为字符串。允许空数组,但具体查询范围由 QMT 决定。底层返回空值时返回 HTTP 500。 -### 1.6 简化卖出 +## 7. 提交委托 -- **POST** `/api/order/sell` -- 对应 QMT:`passorder(24, 1101, ...)`,其余同买入 +`POST /api/trade/passorder` -**响应** `action` 为 `"sell"`。 +| 字段 | 类型 | 必填 | 默认 | 服务端处理 | +| --- | --- | --- | --- | --- | +| `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()`,本地订单标识 | -### 1.7 委托状态列表 +底层参数顺序: -- **POST** `/api/order/status` -- 对应 QMT:`get_trade_detail_data(..., 'order', 'qmt')` +```python +passorder(opType, orderType, account_id, stockCode, prType, price, + volume, strategyName, quickTrade, orderId, ContextInfo) +``` -**请求**:`account` 默认 `"stock"`。 - -**响应** +请求结构示例(实际提交会调用交易函数): ```json { - "orders": [ - { - "order_sys_id": "...", - "status": 0, - "volume_left": 0, - "volume_traded": 0 - } - ] + "opType":23, + "orderType":1101, + "stockCode":"600000.SH", + "prType":11, + "price":10.0, + "volume":100, + "quickTrade":2, + "strategyName":"trend", + "orderId":"trend-BUY-example" } ``` -字段分别对应 `m_strOrderSysID`、`m_nOrderStatus`、`m_nVolumeTotal`、`m_nVolumeTraded`。 - -### 1.8 全部撤单 - -- **POST** `/api/order/cancel_all` -- 对应 QMT:遍历委托,`can_cancel_order` 为真则 `cancel` - -**请求**:`account` 默认 `"stock"`。 - -**响应** +响应示例: ```json { - "status": "success", - "message": "已发出 N 笔撤单请求", - "canceled_orders": [ - {"order_sys_id": "...", "stock": "...", "volume_left": 0} - ] + "status":"success", + "opType":23, + "stockCode":"600000.SH", + "strategy_name":"trend", + "local_order_id":"trend-BUY-example", + "order_ref":"None" } ``` -`stock` 仅 `m_strInstrumentID`(不含市场后缀)。异常 **500**。 +`order_ref` 为 `str(passorder返回值)`;示例对应底层返回 `None`,不保证提供柜台系统订单号。它与查询接口中来源于 `m_strOrderRef` 的同名字段来源不同。 -### 1.9 按股票 + 数量规则撤单 +`success` 仅表示底层调用未抛异常,不代表已经成交或最终报单成功。服务端未按 `orderId` 做幂等去重,请求超时不能证明未提交。 -- **POST** `/api/order/cancel_order` +缺字段、JSON 解析失败及捕获到的类型 / 数值转换错误返回 HTTP 400;底层普通异常返回 502,底层 `HTTPError` 原样传播。封装未检查价格、数量正负、代码非空或枚举有效性。表中为 REST 服务默认值,SDK 显式传参可能不同。 -**请求** +## 8. 撤单 + +`POST /api/trade/cancel_by_id` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | -| `stock` | string | 是 | 必须为 `代码.市场`,与持仓 key 相同 | -| `volume` | int | 是 | 必须 `> 0`;匹配条件为 `VolumeTotal + VolumeTraded == volume` | -| `account` | string | 否 | 默认 `"stock"` | - -匹配到 0 笔时仍 HTTP 200: +| `order_id` | string | 是 | 柜台系统订单号,即查询的 `order_sys_id`,不是本地 `orderId` | ```json -{"status": "failed", "message": "未找到符合条件的活跃订单"} +{"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": "success", - "message": "匹配到 N 笔订单并发出撤单请求", - "canceled_sys_ids": ["..."] + "status":"failed", + "order_id":"示例柜台订单号", + "message":"Order does not exist or cannot currently be canceled" } ``` -缺参 **400** `参数错误:必须提供 stock 且 volume > 0`。 +若 `cancel()` 返回值严格为 `False`,返回 `{"status":"failed","order_id":"示例柜台订单号"}`,无 `message`,仍为 HTTP 200;其他返回值均标记 `success`。最终结果应再查询委托确认。缺失或空字符串订单号返回 400,QMT 普通调用异常返回 500。 -### 1.10 成交明细 +## 9. 发行数据 -- **POST** `/api/order/deal` -- 对应 QMT:`get_trade_detail_data(..., 'deal', 'qmt')` +`POST /api/trade/ipo_data` -**响应**:`{"deals": [ {对象全部非下划线、非可调用属性,值一律 str()} ] }`。 - ---- - -## 2. 策略上下文 `/api/context` - -全部 **GET**,无请求体。数据来自 `self.application.ContextInfo`。 - -| 方法 | 路径 | 对应 QMT | 响应 | -| --- | --- | --- | --- | -| GET | `/api/context/period` | `.period` | `{"period": ...}` | -| GET | `/api/context/barpos` | `.barpos` | `{"barpos": ...}` | -| GET | `/api/context/time_tick_size` | `.time_tick_size` | `{"time_tick_size": ...}` | -| GET | `/api/context/stockcode` | `.stockcode` | `{"stockcode": ...}` | -| GET | `/api/context/dividend_type` | `.dividend_type` | `{"dividend_type": ...}` | -| GET | `/api/context/market` | `.market` | `{"market": ...}` | -| GET | `/api/context/do_back_test` | `.do_back_test` | `{"do_back_test": ...}` | -| GET | `/api/context/benchmark` | `.benchmark` | `{"benchmark": ...}` | -| GET | `/api/context/capital` | `.capital` | `{"capital": ...}` | -| GET | `/api/context/universe` | `.get_universe()` | `{"universe": [...]}` | - -```bash -curl -s -H "X-Token: $TOKEN" http://127.0.0.1:10086/api/context/universe -``` - ---- - -## 3. 数据查询 `/api/data` - -除特别标明的 GET 外均为 POST。`safe_call` 失败时,部分接口返回字段为 `null`,部分返回 `{"error": "..."}` 或 500,以各条为准。 - -### 3.1 证券名称 - -- **POST** `/api/data/stock_name` → `ContextInfo.get_stock_name(stockcode)` - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `stockcode` | `""` | 证券代码 | +| 字段 | 类型 | 必填 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `type` | string | 否 | `"STOCK"` | 原样传给全局 `get_ipo_data(type)`,不转换大小写或校验枚举 | ```json -{"stockcode": "600000.SH", "name": "..."} +{"type":"STOCK"} ``` -### 3.2 上市日期 +响应直接序列化底层结果,不包装或重命名字段,具体结构由 QMT 提供。使用默认参数也需发送 `{}`。 -- **POST** `/api/data/open_date` → 全局 `get_open_date(stockcode)` +## 10. Python 版本 -```json -{"stockcode": "600000.SH", "open_date": "..."} -``` - -### 3.3 最新流通股本 - -- **POST** `/api/data/last_volume` → `get_last_volume` - -失败(`None`)时 **500** `获取流通股本失败`。 - -```json -{"stockcode": "600000.SH", "last_volume": ...} -``` - -### 3.4 K 线时间戳 - -- **POST** `/api/data/bar_timetag` → `get_bar_timetag(index)` - -| 字段 | 默认 | -| --- | --- | -| `index` | `-1` | - -```json -{"index": -1, "timetag": ...} -``` - -### 3.5 最新分笔时间戳 - -- **GET** `/api/data/tick_timetag` → `get_tick_timetag()` - -```json -{"timetag": ...} -``` - -### 3.6 指数成份股 - -- **POST** `/api/data/sector` → `get_sector(sector, realtime)` - -| 字段 | 必填 | 默认 | 说明 | -| --- | --- | --- | --- | -| `sector` | 是 | | 空则 400 `need args sector` | -| `realtime` | 否 | `"0"` | `"0"` 时第二参为 `0`,否则 `int(realtime)` | - -```json -{"sector": "000300.SH", "stocks": []} -``` - -`stocks` 在调用失败时为 `[]`。 - -### 3.7 行业成份股 - -- **POST** `/api/data/industry` → `get_industry(industry)` - -`industry` 为空则 400 `need args industry`。 - -```json -{"industry": "...", "stocks": []} -``` - -### 3.8 板块成份股 - -- **POST** `/api/data/stock_list_in_sector` → `get_stock_list_in_sector(sectorname)` - -`sectorname` 为空则 400 `need args sectorname`。 - -```json -{"sectorname": "沪深A股", "stocks": []} -``` - -### 3.9 指数权重 - -- **POST** `/api/data/weight_in_index` → `get_weight_in_index(indexcode, stockcode)` - -```json -{"indexcode": "000300.SH", "stockcode": "600000.SH", "weight": ...} -``` - -### 3.10 合约乘数 - -- **POST** `/api/data/contract_multiplier` → `get_contract_multiplier(contractcode)` - -```json -{"contractcode": "...", "multiplier": ...} -``` - -### 3.11 无风险利率 - -- **POST** `/api/data/risk_free_rate` → `get_risk_free_rate(index)` - -| 字段 | 默认 | -| --- | --- | -| `index` | `-1` | - -```json -{"index": -1, "risk_free_rate": ...} -``` - -### 3.12 日期对应 K 线索引 - -- **POST** `/api/data/date_location` → `get_date_location(strdate)` - -```json -{"strdate": "20240101", "location": ...} -``` - -### 3.13 历史行情(多品种字典) - -- **POST** `/api/data/history_data` → `get_history_data(len, period, field, dividend_type, skip_paused)` - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `len` | `10` | 根数 | -| `period` | `"1d"` | 周期 | -| `field` | `"close"` | 字段 | -| `dividend_type` | `0` | 整数 | -| `skip_paused` | `"true"` | 小写等于 `"true"` 则为 Python `True` | - -成功:`{"data": ...}`。`safe_call` 得到假值时:`{"error": "获取历史数据失败"}`(仍可能是 HTTP 200)。 - -### 3.14 行情 DataFrame - -- **POST** `/api/data/market_data` → `get_market_data(fields, stocks, start, end, True, period, dividend_type, count)` - -注意第五参在封装里 **写死为 `True`**(QMT 该位置一般为 `skip_paused` 一类开关,以官方签名为准)。 - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `fields` | `""` | 逗号分隔;空则 `[]` | -| `stock_code` | `""` | 逗号分隔代码 | -| `start_time` | `""` | | -| `end_time` | `""` | | -| `period` | `"1d"` | | -| `dividend_type` | `"none"` | | -| `count` | `-1` | | - -有 `to_dict` 则转 dict。失败 **500** `获取行情数据失败`。 - -```json -{"data": {}} -``` - -### 3.15 扩展行情(Level2) - -- **POST** `/api/data/market_data_ex` → `get_market_data_ex(...)` - -| 字段 | 默认 | -| --- | --- | -| `fields` | `""` | -| `stock_code` | `""` | -| `period` | `"follow"` | -| `start_time` | `""` | -| `end_time` | `""` | -| `count` | `-1` | -| `dividend_type` | `"follow"` | - -返回字典:每个 value 优先 `to_dict()`,否则 `str()`。失败 500 `获取扩展行情失败`。 - -### 3.16 分笔 / 全推行情 - -- **POST** `/api/data/full_tick` → `get_full_tick(code_list)` - -| 字段 | 必填 | -| --- | --- | -| `stocks` | 是,逗号分隔;空则 400 `need args stocks` | - -成功时响应体 **就是 QMT 返回对象本身**(不是 `{data: ...}` 包裹)。失败 500 `获取分笔行情失败`。 - -### 3.17 除权除息 / 复权因子 - -- **POST** `/api/data/divid_factors` - -```json -{"stockcode": "...", "factors": {}} -``` - -失败时 `factors` 为 `{}`。 - -### 3.18 期货主力合约 - -- **POST** `/api/data/main_contract` - -```json -{"codemarket": "...", "main_contract": ...} -``` - -### 3.19 毫秒时间戳转日期 - -- **POST** `/api/data/timetag_to_datetime` → 全局 `timetag_to_datetime(timetag, format)` - -| 字段 | 默认 | -| --- | --- | -| `timetag` | `0` | -| `format` | `"%Y-%m-%d %H:%M:%S"` | - -```json -{"timetag": 0, "datetime": "..."} -``` - -### 3.20 总股本 - -- **POST** `/api/data/total_share` - -```json -{"stockcode": "...", "total_share": ...} -``` - -### 3.21 交易日列表 - -- **POST** `/api/data/trading_dates` → `get_trading_dates(stockcode, start_date, end_date, count, period)` - -| 字段 | 默认 | -| --- | --- | -| `stockcode` | `""` | -| `start_date` | `""` | -| `end_date` | `""` | -| `count` | 空字符串 → 内部 `-1` | -| `period` | `"1d"` | - -```json -{"dates": []} -``` - -### 3.22 内盘 / 外盘成交量 - -- **POST** `/api/data/svol` → `get_svol` → `{"stockcode", "svol"}` -- **POST** `/api/data/bvol` → `get_bvol` → `{"stockcode", "bvol"}` - -### 3.23 龙虎榜 - -- **POST** `/api/data/longhubang` → `get_longhubang(stock_list, startTime, endTime)` - -| 字段 | 默认 | -| --- | --- | -| `stock_list` | `""` 逗号分隔 | -| `startTime` | `""` | -| `endTime` | `""` | - -成功 `{"data": ...}`(DataFrame 会 `to_dict`)。假值时 `{"error": "获取龙虎榜数据失败"}`。 - -### 3.24 十大股东 - -- **POST** `/api/data/top10_share_holder` → 全局 `get_top10_share_holder` - -| 字段 | 默认 | -| --- | --- | -| `stock_list` | `""` | -| `data_name` | `"holder"` | -| `start_time` | `""` | -| `end_time` | `""` | - -失败文案:`获取十大股东数据失败`。 - -### 3.25 期权详情 - -- **POST** `/api/data/option_detail` - -```json -{"optioncode": "...", "detail": {}} -``` - -### 3.26 换手率 - -- **POST** `/api/data/turnover_rate` → `get_turnover_rate(stock_list, startTime, endTime)` - -字段同龙虎榜风格(`stock_list` / `startTime` / `endTime`)。失败:`获取换手率失败`。 - -### 3.27 ETF 申赎清单 - -- **POST** `/api/data/etf_info` → `get_etf_info(stockcode)` - -```json -{"stockcode": "...", "info": {}} -``` - -### 3.28 ETF IOPV - -- **POST** `/api/data/etf_iopv` → `get_etf_iopv(stockcode)` - -```json -{"stockcode": "...", "iopv": ...} -``` - -### 3.29 合约详细信息 - -- **POST** `/api/data/instrumentdetail` → `get_instrumentdetail` - -```json -{"stockcode": "...", "detail": {}} -``` - -### 3.30 期货到期日 - -- **POST** `/api/data/contract_expire_date` - -```json -{"codemarket": "...", "expire_date": ...} -``` - -### 3.31 期权标的 → 期权列表 - -- **POST** `/api/data/option_undl_data` - -| 字段 | 说明 | -| --- | --- | -| `undl_code_ref` | 标的代码 | - -```json -{"data": []} -``` - -### 3.32 财务数据(两种调用约定) - -- **POST** `/api/data/financial_data` → `ContextInfo.get_financial_data` - -**约定 A**(单字段):`tabname`、`colname`、`market`、`code` **全部非空** 时调用: - -``` -get_financial_data(tabname, colname, market, code, report_type, barpos) -``` - -| 字段 | 默认 | -| --- | --- | -| `report_type` | `"report_time"` | -| `barpos` | `-1` | - -**约定 B**(否则走批量): - -``` -get_financial_data(fieldList, stockList, startDate, endDate, report_type) -``` - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `fieldList` | `""` | 逗号分隔 | -| `stockList` | `""` | 逗号分隔 | -| `startDate` | `""` | | -| `endDate` | `""` | | -| `report_type` | `"announce_time"` | 注意与约定 A 默认值不同 | - -`ret is None` 时:`{"error": "获取财务数据失败"}`;否则 `{"data": ...}`。 - -### 3.33 多因子数据 - -- **POST** `/api/data/factor_data` → `get_factor_data` - -| 字段 | 说明 | -| --- | --- | -| `fieldList` | 逗号分隔字段 | -| `stockCode` | 若非空:按 **单个代码** 调用 | -| `stockList` | 否则按代码列表调用 | -| `startDate` / `endDate` | 区间 | - -失败:`{"error": "获取因子数据失败"}`。 - -### 3.34 历史 ST - -- **POST** `/api/data/his_st_data` - -```json -{"stockCode": "...", "data": {}} -``` - -注意请求字段是 **`stockCode`**(驼峰),与多数接口的 `stockcode` 不同。 - -### 3.35 历史指数 - -- **POST** `/api/data/his_index_data` - -```json -{"index": "...", "data": {}} -``` - -### 3.36 当前全部行情订阅 - -- **GET** `/api/data/all_subscription` → `get_all_subscription()` - -```json -{"subscriptions": {}} -``` - -### 3.37 指定期权列表 - -- **POST** `/api/data/option_list` - -| 字段 | 默认 | -| --- | --- | -| `undl_code` | `""` | -| `dedate` | `""` | -| `opttype` | `""` | -| `isavailable` | `"true"`(小写 `"true"` 为 True) | - -```json -{"option_list": []} -``` - -### 3.38 过期合约列表 - -- **POST** `/api/data/his_contract_list` - -```json -{"market": "...", "contracts": []} -``` - -### 3.39 期权隐含波动率(实时) - -- **POST** `/api/data/option_iv` - -```json -{"optioncode": "...", "iv": ...} -``` - -### 3.40 BS 理论价格 - -- **POST** `/api/data/bsm_price` → `bsm_price(optionType, objectPrices, strikePrice, riskFree, sigma, days, dividend)` - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `optionType` | `"C"` | | -| `objectPrices` | `""` | 能 `float()` 则标量;否则按逗号拆成 float 列表 | -| `strikePrice` | `0` | | -| `riskFree` | `0` | | -| `sigma` | `0` | | -| `days` | `0` | | -| `dividend` | `0` | | - -```json -{"price": ...} -``` - -### 3.41 BS 隐含波动率 - -- **POST** `/api/data/bsm_iv` - -| 字段 | 默认 | -| --- | --- | -| `optionType` | `"C"` | -| `objectPrices` | `0`(float,与 bsm_price 不同) | -| `strikePrice` | `0` | -| `optionPrice` | `0` | -| `riskFree` | `0` | -| `days` | `0` | -| `dividend` | `0` | - -```json -{"iv": ...} -``` - -### 3.42 本地行情 - -- **POST** `/api/data/local_data` → `get_local_data(stock_code, start_time, end_time, period, divid_type, count)` - -| 字段 | 默认 | -| --- | --- | -| `stock_code` | `""` | -| `start_time` | `""` | -| `end_time` | `""` | -| `period` | `"1d"` | -| `divid_type` | `"none"` | -| `count` | `-1` | - -失败 500 `获取本地行情失败`。成功 `{"data": ...}`。 - -### 3.43 订阅行情 - -- **POST** `/api/data/subscribe_quote` → `subscribe_quote(stock_code, period, dividend_type)` - -| 字段 | 默认 | -| --- | --- | -| `stock_code` | `""` | -| `period` | `"follow"` | -| `dividend_type` | `"follow"` | - -```json -{"status": "success" | "failed", "sub_id": ...} -``` - -`status` 取决于返回值是否为 `None`。 - -### 3.44 反订阅 - -- **POST** `/api/data/unsubscribe_quote` → `unsubscribe_quote(sub_id)` - -| 字段 | 默认 | -| --- | --- | -| `sub_id` | `0` | - -无论底层是否成功,都返回: - -```json -{"status": "success", "sub_id": 0} -``` - ---- - -## 4. 判定 `/api/check` - -### 4.1 是否最后一根 K 线 - -- **GET** `/api/check/is_last_bar` → `is_last_bar()` - -```json -{"is_last_bar": ...} -``` - -### 4.2 是否新 K 线 - -- **GET** `/api/check/is_new_bar` - -```json -{"is_new_bar": ...} -``` - -### 4.3 是否停牌 - -- **POST** `/api/check/is_suspended_stock` - -```json -{"stockcode": "...", "is_suspended": ...} -``` - -### 4.4 是否在指定板块 - -- **POST** `/api/check/is_sector_stock` → 全局 `is_sector_stock(sectorname, market, stockcode)` - -```json -{"sectorname": "...", "stockcode": "...", "is_in_sector": ...} -``` - -响应未带回 `market`。 - -### 4.5 是否属于某类别 - -- **POST** `/api/check/is_typed_stock` → `is_typed_stock(stocktypenum, market, stockcode)` - -| 字段 | 默认 | -| --- | --- | -| `stocktypenum` | `0` | -| `market` | `""` | -| `stockcode` | `""` | - -```json -{"stocktypenum": 0, "stockcode": "...", "result": ...} -``` - -### 4.6 行业分类名称 - -- **POST** `/api/check/get_industry_name_of_stock` → `get_industry_name_of_stock(industryType, stockcode)` - -```json -{"industryType": "...", "stockcode": "...", "industry_name": ...} -``` - ---- - -## 5. 交易 `/api/trade` - -下单类在 `try/except` 中捕获异常后 **400**,并 `logger.exception`。 -`style` 类接口默认 `"LATEST"`,`accId` 默认当前 `ACCOUNT_ID`。 - -价格/数量等枚举含义以迅投 `passorder` 官方文档为准。本封装实际传入值如下。 - -### 5.1 综合下单 passorder - -- **POST** `/api/trade/passorder` - -调用: - -``` -passorder(opType, orderType, acc, stock, prType, price, volume, 'qmt', quickTrade, ctx) -``` - -策略名第三段写死为 `'qmt'`。 - -| 字段 | 必填 | 默认 | 说明 | -| --- | --- | --- | --- | -| `opType` | 是 | | 操作类型;兼容层买=23、卖=24 | -| `stock` | 是 | | | -| `price` | 是 | | | -| `volume` | 是 | | | -| `orderType` | 否 | `1101` | | -| `prType` | 否 | `11` | JSON 字段名为 `prType` | -| `quickTrade` | 否 | `2` | | - -**响应** - -```json -{"status": "success", "opType": 23, "stock": "600000.SH", "order_ref": "..."} -``` - -### 5.2 算法下单 - -- **POST** `/api/trade/algo_passorder` → `algo_passorder(...)` - -| 字段 | 必填 | 默认 | -| --- | --- | --- | -| `opType` | 是 | | -| `stock` | 是 | | -| `price` | 是 | | -| `volume` | 是 | | -| `orderType` | 否 | `1101` | -| `prType` | 否 | `-1`(与 passorder 默认 11 不同) | -| `strategyName` | 否 | `""` | -| `quickTrade` | 否 | `2` | -| `userOrderId` | 否 | `""` | -| `userOrderParam` | 否 | `{}` | - -```json -{"status": "success", "order_ref": "..."} -``` - -### 5.3 智能算法下单 - -- **POST** `/api/trade/smart_algo_passorder` - -| 字段 | 必填 | 默认 | -| --- | --- | --- | -| `opType` / `stock` / `price` / `volume` | 是 | | -| `smartAlgoType` | 是 | | -| `orderType` | 否 | `1101` | -| `prType` | 否 | `-1` | -| `limitOverRate` | 否 | `0` | -| `minAmountPerOrder` | 否 | `0` | -| `startTime` | 否 | `""` | -| `endTime` | 否 | `""` | - -### 5.4 指定手数 / 价值 / 比例 / 目标 / 股数 - -下列接口模式相同:成功返回 `{"status":"success","action":"<函数名>","stock":"..."}`。 - -| 路径 | QMT | 关键字段 | 其它默认 | -| --- | --- | --- | --- | -| POST `/api/trade/order_lots` | `order_lots` | `lots` int | `style=LATEST`, `price=0`, `accId` | -| POST `/api/trade/order_value` | `order_value` | `value` float | 同上 | -| POST `/api/trade/order_percent` | `order_percent` | `percent` float | 同上 | -| POST `/api/trade/order_target_value` | `order_target_value` | `tar_value` float | 同上 | -| POST `/api/trade/order_target_percent` | `order_target_percent` | `tar_percent` float | 同上 | -| POST `/api/trade/order_shares` | `order_shares` | `shares` int | 同上 | - -均需 `stock`。 - -```bash -curl -s -X POST -H "X-Token: $TOKEN" -H "Content-Type: application/json" \ - -d "{\"stock\":\"600000.SH\",\"shares\":100,\"style\":\"LATEST\",\"price\":0}" \ - http://127.0.0.1:10086/api/trade/order_shares -``` - -### 5.5 期货开平仓 - -均需 `stock`、`amount`(手数,int)。可选 `style`、`price`、`accId`。 - -| 路径 | QMT 函数 | `action` | -| --- | --- | --- | -| POST `/api/trade/futures/buy_open` | `buy_open` | `buy_open` | -| POST `/api/trade/futures/buy_close_tdayfirst` | `buy_close_tdayfirst` | `buy_close_tdayfirst` | -| POST `/api/trade/futures/buy_close_ydayfirst` | `buy_close_ydayfirst` | `buy_close_ydayfirst` | -| POST `/api/trade/futures/sell_open` | `sell_open` | `sell_open` | -| POST `/api/trade/futures/sell_close_tdayfirst` | `sell_close_tdayfirst` | `sell_close_tdayfirst` | -| POST `/api/trade/futures/sell_close_ydayfirst` | `sell_close_ydayfirst` | `sell_close_ydayfirst` | - -成功体例: - -```json -{"status": "success", "action": "buy_open", "stock": "IF2509.IF"} -``` - -### 5.6 任务:撤销 / 暂停 / 继续 - -| 路径 | QMT | -| --- | --- | -| POST `/api/trade/cancel_task` | `cancel_task(taskId, acc, accountType, ctx)` | -| POST `/api/trade/pause_task` | `pause_task` | -| POST `/api/trade/resume_task` | `resume_task` | - -| 字段 | 必填 | 默认 | -| --- | --- | --- | -| `taskId` | 是 | | -| `accountType` | 否 | `"stock"` | - -```json -{"status": "success" | "failed", "taskId": "..."} -``` - -`status` 由 QMT 返回值的真假决定。 - -### 5.7 触发前一根 bar 信号 - -- **POST** `/api/trade/do_order` → `do_order(ctx)` - -无需解析 Body(空 Body 也可)。 - -```json -{"status": "success", "message": "信号已触发"} -``` - -### 5.8 交易明细(原始对象属性) - -- **POST** `/api/trade/trade_detail_data` → `get_trade_detail_data(acc, account, datatype, 'qmt')` - -| 字段 | 默认 | 说明 | -| --- | --- | --- | -| `account` | `"stock"` | 账户类型 | -| `datatype` | `"position"` | 如 `position` / `order` / `deal` / `account`(以 QMT 为准) | - -每个元素展开为「非 `_` 开头且不可调用」的属性,值 `str()`。 - -```json -{"data": [{ "...": "..." }]} -``` - -调用失败时 `data` 为 `[]`。 - -### 5.9 按委托号取委托/成交 - -- **POST** `/api/trade/value_by_order_id` → `get_value_by_order_id(orderId, acc, accountType, datatype)` - -| 字段 | 默认 | -| --- | --- | -| `orderId` | `""` | -| `accountType` | `"stock"` | -| `datatype` | `"ORDER"` | - -```json -{"orderId": "...", "data": {}} -``` - -### 5.10 最新委托号 - -- **POST** `/api/trade/last_order_id` → `get_last_order_id(acc, account, datatype, 'qmt')` - -| 字段 | 默认 | -| --- | --- | -| `account` | `"stock"` | -| `datatype` | `"ORDER"` | - -```json -{"last_order_id": ...} -``` - -### 5.11 委托是否可撤 - -- **POST** `/api/trade/can_cancel_order` - -```json -{"orderId": "...", "can_cancel": ...} -``` - -`accountType` 默认 `"stock"`。 - -### 5.12 两融:负债 / 担保 / 可融券 - -均 POST,可选 `accId`(默认当前账号)。返回对象列表,属性展开方式同 5.8。 - -| 路径 | QMT | 响应 | -| --- | --- | --- | -| `/api/trade/debt_contract` | `get_debt_contract(accId)` | `{"data": [...]}` | -| `/api/trade/assure_contract` | `get_assure_contract` | 同上 | -| `/api/trade/enable_short_contract` | `get_enable_short_contract` | 同上 | - -### 5.13 当日新股新债 - -- **POST** `/api/trade/ipo_data` → `get_ipo_data(type)` - -| 字段 | 默认 | -| --- | --- | -| `type` | `""` | - -```json -{"data": {}} -``` - -### 5.14 新股申购额度 - -- **POST** `/api/trade/new_purchase_limit` → `get_new_purchase_limit(accid)` - -| 字段 | 默认 | -| --- | --- | -| `accid` | 当前账号 | - -```json -{"data": {}} -``` - ---- - -## 6. 扩展引用 `/api/ext` - -四个接口结构相同:`name` + `stockcode` + `deviation`(默认 0),并传入 `ContextInfo`。 - -| 路径 | QMT | 响应对 | -| --- | --- | --- | -| POST `/api/ext/ext_data` | `ext_data(extdataname, stockcode, deviation, ctx)` | `extdataname` + `value` | -| POST `/api/ext/ext_data_rank` | `ext_data_rank` | `rank` | -| POST `/api/ext/get_factor_value` | `get_factor_value(factorname, ...)` | `factorname` + `value` | -| POST `/api/ext/get_factor_rank` | `get_factor_rank` | `rank` | - -示例: - -```json -{"factorname": "...", "stockcode": "600000.SH", "value": ...} -``` - ---- - -## 7. 系统 `/api/sys` - -这两支同样需要 `X-Token`。 - -### 7.1 Python 版本 - -- **GET** `/api/sys/python_version` +`GET /api/sys/python_version`,无参数。 ```json { - "python_version": "...", - "python_version_info": { - "major": 3, - "minor": 0, - "micro": 0, - "releaselevel": "final", - "serial": 0 + "python_version":"", + "python_version_info":{ + "major":3, + "minor":11, + "micro":0, + "releaselevel":"final", + "serial":0 } } ``` -用于确认 QMT 内嵌解释器版本。 +值仅为示例,实际取自服务进程的 `sys.version` 和 `sys.version_info`。 -### 7.2 关闭 HTTP 服务 +## 11. 错误响应 -- **POST** `/api/sys/shutdown` +匹配到 `BaseHandler` 的接口默认声明 `Content-Type: application/json; charset=utf-8`,但 `write_error()` 实际输出 `self._reason` 原始文本,**不是 JSON 错误对象**。客户端应先检查 HTTP 状态,错误正文按文本处理。 -先写入响应再 `finish()`,然后 `IOLoop.stop()`。策略进程内的 HTTP 循环结束;是否退出整个 QMT 策略取决于宿主行为。 +| 场景 | 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 的错误处理 | -```json -{"status": "success", "message": "服务器正在关闭..."} +除下单外,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"]}' ``` - -```bash -curl -s -X POST -H "X-Token: $TOKEN" http://127.0.0.1:10086/api/sys/shutdown -``` - ---- - -## 8. 快速对照表 - -| 方法 | 路径 | -| --- | --- | -| POST | `/api/v2/positions` | -| POST | `/api/v2/assets` | -| POST | `/api/holding` | -| POST | `/api/money/total` | -| POST | `/api/money/available` | -| POST | `/api/order/buy` | -| POST | `/api/order/sell` | -| POST | `/api/order/status` | -| POST | `/api/order/cancel_all` | -| POST | `/api/order/cancel_order` | -| POST | `/api/order/deal` | -| GET | `/api/context/period` | -| GET | `/api/context/barpos` | -| GET | `/api/context/time_tick_size` | -| GET | `/api/context/stockcode` | -| GET | `/api/context/dividend_type` | -| GET | `/api/context/market` | -| GET | `/api/context/do_back_test` | -| GET | `/api/context/benchmark` | -| GET | `/api/context/capital` | -| GET | `/api/context/universe` | -| POST | `/api/data/stock_name` | -| POST | `/api/data/open_date` | -| POST | `/api/data/last_volume` | -| POST | `/api/data/bar_timetag` | -| GET | `/api/data/tick_timetag` | -| POST | `/api/data/sector` | -| POST | `/api/data/industry` | -| POST | `/api/data/stock_list_in_sector` | -| POST | `/api/data/weight_in_index` | -| POST | `/api/data/contract_multiplier` | -| POST | `/api/data/risk_free_rate` | -| POST | `/api/data/date_location` | -| POST | `/api/data/history_data` | -| POST | `/api/data/market_data` | -| POST | `/api/data/market_data_ex` | -| POST | `/api/data/full_tick` | -| POST | `/api/data/divid_factors` | -| POST | `/api/data/main_contract` | -| POST | `/api/data/timetag_to_datetime` | -| POST | `/api/data/total_share` | -| POST | `/api/data/trading_dates` | -| POST | `/api/data/svol` | -| POST | `/api/data/bvol` | -| POST | `/api/data/longhubang` | -| POST | `/api/data/top10_share_holder` | -| POST | `/api/data/option_detail` | -| POST | `/api/data/turnover_rate` | -| POST | `/api/data/etf_info` | -| POST | `/api/data/etf_iopv` | -| POST | `/api/data/instrumentdetail` | -| POST | `/api/data/contract_expire_date` | -| POST | `/api/data/option_undl_data` | -| POST | `/api/data/financial_data` | -| POST | `/api/data/factor_data` | -| POST | `/api/data/his_st_data` | -| POST | `/api/data/his_index_data` | -| GET | `/api/data/all_subscription` | -| POST | `/api/data/option_list` | -| POST | `/api/data/his_contract_list` | -| POST | `/api/data/option_iv` | -| POST | `/api/data/bsm_price` | -| POST | `/api/data/bsm_iv` | -| POST | `/api/data/local_data` | -| POST | `/api/data/subscribe_quote` | -| POST | `/api/data/unsubscribe_quote` | -| GET | `/api/check/is_last_bar` | -| GET | `/api/check/is_new_bar` | -| POST | `/api/check/is_suspended_stock` | -| POST | `/api/check/is_sector_stock` | -| POST | `/api/check/is_typed_stock` | -| POST | `/api/check/get_industry_name_of_stock` | -| POST | `/api/trade/passorder` | -| POST | `/api/trade/algo_passorder` | -| POST | `/api/trade/smart_algo_passorder` | -| POST | `/api/trade/order_lots` | -| POST | `/api/trade/order_value` | -| POST | `/api/trade/order_percent` | -| POST | `/api/trade/order_target_value` | -| POST | `/api/trade/order_target_percent` | -| POST | `/api/trade/order_shares` | -| POST | `/api/trade/futures/buy_open` | -| POST | `/api/trade/futures/buy_close_tdayfirst` | -| POST | `/api/trade/futures/buy_close_ydayfirst` | -| POST | `/api/trade/futures/sell_open` | -| POST | `/api/trade/futures/sell_close_tdayfirst` | -| POST | `/api/trade/futures/sell_close_ydayfirst` | -| POST | `/api/trade/cancel_task` | -| POST | `/api/trade/pause_task` | -| POST | `/api/trade/resume_task` | -| POST | `/api/trade/do_order` | -| POST | `/api/trade/trade_detail_data` | -| POST | `/api/trade/value_by_order_id` | -| POST | `/api/trade/last_order_id` | -| POST | `/api/trade/can_cancel_order` | -| POST | `/api/trade/debt_contract` | -| POST | `/api/trade/assure_contract` | -| POST | `/api/trade/enable_short_contract` | -| POST | `/api/trade/ipo_data` | -| POST | `/api/trade/new_purchase_limit` | -| POST | `/api/ext/ext_data` | -| POST | `/api/ext/ext_data_rank` | -| POST | `/api/ext/get_factor_value` | -| POST | `/api/ext/get_factor_rank` | -| GET | `/api/sys/python_version` | -| POST | `/api/sys/shutdown` | - -合计 **104** 条路由(`/api/holding` 与 `/api/v2/positions`、`/api/v2/assets` 与资金类为不同路径、部分共用 Handler)。