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

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

分析时间: 2026-02-21
涉及文件:

  • src/trading/websocket_order_manager.py
  • src/trading/executor.py
  • src/services/realtime_kline_service_base.py
  • src/utils/websocket/enhanced_ws_manager.py

Bug 1 — 致命: HTTP 验证网络错误导致订单误判为 TIMEOUT

位置: src/trading/websocket_order_manager.py:342-367

问题代码

# _resolve_via_http
try:
    resp = self._executor.query_order_status(oid)
except Exception as e:
    logger.error(f"HTTP 查询订单状态异常: oid={oid} | {e}")
    resp = None  # ← 网络异常时 resp = None

status, avg_px, total_sz = self._parse_order_response(resp)
# _parse_order_response(None) 直接返回 OrderStatus.TIMEOUT !

if status == OrderStatus.PENDING:
    return False  # ← 不会走到这里,因为 status == TIMEOUT

# 结果: 将仍在挂单的订单强制标记为 TIMEOUT 并 resolve

_parse_order_response 第 376-377 行:

def _parse_order_response(resp: dict | None) -> tuple[OrderStatus, float, float]:
    if not resp or not isinstance(resp, dict):
        return OrderStatus.TIMEOUT, 0.0, 0.0  # ← 将网络故障等同于 TIMEOUT,逻辑错误

触发路径

  1. 订单 WS 消息超时(timeout_seconds 内未收到 orderUpdates filled)
  2. _timeout_then_verify 调用 _resolve_via_http
  3. HTTP 请求因网络抖动抛出异常
  4. resp = None_parse_order_response(None) 返回 TIMEOUT
  5. 订单被强制 resolve 为 TIMEOUT,从 _tracking 移除
  6. wait_for_order 返回 False,交易系统认为订单未成交

后果

  • 交易所实际已成交的订单在系统中被标记为"失败"
  • Leg A 成交但系统未感知 → 不执行 Leg B → 对冲缺失,单腿裸仓
  • 若系统因"未成交"重新下单 → 重复开仓,资金翻倍暴露
  • 持仓追踪数据与交易所真实持仓不一致

建议修复方向

网络异常时应返回 PENDING(保持等待),而非 TIMEOUT

if not resp or not isinstance(resp, dict):
    # 网络错误:无法判断订单状态,保守处理为仍在挂单
    return OrderStatus.PENDING, 0.0, 0.0

Bug 2 — 严重: 订单缓冲区消息在异常时永久丢失

位置: src/services/realtime_kline_service_base.py:619-633

问题代码

if mgr is not None:
    while self._order_msg_buffer:
        buffered = self._order_msg_buffer.popleft()  # ← 已从队列弹出,无法回滚
        mgr.handle_message(buffered)                  # ← 若此处抛出异常
    mgr.handle_message(msg)
# ...
except Exception as e:
    self.logger.error(f"消息处理失败: {e}", exc_info=True)
    # ← 当前消息已丢失,且 buffer 中剩余所有消息也全部跳过

触发路径

  1. WebSocketOrderManager 初始化期间,部分 orderUpdates/userFills 消息被存入 _order_msg_buffer
  2. 管理器就绪后,_on_message 回调开始回放缓冲区
  3. handle_message 对某条缓冲消息的处理逻辑抛出异常(如字段格式异常、NoneType 等)
  4. 外层 except Exception 捕获,循环中断
  5. 剩余缓冲消息全部丢失(已从 deque 弹出,无法恢复)

后果

  • 丢失的消息可能包含订单成交通知(orderUpdates filled / userFills
  • 订单 tracking 永久停留在 PENDING 状态
  • 等待 timeout_seconds 后触发 HTTP 兜底验证,延迟可达数分钟
  • 若 HTTP 验证也失败(Bug 1 场景),成交订单彻底丢失

建议修复方向

对每条缓冲消息独立 try-except,异常时记录日志但继续处理下一条:

while self._order_msg_buffer:
    buffered = self._order_msg_buffer.popleft()
    try:
        mgr.handle_message(buffered)
    except Exception as e:
        logger.error(f"回放缓冲订单消息失败: {e}", exc_info=True)
        # 继续处理剩余消息

Bug 3 — 严重: _fallback_sz 错误使用 origSz(原始下单量)

位置: src/trading/websocket_order_manager.py:239-242

问题代码

if status_str == "filled":
    tracking._terminal_status = OrderStatus.FILLED
    tracking._fallback_px = _safe_positive_float(order.get("limitPx"))
    tracking._fallback_sz = _safe_positive_float(order.get("origSz"))  # ← BUG: origSz 是原始下单量

字段含义对比

字段 含义 正确用途
origSz 订单创建时的原始请求数量 显示下单大小
totalSz 实际累计成交数量 应作为 filled_size
limitPx 挂单价格 可作为 avg_price 兜底(近似)
avgPx 实际成交均价 应优先作为 avg_price

触发场景

userFills 未在宽限期(5 秒)内到达,系统使用 _fallback_sz 作为成交数量。
若订单部分成交(常见于流动性不足时),origSz > 实际成交量

后果

示例: 下单 100 BTC,实际仅成交 60 BTC
_fallback_sz = origSz = 100 BTC  ← 错误

leg_a_value = 100 * price  ← 基于错误数量
leg_b_size = leg_a_value / base_price  ← Leg B 超额开仓 40 BTC
  • Leg B 市价单数量偏大,实际对冲比超过预期
  • 账户暴露于超额风险敞口
  • 若账户余额不足,Leg B 下单失败并触发回滚,但 Leg A 实际持仓仍在

建议修复方向

tracking._fallback_px = _safe_positive_float(order.get("avgPx") or order.get("limitPx"))
tracking._fallback_sz = _safe_positive_float(order.get("totalSz") or order.get("origSz"))

Bug 4 — 严重: wait_for_order 固定超时窗口可能在 HTTP 验证完成前提前返回

位置: src/trading/websocket_order_manager.py:83-84, 114-118

问题代码

_HTTP_VERIFY_BUFFER = 60  # 固定 60 秒缓冲(硬编码)

def wait_for_order(self, tracking: OrderTracking) -> bool:
    max_wait = tracking.timeout_seconds + self._HTTP_VERIFY_BUFFER
    tracking.result_event.wait(timeout=max_wait)  # ← 最多等 timeout + 60s
    return tracking.status == OrderStatus.FILLED

HTTP 验证实际耗时估算

query_order_status 内部调用 retry_call,默认配置:

  • 最多重试 3 次
  • 最大退避 15 秒
  • 总耗时上限 ≈ 15 + 15 + 15 = 45 秒(极端情况)

加上 _timeout_then_verify 触发延迟,完整流程:

timeout_seconds (等待WS)
+ 网络往返延迟
+ retry_call 重试耗时(最坏 ~45s)
+ _resolve 处理时间

若重试耗时超过 60 秒wait_for_order 先返回 False,随后 HTTP 验证完成并将 tracking.status 改为 FILLED,但调用方已按"失败"处理(可能已撤单或回滚)。

后果

  • 调用方在 wait_for_order 返回 False 后执行 _cancel_order + _get_actual_position_size
  • 与此同时 HTTP 验证在后台将订单解析为成交
  • 两条路径并发操作同一订单,状态不一致

建议修复方向

将缓冲时间设置为动态值,或增大固定缓冲:

_HTTP_VERIFY_BUFFER = 120  # 至少 2 倍最坏重试时间

或在 wait_for_order 返回后,读取最终状态前增加短暂等待校验。


Bug 5 — 中等: 健康检查日志的 modulo 条件失效

位置: src/utils/websocket/enhanced_ws_manager.py:1253

问题代码

# 每次循环后 sleep(WS_HEALTH_CHECK_INTERVAL)
if int(time.time() - self.start_time) % WS_HEALTH_REPORT_INTERVAL == 0:
    self._log_health_report()

问题分析

  • time.sleep() 精度不保证整秒(实际可能多睡或少睡若干毫秒)
  • int(time.time() - self.start_time) 取整后,连续两次循环可能落在同一整数秒
  • WS_HEALTH_REPORT_INTERVALWS_HEALTH_CHECK_INTERVAL 不整除,条件可能数小时都不触发

后果

  • 健康监控日志可能从不输出(可观测性缺失)
  • 或在某个整秒内输出多次(日志噪声)

建议修复方向

使用上次报告时间戳:

_last_health_report = 0.0
# 在循环内:
now = time.time()
if now - self._last_health_report >= WS_HEALTH_REPORT_INTERVAL:
    self._log_health_report()
    self._last_health_report = now

优先级汇总

# 严重程度 直接影响 位置
1 🔴 致命 成交订单被误判为超时,可能导致重复开仓或单腿裸仓 websocket_order_manager.py:344-367
2 🔴 严重 缓冲订单消息异常时全部丢失,订单追踪失效 realtime_kline_service_base.py:619
3 🟠 严重 部分成交时 filled_size 错误,Leg B 超额建仓 websocket_order_manager.py:242
4 🟠 严重 HTTP 验证可能在 wait_for_order 返回后才完成 websocket_order_manager.py:116
5 🟡 中等 健康报告日志 modulo 条件失效,可观测性降低 enhanced_ws_manager.py:1253

修复优先级建议

立即修复(影响资金安全):

  • Bug 1: 网络异常 → PENDING 而非 TIMEOUT
  • Bug 3: origSztotalSzlimitPxavgPx

本周修复(影响系统可靠性):

  • Bug 2: 缓冲区回放独立异常处理
  • Bug 4: 增大 HTTP 验证缓冲时间

下次迭代:

  • Bug 5: 健康报告使用时间戳而非 modulo

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