协整检验通过性抖动导致的订单跟丢了的BUG 1
BUG 分析:Gate 不通过时无法通过 Adaptive Z 回归平仓
摘要
- 现象:某 symbol(如 ETH/USDC:USDC)开仓成功后,若在后续 K 线周期里 Gate1 或 Gate2 无法通过,则该仓位无法再通过 Adaptive Z 回归触发平仓。
- 影响:平仓仅能依赖止损、移动止损、持仓超时;无法在均值回归时主动平仓,持仓时间与风险偏离策略设计(严重 Bug)。
- 根因:退场检查与入场共用「分析结果存在」这一前置条件;Gate 本意过滤入场质量,却误伤了仅需 zscore 的退场路径。
完整因果链
1. 输入(Input)
| 类型 | 描述 |
|---|---|
| T0 | 某 symbol 通过 Gate1+Gate2,成功开仓;position_manager 与策略层均有该 symbol 持仓状态。 |
| T1 | 同一 symbol 再次触发 _analyze_and_alert(symbol, timeframe, kline_time)(下一根或后续 5m K 线),但本周期协整或健康监控变差,Gate1 或 Gate2 不通过。 |
即:输入 = 已开仓的 symbol + 本周期再次触发分析且 Gate 不通过。
2. 状态变化(State)
- T0:
position_manager中该 symbol 有持仓;策略层_positions[symbol]有 PositionTracker(含entry_adaptive_z)。 - T1:价格/协整/健康度等导致本周期
analyze_multi_period()内 Gate1 或 Gate2 不通过 → 函数 return None。
状态变化链:
[ 有该 symbol 持仓 ] + [ 本周期 _analyze_and_alert 被调用 ]
→ [ _run_correlation_and_analysis → analyze_multi_period ]
→ [ Gate1 或 Gate2 不通过 → return None ]
→ [ multi_period_result is None ]
→ [ 后续策略引擎不被调用 ]
3. 调用路径(Call Path)
_analyze_and_alert(symbol, timeframe, kline_time)
└─ _fetch_and_validate_price_data(symbol, timeframe)
└─ _run_correlation_and_analysis(symbol, price_data_cache)
└─ analyze_multi_period(price_data_cache, ...)
└─ Gate1: cointegration_count < threshold → return None
或 Gate2: health_monitor 未通过 → return None
└─ multi_period_result is None
└─ if multi_period_result is None: return ← 出错点:直接 return,不区分有无持仓
└─ [ 未执行 ] _trigger_strategy_if_ready(...)
└─ [ 未执行 ] process_analysis(...) → strategy.process_tick(...) → _check_exit(...)
- 正常路径:Gate 通过 →
multi_period_result非 None →_trigger_strategy_if_ready→process_analysis→process_tick→_check_exit(可产生退场信号)。 - 出错路径:Gate 不通过 →
multi_period_result is None→ 直接 return → 本周期完全不执行退场检查。
4. 出错点(Failure Point)
| 位置 | 文件:行 | 表现 |
|---|---|---|
| 提前 return | realtime_kline_service_base.py 约 1316–1318 行 |
if multi_period_result is None: return。不区分「无持仓」与「有持仓」;有持仓时本应仍执行退场检查,却被与「无分析结果」等同处理。 |
| 策略引擎未进入 | 同上导致的后续未调用 | 本周期不调用 _trigger_strategy_if_ready → process_analysis → strategy.process_tick → _check_exit,Adaptive Z 回归退场逻辑根本不会执行。 |
直接表现:有该 symbol 持仓且本周期 Gate 不通过时,永远不会在本周期触发 Adaptive Z 回归平仓。
5. 根因(Root Cause)
-
退场检查与入场共用同一前置条件
只有multi_period_result非 None(即 Gate 通过)时才进入策略引擎。设计上把「能否做多周期分析并开新仓」和「能否用当前 z4h/adaptive_z 检查已有仓位是否该平仓」绑在一起。 -
Gate 误伤退场路径
Gate 本意是过滤入场质量;退场检查(_check_exit)仅依赖持仓 + z4h/adaptive_z,不依赖 Gate。但因入口处「无分析结果即 return」,导致「有仓但 Gate 不通过 → 永不执行 Adaptive Z 回归退场」。
Gate1/Gate2 在代码中的位置与作用
- Gate1:src/utils/analysis/analysis_core.py 的
analyze_multi_period()中,要求 6 个协整结果里通过数 ≥cointegration_threshold(如 3)。 - Gate2:同一函数内,要求 4H/60D 的
health_monitor通过(短期健康监控)。
二者任一不通过时,analyze_multi_period() 返回 None。
实盘流程示意
flowchart LR
A[_analyze_and_alert] --> B[_run_correlation_and_analysis]
B --> C[analyze_multi_period]
C --> D{Gate1 and Gate2 OK?}
D -->|No| E[return None]
D -->|Yes| F[multi_period_result]
E --> G[early return 不调用策略]
F --> H[_trigger_strategy_if_ready]
H --> I[process_analysis]
I --> J[strategy.process_tick]
J --> K[_check_exit 退场]
Adaptive Z 回归平仓本身不依赖 Gate
在 src/trading/strategy.py 中,_check_exit()(约 558–631 行)只依赖:
- 当前持仓、
entry_adaptive_z、reversion_factor - 当前
z4h、adaptive_z(由策略内部 baseline 的 EMA/STD 计算)
没有任何对 Gate 或 analyze_multi_period 的依赖。问题仅在于:有持仓时是否会对该 symbol 调用 process_tick;当前实现下只有 Gate 通过时才会调用。
止损/移动止损/持仓超时不受 Gate 影响
src/trading/orchestrator.py 中,止损监控由独立线程 _stop_loss_monitor() 执行,遍历 position_manager.open_positions,不依赖 K 线分析或 Gate。因此即使 Gate 一直不通过,仓位仍会在触发固定止损、移动止损或持仓超时时被平仓,只是不会通过「Adaptive Z 回归」这条路径。
小结
| 场景 | 是否还能通过 Adaptive Z 回归平仓? | 是否还有其它平仓方式? |
|---|---|---|
| 开仓后 Gate1/Gate2 不再通过 | 不能(策略引擎本周期不会被调用) | 能:止损、移动止损、持仓超时 |
修复方案(建议实现)
目标:有该 symbol 持仓时,即使本周期 Gate 不通过,仍用当前 z4h/adaptive_z 执行一次退场检查(仅退场、不入场)。
思路:Gate 不通过时若发现该 symbol 有持仓,则用「跳过 Gate 校验」的分析结果(仅拿 zscore)再调用策略引擎,且仅处理退场信号、不处理入场信号。
实现步骤:
-
analysis_core.py
analyze_multi_period(..., skip_gates: bool = False)。当skip_gates=True时:Gate1/Gate2 不通过也不 return None,照常返回含zscore_list等字段的 dict,供退场检查使用。 -
realtime_kline_service_base.py
_run_correlation_and_analysis(..., skip_gates: bool = False),将skip_gates传给analyze_multi_period。- 在
_analyze_and_alert中:当multi_period_result is None时,若存在_trading_orchestrator且该symbol在position_manager.open_positions中,则再调一次_run_correlation_and_analysis(..., skip_gates=True);若得到非 None 的raw_result,则调用策略引擎并传入exit_only=True(或新增仅退场入口)。
-
orchestrator.py
process_analysis(..., exit_only: bool = False)。当exit_only=True时:只处理exit_signal(执行on_exit_signal),不处理entry_signal,避免在 Gate 未通过时开新仓。 -
文档
本文档即 bug 分析持久化;修复完成后可在本文档末尾补充「修复记录」与对应 commit。