Files
PythagorasGoal/docs/diary/2026-05-28.md
T
Adriano cff0d08fca feat(risk): filtro trend per alzare Acc e ridurre DD + modello portafoglio
Filtro opzionale trend_max/ema_long su tutte le fade (MR01/MR02/MR03/MR07):
salta i segnali quando |close-EMA200|/ATR supera la soglia (non fadare un trend
o crollo estremo). Con trend_max=3.0 (default in strategies.yml): accuratezza su
e DD giu' su 7/8 sleeve, drastico su ETH (MR01 71->26%, MR02 42->25%,
MR03 66->34%, MR07 46->21%); edge OOS confermato. MR03 BTC: filtro disattivo
(unico sleeve dove peggiora entrambe).

Scartate come non robuste: vol-target sizing e skip-alta-volatilita' (peggiorano
sia Acc che DD). Aggiunto modello di portafoglio equipesato su sotto-conti
indipendenti: DD aggregato ~14% full / ~10% OOS sul paniere di 8 sleeve, contro
20-70% del singolo -> vera leva anti-drawdown.

Banco di prova: scripts/analysis/risk_improvements.py, risk_portfolio.py.
Helper trend_distance() in fade_base. CLAUDE.md e diario aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:47:52 +02:00

10 KiB
Raw Blame History

2026-05-28 — Giorno 3: Bug dati Cerbero, paper trader fermo, fix MT01 multi-timeframe

12:20 — Sintomo: paper trader live a zero trade

Cosa: check del container pythagoras-multi (multi-strategy paper trader, 6 strategie). Reale: container healthy da ore, ma 0 trade su tutte le strategie, tutte FLAT a €1000. Primo falso indizio: last_bar_ts: 0 in tutti gli status.json. Indagando il worker, quel campo si aggiorna solo a posizione aperta (contatore hold_bars), non ad ogni candela → non è la causa. Il loop era vivo (status.json riscritti ogni 60s). Lezione: non fidarsi del nome di un campo; verificare nel codice quando viene scritto. L'healthcheck del container controlla solo l'esistenza di status.json, non la freschezza → un loop bloccato risulterebbe comunque "healthy".

12:45 — Causa radice: bug lato Cerbero MCP get_historical

Cosa: probe dirette all'endpoint /mcp-deribit/tools/get_historical. Reale: due bug lato server:

  1. end_date data-nuda tronca a mezzanotte: end=oggi restituiva candele solo fino a oggi 00:00. Il df live finiva sempre alla barra di mezzanotte e non avanzava durante la giornata → nessun breakout fresco sull'ultima barra → nessun ingresso (condizione worker last_signal.idx >= last_idx - 1).
  2. Cap a ~5000 righe che ignora start_date: una richiesta di 365g a 15m restituiva ~52 giorni. Ecco perché ML01 si addestrava su soli 88 samples (overfit, train_acc 100%). Lezione: lo zero-trade non era nelle strategie ma nel feed dati. Sempre validare la freschezza/copertura dei dati prima di sospettare la logica.

13:30 — Fix lato Cerbero + verifica

Cosa: report passato al dev di cerbero-mcp; fix deployato (riavvio container) + doc aggiornata in cerbero-mcp/docs/API_REFERENCE.md. Reale dopo deploy (verificato con probe):

  • end=oggi (data nuda) → ultima candela = ora corrente (age ~3 min).
  • 365g a 15m → 35.099 candele, span 365.6g, nessun cap.
  • Supportati anche timestamp con orario (...T14:00:00, naive = UTC).

Nostro client (src/live/cerbero_client.py) invariato: passa già end=oggi, ora corretto. Lezione: "trust but verify" — la doc dichiarava i fix prima che fossero deployati; solo la probe diretta ha confermato cosa era davvero attivo sul server.

14:00 — Problema residuo: MT01 usava un trend 1h STANTIO

Cosa: check di tutte le strategie sul percorso di codice reale con dati freschi. Reale:

  • Tutte le 6 strategie girano senza crash; SQ01/SQ02 generano molti segnali.
  • MT01 leggeva il trend 1h dal parquet statico (load_data(asset,"1h")), non da Cerbero. Il parquet finiva a mezzanotte → per ogni barra 15m di oggi searchsorted cadeva oltre la fine e si agganciava sempre alla candela di mezzanotte (gap 14.8h). La conferma multi-timeframe — il cuore di MT01 — era di fatto congelata e il gap cresce ogni giorno.
  • In data/raw/ mancavano del tutto i parquet 15m (btc_15m, eth_15m) → backtest 15m rotti. Lezione: una strategia live che dipende da un file statico ha un punto cieco temporale; il dato live e quello di backtest devono provenire da fonti coerenti.

14:30 — Fix MT01: trend 1h live da Cerbero

Cosa: modifica al runner perché MT01 prenda l'1h live, non dal parquet.

  • MT01.generate_signals accetta un df_1h opzionale (fallback al parquet se assente).
  • StrategyWorker.tick(df, df_1h=None) lo inoltra ai signal.
  • multi_runner fa fetch 1h live (resolution 60) per gli asset MT01 ad ogni poll (htf_cache). Reale (verificato a codice montato, pre-rebuild): gap del trend 1h sull'ultima barra 0.75h (fresco) contro 14.8h col parquet statico. Segnali invariati sullo storico. Lezione: isolare la dipendenza dal file statico rende MT01 immune al drift tra un download_all() e l'altro.

14:55 — Rigenerazione dati + rebuild

Cosa: download_asset per 15m+1h (saltati 1m/5m, lenti e inutilizzati), poi docker compose up -d --build (il codice src/ è baked nell'immagine). Reale: parquet rigenerati con storia completa 2018→2026 e freschi (15m fino alle 14:45, 1h fino alle 14:00). Container ripartito: 6 strategie attive, ML01 riaddestrato su 534 samples (anno pieno), MT01 senza errori, fetch 1h live OK.

15:00 — Regressione backtest sui dati rigenerati

Cosa: rilanciati i backtest per confermare che i numeri documentati si riproducano sui dati ricreati da zero (BTC/ETH 15m, hold=3, fee 0.2% RT, leva 3x, pos 15%). Reale: accuratezze e drawdown identici, solo +1/+3 trade dalle barre recenti in più.

Strategia Ottenuto Documentato Esito
SQ01 BTC 15m 76.7% / DD 6.7% / 4063t 76.7% / 6.7% / 4062
SQ01 ETH 15m 76.4% / 6.2% / 2951t 76.4% / 6.2% / 2948
SQ02 BTC 15m 79.7% / 6.5% / 1251t 79.7% / 6.5% / 1250
SQ02 ETH 15m 78.6% / 3.4% / 944t 78.6% / 3.4% / 942
MT01 BTC 15m (ema20+vol) 82.7% / 5.9% / 503t 82.7% / 5.9% / 503 ✓ esatto
MT01 ETH 15m (ema20+vol) 81.2% / 2.9% / 404t ok

Lezione: l'integrità dei dati rigenerati è confermata — la pipeline di download produce risultati riproducibili. La config live di MT01 (ema20+vol) coincide col best documentato.

Punti aperti

  1. Backtest e drift dati: MT01 live ora è immune (1h da Cerbero), ma i backtest girano sempre sui dati fino all'ultimo download_all(). Per dati di backtest sempre freschi serve uno scheduling del download (cron/job).
  2. Healthcheck: valutare un check su mtime di status.json (< 180s) per rilevare uno stallo del loop, non solo l'esistenza del file.

23:00 — 3 nuove strategie con edge OOS fee-aware (branch strategy_free)

Obiettivo: trovare almeno 3 nuove strategie (oltre MR01), edge netto validato out-of-sample e fee-aware, per il target €1.000 → ~€50/giorno.

Metodologia (invariata dalla lezione squeeze): ingresso eseguibile a close[i] (nessun look-ahead), backtest netto dopo fee Deribit 0.10% RT + leva 3x, OOS = ultimo 30% held-out, robustezza su griglia parametri + sweep fee 0.000.20% RT, exit TP/SL intrabar o time-limit, una posizione per volta, capitale composto.

Candidati (scripts/analysis/strategy_research_v2.py), tutti mean-reversion (l'edge è sempre il rientro, mai la continuazione):

Candidato Esito Motivo
MR02 Donchian Fade Robusto su tutta la griglia n × sl_atr e tutte le fee
MR03 Keltner Fade Robusto su tutta la griglia n × k; banda ATR, indipendente da Bollinger
MR07 Return Reversal Intero blocco tp_atr=2.0 positivo full+OOS; esposizione ~8%
MR04 Z-score Reversion Robusto ma è MR01 riparametrizzato (stessa banda std): edge non nuovo
MR05 Bollinger + filtro ADX Non robusto: negativo su gran parte della griglia BTC
MR06 RSI(2) Connors ETH 1h negativo; non robusto su entrambi gli asset

Risultati (netto 0.10% RT, leva 3x, OOS, 1h):

Codice Meccanismo BTC OOS ETH OOS DD (full)
MR02 estremi canale Donchian H/L +172% enorme 30% / 42%
MR03 canale ATR su EMA +112% +886% 37% / 66%
MR07 z dei rendimenti di barra +105% +195% 25% / 46%

Validazione live-path (oos_validation.py, legge strategies.yml, exit hold del worker): tutte e tre positive netto OOS su tutto lo sweep fee, anche al pessimistico 0.20% RT → edge robusto pure al meccanismo di exit.

Verifiche: equivalenza esatta backtest produzione vs research engine (MR02 BTC: 2039 trade, DD 29% identici); le 3 classi si caricano dal strategy_loader; aggiunte a strategies.yml (BTC+ETH 1h). Nessuna suite di test nel progetto.

Onestà sul target: con 4 fade indipendenti × 2 asset il PnL storico aggregato supera €50/giorno, ma sono backtest a leva 3x su 8 anni con annate eccezionali (ETH 2024). Plausibile ma da confermare col paper trader live prima del capitale reale. DD alto su ETH (MR03 ~66%, come MR01) → leva più bassa consigliata per quell'asset.

File: strategy_research_v2.py, src/strategies/fade_base.py, scripts/strategies/MR0{2,3,7}_*.py (nuovi); strategy_loader.py, strategies.yml, CLAUDE.md (aggiornati). Lezione confermata: ogni edge robusto trovato finora è mean-reversion; ogni variante trend/continuation o oscillatore senza filtro perde netto.


23:45 — Aumentare Acc e ridurre DD (filtro trend + portafoglio)

Obiettivo: alzare accuratezza e abbassare drawdown sulle 4 fade, senza distruggere l'edge né overfittare (ogni leva misurata FULL e OOS).

Diagnosi: perdite/DD concentrati 20182021 (bear/covid/caos vol), su ETH DD pieno 6671%. Banco di prova: scripts/analysis/risk_improvements.py e risk_portfolio.py.

Leve testate:

Leva Esito Motivo
Sizing vol-target (size ∝ 1/dist-SL) Over-size sui trade a stop stretto → DD su, ritorno giù
Skip alta volatilità (ATR% in coda alta) L'alta vol è positiva per le fade (più reversione): Acc e ritorno giù
Filtro trend (|closeEMA200|/ATR > soglia → salta) Non fada trend/crolli estremi: Acc↑ ovunque, DD↓ molto su ETH, OOS regge
Portafoglio equipesato (sotto-conti indipendenti) Curve poco correlate → DD aggregato 14% (full)/10% (OOS) vs 20-70% singolo

Filtro trend — sweep soglia (assoluta in ATR, regola unica per tutte = niente overfit): 3.0 ATR è l'equilibrio (2.0 taglia troppo ritorno). Effetto su config deployata (base → filtro):

Sleeve Acc DD
MR01 ETH 46→55 71→26
MR02 ETH 49→55 42→25
MR03 ETH 49→52 66→34
MR07 ETH 48→54 46→21
MR01 BTC 51→54 32→34*
MR02 BTC 48→52 29→23
MR07 BTC 49→53 25→18
MR03 BTC 47→47 37→37 (filtro OFF)

*MR01 BTC: DD full +2pt ma Acc +3.7 e DD OOS piatto (14.8→15.0). MR03 BTC: il filtro peggiora entrambe (unico sleeve) → lasciato disattivo nello yaml.

Implementazione: helper trend_distance() in fade_base.py; param opzionali trend_max/ema_long (default None = retro-compatibile) in tutte le strategie (MR01/02/03/07); strategies.yml con trend_max: 3.0, ema_long: 200 (eccetto MR03 BTC). Verificato: equivalenza produzione vs ricerca.

Lezione: il modo onesto di ridurre il DD non è strozzare il sizing (peggiora), ma (a) non opporsi a trend estremi e (b) diversificare su strategie scorrelate.