当前系统订单跟踪存在哪些严重的bug7
订单跟踪系统严重 Bug 分析
基于对 WebSocketOrderManager、HyperliquidExecutor、position_manager、trade_repository 及 WebSocket 消息路由的阅读,整理出当前订单跟踪存在的严重问题与中低风险问题。
一、严重 Bug
1. 超时线程未唤醒主线程(可导致最多 30 秒无意义阻塞)
位置: src/trading/websocket_order_manager.py → _timeout_then_verify
现象:
超时线程在 _resolve_via_http(oid) 返回 False(订单仍在挂单)后,会再次加锁并执行:
tracking = self._tracking.get(oid)
if not tracking or tracking.status != OrderStatus.PENDING:
return # 直接返回,未调用 tracking.result_event.set()
若在这段逻辑执行前,WebSocket 已处理了该订单的 filled 并已从 _tracking 中 pop(oid),则此处的 tracking 为 None,函数直接 return,没有对传入的 tracking 参数调用 result_event.set()。
后果:
- 主线程在
wait_for_order()里阻塞在tracking.result_event.wait(timeout=timeout_seconds + 30)。 - 没有任何人再 set 该 event(WS 已 pop,不会 set;超时线程又直接 return)。
- 主线程只能等 整段 timeout_seconds+30 到期才返回。
- 此时主线程持有的
tracking引用已被 WS 更新为FILLED,结果正确,但多阻塞最多约 30 秒,影响响应与资源占用。
修复建议:
在 if not tracking or tracking.status != OrderStatus.PENDING: return 之前,使用函数参数里的 tracking 引用调用 tracking.result_event.set(),再 return,让主线程立即醒来并根据已更新的 tracking.status 返回。
2. _cache_fill 无效数据被写入,导致成交价/数量错误
位置: src/trading/websocket_order_manager.py → _cache_fill
当前逻辑:
if fill_px <= 0 and fill_sz <= 0:
return
只有同时 fill_px <= 0 且 fill_sz <= 0 才 return。因此:
(0, 1)或(0.0, size)会被写入_fill_prices[oid];- 后续
_on_order_update里filled分支会用fill[0]作为tracking.avg_price,导致成交均价被写成 0; - 若交易所或解析异常出现
(px, 0),也会被缓存,影响filled_size或加权平均逻辑。
后果:
持久化/统计使用错误的成交价或数量,影响对账与风控。
修复建议:
改为“任一无效即不缓存”:
if fill_px <= 0 or fill_sz <= 0:
return
与 _accumulate_fill 的 if fill_px <= 0 or fill_sz <= 0: return 保持一致,避免脏数据进入缓存。
二、中低风险 / 设计权衡
3. orderUpdates 先于 userFills 时真实成交价可能拿不到
位置: wait_for_order 中“用 userFills 补齐真实成交价”的轮询逻辑。
现象:
若先收到 orderUpdates 的 filled,再收到 userFills,主线程只轮询 3 次、每次间隔 0.1s 从 _fill_prices 取数。若 userFills 稍晚到达(网络/顺序),可能轮询结束时仍无数据,最终使用 limitPx 作为成交价。
后果:
成交价可能为挂单价而非实际成交价,影响统计/对账精度,但不影响“是否成交”的结论。
建议:
可适当增加轮询次数或总等待时间,或在文档中明确“可能使用 limitPx 作为成交价”的降级策略。
4. HTTP 订单状态未识别时一律视为 TIMEOUT
位置: _parse_order_response
现象:
仅识别 filled / canceled / margincanceled / rejected / open,其余 raw_status 一律返回 OrderStatus.TIMEOUT。
风险:
若交易所未来返回如 partially_filled 等状态,会被误判为超时,可能过早结束追踪或误报超时。当前若 Hyperliquid 仅返回上述几种状态,则风险较低。
建议:
确认交易所文档中订单状态枚举;若有“部分成交”等状态,显式映射或打日志后再归为 TIMEOUT。
5. 重连后订单消息缓冲与 replay 顺序
位置: realtime_kline_service_base.py 中订单 channel 的缓冲与 handle_message 调用顺序。
现象:
订单管理器未就绪时,orderUpdates / userFills 会进入 _order_msg_buffer;就绪后先 replay 缓冲再处理当前消息。若 replay 中出现“历史会话”的订单更新,当前 _tracking 可能没有对应 oid,会安全跳过。
结论:
当前逻辑下不会因 replay 导致错误状态;仅需注意 replay 期间消息量大时的延迟与顺序依赖(与第 3 点类似)。
三、已确认无问题的点
- verify_pending_orders 与超时线程: 同一 oid 可能被多处做 HTTP 查询,先到者更新并 set event,不会重复 set 或状态错乱。
- 订单保存时机: 仓位/订单持久化在
_track_limit_order返回之后,使用的已是更新后的order_result(含 status/price/size),无“先持久化再追踪”的时序问题。 - oid 类型:
track_order内int(oid),executor 解析 API 时使用int(raw_oid),调用链一致;若未来有其它入口传入非 int 需统一规范。 - 开仓失败时 save_order: 保存的是失败时的 leg_a 状态,用于审计,行为合理。
四、修复优先级建议
| 优先级 | 问题 | 建议 |
|---|---|---|
| P0 | 超时线程未 set event 导致主线程多等 30s | 在 return 前对参数 tracking 调用 result_event.set() |
| P0 | _cache_fill 允许 (0, sz)/(px, 0) 写入 |
条件改为 fill_px <= 0 or fill_sz <= 0 时 return |
| P1 | userFills 晚到时成交价降级为 limitPx | 增加轮询或等待时间,或文档化降级策略 |
| P2 | 未知 HTTP 状态视为 TIMEOUT | 对照交易所文档补充状态映射与日志 |
完成 P0 两项即可消除当前最严重的阻塞与错误成交价问题。