当前系统订单跟踪存在哪些严重的bug12
订单跟踪系统严重 Bug 分析报告
分析时间: 2026-02-21
涉及文件:
src/trading/websocket_order_manager.pysrc/trading/executor.pysrc/services/realtime_kline_service_base.pysrc/utils/websocket/enhanced_ws_manager.py
Bug 1 — 致命: HTTP 验证网络错误导致订单误判为 TIMEOUT
位置: src/trading/websocket_order_manager.py:342-367
问题代码
# _resolve_via_http
try:
resp = self._executor.query_order_status(oid)
except Exception as e:
logger.error(f"HTTP 查询订单状态异常: oid={oid} | {e}")
resp = None # ← 网络异常时 resp = None
status, avg_px, total_sz = self._parse_order_response(resp)
# _parse_order_response(None) 直接返回 OrderStatus.TIMEOUT !
if status == OrderStatus.PENDING:
return False # ← 不会走到这里,因为 status == TIMEOUT
# 结果: 将仍在挂单的订单强制标记为 TIMEOUT 并 resolve
_parse_order_response 第 376-377 行:
def _parse_order_response(resp: dict | None) -> tuple[OrderStatus, float, float]:
if not resp or not isinstance(resp, dict):
return OrderStatus.TIMEOUT, 0.0, 0.0 # ← 将网络故障等同于 TIMEOUT,逻辑错误
触发路径
- 订单 WS 消息超时(
timeout_seconds内未收到orderUpdatesfilled) _timeout_then_verify调用_resolve_via_http- HTTP 请求因网络抖动抛出异常
resp = None→_parse_order_response(None)返回TIMEOUT- 订单被强制 resolve 为 TIMEOUT,从
_tracking移除 wait_for_order返回False,交易系统认为订单未成交
后果
- 交易所实际已成交的订单在系统中被标记为"失败"
- Leg A 成交但系统未感知 → 不执行 Leg B → 对冲缺失,单腿裸仓
- 若系统因"未成交"重新下单 → 重复开仓,资金翻倍暴露
- 持仓追踪数据与交易所真实持仓不一致
建议修复方向
网络异常时应返回 PENDING(保持等待),而非 TIMEOUT:
if not resp or not isinstance(resp, dict):
# 网络错误:无法判断订单状态,保守处理为仍在挂单
return OrderStatus.PENDING, 0.0, 0.0
Bug 2 — 严重: 订单缓冲区消息在异常时永久丢失
位置: src/services/realtime_kline_service_base.py:619-633
问题代码
if mgr is not None:
while self._order_msg_buffer:
buffered = self._order_msg_buffer.popleft() # ← 已从队列弹出,无法回滚
mgr.handle_message(buffered) # ← 若此处抛出异常
mgr.handle_message(msg)
# ...
except Exception as e:
self.logger.error(f"消息处理失败: {e}", exc_info=True)
# ← 当前消息已丢失,且 buffer 中剩余所有消息也全部跳过
触发路径
WebSocketOrderManager初始化期间,部分orderUpdates/userFills消息被存入_order_msg_buffer- 管理器就绪后,
_on_message回调开始回放缓冲区 handle_message对某条缓冲消息的处理逻辑抛出异常(如字段格式异常、NoneType 等)- 外层
except Exception捕获,循环中断 - 剩余缓冲消息全部丢失(已从 deque 弹出,无法恢复)
后果
- 丢失的消息可能包含订单成交通知(
orderUpdates filled/userFills) - 订单 tracking 永久停留在 PENDING 状态
- 等待
timeout_seconds后触发 HTTP 兜底验证,延迟可达数分钟 - 若 HTTP 验证也失败(Bug 1 场景),成交订单彻底丢失
建议修复方向
对每条缓冲消息独立 try-except,异常时记录日志但继续处理下一条:
while self._order_msg_buffer:
buffered = self._order_msg_buffer.popleft()
try:
mgr.handle_message(buffered)
except Exception as e:
logger.error(f"回放缓冲订单消息失败: {e}", exc_info=True)
# 继续处理剩余消息
Bug 3 — 严重: _fallback_sz 错误使用 origSz(原始下单量)
位置: src/trading/websocket_order_manager.py:239-242
问题代码
if status_str == "filled":
tracking._terminal_status = OrderStatus.FILLED
tracking._fallback_px = _safe_positive_float(order.get("limitPx"))
tracking._fallback_sz = _safe_positive_float(order.get("origSz")) # ← BUG: origSz 是原始下单量
字段含义对比
| 字段 | 含义 | 正确用途 |
|---|---|---|
origSz |
订单创建时的原始请求数量 | 显示下单大小 |
totalSz |
实际累计成交数量 | 应作为 filled_size |
limitPx |
挂单价格 | 可作为 avg_price 兜底(近似) |
avgPx |
实际成交均价 | 应优先作为 avg_price |
触发场景
userFills 未在宽限期(5 秒)内到达,系统使用 _fallback_sz 作为成交数量。
若订单部分成交(常见于流动性不足时),origSz > 实际成交量。
后果
示例: 下单 100 BTC,实际仅成交 60 BTC
_fallback_sz = origSz = 100 BTC ← 错误
leg_a_value = 100 * price ← 基于错误数量
leg_b_size = leg_a_value / base_price ← Leg B 超额开仓 40 BTC
- Leg B 市价单数量偏大,实际对冲比超过预期
- 账户暴露于超额风险敞口
- 若账户余额不足,Leg B 下单失败并触发回滚,但 Leg A 实际持仓仍在
建议修复方向
tracking._fallback_px = _safe_positive_float(order.get("avgPx") or order.get("limitPx"))
tracking._fallback_sz = _safe_positive_float(order.get("totalSz") or order.get("origSz"))
Bug 4 — 严重: wait_for_order 固定超时窗口可能在 HTTP 验证完成前提前返回
位置: src/trading/websocket_order_manager.py:83-84, 114-118
问题代码
_HTTP_VERIFY_BUFFER = 60 # 固定 60 秒缓冲(硬编码)
def wait_for_order(self, tracking: OrderTracking) -> bool:
max_wait = tracking.timeout_seconds + self._HTTP_VERIFY_BUFFER
tracking.result_event.wait(timeout=max_wait) # ← 最多等 timeout + 60s
return tracking.status == OrderStatus.FILLED
HTTP 验证实际耗时估算
query_order_status 内部调用 retry_call,默认配置:
- 最多重试 3 次
- 最大退避 15 秒
- 总耗时上限 ≈ 15 + 15 + 15 = 45 秒(极端情况)
加上 _timeout_then_verify 触发延迟,完整流程:
timeout_seconds (等待WS)
+ 网络往返延迟
+ retry_call 重试耗时(最坏 ~45s)
+ _resolve 处理时间
若重试耗时超过 60 秒,wait_for_order 先返回 False,随后 HTTP 验证完成并将 tracking.status 改为 FILLED,但调用方已按"失败"处理(可能已撤单或回滚)。
后果
- 调用方在
wait_for_order返回False后执行_cancel_order+_get_actual_position_size - 与此同时 HTTP 验证在后台将订单解析为成交
- 两条路径并发操作同一订单,状态不一致
建议修复方向
将缓冲时间设置为动态值,或增大固定缓冲:
_HTTP_VERIFY_BUFFER = 120 # 至少 2 倍最坏重试时间
或在 wait_for_order 返回后,读取最终状态前增加短暂等待校验。
Bug 5 — 中等: 健康检查日志的 modulo 条件失效
位置: src/utils/websocket/enhanced_ws_manager.py:1253
问题代码
# 每次循环后 sleep(WS_HEALTH_CHECK_INTERVAL)
if int(time.time() - self.start_time) % WS_HEALTH_REPORT_INTERVAL == 0:
self._log_health_report()
问题分析
time.sleep()精度不保证整秒(实际可能多睡或少睡若干毫秒)int(time.time() - self.start_time)取整后,连续两次循环可能落在同一整数秒- 若
WS_HEALTH_REPORT_INTERVAL与WS_HEALTH_CHECK_INTERVAL不整除,条件可能数小时都不触发
后果
- 健康监控日志可能从不输出(可观测性缺失)
- 或在某个整秒内输出多次(日志噪声)
建议修复方向
使用上次报告时间戳:
_last_health_report = 0.0
# 在循环内:
now = time.time()
if now - self._last_health_report >= WS_HEALTH_REPORT_INTERVAL:
self._log_health_report()
self._last_health_report = now
优先级汇总
| # | 严重程度 | 直接影响 | 位置 |
|---|---|---|---|
| 1 | 🔴 致命 | 成交订单被误判为超时,可能导致重复开仓或单腿裸仓 | websocket_order_manager.py:344-367 |
| 2 | 🔴 严重 | 缓冲订单消息异常时全部丢失,订单追踪失效 | realtime_kline_service_base.py:619 |
| 3 | 🟠 严重 | 部分成交时 filled_size 错误,Leg B 超额建仓 | websocket_order_manager.py:242 |
| 4 | 🟠 严重 | HTTP 验证可能在 wait_for_order 返回后才完成 | websocket_order_manager.py:116 |
| 5 | 🟡 中等 | 健康报告日志 modulo 条件失效,可观测性降低 | enhanced_ws_manager.py:1253 |
修复优先级建议
立即修复(影响资金安全):
- Bug 1: 网络异常 → PENDING 而非 TIMEOUT
- Bug 3:
origSz→totalSz,limitPx→avgPx
本周修复(影响系统可靠性):
- Bug 2: 缓冲区回放独立异常处理
- Bug 4: 增大 HTTP 验证缓冲时间
下次迭代:
- Bug 5: 健康报告使用时间戳而非 modulo