全系统key配对升级 bug5
配对维度优化后系统缺陷与不足分析
持久化自分析报告。在全系统 Key 已改为 (symbol, base_symbol) 配对维度的前提下,从一致性、边界情况、索引与节流设计、可维护性四方面分析当前仍存在的缺陷与可改进点。
一、当前状态概览
全系统配对维度改造已基本落地:
- 类型:
src/trading/models.py中PairKey = (symbol, base_symbol),单腿/孤儿用base_symbol=""。 - 策略/仓位/配置:策略引擎、仓位管理器、配置的配对覆盖均按
PairKey或"ALT|BASE"键存取。 - DB:
trading_signals、pair_positions、analysis_results的索引/压缩已通过database/migrations/20260219_fix_pairkey_indexes.sql迁移为 PairKey 维度;symbol_blacklist、cointegrated_pairs表结构本身即配对维度。 - 实时服务:黑名单/新币冷却/配对关系缓存均使用
(symbol, base_symbol);WebSocket 缓存仍为按币种 key(coin:interval、coin:l2Book),与交易所数据按币种推送一致,属合理设计。
以下为仍存在的缺陷与不足及建议。
二、缺陷与不足
1. base_symbol 归一化不统一(潜在 Key 分裂)
现象:
部分路径用 base_symbol or "",部分直接用 row["base_symbol"] 或参数,若未来出现 None(如 DB 连接器、历史数据)会得到 (sym, None) 与 (sym, "") 两套 key,导致同一逻辑配对被当成两个 key。
位置:
src/trading/trade_repository.py第 269 行:get_positions_by_symbols已用row.get("base_symbol") or "";- 同文件第 292 行:
get_known_pair_relations返回row["base_symbol"],未做or ""。若某处返回None,后续用该 list 做 key 会得到(sym, None)。 src/services/realtime_kline_service_base.py第 1323 行:_load_blacklist_from_db使用(row['symbol'], row['base_symbol']),未防御None。
建议:
在全系统约定「配对 Key 的 base 为空一律用 ""」。
get_known_pair_relations改为(row["symbol"], row.get("base_symbol") or "")。- 黑名单恢复改为
(row['symbol'], row.get('base_symbol') or '')。 - 可选:在
src/trading/strategy.py的_params_for、on_tick等入口对base_symbol做防御性base_symbol = base_symbol or "",避免调用方漏传导致(sym, None)。
2. analysis_results 遗留“仅 symbol”索引
现象:
database/init_timescaledb.sql 中仍保留:
idx_analysis_symbol_timeon(symbol, analysis_time DESC)(第 142–143 行)idx_analysis_kline_timeon(symbol, kline_time DESC)(第 155–156 行)
而业务查询已按 PairKey 使用 (symbol, base_symbol, ...)(如 src/utils/database/timescaledb.py 的 query_avg_zscore_4h、orchestrator 的灌缓冲区 SQL)。
迁移 20260219_fix_pairkey_indexes.sql 只改了 trading_signals、pair_positions 和 analysis_results 的压缩,未删除 analysis_results 的旧索引。
影响:
写入 analysis_results 时多维护两个 symbol-only 索引,增加写入开销与存储;新库从 init 脚本建库会一直带着这两个索引,与「全系统 PairKey 维度」不一致。
建议:
- 在迁移中或新建迁移中:对 analysis_results 执行
DROP INDEX IF EXISTS idx_analysis_symbol_time;与DROP INDEX IF EXISTS idx_analysis_kline_time;(若仍有脚本按 symbol+kline_time 查,需先改为按 PairKey 查并确认无依赖后再删)。 - 或在文档中明确:查询 analysis_results 一律带 base_symbol,以 PairKey 索引为准,旧索引仅作兼容保留并计划废弃。
3. recent_analysis 节流维度与“配对维度”不一致
现象:
src/services/realtime_kline_service_base.py 中 recent_analysis 的 key 为 (symbol, timeframe)(第 884、930、946 行)。
即:同一 symbol 在同一 timeframe 下,无论对多少个 base(如 PURR|HYPE、PURR|BTC)只做一次「分析任务」节流,然后在该任务内对多个 base 依次执行 _analyze_and_alert。
影响:
- 若期望「按配对维度」节流(例如 PURR|HYPE 与 PURR|BTC 独立节流),当前实现不满足。
- 若期望「按 symbol+timeframe 只调度一次、再对多 base 展开」以控制 CPU/DB 压力,则当前设计一致,但文档未说明,易被误以为按 pair 节流。
建议:
- 在
realtime_kline_service_base或相关文档中明确:recent_analysis 是 (symbol, timeframe) 维度的节流,不是 (symbol, base_symbol, timeframe),并说明设计理由(如减少同一 K 线下的重复调度)。 - 若产品上需要「按配对」节流(例如某配对分析过密则单独限流),则需将 key 改为
(symbol, base_symbol, timeframe)并评估 QPS/存储影响。
4. 策略层 Key 的防御性归一化
现象:
src/trading/strategy.py 中所有使用 key 的地方均为 key = (symbol, base_symbol)(如第 220、273、292、324 行等)。若上游误传 base_symbol=None,会得到 (sym, None),与 position_manager / orchestrator 使用的 (sym, "") 不一致,导致「同一配对」在策略与仓位侧被当作不同 key。
建议:
在策略入口做一次统一归一化(不影响现有正确调用方):
- 在
_params_for、on_tick、prime_buffer、get_adaptive_z、is_ready、cleanup_pair等对外接口中,将base_symbol视为可选并执行base_symbol = base_symbol or ""(或仅在构造 key 时使用(symbol, base_symbol or "")),保证策略内部所有 dict/set 的 key 与仓位、orchestrator 一致。
5. 单腿/孤儿仓位的 PairKey 一致性(已满足,仅作确认)
结论:
单腿或 base 腿残留转孤儿时,均使用 base_symbol="",且 pair_positions 表 base_symbol NOT NULL 存为 ""。
position_manager 的 _pair_key、开仓 key、orchestrator 的仓位同步与退出均使用 base_symbol or "",与策略、DB 一致,此处无缺陷。
6. WebSocket 缓存为“按币种”的说明
结论:
src/utils/websocket/enhanced_ws_manager.py 使用 coin:interval、coin:l2Book 等按币种缓存,与交易所数据按币种推送一致;配对由「alt 币种 + base 币种」在业务层组合。设计正确,无需改为 pair 维度。
建议在注释或架构文档中简短说明:WS 缓存为 coin 维度,配对维度在应用层用 (symbol, base_symbol) 组合,避免后续误改成 pair key。
三、数据流与 Key 维度小结(便于核对)
flowchart LR
subgraph ws [WS/数据层]
CoinKline["K线/L2 按 coin"]
CoinKline --> App
end
subgraph app [应用层 PairKey]
App["(symbol, base_symbol)"]
App --> Strategy
App --> PositionManager
App --> Blacklist
App --> RealtimeThrottle["recent_analysis: (symbol, tf)"]
end
subgraph db [DB]
Strategy --> AR["analysis_results"]
PositionManager --> PP["pair_positions"]
Blacklist --> SB["symbol_blacklist"]
end
- 一致:策略、仓位、黑名单、DB 表与迁移均为 PairKey;WS 为 coin 维度且合理。
- 待统一:base_symbol 的
None→""归一化(repo/黑名单加载/策略入口);analysis_results 旧索引的清理或文档说明;recent_analysis 节流维度的文档或改为 pair 维度。
四、建议优先级
| 优先级 | 项 | 动作 |
|---|---|---|
| 高 | base_symbol 归一化 | get_known_pair_relations、_load_blacklist_from_db 使用 or "";策略入口可选防御 (symbol, base_symbol or "") |
| 中 | analysis_results 索引 | 评估后 DROP 旧 symbol-only 索引,或在文档中标注以 PairKey 索引为准并计划废弃旧索引 |
| 中 | recent_analysis 语义 | 文档明确 (symbol, timeframe) 节流;若需按配对节流则改 key 为 (symbol, base_symbol, timeframe) |
| 低 | 文档 | 说明 WS 缓存为 coin 维度、PairKey 仅在应用层;新贡献者须知「空 base 一律 ""」 |
按上述顺序处理可进一步消除配对维度优化后的残留不一致与歧义,并避免未来因 None/旧索引/节流语义导致的隐蔽问题。