Files
qmt_bridge/docs/研究/官方接口核实报告.md
T

191 lines
11 KiB
Markdown

# QMT 官方接口核实报告(迅投知识库 dict.thinktrader.net)
核实日期:2026-08-19
资料来源:迅投官方知识库 http://dict.thinktrader.net(所有页面已存为 docs_ref/*.txt 供查阅)
---
## 一、结论速览
| # | 方案假设 | 官方文档核实结果 | 影响 |
|---|---------|----------------|------|
| 1 | `adjust()` 是 QMT 定时回调 | ❌ **不存在**。官方定时回调是 `ContextInfo.run_time(funcName, period, startTime)`(支持 `500nMilliSecond`)或新版 `ContextInfo.schedule_run` | 骨架代码要改 |
| 2 | HTTP 线程(daemon)+ 队列桥接 | ❌ **官方明确:内置 Python 无法使用多线程和多进程,所有策略在同一线程执行** | 方案核心架构要重新设计 |
| 3 | `xtdata.get_market_data_ex` 任意线程直调 | ⚠️ **xtdata 模块主动获取接口是 `get_market_data`,不是 `get_market_data_ex`**(后者是 ContextInfo 方法)。且 xtdata 文档写明"本质是和 MiniQmt 建立连接" | 函数名要改;能否连大 QMT 需实测 |
| 4 | `passorder` 下单 → 结果回写 | ✅ 确认 `passorder(opType, orderType, accountid, orderCode, prType, price, volume, strategyName, quickTrade, userOrderId, ContextInfo)`,**返回无**,真实状态靠 `order_callback`/`deal_callback` 主推(仅实盘模式生效,需先 `set_account`) | 你的判断正确,补充细节 |
| 5 | 下单须在策略线程执行 | ✅ 正确,必须。所有交易函数依赖 ContextInfo 和策略上下文 | 成立 |
| 6 | Python 3.6.8 | ✅ 官方确认:"内置了 3.6 版本的 python 运行环境" | 成立 |
| 7 | `# coding:gbk` | ✅ 官方明确要求 | 成立 |
| 8 | `cancel(orderId, accountId, ContextInfo)` | ⚠️ 实际签名是 `cancel(orderId, accountId, accountType, ContextInfo)`,**有 accountType 参数** | 修正 |
---
## 二、逐项核实详情
### 2.1 内置 Python 运行环境(快速开始页)
> "QMT 极速策略交易系统...内置了 **3.6 版本**的 python 运行环境,提供行情数据与交易下单两大核心功能。"
- 确认方案 4.3 的"Python 3.6.8"判断正确(官方口径 3.6)。
- 三种运行机制(官方明确):
1. 逐 K 线驱动 `handlebar`
2. 事件驱动 `subscribe`(订阅推送)
3. **定时任务 `run_time`(固定间隔触发)**
### 2.2 ⚠️ 关于线程(使用须知页)—— 最关键发现
> "**QMT中,python 无法使用多线程和多进程,而且所有策略都在同一线程中执行**,所以策略中应该尽量避免阻塞类的写法,否则会影响其他策略的执行。"
**这是对整个方案的颠覆性约束**:
- 方案假设的"init() 里启动 HTTP 服务线程(daemon)"**很可能直接失败**——多线程不可用
- 队列桥接(HTTP线程入队 → adjust 出队)的双线程前提不成立
- 需要找到"单线程事件循环"形态的替代架构(见下文第三节)
### 2.3 系统函数(系统函数页)—— 没有 adjust
- 官方系统函数列表:`ContextInfo`对象、`init``after_init``handlebar``ContextInfo.schedule_run``ContextInfo.cancel_schedule_run``ContextInfo.run_time``stop``ContextInfo.is_last_bar``ContextInfo.is_new_bar` 等。
- **全文没有 `adjust` 函数**。
- 替代:定时回调用
```python
# 500ms 定时器
ContextInfo.run_time("f", "500nMilliSecond", "2019-10-14 13:20:00")
# 或新版
ContextInfo.schedule_run(func, time_point, repeat_times, interval, name)
```
### 2.4 交易下单函数(交易函数页)
**passorder 完整签名**:
```python
passorder(opType, orderType, accountid, orderCode, prType, price, volume,
strategyName, quickTrade, userOrderId, ContextInfo)
```
- **返回:无**(不是布尔/错误码!下单是否成功只能靠回调或查询委托)
- opType:23=股票买入, 24=股票卖出(期货另有开平仓)
- orderType:1101=按股数(深沪通用), 1102=沪市按股, 1202=按金额(对账号组)
- prType:5=最新价, 11=限价, 14=模型价(跟价), 12=市价
- quickTrade:0=逐K线生效(默认), 1=最后一根K线立即, **2=调用即下单(不判bar状态)**
- userOrderId:用户自设委托ID,会进入 order/deal 对象的 **m_strRemark** 字段
- **编译器界面执行的下单函数不会产生实际委托**(必须策略交易界面运行)
- 下单真实结果靠 `get_trade_detail_data` 查询或回调主推
**cancel 实际签名**:
```python
cancel(orderId, accountId, accountType, ContextInfo)
```
- accountType: 'FUTURE'/'STOCK'/'CREDIT'/'HUGANGTONG'/'SHENGANGTONG'/'STOCK_OPTION'
- 返回 bool(是否发出撤单信号)
- 注意:撤单参数是 **orderId(委托号, m_strOrderSysID)**,不是 userOrderId
**其他下单**:algo_passorder(拆单)、smart_algo_passorder(VWAP等,需权限)、cancel_task/pause_task/resume_task(任务级)、get_basket/set_basket(篮子)。
### 2.5 交易查询函数(交易函数页)
```python
get_trade_detail_data(accountID, strAccountType, strDatatype[, strategyName])
```
- strDatatype: 'ACCOUNT'/'POSITION'/'POSITION_STATISTICS'/'ORDER'/'DEAL'/'TASK'
- 返回 list 对象,字段以 m_ 开头,如:
- 账号: `m_dBalance`(总资产) `m_dAssureAsset`(净资产) `m_dAvailable`(可用) `m_dInstrumentValue`(市值) `m_dPositionProfit`(盈亏)
- 持仓: `m_strInstrumentID` `m_nVolume`(持仓量) `m_nCanUseVolume`(可用) `m_dOpenPrice`(成本) `m_dInstrumentValue` `m_dPositionProfit`
- 委托: `m_strOrderSysID`(委托号) `m_nOrderStatus`(状态) `m_strRemark`(userOrderId) `m_nVolumeTraded`
- 成交: `m_strOrderSysID` `m_dPrice` `m_nVolume` `m_dTradeAmount`
**get_value_by_order_id(orderId, accountID, accountType)** 按委托号取委托/成交信息。
**get_last_order_id(accountID, accountType, 'order'/'deal')** 取最新委托号。
### 2.6 成交回报主推回调(回调函数页)
| 回调 | 签名 | 说明 |
|------|------|------|
| account_callback | (ContextInfo, accountInfo) | 资金账号状态变化 |
| task_callback | (ContextInfo, taskInfo) | 任务状态变化 |
| **order_callback** | (ContextInfo, orderInfo) | **委托状态变化主推** |
| **deal_callback** | (ContextInfo, dealInfo) | **成交状态变化主推** |
| position_callback | (ContextInfo, positionInfo) | 持仓状态变化 |
| orderError_callback | (ContextInfo, orderArgs, errMsg) | 异常下单 |
**重要提示(官方)**:
- 回调**仅在实盘运行模式下生效**
- 需要先在 init 里调用 **`ContextInfo.set_account(account)`**
- order 对象的 `m_strRemark` = userOrderId, `m_strOrderSysID` = 委托号
### 2.7 行情数据(行情函数页 + XtQuant 文档)
**内置 Python(策略内)**:
```python
ContextInfo.get_market_data_ex(fields=[], stock_code=[], period='1d',
start_time='', end_time='', count=-1, dividend_type='follow',
fill_data=True, subscribe=True)
```
- 返回 {stock_code: pd.DataFrame},index 为 time,columns 为 fields(open/high/low/close/volume/amount...)
- 注意:该函数**不建议在 init 中运行**(init 中只能取本地数据)
- 周期: 'tick' '1m' '5m' '15m' '30m' '1h' '1d' '1w' '1mon' '1q' '1hy' '1y'
- 其他: `ContextInfo.get_full_tick`(全推) `ContextInfo.subscribe_quote`(订阅) `ContextInfo.get_history_data`(不推荐) `ContextInfo.get_local_data`(不推荐)
**XtQuant 原生(外部 Python)**:
```python
xtdata.get_market_data(field_list=[], stock_list=[], period='1d',
start_time='', end_time='', count=-1, dividend_type='none', fill_data=True)
```
- **注意:xtdata 的主动获取接口是 `get_market_data`(不是 `get_market_data_ex`!)** `get_market_data_ex` 是内置 Python 的 ContextInfo 方法
- xtdata 返回:period 为 K 线时 {field: pd.DataFrame}(index 为 stock_list, columns 为 time_list)——与内置 Python 的返回结构**不同**(内置是 {stock: df},xtdata 是 {field: df})!
- **运行逻辑(官方原文)**: "xtdata提供和MiniQmt的交互接口,本质是**和MiniQmt建立连接**,由MiniQmt处理行情数据请求"
### 2.8 XtQuant 交易模块(xttrader 文档)
- 外部 Python 可用 `XtQuantTrader(path, session_id)` 连接,**文档明确绑定 MiniQMT**(`userdata_mini` 路径)
- 完整 API:order_stock(同步)/order_stock_async(异步)、cancel_order_stock、query_stock_asset/orders/trades/positions、回调 on_stock_order/on_stock_trade/on_order_error 等
- **这印证了方案判断:外部 xtquant 依赖 miniQMT,大 QMT 不适用,必须走桥策略**
### 2.9 ContextInfo 使用注意(使用须知页)
> "由于底层机制的限制,ContextInfo 中存储的变量值将会回滚...请避免在其中存储任何变量。"
- 官方推荐用 `class G(): pass; g = G()` 全局对象存状态
- 桥策略的队列/结果字典等必须用模块级全局变量,不能挂 ContextInfo
---
## 三、对方案的修正建议(基于官方文档)
### 3.1 核心架构问题:单线程约束
官方明确"python 无法使用多线程"。这意味着方案的核心机制(HTTP 线程收请求 + 队列 + adjust 线程执行)需要重新设计。可选方向:
**方向 A:单线程异步 HTTP(推荐验证)**
- 用 `asyncore`/`selectors` 实现非阻塞 HTTP,或
- 用 `BaseHTTPRequestHandler` 但**不开线程**——利用 run_time 定时器轮询处理 socket
- 但 HTTP 长连接/慢客户端会阻塞整个策略,风险仍在
**方向 B:桥只做"指令中继",HTTP 放外部(推荐)**
- 把 HTTP 服务放在**外部独立进程**(Linux 侧或 Windows 侧 Python 3.14),不占用 QMT 策略线程
- QMT 策略内只保留一个轻量"指令执行器":run_time 定时轮询一个本地指令源(文件/命名管道/端口)
- 外部 HTTP 桥收到 /order → 写指令文件/管道 → QMT 定时器取指令 → passorder → 回写结果文件
- 这样 QMT 内零线程、零阻塞,完全符合官方约束;HTTP 的并发/超时/安全都在外部处理
**方向 C:验证"线程是否真的不可用"**
- 官方文档说不可用,但社区有说法"能起线程但会阻塞主线程/不稳定"
- 建议在国金 GJQMT 上做个 10 分钟实测:init 里 `threading.Thread(target=...).start()` 看是否报错、是否影响 handlebar
- **如果实测线程能跑**,原方案(方向A变体)可保留,但必须加超时保护,避免慢请求卡死策略
### 3.2 函数修正清单
1. ~~adjust()~~ → `ContextInfo.run_time("on_bridge_timer", "500nMilliSecond", "...")`
2. ~~xtdata.get_market_data_ex~~ → 内置 Python 用 `ContextInfo.get_market_data_ex`;若用 xtdata 模块则 `xtdata.get_market_data`
3. ~~cancel(orderId, accountId, ContextInfo)~~ → `cancel(orderId, accountId, 'STOCK', ContextInfo)`
4. 下单状态:passorder **无返回** → 必须 `order_callback` + `deal_callback`(实盘模式 + set_account)或轮询 `get_trade_detail_data`
5. 状态存储:不能挂 ContextInfo → 用模块级全局 class 实例
### 3.3 必须实测的三个点(写码前)
1. **线程可用性**:init 里起 daemon 线程是否真的失败(决定方向A还是B)
2. **set_account**:实盘模式回调是否需要、如何配置账号
3. **xtdata 在大 QMT 内能否 import 并取数**:若大 QMT 无 miniQMT 的 userdata_mini 连接,xtdata 可能连不上——则行情一律走 `ContextInfo.get_market_data_ex`
### 3.4 仍成立的设计
- 队列桥接思路(若线程可用)、复权/周期参数、GBK 编码、8610 端口复用、脚本零改动目标
- passorder 必须在策略上下文执行(官方:下单函数需要 ContextInfo,且编译器环境不下单)
- 实盘模式登录才能收完整回报(官方:回调仅实盘生效)