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

订单跟踪严重 Bug 分析报告

1. 架构与数据流概览

订单跟踪涉及三处核心代码:

sequenceDiagram
  participant Exec as Executor
  participant WSM as WebSocketOrderManager
  participant WS as WebSocket/K线服务
  Exec->>WSM: track_order(oid, coin, timeout)
  WSM->>WSM: _timeout_then_verify 后台线程
  Exec->>WSM: wait_for_order(tracking)
  WS->>WSM: handle_message(orderUpdates/userFills)
  WSM->>WSM: _resolve(oid)
  WSM-->>Exec: result_event.set() -> 返回 FILLED/其他

2. 严重 Bug 列表

Bug 1:超时线程与 WS “filled” 的竞态 —— 已成交被误判为 TIMEOUT(高严重)

位置websocket_order_manager.py_timeout_then_verify(约 317–329 行)。

现象:HTTP 查询返回订单仍为 open 后,在“设置 _terminal_status = OrderStatus.TIMEOUT”与“调用 _resolve(oid)”之间存在时间窗。若此时 WebSocket 的 orderUpdates “filled” 到达,会先把同一 tracking 的 _terminal_status 设为 FILLED;若随后超时线程先进入 _resolve(),则读到的是自己刚写的 TIMEOUT(或已被 WS 改成 FILLED,取决于谁后写、谁先进 _resolve)。
更糟糕的场景:超时线程先执行完 with self._lock: current._terminal_status = TIMEOUT 并释放锁,然后再调用 _resolve(oid)。在这段“无锁”间隙内,WS 线程可能尚未被调度,超时线程接着在 _resolve() 里看到 _terminal_status == TIMEOUT,从而把实际已成交的订单结算为 TIMEOUT。下游 Executor 会认为“未成交”,可能触发撤单、部分成交分支或错误通知。

根因:在“HTTP 确认为 open → 强制超时”路径中,从设 TIMEOUT 到真正解析之间没有再次用 HTTP 确认是否已成交,竞态窗口内 WS 可能晚到或尚未被处理。

修复方向:在设置 _terminal_status = OrderStatus.TIMEOUT 之后、调用 _resolve(oid) 之前,再做一次 HTTP query_order_status(oid)。若结果为 filled/canceled/rejected,则按该终态更新 _terminal_status 及价格/数量后再 _resolve(oid);仅当第二次 HTTP 仍为 open 时才按 TIMEOUT 解析。这样可大幅缩小“误判为超时”的窗口。


Bug 2:订单管理器未就绪时消息只缓冲不丢弃,可能无限堆积(中高严重)

位置realtime_kline_service_base.py 约 614–633 行。

现象:当 _get_ws_order_manager() 返回 None(例如交易未启用或 Orchestrator 启动失败/未就绪)时,orderUpdates/userFills 会被放入 _order_msg_buffer(无界 deque)。仅当 buf_size > 200 时打 ERROR 日志,不会丢弃消息。若订单管理器长期不可用(如配置错误或启动失败),缓冲区会持续增长,占用内存且重连/恢复后回放顺序与真实时间可能不符,影响状态一致性。

根因:缓冲区无上限、无淘汰策略,且“未就绪”场景未区分“暂时未就绪”与“永久不可用”。

修复方向

  • _order_msg_buffer 设置最大长度(例如 500),超出时丢弃最旧或最新消息并打告警;和/或
  • 若检测到“订单管理器永久不可用”(例如交易已禁用),则不再缓冲、直接丢弃并打一次告警,避免无界增长。

Bug 3:重连后补查与超时线程的重复/竞态(中严重)

位置websocket_order_manager.pyverify_pending_orders()(约 127–141 行)与 _timeout_then_verify / _resolve_via_http

现象:WebSocket 重连后,Executor 收到 WebSocketReconnectedEvent 并调用 verify_pending_orders(),对当前所有 PENDING 的 oid 依次调用 _resolve_via_http(oid)。此时某些订单可能正由 _timeout_then_verify 在同一时刻进行 HTTP 验证或即将调用 _resolve()。两处可能对同一 oid 重复 HTTP 查询并都可能调用 _resolve(oid)。现有实现依赖 _resolve() 的幂等(第二次 get(oid) 为 None 即 return),因此不会“双写”状态,但会产生重复 HTTP 请求,并在高并发下放大“Bug 1”的竞态窗口(例如一个线程刚 HTTP 得到 open 并准备设 TIMEOUT,另一线程 HTTP 得到 filled 并 resolve,顺序不确定)。

修复方向

  • _resolve_via_httpverify_pending_orders 中,对“正在被超时线程验证”的 oid 做简单规避(例如用 set 记录“正在 HTTP 验证的 oid”,超时线程和重连补查共用一个锁或标记,避免对同一 oid 并发 HTTP);和/或
  • 与 Bug 1 的修复结合:统一“最后一步前再查一次 HTTP”的策略,减少误判 TIMEOUT 的同时也减少重复解析。

Bug 4:重复追踪同一 oid 时旧追踪被 CANCELED,调用方仍可能用旧 tracking 判断(中严重)

位置websocket_order_manager.py 约 95–111 行;executor.py 约 581–584 行。

现象track_order(oid, ...) 若发现 _tracking 中已有同一 oid,会把旧 tracking 的 status 置为 CANCELED、cancel 其 _fill_timer 并 result_event.set(),然后放入新 tracking。若之前有调用方仍持有OrderTracking 引用并在 wait_for_order(old_tracking) 中阻塞,会被正确唤醒,且 old_tracking.status == OrderStatus.CANCELED,所以会返回 False,逻辑正确。
但若调用方在“重复追踪”发生前就拿到了 tracking,随后另一线程(或同一策略的另一次下单)对同一 oid 再次调用 track_order,则旧 tracking 被置为 CANCELED。若此时旧调用方尚未调用 wait_for_order(例如先做别的事再 wait),则会在 wait_for_order 里等到 event 被 set,得到 False。若业务上把“同一 oid 的第二次追踪”视为错误或未考虑,可能误以为“第一次下的单被取消”。
更严重的是:若交易所复用 oid(极少但理论上存在),或业务层误对同一 oid 调用两次 track_order,第二次会覆盖第一次的追踪,第一次的 wait_for_order 会因 CANCELED 而返回 False,可能被解释为“订单取消”而非“被新追踪替代”。

修复方向

  • track_order 中,对“重复 oid”打更明确的日志(例如 WARNING 并注明“旧追踪被终止,新追踪已注册”),便于区分“交易所取消”与“重复追踪覆盖”;
  • 若业务上不允许同一 oid 被重复追踪,可在 Executor 侧保证“同一 oid 只会被当前流程唯一 track”,避免重复调用。

Bug 5:宽限期 5 秒内只依赖 userFills,若 API 不推送或延迟则用挂单价(中低严重)

位置websocket_order_manager.py 约 246–253 行;_resolve() 中 184–186 行。

现象:orderUpdates 收到 “filled” 后若尚无 userFills,会启动 5 秒定时器,到期后 _resolve(oid),此时若仍无 has_fill_price 则用 _fallback_px / _fallback_sz(来自 order 的 limitPx / origSz)作为成交价和数量。若 userFills 因 API 延迟或漏推而永远不到,则 5 秒后会用挂单价结算,可能和真实成交价不一致,影响 PnL 与统计。

修复方向:宽限期到期时若 not tracking.has_fill_price,在 _resolve() 内(或定时器回调里)先做一次 HTTP query_order_status(oid),若 API 返回 filled 且带 avgPx/totalSz,则用 HTTP 结果覆盖 _fallback 再解析,减少“仅用挂单价”的误用。


3. 其他观察(非严重但建议关注)

  • orderUpdates 单条结构:当前代码假定 item.get("status") 在顶层、item.get("order") 为内层对象(含 oid、limitPx、origSz)。根据 Hyperliquid 文档,该结构正确;若未来 API 变更需同步调整解析逻辑。
  • wait_for_order 最大等待max_wait = timeout_seconds + _HTTP_VERIFY_BUFFER(60s),不会永久阻塞;超时后返回 tracking.status == OrderStatus.FILLED(通常为 False)。
  • 重连事件与补查WebSocketReconnectedEvent 触发后清缓存并调用 verify_pending_orders(),逻辑正确;与超时线程的竞态见 Bug 3。

4. 修复优先级建议

优先级 Bug 建议
P0 Bug 1:超时与 WS 竞态导致误判 TIMEOUT 在强制 TIMEOUT 前增加一次 HTTP 再确认,再调用 _resolve
P1 Bug 2:订单消息缓冲无界 为 _order_msg_buffer 设上限或“不可用时丢弃”策略
P2 Bug 3:重连补查与超时线程重复/竞态 对“正在 HTTP 验证”的 oid 做去重或与 Bug 1 统一二次确认逻辑
P2 Bug 4:重复 oid 追踪语义 明确日志与业务约定,避免重复 track 同一 oid
P3 Bug 5:宽限期后无 userFills 用挂单价 宽限期到期时可选 HTTP 回填 avgPx/totalSz 再 resolve

以上为当前代码与流程下识别出的订单跟踪相关严重问题及修复方向;实现时需保持 _resolve() 幂等与锁约定,避免引入新的竞态。

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