全系统key配对升级 bug21

配对维度优化后系统缺陷分析报告

分析时间:2026-02-20
代码基准:HEAD(requirements update 6 debug 18)
分析范围:全系统 Key 迁移至 PairKey = tuple[str, str] 后的最新状态


已确认修复的问题 ✅

项目 修复内容 位置
启动SQL时间边界 已添加 >= NOW() - INTERVAL '30 days' orchestrator.py:140-152
KillSwitch平仓 stop_loss_monitor 中已加入 KillSwitch 检查并遍历平仓 orchestrator.py:692-702
stop()主动平仓 添加 close_positions_on_stop 参数支持主动平仓 orchestrator.py:239-283
数据库索引 全系统 symbol → (symbol, base_symbol) 配对索引迁移 20260219_fix_pairkey_indexes.sql
统计表扩展 daily_trading_stats 添加 symbol/base_symbol 列 20260220_bugfix_batch.sql
移动止损分离 update_trailing_peak 与 check_trailing_stop 已拆分 risk_manager.py

仍存在的缺陷

🔴 严重缺陷(直接影响资金安全/核心功能)


缺陷 S1:KillSwitch 平仓无异常保护

  • 位置: src/trading/orchestrator.py:692-698
  • 问题: _stop_loss_monitor() 中对每个仓位依次调用平仓,但没有 try-except 包裹。单个仓位平仓抛出异常会中断整个循环,导致后续仓位无法触发 KillSwitch 平仓。
  • 影响: 紧急情况下仅能平掉第一个失败前的仓位,其余仓位继续承受风险。
  • 建议:
    for pos in list(self._position_manager.open_positions):
        try:
            await self._close_with_retry(pos, "kill_switch")
        except Exception as e:
            logger.error(f"KillSwitch平仓失败 {pos}: {e}", exc_info=True)
    

缺陷 S2:冷却期结束后突破信号可能丢失

  • 位置: src/trading/strategy.py:638-641
  • 问题: 冷却期结束时,同一 tick 内同时清除 _last_trade_time_prev_above_threshold(重置为 False)。若当前 tick 已在阈值上方,由于 prev=False、cur=True 满足"突破"条件,但状态重置操作与检测同步执行,实际行为取决于代码执行顺序——可能在冷却期内已出现过的突破状态被消费掉,导致信号丢失。
  • 影响: 止损冷却结束后,若行情继续极端,合法的再入场信号可能被误跳过。
  • 建议: 冷却期结束时仅清除 _last_trade_time,在下一个 tick 时再允许突破检测(使用 _cooling_just_ended 标志位延迟一拍)。

缺陷 S3:Base 腿 PnL 费用计算使用入场价而非出场价

  • 位置: src/trading/position_manager.py:677-678
  • 代码:
    base_notional = position.base_entry_price * position.base_size  # ← 错误
    
  • 问题: 费用应基于实际成交的名义价值(出场价 × size),使用入场价会导致:
    • 价格上涨时:低估费用 → 高估净 PnL
    • 价格下跌时:高估费用 → 低估净 PnL
  • 建议: 改为 base_exit_price * position.base_size(出场价可从 leg_b 结果或 mid_price 推断)。

缺陷 S4:Alt 腿消失时 Base 腿可能变成幽灵仓位

  • 位置: src/trading/position_manager.py:347-414
  • 问题: 当 alt_gone_from_exchange=True 时,系统直接计算 PnL 并标记仓位关闭。但若此时 base 腿仍在交易所存在(base_size > 0),且 _adopt_residual_base_leg() 未被调用(或调用时机不对),该 base 腿会成为系统完全不知晓的幽灵仓位,持续占用资金但不受任何止损管理。
  • 建议: 在标记仓位关闭前,强制检查 base_size,若 > 0 则必须先收纳或平仓。

🟠 重要缺陷(影响系统鲁棒性/数据完整性)


缺陷 A1:孤儿仓位 peak_pnl_pct 未初始化导致移动止损失效

  • 位置: src/trading/position_manager.py:1253-1268
  • 问题: 孤儿单腿仓位创建时,peak_pnl_pct 保持默认值 0.0。首次 tick 更新时,若当前 unrealized_pnl_pct 为负数,而 peak_pnl_pct=0 使得 drawdown_from_peak = 0 - current < 0,移动止损逻辑判断为未达到止损阈值,实际上这个"峰值"是虚假的。
  • 建议: 孤儿仓位创建后,第一次进入止损检查前,先用当前 unrealized_pnl_pct 初始化 peak_pnl_pct(peak_pnl_pct = max(0.0, current_pnl_pct))。

缺陷 A2:孤儿配对检测依赖方向推断而非数据库确认

  • 位置: src/trading/position_manager.py:1219-1223
  • 问题: 孤儿检测通过 (alt_szi > 0) == (base_szi > 0) 来判断是否为 pair 模式——即"两个仓位方向相同"则认为是原始配对。但这是推断,不是确认:两个不相关的独立仓位碰巧方向相同时,会被误识别为配对,创建错误的 PairPosition。
  • 建议: 孤儿恢复应优先查询数据库中的已知配对记录(open_pair_positions 表),只在数据库无记录时才使用推断逻辑,并在日志中明确标注"推断配对"。

缺陷 A3:on_position_closed 未清理 _last_adaptive_z

  • 位置: src/trading/strategy.py:310-325
  • 问题: 平仓回调清理了 _positions_exit_pending_prev_above_threshold,但未清理 _last_adaptive_z[pair_key]
  • 影响: 长期运行后,已关闭的配对仍在字典中占用内存(内存缓慢泄漏);重新开仓同一配对时,日志中可能显示上一次平仓时的旧 adaptive_z 值,造成监控混淆。
  • 建议: 在 on_position_closed 中添加 self._last_adaptive_z.pop(pair_key, None)

缺陷 A4:配对级限速器字典无清理机制(潜在内存泄漏)

  • 位置: src/trading/orchestrator.py:547-552
  • 问题: _pair_rate_limiters: dict[PairKey, RateLimiter] 采用惰性创建,每次新配对首次信号时创建限速器,但从不删除。若配对长期不活跃或被下架,对应的限速器仍留在字典中。
  • 影响: 配对数量多且频繁变动时,内存占用持续增长。
  • 建议: 在仓位关闭后(on_position_closed 触发时),检查该 pair_key 是否还有活跃仓位,若无则移除限速器。

缺陷 A5:Exit Price 覆盖逻辑设计脆弱

  • 位置: src/trading/trade_repository.py:122-125
  • 问题: save_position() INSERT 时 exit_price 传入 0,ON CONFLICT 使用 COALESCE(NULLIF(..., 0), old_value) 保留非零值。
    • 并发风险: 若 update_position_status()save_position() 并发执行,两次写入可能以不确定顺序落库,realized_pnl 可能被后执行的 INSERT 覆盖。
    • 设计脆弱: 依赖"0 作为哨兵值"来区分"未设置"和"真实值为0",属于反模式。
  • 建议: save_position() INSERT 时不包含 exit_price/realized_pnl 字段;exit_price 仅通过专用的 update_position_status() 方法写入。

缺陷 A6:止损 check 与 peak update 调用顺序有隐患

  • 位置: src/trading/risk_manager.py:250-264orchestrator.py:710-719
  • 问题: check_trailing_stop() 依赖调用方保证先调用 update_trailing_peak()。如果调用顺序被误改(先 check 后 update),移动止损会用上一 tick 的峰值判断本 tick 数据,产生漏报。
  • 建议: 将 update_trailing_peak() 合并入 check_trailing_stop() 内部,对外提供单一入口,消除调用顺序依赖。

🟡 中等缺陷(影响精度/可维护性)


缺陷 M1:资金费率未计入 PnL(已知设计局限)

  • 位置: src/trading/position_manager.py:629,685
  • 问题: 代码注释明确说明"暂不扣减资金费",但也没有在日志中输出估算的资金费金额。realized_pnl 实际上是"PnL - 交易手续费",资金费可能为正(做空时收取)或负(做多时支付),在极端行情下资金费率可达 0.1%~1%/8h,忽略会使实际收益偏差较大。
  • 建议: 调用 Hyperliquid API 的 userFunding 接口,或至少在日志中估算资金费(资金费率 × 名义价值 × 持仓小时数 / 8)。

缺陷 M2:峰值权益文件写入无原子性保护

  • 位置: src/trading/risk_manager.py:48_save_peak_equity
  • 问题: 直接写入 .peak_equity 文件,多线程环境下若同时触发写入,可能写入不完整数据(部分字节),下次启动时解析失败。
  • 建议: 采用原子写(先写 .peak_equity.tmp,完成后 rename),Python 3.3+ 的 os.replace() 保证原子性。

缺陷 M3:_compute_adaptive_z 含隐式副作用

  • 位置: src/trading/strategy.py:369-384(方法本身),src/trading/strategy.py:450(调用处)
  • 问题: 方法名暗示纯计算,但调用方需要手动执行 bl.last_std = computed_std 才能完成状态更新——这是 Command-Query Separation 违反,容易被遗漏。
  • 建议: 要么在方法内部直接更新 bl.last_std(并明确命名为 _update_and_compute_adaptive_z),要么完全不更新,让返回值由调用方决定如何使用。

缺陷 M4:信号去重字典清理策略低效

  • 位置: src/trading/strategy.py:533-537
  • 问题: 去重字典仅在条目数 > 500 时触发一次清理(删除超过 60s 的条目)。在低频场景下,字典长期不触发清理;在高频场景下,500 的阈值可能过小。
  • 建议: 改为定时清理(如每 60s 一次,与去重窗口一致),避免依赖条目数触发。

缺陷 M5:check_max_drawdown 初始化逻辑有漏洞

  • 位置: src/trading/risk_manager.py:319-337
  • 问题: 若 peak_equity <= 0 and account_value <= 0 时直接跳过检查(返回 True,即允许)。但在初始化阶段或账户异常情况下,account_value 可能真的为 0,此时应该拒绝开仓而不是放行。
  • 建议: peak_equity <= 0 时应先用当前 account_value 初始化 peak_equity;若 account_value 也 <= 0,应记录 warning 并返回 False(拒绝)。

🟢 轻微缺陷(扩展性/规范性)


缺陷 L1:coin_to_symbol 硬编码 USDC 计价币种

  • 位置: src/trading/models.py:210-218
  • 问题: 函数固定拼接 /USDC:USDC 后缀,限制系统扩展至 USDT、BTC 等其他计价币种。
  • 建议: 添加 quote_asset: str = "USDC" 参数。

缺陷 L2:PositionStatus 枚举缺少中间状态

  • 位置: src/trading/models.py:33-40
  • 问题: 枚举仅有 OPEN / CLOSED / ERROR,缺少 PENDING_CLOSE 状态用于区分"正在执行平仓中"与"已完成平仓"的过渡状态,导致平仓重试逻辑中状态判断不精确。

缺陷 L3:datetime 时区不统一

  • 位置: src/trading/risk_manager.py:266-282(处理 naive datetime)、position_manager.py 中多处
  • 问题: 部分代码使用 UTC aware datetime,部分使用 naive datetime,需要在比较时做兼容处理,增加代码复杂度并存在隐患。
  • 建议: 在 models.py 中统一规定所有 datetime 字段为 UTC aware,在数据入口处强制转换。

缺陷汇总表

优先级 编号 缺陷描述 核心文件 行号
🔴 严重 S1 KillSwitch 平仓无异常保护 orchestrator.py 692-698
🔴 严重 S2 冷却期结束时突破信号可能丢失 strategy.py 638-641
🔴 严重 S3 Base 腿费用用入场价而非出场价 position_manager.py 677-678
🔴 严重 S4 Alt 腿消失时 Base 腿变幽灵仓位 position_manager.py 347-414
🟠 重要 A1 孤儿仓位 peak_pnl_pct 未初始化 position_manager.py 1253-1268
🟠 重要 A2 孤儿配对检测逻辑依赖推断不严谨 position_manager.py 1219-1223
🟠 重要 A3 on_position_closed 未清理 _last_adaptive_z strategy.py 310-325
🟠 重要 A4 配对级限速器无清理机制(内存泄漏) orchestrator.py 547-552
🟠 重要 A5 Exit Price 并发写入冲突风险 trade_repository.py 122-125
🟠 重要 A6 止损 check/update 调用顺序有隐患 risk_manager.py 250-264
🟡 中等 M1 资金费率完全未计入 PnL position_manager.py 629,685
🟡 中等 M2 峰值权益文件写入非原子性 risk_manager.py 48
🟡 中等 M3 _compute_adaptive_z 含隐式副作用 strategy.py 369-384
🟡 中等 M4 信号去重字典清理策略低效 strategy.py 533-537
🟡 中等 M5 check_max_drawdown 初始化允许异常放行 risk_manager.py 319-337
🟢 轻微 L1 coin_to_symbol 硬编码 USDC models.py 210-218
🟢 轻微 L2 PositionStatus 缺少 PENDING_CLOSE 状态 models.py 33-40
🟢 轻微 L3 datetime 时区不统一 risk_manager.py 266-282

合计:4 个严重 + 6 个重要 + 5 个中等 + 3 个轻微 = 18 个待处理缺陷


修复优先级建议

优先级 编号 理由
P0(立即修复) S1、S2、S3、S4 直接影响资金安全和核心功能正确性
P1(本周修复) A1、A2、A5 影响仓位数据完整性和止损功能
P2(计划修复) A3、A4、A6、M2、M5 鲁棒性和数据准确性提升
P3(优化改进) M1、M3、M4、L1、L2、L3 精度、可维护性、扩展性

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