订单跟踪系统BUG34

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

分析日期:2026-02-22
项目路径:Trading-in-websocket
分析范围:订单跟踪系统(OrderFilledEvent、WebSocket消息处理、并发控制、数据一致性)


概览

发现 7 个严重 Bug3 个设计问题,涉及竞态条件、死锁、内存泄漏、数据一致性等方面。

严重等级汇总

Bug # 标题 严重等级 核心影响
BUG #1 Grace Timer 竞态条件 CRITICAL 订单二重结算、金额错误
BUG #2 Timer 内存泄漏 HIGH 内存溢出、系统崩溃
BUG #3 Fill Price 为 0 被视作有效 CRITICAL 成交价 0.0 传递至下游
BUG #4 监控线程异常被吞掉 CRITICAL 订单永久卡在 PENDING
BUG #5 Fill 去重逻辑缺陷 HIGH 成交量重复累计
BUG #6 撤单后的 TOCTOU 竞态 HIGH 成交量/价格数据错误
BUG #7 WebSocket 锁内做网络 IO CRITICAL 消息处理完全停止(死锁)

CRITICAL 级别 Bug

BUG #1:Grace Timer 竞态条件 — 订单二重结算

文件src/trading/websocket_order_manager.py
行号:329–350, 370–408

问题描述

OrderStatusEvent(orderUpdates)中 status="filled" 先到达,但对应的 OrderFilledEvent(userFills)尚未到达时,代码会启动一个 5 秒的宽限期定时器。

# 第 329–350 行
if status_str == "filled":
    tracking._ws_status = OrderStatus.FILLED
    tracking._fallback_px = event.price
    tracking._fallback_sz = event.size
    if tracking.has_fill_price:
        should_resolve = True
    else:
        if tracking._grace_timer is None:
            timer = threading.Timer(
                self._FILL_GRACE_SEC, self._resolve,  # 宽限期 5 秒
                [oid, tracking]
            )
            timer.daemon = True
            tracking._grace_timer = timer
            timer.start()

竞态场景

  1. 宽限期定时器启动后,OrderFilledEvent 立即到达
  2. _on_order_filled_event 在第 403–408 行设置 should_resolve = True,调用 _resolve()
  3. 宽限期定时器同时在后台触发 _resolve(oid, tracking)
  4. 两个线程同时调用 _resolve(),导致二重结算

影响

  • 成交价被重复累计
  • 结算事件被触发两次
  • 下游业务收到错误的成交数据

修复建议

_resolve() 中增加幂等性保护,或在定时器触发前检查 tracking.status 是否已经完成。

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

BUG #3:Fill Price 为 0 被视作有效 — 成交价 0.0 传递至下游

文件src/trading/websocket_order_manager.py
行号:257–277

问题描述

订单标记为 FILLED 时,代码会尝试填充价格信息,但逻辑存在缺陷:

# 第 257–277 行
if final_status == OrderStatus.FILLED:
    if status is not None:  # HTTP/超时模式
        if not tracking.has_fill_price:
            if px > 0:
                tracking.avg_price = px
                tracking.filled_size = sz if sz > 0 else tracking.filled_size
            # ⚠️ BUG:即使 px=0,has_fill_price 仍被设为 True
            if tracking.avg_price <= 0:
                tracking.avg_price = tracking._fallback_px
            tracking.has_fill_price = True  # 第 270 行
    else:  # WS 模式
        if not tracking.has_fill_price:
            tracking.avg_price = tracking._fallback_px
            tracking.filled_size = tracking._fallback_sz
            tracking.has_fill_price = True  # 第 277 行

问题路径

px=0_fallback_px=0 时:

  1. avg_price 保持 0.0
  2. has_fill_price 被设置为 True
  3. executor.py 第 597–600 行检查 has_fill_price,认为已有价格,跳过 _backfill_order_price()
  4. 成交价为 0.0 的订单被传递给下游业务
# executor.py 第 597–600 行
if not tracking.has_fill_price:
    self._backfill_order_price(order_result, coin)  # 被错误跳过!
else:
    self._verify_fill_completeness(order_result, tracking, coin)

影响

  • 成本计算、风险评估等下游模块收到错误的 0.0 价格
  • 损失 HTTP 查询回填的机会

修复建议

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

if tracking.avg_price > 0:
    tracking.has_fill_price = True
# 否则保持 has_fill_price=False,允许后续回填

BUG #4:监控线程异常被吞掉 — 订单永久卡在 PENDING

文件src/trading/websocket_order_manager.py
行号:427–434

问题描述

虽然代码注释标注"Bug 4 修复",但该修复本身不完整:

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)
        self._resolve(oid, tracking, OrderStatus.TIMEOUT)  # 第 434 行

问题点

  1. _resolve() 自身抛出异常,该异常被外层 try/except 吞掉,订单状态停留在 PENDING
  2. _monitor_order_inner 中任何 HTTP 查询异常都会触发 TIMEOUT 标记(语义错误)
  3. wait_for_order() 因此无限等待,线程永远不会释放

影响

  • 订单在 PENDING 状态永久挂起
  • 调用 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 失败,强制设置结果: oid={oid} | {resolve_err}")
            tracking._result.set_exception(resolve_err)  # 确保 Future 被释放

BUG #7:WebSocket 订阅锁内做网络 IO — 系统性死锁

文件src/utils/websocket/enhanced_ws_manager.py
行号:977–988, 653–675

问题描述

add_subscriptions() 方法在持有 subscriptions_lock 期间调用 ws.send()(网络 IO):

# 第 977–988 行
with self.subscriptions_lock:  # 持有锁期间...
    if sub_key in self.active_subscriptions:
        skipped_count += 1
        continue
    self.subscriptions.append(subscription)
    if self.state == ConnectionState.CONNECTED and self._is_connected():
        msg = {"method": "subscribe", "subscription": subscription}
        self.ws.send(json.dumps(msg))  # ⚠️ 在锁内做网络 IO!

死锁路径

WebSocket 消息线程
  └─ _on_message() 被调用
       └─ 发布 OrderFilledEvent
            └─ executor.py 处理事件
                 └─ 调用 add_subscriptions()
                      └─ 等待 subscriptions_lock  ← 被阻塞

add_subscriptions() 调用线程
  └─ 持有 subscriptions_lock
       └─ 调用 ws.send() ← 等待 WebSocket 消息线程

两个线程互相等待,产生系统性死锁,后续所有 WebSocket 消息无法处理。

影响

  • WebSocket 消息处理完全停止
  • 所有订单事件(OrderFilledEvent、OrderStatusEvent)无法接收
  • 系统实质性崩溃

修复建议

将网络 IO 移到锁外执行:

def add_subscriptions(self, subscriptions):
    pending_sends = []
    with self.subscriptions_lock:
        for subscription in subscriptions:
            sub_key = ...
            if sub_key in self.active_subscriptions:
                continue
            self.subscriptions.append(subscription)
            pending_sends.append(subscription)

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

HIGH 级别 Bug

BUG #2:Grace Timer 内存泄漏

文件src/trading/websocket_order_manager.py
行号:249–251, 343–350

问题描述

定时器在多个场景下无法被正确清理:

  • 场景一:OrderFilledEvent 在定时器触发前 0.1 秒到达,定时器继续在后台运行
  • 场景二track_order() 被重复调用时,旧 tracking 的定时器被 cancel,但新 tracking 的定时器若随后又被替换,则无法被 cancel
# 第 117–118 行:旧 tracking 的定时器被取消
if old._grace_timer:
    old._grace_timer.cancel()

# 第 343–350 行:新 tracking 的定时器如果再次被替换,则不会被取消
timer = threading.Timer(self._FILL_GRACE_SEC, self._resolve, [oid, tracking])
timer.daemon = True
tracking._grace_timer = timer
timer.start()

影响

  • 高频交易场景下大量定时器积累,导致内存泄漏
  • 野生定时器可能访问已被清理的对象

BUG #5:Fill 去重逻辑缺陷 — 成交量重复累计

文件src/utils/websocket/enhanced_ws_manager.py(1436–1464)
文件src/trading/websocket_order_manager.py(389–392)

问题描述

去重基于 fill_id,但重复追踪同一订单时,新 tracking 对象的 _fill_ids 为空:

# websocket_order_manager.py 第 110–115 行:重复追踪
old = self._tracking.get(oid)
if old:
    logger.warning(f"重复追踪 oid={oid}...")
    old.status = OrderStatus.CANCELED
    # ⚠️ 旧 tracking 的 _fill_ids 不被继承给新 tracking!
    self._tracking[oid] = tracking  # 新 tracking._fill_ids 为空集

影响

  • WebSocket 重连时,旧 fills 被新 tracking 重新处理
  • 成交价和成交量被重复累计,数据严重错误

修复建议

重复追踪时,将旧 tracking 的 _fill_ids 迁移至新 tracking:

if old:
    tracking._fill_ids = old._fill_ids.copy()  # 继承去重集合
    self._tracking[oid] = tracking

BUG #6:撤单后的 TOCTOU 竞态 — 成交量/价格数据错误

文件src/trading/executor.py
行号:769–804, 916–930

问题描述

撤单后查询订单状态存在时间窗口:

# 第 916–930 行
if result.leg_a.order_id:
    self._cancel_order(alt_coin, result.leg_a.order_id)  # 步骤 1:撤单

# ⚠️ 此时可能有新 fill 在途
actual_filled, fill_px = self._check_order_after_cancel(  # 步骤 2:查询
    alt_coin, result.leg_a.order_id, alt_is_buy
)

查询失败时的降级逻辑返回价格 0.0

# 第 799–804 行
except Exception as e:
    logger.warning(f"撤单后订单查询失败,降级到仓位查询: ...")
    actual = self._get_actual_position_size(...)
    return actual, 0.0  # ⚠️ 返回价格 0.0!

影响

  • 撤单和查询之间的新 fill 可能被遗漏或重复计算
  • 降级场景下价格为 0.0,触发上层的回填逻辑,但成本已增加

设计问题

问题 D:EventBus 同步分发中的并发修改风险

文件src/events/event_bus.py
行号:89–107

EventBus 在锁外调用 handler,若 handler 内部触发取消订阅,存在并发修改 _subscribers 列表的风险。虽然复制了列表规避了 ConcurrentModificationException,但逻辑一致性无法保证。


问题 E:微仓位回滚时机过早

文件src/trading/executor.py
行号:933–947

成交率 < 5% 时触发回滚,但此时 orderUpdates 事件可能尚未全部到达,导致:

  • 基于不完整数据做出错误的回滚决策
  • 回滚使用市价,与成交价偏差较大,增加实际损耗

问题 F:本地缓存从未被事件更新

文件src/trading/executor.py
行号:91–100

_cached_positions_cached_account_value 等缓存字段在 __init__ 中初始化,但未订阅对应的 PositionUpdatedEvent 等事件,导致:

  • 缓存始终为初始空值
  • 持仓查询返回过期数据,引发错误的风险管理决策

修复优先级

立即处理(生产环境)

  1. BUG #7 — WebSocket 死锁(enhanced_ws_manager.py:977):锁内网络 IO 导致系统级死锁
  2. BUG #1 — Grace Timer 竞态(websocket_order_manager.py:329):二重结算导致财务数据错误
  3. BUG #3 — Fill Price 为 0(websocket_order_manager.py:270):错误成交价传递至下游
  4. BUG #4 — 超时异常被吞(websocket_order_manager.py:434):订单永久挂起

短期处理

  1. BUG #2 — 内存泄漏(websocket_order_manager.py:343):影响长期运行稳定性
  2. BUG #5 — Fill 去重缺陷(enhanced_ws_manager.py:1436):高频交易时触发
  3. BUG #6 — TOCTOU 竞态(executor.py:916):边界情况下数据错误

规划处理

  1. 问题 D — EventBus 并发修改风险
  2. 问题 E — 微仓位回滚时机
  3. 问题 F — 缓存一致性

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