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

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

分析日期:2026-02-21
分析范围:src/trading/executor.pysrc/trading/websocket_order_manager.pysrc/trading/position_manager.pysrc/trading/trade_repository.py


严重性汇总

# 位置 分类 影响
1 executor.py:677 🔴 严重 成交价回填永久失效,PnL 计算错误
2 executor.py:1143 🔴 严重 错误状态混淆,监控和 DB 记录失真
3 websocket_order_manager.py:83 🟠 中等 竞态导致成交价丢失,PnL 偏差
4 position_manager.py:1134 🟠 中等 幽灵仓位 PnL 未记录,统计不准
5 position_manager.py:550 🟠 中等 Lark 异常导致风险/统计模块静默失效
6 websocket_order_manager.py:210 🟡 次要 语义错误,有其他保护,功能尚可

Bug 1(最严重):_backfill_order_price API 响应未解包,导致成交价永远回填失败

位置executor.py:677-688

根因

query_order_by_oid 返回的原始格式是嵌套结构:

{
  "status": "order",
  "order": {
    "status": "filled",
    "avgPx": "1234.5",
    "totalSz": "1.0"
  }
}

query_order_status:1571)已正确实现解包逻辑:

# executor.py:1575-1579
if raw.get("status") == "order" and isinstance(raw.get("order"), dict):
    return raw["order"]   # ✅ 返回内层,包含 avgPx/totalSz
return raw

_backfill_order_price:677)直接在外层 dict 上调用 get("avgPx")永远返回 None

# executor.py:677-688(有 bug 的代码)
status_resp = retry_call(
    lambda: self._info.query_order_by_oid(self._wallet.address, order_result.order_id),
    description="订单状态查询",
)
if isinstance(status_resp, dict):
    avg_px = status_resp.get("avgPx") or status_resp.get("avg_px")   # ❌ 外层无此字段,永远 None
    total_sz = status_resp.get("totalSz") or status_resp.get("total_sz")  # ❌ 同上

触发场景(3 处调用,全部受影响)

调用位置 触发条件
limit_open :822 超时撤单后发现部分成交
limit_close Leg A :985 超时撤单后持仓归零,视为成交
limit_close Leg B :1021 超时撤单后持仓归零,视为成交

影响

  • order_result.price 保持为挂单价而非实际成交均价
  • PnL 计算错误,偏差取决于市场价格与挂单价的差值
  • DB 中 trade_orders.price 字段记录失真
  • 开仓时 Leg B 数量基于错误的 Leg A 价格计算,导致对冲比例偏差

修复方案

_backfill_order_price 应复用已有的 query_order_status 方法(已正确解包),而非直接调用底层 API:

# 修复思路
def _backfill_order_price(self, order_result: OrderResult, coin: str):
    if not order_result.order_id:
        return
    try:
        # 复用 query_order_status(已做嵌套解包)
        status_resp = self.query_order_status(order_result.order_id)
        if isinstance(status_resp, dict):
            avg_px = status_resp.get("avgPx") or status_resp.get("avg_px")
            total_sz = status_resp.get("totalSz") or status_resp.get("total_sz")
            ...

Bug 2:_parse_order_response 错误订单状态混淆(ERROR 被标为 REJECTED

位置executor.py:1143-1146

根因

# executor.py:1143-1146(有 bug 的代码)
elif statuses and ORDER_STATUS_ERROR in statuses[0]:
    result.success = False
    result.error_message = statuses[0][ORDER_STATUS_ERROR]
    result.status = ORDER_STATUS_REJECTED   # ❌ 应为 ORDER_STATUS_ERROR

常量定义:

  • ORDER_STATUS_REJECTED = "rejected"(交易所主动拒绝,如保证金不足)
  • ORDER_STATUS_ERROR = "error"(系统/API 错误,非正常拒绝)

当交易所返回 error 状态时,系统将其错误地标记为 rejected

影响

  • _notify_limit_order 中的通知条件(:516):result.status in (ORDER_STATUS_REJECTED, ORDER_STATUS_ERROR) 虽然两者都能触发,但告警标题逻辑混乱
  • DB trade_orders.status 字段无法区分"交易所拒绝"和"系统错误"两种场景,影响事后排查
  • Lark 告警中显示的状态信息不准确

修复方案

elif statuses and ORDER_STATUS_ERROR in statuses[0]:
    result.success = False
    result.error_message = statuses[0][ORDER_STATUS_ERROR]
    result.status = ORDER_STATUS_ERROR   # ✅ 正确状态

Bug 3:wait_for_orderuserFills 成交价存在竞态丢失窗口

位置websocket_order_manager.py:83-98

根因

# websocket_order_manager.py:83-98
def wait_for_order(self, tracking: OrderTracking) -> bool:
    tracking.result_event.wait(timeout=tracking.timeout_seconds + 30)  # 由 orderUpdates 触发 set()

    # ❌ event.set() 后立即 pop,但 userFills 可能还未到达
    if tracking.status == OrderStatus.FILLED and not tracking.has_fill_price:
        with self._lock:
            fill = self._fill_prices.pop(tracking.oid, None)   # 此时可能仍为 None
        if fill:
            ...

竞态时序

[Thread A: orderUpdates]         [Thread B: wait_for_order]       [Thread C: userFills]
        |                                    |                              |
  status=FILLED                             |                              |
  _tracking.pop(oid)                        |                              |
  event.set() ──────────────────────► wait() 返回                         |
                                      检查 has_fill_price=False            |
                                      _fill_prices.pop(oid) → None        |
                                      成交价丢失 ✗                        |
                                                                   _cache_fill(oid, px, sz)
                                                                   (已无人消费)

影响

  • tracking.avg_price 保持为 0 或挂单价(取决于 _on_order_update 的 fallback 逻辑)
  • _track_limit_orderorder_result.price 使用错误价格
  • 在价格波动较大时,PnL 计算偏差显著

修复方向

wait_for_order 的 pop 之前加入短暂等待,或在 _on_order_update 中将 has_fill_price 标志的设置延迟到 userFills 到达后,避免提前 set event。


Bug 4:sync_with_exchange 幽灵仓位关闭后 realized_pnl 未更新 DB

位置position_manager.py:1134-1138

根因

# position_manager.py:1134-1138(有 bug 的代码)
for pos_id, close_time, *_ in _close_ops:
    self._repo.update_position_status(
        position_id=pos_id,
        status=PositionStatus.CLOSED,
        close_time=close_time,
        # ❌ 缺少 realized_pnl 参数
    )

对比正常平仓流程(_execute_close :538-546):

# executor.py:538-546(正确的平仓流程)
self._repo.update_position_status(
    position_id=position.position_id,
    status=PositionStatus.CLOSED,
    close_time=now,
    realized_pnl=realized_pnl,   # ✅ 正确记录 PnL
    alt_exit_price=alt_exit_price,
    ...
)

影响

  • 幽灵仓位(交易所持仓消失但系统未感知的仓位)关闭时,DB 中 realized_pnl 保持为 0
  • daily_trading_stats.total_realized_pnl 统计数据不准确
  • 历史 PnL 报表中该仓位的盈亏记录永久丢失

修复方向

_close_ops 元组中增加 realized_pnl 字段,在 update_position_status 调用时传入(可使用 mid_price 估算,与 _execute_closealt_gone_from_exchange 分支的处理逻辑一致)。


Bug 5:平仓 DB 更新失败后,后续统计操作被意外中断

位置position_manager.py:550-558

根因

# position_manager.py:550-558(有 bug 的代码)
except Exception as e:
    logger.error(f"平仓 DB 更新失败: {position.position_id} | {e}")
    sender_colourful(        # ❌ 无 try-except 保护
        title="平仓 DB 更新失败",
        content=(
            f"仓位 `{position.position_id}` 已在交易所平仓但 DB 更新失败,请手动核查\n"
            f"```\n{e}\n```"
        ),
    )
    # 若 sender_colourful 抛出异常,以下两行永远不会执行:

self._risk_manager.update_daily_pnl(realized_pnl)    # ❌ 风险统计丢失
self._repo.update_daily_stats(...)                    # ❌ 日交易统计丢失

触发条件

Lark Bot 发生网络故障(OSErrorTimeoutError 等)时,sender_colourful 抛出异常,后续的 update_daily_pnlupdate_daily_stats 静默跳过,无任何日志提示。

影响

  • RiskManager._daily_pnl 累计值偏低,可能影响止损熔断触发阈值
  • daily_trading_stats 统计的 trades_closedtotal_realized_pnl 不准确
  • 问题静默,仅靠外部对账才能发现

修复方案

except Exception as e:
    logger.error(f"平仓 DB 更新失败: {position.position_id} | {e}")
    try:
        sender_colourful(title="平仓 DB 更新失败", content=...)
    except Exception as notify_err:
        logger.error(f"平仓 DB 更新失败告警发送失败: {notify_err}")
    # 无论通知是否成功,统计更新必须执行

Bug 6(次要):_on_user_fill 的条件判断语义错误

位置websocket_order_manager.py:210

根因

# websocket_order_manager.py:210(语义错误)
if not px and not sz:   # ❌ AND 逻辑,语义错误(应为 OR)
    continue

当前行为:只有 pxsz 同时为假值时才跳过。若 px=None, sz="10",则不跳过,但 fill_px=0.0,会被 _accumulate_fill 内部的 if fill_px <= 0 or fill_sz <= 0: return 拦截。

正确语义:只要 pxsz 任一缺失,则该条成交记录不完整,应跳过:

if not px or not sz:   # ✅ OR 逻辑,语义正确
    continue

影响

目前由 _accumulate_fill 内部保护兜底,无实际功能问题,但属于防御性编程缺失,代码语义不清晰。


修复优先级建议

  1. 立即修复(Bug 1、2):直接影响成交价记录准确性和 PnL 计算,每次限价单超时均会触发
  2. 尽快修复(Bug 3、4、5):在特定条件下触发,影响统计准确性和风险控制
  3. 计划修复(Bug 6):无实际功能影响,代码 review 时一并处理

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