全系统key配对升级 bug6

PairKey 维度优化后的缺陷与不足分析

持久化分析报告。在全系统 Key 已改为 (symbol, base_symbol) 配对维度的前提下,通过实际代码阅读,对已知问题进行确认,并补充新发现的缺陷。

日期:2026-02-19


一、现状验证(已正确落地项)

模块 状态
策略层 strategy.py ✅ 所有状态字典按 (symbol, base_symbol) 存储
仓位管理 position_manager.py _pair_key / 开仓防重复均使用 PairKey
黑名单缓存 realtime_kline_service_base.py ✅ TTLCache key 为 (symbol, base_symbol)
DB 索引 trading_signals init_timescaledb.sql 已用 (symbol, base_symbol, signal_time DESC)
DB 索引 pair_positions init_timescaledb.sql 已用 (symbol, base_symbol, status)
DB 压缩 analysis_results compress_segmentby = 'symbol, base_symbol'
单腿/孤儿仓位 base_symbol="" 统一约定

二、缺陷与不足

缺陷 1:base_symbol 归一化不统一(高优先级)

已记录于 docs/pair-dimension-analysis.md,代码确认仍未修复

位置一src/trading/trade_repository.py:292

# 当前(有风险)
return [(row["symbol"], row["base_symbol"]) for row in rows]

# 应改为
return [(row["symbol"], row.get("base_symbol") or "") for row in rows]

位置二src/services/realtime_kline_service_base.py:1324

# 当前(有风险)
self._blacklist_cache[(row['symbol'], row['base_symbol'])] = True

# 应改为
self._blacklist_cache[(row['symbol'], row.get('base_symbol') or '')] = True

风险:若数据库驱动在异常状态下返回 None,会产生 (sym, None)(sym, "") 两个不同 Key,同一配对被双重追踪,黑名单失效或仓位管理混乱。


缺陷 2:trading_signals 压缩分段未同步为 PairKey(高优先级)⭐ 新发现

位置database/init_timescaledb.sql:457-461

-- 当前(与索引不一致)
ALTER TABLE trading_signals SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'symbol'   -- ← 仅 symbol,缺 base_symbol
);

对比:同文件的查询索引已是 PairKey 维度:

CREATE INDEX IF NOT EXISTS idx_trading_signals_pair
ON trading_signals (symbol, base_symbol, signal_time DESC);  -- ← PairKey ✅

影响

  • TimescaleDB 按 compress_segmentby 分段压缩,查询时优先利用分段减少扫描范围。
  • 当前分段为 symbol,按 (symbol, base_symbol) 查询时需扫描同 symbol 下所有 base_symbol 的数据块,无法利用分段剪枝,压缩后查询性能下降。
  • 迁移文件 20260219_fix_pairkey_indexes.sql 也未修复此处,存量数据库同样受影响。

修复方案(新建迁移):

-- 新迁移文件:database/migrations/20260220_fix_trading_signals_compress.sql
SELECT remove_compression_policy('trading_signals', if_exists => TRUE);
ALTER TABLE trading_signals SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'symbol, base_symbol'
);
SELECT add_compression_policy(
    'trading_signals',
    INTERVAL '30 days',
    if_not_exists => TRUE
);

同时更新 init_timescaledb.sql:460'symbol, base_symbol'


缺陷 3:analysis_results 旧 symbol-only 索引残留(中优先级)

已记录于 docs/pair-dimension-analysis.md,代码确认仍未修复

位置database/init_timescaledb.sql:141-156

-- 旧索引(仍存在,与 PairKey 架构不一致)
CREATE INDEX IF NOT EXISTS idx_analysis_symbol_time
ON analysis_results (symbol, analysis_time DESC);   -- ← 仅 symbol

CREATE INDEX IF NOT EXISTS idx_analysis_kline_time
ON analysis_results (symbol, kline_time DESC);      -- ← 仅 symbol

同文件 init_timescaledb.sql:200-201 已有正确的 PairKey 索引:

CREATE INDEX IF NOT EXISTS idx_analysis_results_symbol_timeframe
ON analysis_results (symbol, base_symbol, analysis_time DESC);  -- ✅

迁移文件漏 DROP20260219_fix_pairkey_indexes.sql 中已 DROP 了 trading_signalspair_positions 的旧索引,但未 DROP analysis_results 的两个旧索引:

-- 迁移文件中缺失的部分:
DROP INDEX IF EXISTS idx_analysis_symbol_time;
DROP INDEX IF EXISTS idx_analysis_kline_time;

影响:存量数据库和新建数据库均保留两个无效旧索引,写入 analysis_results 时需额外维护,增加写入开销。


缺陷 4:recent_analysis 节流维度语义不清(中优先级)

已记录于 docs/pair-dimension-analysis.md,代码确认仍未添加注释

位置src/services/realtime_kline_service_base.py:885, 931, 946

task_key = (symbol, timeframe)   # ← 非 PairKey 维度,不含 base_symbol
# ...
self.recent_analysis[task_key] = current_time

设计意图(正确,但无文档):同一 symbol+timeframe 只调度一次分析任务,任务内部 for base_sym in base_symbols 循环所有配对,以控制 CPU/DB 调用压力。

风险:维护者极易误以为是配对级节流,在排查问题或重构时可能误改 key 格式,破坏原有调度语义。

建议:在该行附近添加注释:

# recent_analysis key 为 (symbol, timeframe),不含 base_symbol。
# 设计意图:同一 K 线下只调度一次分析任务,任务内部对所有配对循环执行
# _analyze_and_alert,避免同 K 线重复触发多次 DB 查询。
# 若需按配对独立节流,需将 key 改为 (symbol, base_symbol, timeframe),
# 并评估 QPS/存储影响。
task_key = (symbol, timeframe)

缺陷 5:策略层入口缺少防御性归一化(低优先级)

已记录于 docs/pair-dimension-analysis.md,代码确认仍未修复

位置src/trading/strategy.py:220, 273, 292, 310, 322...(多处)

def sync_position(self, symbol, base_symbol, ...):
    key = (symbol, base_symbol)   # 若调用方传 None,得到 (sym, None)

def on_position_opened(self, symbol, base_symbol, ...):
    key = (symbol, base_symbol)   # 同上

def cleanup_pair(self, symbol, base_symbol):
    key = (symbol, base_symbol)   # 同上

position_manager.py 中有 or "" 保护:

key = (signal.symbol, signal.base_symbol or "")  # ← 有防御

风险:若调用方误传 base_symbol=None,策略层 key 为 (sym, None),与仓位管理器 key (sym, "") 不一致,导致同一配对在两个模块被识别为不同 key,开仓/平仓回调错位。

修复建议(可选,防御深度):

# 在各对外接口入口统一处理
key = (symbol, base_symbol or "")

三、缺陷汇总表

# 优先级 问题描述 文件位置 文档记录
1 🔴 高 base_symbol None 未归一化,Key 分裂风险 trade_repository.py:292, realtime_kline_service_base.py:1324 已记录
2 🔴 高 trading_signals 压缩分段仍为单 symbol init_timescaledb.sql:460 + 迁移文件漏修 新发现
3 🟡 中 analysis_results 旧索引残留,迁移未 DROP init_timescaledb.sql:142,155 + 迁移文件 已记录
4 🟡 中 recent_analysis 节流维度无注释说明 realtime_kline_service_base.py:885 已记录
5 🟢 低 strategy.py 各入口无防御归一化 strategy.py:220,273,292... 已记录

四、修复建议(按优先级排序)

立即修复(高优先级)

  1. base_symbol 归一化:在 trade_repository.py:292realtime_kline_service_base.py:1324 使用 row.get("base_symbol") or ""
  2. trading_signals 压缩分段:新建迁移文件修复存量库,同时更新 init_timescaledb.sql:460'symbol, base_symbol'

短期修复(中优先级)

  1. 删除遗留索引:在迁移文件或新建迁移中 DROP INDEX IF EXISTS idx_analysis_symbol_time; DROP INDEX IF EXISTS idx_analysis_kline_time;
  2. 添加代码注释:在 realtime_kline_service_base.py:885 附近说明 (symbol, timeframe) 节流的设计意图。

可选修复(低优先级)

  1. 策略入口防御strategy.py 各公共方法改用 key = (symbol, base_symbol or "")

五、数据流与 Key 维度核对图(更新版)

WS/数据层(coin 维度,合理)
  └─ "{coin}:{interval}" / "{coin}:l2Book"
        ↓
应用层(PairKey = (symbol, base_symbol))
  ├─ 策略 strategy.py ✅
  ├─ 仓位 position_manager.py ✅
  ├─ 黑名单缓存 ✅(但 DB 恢复时未归一化 ⚠️)
  └─ 分析节流 recent_analysis((symbol, timeframe),有意设计,缺注释 ⚠️)
        ↓
数据库层
  ├─ analysis_results:PairKey 压缩 ✅,旧索引残留 ⚠️
  ├─ trading_signals:PairKey 索引 ✅,压缩分段未更新 🔴
  ├─ pair_positions:PairKey 索引 ✅,压缩 N/A
  └─ trade_repository 查询:base_symbol 未归一化 🔴

Read more

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG