实盘信号强度默认 weak 与策略层语义不一致

BUG 分析:实盘信号强度默认 weak 与策略层语义不一致

摘要

  • 现象on_entry_signal 的默认参数 signal_strength="weak" 与「实盘策略层只产生 strong/medium」的语义不一致,若新调用方未传该参数会得到 0.7× 仓位,且 TRADING_STRENGTH_SCALE_WEAK 在实盘路径中从未被使用,易造成误解。
  • 影响:潜在错误仓位倍乘(0.7×)、配置无效感、维护者困惑。
  • 根因:API 默认值与业务约定(入场即至少 medium)脱节;weak 仅用于分析/回测未在配置与风控处显式说明。

完整因果链

1. 输入(Input)

类型 描述
主路径 实盘 tick 到达,策略层产生入场信号entry_signal is not None,且 abs(entry_signal.adaptive_z) >= strategy_adaptive_threshold
触发 BUG 的输入 某调用方(如新功能、测试、或外部集成)调用 on_entry_signal(symbol=..., multi_period_result=..., ...) 未传入 signal_strength

即:输入 = 一次“入场”决策 + 调用 on_entry_signal 时省略 signal_strength 参数。


2. 状态变化(State)

  • 策略层约定:入场信号仅在 |adaptive_z| >= threshold 时产生 → 语义上「有入场」等价于「信号强度至少为 medium」。
  • API 约定on_entry_signal(signal_strength=...) 未传时使用默认值。
  • 默认值状态:原实现中默认值为 "weak",与上述语义矛盾 → 出现「已决定入场,但强度被当作 weak」的中间状态。

状态变化链:

[ 策略决定入场 ] → [ 调用 on_entry_signal(..., 未传 signal_strength) ] 
  → [ 使用默认 "weak" ] 
  → [ PairTradeSignal.signal_strength = "weak" ]
  → [ 后续仓位计算按 weak 处理 ]

3. 调用路径(Call Path)

调用方
  └─ on_entry_signal(symbol, multi_period_result, ..., 未传 signal_strength)
       └─ 默认 signal_strength = "weak"
       └─ PairTradeSignal(signal_strength=signal_strength)   # 即 "weak"
            └─ [ 频率/风控等 ] → position_manager.open_position(signal)
                 └─ _open_position_inner(signal)
                      └─ risk_manager.calculate_position_size(signal, alt_price, base_price, available_balance)
                           └─ scale = _strength_scale["weak"]   # 0.7
                           └─ position_usd = base_usd * 0.7
                      └─ 用 (alt_size, base_size) 下单
  • 正常实盘路径process_tick始终传入 signal_strength=strength(strong 或 medium),故不会走到默认值。
  • 出错路径:任何不传 signal_strength 的调用都会落入默认 "weak",并沿上述路径一直传到 calculate_position_size,使用 0.7 倍乘。

4. 出错点(Failure Point)

位置 文件:行(示意) 表现
API 默认值 orchestrator.py: on_entry_signal(..., signal_strength: str = "weak") 未传参时注入 "weak",与「入场即至少 medium」矛盾。
仓位倍乘 risk_manager.py: calculate_position_sizescale = _strength_scale["weak"] 得到 scale=0.7,仓位为 base_position_usd * 0.7,小于本应有的至少 1.0×。
配置歧义 config.py / .env: TRADING_STRENGTH_SCALE_WEAK=0.7 实盘策略层从不产生 weak,修改该值对实盘无影响,易被误以为会生效。

直接表现:在「未传 signal_strength」的调用路径上,仓位被错误地缩小为 0.7×(相对“至少 medium”的 1.0×)。


5. 根因(Root Cause)

  1. API 契约与业务语义不一致
    业务约定:「产生入场信号」⇒ 信号强度至少为 medium。但 on_entry_signal 的默认参数设为 "weak",未传参时等价于“弱信号入场”,与约定冲突。

  2. 默认值未与唯一实盘入口对齐
    实盘唯一入场入口在 process_tick 中且显式传入 strong/medium,因此历史未暴露问题;默认值仅在新调用方省略参数时生效,属于潜在陷阱而非必然复现。

  3. weak 的用途未在配置与风控处显式说明
    weak 仅在分析/回测等路径使用,实盘策略层不产生 weak。配置与代码中未注明「weak 仅分析/回测」,导致误以为 TRADING_STRENGTH_SCALE_WEAK 会影响实盘。


因果链小结(一图流)

输入: 调用 on_entry_signal(..., 未传 signal_strength)
  ↓
状态: 默认 signal_strength = "weak" → PairTradeSignal.signal_strength = "weak"
  ↓
调用路径: on_entry_signal → PairTradeSignal → open_position → calculate_position_size
  ↓
出错点: risk_manager 使用 scale=0.7 → 仓位 = base_usd × 0.7(应为至少 1.0×)
  ↓
根因: API 默认 "weak" 与「入场即至少 medium」的语义不一致;weak 仅分析/回测未文档化

修复建议(已实施或可实施)

  1. 修改默认值
    on_entry_signal(..., signal_strength: str = "medium"),使未传参时与「至少 medium」一致,避免误用 0.7×。

  2. 文档与注释

    • on_entry_signal 的 docstring 中说明:实盘仅传 strong/medium;默认 medium 表示未传时按 1.0× 仓位。
    • risk_manager._strength_scale 处注明:实盘策略层只产生 strong/medium,weak 用于分析/回测。
    • config / .env.example 中注明:TRADING_STRENGTH_SCALE_WEAK 仅分析/回测使用,实盘不产生 weak。
  3. 可选
    若希望从类型上约束实盘路径,可考虑在实盘调用处用 Literal["strong", "medium"] 或单独类型标注,避免误传 weak(需评估与现有分析/回测代码的兼容性)。


相关代码索引

角色 文件 关键位置
入场强度判定 src/trading/orchestrator.py process_tickstrength = "strong" if abs_z >= threshold*1.4 else "medium"
入口 API src/trading/orchestrator.py on_entry_signal(..., signal_strength=...)
仓位倍乘 src/trading/risk_manager.py calculate_position_size_strength_scale[signal.signal_strength]
配置 src/trading/config.py strength_scale_strong/medium/weak

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