tradingSystem/QMT_SIDE_S3_CLOSEOUT.md

217 lines
12 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 侧S3 收尾的五件事2026-07-30
> 面向 QMT 侧研发。上一份是 `QMT_TEST_ENV_REQUIREMENTS.md`R1R4 模拟环境需求),
> **R1R4 已于今天上午验完,主体可以划掉**——先说这个,避免重复投入。
> 本文只列剩下的五件事,按优先级排。第 1 条阻断切换,第 5 条最容易被忽略但后果最重。
---
## 0. 先说已经通了的(无需再动)
| 项 | 结果 | 实测依据 |
|---|---|---|
| **R1 可控成交** | ✅ | `600000.SH sell 100 @10.13`(涨跌停带内、高于市价)停在 `SUBMITTED`、成交 0。同代码同数量的 `@99.99` 昨天是秒成,行为确实改了 |
| **R2 代码校验** | ✅ | `999999.SH``reject{instruction_id, code:"UNKNOWN_CODE", reason:"instrument not found: 999999.SH"}`。三点全对:带 `instruction_id`(委托级而非协议级)、码值取自协议 §7.3 表而非自造、reason 带原文。**这一类做得很干净** |
| **R3 部分成交** | ✅ | `PARTIAL → PARTIAL → FILLED` 序列正确(见下面第 1 条,只有 `trade_no` 的格式要改) |
| **R4 到期自动撤** | ✅ | `valid_until` 到点推 `order_update status=EXPIRED``leaves_qty` 归零 |
| **主动撤单** | ✅ | `SUBMITTED → CANCELLED``leaves_qty=0`,与我方本地 `cancel_state` 一致 |
| 控制面不被业务阻塞 | ✅ | 时钟差稳定在 +0.92 秒,最慢一条上行只比最快的晚 164 ms。`ack_seq` 挪出控制面那一改很有效——昨天那个 +30 秒的投递卡顿再没出现 |
**协议 §9 的 S3「五类各至少一次」至此凑齐**(全成 / 部分成交 / 主动撤单 / 到期过期 / 拒绝)。
---
## 1. `trade_no` 的格式不合协议 §5.5**这一条阻断切换**
实测收到的是:
```
trade_no: "T-SHADOW-6a21…" ← 每笔一个随机串
trade_no: "T-SHADOW-9f64…"
trade_no: "T-SHADOW-772b…"
```
协议 §5.5 定的是 **`{broker_order_id}#{n}`**,即同一张委托 `SHADOW-7c472c48f0` 的三笔应为:
```
SHADOW-7c472c48f0#1
SHADOW-7c472c48f0#2
SHADOW-7c472c48f0#3
```
**为什么这不是格式洁癖。** PMS 侧有两层去重seq 水位§6.1+ `trade_no` 唯一索引§5.5)。
随机串**当下**能去重——唯一索引照样拦得住重复。问题在**补发**
按 §6.1 断线重连要重发 `last_seq+1` 之后的消息。如果贵方在补发时**重新生成**了随机
`trade_no`,同一笔成交就会带着两个不同的 `trade_no` 到达 PMS。第二层去重当场失效只剩 seq
一层;而 seq 那层在我方水位回退或 inbox 清理后也不存在了。**结果是同一笔成交入账两次**——
持仓多算、摊薄成本算错,而摊薄成本是我方安全垫的分母,安全垫又是补仓/加仓/减仓的共同判据。
`{broker_order_id}#{n}` 是**确定性**的:委托号 + 第几笔,补发一万次都是同一个值。这正是协议
当初定这个格式的理由。
**请确认**:现在的 `T-SHADOW-xxx` 在补发时是原值重放,还是重新生成?如果是重新生成,
这条必须改完我们才敢切 `PMS_DISPATCH_MODE=ws`
---
## 2. 限价超出涨跌停区间:请回 `BAD_PARAM`
实测 `600000.SH sell 100 @99.99`(浦发涨停价约 11.1,挂到市价近 10 倍)**既没被拒也没成交**
一路挂到 `EXPIRED`
**这一半是我们协议的锅,已经修了。** 原 §7.3 拒绝码表里:
- `LIMIT_UP` / `LIMIT_DOWN` 指的是「涨/跌停**无法成交该方向**」——价格合法,只是此刻撮不上,
所以 `retryable=是`(换价重发有意义)
- `BAD_PARAM` 原文只写了「价格**精度**超限」
- **「限价超出当日涨跌停区间」没有落点**——贵方按表实现,找不到该回哪个码
协议已升 **V1.0.1**`BAD_PARAM` 的含义扩为「价格不合法——精度超限,**或限价超出当日涨跌停
区间**」,并补了与 `LIMIT_UP`/`LIMIT_DOWN` 的分界说明。**消息集与状态机一字未动,贵方现有实现
不改也合规**,只是多一个校验点。
请在模拟环境补上这个校验:越界限价 → `reject{code:"BAD_PARAM", retryable:false}`
(顺带提一句:变更记录表的顺序我们也修了,原先 V1.0 那行排在 V0.9.2 上面,正是 `ack_seq`
被漏掉的原因。现在按版本号从旧到新排,**读到最后一行才是当前版本**。)
---
## 3. 请提供 07-29 的 `signature verification failed` 日志段
我方 `pms_qmt_inbox` 里有 **19 条连续的**
```json
seq 6299 6317
{"instruction_id": null, "code": "SIG_INVALID",
"reason": "signature verification failed", "retryable": false}
```
即**贵方验不过我方的签名**,连续 19 条。签名是协议三条安全底座之一,这个数量不像偶发。
我方现在查不动了:那段 pms-ws 日志随容器重建消失,且 PMS 侧不留发送侧流水,所以**无从知道
被拒的是哪 19 条消息**。这很要紧——协议级 `reject` 我方不自动重发,**那 19 条消息永久丢失**。
如果里面有 `place_order``cancel_order`,那就是「发出去了没人执行」,而通道一路显示 ONLINE。
**请帮忙查**:贵方收到的原始帧一定在日志里。想知道两件事:
1. 被拒的是哪些 `type``place_order` / `cancel_order` / `ack_seq` / `hello` / `ping`
2. 验签失败的直接原因——公钥读错?规范化串对不上?还是某类消息的字段序列化有差异?
**一个我们比较怀疑的方向**:带中文 `note``place_order`。我方联调单的 `note`
「ws 联调测试单」UTF-8 处理只要有一点差异(转义、`ensure_ascii`、字节序)规范化串就对不上,
而其它不带中文的消息都正常。协议 §2.1.1 的测试向量建议双方再各跑一次比对。
---
## 4. `_sender_loop` 的队列与 socket 生命周期不一致(第三次提,仍未得答复)
`QMT_SIDE_CONTROL_PATH.md``QMT_TEST_ENV_REQUIREMENTS.md` §5.1 都提过:
```python
sender = asyncio.create_task(self._sender_loop(websocket)) # socket 创建时绑死
async def _sender_loop(self, websocket):
while True:
env = await self._send_queue.get() # 队列每轮重读实例属性
await websocket.send(...) # 发往绑死的那个 socket
```
`self._send_queue` 每次新连接会被重新赋值。旧连接的 `sender` 若尚未被 cancel`finally` 是
异步执行的,`close()` 返回不代表旧任务已收尾),下一轮 `get()` 拿到的是**新队列**的消息,却
发往**旧的已关闭 socket** → `send` 抛异常 → `break`**消息静默丢失**,新连接那边在等一个
永远不来的回报。
建议把队列作为参数传入,与 socket 同生命周期:
```python
queue = asyncio.Queue()
self._send_queue = queue
sender = asyncio.create_task(self._sender_loop(websocket, queue))
```
**这条和第 3 条的现象可以互相解释**(都是「消息发出去了但对端没正确处理」),所以一起看。
---
## 5. 重建模拟持仓时,`cost_price` 必须是真实成本价(最容易忽略,后果最重)
背景:贵方今天上午清空了 `trading_position`我方也把账本清干净了PMS 侧 07-30 11:14
**现在双方都是空的**,等贵方装上持仓后我方按「以下游为准」认领重建。
请务必注意——`trading_position` 里的 **`cost_price` 会直接变成我方账本的开仓价**
- 我方 `ADD_RECON_LOT` 的开仓价**优先取 `cost_price`**,取不到才拿现价兜底(并标注是估的)
- **若 `cost_price` 填 0、留空、或等于现价**,我方摊薄成本就等于当天价、**安全垫齐刷刷是 0**
- 安全垫是盈利加仓(≥3%)、保垫减仓(峰值≥6%)、补仓评估档(8%/15%)**共同的判断依据**
它一错,整条纪律链跟着错,页面和日报上的浮动盈亏也全是 0
- 一只真实成本 20、现价 10 的票(实亏 50%)会被记成「不赚不亏」,该评估的补仓不评估
2026-07-29 首次接管 22 只持仓时踩过一次。我方取值优先级已有单测守着,**但数据本身对不对
只能靠贵方**。`available_quantity`T+1 可卖)同理,请填合理值。
**5.1 先确认一件事:模拟仓现在那 1100 股是哪来的?**
07-30 12:00 我方收到的 `query_positions` 回应(字段完全按 §5.7,这点很干净):
```json
{"kind": "positions", "as_of": 1785384340542, "items": [
{"ts_code": "600000.SH", "total_qty": 1100, "avail_qty": 1100, "frozen_qty": 0,
"cost_price": 9.2732, "market_price": 9.27}]}
```
`1100 股 @ 9.2732` 这两个数我方认得——**清账前 PMS 账本里那笔来历不明的持仓,成本正好是 9.273**
而它是 07-29 联调期那批测试买单成交出来的(我方当时误把停机遗留的联调成交当外部成交入了账)。
所以想确认:**模拟仓这 1100 股是不是同一批联调单的产物?**
- **是** → 请连它一起清掉,我方这边不认领;测试数据进了账本,后面每一步都建在错的基础上
- **不是** → 请说明 `cost_price 9.2732` 的来历,我方按真实持仓认领
在得到答复前,我方已停掉日终结算调度,不会自动认领。
**5.2 装模拟持仓时,希望能覆盖这几种情形**
只有一只、且成本≈现价(浮动盈亏 ≈ 0的话我方好几条纪律根本验不到——安全垫是盈利加仓
≥3%)、保垫减仓(峰值 ≥6%、补仓评估档8% / 15%**共同的判断依据**,全 0 就等于这些
逻辑一条都跑不到。希望能有:
| 情形 | 目的 |
|---|---|
| 一只明显浮盈(现价高于成本 5% 以上) | 验盈利加仓与保垫减仓 |
| 一只明显浮亏(现价低于成本 10% 以上) | 验补仓评估档 |
| 一只当日买入(`avail_qty < total_qty` | T+1 可卖量口径 |
| 3~5 分散在不同板块 | 验组合上限与行业集中度 |
数量不用大每只 100~1000 股即可——我方验的是逻辑分支不是金额
**另外两件请提前通知我方的事:**
1. **重建模拟环境会不会重置 seq / 清空存上行消息的 Redis** 目前我方水位 8052与贵方
自报 8045 在握手那一刻是对齐的若贵方 seq 重置回小值而我方水位不动就是**序号倒挂**——
我方会把贵方此后发的每一条**含成交**都当成不高于水位静默丢弃而通道显示一切正常
这种情况必须双方**商定同时归零**我方绝不会单方面下调水位那会导致区间内成交重复入账)。
2. **装持仓的时点**我方 15:10 的日终结算会自动认领 `trading_position` 的内容且账本为空时
不设任何拦阻所以贵方装到一半时如果正好跨过 15:10我方会拿半成品建账装好了说一声
即可我方在那之前会把调度停掉
---
## 6. 已经闭环、不用再回的
- **`trading_buy_plan` 轮询已于 2026-07-30 10:55 停止** ——
`QMT_INTERFACE_REQUIREMENTS.md` C2旧通道停用清单那个长期未决项至此关闭多谢
- `nonce` 16 字节(§2.2)、`ack_seq` 累积确认(§4.5)、§6.1 序号补发Ed25519 双向签名互通 —— 全部实测通过
**唯一还想确认的是 D3**贵方当前消费决策系统信号的**全部位置**是否都停了
C2 只覆盖了 `trading_buy_plan` 这一条路,「直接执行决策系统卖出指令与盘中 ENTRY/EXIT 信号
那一条如果还有别的消费点切换当天两套系统会对同一只票同时下单
---
## 优先级建议
**5.1(一句话就能答,卡着我方认领)> 1阻断切换> 5 / 5.2(数据正确性,做在装持仓之前)> 3安全底座> 2 > 4**
1 条改完 + 5 条按口径装好持仓我方即可进入小仓位实盘」; 3 条不查明签名这条
安全底座上就一直挂着一个问号