订单跟踪bug14

订单跟踪系统 Bug 分析报告(第二轮)

分析日期: 2026-02-21
涉及文件:

  • src/trading/websocket_order_manager.py
  • src/services/realtime_kline_service_base.py
  • src/utils/websocket/enhanced_ws_manager.py
  • src/trading/executor.py
  • src/events/event_bus.py

BUG 1(致命):重连事件同步调用 HTTP 补查,阻塞 WebSocket 线程

位置: enhanced_ws_manager.py:567-586executor.py:1554-1569

调用链

_on_open(WS线程)
  → EventBus.publish(WebSocketReconnectedEvent)  [同步阻塞]
  → executor._on_websocket_reconnected
  → verify_pending_orders()
  → _resolve_via_http(oid)  [HTTP 调用,每次 5-15s]

问题描述

_on_open 在 WebSocket 线程中运行。EventBus.publish同步调用(event_bus.py:99-107),若存在多个 pending 订单,verify_pending_orders 会在 WS 线程上连续发出 HTTP 请求,阻塞 ws_ready_event.set()(位于 _on_open 最后一行)。

与此同时,_connect 主线程正在等待:

# enhanced_ws_manager.py:540-541
if not self.ws_ready_event.wait(timeout=self.timeout):
    raise TimeoutError(...)

风险

若 HTTP 补查总耗时超过 WS_TIMEOUT_connect 抛出 TimeoutError,触发再次重连,再次触发 _on_open,再次补查,形成死循环。极端情况下服务永远无法成功重连。

复现条件

  • WebSocket 断连时存在 ≥1 个 PENDING 订单
  • HTTP 查询耗时 × 订单数 > WS_TIMEOUT(默认约 30s)

修复思路

_on_open 中将事件 publish 移至锁外,或让 _on_websocket_reconnected 在独立线程中执行 verify_pending_orders,避免阻塞 WS 线程。


BUG 2(致命):wait_for_order 可能在订单实际成交后返回 False

位置: websocket_order_manager.py:124-128_timeout_then_verify:320-383

问题代码

def wait_for_order(self, tracking: OrderTracking) -> bool:
    max_wait = tracking.timeout_seconds + self._HTTP_VERIFY_BUFFER  # +90s
    tracking.result_event.wait(timeout=max_wait)
    return tracking.status == OrderStatus.FILLED

问题描述

_timeout_then_verify 超时后的最长执行路径

等待 timeout_seconds
+ 首次 HTTP 查询耗时(网络慢时可达 30s+)
+ HTTP_RETRY_DELAY(15s 固定等待)
+ 二次 HTTP 查询耗时(网络慢时可达 30s+)

若两次 HTTP 各耗时 45s,额外耗时约 105s,超过 _HTTP_VERIFY_BUFFER = 90s 的缓冲。

wait_for_order 的等待超时先行返回,此时 tracking.status 仍为 PENDING,函数返回 False

风险

调用方收到 False 后认为订单失败,可能执行补偿操作(如重新下单)。而后台 _timeout_then_verify 线程随后通过 HTTP 确认订单已成交并 resolve,导致:

  • 重复开仓:同一方向下了两笔订单
  • 仓位误判:调用方记录订单失败,实际仓位已建立

修复思路

增大 _HTTP_VERIFY_BUFFER(如 180s),或在 wait_for_order 返回后二次确认 tracking.status(重新等待短暂时间),或通过回调而非阻塞等待的方式通知调用方最终结果。


BUG 3(严重):_http_querying 标志误返回 "resolved",可能导致订单永久 PENDING

位置: websocket_order_manager.py:412-414

问题代码

def _resolve_via_http(self, oid: int) -> str:
    with self._lock:
        ...
        if tracking._http_querying:
            return "resolved"  # ❌ 语义错误:查询进行中 ≠ 已解析
        tracking._http_querying = True

危险并发场景

时刻 T1: verify_pending_orders(重连触发)开始查询 oid=X
         → _http_querying = True

时刻 T2: _timeout_then_verify(订单已超时)调用 _resolve_via_http(X)
         → 见到 _http_querying = True
         → 返回 "resolved"
         → 超时线程判断订单已处理,退出

时刻 T3: verify_pending_orders 的 HTTP 查询失败(网络异常)
         → 不解析,返回 "error",verify_pending_orders 忽略返回值

结果: 超时线程已退出,WS 消息不再到来,订单永久 PENDING

影响

  • 订单资金永久占用,无法释放
  • wait_for_order 调用方永远阻塞(直到 max_wait 超时返回 False

修复思路

_http_querying = True 时的返回值改为 "pending"(新增状态),让 _timeout_then_verify 等待一段时间后再重试,而非直接退出。或使用更健壮的锁机制(如 asyncio.Event)等待并发查询完成。


BUG 4(严重):当前订单消息无异常隔离,异常时静默丢失

位置: realtime_kline_service_base.py:619-625

问题代码

if mgr is not None:
    # 缓冲消息有异常隔离 ✅
    while self._order_msg_buffer:
        buffered = self._order_msg_buffer.popleft()
        try:
            mgr.handle_message(buffered)
        except Exception as e:
            self.logger.error(f"回放缓冲订单消息失败: {e}", exc_info=True)

    mgr.handle_message(msg)   # ❌ 当前消息无 try/except 保护

问题描述

缓冲区中的历史消息有逐条 try/except 保护(异常隔离),但当前最新消息 mgr.handle_message(msg) 没有异常保护。

_on_order_update_on_user_fill 因以下原因抛出异常:

  • 消息字段格式异常(API 变更)
  • 内部逻辑错误

异常会传播到 on_message 的外层 except,当前订单消息被日志记录后永久丢失

风险

  • orderUpdates "filled" 消息丢失 → 订单不进入宽限期等待 → 超时后强制 TIMEOUT
  • userFills 消息丢失 → 成交价格无法记录,退化使用兜底价或 HTTP 补查价

修复思路

mgr.handle_message(msg) 包裹与缓冲消息相同的 try/except 异常隔离。


BUG 5(严重):_parse_order_response 未处理 "triggered" 状态

位置: websocket_order_manager.py:471-482

问题代码

@staticmethod
def _parse_order_response(resp: dict | None) -> tuple[OrderStatus | None, float, float]:
    ...
    if raw_status == "filled":
        return OrderStatus.FILLED, avg_px, total_sz
    if raw_status in ("canceled", "margincanceled"):
        return OrderStatus.CANCELED, 0.0, 0.0
    if raw_status == "rejected":
        return OrderStatus.REJECTED, 0.0, 0.0
    if raw_status == "open":
        return OrderStatus.PENDING, 0.0, 0.0
    # ❌ 缺少: "triggered"(条件单触发转市价单)
    logger.warning(f"HTTP 订单状态未识别: raw_status={raw_status!r},视为查询失败")
    return None, 0.0, 0.0

问题描述

Hyperliquid 的止损单/止盈单触发时,订单状态会短暂变为 "triggered",表示条件已满足、正在以市价执行。此状态未被处理,_parse_order_response 返回 (None, 0.0, 0.0),被视为 HTTP 查询失败。

后续影响链

HTTP 返回 "triggered"
→ _parse_order_response 返回 None(视为 error)
→ _timeout_then_verify 触发 15s 重试
→ 二轮查询订单可能已 filled/canceled
   (若二轮仍为 triggered)→ 强制 TIMEOUT
→ tracking 以 TIMEOUT 状态结算,丢失真实成交信息
→ 策略认为止损未触发,可能不更新仓位

修复思路

_parse_order_response 中增加对 "triggered" 状态的处理,视为 PENDING(订单仍在处理中),避免误判为查询失败。


BUG 6(中等):_connect 过早清空 active_subscriptions

位置: enhanced_ws_manager.py:514-517

问题代码

def _connect(self):
    # 主线程执行(连接建立前)
    with self.subscriptions_lock:
        subscriptions_to_use = list(self.subscriptions)
        self.active_subscriptions.clear()   # ← 过早清空!

    # ... WS 线程启动,_on_open 稍后才真正订阅并填充 active_subscriptions

问题描述

active_subscriptions_connect 中被清空,但实际的订阅发送和 active_subscriptions 填充发生在 WS 线程的 _on_open 中。在这段窗口期内,若 add_subscriptions 被并发调用:

  1. 检查 if sub_key in self.active_subscriptions → 空字典,总是 False
  2. 认为是新订阅,添加到 self.subscriptions 列表
  3. _on_open 完成后,active_subscriptionssubscriptions_to_use(旧快照)填充
  4. 窗口期添加的订阅在 active_subscriptions 中不存在
  5. 下次 add_subscriptions 调用时再次被视为新订阅,发送重复订阅请求

风险

服务端收到重复订阅请求(通常返回 subscriptionResponse 或静默忽略,但可能增加服务端状态管理负担),以及 active_subscriptions 状态不准确导致去重失效。

修复思路

_on_open 中(WS 线程)统一管理 active_subscriptions 的清空和填充,_connect 主线程不操作 active_subscriptions,仅传递订阅列表快照。


总结

# 位置 严重程度 核心风险
1 _on_open 同步 HTTP 补查 🔴 致命 重连死循环,服务永远无法恢复
2 wait_for_order 超时竞态 🔴 致命 误判订单失败导致重复开仓
3 _http_querying 误返回 "resolved" 🟠 严重 订单永久 PENDING,资金占用
4 当前订单消息无异常隔离 🟠 严重 订单更新静默丢失
5 缺少 "triggered" 状态处理 🟠 严重 止损/止盈订单以 TIMEOUT 结算
6 过早清空订阅状态 🟡 中等 重复订阅,状态不准确

优先修复建议:BUG 1 和 BUG 2 直接影响系统稳定性和资金安全,应首先处理。

Read more

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG