订单跟踪系统BUG38

订单跟踪系统 — 复杂度审计报告

审计范围src/trading/executor.py · src/trading/websocket_order_manager.py
验证方式:静态分析 + 测试网实测(scripts/verify_order_tracking.py --coin ICP --size 10


一、审计结论

发现 5 项复杂度问题,根源均为对 Hyperliquid API 响应结构的错误假设。
测试网验证推翻了两个核心前提:

假设 实测结果
query_order_by_oid 返回 avgPx / totalSz 从不返回,两字段对任意状态均缺失
userFillsorderUpdates 晚到 同时到达(实测差值 0ms)

直接后果:

  • _backfill_order_price / _verify_fill_completeness 写入的字段永远是 0,是有网络开销的空操作
  • _close_limit_leg_timeoutremaining 恒等于原始仓位量,平仓数量计算始终错误
  • has_fill_price 字段与 filled_size > 0 语义完全重合,是冗余状态

二、问题清单

问题 A — 两个死函数(executor.py)

函数_backfill_order_priceL698–724)、`_verify_fill_completeness`(L726–766)

死因:两函数均调用 resp.get("avgPx")resp.get("totalSz")
query_order_by_oid 的响应对 filled 和 canceled 均无这两个字段。
每次调用只产生网络开销,不产生任何效果。

调用点(共 3 处,均为死代码)

  • _track_limit_orderif/else has_fill_price 分叉,两个分支分别调用两函数
  • _close_limit_leg_timeoutelse 分支调用 _backfill_order_price
  • limit_open:调用 _backfill_order_price

净删除:~70 行 + 3 处调用


问题 B — _close_limit_leg_timeout 解析错误(executor.py)

位置:~L787–818

错误

filled_sz = self._safe_pos_float(resp.get("totalSz"))  # → 永远 0
fill_px   = self._safe_pos_float(resp.get("avgPx"))    # → 永远 0

totalSz / avgPx 不在响应顶层,正确结构为 resp["order"]["origSz"]resp["order"]["sz"]

后果filled_sz 恒为 0 → remaining = position_size(全量)→ 超时平仓总是按全量下单。
remaining == 0 的分支(L810–818)永远不会到达,是不可达代码。

正确解析

inner     = resp.get("order", {})
orig_sz   = self._safe_pos_float(inner.get("origSz"))
remain_sz = self._safe_pos_float(inner.get("sz"))
filled_sz = max(0.0, orig_sz - remain_sz) if orig_sz > 0 else 0.0
fill_px   = self._safe_pos_float(inner.get("limitPx"))  # avgPx 不可用,limitPx 最优近似

问题 C — has_fill_price 冗余字段(websocket_order_manager.py)

字段OrderTracking.has_fill_price: bool = False(L48)

冗余原因has_fill_price 的语义等价于 filled_size > 0
filled_size 在每次 fill 事件时累计,有 fill 则必然 > 0,无 fill 则必然 == 0。
两个字段表达同一个事实,无需单独维护。

引用点(共 4 处需处理)

  • OrderTracking 字段声明(L48):删除
  • _resolve() 内赋值(~L259):删除
  • _on_order_filled_event 内赋值(~L378):删除
  • _on_order_status_event 内条件判断(~L319):替换为 filled_size > 0

保留 _grace_timer:验证显示正常路径两事件同时到达、timer 不启动。
但若 userFills WS 消息丢失,timer 是防止订单卡在 PENDING(直至 600s 超时)的唯一保障。
风险高于收益,不删除。


问题 D — _parse_order_response 解析错误(websocket_order_manager.py)

位置:~L537–568

错误

avg_px   = _pos_float(resp.get("avgPx") or resp.get("avg_px"))    # → 永远 0
total_sz = _pos_float(resp.get("totalSz") or resp.get("total_sz")) # → 永远 0

后果_http_check 通过 HTTP 检测到已成交/已撤销订单时,
resolve 写入 avg_price=0filled_size=0,价格信息完全丢失。

正确解析

inner    = resp.get("order", {})
total_sz = max(0.0, _pos_float(inner.get("origSz")) - _pos_float(inner.get("sz")))
avg_px   = _pos_float(inner.get("limitPx"))  # avgPx 不可用,limitPx 是最优近似

问题 E — _http_check 魔法字符串(websocket_order_manager.py)

位置:~L493–530

问题:函数返回 "open" / "resolved" / "stale" / "busy" / "error" 5 个字符串,
调用方用字符串比较做分支,类型不安全,可读性差,且 "stale" 语义含糊。

改动:返回值改为 Optional[bool]

旧返回值 语义 新返回值
"resolved" / "stale" 已处理 True
"open" 仍挂单,应 TIMEOUT False
"busy" / "error" 跳过本轮 None

签名def _http_check(self, oid: int) -> bool | None:

调用方模式(2 处统一):

result = self._http_check(oid)
if result is True:
    return
if result is False:
    self._resolve(oid, tracking, OrderStatus.TIMEOUT)
    return
# None → error/busy,继续重试逻辑

三、修改汇总

文件 修复项 操作 净变化
src/trading/executor.py A 删除 2 函数 + 3 调用点 −~70 行
src/trading/executor.py B 修复 _close_limit_leg_timeout 解析 ~0 行(改写)
src/trading/websocket_order_manager.py C 删除 has_fill_price 字段及 4 处引用 −4 行
src/trading/websocket_order_manager.py D 修复 _parse_order_response 解析 ~0 行(改写)
src/trading/websocket_order_manager.py E _http_check 返回类型改为 Optional[bool] ~0 行(改写)

总净删除:约 85 行,无新增抽象层。


四、执行顺序

  1. Fix A:先删函数体,再删 3 处调用点(避免遗漏)
  2. Fix B:独立改写 _close_limit_leg_timeout 内解析逻辑
  3. Fix C:搜索 has_fill_price 全部引用,逐一处理
  4. Fix D:改写 _parse_order_response 两行
  5. Fix E:改返回值签名 + 统一 2 处调用方

五、验证方法

python scripts/verify_order_tracking.py --coin ICP --size 10
验证点 预期结果
Fix A 完成后 grep -r "_backfill_order_price|_verify_fill_completeness" src/ 结果为空
Fix B 完成后 Verify B 输出 filled = origSz - sz = 0.0(无成交挂单)
Fix D 完成后 HTTP 路径 resolve 时日志中 size/price 非 0
Fix E 完成后 grep "_http_check" src/ 调用方无裸字符串比较

附:API 响应结构说明

query_order_by_oidquery_order_status 解包后的结构:

{
  "order": {
    "origSz": "10.0",
    "sz": "10.0",
    "limitPx": "2.0542",
    "side": "B",
    "coin": "ICP",
    "oid": 48961810081
  },
  "status": "canceled"
}

不存在的字段avgPxtotalSz(仅在下单即时响应 exchange.order() 中存在)

成交量计算filled = origSz - sz(取消时两者相等,成交 = 0;部分成交再取消时两者不等)

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