订单跟踪严重 Bug 分析1
订单跟踪严重 Bug 分析
本文档汇总当前系统订单跟踪存在的严重与重要 bug,及修复优先级建议。
1. 最严重:双 EventBus 隔离,订单事件永远收不到(核心功能静默失效)
现象:OrderStatusEvent、OrderFilledEvent 由交易 WS 发布,但 WebSocketOrderManager 订阅的是另一条总线,导致所有通过 WebSocket 的订单状态与成交事件均未被消费。
根因:
- Executor 在
src/trading/executor.py中创建自己的self._event_bus = EventBus(),并传给WebSocketOrderManager(executor=self, event_bus=self._event_bus)(约 L89、L146)。 - 交易 WS 在
src/services/realtime_kline_service_base.py中创建EnhancedWebSocketManager时未注入任何 event_bus;EnhancedWebSocketManager(src/utils/websocket/enhanced_ws_manager.py)在 L306 内部固定self._event_bus = EventBus(),即每个 WS 实例一条独立总线。
因此:
- 发布端:
ws_trading_manager._cache_latest_data()→_publish_order_status_events()/_publish_fill_events()→self._event_bus.publish(...)(发布到 ws_trading_manager 的 EventBus)。 - 消费端:
WebSocketOrderManager订阅的是 executor._event_bus。 - 两条总线为不同实例,事件不会跨总线传递,故 WebSocketOrderManager 从未收到任何 OrderStatusEvent / OrderFilledEvent。
实际行为:订单追踪只能依赖
- 2s 早期 HTTP 检查,或
- 超时后的 HTTP 兜底,
而 orderUpdates / userFills 的实时路径完全不起作用(无状态更新、无 fill 累计、无宽限期逻辑),等价于「WS 订单追踪静默失效」。
影响范围:除订单追踪外,同一交易 WS 发布的 PositionUpdatedEvent、BalanceChangedEvent、WebSocketReconnectedEvent 若仅由 Executor 通过 executor._event_bus 订阅,则同样收不到;需一并确认并修复 EventBus 统一注入。
修复方向:
- 为
EnhancedWebSocketManager增加可选参数event_bus: EventBus | None = None;若传入则使用该实例,否则内部EventBus()(兼容仅行情 WS 的场景)。 - 在创建交易 WS 时,将 executor 的
_event_bus注入到EnhancedWebSocketManager(需在 realtime 服务 / orchestrator 中拿到 executor 的 event_bus 并传入),保证 orderUpdates/userFills/user 相关事件与 WebSocketOrderManager、Executor 订阅端在同一总线上。
2. 重要:空 fill_id 导致去重失效、重复累计(潜在)
位置:src/trading/websocket_order_manager.py 中 _on_order_filled_event(约 L384–386)。
逻辑:使用 event.fill_id in tracking._fill_ids 去重,再 tracking._fill_ids.add(event.fill_id)。若某条 OrderFilledEvent 的 fill_id == "",则多条此类事件会被视为同一条(都命中同一个空字符串),导致只累计第一笔、后续重复 fill 被误去重,从而少算成交量、均价偏差。
当前发布端:_publish_fill_events(src/utils/websocket/enhanced_ws_manager.py)仅在 tid is not None 时发布,且 fill_id=f"tid:{tid}",故当前从 userFills 来的事件不会出现空 fill_id。但若将来有其他来源发布 OrderFilledEvent(如 user 频道或测试/回放)且未设 fill_id,就会触发该 bug。
建议:在 _on_order_filled_event 中若 not event.fill_id,则使用备用 key(例如 f"oid:{oid}:px:{event.filled_price}:sz:{event.filled_qty}" 或带时间戳)或直接拒绝该条事件并打日志,避免空 fill_id 参与去重导致少计 fill。
3. 重要:文档/历史 BUG 与当前实现的一致性(供修复时对照)
以下来自 docs/WebSocket数据结构校验问题报告.md,当前代码已部分修复,但值得在修 EventBus 时一并核对:
- BUG-1(userFills 解析失效):报告针对的是旧版
websocket_order_manager.handle_message/_on_user_fill把data当 list 迭代、拿不到oid的问题。当前已改为 EventBus 路径,且_publish_fill_events使用data.get("fills", [])和order_id=str(oid)、fill_id=f"tid:{tid}",发布端已正确;问题在于事件未送到 WebSocketOrderManager(见第 1 条)。 - BUG-2(orderUpdates 拒绝状态):当前
_on_order_status_event已用"rejected" in status_str覆盖minTradeNtlRejected等,已修复。 - BUG-3(order_id 类型):发布端已统一
order_id=str(oid),已修复。 - BUG-5(fill_id):发布端已有一等字段
fill_id=f"tid:{tid}",已满足;消费端需防空 fill_id(见第 2 条)。
4. 中等:重连后「仅 PENDING 且 _ws_status is None」的订单才被 HTTP 补查
位置:verify_pending_orders(src/trading/websocket_order_manager.py)中 pending 筛选条件(约 L139–142)。
逻辑:只对 status == PENDING and _ws_status is None 的订单做 HTTP 补查。若某单在断线前已收到 orderUpdates 的 "filled"(即 _ws_status = FILLED),但尚未收到 userFills,则处于宽限期、不会被 verify_pending_orders 选中,只能等 5s 宽限期结束后用 fallback 价解析。在「断线期间已完全成交」的场景下,会多等最多 5s 才用兜底价结算,不阻塞但略影响时效与价格来源。在修复第 1 条并保证事件可达后,此类情况会减少;若需更稳妥,可考虑在重连后对「PENDING 且 _ws_status is not None 且尚未 resolve」的订单也做一次 HTTP 补查(或缩短宽限期)。
5. 小结与修复优先级
| 优先级 | 问题 | 影响 | 修复要点 |
|---|---|---|---|
| P0 | 双 EventBus 隔离 | WS 订单/成交事件从未被消费,订单追踪完全依赖 HTTP | 交易 WS 使用 executor 的 EventBus(EnhancedWebSocketManager 支持注入) |
| P1 | 空 fill_id 去重 | 若他处发布无 fill_id 的 OrderFilledEvent,会少计 fill | 消费端对空 fill_id 做备用 key 或拒绝并打日志 |
| P2 | 重连补查范围 | 宽限期内的订单不参与 verify_pending_orders,多等最多 5s | 可选:扩展补查条件或缩短宽限期 |
建议优先实现 P0:在 realtime 服务创建交易 WS 时注入 executor 的 _event_bus,并让 EnhancedWebSocketManager 支持可选 event_bus 参数;随后再在 WebSocketOrderManager 侧对空 fill_id 做防护(P1)。