订单跟踪bug20
订单跟踪系统严重Bug分析报告
Context
基于对 Hyperliquid 永续合约交易系统的全面代码审计,识别出订单跟踪、仓位管理、风控交互等环节中的严重bug。系统采用 WebSocket 实时追踪 + HTTP 回退验证的双路架构,涉及多线程并发操作。
P0 严重Bug(必须立即修复)
Bug 1: wait_for_order() 无超时 + _timeout_loop 提前返回 → 可能永久阻塞
文件: src/trading/websocket_order_manager.py
问题:
wait_for_order()(line 133) 使用tracking.result_event.wait()无任何超时参数- 注释声称
_timeout_loop保证所有路径都调用result_event.set() - 但
_timeout_loop(line 404-406) 在_ws_status已设置时提前 return,不调用_finish() - 此时完全依赖 grace timer (5秒后) 调用
_finish() - 如果 grace timer daemon 线程异常(进程信号中断、内存压力导致线程无法调度),
result_event永远不会被 set
影响: 整个交易执行线程永久挂起,系统无法处理新信号,等同于交易系统宕机
修复方案: 在 _timeout_loop 的提前返回路径中主动调用 _finish(oid):
# line 404-406 修改为:
with self._lock:
if tracking._ws_status is not None:
# 不再依赖 grace timer,直接完成
pass # fall through to _finish
else:
... # 原有逻辑
self._finish(oid) # 保证 result_event.set()
Bug 2: Leg B 失败 + Leg A 回滚失败 → 交易所残留仓位无系统追踪
文件: src/trading/executor.py (line 919-942) + src/trading/position_manager.py (line 163)
问题:
- Leg B 市价单失败后尝试回滚 Leg A
- 如果回滚也失败 (line 927-929),设置
result.leg_a.success = False - position_manager (line 163) 检查
result.leg_a.success,False 则不创建仓位记录 - 但交易所仍然持有 Leg A 的实际仓位!
- 虽然发送了告警 (line 930-941),但系统中没有任何追踪记录
- 该孤儿仓位无止损保护,直到下次系统重启时被 orphan detection 发现
影响: 交易所上有未受监控的裸仓位,无止损保护,极端行情下可能造成重大亏损
修复方案: 回滚失败时,将仓位降级为 pair_mode="single" 并记录,而非标记失败:
# 回滚失败时,仍创建仓位记录以启用止损监控
result.leg_a.success = True # 保持True
result.leg_a.error_message = "Leg B失败且回滚失败,仅保留Leg A"
result.leg_b = None # 清除失败的Leg B
# position_manager 会以 single mode 创建仓位
P1 重要Bug(尽快修复)
Bug 3: 平仓异常恢复后无退避延迟 → 可能 busy loop
文件: src/trading/position_manager.py (line 317-318)
问题:
_execute_close()抛异常后,position 状态回退为 OPEN (line 318)- 止损监控线程下一轮循环立即发现 OPEN 状态,重新触发平仓
- 如果异常是持续性的(网络断开、交易所维护),形成高频重试 busy loop
- 每轮循环都执行网络查询
_check_exchange_has_position()(line 310)
影响: 网络异常期间大量无效请求,可能触发交易所限流,消耗CPU
修复方案: 在异常恢复后添加冷却时间标记:
position._last_close_error_time = datetime.now()
# 止损监控中检查冷却期
if hasattr(pos, '_last_close_error_time') and (now - pos._last_close_error_time).seconds < 30:
continue # 跳过冷却中的仓位
Bug 4: _get_actual_position_size 按 coin 查询,多配对时返回合并仓位
文件: src/trading/executor.py (line 651-674)
问题:
- 方法按
coin名称查询交易所仓位,返回第一个匹配的abs(szi) - Hyperliquid 按 coin 维度管理仓位(同一 coin 的所有交易净额合并)
- 如果同一 coin 出现在不同配对中(如 ETH|BTC 和 ETH|SOL),交易所返回的是净仓位
- 系统按配对分开追踪,但交易所是合并的
影响:
- 部分成交率计算基于合并仓位,可能远大于实际本次订单的成交量
- 平仓时
_get_actual_position_size返回的是所有配对的合并量 - 导致一次平仓操作可能错误地平掉多个配对的仓位
触发条件: 同一 coin 在多个活跃配对中使用(当前 single position 模式下概率较低,但 orphan adoption 后可能出现)
修复方案: 在系统层面禁止同一 coin 同时存在于多个活跃配对中,或在查询时传入期望数量做交叉验证。
Bug 5: WebSocket 重连窗口期新订单错过更新
文件: src/trading/executor.py (WS 重连逻辑)
问题:
- WS 断连后重连,
verify_pending_orders()异步补查断连前的 PENDING 订单 - 但如果策略在重连期间生成新信号并提交新订单:
- 新订单已注册到
_tracking - WS 订阅可能尚未完全恢复
- 新订单的
orderUpdates/userFills消息可能被丢弃
- 新订单已注册到
- 新订单只能等待完整 timeout (600秒) 后通过 HTTP 回退获取状态
影响: 限价单跟踪严重延迟(从实时退化到10分钟级别),可能错过最佳退出时机
修复方案: 添加 WS 恢复状态标志,在恢复期间暂停新订单提交或强制使用 HTTP 轮询。
P2 一般Bug(计划修复)
Bug 6: peak_pnl_pct 并发读写竞态
文件: src/trading/risk_manager.py (读取) + src/trading/position_manager.py (写入)
问题:
peak_pnl_pct在止损线程中被读取和比较(无 position_manager lock)- 在价格同步线程中被更新(持有 lock)
max()操作是 read-compare-write 非原子序列- 可能导致峰值被覆盖,移动止损精度降低
Bug 7: Grace Timer 线程泄漏
文件: src/trading/websocket_order_manager.py (line 330-337)
问题:
- 代码在创建新 timer 前会 cancel 旧 timer (line 330-331)
- 但
cancel()只阻止 timer 执行回调,不会终止已启动的线程 - 如果收到大量重复 "filled" 消息,会产生多个等待中的 Timer 线程
- 每个 Timer 线程占用系统资源直到超时
影响: 线程资源泄漏,长时间运行后可能耗尽系统线程
系统性风险
风险 1: 订单持久化窗口
position_manager.py:158-183 之间存在持久化空窗:
- 订单在交易所已执行 (line 158)
- 仓位尚未写入数据库 (line 183)
- 如果进程在此窗口崩溃,订单执行信息丢失
风险 2: 锁外执行 I/O 操作
close_position() 在锁内设置 CLOSING 状态后释放锁,然后执行网络 I/O (_execute_close)。这是为了避免持锁期间阻塞,但引入了并发风险(已通过状态检查缓解)。
修复优先级
| 优先级 | Bug | 修复复杂度 | 影响范围 |
|---|---|---|---|
| 立即 | Bug 1: wait_for_order 永久阻塞 | 低 (3行代码) | 系统存活性 |
| 立即 | Bug 2: 回滚失败无追踪 | 中 (需改逻辑) | 资金安全 |
| 本周 | Bug 3: 异常恢复 busy loop | 低 (加冷却) | 系统稳定性 |
| 本周 | Bug 5: WS重连窗口期 | 中 (加状态标志) | 订单时效性 |
| 下周 | Bug 4: 多配对合并仓位 | 高 (架构改动) | 仓位准确性 |
| 下周 | Bug 6: peak_pnl_pct 竞态 | 低 (加锁) | 止损精度 |
| 下周 | Bug 7: Timer线程泄漏 | 低 (1行代码) | 资源使用 |
验证方法
- Bug 1: 模拟 grace timer 超时未执行场景,验证 wait_for_order 是否能在有限时间内返回
- Bug 2: 模拟 Leg B + 回滚双失败,验证系统是否创建了仓位记录并启用止损
- Bug 3: 模拟持续性网络异常,观察日志中平仓重试频率是否有退避
- Bug 5: 在 WS 重连期间提交订单,验证订单状态是否及时更新