全系统 Key 改为配对维度 (symbol, base_symbol)-设计文档
全系统 Key 改为配对维度 (symbol, base_symbol)
目标
- 所有“按币种/标的”区分的 key 统一改为配对维度:
(symbol, base_symbol)。 - 同一 symbol 对多个 base 时:基线、持仓、突破状态、冷却、退场重试等均按配对独立,互不覆盖、语义清晰。
- 单 base 场景(single 或全局唯一 base):
base_symbol使用""或配置的默认 base,保持兼容。
1. 配对 Key 约定
- 类型:
tuple[str, str],即(symbol, base_symbol)。base_symbol为空时用""(single 模式或未指定时)。 - 辅助:在
src/trading/models.py或src/utils中提供:def pair_key(symbol: str, base_symbol: str | None) -> tuple[str, str]
返回(symbol, base_symbol or ""),供各处统一生成 key。- 若需字符串形式(日志/缓存 key):
f"{symbol}|{base_symbol or ''}"。
- 兼容:现有 single 模式或“仅一个 base”时,调用方传入
base_symbol=""或配置的base_symbol,行为与“单配对”一致。
2. 策略层 (src/trading/strategy.py)
- 涉及字典(全部改为以
pair_key(symbol, base_symbol)为 key):_baselines_prev_above_threshold_positions_last_trade_time_exit_pending(改为 set of pair_key)_signal_history(key 可改为f"{pair_key}:{direction}:{ts}"或保留 pair 维度)_last_near_thresh_time_last_status_time_tick_count_last_adaptive_z
- 接口:
process_tick(symbol, z4h, timestamp, ..., base_symbol: str | None = None)
内部key = pair_key(symbol, base_symbol),所有读写用key。_get_baseline(symbol, base_symbol)→ 使用pair_key(symbol, base_symbol)查找/创建。is_ready(symbol, base_symbol)、get_adaptive_z(symbol, base_symbol)等增加base_symbol参数并改用 pair key。prime_buffer(symbol, base_symbol, z4h_values):按配对灌入历史 z4h;若无 base 维度则base_symbol=""。on_position_opened(symbol, base_symbol, direction, entry_z4h, adaptive_z, ...)、on_position_closed(symbol, base_symbol, ...)、sync_position(symbol, base_symbol, ...)、on_exit_failed(symbol, base_symbol)、on_entry_rejected(symbol, base_symbol)、clear_symbol(symbol, base_symbol)等:凡带 symbol 的入口均增加base_symbol,内部用 pair key。
- PositionTracker:可增加
base_symbol: str字段便于日志与告警,key 仍为 pair。 - 策略参数:
_params_for(symbol)若当前支持按 symbol 覆盖,可保留仅 symbol 的 overrides,或扩展为_params_for(symbol, base_symbol)(按需)。
3. 仓位管理层 (src/trading/position_manager.py)
- 内存结构:
_positions: dict[tuple[str, str], PairPosition],key =(symbol, base_symbol)。 - 开仓:
open_position(signal)中,key = (signal.symbol, signal.base_symbol or ""),self._positions[key] = position。 - 平仓 / 查询:所有原先
_positions.get(symbol)、_positions.pop(symbol)改为按(symbol, base_symbol)查找;对外接口需同时接受symbol+base_symbol(或传入PairPosition取其二)。- 例如:
close_position(symbol, base_symbol, signal, reason, ...)或close_position(position)内部用(position.symbol, position.base_symbol)作 key。
- 例如:
- open_positions:保持返回
list[PairPosition]即可(每个 position 已有 symbol、base_symbol)。 - 恢复/对账:从 DB 读出的每行
(symbol, base_symbol, ...)转为key = (row["symbol"], row.get("base_symbol") or ""),再_positions[key] = position;避免按 symbol 覆盖。 - 孤儿收纳:
_adopt_paired_orphans等处写入_positions时使用(position.symbol, position.base_symbol)为 key。 - sync_with_exchange、recover:遍历
_positions.items()时 key 为 pair,value 为PairPosition;需要“按 symbol 列出某 symbol 下所有仓位”时,用列表推导按pos.symbol == symbol过滤即可。
4. 仓储层 (src/trading/trade_repository.py)
- get_positions_by_symbols(symbols):改为 get_positions_by_pairs(pairs: list[tuple[str, str]]) 或保留原名但语义改为“按配对查询”:
- SQL:
WHERE (symbol, base_symbol) = ANY(%s)或WHERE (symbol, base_symbol) IN (...),参数为[(s, b) for (s, b) in pairs]。 - 返回:
dict[tuple[str, str], dict],即{(row["symbol"], row.get("base_symbol") or ""): row for row in rows},这样同一 symbol 不同 base 不会互相覆盖。
- SQL:
- 其他:若还有按 symbol 查仓位的方法,改为按 (symbol, base_symbol) 或返回 list 由调用方按 pair 建 dict。
- get_known_pair_relations:当前返回
{alt_symbol: base_symbol},一对多时会丢信息;可改为list[tuple[str, str]]或{(alt, base): True},供孤儿收纳等使用。需与 position_manager 的孤儿逻辑一起看。
5. 编排器 (src/trading/orchestrator.py)
- process_analysis:已有
symbol、multi_period_result.get("base_symbol"),调用策略时始终传入 base_symbol(可为""):self._strategy.process_tick(symbol, z4h, timestamp, ..., base_symbol=multi_period_result.get("base_symbol") or "")
- 退场:
on_exit_signal(reversion_info)中reversion_info已含symbol、base_symbol;查找要平的仓位时用(reversion_info.symbol, reversion_info.base_symbol)在 position_manager 中查找。 - 入场:
on_entry_signal成功后on_position_opened(symbol, base_symbol, direction, ...);sync_position时对每个pos传pos.symbol, pos.base_symbol。 - 启动时灌历史 z4h:若 DB 的
analysis_results按 (symbol, base_symbol) 存,则按配对查询并灌入:- 先得到“待交易配对”列表(可从配置或 cointegrated_pairs 等来源);
- 对每个
(sym, base_sym)查询WHERE symbol = %s AND base_symbol = %s ... zscore_4h,再prime_buffer(sym, base_sym, z4h_values)。
- “是否有该 symbol 下任意持仓”:若仍需用于黑名单豁免等,可提供
has_open_position_for_symbol(symbol):在 position_manager 中判断是否存在任意pos.symbol == symbol;退场/策略状态仍严格按 pair 判断。
6. 服务层 (src/services/realtime_kline_service_base.py)
- _has_open_position:改为 _(symbol, base_symbol)(或保留
_has_open_position(symbol, base_symbol=None),若传入 base_symbol 则按配对查,否则按 symbol 是否存在任意持仓)。- 实现:问 position_manager 是否有
(symbol, base_symbol)的仓位;若接口是has_position(symbol, base_symbol)则直接调。
- 实现:问 position_manager 是否有
- _trigger_strategy_if_ready:已传
base_symbol=base_sym,保证process_analysis(..., multi_period_result=...)里能拿到 base_symbol 并一路传到 strategy.process_tick。 - 黑名单、配对缓存:已是 (symbol, base_symbol),无需改 key;仅需保证“有持仓”判断与 pair 一致(例如某 symbol 对 base A 有仓位,则仅对 (symbol, A) 豁免拉黑,不影响 (symbol, B))。
7. 数据库与历史数据
- pair_positions:表已有
symbol、base_symbol,无需改表结构;唯一约束若有需要可加UNIQUE(symbol, base_symbol)在 (open/opening/closing) 维度,防止同一配对重复开仓(按业务决定是否必要)。 - analysis_results:已有
symbol、base_symbol;灌 buffer 的 SQL 改为WHERE symbol = %s AND base_symbol = %s AND zscore_4h IS NOT NULL ...,按配对取历史 z4h。 - symbol_blacklist:已是 (symbol, base_symbol),无需改。
8. 其他调用点
- risk_manager:若存在按
pos.symbol == signal.symbol的判断,改为同时比较base_symbol,或改为按 pair 查找仓位。 - executor:执行层主要用
signal.symbol、signal.base_symbol与position.symbol、position.base_symbol,不直接维护 dict key;只需保证传入的 position/signal 来自“按 pair 查到的”即可。 - 告警/格式化:已带 symbol、base_symbol,无需改 key,仅保证数据源来自按 pair 的仓位与信号。
9. 实施顺序建议
- 模型与工具:增加
pair_key(symbol, base_symbol)及(可选)字符串形式,统一放在一处(如models.py或trading/utils.py)。 - 策略层:先改
_baselines、_positions及process_tick的 key 与参数,再改其余字典与对外接口(prime_buffer、sync_position、on_position_opened 等)。 - 仓储层:
get_positions_by_symbols→get_positions_by_pairs及返回dict[(symbol, base_symbol), row],并调整调用方(position_manager)。 - 仓位管理层:
_positions改为 pair key,所有 get/pop/赋值与恢复、对账、孤儿收纳改为按 pair。 - 编排器:传入/使用 base_symbol,灌 buffer 按配对查 DB,退场/入场/同步均按 pair。
- 服务层:
_has_open_position(symbol, base_symbol)及与 position_manager 的接口对齐。 - 回归:单 base / single 模式用
base_symbol=""或默认 base 跑通;多 base 场景下同一 symbol 两配对基线与持仓互不干扰。
10. 风险与注意点
- 向后兼容:若已有未带 base_symbol 的仓位(DB 中 base_symbol 为空),加载时统一为
(symbol, ""),与 single 模式一致。 - 配置:若存在“按 symbol 禁用平仓/关闭”等,需明确是“该 symbol 下所有配对”还是“仅某配对”;通常按配对更清晰。
- 日志与监控:所有按 pair 的 key 在日志中建议打印为
symbol vs base_symbol,便于排查。
改造完成后,基线更新、持仓、突破状态、冷却、退场重试等全部按配对维度隔离,数据不再混用或互相覆盖。