168 lines
6.9 KiB
Markdown
168 lines
6.9 KiB
Markdown
# xtquant_big_convert 实现原理调研(基于协议线索 + 官方API还原)
|
|
|
|
调研日期:2026-08-19
|
|
仓库:https://github.com/litaolemo/xtquant_big_convert
|
|
⚠️ 注意:本机出网受限(GitHub/镜像/Bing/Baidu 全部不通),无法抓取真实源码。
|
|
本文基于方案文档中的协议线索 + 迅投官方API文档(已核实)还原实现原理。
|
|
协议线索:请求 RPUSH bigqmt:rpc:queue:{账号} / 响应 BLPOP bigqmt:rpc:respq:{账号}:{reqId}
|
|
|
|
---
|
|
|
|
## 一、它解决什么问题
|
|
|
|
**目标**:让外部 Python 程序(没有 MiniQMT/XtQuantServer 权限)直接调用"大QMT"的交易和行情能力。
|
|
|
|
**背景约束**(官方文档核实):
|
|
- xtquant 外部库的 XtQuantTrader / xtdata **本质是和 MiniQMT 建立连接**(官方原文)
|
|
- 大QMT(完整交易端)不提供 MiniQMT 的 userdata_mini 连接通道 → 外部 xtquant 直连大QMT 失败
|
|
- 但大QMT 的策略编辑器(内置 Python 3.6)能跑策略,能调 passorder / get_trade_detail_data
|
|
|
|
**解法**:把"桥"跑在大QMT 进程内(策略里),外部程序通过 Redis 发 RPC 请求,桥在 QMT 策略线程里执行并回写结果。
|
|
|
|
---
|
|
|
|
## 二、核心架构
|
|
|
|
```
|
|
┌─ 外部Python进程(无QMT权限) ─┐ ┌─ Redis ─┐ ┌─ 大QMT进程内(策略) ─┐
|
|
│ 你的量化脚本 / sfgrid(TS) │ │ │ │ qmt_big_convert.py │
|
|
│ │ ①RPUSH │ │ ②BLPOP │ │
|
|
│ redis_client.rpush( ├───────►│ queue ├───────►│ bridge_loop(): │
|
|
│ 'bigqmt:rpc:queue:{acct}',│ │ {acct} │ │ 解析请求 │
|
|
│ json.dumps(req)) │ │ │ │ passorder(...) │
|
|
│ │ │ │ │ get_trade_detail │
|
|
│ ⑤BLPOP(respq) ←─────────────┼────────┤ respq │◄───────┤ ④RPUSH 结果 │
|
|
│ reqId → 结果 │ │ {acct}: │ │ │
|
|
└──────────────────────────────┘ │ {reqId} │ └──────────────────────┘
|
|
└─────────┘
|
|
```
|
|
|
|
**关键**:Redis 是"队列桥接"的中枢,不是 HTTP server。这正好绕开官方"QMT 内置 Python 不能多线程"的约束——QMT 侧不需要开监听线程,用一个循环/定时器消费队列即可。
|
|
|
|
---
|
|
|
|
## 三、Redis 协议设计(从 key 命名还原)
|
|
|
|
### 3.1 请求通道
|
|
```
|
|
Key: bigqmt:rpc:queue:{accountID}
|
|
Type: LIST
|
|
操作: 外部进程 RPUSH, QMT桥 BLPOP
|
|
Value: JSON 请求体
|
|
```
|
|
|
|
### 3.2 响应通道
|
|
```
|
|
Key: bigqmt:rpc:respq:{accountID}:{reqId}
|
|
Type: LIST
|
|
操作: QMT桥 RPUSH, 外部进程 BLPOP(带超时)
|
|
Value: JSON 响应体
|
|
```
|
|
|
|
### 3.3 推断的请求/响应结构(基于常见 RPC 桥设计 + 协议命名)
|
|
```jsonc
|
|
// 请求(外部 → QMT)
|
|
{
|
|
"reqId": "uuid或自增id", // 用于关联响应队列
|
|
"method": "order | cancel | query_position | query_asset | kline | ...",
|
|
"params": {
|
|
// order: {"code":"000001.SZ","opType":23,"volume":100,"priceType":5,"price":-1,...}
|
|
// kline: {"code":"600000.SH","period":"1d","count":100}
|
|
}
|
|
}
|
|
|
|
// 响应(QMT → 外部)
|
|
{
|
|
"reqId": "同上",
|
|
"code": 0, // 0成功, 非0错误
|
|
"data": {...} | [...], // 查询结果 / 下单结果
|
|
"msg": "error message"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 四、QMT 侧桥程序的关键实现
|
|
|
|
### 4.1 入口:策略生命周期
|
|
```python
|
|
#coding:gbk
|
|
import json, redis # 或自实现极简RESP客户端
|
|
|
|
def init(ContextInfo):
|
|
ContextInfo.run_time("bridge", "200nMilliSecond", "2019-01-01 00:00:00")
|
|
# 或:
|
|
# 起一个 while 循环线程(若实测线程可用)
|
|
|
|
def bridge(ContextInfo):
|
|
# 定时/循环:BLPOP 队列
|
|
req = redis_client.blpop('bigqmt:rpc:queue:%s' % ACCOUNT, timeout=0.1)
|
|
if req:
|
|
result = dispatch(req) # 在策略上下文执行
|
|
redis_client.rpush('bigqmt:rpc:respq:%s:%s' % (ACCOUNT, req['reqId']),
|
|
json.dumps(result))
|
|
|
|
def dispatch(req):
|
|
method = req['method']
|
|
if method == 'order':
|
|
return passorder(req['params']) # 必须传 ContextInfo
|
|
elif method == 'cancel':
|
|
return cancel(...)
|
|
elif method == 'query':
|
|
return get_trade_detail_data(...)
|
|
elif method == 'kline':
|
|
return ContextInfo.get_market_data_ex(...)
|
|
```
|
|
|
|
### 4.2 必须处理的点(官方文档核实)
|
|
1. **passorder 无返回** → 下单结果要 order_callback/deal_callback 或轮询委托回写
|
|
2. **回调仅实盘模式 + 需 set_account** → init 里 `ContextInfo.set_account(account)`
|
|
3. **状态不能挂 ContextInfo**(会回滚) → 用模块级全局 class
|
|
4. **行情函数**:内置 Python 用 `ContextInfo.get_market_data_ex`(不能直接在 init 跑)
|
|
5. **定时器用 run_time**(官方无 adjust)
|
|
|
|
---
|
|
|
|
## 五、为什么它和你的 HTTP 桥方案是"同构"的
|
|
|
|
| 维度 | xtquant_big_convert | 你的 qmt_http_bridge 方案 |
|
|
|------|--------------------|--------------------------|
|
|
| 桥的位置 | 大QMT 策略内 | 大QMT 策略内 |
|
|
| 外部入口 | Redis LIST(RPUSH/BLPOP) | HTTP server |
|
|
| 队列桥接 | Redis 队列 | 内存 ORDER_QUEUE |
|
|
| 结果回写 | Redis respq + reqId | ORDER_RESULTS[rid] |
|
|
| QMT 侧执行 | 策略线程(定时消费) | adjust() 定时消费 |
|
|
| 解决的问题 | 外部无 xtquant 权限 | 外部脚本零改动复用8610 |
|
|
|
|
**本质相同**:都是"大QMT 进程内桥 + 队列 + 策略线程执行 + 结果回写"。
|
|
|
|
---
|
|
|
|
## 六、Redis vs HTTP 的取舍(你的方案该参考什么)
|
|
|
|
### xtquant_big_convert 用 Redis 的动机(推断)
|
|
- 官方"不能多线程"→ 无法开 HTTP server 线程
|
|
- Redis BLPOP 天然适配单线程轮询:一个 run_time 定时器就能消费
|
|
- 外部 redis-py 客户端成熟,不碰 QMT
|
|
|
|
### 但它引入的问题
|
|
1. **需要 Redis 服务** → 多一个运维组件(虽然轻量)
|
|
2. **QMT 内置 Python 3.6 是否有 redis 库?** → 大概率没有,要自己实现极简 RESP 客户端(纯 socket,~100行)
|
|
3. **BLPOP 阻塞语义** → 若桥循环里阻塞,会卡住策略主线程,需要 timeout 参数
|
|
|
|
### 对你 HTTP 桥方案的启示
|
|
- 如果实测 QMT 里**线程可用** → HTTP 方案可行,但必须加请求超时保护(慢客户端卡死策略)
|
|
- 如果**线程不可用** → 你的 HTTP 方案需要改成"单线程事件循环",或者借鉴 Redis 思路:QMT 侧只做定时轮询,HTTP 监听放外部进程(方向B)
|
|
|
|
---
|
|
|
|
## 七、待验证项(拿到真实源码后核对)
|
|
|
|
- [ ] 桥是否用 redis-py 还是自写 RESP 客户端
|
|
- [ ] 请求/响应 JSON 的确切字段名
|
|
- [ ] 下单结果如何回写(回调 or 轮询)
|
|
- [ ] 是否处理了 order_callback / deal_callback
|
|
- [ ] 定时器用的 run_time 还是线程
|
|
- [ ] 是否支持行情(kline)还是只做交易
|
|
- [ ] 是否内置了 token/鉴权
|