全系统key配对升级 bug4
全系统 Pair-Key 重构后缺陷分析报告
分析日期: 2026-02-19
分析范围: 全系统 Key 改为配对维度(symbol, base_symbol)后的完整状态评估
一、已修复项确认 ✅
| # | Bug | 文件 | 状态 |
|---|---|---|---|
| 1 | 平仓失败路径无锁保护 | position_manager.py:527 |
✅ 已修复 with self._lock |
| 2 | 配对级参数不继承币种级 | config.py:362-404 |
✅ 已修复,传入 symbol_overrides |
| 3 | 就绪阈值过低 (25%→50%) | strategy.py:134,141,386 |
✅ 已修复 // 4 → // 2 |
| 4 | 新币黑名单仅 symbol 维度 | realtime_kline_service_base.py:244 |
✅ 已改为 set[tuple[str, str]] |
| 5 | 数据自愈 zscore 查询缺 base_symbol | orchestrator.py:466-467 |
✅ 已添加 AND base_symbol = %s |
| 6 | 数据自愈触发列表仅枚举 symbol | realtime_kline_service_base.py:1690 |
✅ 已改为 SELECT DISTINCT symbol, base_symbol |
二、仍存在的缺陷
缺陷 A (高):symbol_blacklist 和 close_disabled_symbols 未配对化
文件: src/trading/config.py:69,71,259,265-270
现状:
symbol_blacklist: set[str] # 仅存储 coin name,如 "PURR"
close_disabled_symbols: set[str] # 仅存储 coin name
问题: 这两个集合以 coin 维度工作,无法区分同一 symbol 的不同配对。例如 PURR|HYPE 需要禁止平仓而 PURR|BTC 不需要时,当前设计无法处理。
影响范围:
config.py:259—is_symbol_allowed()只检查 coin,无法按配对区分config.py:265-270—is_close_disabled()只检查 coinposition_manager.py:243—close_position()调用is_close_disabled(symbol)不传 base_symbolposition_manager.py:1021—_collect_orphan_candidates()用coin in close_disabled_symbols
严重程度: 🟡 中等 — 当前系统实际运行的是单 base_symbol 模式,短期不会触发;但与系统设计目标(配对维度独立)不一致,扩展到多 base 时会暴露。
缺陷 B (中):_pair_cache 无动态刷新机制
文件: src/services/realtime_kline_service_base.py:206-209
现状:
self._pair_cache: Dict[str, List[str]] = {}
self._load_pair_cache() # 仅在 __init__ 时加载一次
问题: 配对关系缓存仅在服务启动时从 DB 加载。如果运行期间 cointegrated_pairs 表中新增了配对关系,服务无法感知,新配对不会被分析。
影响: 需要重启服务才能识别新配对。在系统需要动态添加配对时是瓶颈。
缺陷 C (中):_execute_close 中 position 字段修改在锁外
文件: src/trading/position_manager.py:358-361
现状:
# 这段代码在 _execute_close() 内,此时锁已在 close_position() 入口处释放
position.status = PositionStatus.CLOSED # ← 无锁保护
position.close_time = now # ← 无锁保护
position.realized_pnl = realized_pnl # ← 无锁保护
position.alt_current_price = alt_exit_price
with self._lock:
self._positions.pop(key, None) # ← 只有 pop 有锁
问题: position 对象是共享引用。在 status 被设为 CLOSED 之前,其他线程(如同步线程、止损检查线程)可以通过 self._positions[key] 访问到同一个对象,读到不一致的中间状态(status=CLOSING 但 pnl 已更新)。
对比: 已修复的 Bug #1(line 527)是同类问题。此处是成功路径上的同类遗漏。
缓解因素: Python GIL 使得实际 data race 概率极低,但设计上不一致。
缺陷 D (低):脚本文件 SQL 查询未配对化(~18 处)
影响文件:
src/scripts/backtest_base.pysrc/scripts/optimize_adaptive_zscore.pysrc/scripts/optimize_adaptive_zscore_v2.pysrc/scripts/fix_buffer_loading.pysrc/scripts/backfill_all_data.pysrc/scripts/validate_multicoin_btc_base.pysrc/scripts/validate_ema_std_decouple.pysrc/scripts/query_analyze_result/check_missing_purr_zscore.pysrc/scripts/query_analyze_result/check_buffer_continuity.pysrc/scripts/query_analyze_result/query_eth_zscore.pysrc/scripts/query_analyze_result/query_purr_zscore.pysrc/scripts/query_analyze_result/query_purr_zscore_beyond2_5.pysrc/scripts/query_analyze_result/compute_multi_timeframe_zscore.py
模式: WHERE symbol = %s 缺少 AND base_symbol = %s
影响: 脚本仅用于调试/回测,不影响生产。但当 DB 中同一 symbol 存在多个 base_symbol 的记录时,查询结果会混合不同配对的数据,导致回测/分析结论不准确。
缺陷 E (低):历史数据压缩策略不一致
问题: 数据库迁移只对 新建的压缩段 使用 (symbol, base_symbol) 作为分段键。已有的历史压缩块仍使用旧的 symbol-only 压缩策略。两种压缩策略共存于同一张表。
影响: 不影响查询正确性(WHERE 过滤在压缩之上),但增加了运维理解成本,且查询性能在历史区间可能略有差异。
三、设计层面的不足
不足 1:黑名单机制碎片化
系统存在 三套 独立的黑名单机制,职责边界模糊:
| 机制 | 位置 | 维度 | 生命周期 |
|---|---|---|---|
symbol_blacklist (config) |
config.py:69 |
coin | 静态(env 变量) |
new_coin_blacklist (runtime) |
realtime_kline_service_base.py:244 |
(symbol, base_symbol) ✅ | 运行时(重启清零) |
DB 黑名单 (_blacklist_cache) |
realtime_kline_service_base.py:249 |
(symbol, base_symbol) ✅ | 持久化(24h TTL) |
其中 config 层的 symbol_blacklist 是唯一未配对化的。建议:要么统一为配对维度,要么明确文档化其 "coin 级全局禁止" 的语义。
不足 2:_execute_close 锁策略不对称
close_position() 方法中的三条退出路径锁行为不一致:
| 路径 | 锁状态 | 状态 |
|---|---|---|
| 成功(line 358-366) | 字段修改无锁,仅 pop 有锁 | ⚠️ 不一致 |
| 异常(line 284-287) | with self._lock 恢复 |
✅ 有锁 |
| 正常失败(line 527) | with self._lock 恢复 |
✅ 已修复 |
理想状态应统一:所有 position 状态字段的修改都在锁内完成。
四、总结与优先级建议
| 优先级 | 缺陷 | 建议 |
|---|---|---|
| 🔴 无 | — | 核心生产路径已完整 |
| 🟡 高 | 缺陷 A: 黑名单/禁止平仓未配对化 | 扩展到多 base 前必须修复 |
| 🟡 中 | 缺陷 B: _pair_cache 无刷新 | 添加定时刷新(如 5min 周期)或 DB 变更通知 |
| 🟡 中 | 缺陷 C: 成功路径 position 字段无锁 | 将 status/pnl 修改移入 with self._lock |
| 🟢 低 | 缺陷 D: 脚本 SQL 未配对化 | 批量添加 AND base_symbol = %s |
| 🟢 低 | 缺陷 E: 历史压缩策略不一致 | 文档化或安排重压缩窗口 |
总体评估: 核心生产路径(策略 → 信号 → 仓位管理 → 交易执行 → 数据自愈)的配对维度重构 已基本完成(~95%),3 个关键 Bug 已修复。剩余问题主要集中在:(1) config 层黑名单的配对化设计债务,(2) 辅助设施(缓存刷新、脚本)的配套更新,(3) 锁策略的一致性打磨。在当前单 base_symbol 运行模式下,这些问题不会触发实际故障,但在扩展到多 base 场景前需要逐步清理。