当前系统订单跟踪存在哪些严重的bug10

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

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

  • src/trading/executor.py
  • src/trading/websocket_order_manager.py
  • src/trading/position_manager.py
  • src/utils/websocket/enhanced_ws_manager.py

🔴 严重 Bug(可能造成资金损失)

Bug 1:_publish_orderbook_event L2 订单簿数据完全解析错误

文件: src/utils/websocket/enhanced_ws_manager.py:1364-1365

问题代码:

# 当前代码(错误)
bids=levels[0][1] if len(levels) > 0 and len(levels[0]) > 1 else [],
asks=levels[0][0] if len(levels) > 0 and len(levels[0]) > 0 else [],

Hyperliquid L2Book 实际格式:

{
    "channel": "l2Book",
    "data": {
        "coin": "BTC",
        "levels": [
            [{"px": "50000", "sz": "1", "n": 5}, ...],  // index 0 = bids
            [{"px": "50001", "sz": "0.5", "n": 3}, ...]  // index 1 = asks
        ]
    }
}

正确写法:

bids=levels[0] if len(levels) > 0 else [],   # 全部买单列表
asks=levels[1] if len(levels) > 1 else [],   # 全部卖单列表

现象分析:

  • levels[0][1] → 取的是第二档买单的单个 dict,而非全部 bids 列表
  • levels[0][0] → 取的是最优买单的单个 dict,而非全部 asks 列表

后果: 所有 OrderBookUpdatedEvent 订阅者收到的 bids/asks 数据对调且截断为单档,是完全错误的数据。

注意: 交易关键路径(executor.py 中通过 latest_data 缓存直接读取 raw 消息计算 mid price)走的是独立代码路径,不受此 Bug 影响。受影响范围为所有 OrderBookUpdatedEvent 的下游订阅者。


Bug 2:get_available_balance WS 断连时不回退 HTTP,返回过期缓存

文件: src/trading/executor.py:1839-1844

问题代码:

# get_account_value —— 有断连检测 ✅
if age < self._cache_ttl and self._position_cache_ts > 0:
    if self._is_ws_disconnected():
        logger.debug("WS 断连,跳过账户价值缓存,降级 HTTP 查询")
    else:
        return self._cached_account_value

# get_available_balance —— 缺少断连检测 ❌
if age < self._cache_ttl and self._balance_cache_ts > 0:
    logger.debug(f"✅ 可用余额缓存命中 | balance=${self._cached_available_balance:.2f} | age={age:.1f}s")
    return self._cached_available_balance   # ← WS 断连时仍返回过期数据!

调用链:

limit_open
  └── _precheck_leg_b_conditions
        └── get_available_balance()   ← 返回断连前的旧余额
  └── get_available_balance(force_refresh=True)  ← Leg B 余额复检
        └── 同样缺少断连检测

后果: WS 断连(重连中)期间:

  1. Leg B 余额预检查使用过期余额 → 可能在余额不足时仍通过验证,导致 Leg B 下单失败
  2. Leg B 余额复检使用 force_refresh=True 可绕过缓存,但若缓存命中(TTL 未过期)仍返回旧值
  3. get_account_valueget_available_balance 行为不一致,逻辑上是遗漏

Bug 3:Leg B 定价使用的 all_mids 在 Leg A 追踪开始前预取,价格严重过期

文件: src/trading/executor.py:734, 860-864

问题代码:

def limit_open(self, signal, alt_size, base_size=0.0):
    # ① Leg A 下单"之前"预取价格
    all_mids = self.get_all_mids() if self._config.pair_mode == "pair" and base_size > 0 else {}

    # ② Leg A 限价单追踪(最长可达 limit_order_timeout 秒,默认 600s)
    leg_a_filled = self._track_limit_order(result.leg_a, alt_coin, timeout)

    # ③ 追踪结束后,仍使用 ① 时预取的价格
    base_price = all_mids.get(base_coin, 0.0) if all_mids else 0.0
    if base_price <= 0:                          # 只有缓存返回 0 才重新获取
        base_price = self.get_all_mids().get(base_coin, 0.0)

后果: 在 Leg A 追踪的数分钟内,若 base_coin 价格发生较大波动:

  • Leg B 数量 = leg_a_value / base_price(旧) → 数量计算偏差
  • 实际对冲名义价值与 Leg A 不等,配对比例失衡
  • 市场剧烈波动时(如大行情),偏差可超过 5%-10%

触发频率: 每次 Leg A 未能即时成交(需要等待追踪)的限价开仓均受影响。


🟠 中等 Bug(影响数据准确性)

Bug 4:_on_order_update 无 userFills 时使用 limitPx 替代实际成交价

文件: src/trading/websocket_order_manager.py:188-203

问题代码:

if status_str == "filled":
    fill = self._fill_prices.pop(oid, None)
    if fill and fill[0] > 0:
        # 路径 A: userFills 已到达 → 使用真实成交价 ✅
        tracking.avg_price = fill[0]
        tracking.filled_size = fill[1] if fill[1] > 0 else float(order.get("origSz", 0))
        tracking.has_fill_price = True
    elif tracking.has_fill_price:
        # 路径 B: fill 已在 tracking 中积累 → 跳过(正确)✅
        pass
    else:
        # 路径 C: 无任何 userFills 信息 → 回退到挂单价 ❌
        tracking.avg_price = float(order.get("limitPx", 0))    # 挂单价而非成交价
        tracking.filled_size = float(order.get("origSz", 0))   # 原始委托量

wait_for_order 轮询窗口:

# 仅轮询 5 次 × 0.2s = 最多等待 1 秒
for _ in range(self._FILL_POLL_ROUNDS):   # FILL_POLL_ROUNDS = 5
    fill = self._fill_prices.pop(tracking.oid, None)
    ...
    time.sleep(self._FILL_POLL_INTERVAL)  # FILL_POLL_INTERVAL = 0.2

后果:

  • 若 userFills 推送延迟 > 1 秒,实际成交价永久丢失
  • tracking.avg_price = limitPx(挂单价),若有价格改善则 PnL 计算偏低
  • 此价格传递到 OrderResult.priceposition.alt_entry_price → 影响持仓盈亏记录

Bug 5:_backfill_order_price"0" 字符串的 truthy 陷阱

文件: src/trading/executor.py:681-686

问题代码:

avg_px = resp.get("avgPx") or resp.get("avg_px")
total_sz = resp.get("totalSz") or resp.get("total_sz")
if avg_px:                           # ← "0" 字符串是 truthy!
    order_result.price = float(avg_px)   # → 0.0,价格被错误覆盖
if total_sz:
    order_result.size = float(total_sz)

Python 行为说明:

# 数字 0 是 falsy,字符串 "0" 是 truthy!
bool(0)    # False
bool("0")  # True  ← 陷阱所在

对比: 同项目 websocket_order_manager.py:324 正确处理了此问题:

# 正确写法 ✅
avg_px = float(resp.get("avgPx") or resp.get("avg_px") or 0)

后果: 若 HTTP 订单查询 API 返回 "avgPx": "0"(字符串形式的零),order_result.price 会被设为 0.0,后续基于此价格的 PnL 计算会产生极大偏差。


Bug 6:wait_for_order +30s buffer 可能不足,引发已成交订单被撤单

文件: src/trading/websocket_order_manager.py:102

时序分析:

时间线:
  t=0          track_order() 启动追踪,启动 _timeout_then_verify 线程
  t=timeout    _timeout_then_verify 超时,开始 HTTP 查询(可能含重试)
  t=timeout+30 wait_for_order() 超时返回 False ← 此时 HTTP 查询可能尚未完成
  t=timeout+30 调用方执行 _cancel_order()       ← 撤单请求发出
  t=timeout+35 _resolve_via_http 返回: 订单已成交 ← 但撤单已在途

竞态结果:

  • 若撤单请求先到达交易所 → 已成交的订单被撤掉(交易所拒绝,返回错误)
  • 若成交确认先处理 → 撤单被拒,系统降级查询实际持仓(正确兜底)
  • 极端情况(网络慢 + 重试):两个请求同时在途,结果不确定

根本原因: _resolve_via_http 内部调用 query_order_status,该函数通过 retry_call 可能重试多次,实际耗时可超过 30 秒。


🟡 逻辑缺陷(低频但需关注)

Bug 7:Leg A 回滚失败时产生无风控的隐形持仓

文件: src/trading/executor.py:629-649 + src/trading/position_manager.py

场景:

1. Leg A 成交
2. Leg B 下单失败 → 触发 _rollback_leg_a()
3. _rollback_leg_a() 也失败(网络异常等)
4. 代码依然执行: result.leg_a.success = False
5. _open_position_inner 因 leg_a.success=False 不存储仓位

结果:

  • 交易所:Leg A 仓位真实存在(回滚失败)
  • 系统内存:无该仓位记录
  • 数据库:无该仓位记录
  • 该仓位无止损/超时/移动止损保护

发现时机: 仅在下次 sync_with_exchange 的孤儿检测时发现,此时价格可能已大幅不利变动。Lark 告警会发出,但已错过最佳处置窗口。


汇总表

编号 文件 行号 危险级别 核心影响
Bug 1 enhanced_ws_manager.py 1364-1365 🔴 严重 OrderBookUpdatedEvent bids/asks 数据完全错误
Bug 2 executor.py 1839-1844 🔴 严重 WS 断连期间余额检查使用过期数据
Bug 3 executor.py 734, 860-864 🔴 严重 Leg B 对冲数量因价格过期而失准
Bug 4 websocket_order_manager.py 197-199 🟠 中等 userFills 延迟时成交价丢失,使用挂单价替代
Bug 5 executor.py 681-686 🟠 中等 "0" 字符串 truthy 陷阱,成交价可被覆盖为 0
Bug 6 websocket_order_manager.py 102 🟠 中等 超时 buffer 不足,已成交订单可能被撤单
Bug 7 executor.py / position_manager.py 629+ 🟡 低频 回滚失败后产生无风控隐形持仓

修复优先级建议

优先级 P0(立即修复,影响资金安全):
  → Bug 2: 为 get_available_balance 补充 WS 断连检测
  → Bug 3: Leg B 下单前重新获取实时价格,不使用预取的 all_mids

优先级 P1(尽快修复,影响数据正确性):
  → Bug 1: 修正 _publish_orderbook_event 的 levels 索引
  → Bug 4: 延长 userFills 等待窗口,或在 wait_for_order 后异步补填价格

优先级 P2(计划修复):
  → Bug 5: 参照 _safe_positive_float 重写 _backfill_order_price 的价格解析
  → Bug 6: 增大 buffer 或改用超时事件链式通知
  → Bug 7: 在 _rollback_leg_a 失败时主动写入"孤儿仓位"记录

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