订单跟踪系统bug分析23
订单跟踪严重 Bug 分析
一、订单跟踪架构简述
sequenceDiagram
participant WS as WebSocket
participant Kline as RealtimeKlineServiceBase
participant Mgr as WebSocketOrderManager
participant Mon as _monitor_order
participant Exec as Executor
Exec->>Mgr: track_order(oid, coin, timeout)
Mgr->>Mgr: _tracking[oid]=tracking
Mgr->>Mon: start daemon thread
Exec->>Mgr: wait_for_order(tracking)
WS-->>Kline: orderUpdates / userFills
Kline->>Kline: channel = msg.get("channel") or msg.get("type")
Kline->>Mgr: handle_message(msg)
Mgr->>Mgr: _on_order_update / _on_user_fill
Mgr->>Mgr: _finish(oid) or grace timer
Mon->>Mon: early HTTP check (2s)
Mon->>Exec: query_order_status(oid)
Mon->>Mgr: _finish_direct(...)
Mgr->>Exec: result_event.set()
- WS 路径:推送 → K 线服务路由 →
WebSocketOrderManager.handle_message→_on_order_update/_on_user_fill→_finish(oid)或宽限期定时器。 - HTTP/超时路径:
_monitor_order早期检查(2s) + 超时后 HTTP 验证 →_finish_direct(oid, tracking, status, ...)。 - 重连:
WebSocketReconnectedEvent→verify_pending_orders()双轮 HTTP 补查。
关键文件:
src/trading/websocket_order_manager.py:追踪状态、WS 处理、HTTP 兜底、identity check。src/services/realtime_kline_service_base.py:订单消息路由、缓冲、_get_ws_order_manager。src/trading/executor.py:_track_limit_order、query_order_status、重连时调用verify_pending_orders。
二、严重 Bug
1. WebSocketOrderManager 只认 channel,不认 type,导致订单消息被静默丢弃
位置:src/trading/websocket_order_manager.py 第 141-147 行
- K 线服务侧已做兼容(注释中的 Bug F):
channel = msg.get("channel") or msg.get("type", ""),用于决定是否进入订单分支并调用mgr.handle_message(msg)。 - 但 订单管理器 内只使用:
channel = message.get("channel", "")。 - 若 Hyperliquid 推送格式为
{"type": "orderUpdates", "data": [...]}或{"type": "userFills", "data": [...]}且无channel字段,则 manager 内channel == "",不会进入_on_order_update/_on_user_fill,消息被静默丢弃。 - 后果:订单已成交/取消/拒绝时,WS 路径永远不结算,只能依赖 2s 早期 HTTP 或整段超时后的 HTTP 兜底,导致:
- 正常成交被误判为“超时”或长时间阻塞;
- 依赖 HTTP 轮询,延迟和 API 压力增大。
修复建议:在 WebSocketOrderManager.handle_message 中与 K 线服务保持一致,使用
channel = message.get("channel") or message.get("type", ""),再根据 channel 分支到 _on_order_update / _on_user_fill。
2. 仅 userFills 到达而 orderUpdates 未到达时,无法提前结算(延迟/体验问题)
位置:src/trading/websocket_order_manager.py 第 356-388 行(_on_user_fill)
- 设计上:终态由 orderUpdates 提供(
_ws_status),userFills 只提供成交价与数量;只有在tracking._ws_status is not None时,userFills 才触发_finish(oid)。 - 若因网络/推送顺序等原因 只有 userFills 到达、orderUpdates 始终未到达,则:
- 会正确累计
has_fill_price、avg_price、filled_size; - 但不会调用
_finish(oid),必须等 超时后 HTTP 兜底 才能结算。
- 会正确累计
- 后果:订单实际已成交,但前端/逻辑上要等数分钟(例如 600s)才看到“已成交”,影响体验与后续逻辑;若 HTTP 也失败,会再走重试和强制 TIMEOUT,进一步放大延迟。
修复建议(可选):在 _on_user_fill 中,当累计的 filled_size 达到一定阈值(或与 order 的 totalSz/预期数量一致)且 has_fill_price 为 True 时,可考虑将 _ws_status 置为 FILLED 并触发 _finish(oid),作为“仅 userFills”的兜底路径;需与现有 orderUpdates 语义和 API 文档核对,避免与交易所终态不一致。
三、已排除或低风险点(简要)
- 重复 oid 替换:
track_order中会取消旧_grace_timer并old.result_event.set(),旧追踪不会永久阻塞;_finish_direct的 identity check 防止误操作新 tracking。 - verify_pending_orders 重试:重试时传入的
(oid, tracking)若已被新track_order替换,_finish_direct因 identity check 返回 False,旧 tracking 已在替换时被 set,无永久阻塞。 - 宽限期定时器与替换:旧 tracking 被替换时已 cancel timer;若 timer 已触发,
_finish(oid)取到的是新 tracking(_ws_status为 None),直接 return,不会误结算新单。 - 重连双轮补查:
verify_pending_orders与_monitor_order的 HTTP 路径通过_http_busy互斥,返回 "busy" 时由调用方重试或放弃,逻辑正确。 - wait_for_order 永久阻塞:所有退出路径(WS 的
_finish、HTTP/超时的_finish_direct、替换时的old.result_event.set())都会对对应 tracking 调用result_event.set(),设计上不会永久阻塞。
四、建议修复优先级
| 优先级 | 问题 | 建议 |
|---|---|---|
| P0 | Manager 只认 channel 不认 type |
在 handle_message 中统一使用 channel = message.get("channel") or message.get("type", "") |
| P1 | 仅 userFills 时无法提前结算 | 在 userFills 路径增加“可推断已完全成交”时的兜底 _finish(需与 API 行为对齐) |
完成 P0 后,建议在测试/预发环境用真实或模拟的 {"type": "orderUpdates"} / {"type": "userFills"} 推送验证订单能通过 WS 路径正确结算,并观察是否仍存在“已成交却长时间未结算”的 case,再决定是否实施 P1。