Skip to content

契约 v2 + 查询串线三条修复(破坏性变更) - #8

Merged
suny911 merged 3 commits into
mainfrom
worktree-fix-response-mismatch
Aug 3, 2026
Merged

suny911 merged 3 commits into
mainfrom
worktree-fix-response-mismatch

Conversation

@suny911

@suny911 suny911 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

两批工作,同一条 PR:(A) 2026-08-03 查询串线的受控端三条修复(B) 消费方契约冻结 v2 的全部实现。均建议盘后部署。

⚠️ BREAKING CHANGE:所有工具回执改统一信封、键名与数值类型全面调整、无兼容形。消费侧需同批改造(需求方已确认成本自担,配套改造 rd-trader-contract-v2-consumer 已入队)。


A. 串线修复(三条 + 追加一条)

  1. 抓表零请求-响应校验ths/table_guard.py:表头必须命中自身特征列、且不得命中他表独有特征列;错表重抓 3 次仍不符即显式 failed 带 got_columns。表头取自原始文本而非解析行——空表照样有表头,必须能通过校验并以 data: [] 正常返回。
  2. 超时线程脱缰 → 调用代次:超时时 invalidate_inflight(),脱缰线程在翻页/抓表/弹窗/提交四类检查点自行中止;代次可重入。
  3. 重连后旧 reply 错投 → 回执携带收帧连接,发送前比对,不一致丢弃留痕。
  4. 市价单基线硬失败(追加拍板)→ 读不到成交表基线就不提交,空基线会把历史成交误算成本次成交。

附带:trader.log 由每次启动 'w' 覆盖改为追加 + 体积滚动(5MB × 5),保住回溯证据。

B. 契约 v2

实现
C1 统一信封 contract.py 单点定义 {status, code, data, error{class,broker_msg,message}, contract_version};dispatcher 对非契约形态转 internal_error 而非放行
C2 两层错误分类 结构性判定由控制流得出;柜台原文关键词表尽力映射,认不出一律 unknown,原文保留 broker_msg;unknown/unknown_outcome 属不可自动重试集合
C3 在飞单 终态不出现;状态识别不出按在飞保守返回;行结构钉死
C4/C5a/C5b order_ledger.py(SQLite 落盘 ≥5 交易日):幂等重发绝不二次提交、同 id 异参拒绝、台账不可用一律拒单、新增 query_order 三档分辨率
C6 类型单位 数值 number(元/分、厘、股、_pct),-- → null 而非 0
C7 契约即测试 schema 升规范件 + test_contract_docs_sync.py 断言实现↔PROTOCOL.md↔schema↔网关常量四处不漂移
G3/G4/B1/B2/B3/S2 PROTOCOL.md 写死:busy 背压与退避、会话生命周期返回形态、settlement 入 guard、成交时间 ISO(日期时区来自本机时钟)、空表语义永久锁定、消费侧节奏建议

三处值得单独看的判断

  • status: failed 不等于「未提交」:下单超时是 code=submitted_unconfirmed。协议里用独立小节写死,安全动作是同 coid 原样重发。
  • order_event 改读内部全量表orders_active 按 C3 过滤终态后,事件推送若继续读它会把 filled/canceled 全部丢失。新增内部 orders_active_all 通道。
  • coid 回显是尽力而为:超时单与外部单为 null,对账主键是 entrust_no

网关配套(另一仓)

guling-mcp-gateway 分支 fix-rpc-correlation(该仓无 remote,提交在本地):

  • cf419b1 串线根因:配对键改「token + 网关自生成 id」+ 回执来源校验;
  • c1da9c5 契约 v2:initialize 暴露 contract_version失败回执透传完整信封(原先只回一句散文,等于在网关层丢掉机器分类能力)。

验收

  • 受控端 254 passed / 10 skipped(新增 51 条契约测试);compileall 通过。
  • 网关 go build + go test ./... 全绿。
  • 真机验收与 alerts 统计由消费侧代跑。

🤖 Generated with Claude Code

suny911 and others added 3 commits August 4, 2026 01:06
2026-08-03 实盘日消费侧 balance 12 次收到成交明细表/持仓表(status=succeed)。
双侧核实后归因分三方,本仓侧三条(均无条件修,不依赖网关裁决结果):

1. 抓表零请求-响应校验(最优先,单线程安静日也在漏)
   翻页快捷键是全局按键,没落到 xiadan 时 grid 里还是上一次查询的表,Ctrl+C
   原样抓走;read_table_text 的剪贴板序号校验只防「陈旧残留」,不防「不是本次
   请求的那一页」;三个 grid 查询过去非空即 code=0 出门。
   新增 ths/table_guard.py:表头必须命中自身特征列、且不得命中他表独有特征列
   (只查前者的话未知第四张表照样漏)。错表即重抓(3 次),仍不符则显式 failed
   并带 got_columns——禁 succeed 携错表出门。表头取自原始文本而非解析结果:
   空表(今天无挂单/无成交)照样有表头,必须能通过校验并以 data=[] 正常返回。
   orders_active 走的正是这条路径,其错表会被消费侧读成「无挂单」→ 孤儿挂单
   存活、止损哨兵被架空,比 balance 症状更险。

2. 超时线程脱缰
   dispatcher 的 25s 总超时用 wait_for 包 to_thread,超时只取消等待协程——线程
   取消不掉,它还在发全局按键,而 finally 已放 win_lock 让下一笔进场,两个线程
   同击一个 xiadan 窗口。新增调用代次:工作线程进场登记代次,dispatcher 超时时
   invalidate_inflight() 把代次 +1,线程在检查点自行中止。检查点覆盖翻页
   (switch_to_normal/refresh)、抓表(read_table_text)、弹窗(input_ocr/dialogs.pump/
   dialog_cleanup)、提交(下单/市价单/撤单点击)。代次可重入,下单内部查成交表
   继承同一代次。

3. 重连后旧 reply 错投
   ws_client 发送用 self.ws 当前值,一笔 RPC 最长跑 25s,其间可能已重连——旧
   会话的回执发到新连接上归属无从保证。改为携带收帧时的连接,发送前比对,
   不一致则丢弃并留痕。

另:trader.log 由每次启动 'w' 覆盖改为追加 + 体积滚动(保留 5 份)。本次查
08-03 串线时受控端凌晨重启一次,当天全部 RPC 日志(含每笔 call 的 id/method,
正是定位串线归属的关键证据)被抹掉,回溯路径直接灭失。

测试:新增 27 条(表头判据/三表校验含空表与重抓恢复/五个代次检查点/dispatcher
超时作废/回执跨连接丢弃),全量 140 passed 10 skipped。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
市价单的成交量/均价靠下单前后的成交表差分(认「after 里 before 没有的行」)。
原实现在 pre = get_filled_orders() 失败时以 before=[] 继续下单,当日同股同向的
历史成交会被整体算成本次成交——污染的是真钱 sizing 的输入,而市价单发出去无法
回收。表头归属校验上线后,「抓到错表」也会归到基线失败这一路,触发概率略升。

改为硬失败:基线拿不到就不提交,回执带 submitted=False 与失败原因,让调用方重试。
BREAKING CHANGE: 所有工具回执改为统一信封,键名与数值类型全面调整,无兼容形。
消费侧需同步改造(需求方已确认为一次性可承受成本,配套改造已入队)。

对应 docs/consumer-contract-freeze.md v2 定稿(三件拍板项已裁定):

C1 统一信封:{status, code(机器枚举串), data, error{class,broker_msg,message},
contract_version:"2"},含 buy/sell/cancel 与 busy/超时,消灭「查询带 data 包裹、
买卖回执裸体」两形。新增 src/trader/contract.py 单点定义,dispatcher 对非契约形态
一律转 internal_error 而非放行。⚠️ status=failed 不等于未提交:下单超时为
code=submitted_unconfirmed + class=unknown_outcome,安全动作是同 id 原样重发。

C2 错误机器分类(两层):结构性判定由控制流得出(busy/timeout/read_failed/
table_mismatch/ledger_unavailable/...);柜台原文走关键词表尽力映射
(资金不足/价格超限/数量不合规/停牌/无权限/柜台超时),**认不出一律 unknown**,
原文始终保留在 broker_msg。unknown 与 unknown_outcome 均属不可自动重试集合。

C3 orders_active 只返回在飞单:终态(已成/已撤/废单/数量已满)不出现;
**状态识别不出的行按在飞保守返回**——宁可多给一行也不能把活单藏起来。行结构钉死。
order_event 推送改读内部全量表(orders_active_all),否则终态行被过滤会导致
filled/canceled 事件全部丢失。

C4/C5a/C5b client_order_id:新增 order_ledger.py(SQLite 落盘,保留 ≥5 交易日)。
coid 不写入柜台(同花顺委托无自定义字段),仅存台账并按 entrust_no join 回显——
超时单与外部单为 null 是结构性合法态,对账主键是 entrust_no。幂等:同 id 重发绝不
产生第二次提交,返回首次回执;首次未落定则回 unknown_outcome(契约不撒谎);同 id
不同参数拒绝(invalid_params);**台账不可用一律拒单,禁静默降级**。新增 query_order
工具,分辨率分 by_entrust_no/heuristic/unresolved 三档并在回执中明示。

C6 类型与单位:数值一律 number(金额元/取整到分、价格到厘、数量股、%键名改 _pct),
**"--"/空占位符映射 null 而非 0**(0 是真实数字,会被下游当真值用)。键名保留中文
与同花顺列名同字面。

C7 契约即测试:tools_schema.json 升为规范件(含 contract_version 与 query_order),
新增 test_contract_docs_sync.py 断言 code/class/状态值域三处(实现↔PROTOCOL.md↔
schema↔网关常量)不漂移。

网关侧配套:initialize.serverInfo 暴露 contract_version;失败回执改为透传**完整信封**
而非仅一句散文(只回散文等于在网关层丢掉机器分类能力)。见 guling-mcp-gateway
分支 fix-rpc-correlation。

PROTOCOL.md 重写回执契约节:code 值域表、两层 error.class、busy 背压与退避建议(G3)、
会话生命周期返回形态(G4)、空表语义永久锁定(B3)、成交时间时区来自本机时钟(B2)、
消费侧节奏建议(S2)。

另:市价单基线硬失败沿用;白名单拒绝路径补上信封(原先只有 error 字符串)。

测试:254 passed / 10 skipped(新增 51 条契约测试:信封同形、柜台原文分类、
占位符 null、行结构与在飞判据、ISO 时间、幂等四态、查单三档分辨率、文档同步)。
@suny911 suny911 changed the title fix(ths): 查询响应串线三条修复——表头归属校验/超时线程代次作废/回执错投丢弃 契约 v2 + 查询串线三条修复(破坏性变更) Aug 3, 2026
@suny911
suny911 marked this pull request as ready for review August 3, 2026 18:08
@suny911
suny911 merged commit f1ff625 into main Aug 3, 2026
1 check passed
@suny911
suny911 deleted the worktree-fix-response-mismatch branch August 3, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant