实盘信号强度默认 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_size → scale = _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)
-
API 契约与业务语义不一致
业务约定:「产生入场信号」⇒ 信号强度至少为 medium。但on_entry_signal的默认参数设为"weak",未传参时等价于“弱信号入场”,与约定冲突。 -
默认值未与唯一实盘入口对齐
实盘唯一入场入口在process_tick中且显式传入 strong/medium,因此历史未暴露问题;默认值仅在新调用方省略参数时生效,属于潜在陷阱而非必然复现。 -
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 仅分析/回测未文档化
修复建议(已实施或可实施)
-
修改默认值
on_entry_signal(..., signal_strength: str = "medium"),使未传参时与「至少 medium」一致,避免误用 0.7×。 -
文档与注释
- 在
on_entry_signal的 docstring 中说明:实盘仅传 strong/medium;默认 medium 表示未传时按 1.0× 仓位。 - 在
risk_manager._strength_scale处注明:实盘策略层只产生 strong/medium,weak 用于分析/回测。 - 在
config/.env.example中注明:TRADING_STRENGTH_SCALE_WEAK仅分析/回测使用,实盘不产生 weak。
- 在
-
可选
若希望从类型上约束实盘路径,可考虑在实盘调用处用 Literal["strong", "medium"] 或单独类型标注,避免误传 weak(需评估与现有分析/回测代码的兼容性)。
相关代码索引
| 角色 | 文件 | 关键位置 |
|---|---|---|
| 入场强度判定 | src/trading/orchestrator.py |
process_tick 中 strength = "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 |