全系统key配对升级 bug15

系统配对维度重构后缺陷与不足分析

背景

项目在 f5543c3 提交中将全系统 Key 从单一 symbol 改为 PairKey = (symbol, base_symbol) 配对维度。此后经过 14 次调试提交(debug 1-13)进行修复。本分析基于当前 HEAD (2ab27b0) 状态,识别出重构后仍存在的缺陷和不足。


一、高优先级缺陷(影响正确性/资金安全)

1. sync_with_exchange 未更新 pair 模式下 base_current_price

  • 文件: src/trading/position_manager.py:960-970
  • 问题: sync_with_exchange 内的 all_mids 价格更新循环只更新了 alt_current_price完全跳过了 base_current_price
  • 影响: 在两次 _stop_loss_monitor 轮次之间,base_current_price 可能是过时值,导致同步线程中 base 腿消失判断后的 PnL 估算不准确
  • 修复: 在 sync_with_exchange 的价格更新循环中增加 pair 模式 base 价格更新逻辑(参考 update_position_prices() 的实现)

2. 孤儿仓位 entry_adaptive_z=0 导致均值回归退出永久失效

  • 文件: src/trading/strategy.py:572-574
  • 问题: _check_exitif abs(baseline) < 1e-9: return None — 当 entry_adaptive_z=0.0 时,均值回归退出信号永远不会触发
  • 影响范围: 所有通过以下路径产生的孤儿仓位:
    • _adopt_residual_base_leg() 创建的残留 base 腿仓位
    • _build_orphan_positions() 中 B 路径(配对孤儿)和 C 路径(单腿孤儿)
    • 仓位同步中收纳的、DB 无记录的交易所仓位
  • 当前缓解: 这些仓位仅依赖止损/移动止损/超时退出(已在告警中注明),但缺少策略级退出能力
  • 建议: 在 _position_sync 中尝试用当前 adaptive_z 回填孤儿的 entry_adaptive_z(orchestrator.py:763-766 已有部分实现,但仅在新收纳时执行一次)

3. _collect_orphan_candidates 缺少配对级 close_disabled 检查

  • 文件: src/trading/position_manager.py:1217
  • 代码: self._config.is_close_disabled(coin_to_symbol(coin))
  • 问题: 只传了 symbol,缺少 base_symbol 参数,配对级 close_disabled(如 PURR|HYPE)检查被绕过
  • 影响: 配对级禁止平仓的代币可能被错误收纳为孤儿仓位
  • 修复: 此处无法确定 base_symbol(因为只有交易所 coin 信息),需在后续 _build_orphan_positions 中加入配对级过滤

4. 策略缓冲区灌入的 N+1 查询问题

  • 文件: src/trading/orchestrator.py:137-163
  • 问题: 先查所有 DISTINCT (symbol, base_symbol) 对,再对每个配对单独执行历史 z4h 查询
  • 影响:
    • 启动时间随历史配对数线性增长(若有 100 个历史配对 = 100 次 DB 查询)
    • 无黑名单过滤前置优化:先查 DB 再检查 is_symbol_allowed,浪费了被禁配对的查询
  • 建议: 改用批量查询 + 应用层分组;或将 WHERE 中加入白名单/黑名单过滤

二、中优先级缺陷(影响健壮性/运维效率)

5. WebSocket 健康监控 warning/timeout 阈值相同

  • 文件: src/config.py:114-115
  • 当前值: WS_HEALTH_MONITOR_TIMEOUT = 15, WS_HEALTH_MONITOR_WARNING_THRESHOLD = 15
  • 问题: 两个阈值完全相同,warning 日志在触发断连的同一时刻才输出,丧失了预警价值
  • 修复: 将 warning 阈值设为 timeout 的 60-70%,如 WARNING=10, TIMEOUT=15

6. process_analysis 强制要求非空 base_symbol

  • 文件: src/trading/orchestrator.py:315-317
  • 代码: if not base_symbol: return False
  • 问题: 当 pair_mode="single" 时,分析层可能传入空的 base_symbol(因为单币模式不关心基准币种),但 orchestrator 会将信号直接丢弃并记录 ERROR
  • 影响: 单币模式下合法信号被静默拒绝
  • 建议: 当 pair_mode="single" 时允许 base_symbol 为空,使用 DEFAULT_BASE_SYMBOL 或空字符串降级

7. get_positions_by_symbols 查询缺少 base_symbol 去重

  • 文件: src/trading/trade_repository.py:256-269
  • 问题: WHERE symbol = ANY(%s) 只按 symbol 查询,结果 dict 用 (symbol, base_symbol) 做 key
  • 边界场景: 同一 symbol 存在多条不同 base_symbol 的活跃仓位时,只有最后一条被保留(dict 覆盖)
  • 修复: 改为返回 list 或使用 defaultdict(list) 防止覆盖

8. 信号去重 cooldown 硬编码

  • 文件: src/trading/strategy.py:98
  • 代码: self._signal_cooldown = 60 # 秒
  • 问题: 60 秒去重窗口无法通过配置调整,不同配对可能需要不同的去重策略
  • 建议: 改为从 StrategyParams 读取,或至少提取为类级常量

9. 未使用的 Redis 依赖

  • 文件: pyproject.toml
  • 问题: redis>=7.1.0 被声明但代码中无任何 import redis
  • 影响: 增加安装时间、依赖冲突风险、攻击面
  • 修复: 移除或注释

10. _adopt_residual_base_leg 的 close_disabled 检查逻辑有误

  • 文件: src/trading/position_manager.py:668
  • 代码: if self._config.is_close_disabled(original_position.symbol, orphan_symbol)
  • 问题: 检查的是"原始配对(alt, base)是否禁用平仓",但此处要判断的是"孤儿币种本身是否禁用平仓"
  • 应改为: 同时检查 is_close_disabled(orphan_symbol, "") — 即孤儿作为单币仓位时是否被禁止平仓

三、低优先级 / 架构改进建议

11. 测试覆盖严重不足

  • 现状: 整个交易模块(strategy/position_manager/orchestrator/risk_manager)无任何单元测试
  • .gitignore 排除了 test*tests/ 目录
  • 唯一测试文件为 src/utils/data_healing/test_basic.py(手动 print + assert 模式)
  • 风险: 配对维度重构后的正确性完全依赖手动验证和线上调试(14 次 debug 提交为佐证)
  • 建议: 优先为 AdaptiveBollingerStrategyRiskManager 编写关键路径单元测试

12. 配对级 stop_loss_pct / max_hold_hours 已支持但移动止损参数未支持

  • 文件: src/trading/config.py vs src/trading/risk_manager.py
  • 问题: trailing_stop_activation_pcttrailing_stop_callback_pct 仅在 TradingConfig 全局层定义,不在 StrategyParams
  • 影响: 所有配对共享相同的移动止损参数,无法按配对差异化
  • 建议: 将移动止损参数加入 StrategyParams,与 stop_loss_pct 同级管理

13. 宽泛异常捕获

  • 多处: except Exception as e 用于捕获所有异常
    • orchestrator.py:886 — 仓位恢复的最外层
    • position_manager.py:793 — DB 行加载
    • trade_repository.py — 所有 DB 操作
  • 问题: 吞没了具体异常类型,调试困难
  • 建议: 逐步收窄为具体异常类型(psycopg.Error, KeyError, ValueError 等)

14. pair_strategy_overrides key 格式不统一

  • 配置端: 环境变量用 __(双下划线)分隔:PURR__HYPE
  • 内部存储: dict key 用 |(竖线)分隔:PURR|HYPE
  • 日志输出: 也用 |
  • 这三种格式在整个系统中并存,虽然有转换逻辑,但增加了理解成本

15. 策略引擎内每个 tick 都输出 INFO 日志

  • 文件: src/trading/strategy.py:465-469
  • 问题: logger.info(f"📍 tick({tick_type})") 在每个 tick 都执行
  • 影响: 高频运行时日志量极大(30 个分析线程 × 多配对 × 5 分钟 tick)
  • 建议: 将常规 tick 日志改为 DEBUG 级别,仅在信号触发/接近阈值时使用 INFO

16. peak_equity 重启后丢失

  • 文件: src/trading/risk_manager.py:292-294, orchestrator.py:129
  • 问题: 最大回撤检查依赖的 _peak_equity 仅在内存中维护,重启后以当前账户价值重新初始化
  • 影响: 若账户在重启前已出现大幅回撤,重启后回撤计数器归零,可能导致回撤保护失效
  • 建议: 将 peak_equity 持久化到 DB 或文件,重启后恢复

四、配对维度迁移完整性评估

已正确迁移的组件 ✅

组件 文件 PairKey 使用
策略引擎 strategy.py 所有 7 个状态字典均使用 PairKey
仓位管理器 position_manager.py _positions + _opening_pairs
风控审查 risk_manager.py 重复仓位检查按 (symbol, base_symbol)
交易编排器 orchestrator.py 信号处理、同步、止损
配置系统 config.py 三级参数层级 (pair > symbol > global)
黑名单/禁闭 config.py 支持币种级和配对级
数据库 trade_repository.py 所有查询含 base_symbol
DB 索引 migrations/ 已迁移到 (symbol, base_symbol) 复合索引

仍有遗留的区域 ⚠️

区域 问题 严重度
孤儿收集 _collect_orphan_candidates 缺 base_symbol
残留收纳 _adopt_residual_base_leg close_disabled 检查逻辑
价格同步 sync_with_exchange 缺 base 价格更新
仓位查询 get_positions_by_symbols 同 symbol 多 base 覆盖
单币模式 process_analysis 强制非空 base_symbol

五、建议修复优先级

  1. 立即修复: #1 (sync 价格缺失), #3 (孤儿 close_disabled), #5 (WS 阈值)
  2. 短期优化: #2 (孤儿 adaptive_z), #4 (N+1 查询), #6 (单币模式兼容), #10 (close_disabled 逻辑)
  3. 中期建设: #7 (查询去重), #8 (cooldown 可配置), #11 (测试覆盖), #12 (移动止损配对级)
  4. 长期改进: #13 (异常收窄), #14 (格式统一), #15 (日志级别), #16 (peak_equity 持久化)

六、验证方式

  1. #1 修复验证: 在 pair 模式下运行,观察 sync_with_exchange 后 base_current_price 是否被正确更新
  2. #2 修复验证: 模拟孤儿仓位收纳,验证 entry_adaptive_z 非零时均值回归退出是否正常触发
  3. #5 修复验证: 断开 WS 连接,验证 warning 日志在 timeout 之前输出
  4. 单元测试: 为 AdaptiveBollingerStrategy.process_tick 编写测试,覆盖入场/退出/冷却/孤儿场景
  5. 集成测试: 使用 testnet 环境端到端验证配对开仓 → 均值回归平仓 → 孤儿收纳完整流程

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