订单跟踪系统bug分析24

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

分析日期: 2026-02-21
分析范围: websocket_order_manager.py, executor.py, position_manager.py, realtime_kline_service_base.py


Bug 1 [严重] userFills 重复累计 → 成交价/数量错误

位置: websocket_order_manager.py:559-569 _accumulate_fill

问题: _accumulate_fill 没有任何 fill 级别的去重机制。它简单地将每笔 fill 的 px * sz 累加计算加权平均价。在以下场景会出错:

  • WS 重连后交易所重播消息:如果 MessageDeduplicator 的 TTL 已过期,同一笔 fill 会被重复计入
  • 缓冲区回放时序问题_order_msg_buffer 中的 userFills 消息在回放时,如果新的 WS 连接同时发来了相同的消息

后果: filled_size 膨胀(如实际成交 1.0 变成 2.0),avg_price 虽然可能不变(同价),但后续 position_manager 使用错误的 filled_size 会导致仓位记录不准确,PnL 计算出错。

# 当前代码 - 无 fill 去重
@staticmethod
def _accumulate_fill(tracking, fill_px, fill_sz):
    old_sz = tracking.filled_size
    new_sz = old_sz + fill_sz  # 重复 fill 直接累加!
    ...

建议: 在 OrderTracking 中增加已处理的 fill ID 集合(cloidtid),避免重复累计。


Bug 2 [严重] WS 重连期间丢失的 userFills 导致成交价不完整

位置: websocket_order_manager.py:356-388 + executor.py:584-592

问题: 订单可能有多笔部分成交(如 3 笔 partial fills),WS 重连期间可能只收到其中 1-2 笔。_accumulate_fill 基于收到的 fills 计算加权平均价,但不验证累计量是否等于实际总成交量。

时间线:

  1. 订单在交易所分 3 笔成交($100 × 0.5 + $101 × 0.3 + $99 × 0.2)
  2. WS 收到第 1 笔 fill → avg_price = $100, size = 0.5
  3. WS 断连
  4. 第 2、3 笔 fill 丢失
  5. WS 重连,orderUpdates "filled" 到达
  6. _finish 被调用:has_fill_price = True(因为收到了第 1 笔),使用不完整的 avg_price

后果: _track_limit_ordertracking.has_fill_price = True → 跳过 HTTP _backfill_order_price → 最终使用的成交价只基于部分 fills,PnL 计算不准确。

# executor.py:591 - has_fill_price=True 时跳过 HTTP 回填
if not tracking.has_fill_price:
    self._backfill_order_price(order_result, coin)  # 被跳过!

建议: 在 _finish/_finish_direct 解析时比较 tracking.filled_size_fallback_sz(来自 orderUpdates 的 totalSz),如果差异超过阈值,强制触发 HTTP 回填。


Bug 3 [严重] 限价单超时撤单的 TOCTOU 竞态窗口

位置: executor.py:819-851 (limit_open 超时处理)

问题: 超时 → 撤单 → 查询持仓,这三步之间存在时间窗口:

t0: track_order 返回 TIMEOUT
t1: _cancel_order(oid)           ← 撤单请求发出
t2: 交易所处理撤单(可能失败)
t3: _get_actual_position_size()  ← 查询持仓
    ...
t4: 交易所在 t3 之后才完成订单匹配  ← 竞态!

场景: 撤单和查询之间,订单在交易所端刚好成交。_get_actual_position_size 返回 0(撤单前的缓存或查询时延),代码判断"无成交"返回失败。但实际上订单已成交,产生了一个无人管理的孤儿仓位

缓解因素: sync_with_exchange 定期扫描会最终发现孤儿仓位,但有时间窗口(取决于同步频率),这段时间仓位无止损保护。

建议: 撤单后增加短暂延迟(如 500ms),或增加二次确认查询。


Bug 4 [高] 部分成交无最小阈值 → 微型仓位

位置: executor.py:830-843

问题: 限价单超时后,接受任意数量的部分成交,注释明确写着"无成交率限制":

if actual_filled > 0:
    # 🆕 有部分成交 - 无条件接受
    result.leg_a.success = True

如果只成交了 0.01%(如 $100 仓位只成交了 $0.01),Leg B 会按比例计算出同样微小的数量。最终创建的配对仓位:

  • 名义价值极小,手续费占比极高
  • 可能低于交易所最小订单量限制(round_size 截断为 0 → Leg B 失败 → 回滚)
  • 即使不回滚,止损/平仓操作也可能因金额过小而失败

建议: 增加最小成交率阈值(如 10%),低于阈值视为未成交。


Bug 5 [高] _order_msg_buffer 无大小限制 → 内存泄漏

位置: realtime_kline_service_base.py:193 + 640

问题:

self._order_msg_buffer: deque = deque()  # 无 maxlen!

如果 _get_ws_order_manager() 始终返回 None(交易模块初始化失败),每条 orderUpdates/userFills 消息都会被追加到缓冲区,永不释放。代码在 100/1000 条时输出警告/错误日志,但不做截断或丢弃

长期运行下,这会导致内存持续增长。

建议: 使用 deque(maxlen=1000) 或在超过阈值时主动清理。


Bug 6 [中] _finish 中 fallback 价格可能为 0

位置: websocket_order_manager.py:225-230

问题: 当 orderUpdatesavgPxlimitPx 都为空/0 时,_fallback_px 为 0。如果此时 userFills 也未到达(has_fill_price = False),订单会以 avg_price = 0 完成解析:

if tracking.status == OrderStatus.FILLED and not tracking.has_fill_price:
    tracking.avg_price = tracking._fallback_px  # 可能为 0!
    tracking.filled_size = tracking._fallback_sz  # 可能为 0!

随后 _track_limit_order 检查:

if tracking.avg_price > 0:
    order_result.price = tracking.avg_price  # 跳过!

order_result.price 保持为限价(_place_limit_order 中设置的)。但如果实际成交价与限价偏差较大,PnL 计算会不准确。


Bug 7 [中] verify_pending_orders 对同一 oid 的并发 HTTP 查询

位置: websocket_order_manager.py:149-184 + executor.py:1631-1641

问题: _on_websocket_reconnected 启动双轮验证线程:

def _double_verify():
    self._ws_order_manager.verify_pending_orders()
    time.sleep(5)
    self._ws_order_manager.verify_pending_orders()

同时,每个订单的 _monitor_order 线程也可能在执行 _http_check。虽然 _http_busy set 提供了并发保护,但 verify_pending_orders 遍历所有 pending 订单并逐个调用 _http_check,每次间隔 1s。如果某个 oid 在第一轮验证时返回 "busy"(monitor 线程正在查询),它会加入 retry_list。3s 后重试时 monitor 线程可能已完成查询并解析了该订单,此时 _http_check 返回 "resolved"。这不会造成错误,但增加了不必要的 HTTP 调用压力。


已修复的 Bug(本次 diff 中)

Bug 位置 问题 修复
_finish_direct 覆盖 WS 真实价格 websocket_order_manager.py:258 HTTP px 无条件覆盖 userFills 价格 增加 if not tracking.has_fill_price 检查
_get_actual_position_size 用缓存数据 executor.py:665 撤单后用缓存仓位量(可能过期) 增加 force_refresh 参数
_close_ops 类型注解不匹配 position_manager.py:1134 7 元素注解,实际 8 元素 修正为 8 元素
closed_pairs 重复添加 position_manager.py:1263 base_leg_lost 平仓后重复追加 增加 if lost_key not in closed_pairs
_order_msg_buffer 迭代竞态 realtime_kline_service_base.py:621 while buffer + popleft 非原子 改为 try/except IndexError
leg_b_error 引用不存在的属性 executor.py:948 result.leg_b_error 不存在 → AttributeError 先保存 leg_b_error 再置 None

优先修复建议

  1. [紧急] Bug 2 - 增加 filled_sizetotalSz 的一致性校验,不一致时强制 HTTP 回填
  2. [紧急] Bug 1 - 在 _accumulate_fill 中增加 fill 级别去重(按 trade ID)
  3. [高] Bug 4 - 增加最小成交率阈值
  4. [高] Bug 5 - 给 _order_msg_buffer 加上 maxlen
  5. [中] Bug 3 - 撤单后增加延迟或二次确认

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