当前系统订单跟踪存在哪些严重的bug8
订单跟踪系统严重Bug分析报告(第二轮)
分析日期:2026-02-21
分析范围:git status 显示的已修改文件
分析文件:executor.py/position_manager.py/trade_repository.py/websocket_order_manager.py/enhanced_ws_manager.py/realtime_kline_service_base.py
BUG #1 🔴 WebSocket订单状态丢失竞态条件
严重等级: 严重
文件: src/trading/websocket_order_manager.py ~第167-199行
类别: 竞态条件
问题描述
_on_order_update 在锁内执行 self._tracking.pop(oid, None) 删除 tracking 对象后,_resolve_via_http 在锁外查询时获取 None,误判订单已结算,导致成交价永远不被记录,PnL计算错误。
竞态时序
T1: 线程A (_on_order_update) 在锁内 pop(oid) 删除 tracking
T2: 线程B (wait_for_order) 在锁外等待 result_event
T3: 线程C (_resolve_via_http) 查询 self._tracking.get(oid) → None
T4: _resolve_via_http 误判"已结算",返回 True
结果: 成交价未记录,PnL计算错误
影响
- 订单成交价永远为 0 或初始值
- PnL 计算完全失真
- 订单状态与实际交易所状态不一致
BUG #2 🔴 成交价缓存TOCTOU漏洞
严重等级: 严重
文件: src/trading/websocket_order_manager.py ~第173-178行 & 第335-350行
类别: TOCTOU(检查时间与使用时间不一致)
问题代码
# _on_order_update(锁内)
fill = self._fill_prices.pop(oid, None) # 弹出缓存
if fill and fill[0] > 0:
tracking.avg_price = fill[0]
# _cache_fill(锁外,并发)
existing = self._fill_prices.get(oid) # 读取
# ... 计算新价格 ...
self._fill_prices[oid] = (new_px, new_sz) # 覆盖写入,竞态窗口
影响
- 多次部分成交场景下加权均价计算错误
_MAX_FILL_CACHE溢出时 FIFO 删除可能删掉错误的订单
BUG #3 🟠 订单状态机转换不原子
严重等级: 高
文件: src/trading/websocket_order_manager.py ~第244-251行
类别: 状态机逻辑
问题描述
超时验证线程与成交通知线程并发时,已被 pop 的 tracking 对象被重新设为 TIMEOUT 状态,造成状态机逻辑冲突,可能导致重复追踪。
非法状态转换路径
正常: PENDING → FILLED / CANCELED / REJECTED
异常: PENDING → TIMEOUT(对象已被 pop,操作无意义)
结果: 可能重新激活已删除的订单,造成重复追踪
BUG #4 🟠 账户余额缓存陈旧导致超额下单
严重等级: 高
文件: src/trading/executor.py ~第1624-1637行
类别: 缓存策略
问题代码
def get_account_value(self, force_refresh: bool = False) -> float:
if not force_refresh:
with self._cache_lock:
age = time.time() - self._position_cache_ts
if age < self._cache_ttl and self._position_cache_ts > 0:
return self._cached_account_value # ❌ 可能返回严重过期的值
影响
- WebSocket 掉线期间,缓存 TTL=5秒,返回严重过期的账户余额
- 高频交易下允许"负余额"下单
- Leg B 失败后回滚 Leg A,但已产生滑点损失
BUG #5 🟠 限价单撤单返回值未检查
严重等级: 高
文件: src/trading/executor.py ~第808-838行
类别: 错误处理缺失
问题代码
if result.leg_a.order_id:
self._cancel_order(alt_coin, result.leg_a.order_id) # ❌ 不检查返回值
# 查询实际成交量(撤单失败时数据可能不一致)
actual_filled = self._get_actual_position_size(alt_coin)
影响
- 撤单失败时订单仍在交易所挂单
- 继续下 Leg B,造成双腿超额持仓
_get_actual_position_size可能因网络超时返回 0,真实持仓被忽视
BUG #6 🟠 仓位同步覆盖写竞态
严重等级: 高
文件: src/trading/position_manager.py ~第357-369行
类别: 竞态条件
竞态时序
T0: 内存仓位 = {alt: 1.5, base: 100}
T1: get_positions() 查询交易所,返回 {alt: 1.5}
T2: 另一线程下单 Leg A,内存更新为 {alt: 2.0}
T3: _execute_close 用旧查询结果 1.5 覆盖内存值 2.0
结果: 仓位追踪错误,后续平仓数量不足
影响
- 仓位数量记录错误,平仓时数量偏少
- 残余仓位无法被系统感知,形成隐藏风险敞口
BUG #7 🟡 孤儿仓位内存-DB不一致
严重等级: 中
文件: src/trading/position_manager.py ~第807-849行
类别: 数据原子性
问题描述
内存更新完成后,_repo.save_position() 在锁外进行持久化,若此时持久化失败,内存与 DB 产生分歧。重启后恢复时加载错误数据。
with self._lock:
existing.alt_size = actual_size # 内存更新(锁内)
# 锁外:持久化(可能失败)
self._repo.save_position(size_sync_pos) # ❌ 无乐观锁,失败无回滚
BUG #8 🟡 TradeRepository非原子并发更新
严重等级: 中
文件: src/trading/trade_repository.py ~第170-209行
类别: 数据库操作
问题描述
多线程同时更新同一仓位的 PnL 和价格字段时,SQL 非原子执行导致中间状态被读取,财务数据可能被部分覆盖。
# SQL 字段拼接(非原子)
sql = f"UPDATE pair_positions SET {', '.join(updates)} WHERE position_id = %s"
BUG #9 🟡 WebSocket重连时订阅状态竞态
严重等级: 中
文件: src/utils/websocket/enhanced_ws_manager.py ~第587-638行
类别: 并发订阅管理
竞态时序
T1: _on_open 执行,清空 active_subscriptions(全量清空)
T2: 业务线程并发调用 add_subscriptions
T3: add_subscriptions 检查 "sub_key not in active_subscriptions"(此时为空,通过)
T4: 产生重复订阅请求,或部分订阅丢失
二级问题
ws.send() 失败后无全量重试或回滚机制,导致部分订阅在交易所侧存在但本地未记录。
BUG #10 🟠 _fill_prices 内存泄漏
严重等级: 高
文件: src/utils/websocket/enhanced_ws_manager.py ~第260-350行
类别: 内存管理
问题代码
self._fill_prices: Dict[int, Tuple[float, float]] = {} # ❌ 无TTL的普通dict
# 淘汰策略缺陷
if len(self._fill_prices) > self._MAX_FILL_CACHE:
del self._fill_prices[next(iter(self._fill_prices))] # ❌ FIFO删除最早插入,非最旧数据
影响
- 高频交易下
_fill_prices持续增长,TTLCache保护不覆盖此 dict - FIFO 策略可能删除"最近高频订单",保留"旧低频订单"
- 应使用
OrderedDict或带 TTL 的缓存结构
BUG #11 🟡 oid类型不一致导致dict查找失败
严重等级: 中
文件: src/trading/websocket_order_manager.py ~第43-74行
类别: 类型安全
问题描述
- API 响应的 oid 可能为字符串
- WebSocket 消息的 oid 可能为整数
self._tracking[oid]因 key 类型不匹配而静默查找失败
def track_order(self, oid: int, coin: str, ...) -> OrderTracking:
oid = int(oid) # ❌ 假设调用者已保证类型,重复转换无法覆盖所有路径
BUG #12 🟠 平仓PnL计算中基准币成交价为0
严重等级: 高
文件: src/trading/position_manager.py ~第696-735行
类别: PnL计算
问题代码
base_exit_price = order_result.leg_b.price
if (not base_exit_price or base_exit_price <= 0) and position.base_symbol:
base_exit_price = self._executor.get_all_mids().get(...)
# ❌ get_all_mids() 失败或超时时,base_exit_price 保持为 0.0
# ❌ 未降级到 position.base_current_price
影响
- Leg B 平仓失败且
get_all_mids()超时时,base_exit_price = 0.0 - base 腿 PnL 完全丢失,可能导致 30-50% 的收益未记录
BUG #13 🟡 平仓禁用配置检查时间窗口
严重等级: 中
文件: src/trading/position_manager.py ~第260-290行
类别: 配置热更新
竞态时序
T1: is_close_disabled("COIN", "BASE") → False(允许平仓)
T2: 配置热更新,将 COIN|BASE 添加到禁用列表
T3: 执行平仓逻辑(缺少二次检查)
T4: 订单已提交到交易所
结果: 管理员紧急禁用操作失效
📊 Bug汇总与优先级
| Bug# | 严重等级 | 类别 | 文件 | 复现难度 |
|---|---|---|---|---|
| #1 | 🔴 严重 | 竞态条件 | websocket_order_manager.py | 中 |
| #2 | 🔴 严重 | TOCTOU | websocket_order_manager.py | 中 |
| #3 | 🟠 高 | 状态机 | websocket_order_manager.py | 低 |
| #4 | 🟠 高 | 缓存过期 | executor.py | 高 |
| #5 | 🟠 高 | 错误处理 | executor.py | 高 |
| #6 | 🟠 高 | 竞态条件 | position_manager.py | 中 |
| #7 | 🟡 中 | 原子性 | position_manager.py | 低 |
| #8 | 🟡 中 | 非原子更新 | trade_repository.py | 低 |
| #9 | 🟡 中 | 订阅竞态 | enhanced_ws_manager.py | 中 |
| #10 | 🟠 高 | 内存泄漏 | enhanced_ws_manager.py | 高 |
| #11 | 🟡 中 | 类型安全 | websocket_order_manager.py | 低 |
| #12 | 🟠 高 | PnL丢失 | position_manager.py | 中 |
| #13 | 🟡 中 | 配置时间窗 | position_manager.py | 低 |
⚠️ 高危影响评估
直接影响
- 订单状态丢失 → 仓位追踪错误
- PnL 计算错误 → 财务报表不准确
- 缓存不一致 → 风险管理失效
- 并发下单失败 → 交易失败率上升
间接影响
- 内存泄漏 → 长期运行内存溢出
- 孤儿仓位泄漏 → 持仓被无限期占用
- 黑名单配置失效 → 风险币种无法紧急禁用
修复优先级
| 优先级 | Bug编号 | 原因 |
|---|---|---|
| P0 🔴 | #1, #2 | 成交价丢失,PnL完全错误,核心数据不可信 |
| P1 🟠 | #4, #5, #6, #10, #12 | 超额下单、内存泄漏、PnL缺失 |
| P2 🟡 | #3, #7, #8, #9, #11, #13 | 状态不一致、数据分歧 |
最高风险组合:Bug #1 + #2 + #12 同时存在时,系统长期运行后财务数据完全不可信。建议在下次部署前优先修复 P0 级别问题。