全系统key配对升级 bug16
配对维度优化后 — 缺陷与不足分析
分析范围:全系统 Key 改为配对维度
(symbol, base_symbol)后的当前状态
日期:2026-02-20
一、已正确落地的部分(简要)
- 内存状态:
PositionManager._positions、AdaptiveBollingerStrategy._baselines/_positions/_last_adaptive_z等均按PairKey = (symbol, base_symbol)存储。 - 配置与风控:
get_strategy_params(symbol, base_symbol)、is_symbol_allowed/is_close_disabled支持币种级与配对级。 - 持久化:
trading_signals、pair_positions、analysis_results、symbol_blacklist均含base_symbol;迁移脚本已把索引/压缩分段改为 PairKey。 - 策略与编排:入场/退场、止损、仓位同步、信号去重键
symbol:base_symbol:direction:ts均按配对维度处理。 - 分析层:黑名单、新币黑名单的 key 均为
(symbol, base_symbol);分析结果写入带base_symbol;历史 z4h 灌入按(symbol, base_symbol)分组。
二、缺陷(建议修复)
1. 新币黑名单与取消订阅的维度不一致(严重)
位置:src/services/realtime_kline_service_base.py(约 1116–1148 行)
现象:
new_coin_blacklist 按配对存储:pair_key = (symbol, base_symbol)。但“数据不足”时对**整个币种(coin)**取消 K 线订阅:
coin = symbol.split('/')[0]
subscriptions_to_remove = [
{"type": "candle", "coin": coin, "interval": "5m"},
...
]
self.ws_manager.remove_subscriptions(subscriptions_to_remove)
问题:
例如仅 PURR|HYPE 因 4H 数据不足被加入 new_coin_blacklist,却会取消整个 PURR 的订阅,导致 PURR|BTC 等其它配对也不再收到 K 线,被错误地“连带”停掉。
建议:
- 方案 A(推荐):取消订阅前判断:仅当该
symbol在_pair_cache中的所有 base 对应的配对都已出现在new_coin_blacklist时,才取消该 symbol 的订阅;否则只把当前配对加入黑名单,不取消订阅。 - 方案 B:不在此处取消订阅,仅做“该配对不分析”;若需节省资源,再单独做“按 symbol 聚合、全部配对都不可用再退订”的逻辑。
2. 数据自愈仅针对默认 base,多 base 配对未覆盖
位置:src/services/realtime_kline_service_base.py(约 1717–1722 行)
现象:
自愈配对列表为:
heal_pairs = [
(s, self.base_symbol) for s in self.symbols
if s != self.base_symbol and (s, self.base_symbol) in pairs_with_data
]
即只自愈 (symbol, self.base_symbol),未使用 _pair_cache 中的多 base。
问题:
当同一 alt 对应多个 base(如 PURR|HYPE、PURR|BTC)时,只有 (PURR, self.base_symbol) 会参与自愈;若默认 base 为 HYPE,则 PURR|BTC 缺数时不会被自愈。
建议:
从 _pair_cache 构建需自愈的配对列表,例如:
- 对每个
s in self.symbols,取bases = self._pair_cache.get(s, []),若为空则用[self.base_symbol]; heal_pairs = [(s, b) for s in self.symbols for b in bases if s != b and (s, b) in pairs_with_data]
(并去重、控制并发/超时,避免一次自愈过多配对)。
三、不足与改进建议(非致命)
3. get_positions_by_symbols 仅按 symbol 过滤
位置:src/trading/trade_repository.py(约 257–262 行)
现状:
WHERE symbol = ANY(%s),返回的是“这些 symbol 下的所有仓位”(含不同 base_symbol)。
当前唯一调用方是 PositionManager._build_orphan_positions,在内存中再按 (row["symbol"], row.get("base_symbol") or "") 分组使用,逻辑正确。
建议:
保持现状即可;若未来有“按配对查询仓位”的接口,建议新增 get_open_positions_by_pairs(pairs: list[PairKey]),避免误用“按 symbol 列表”的语义。
4. 每日统计无配对维度
位置:src/trading/trade_repository.py — update_daily_stats
表:daily_trading_stats(stat_date, network, ...)
当前设计是“按日+网络”的全局汇总,无 (symbol, base_symbol)。
建议:
若产品需要“按配对看每日表现”,可后续增加按 PairKey 的统计表或字段;当前若仅看全局,可保持不变。
5. 历史已压缩 Chunk 仍为旧分段方式
位置:迁移说明 database/migrations/20260219_fix_pairkey_indexes.sql 末尾
现状:
新数据已按 (symbol, base_symbol) 分段压缩;已存在的压缩 chunk 仍是按旧分段(如仅 symbol)压缩的,不会自动重写。
建议:
若需对历史数据也按 PairKey 分段压缩,可对相关 hypertable 执行 decompress_chunk + 再压缩(需在低峰期、并评估 I/O 与锁);否则仅接受“历史 chunk 保持旧分段,新数据新分段”的混合状态,并在文档中说明。
6. 入队节流维度为 (symbol, timeframe),不含 base
位置:realtime_kline_service_base.py 注释(约 885 行)
现状:
同一根 K 线只调度一次分析任务,任务内部对该 symbol 的所有 base 循环执行 _analyze_and_alert。节流 key 为 (symbol, timeframe),不按 base 分开。
结论:
这是有意设计(一根 K 线驱动多配对分析),与配对维度不冲突;仅需在文档中明确“节流按 symbol+timeframe,分析按 (symbol, base_symbol)”。
四、小结
| 类型 | 描述 | 优先级 |
|---|---|---|
| 缺陷 1 | 新币黑名单按配对,但取消订阅按币种,导致其它配对被连带停数据 | 高 |
| 缺陷 2 | 数据自愈只考虑 (symbol, self.base_symbol),多 base 配对未自愈 |
中 |
| 不足 3–6 | 仓库接口语义、每日统计、历史压缩、节流维度等 | 低/文档 |
建议优先修复缺陷 1(新币黑名单与取消订阅的配对/币种一致性),再按需实现缺陷 2 的多 base 自愈与文档化其余不足。