全系统key配对升级 bug22
配对维度优化后系统缺陷与不足分析
一、当前状态概览
核心交易与持久化已统一为 PairKey = (symbol, base_symbol) 维度:
- strategy.py:
_baselines、_positions、_exit_pending、_last_trade_time等均为dict[PairKey, ...]或set[PairKey] - position_manager.py:
_positions、_opening_pairs以PairKey索引,_pair_key(pos)统一从PairPosition取 key - orchestrator.py:
_pair_rate_limiters按(symbol, base_symbol)限速 - config.py:
pair_strategy_overrides键为"ALT|BASE",get_strategy_params(symbol, base_symbol)用split('/')[0]对齐;is_symbol_allowed/is_close_disabled支持币种级与配对级 - realtime_kline_service_base.py:
new_coin_blacklist、_blacklist_cache以(symbol, base_symbol)为 key - DB:
database/migrations/20260219_fix_pairkey_indexes.sql已将trading_signals、pair_positions、analysis_results的索引/压缩改为(symbol, base_symbol)维度
有意保持「非配对」维度的部分:
- K 线、订阅、活跃币种:按 symbol(及 timeframe)合理
- 分析任务节流:
task_key = (symbol, timeframe),一个 symbol 一次任务内对所有 base 循环分析,属设计选择 - 孤儿仓位:显式用
(orphan_symbol, "")与base_symbol=""、pair_mode="single"表示单腿
二、已识别的缺陷与风险
1. 策略层 cleanup_pair 未被使用
- 位置:
src/trading/strategy.py第 328 行定义cleanup_pair(symbol, base_symbol),会清除_baselines在内的全部该配对状态。 - 现状:全代码库仅通过
on_position_closed做平仓后清理;on_position_closed有意不清理_baselines(注释:“不清理 _baselines 缓冲区可复用”)。 - 影响:若业务上需要「彻底移除某配对」(如从 cointegrated_pairs 删除且不再交易),没有统一入口释放该配对的
_baselines等状态;长期运行且配对集合变化时可能多占内存。属于设计/使用不完整,非 Key 维度错误。
2. 孤儿 key (symbol, "") 与 None 的 key 一致性
- 约定:孤儿在
_positions中为(orphan_symbol, ""),各处用row.get("base_symbol") or ""归一化。 - 风险:若某处将
base_symbol=None传入并作为 dict 的 key 成分(例如(s, None)),会与(s, "")形成不同 key,导致重复条目或查不到。 - 现状:
trade_repository.py、position_manager.py、orchestrator.py已用row.get("base_symbol") or ""。realtime_kline_service_base.py多处if base_symbol is None: base_symbol = self.base_symbol,未统一写为or "",若self.base_symbol为 None 仍可能把 None 写入_blacklist_cache。
- 建议:所有构造 PairKey 或写入
_blacklist_cache的地方统一为(symbol, base_symbol or ""),避免 None 进入 key。
3. get_known_pair_relations 含孤儿 key 的语义
- 位置:
src/trading/trade_repository.py第 275–293 行:从pair_positions的 DISTINCT (symbol, base_symbol) 返回,含base_symbol = ''的孤儿行。 - 使用:
position_manager.py第 1216–1224 行在「配对孤儿」阶段遍历known_pairs,用symbol_to_coin(base_symbol);对(alt, "")得base_coin="","" not in remaining为 True 会 continue,逻辑正确。 - 不足:若未来其他调用方假设「返回的均为双腿配对」,可能误用;接口注释未写明「可能包含 (symbol, "")」。
三、设计取舍(非缺陷)
- 任务节流按 (symbol, timeframe):同一 symbol 多 base 共享一次分析任务,不按配对节流。当前设计合理;若将来要「按配对限流」需改 task_key。
- 黑名单/close_disabled 为字符串集合:支持
"PURR"与"PURR|HYPE",在is_symbol_allowed/is_close_disabled(symbol, base_symbol)内解析,行为与配对维度一致,仅 key 表示形式不同。 - 孤儿仅由 position_manager 与止损监控处理:分析层
_pair_cache来自 cointegrated_pairs,不会出现(symbol, ""),不会对孤儿产生信号,设计一致。
四、可改进点(建议)
-
统一 base_symbol 空值
- 在 realtime 服务及所有构造 PairKey / 黑名单 key 处,将
base_symbol is None时设为base_symbol = base_symbol or "",避免(s, None)与(s, "")并存。
- 在 realtime 服务及所有构造 PairKey / 黑名单 key 处,将
-
文档化孤儿约定
- 在
position_manager.py或models.py的 PairKey 注释中写明:孤儿仓位使用(symbol, "")且pair_mode="single";迭代_positions时需考虑base_symbol == ""。
- 在
-
get_known_pair_relations 接口说明
- 在 docstring 中注明返回列表可能包含
(symbol, ""),表示历史存在过的单腿/孤儿仓位,调用方不应假设全部为「双腿配对」。
- 在 docstring 中注明返回列表可能包含
-
cleanup_pair 的使用策略
- 若需要「配对永久下线」时释放策略状态:在配对从 cointegrated_pairs 移除或类似流程中显式调用
strategy.cleanup_pair(symbol, base_symbol);否则在文档中说明「仅用 on_position_closed,有意保留 _baselines 复用」即可。
- 若需要「配对永久下线」时释放策略状态:在配对从 cointegrated_pairs 移除或类似流程中显式调用
五、结论
- 配对维度改造在交易栈、配置、DB、realtime 黑名单/缓存上已一致,未发现「仍按 symbol 单维度存状态」的遗漏点。
- 主要风险:
base_symbol为None时可能产生(s, None)与(s, "")的 key 不一致,建议全路径统一为or ""。 - 次要不足:
cleanup_pair未接入流程、get_known_pair_relations与孤儿 key 的语义未在文档中写清;均为可文档化或小范围改动即可解决的问题。