订单跟踪BUG21
订单跟踪系统严重 Bug 分析
分析时间: 2026-02-21
涉及文件:realtime_kline_service_base.py,websocket_order_manager.py,executor.py
一、订单跟踪链路概览
flowchart LR
subgraph place [下单]
Place[place_order]
Track[track_order]
Wait[wait_for_order]
end
subgraph ws [WebSocket]
WS[on_message]
Buf[_order_msg_buffer]
Mgr[handle_message]
end
subgraph resolve [结算]
Finish[_finish / _finish_direct]
HTTP[_http_check]
end
Place --> Track --> Wait
WS --> Buf
Buf --> Mgr
Mgr --> Finish
Track --> Monitor[_monitor_order]
Monitor --> HTTP
HTTP --> Finish
- WS 路径:
orderUpdates/userFills→realtime_kline_service_base.py路由 →_get_ws_order_manager().handle_message()→websocket_order_manager.py内_on_order_update/_on_user_fill→_finish()。 - HTTP/超时路径:
_monitor_order早期检查(2s) + 超时后_http_check→_finish_direct()。 - 撤单后:executor 在限价单超时/撤单后调用
_get_actual_position_size()判断部分成交与残余持仓。
以下仅列出严重或与订单跟踪直接相关的问题。
二、严重 Bug 列表
1. 缓冲区回放竞态 + 异常导致订单消息永久丢失(高)
位置:src/services/realtime_kline_service_base.py 约 618–626 行。
问题 1(竞态)
_order_msg_buffer 为无锁 deque。多线程下(如 WS 回调线程)可能同时:
- 线程 A/B 都通过
if self._order_msg_buffer进入while self._order_msg_buffer; - 一个线程
popleft()后另一线程再popleft()可能对空 deque 抛IndexError,导致回调崩溃、后续订单消息处理中断。
问题 2(异常丢消息)
当前逻辑是先 popleft() 再 handle_message(buffered)。若 handle_message(buffered) 抛异常,该条订单消息已被移出缓冲区且仅打日志,不会重试也不会再入队,对应订单的 WS 状态更新会永久丢失,可能造成该单一直处于 PENDING、最终依赖超时/HTTP 才结算,或价格/数量依赖 HTTP 而非更准确的 userFills。
已有记录:竞态部分见 订单跟踪bug2.md Bug 2。
修复方向:
- 用锁保护“判空 + popleft + 处理”整段,或使用“先 popleft 再处理、异常时重新放回队首/重试”策略,避免单次异常导致一条消息永久丢失;
- 空 deque 的
popleft()用 try/except 或“加锁后再次判空再 popleft”防止IndexError。
2. HTTP 结算覆盖已存在的 userFills 数据(中高)
位置:src/trading/websocket_order_manager.py _finish_direct() 约 257–263 行。
if status == OrderStatus.FILLED:
if px > 0:
tracking.avg_price = px
tracking.has_fill_price = True
if sz > 0:
tracking.filled_size = sz
问题:
当订单已通过 userFills 累计了更精确的加权均价和 filled_size 时,tracking.has_fill_price 已为 True,但 HTTP 路径(早期检查或超时后 _http_check)若查到 filled 并调用 _finish_direct(oid, tracking, status, avg_px, total_sz),仍会无条件用 HTTP 返回的 avg_px/total_sz 覆盖 tracking.avg_price 和 tracking.filled_size。
结果是:WS 已提供的更准确成交价/数量被 HTTP 单次查询结果覆盖,下游(如 executor 的 order_result.price / order_result.size)拿到的是 HTTP 数据而非 userFills 汇总,影响统计与风控精度。
修复方向:
仅在 not tracking.has_fill_price 或 not tracking.filled_size 等情况下用 HTTP 的 px/sz 回填,否则保留已有 userFills 数据。
3. 限价单超时/撤单后用缓存持仓判断部分成交与残余(高,与订单跟踪结果处理直接相关)
位置:src/trading/executor.py 限价单超时分支及平仓撤单后:
- 约 664–676 行
_get_actual_position_size()内部使用get_positions(),未强制刷新; - 约 826 行(limit_open 超时后)、1023 / 1064 行(limit_close 撤单后)在判断部分成交、残余持仓时都依赖该缓存。
问题:
撤单或超时后,交易所状态已变,但 get_positions() 可能仍返回 5s TTL 内的旧缓存。
- 缓存显示有持仓而实际已撤单清零 → 可能误建仓位或误判部分成交;
- 缓存显示无持仓而实际有部分成交/残留 → 漏记部分成交或留下无人管理的残余仓位。
影响:与“订单跟踪结果”的后续处理直接相关,可能造成资金/仓位错误。
已有记录:订单跟踪bug2.md Bug 5、Bug 6。
修复方向:
为 _get_actual_position_size 增加 force_refresh 参数(或等效方式),在限价单超时撤单/平仓撤单后调用时传入 force_refresh=True,使用实时持仓数据。
4. 重连补查与 monitor 并发下的“busy”与重试(中)
位置:src/trading/websocket_order_manager.py verify_pending_orders() 与 _http_check() 的 _http_busy 逻辑。
问题:
重连后 verify_pending_orders() 对每个 PENDING 订单调用 _http_check(oid);若同一 oid 的 _monitor_order 也在做早期检查或超时 HTTP,会命中 oid in self._http_busy 返回 "busy"。
- verify 侧:该单被加入 retry_list,约 3 秒后重试,逻辑可接受;
- monitor 侧:得到 "busy" 后进入 15 秒 sleep 再二轮 HTTP,可能拉长该单解析时间,在极端情况下与“强制 TIMEOUT”的节奏叠加,影响体验和日志清晰度。
当前设计不会导致重复结算(identity check),但并发控制可考虑更明确(例如同一 oid 串行化 HTTP 或共享结果),以减少重试与延迟。
三、与订单跟踪相关的已记录问题(简要)
- Bug 8(低):订单跟踪bug2.md — 早期 HTTP 与 WS 的竞态窗口;identity check 已避免重复结算,主要影响价格精度,可与上述“HTTP 覆盖 userFills”一并考虑优化。
- Bug 1(中高):
executor.py中leg_b=None后错误信息丢失,影响订单失败诊断,与订单结果展示相关。
四、修复优先级建议
| 优先级 | 问题 | 影响 |
|---|---|---|
| P0 | 缓冲区回放:竞态 + 异常丢消息 | 订单消息丢失、回调崩溃、状态长期依赖 HTTP/超时 |
| P0 | 撤单/超时后持仓查询使用缓存(Bug 5/6) | 仓位误判、资金风险 |
| P1 | HTTP 覆盖 userFills 成交价/量 | 成交价与数量不准确,影响统计与风控 |
| P2 | 重连补查与 monitor 并发 busy/重试 | 解析延迟与日志噪音 |
建议先修复 P0(缓冲区线程安全 + 异常不丢单;撤单后强制刷新持仓),再处理 P1(保留 userFills、仅在缺失时用 HTTP 回填)。