全系统Key改为配对维度bug2

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

分析日期:2026-02-19
分析范围:全系统 Key 改为 (symbol, base_symbol) 配对维度后的代码质量审查
结论:配对维度架构整体一致,发现 3 个真实缺陷,1 个设计改进建议


整体评估

维度 状态 说明
配对 Key 架构一致性 ✅ 强一致 (symbol, base_symbol) 贯穿全系统所有模块
策略参数三层优先级(查询) ✅ 正确 pair > symbol > globalget_strategy_params 中实现
数据库映射 ✅ 一致 SQL WHERE 子句与内存 key 格式完全对应
并发安全 ⚠️ 部分缺陷 一处状态恢复未持锁(设计不对称)
配置构建 ❌ 功能缺陷 配对级参数构建时跳过了币种级回退
信号就绪判断 ⚠️ 阈值偏低 25% 填充率不满足统计可靠性要求

Bug #1 — _execute_close 正常失败路径状态恢复无锁

文件src/trading/position_manager.py
行号:527(出错点),对比 280(正确加锁)和 287(正确加锁)
严重性:🟡 中(设计违规,Python GIL 下实际触发概率低)

完整因果链

输入
└── 止损监控 / 信号处理 → 调用 close_position(symbol, base_symbol)

状态变化
└── pm.py:272  with self._lock:
    pm.py:280      position.status = CLOSING   ✅ 在锁内设置

调用路径
└── pm.py:283  return self._execute_close(key, position, ...)
    └── pm.py:411  market_close(position) → 交易所返回 success=False
    └── pm.py:526  else:  ← Leg A 失败分支
        pm.py:527      position.status = PositionStatus.OPEN   ❌ 无锁!

出错点
├── 成功路径  pm.py:494:  with self._lock: self._positions.pop()          ✅
├── 异常路径  pm.py:285:  with self._lock: position.status = OPEN          ✅
└── 正常失败  pm.py:527:  position.status = PositionStatus.OPEN(无锁)    ❌

根因
└── _execute_close 在锁外运行(锁在 close_position 入口归还)。
    三条"结束平仓"路径中:成功路径在锁内 pop,异常路径在锁内恢复状态,
    但正常失败路径(Leg A 失败,正常 return None)直接修改属性,未持锁。
    两条"恢复为 OPEN"路径行为不对称,违反设计约定。

并发场景分析

时序场景 实际行为 是否有害
T1 在执行 line 527 之前,T2 调用 close_position T2 进锁看到 CLOSING → return None(短暂屏蔽) 低风险
T1 执行 line 527 后,T2 调用 close_position T2 进锁看到 OPEN → 正常执行 无害

修复方案

# position_manager.py line 526-527
else:
    with self._lock:                         # ← 新增
        position.status = PositionStatus.OPEN

Bug #2 — 配对级参数构建不继承币种级,直接回退全局默认

文件src/trading/config.py
行号:362-386(函数定义),481(调用点)
严重性:🔴 高(静默功能缺陷,参数错误但无报错)

完整因果链

输入(用户预期:PURR|HYPE 继承 PURR 的 EMA=72,只调整阈值)
├── TRADING_STRATEGY_OVERRIDE_SYMBOLS=PURR
├── TRADING_STRATEGY_PURR_EMA_SPAN=72
├── TRADING_STRATEGY_OVERRIDE_PAIRS=PURR__HYPE
└── TRADING_STRATEGY_PURR__HYPE_ADAPTIVE_THRESHOLD=2.0

状态变化(config.py:480-481)
├── 第1步: symbol_overrides = _load_symbol_strategy_overrides(global_cfg)
│          → {"PURR": StrategyParams(ema_span=72, ...)}       ✅
└── 第2步: pair_overrides = _load_pair_strategy_overrides(global_cfg)
           ↑ 只传入 global_cfg,不传入第1步的结果!

调用路径(config.py:362-386)
└── def _load_pair_strategy_overrides(global_cfg: dict):    ← 签名缺少 symbol_overrides
        for pair in ["PURR__HYPE"]:
            overrides["PURR|HYPE"] = _build_strategy_params(
                "TRADING_STRATEGY_PURR__HYPE_",
                global_cfg    ← 回退来源是全局配置
            )

出错点(_build_strategy_params,config.py:311-340)
└── os.getenv("TRADING_STRATEGY_PURR__HYPE_EMA_SPAN") → None(未设置)
    return global_cfg["ema_span"]   ← 返回 36(全局默认)❌
    期望返回: 72(PURR 币种级)

根因
└── _load_pair_strategy_overrides 函数签名只接收 global_cfg 一个参数(line 362-363)。
    load_trading_config(line 481)调用时未传入已构建的 symbol_strategy_overrides。
    三层优先级在【查询时】(get_strategy_params)实现正确,
    但在【构建时】(_load_pair_strategy_overrides)只有两层(pair_env > global),
    静默跳过了 symbol 层。系统不会产生任何报错或警告。

数字验证

配置层 ema_span 实际值 期望值
全局默认 36
币种级 PURR 72 72 ✅
配对级 PURR|HYPE 36(错误) 72

修复方案

# config.py — load_trading_config 内部(约 line 480-481)
_symbol_overrides = _load_symbol_strategy_overrides(_global_strategy_cfg)
config = TradingConfig(
    ...
    symbol_strategy_overrides=_symbol_overrides,
    pair_strategy_overrides=_load_pair_strategy_overrides(
        _global_strategy_cfg,
        _symbol_overrides,          # ← 新增:传入币种级参数
    ),
)

# config.py — _load_pair_strategy_overrides 函数签名和内部逻辑(约 line 362)
def _load_pair_strategy_overrides(
    global_cfg: dict,
    symbol_overrides: dict[str, StrategyParams],    # ← 新增参数
) -> dict[str, StrategyParams]:
    ...
    for pair in pairs:
        alt_asset = pair.split("__")[0]
        # 若存在币种级覆盖,以其为基础;否则回退全局
        if alt_asset in symbol_overrides:
            sym_p = symbol_overrides[alt_asset]
            base_cfg = {
                "ema_span": sym_p.ema_span,
                "std_window": sym_p.std_window,
                "adaptive_threshold": sym_p.adaptive_threshold,
                "min_zscore_abs": sym_p.min_zscore_abs,
                "reversion_factor": sym_p.reversion_factor,
                "cooldown_minutes": sym_p.cooldown_minutes,
                "stop_loss_pct": sym_p.stop_loss_pct,
                "max_hold_hours": sym_p.max_hold_hours,
            }
        else:
            base_cfg = global_cfg
        pair_key = pair.replace("__", "|", 1)
        overrides[pair_key] = _build_strategy_params(
            f"TRADING_STRATEGY_{pair}_", base_cfg    # ← 使用正确的回退源
        )

验证

# 单元测试断言
config = load_trading_config()  # 使用上述环境变量
params = config.get_strategy_params("PURR/USDC:USDC", "HYPE/USDC:USDC")
assert params.ema_span == 72          # 继承自 PURR 币种级
assert params.adaptive_threshold == 2.0  # 来自 PURR|HYPE 配对级覆盖

Bug #3 — is_ready 缓冲区就绪阈值过低(25%)

文件src/trading/strategy.py
行号:134(is_ready),386(_compute_adaptive_z),141(any_ready
严重性:🟡 中(早期信号质量差,服务重启后窗口期内可能误开仓)

完整因果链

输入
└── 服务重启,PURR|HYPE 配对开始逐条接收 K 线(std_window=72 by default)

状态变化(每条新 K 线)
└── _process_tick_unlocked() → 更新 Welford 统计量
    bl.std_window: 1 → 2 → ... → 18 条(第 18 条时触发就绪)

调用路径(第 18 条 K 线)
├── strategy.py:447  if not self.is_ready(symbol, base_symbol): return None
│   └── strategy.py:134  min_required = max(10, 72 // 4) = 18
│       len(bl.std_window) = 18 >= 18 → True   ← 仅 25% 就认为就绪 ❌
│
└── strategy.py:457  adaptive_z = _compute_adaptive_z(bl, z4h)
    └── strategy.py:386  if n < max(10, maxlen // 4): return None
        ← 同样的 25% 阈值,通过检查
    └── strategy.py:388  variance = bl.welford_m2 / (n-1) = m2 / 17
        ← 自由度仅 17,样本量远低于统计可靠基准(n=30)
    └── strategy.py:392  return (z4h - ema) / std
        ← std 基于不稳定方差,可能偏小 → adaptive_z 被放大 → 误触阈值 → 虚假开仓信号

出错点
└── strategy.py:134(is_ready)和 strategy.py:386(_compute_adaptive_z)
    均使用 `max(10, bl.std_window.maxlen // 4)` 作为最小样本要求

根因
└── 25% 阈值(maxlen // 4)是武断设定,缺乏统计依据。
    Welford 增量方差在小样本时误差大:
    - std_window=72,25%=18 点:自由度 17,方差估计标准误 ≈ σ²·√(2/17) ≈ 34% 的相对误差
    - std_window=72,50%=36 点:自由度 35,相对误差降至 ≈ 24%
    早期 std 偏低 → adaptive_z 被放大 → 误触入场阈值 → 虚假开仓。

数量化对比

参数 当前(25%=// 4 建议(50%=// 2
std_window=72 就绪需 18 点,自由度 17 36 点,自由度 35
std_window=36 就绪需 10 点(被 max 截断) 18 点,自由度 17
方差估计相对误差(std_window=72) ~34% ~24%
服务重启后不可靠窗口 前 18 条 K 线 前 36 条 K 线

修复方案

# strategy.py line 134(is_ready)
min_required = max(10, bl.std_window.maxlen // 2)    # 25% → 50%

# strategy.py line 141(any_ready,同步修改)
min_required = max(10, bl.std_window.maxlen // 2)

# strategy.py line 386(_compute_adaptive_z,同步修改)
if n < max(10, bl.std_window.maxlen // 2):
    return None

三处需同步修改,保持一致。


设计改进建议(非 Bug)

配对缓存无定期刷新(realtime_kline_service_base.py

影响:服务运行中若 DB 新增配对,需重启才能生效
建议:在现有健康监控循环中,每 5 分钟调用一次 _load_pair_cache()
优先级:🟠 低(运维便利性,不影响已有配对)


修复优先级

优先级 Bug 原因
🔴 立即 Bug #2(配置级联) 静默功能错误,影响所有配置了配对级覆盖的用户,且无任何报警
🟡 近期 Bug #3(阈值偏低) 影响重启后的早期信号质量,与风控关联
🟢 日后 Bug #1(无锁) 设计不对称,Python GIL 下实际触发概率低

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