订单跟踪系统BUG36

订单跟踪系统严重 Bug 因果链分析报告

分析日期:2026-02-23
项目路径:Trading-in-websocket
分析范围:订单跟踪系统(WebSocket 事件、并发控制、数据一致性)
因果链格式:输入 → 状态变化 → 调用路径 → 出错点 → 根因


概览

发现 5 个 CRITICAL/P0 级 + 3 个 HIGH 级 Bug,分布于 EventBus 隔离、死锁、竞态、数据错误等方面。

严重等级汇总

Bug # 标题 严重等级 核心后果
B1 双 EventBus 隔离 P0 / 静默失效 WS 所有订单/成交事件从未被消费
B2 WebSocket 锁内网络 IO CRITICAL / 死锁 消息处理线程完全停止
B3 Grace Timer 竞态 CRITICAL / 二重结算 成交价错误
B4 Fill Price 为 0 被视作有效 CRITICAL / 数据错误 0.0 价格传递至下游
B5 监控线程异常被吞 CRITICAL / 永久阻塞 wait_for_order() 无限等待
B6 Grace Timer 内存泄漏 HIGH 长期运行内存溢出
B7 Fill 去重逻辑缺陷 HIGH 成交量重复累计
B8 撤单后 TOCTOU 竞态 HIGH 成交量/价格数据错误

CRITICAL 级别 Bug


B1:双 EventBus 隔离 — WS 订单事件从未被消费(P0)

文件src/utils/websocket/enhanced_ws_manager.py L306、src/trading/executor.py L89/L146

完整因果链

输入:
  交易所 WebSocket 推送 orderUpdates / userFills 消息

预期状态变化路径:
  WS 消息到达 → 解析 → 发布 OrderStatusEvent / OrderFilledEvent
  → WebSocketOrderManager 接收 → 更新 tracking._ws_status / 累计 fill
  → _resolve() → tracking.result_event.set()

实际调用路径(错误):

  1. EnhancedWebSocketManager.__init__()
     └─ self._event_bus = EventBus()          # 内部新建实例 A

  2. Executor.__init__()
     └─ self._event_bus = EventBus()          # 独立实例 B
     └─ WebSocketOrderManager(event_bus=self._event_bus)   # 订阅实例 B

  3. WS 消息到达
     └─ ws_trading_manager._cache_latest_data()
         └─ _publish_order_status_events()
             └─ self._event_bus.publish(OrderStatusEvent)  # 发布到实例 A

  4. WebSocketOrderManager._on_order_status_event()
     └─ 订阅在实例 B,永远等不到实例 A 的事件

出错点:
  enhanced_ws_manager.py L306:
    self._event_bus = EventBus()   # 每个 WS 实例固定创建新总线,无法注入

根因:
  EnhancedWebSocketManager 不支持 event_bus 参数注入。
  发布端(trading WS)与消费端(WebSocketOrderManager)使用两个
  不同的 EventBus 实例,事件无法跨总线传递,WS 订单追踪路径
  静默失效,系统退化为纯 HTTP 兜底(2s 早期检查 + 600s 超时)。

影响范围

  • orderUpdatesOrderStatusEvent:从未到达 WebSocketOrderManager
  • userFillsOrderFilledEvent:从未到达 WebSocketOrderManager
  • 5s 宽限期逻辑:永不触发
  • 成交均价累计:永不执行(只能用 HTTP 兜底价)
  • PositionUpdatedEvent / BalanceChangedEvent:同样孤立

修复建议

EnhancedWebSocketManager 增加可选参数 event_bus: EventBus | None = None;在创建交易 WS 时传入 executor 的 _event_bus

# enhanced_ws_manager.py
def __init__(self, ..., event_bus: EventBus | None = None):
    self._event_bus = event_bus if event_bus is not None else EventBus()

# realtime_kline_service_base.py(创建 trading WS 处)
self.ws_trading_manager = EnhancedWebSocketManager(
    ...,
    event_bus=self.executor._event_bus   # 注入同一总线
)

B2:WebSocket 锁内做网络 IO — 系统性死锁(CRITICAL)

文件src/utils/websocket/enhanced_ws_manager.py L977–988, L653–675

完整因果链

输入:
  任意触发 add_subscriptions() 的操作(首次连接、重连、动态订阅)
  同时 WebSocket 消息线程正在处理事件

状态变化:
  系统进入两线程互相等待的死锁状态,所有 WS 消息处理停止

调用路径(死锁形成):

  线程 A(业务线程):
    add_subscriptions()
    └─ with self.subscriptions_lock:          # 持有锁 L
        └─ self.ws.send(json.dumps(msg))      # 等待 WS 消息线程 ↓

  线程 B(WebSocket 消息线程):
    _on_message(raw_msg)
    └─ _cache_latest_data()
        └─ 发布 OrderFilledEvent
            └─ executor 处理事件
                └─ 调用 add_subscriptions()
                    └─ with self.subscriptions_lock:  # 等待锁 L ↑
                         → 永远等不到(被线程 A 持有)

出错点:
  enhanced_ws_manager.py L977–988:
    with self.subscriptions_lock:
        ...
        self.ws.send(json.dumps(msg))   # 锁内做阻塞网络 IO

根因:
  锁的粒度过大,"修改订阅列表"(内存操作,应在锁内)和
  "发送订阅消息"(网络 IO,不应在锁内)混在同一个临界区。
  WebSocket 消息线程在等待锁时无法处理任何消息,
  而持锁方需要 WS 消息线程完成 send,形成经典死锁。

影响

  • WebSocket 消息处理完全停止,无任何报错(静默卡死)
  • 所有 OrderStatusEvent / OrderFilledEvent 无法接收
  • 系统实质性崩溃

修复建议

将网络 IO 移至锁外:

def add_subscriptions(self, subscriptions):
    pending_sends = []
    with self.subscriptions_lock:             # 锁内只做内存操作
        for subscription in subscriptions:
            if sub_key in self.active_subscriptions:
                continue
            self.subscriptions.append(subscription)
            pending_sends.append(subscription)

    for subscription in pending_sends:        # 锁外做网络 IO
        msg = {"method": "subscribe", "subscription": subscription}
        self.ws.send(json.dumps(msg))

B3:Grace Timer 竞态 — 订单二重结算(CRITICAL)

文件src/trading/websocket_order_manager.py L329–350, L370–408, L223

完整因果链

输入:
  一笔限价单成交,orderUpdates 先到,userFills 几乎同时到(< 5ms)

状态变化(出错):

  T+0ms:   OrderStatusEvent(status="filled") 到达
           → tracking._ws_status = FILLED
           → tracking.has_fill_price == False
           → 启动 5s Grace Timer(timer 线程 A)

  T+50ms:  OrderFilledEvent(fill_id="tid:xxx") 到达(线程 B)
           → _fill_ids.add("tid:xxx")
           → _accumulate_fill(tracking, px, qty)   ← 第 1 次累计
           → tracking.has_fill_price = True
           → tracking._ws_status != None → should_resolve = True
           → 调用 _resolve(oid, tracking)           ← 线程 B 执行

  T+5000ms: Grace Timer 触发(线程 A)
           → 调用 _resolve(oid, tracking)           ← 线程 A 执行
           → _resolve() 内部无幂等保护,二次进入成功

调用路径:
  _on_order_status_event() [L329–350]
    └─ timer = threading.Timer(5s, self._resolve, [oid, tracking])
        └─ timer.start()                 ← timer 线程 A 启动

  _on_order_filled_event() [L370–408]   ← 与 timer 并行运行
    └─ _accumulate_fill()
    └─ _resolve(oid, tracking)           ← 线程 B 同时调用

出错点:
  websocket_order_manager.py L329–350 启动 timer
  websocket_order_manager.py L403–408 并发调用 _resolve()
  _resolve() 内部缺少幂等性检查(未检查 status != PENDING 时提前返回)

根因:
  _resolve() 不是幂等的。虽有 tracking identity check(验证 tracking
  是否被替换),但未检查 tracking.status 是否已完成。两个线程同时
  通过 identity check,都执行状态写入和 fill 累计,导致 avg_price /
  filled_size 被重复计算。

影响

  • 成交均价错误(被重复累加)
  • 下游仓位 PnL 计算基于错误数据
  • 结算事件触发两次

修复建议

在 Grace Timer 的回调中增加状态检查:

def _on_grace_timeout(self, oid: int, tracking: OrderTracking):
    with self._lock:
        if tracking.status != OrderStatus.PENDING:
            return   # 已被正常路径结算,跳过
    self._resolve(oid, tracking)

B4:Fill Price 为 0 被视作有效 — 成交价 0.0 传递至下游(CRITICAL)

文件src/trading/websocket_order_manager.py L257–277、src/trading/executor.py L597–600

完整因果链

输入:
  一笔订单通过 HTTP 兜底路径结算(B1 导致 WS 路径失效后的常态)
  HTTP 查询超时或返回 avgPx=0,fallback_px 同样为 0

状态变化(错误路径):

  _resolve(oid, tracking, status=FILLED, px=0.0, sz=X)
    └─ 进入 HTTP/超时模式(status is not None)
    └─ tracking.has_fill_price == False
    └─ if px > 0:                      # False,跳过 avg_price 赋值
    └─ if tracking.avg_price <= 0:
           tracking.avg_price = tracking._fallback_px   # fallback 也是 0
    └─ tracking.has_fill_price = True  # ← 无条件设为 True(BUG 位置 L270)

调用路径:
  _resolve() [websocket_order_manager.py L257–277]
    └─ tracking.has_fill_price = True   # L270,不检查 avg_price > 0

  executor.py [L597–600]
    └─ if not tracking.has_fill_price:
           self._backfill_order_price(...)   # 被错误跳过!
       else:
           self._verify_fill_completeness(...)  # 基于 0.0 价格执行

出错点:
  websocket_order_manager.py L270:
    tracking.has_fill_price = True   # 无条件,不检查 avg_price > 0

根因:
  has_fill_price 的语义应为"已有有效非零成交价",但实现为
  "进入过成交价赋值分支"。当 px=0 且 fallback_px=0 时,
  avg_price 仍为 0.0,但 has_fill_price 被标记为 True,
  阻止了下游 HTTP 回填(_backfill_order_price)。

触发条件(与 B1 叠加必然触发):
  B1 导致 WS 路径失效 → 退化为 HTTP 兜底
  HTTP 查询超时 → px=0, fallback_px=0
  → has_fill_price=True 但 avg_price=0.0
  → executor 计算仓位成本 = 0.0

影响

  • 仓位开仓成本记录为 0.0
  • PnL 计算严重偏差(虚假盈利)
  • 风险管理决策基于错误数据

修复建议

设置 has_fill_price=True 前,校验 avg_price > 0

# websocket_order_manager.py L270 附近
if tracking.avg_price > 0:
    tracking.has_fill_price = True
# 否则保持 has_fill_price=False,允许后续 HTTP 回填

B5:监控线程异常被吞 — 订单永久卡在 PENDING(CRITICAL)

文件src/trading/websocket_order_manager.py L427–434

完整因果链

输入:
  _monitor_order_inner() 执行期间发生任何未预期异常
  (HTTP 超时、网络错误、数据解析失败等)

状态变化:
  tracking.status 停留在 PENDING
  tracking.result_event 永不被 set()
  调用 wait_for_order() 的线程永久阻塞

调用路径(当前"修复"后仍存在的缺陷):

  _monitor_order() [L427–434]
    try:
        _monitor_order_inner(oid, tracking)
    except Exception as e:
        logger.error(...)
        self._resolve(oid, tracking, OrderStatus.TIMEOUT)   # 若此处也抛异常

  当 _resolve() 自身异常时:
    └─ 异常被外层 try/except Exception 吞掉
    └─ tracking.result_event.set() 从未被调用
    └─ wait_for_order() 无限阻塞

出错点:
  websocket_order_manager.py L434:
    self._resolve(oid, tracking, OrderStatus.TIMEOUT)
    # 无嵌套 try/except,_resolve 异常直接传播到外层被吞

根因:
  错误恢复路径(exception handler 内的 _resolve 调用)本身没有异常保护。
  若 _resolve() 因任何原因抛出异常,该异常被外层 except 捕获并记录,
  但 result_event 永不被释放,导致上层调用永久阻塞。

影响

  • 订单追踪线程永久挂起(未释放)
  • wait_for_order() 线程永久阻塞
  • 系统逐渐累积僵尸线程,最终线程池耗尽
  • 持仓开启/关闭操作被阻塞,无法响应市场

修复建议

def _monitor_order(self, oid: int, tracking: OrderTracking):
    try:
        self._monitor_order_inner(oid, tracking)
    except Exception as e:
        logger.error(f"监控线程异常: {tracking.coin} oid={oid} | {e}", exc_info=True)
        try:
            self._resolve(oid, tracking, OrderStatus.TIMEOUT)
        except Exception as resolve_err:
            logger.critical(
                f"_resolve 失败,强制释放 result_event: oid={oid} | {resolve_err}"
            )
            tracking.result_event.set()   # 确保 wait_for_order() 不永久阻塞

HIGH 级别 Bug


B6:Grace Timer 内存泄漏(HIGH)

文件src/trading/websocket_order_manager.py L249–251, L343–350

完整因果链

输入:
  高频交易 / WebSocket 重连导致同一 oid 被多次 track_order()

状态变化:
  Timer 对象积累,GC 无法及时回收(daemon 线程持有引用)

调用路径:

  track_order(oid, ...) 第 1 次
    └─ timer_1 = threading.Timer(5s, _resolve, [oid, tracking_1])
    └─ tracking_1._grace_timer = timer_1
    └─ timer_1.start()

  track_order(oid, ...) 第 2 次(重连/重试)
    └─ old._grace_timer.cancel()       ← timer_1 被取消(标志位设置)
    └─ _tracking[oid] = tracking_2
    └─ timer_2 启动

  timer_1 虽被 cancel(),其底层线程仍需等待超时才真正结束。
  高频场景下,大量 cancel-but-not-joined 的定时器线程积累。

出错点:
  websocket_order_manager.py L343–350:
    timer = threading.Timer(...)
    timer.start()   # cancel() 只设置标志位,不中断线程

根因:
  Python threading.Timer.cancel() 不终止线程,只设置标志位令回调不执行。
  线程本身仍在 sleep,持有对象引用直到超时到期。高频交易时积累大量
  后台 Timer 线程,导致内存和线程描述符泄漏。

修复建议

将宽限期逻辑改为使用 threading.Event 控制的单后台线程,或使用 concurrent.futures 的延迟任务,可被真正取消。


B7:Fill 去重逻辑缺陷 — 成交量重复累计(HIGH)

文件src/trading/websocket_order_manager.py L110–115、src/utils/websocket/enhanced_ws_manager.py L1436–1464

完整因果链

输入:
  WebSocket 重连后,交易所重新推送历史 userFills 事件
  或同一 oid 被多次 track_order()(重连后的常见场景)

状态变化:
  新 tracking 对象的 _fill_ids 为空集
  历史 fill 被重新处理,累计至新 tracking 的 avg_price / filled_size

调用路径:

  WS 重连
    └─ verify_pending_orders()
        └─ track_order(oid, ...)        ← 创建 tracking_new,_fill_ids={}

  WS 重新推送历史 fills
    └─ _on_order_filled_event(event)
        └─ fill_id = "tid:12345"
        └─ if fill_id in tracking_new._fill_ids:   # False(空集)
               return
        └─ tracking_new._fill_ids.add("tid:12345")
        └─ _accumulate_fill(...)        ← 重复累计!

出错点:
  websocket_order_manager.py L110–115:
    self._tracking[oid] = tracking     # 新 tracking._fill_ids 为空集
    # 旧 tracking 的 _fill_ids 未被继承

根因:
  track_order 的重复追踪逻辑仅 cancel 了旧 timer,
  未迁移 _fill_ids 去重集合。重连后旧 fill 被重新投递,
  新 tracking 将其视为首次到达,导致均价和成交量双重错误。

修复建议

# websocket_order_manager.py L110–115 附近
if old:
    tracking._fill_ids = old._fill_ids.copy()   # 继承去重集合
    self._tracking[oid] = tracking

B8:撤单后 TOCTOU 竞态 — 成交量/价格数据错误(HIGH)

文件src/trading/executor.py L769–804, L916–930

完整因果链

输入:
  部分成交后触发撤单(限价单部分成交后超时撤单)

状态变化:
  撤单请求发出 → 在途 fill 落点在"撤单"和"查询"之间 → fill 被遗漏
  或查询失败 → 降级返回 price=0.0

调用路径:

  executor.py L916–930
    self._cancel_order(alt_coin, order_id)          # 步骤 1:撤单
    # ← 此时间窗口(约 10–50ms)内新 fill 可能到达
    actual_filled, fill_px = self._check_order_after_cancel(
        alt_coin, order_id, alt_is_buy              # 步骤 2:查询
    )

  _check_order_after_cancel() L769–804
    except Exception:
        actual = self._get_actual_position_size(...)
        return actual, 0.0            # ← 降级返回 0.0

出错点:
  executor.py L799–804:
    return actual, 0.0   # 降级路径返回价格 0.0

根因:
  撤单(HTTP)和查询(HTTP)是两次独立的网络请求,之间存在时间窗口。
  交易所在此窗口内处理的 fill 无法被单次查询捕获(TOCTOU)。
  降级路径使用仓位差值估算数量但硬编码价格 0.0,
  触发上层错误的价格回填流程,并引入不一致的仓位数据。

修复建议

降级路径应返回 None 而非 0.0,让上层明确触发 HTTP 价格回填:

except Exception:
    actual = self._get_actual_position_size(...)
    return actual, None   # 明确告知上层价格未知,触发回填

Bug 叠加效应分析

B1(EventBus 隔离)
  ↓ WS 路径失效,所有订单退化为 HTTP 兜底
  ↓
  ├─→ B4(Fill Price 为 0)
  │     HTTP 超时时 px=0,has_fill_price 错误标记,跳过回填
  │     → 成交价 0.0 传递到仓位系统 → PnL 计算基于错误开仓价
  │
  └─→ B5(监控线程异常被吞)
        HTTP 查询失败 → _resolve 异常 → result_event 永不释放
        → wait_for_order() 永久阻塞 → 持仓操作停止响应

B2(WebSocket 死锁)
  ↓ 触发后系统完全丧失 WS 能力
  ↓ 叠加 B1,此时 HTTP 兜底也因线程阻塞而失效
  ↓
  系统完全失去订单追踪能力(双重失效)

修复优先级与关键文件索引

立即处理(P0 / CRITICAL)

顺序 Bug 关键文件 关键行 修复要点
1 B2 WebSocket 死锁 enhanced_ws_manager.py L977–988 ws.send() 移至锁外
2 B1 EventBus 隔离 enhanced_ws_manager.py L306 支持 event_bus 参数注入
B1 EventBus 隔离 realtime_kline_service_base.py 创建 trading WS 处 传入 executor._event_bus
3 B3 Grace Timer 竞态 websocket_order_manager.py L329–350, L223 _resolve() 检查 status != PENDING
4 B4 Fill Price 为 0 websocket_order_manager.py L270, L277 设置 has_fill_price 前校验 avg_price > 0
5 B5 监控线程异常 websocket_order_manager.py L427–434 嵌套 try/except 保护 _resolve,兜底 result_event.set()

短期处理(HIGH)

顺序 Bug 关键文件 关键行 修复要点
6 B6 Timer 内存泄漏 websocket_order_manager.py L343–350 改用可真正取消的延迟机制
7 B7 Fill 去重缺陷 websocket_order_manager.py L110–115 重复 track_order 时继承 old._fill_ids
8 B8 TOCTOU 竞态 executor.py L799–804, L916–930 降级路径返回 None 而非 0.0

验证方法

Bug 验证方式
B1 注入同一 event_bus 后,打印 _on_order_status_event 被调用次数,应与 WS 推送次数一致
B2 add_subscriptions 调用期间并发推送 WS 消息,超时内未死锁即通过
B3 模拟 orderUpdatesuserFills 同时到达,确认 _resolve 只执行一次(fill_count == 1
B4 Mock HTTP 返回 px=0,确认 has_fill_price == False_backfill_order_price 被调用
B5 _resolve 内注入异常,确认 result_eventset()wait_for_order() 正常返回
B7 重连后推送旧 fill_id,确认 _fill_ids 去重生效,filled_size 不变

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