订单跟踪bug19
订单跟踪系统严重 Bug 分析
一、订单跟踪流程概览
sequenceDiagram
participant Exec as Executor
participant Mgr as WebSocketOrderManager
participant WS as WebSocket/K线服务
participant API as HTTP API
Exec->>API: 下限价单
API-->>Exec: 返回 oid
Exec->>Mgr: track_order(oid) + wait_for_order(tracking)
Mgr->>Mgr: _timeout_loop(oid, tracking) 启动
par WS 路径
WS->>Mgr: handle_message(orderUpdates/userFills)
Mgr->>Mgr: _on_order_update / _on_user_fill
Mgr->>Mgr: _finish(oid) -> result_event.set()
else 超时路径
Mgr->>Mgr: result_event.wait(timeout) 超时
Mgr->>API: _http_check(oid)
API-->>Mgr: status
Mgr->>Mgr: _finish_direct(oid, tracking, status) -> result_event.set()
end
Mgr-->>Exec: wait_for_order 返回 True/False
核心代码位置:
- 追踪注册与等待:
src/trading/executor.py中_track_limit_order(约 559–610 行)调用track_order/wait_for_order - 状态机与超时:
src/trading/websocket_order_manager.py中_finish/_finish_direct、_timeout_loop、_http_check - 消息入口:K 线服务
on_message中根据channel路由到mgr.handle_message(msg)(src/services/realtime_kline_service_base.py约 613–627 行)
二、严重 Bug 列表
1. WS "filled" 先于 track_order(oid) 到达导致长时间阻塞(严重)
现象:限价单在交易所已成交,但本地要等接近一整段 timeout_seconds(例如 600 秒)才返回结果。
原因:
HTTP 下限价单返回 oid 后,到执行 track_order(oid) 之间有一段代码(解析响应、赋值 order_result.order_id、再调用 _track_limit_order)。若在这段时间内 WebSocket 已推送 orderUpdates 的 filled(或先到 userFills),则:
- 在
handle_message→_on_order_update/_on_user_fill中会执行tracking = self._tracking.get(oid) - 此时该
oid尚未被注册,tracking为None,逻辑直接continue,这条 "filled" 被永久丢弃 - 随后
track_order(oid)才注册并wait_for_order(tracking) - 之后没有任何路径会再对该订单调用
_finish/_finish_direct(WS 已丢),只有_timeout_loop在result_event.wait(timeout=tracking.timeout_seconds)超时后做 HTTP 验证 - 因此本可即时返回的成交会被拖延最多 timeout_seconds 才由 HTTP 解析并
result_event.set()
影响:资金与仓位在交易所已更新,但策略/上层要等数百秒才得到“已成交”结果,影响体验和后续逻辑,且容易被误认为“系统卡住”。
修复方向:
在 track_order(oid) 内、注册到 _tracking 之后,立即对该 oid 做一次 HTTP 状态查询;若已为 filled/canceled/rejected,直接用 _finish_direct 结算并 result_event.set(),避免依赖“可能已错过”的 WS 消息,同时不改变现有 WS 优先的设计。
2. 订单消息路由依赖 channel,与订阅字段 type 不一致(中高)
现象:若交易所推送的订单相关消息使用 type 而不是 channel 标识类型,则所有 orderUpdates/userFills 都不会被路由到订单管理器,所有限价单都会走“超时 + HTTP”路径。
依据:
- 订阅时使用
"type": "orderUpdates"/"type": "userFills"(src/services/realtime_kline_service_base.py约 444–445 行) - 路由时使用
channel = msg.get("channel", "")且仅当channel in ("orderUpdates", "userFills")才交给mgr.handle_message(约 614–615 行) - 若推送格式为
{"type": "orderUpdates", "data": ...}而无channel,则channel为空,消息不会进入订单分支,可能被当作 K 线等其它逻辑处理或忽略
影响:WS 订单跟踪整体失效,所有订单都需等超时后 HTTP 才结算,等同于 Bug 1 的“全局版”。
修复方向:
- 确认 Hyperliquid 实际推送中订单消息的字段名(
channel还是type) - 路由时同时兼容
msg.get("channel")与msg.get("type"),例如:channel = msg.get("channel") or msg.get("type") or "",再判断是否为orderUpdates/userFills
3. 订单消息缓冲区仅在“新订单消息”到达时回放(中等)
现象:断线或未就绪期间缓存的 orderUpdates/userFills 只有在再次收到 orderUpdates 或 userFills 时才会被回放;若长时间没有新订单推送,已缓存的订单状态更新会一直不处理。
依据:
- 在
on_message中,当mgr is not None时,先while self._order_msg_buffer: ... mgr.handle_message(buffered)再mgr.handle_message(msg)(约 618–625 行) - 若
mgr is None,只做self._order_msg_buffer.append(msg)(约 629 行),没有任何单独线程或定时任务在“仅 K 线消息”到达时回放缓冲区 - 因此只有在“当前这条消息是 orderUpdates/userFills”时才会触发回放;若重连后长时间没有新订单或订单推送,缓冲区里的历史订单更新会延迟到“下一笔订单相关推送”才被处理
影响:断线/重连窗口内的订单状态可能长时间未结算,依赖重连后的 verify_pending_orders 用 HTTP 补查;若 HTTP 也异常或延迟,用户会感觉“订单卡在 PENDING”。
修复方向:
- 在 WebSocket 重连或订单管理器首次就绪时,主动对当前
_order_msg_buffer做一次回放(与现有while self._order_msg_buffer逻辑一致),而不是仅依赖“下一条订单消息” - 和 Executor 侧
WebSocketReconnectedEvent后调用的verify_pending_orders配合,可进一步减少“漏结算”窗口
4. 重连后 verify 与 _timeout_loop 的 HTTP 并发(低~中)
现象:同一 oid 可能同时被“重连后的 verify_pending_orders”和“超时线程的 _timeout_loop”做 HTTP 查询;_http_busy 会令其中一方得到 "busy" 并跳过或重试,可能造成某一轮 verify 对部分订单“未处理”。
依据:
verify_pending_orders遍历 PENDING 并串行调用_http_check(oid),中间time.sleep(1)(约 162–166、172 行)_timeout_loop在超时后也会调用_http_check(oid)(约 406、431 行)_http_check用self._http_busy保证同一 oid 不会并发 HTTP(约 356–358、375 行),另一线程会得到"busy"- verify 得到
"busy"时把该 oid 放入 retry_list,二次重试前再 sleep(3)(约 173–174 行),而 _timeout_loop 会自己 sleep(_HTTP_RETRY_DELAY) 后重试,最终都会由某一路径完成结算,不会“重复结算”,但 verify 的第一轮可能对部分 oid 无效果
影响:逻辑正确性无问题(不会重复 finish),主要是重连后第一轮补查的“覆盖率”可能略低,依赖第二轮或 _timeout_loop 补足;可视为设计上的竞态说明而非致命 bug。
修复方向:
- 保持现状即可;若希望重连后更快收敛,可在 verify 的 retry 逻辑里对
"busy"做短时 sleep 后再重试该 oid,而不是仅依赖固定 3 秒后的第二轮全量重试
三、小结与优先级
| 优先级 | Bug | 影响 | 建议 |
|---|---|---|---|
| P0 | WS filled 先于 track_order 被丢弃 | 单笔订单可能阻塞至 timeout(如 600s)才返回 | 在 track_order 内注册后立即做一次 HTTP 状态查询并可能直接 _finish_direct |
| P1 | 消息路由仅看 channel | 若 API 用 type,则所有 WS 订单跟踪失效 | 路由时兼容 channel 与 type |
| P2 | 缓冲区仅在新订单消息时回放 | 断线/未就绪期间的订单更新可能长时间未处理 | 重连/管理器就绪时主动回放缓冲区 |
| P3 | verify 与 _timeout_loop 的 HTTP 竞态 | 重连首轮补查可能部分 oid 被 busy 跳过 | 可选:对 busy 做短时重试 |
建议优先修复 P0 和 P1(并确认交易所实际推送格式),再视需要处理 P2/P3。