docs(diary): bugfix exit a orizzonte SH01 (3 barre -> H=12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
# 2026-06-01 — Bugfix: SH01 usciva a 3 barre invece di H=12 (exit a orizzonte)
|
||||||
|
|
||||||
|
> Diagnosi partita da un check sulla debolezza apparente di **SH01_BTC** nel paper
|
||||||
|
> trading live PORT06 (accuratezza 33,3% su 3 trade). Non era sfortuna statistica:
|
||||||
|
> era un bug di exit nello `StrategyWorker`.
|
||||||
|
|
||||||
|
## Sintomo
|
||||||
|
|
||||||
|
Nel live PORT06 (Docker `pythagoras-portfolio`), SH01_BTC mostrava 3 trade tutti
|
||||||
|
`long`, **tutti chiusi con `reason: "hold_limit"` a `bars_held: 3`**, con `tp: null
|
||||||
|
sl: null`:
|
||||||
|
|
||||||
|
| # | entry | exit | bars | net % | esito |
|
||||||
|
|---|-------|------|------|------:|:---:|
|
||||||
|
| 1 | 73529.5 | 73433.0 | 3 | −0,46% | ❌ |
|
||||||
|
| 2 | 73759.5 | 73839.5 | 3 | +0,02% | ✅ |
|
||||||
|
| 3 | 73811.5 | 73766.0 | 3 | −0,32% | ❌ |
|
||||||
|
|
||||||
|
`oos_signal_precision` nei log di TRAIN scendeva 55,6% → 50,0% → 43,3%.
|
||||||
|
|
||||||
|
## Causa
|
||||||
|
|
||||||
|
SH01 (`scripts/strategies/SH01_shape_ml.py`, config **W24 H12 th0.58**) è una
|
||||||
|
strategia **horizon-only**: predice il segno del rendimento a **H=12 barre** ed esce a
|
||||||
|
H barre. I suoi Signal portano `metadata={"max_bars": H}` (=12) e **nessun TP/SL**.
|
||||||
|
|
||||||
|
Nello `StrategyWorker.tick()` la logica di uscita era:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if self.tp and self.sl: # SH01: False (tp=sl=0)
|
||||||
|
... usa self.max_bars ... # -> max_bars=12 consultato SOLO qui
|
||||||
|
elif self.bars_held >= self.hold_bars: # fallback legacy hold_bars=3
|
||||||
|
self._close_position(..., "hold_limit") # SH01 finiva QUI
|
||||||
|
```
|
||||||
|
|
||||||
|
`self.max_bars` (=12, settato correttamente in `_open_position`) era onorato **solo
|
||||||
|
dentro il ramo `tp and sl`**. Senza TP/SL, SH01 cadeva sul fallback `hold_bars=3` e
|
||||||
|
chiudeva a 3 barre. L'edge di SH01 — per CLAUDE.md è nell'**asimmetria sull'orizzonte
|
||||||
|
H, non nella frequenza** (win-rate ~50%) — non aveva tempo di realizzarsi: tagliato a
|
||||||
|
3/12, degenera in rumore.
|
||||||
|
|
||||||
|
Solo SH01 (BTC+ETH) era colpito: tutte le fade (MR01/MR02/MR07, DIP01) portano
|
||||||
|
tp+sl+max_bars e usano il ramo intrabar corretto.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
`src/live/strategy_worker.py`: aggiunto un ramo per l'exit a orizzonte puro, prima del
|
||||||
|
fallback `hold_bars`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
elif self.max_bars:
|
||||||
|
# Exit puro a orizzonte (strategie senza TP/SL, es. SH01 shape-ML H=12):
|
||||||
|
# onora max_bars dalla metadata del Signal, non il fallback hold_bars=3.
|
||||||
|
if self.bars_held >= self.max_bars:
|
||||||
|
self._close_position(current_price, "time_limit")
|
||||||
|
```
|
||||||
|
|
||||||
|
Le fade restano invariate (entrano nel ramo `tp and sl`).
|
||||||
|
|
||||||
|
## Verifica
|
||||||
|
|
||||||
|
- Nuovo test `tests/portfolio/test_horizon_exit.py` (2 casi): con `max_bars=12` resta
|
||||||
|
in posizione a 3 barre; esce a 12 con `reason: "time_limit"` e `bars_held: 12`.
|
||||||
|
- Suite completa: **43 passed**.
|
||||||
|
- Container riavviato: **tutti i 17 sleeve RESUME puliti**, inclusa una posizione
|
||||||
|
SH01_ETH short aperta che ora seguirà l'exit a 12 barre.
|
||||||
|
|
||||||
|
## Atteso d'ora in poi
|
||||||
|
|
||||||
|
I trade SH01 nei log mostreranno `reason: "time_limit"` con `bars_held: 12` invece di
|
||||||
|
`hold_limit / 3`. Il 33% di accuratezza era un artefatto dell'exit prematuro; ora la
|
||||||
|
strategia gira sull'orizzonte su cui è validata (BTC OOS Sharpe 2,72, expanding).
|
||||||
|
Resta comunque un **diversificatore** del MASTER, non un motore di ritorno standalone.
|
||||||
|
|
||||||
|
## Lezione
|
||||||
|
|
||||||
|
Il backtest di SH01 (`fade_base`/engine onesto) esce a H barre via `max_bars`; il
|
||||||
|
worker live deve replicarlo. Quando una strategia non porta TP/SL ma solo un
|
||||||
|
orizzonte, il fallback `hold_bars` del worker la **falsa silenziosamente**. Verificare
|
||||||
|
sempre che la convenzione di exit del worker live coincida con quella del backtest
|
||||||
|
validato — non solo l'ingresso.
|
||||||
Reference in New Issue
Block a user