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

订单跟踪严重 Bug 分析

一、订单跟踪数据流概览

sequenceDiagram
    participant Exec as Executor
    participant WSM as WebSocketOrderManager
    participant WS as K线服务 on_message
    participant API as 交易所 HTTP/WS

    Exec->>WSM: track_order(oid, coin, timeout)
    WSM->>WSM: _tracking[oid]=tracking, 启动 _timeout_then_verify 线程
    Exec->>WSM: wait_for_order(tracking) 阻塞
    Note over WS,API: 订单消息来自同一 WebSocket
    API-->>WS: orderUpdates / userFills
    WS->>WSM: handle_message(msg)
    WSM->>WSM: _on_order_update 或 _on_user_fill
    WSM->>WSM: 更新 tracking,_tracking.pop(oid),result_event.set()
    WSM->>Exec: wait 返回,Executor 根据 status 继续
  • 状态来源orderUpdates 决定终态(filled/canceled/rejected),userFills 提供真实成交价(加权累计)。
  • 唤醒:只有 _on_order_update 收到终态或 _resolve_via_http 查询到终态时才会 result_event.set();超时线程在 timeout_seconds 后做一次 HTTP 兜底。

涉及核心文件:


二、严重 Bug 列表

1. 订单消息缓冲区溢出导致订单更新永久丢失(高)

位置realtime_kline_service_base.py 第 193、616–626 行。

现象:当 _get_ws_order_manager() 返回 None 时(例如 orchestrator/executor 未就绪或异常),orderUpdates/userFills 被放入 _order_msg_buffer = deque(maxlen=50)。缓冲区满后,新消息会挤掉最旧的一条(deque 的默认行为),被挤掉的消息不会在任何地方再被处理

后果

  • 若被丢的是某订单的 orderUpdates(如 filled),该订单在 _tracking 中会一直保持 PENDING,直到超时线程做 HTTP 查询;若 HTTP 也失败或尚未执行,会误判为 TIMEOUT。
  • 若被丢的是 userFills,真实成交价可能永远无法写入,只能退化为 limitPx/origSz,影响风控与对账。

触发条件:在订单管理器可用之前,同一连接上已收到超过 50 条订单相关消息(例如重连后历史回放 + 新推送叠加,或启动顺序异常导致 manager 迟迟未就绪)。


2. 成交价缓存驱逐导致错误成交价或真实价丢失(高)

位置websocket_order_manager.py 第 352–368 行 _cache_fill

现象_fill_prices 按“先入先出”保留最多 _MAX_FILL_CACHE(500)条。超出时用 del self._fill_prices[next(iter(self._fill_prices))] 删除插入顺序最早的一条。驱逐策略与“是否仍被追踪”无关,因此可能删掉当前仍在 _tracking 中、尚未收到 orderUpdates filled 的订单的 userFills。

后果

  • 当该订单后来收到 orderUpdatesfilled 时,_on_order_updatefill = self._fill_prices.pop(oid, None) 得到 None,会退化为 limitPx / origSz成交价/成交量不准确
  • orderUpdates 迟迟不到、仅依赖 wait_for_order 内轮询 _fill_prices 取价,被驱逐后轮询永远拿不到该 oid 的 fill,真实成交价永久丢失

触发条件:短时间内大量不同 oid 的 userFills(例如多腿、多币种或高频)导致缓存超过 500 条,且部分订单尚未收到 orderUpdates 终态。


3. 同一 oid 重复 track 导致前一次 wait 永久阻塞(中高)

位置websocket_order_manager.py 第 88–98 行 track_order

现象:对同一 oid 再次调用 track_order 时,会直接 self._tracking[oid] = tracking 覆盖旧条目。旧的 OrderTracking 不再在 _tracking 中,因此:

  • 之后所有 _on_order_update / _on_user_fill / _resolve_via_http 都只更新的 tracking;
  • 旧 tracking 的 result_event 永远不会被 set

后果:若有线程正在 wait_for_order(old_tracking),会永久阻塞。正常单笔限价流一般不会对同一 oid 重复 track,但在重试、重复下单或上层逻辑错误时可能发生。


4. 仅靠 orderUpdates 终态唤醒,WS 丢包或延迟会误判超时(中)

位置:整体设计——result_event.set() 仅在 websocket_order_manager.py_on_order_update(终态)或 _resolve_via_http 中调用;_on_user_fill 从不 set event。

现象:若交易所只推了 userFills(或用户已实际全成),但 orderUpdatesfilled 因网络/交易所延迟或丢包未到达,则:

  • 超时前没有任何路径会 set result_event
  • 超时线程触发后依赖 HTTP 查询;若 HTTP 失败或超时,会将该订单标为 TIMEOUT 并 set event,即使交易所上已成交。

后果:业务层会认为“限价单超时”,可能触发撤单、补单或风控逻辑,与真实成交状态不一致。


5. wait_for_order 在锁外读取 tracking 与 fill 轮询的竞态(低~中)

位置websocket_order_manager.py 第 100–120 行 wait_for_order

现象:设计上“所有 tracking 状态变更在 _lock 内完成”,但 wait_for_orderresult_event.wait() 返回后:

  • 读取 tracking.statustracking.has_fill_price 未持锁
  • 随后在轮询中持锁只对 _fill_prices.pop(tracking.oid, None)

在 CPython 下简单属性读通常安全,但若与 bug 2(fill 被驱逐)叠加:_on_order_update 已把 tracking 置为 FILLED 并 pop 掉 fill,或 fill 已被驱逐,则这里可能拿到错误的 avg_price/filled_size 或拿不到真实价。

后果:主要放大 bug 2 的影响(错误成交价被上层使用),单独看为理论上的可见性/一致性风险。


三、修复建议摘要

Bug 建议方向
1 缓冲区溢出 增大 maxlen 或改为按 oid 去重/按订单维度的有界队列;manager 未就绪时考虑拒绝新下单或明确降级策略,避免静默丢消息。
2 成交价驱逐 驱逐时跳过仍在 _tracking 中的 oid;或按 oid 为键、仅对“已从 _tracking 移除”的 oid 做 LRU/条数上限。
3 重复 track 同一 oid 已存在时:要么返回现有 tracking 并避免覆盖,要么先结束旧 tracking(如置为 CANCELED/REJECTED 并 set event)再写入新 tracking。
4 单一唤醒 在 userFills 累计达到订单规模(若能从上下文或 API 获知)或收到明确“全成”语义时,也可 set result_event;和/或加强超时路径下 HTTP 重试与日志,减少误判 TIMEOUT。
5 锁外读 在 wait_for_order 中,对 status/has_fill_price 的读取和后续 fill 轮询统一在一段持锁区域内完成,或复制出所需字段再释放锁,避免与驱逐/更新的竞态。

四、结论

当前订单跟踪最严重的问题集中在:消息在缓冲区溢出时被静默丢弃成交价缓存驱逐与“是否仍被追踪”脱节、以及同一 oid 重复追踪导致旧 wait 永久阻塞。建议优先修复 1、2、3,再结合业务需求处理 4 和 5,并补充针对“订单管理器未就绪”和“大量 userFills”的单元/集成测试与监控。

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