Files
PythagorasGoal/CLAUDE.md
T

22 KiB
Raw Blame History

PythagorasGoal — Istruzioni per agenti

Panoramica

Progetto di ricerca: riconoscimento pattern frattali per trading algoritmico su criptovalute (BTC, ETH). L'obiettivo è arrivare a €50/giorno di profitto partendo da €1.000, entro 68 mesi.

Stack

  • Linguaggio: Python 3.11+
  • Package manager: uv (dipendenze in pyproject.toml, lock in uv.lock)
  • Dati: Parquet in data/raw/ (non committati, ~70 MB)
  • ML: scikit-learn (GradientBoostingClassifier)
  • Analisi: numpy, pandas, scipy
  • API dati: Cerbero MCP su cerbero-mcp.tielogic.xyz (Deribit, Bybit, Hyperliquid), ccxt/Binance come fallback
  • Config: pyyaml per strategies.yml

Struttura

src/data/              → download e caricamento dati
  downloader.py        → download/caricamento parquet (gate: solo strumenti validati)
  instruments.py       → discovery + validazione strumenti per exchange, registry
src/fractal/           → indicatori frattali (patterns.py, indicators.py, similarity.py)
src/backtest/          → engine di backtesting (engine.py)
src/strategies/        → classe base Strategy ABC + indicatori condivisi
  base.py              → Strategy, Signal, BacktestResult, YearlyStats
  indicators.py        → keltner_ratio, detect_squeezes, ema, atr, rv, correlation
src/live/              → paper trading live multi-strategia
  multi_runner.py      → orchestratore: carica YAML (strategies + pairs), fetch candele, tick worker
  strategy_worker.py   → worker single-leg: capital, trade log, stato persistente.
                         Exit guidati da strategia (TP/SL/max_bars via Signal.metadata),
                         fallback hold_bars/stop -2%. Usa fee_rt della strategia.
  pairs_worker.py      → worker a 2 GAMBE per PR01 (market-neutral): long A / short B sullo
                         z-score del log-ratio, exit |z|<=z_exit o max_bars, fee su 2 gambe.
  strategy_loader.py   → import dinamico classi Strategy da scripts/strategies/
  cerbero_client.py    → client HTTP per Cerbero MCP (Deribit testnet)
  signal_engine.py     → squeeze + ML real-time (legacy ML01, ora in waste) + validazione OOS
  telegram_notifier.py → notifiche Telegram per trade
src/portfolio/         → portafogli di prima classe (capitale-pool, backtest+live)
  base.py              → SleeveSpec, Portfolio (.backtest), load_active_portfolio
  weighting.py         → schemi pesi: equal/cap/inverse_vol/cluster_rp/manual
  sleeves.py           → builder unificato equity-per-sleeve (fonte unica, parità report)
  ledger.py            → PortfolioLedger: capitale/PnL/DD/persistenza+resume
  runner.py            → PortfolioRunner live (data Cerbero v2, sizing, ribilancio)
scripts/strategies/    → strategie con edge validato OOS: FADE (MR01/MR02/MR07),
                         HONEST (DIP01/TR01/ROT02), PAIRS (PR01), TSMOM + portafogli (PORT01/02/03)
scripts/portfolios/    → definizioni PORT01-06 + report run()
scripts/waste/         → strategie scartate (W01-W28 + famiglia squeeze SQ/MT/ML/AD/CM/PD)
scripts/analysis/      → ricerca/validazione OOS fee-aware (strategy_research, oos_validation, ...)
strategies.yml         → config multi-strategy paper trader
docs/diary/            → diario di ricerca giornaliero
docs/specs/            → specifiche di design
data/raw/              → file .parquet OHLCV (gitignored)
data/instruments_registry.json → allowlist strumenti validati (gate del downloader)

Comandi

uv sync                                              # installa dipendenze
uv run python -m src.data.downloader                  # scarica dati storici (solo strumenti validati)
uv run python -m src.data.instruments                 # (ri)costruisci il registry strumenti validati
uv run python scripts/strategies/MR01_bollinger_fade.py        # backtest una strategia (es. fade)
uv run python scripts/strategies/PR01_pairs_reversion.py       # backtest pairs market-neutral
uv run python scripts/analysis/strategy_research.py            # ricerca strategie fee-aware OOS
uv run python scripts/analysis/oos_validation.py               # perche' la famiglia squeeze e' scartata
uv run python scripts/analysis/report_families.py             # report per anno di tutte le famiglie
uv run python scripts/analysis/validate_worker_pairs.py        # replay worker 2 gambe == backtest
uv run python -m src.live.multi_runner                # paper trading live multi-strategia (strategie + pairs)
uv run python scripts/portfolios/PORT06_master_shape.py   # report backtest portafoglio (default)
uv run python -m src.portfolio.runner                      # paper trading a PORTAFOGLIO (capitale pool)
uv run python scripts/analysis/smoke_portfolio.py          # smoke live data layer Cerbero v2
docker compose up -d                                  # deploy Docker
uv run pytest                                         # test

Dati storici

Scaricati e salvati localmente in Parquet. Per rigenerarli:

from src.data.downloader import download_all, load_data
download_all()           # scarica BTC + ETH su 5m/15m/1h dal 2018
df = load_data("ETH", "15m")  # carica un asset/timeframe

Fonte primaria: Cerbero MCP (endpoint /mcp-deribit/tools/get_historical). Token observer: nel file secrets/observer.token del progetto CerberoSuite.

Strumenti & validazione (gate raccolta dati)

src/data/instruments.py scopre e valida gli strumenti per ogni exchange implementato — Deribit e Hyperliquid (esclusi Alpaca/stocks e Bybit, il cui feed testnet è farlocco). Per ogni perpetuo enumera via get_instruments /get_markets e verifica sui dati storici realmente raccoglibili: esistenza, congruenza OHLC, not-flat (scarta contratti morti), liquidità (volume daily) e congruenza prezzo cross-exchange (scostamento dalla mediana del base-coin ≤ 5% → scarta outlier come SOL-PERPETUAL=9.6 vs SOL reale ~82).

Output: data/instruments_registry.json (strumenti validi, timeframe, start-date). Gate: _download_cerbero_range rifiuta gli strumenti non validati (override allow_unvalidated=True solo per casi eccezionali). Rigenera con python -m src.data.instruments.

NB testnet. Il token Cerbero punta a testnet; la congruenza cross-exchange è il filtro che distingue i feed realistici (Deribit, Hyperliquid) da quelli farlocchi (Bybit). Simboli Deribit: BTC/ETH = <COIN>-PERPETUAL (inverse); alt = <COIN>_USDC-PERPETUAL (lineari USDC). Registry attuale: Deribit 18/106, Hyperliquid 66/74 validi (major liquidi: BTC dal 2018, alt dal 2022).

Strategie attive

LEZIONE CRITICA (2026-05-28). L'intera famiglia squeeze-breakout (SQ01-04, MT01, ML01, AD01, CM01, PD01) è stata scartata in scripts/waste/: le accuratezze storiche 76-82% erano un artefatto di look-ahead. Quei backtest decidono la direzione con sign(close[i]-close[i-1]) (la candela di breakout i) ma entrano a close[i-1] — cioè comprano prima della candela che usano per scegliere la direzione. Dal vivo il worker scopre il breakout solo a close[i] ed entra lì: l'edge sparisce (win-rate ~47%, lancio di moneta). Sotto ingresso onesto e fee reali tutte perdono, anche a fee zero. Inoltre i breakout rientrano (mean-reversion > continuation). Vedi scripts/analysis/oos_validation.py e intrabar_test.py.

Tutte le strategie estendono src.strategies.base.Strategy (generate_signals() → backtest()). Le strategie mean-reversion condividono src.strategies.fade_base.FadeStrategy (backtest intrabar TP/SL/max_bars). Strategie con edge netto validato OOS fee-aware (tutte fade/mean-reversion):

Codice Nome Meccanismo Edge OOS netto (1h, fee 0.10% RT) DD Note
MR01 Bollinger Fade banda std attorno a SMA BTC +201% / ETH +1238% 15-72% Fada la banda, TP alla media, SL ad ATR
MR02 Donchian Fade estremi canale H/L BTC +172% / ETH enorme 30-42% Fada la rottura del canale, TP al centro
MR07 Return Reversal z dei rendimenti di barra BTC +105% / ETH +195% 25-46% Fada il movimento estremo, exit in ATR; esposizione ~8%

MR03 Keltner Fade spostata in scripts/waste/: era la fade più debole (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa idea di banda). Rimuoverla dal portafoglio ne ha migliorato le metriche. La funzione keltner_fade resta in strategy_research_v2.py come record.

Lezione confermata: l'edge è sempre mean-reversion (i breakout rientrano). Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro (RSI revert, ADX-filtered fade) perdono netti → restano scartati.

Ogni strategia è robusta su tutta la sua griglia parametri (entrambi gli asset → tutte positive OOS) e su tutte le fee 0.00-0.20% RT (margine ampio). MR01 validato col worker reale: BTC +196% / ETH +251% OOS (nov 2023→mag 2026). Ricerca completa: scripts/analysis/strategy_research.py (MR01) e scripts/analysis/strategy_research_v2.py (MR02/MR03/MR07). Validazione live-path: scripts/analysis/oos_validation.py.

Filtro trend (riduzione DD + aumento Acc). Tutte le fade accettano i parametri opzionali trend_max / ema_long: saltano i segnali quando il prezzo è troppo esteso rispetto al trend di fondo (|close EMA(ema_long)| / ATR(14) > trend_max), cioè quando si starebbe fadando un trend/crollo estremo. Con trend_max=3.0, ema_long=200 (default in strategies.yml): accuratezza su tutti gli sleeve e DD giù drasticamente su ETH (MR01 71%→26%, MR02 42%→25%, MR03 66%→34%, MR07 46%→21%), edge OOS confermato (vedi scripts/analysis/risk_management.py). Unica eccezione: MR03 BTC, dove il filtro peggiora entrambe → lasciato disattivo. Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano).

Portafoglio. Diversificare su sotto-conti indipendenti equipesati (le 4 strategie × BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown.

Combinare le due famiglie (fade + honest). Le fade (reversione intraday 1h) e le honest (DIP/TR/ROT trend+rotazione multi-crypto) sono quasi scorrelate (correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve → DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD / 2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in scripts/analysis/combine_portfolio.py.

ROT02 — riduzione DD (top_k 2→3). La rotazione dual-momentum honest concentrava il book su 2 soli asset (DD 40%). Diversificare su 3 (top_k=3) dimezza quasi il DD (40%→26%) e alza pure il ritorno full (+1095%→+1303%, ret/DD da 27 a 50); il vol-target abbassa il DD ma sacrifica ritorno, quindi si tiene top_k=3 senza VT. Applicato a ROT02_dual_momentum.py e a _rot_daily_equity (usata dai portafogli).

Portafogli pronti (artefatti accorpati e migliorati). Oltre a PORT01 (solo honest), due script in scripts/strategies/:

  • PORT02_fade_master.py — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend), equal-weight daily: DD ~8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
  • PORT03_all_master.py — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti: equal (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e 5050 fra le due famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata. Come PORT01, sono meta-portafogli (script run() di report), non Strategy con generate_signals, quindi non nel strategy_loader.

Esplorazione famiglie alternative (branch strategy_explore, 2026-05-29). Esplorate 9 famiglie nuove con agenti paralleli su harness onesto condiviso (scripts/analysis/explore_lab.py). 7 sono rumore (rifiutate: stagionalità oraria/mensile, cross-sectional reversal, opening-range breakout, lead-lag BTC→alt, continuation intraday — quest'ultima riconferma la dominanza mean-reversion). Due edge reali:

  • PR01 Pairs (scripts/strategies/PR01_pairs_reversion.py): spread reversion market-neutral sul log-ratio z-score, config UNIVERSALE n=50 z_in=2.0 z_exit=0.75 max_bars=72 (anti-overfit, niente tuning per-coppia). 5 coppie robuste: ETH/BTC (Sharpe 4.36), LTC/ETH (3.08), ADA/ETH (2.69), BTC/LTC (2.36, robusta anche 4h), ETH/SOL (1.96, la più debole). Pattern: sempre alt-liquido vs major. Plateau confermato (heatmap 20/20 Sharpe>1) + walk-forward (ETH/BTC 11/12 finestre+). BNB/ETH scartata (overfit). Corr col mercato ~0.02-0.08. Fee su 2 gambe: worker live implementato (src/live/pairs_worker.py, sezione pairs: in strategies.yml). LOGICA validata (validate_worker_pairs.py: replay == backtest ESATTO). LIVE (live_smoke_pairs.py, smoke reale Cerbero): tutte e 5 le coppie con feed live fresco. Naming Deribit: BTC/ETH = <COIN>-PERPETUAL (inverse); alt = <COIN>_USDC-PERPETUAL (lineari USDC, storia dal 2022). Trappola: LTC-PERPETUAL/SOL-PERPETUAL danno vuoto/dati errati → usare sempre _USDC-PERPETUAL. Resta da verificare solo liquidità/fill in esecuzione. Verifica edge: pairs_research.py.
  • TSM01 (scripts/analysis/tsmom_research.py): TSMOM multi-orizzonte 3/6/12m + risk-off, gross 0.30, distinto da ROT02 (corr 0.62), DD 15-22%, mai un anno negativo. Robusto (36/36 config OOS+) ma diversificatore, non motore di ritorno (rende meno di ROT02).

Aggiungere i 5 pairs al MASTER (quasi scorrelati, ~0.02-0.09) è il free-lunch più grande (scripts/analysis/combine_v2.py). Numeri sobri onesti (l'OOS singolo 2024-25 è regime calmo → ottimistico ~50%): worst-DD su 90g rolling ~6% (non 2.3%), Sharpe atteso ~5 (mediana semestrale), ogni anno positivo dal 2021, regge leva 2x + slippage doppio (CAGR 36%, Sharpe 5.1). Config robusta raccomandata: MASTER-esteso equal-weight, leva 2x, cap pairs ~30-35% (i pairs sono ~57% del rischio; worker live a 2 gambe implementato, validato e con feed live su tutte e 5 le coppie — resta da verificare liquidità/fill in esecuzione reale). La confluenza multi-TF è stata SCARTATA (overfit).

Pattern del segnale per FORMA (branch shape_patterns, 2026-05-29). Esplorate 5 famiglie di shape forecasting con agenti paralleli su harness onesto (scripts/analysis/shape_lab.py: analog kNN causale, no-look-ahead verificato). 4/5 sono RUMORE (riconfermano la dominanza mean-reversion): analog kNN sulla forma grezza (solo BTC-overfit), encoding candele UP/DOWN/DOJI+body/shadow (hit-rate ~50%), DTW+template geometrici (DTW peggiora l'euclidea; template overfit), PIP/pivot/zig-zag (0/48 robuste). Vedi scripts/analysis/shape_*_research.py.

  • SH01 Shape-ML (scripts/strategies/SH01_shape_ml.py): UNICO edge. Una LogisticRegression legge 17 feature di forma (body/shadow, rendimenti, pendenza/curvatura, pos max/min, RSI, estensione) e predice il segno del rendimento a H barre in walk-forward (scaler+modello solo sul passato, no leakage). Config W24 H12 th0.58. A differenza dello squeeze regge fee 0.20% RT. Win-rate ~50% → l'edge è nell'asimmetria, non nella frequenza. Validazione (scripts/analysis/shape_ml_validate.py): BTC robusto OVUNQUE (expanding +219%/ OOS +42% Sharpe 2.72 8-9 anni; rolling 2y +166%/+96%; stress 2x+slippage OK), ETH/ADA robusti solo expanding (secondari), LTC/SOL/XRP scartati. Griglia: 5/27 celle robuste su cresta stretta W24/H8-12 → overfit moderato, scelta la config conservativa. Valore vero: diversificatore (corr +0.08 col MASTER); aggiungerlo migliora l'OOS del MASTER (Sharpe 4.33→5.10, DD 4.7%→4.2%). NON motore standalone. LIVE: serve worker con retraining periodico (lo StrategyWorker è a regola fissa) → in MODULE_MAP ma non ancora in strategies.yml. Diario: docs/diary/2026-05-29-shape.md.

Metodologia obbligatoria per ogni nuova strategia (per non ripetere l'errore squeeze):

  1. Ingresso eseguibile: direzione e prezzo decisi con dati fino a close[i], mai close[i-1] con direzione da i.
  2. Backtest NETTO dopo fee realistiche Deribit (0.10% RT taker, non 0.20%) + leva.
  3. Validazione out-of-sample (held-out) + robustezza su griglia parametri + sweep fee.
  4. Crea script in scripts/strategies/, aggiungi a MODULE_MAP (strategy_loader.py) e a strategies.yml.

Strategie scartate storiche in scripts/waste/ (W01-W28 + la famiglia squeeze).

Verso €50/giorno. Con 4 strategie indipendenti (MR01/MR02/MR03/MR07) × 2 asset (BTC/ETH) su €1000 ciascuna, il PnL medio storico aggregato è ben oltre €50/giorno; ma quei numeri sono backtest a leva 3x su 8 anni e includono anni eccezionali (es. ETH 2024). Stima onesta: il target è plausibile su un portafoglio diversificato di queste fade, ma va confermato col paper trader live prima di rischiare capitale reale.

Portafogli

  • Un Portfolio è un oggetto di prima classe (src/portfolio/) con definizione (sleeve + schema pesi) e due facce sulla STESSA definizione: .backtest() (riusa il builder unico di sleeves.py → parità esatta con report_families) e live (PortfolioRunner: capitale pool condiviso, sizing per peso, ribilancio giornaliero, ledger aggregato in data/portfolios/{code}/).
  • Schemi peso: equal (default), cap (tetto per famiglia, es. pairs 33% — config raccomandata), inverse_vol, cluster_rp (equal fra cluster naturali poi inverse-vol intra-cluster), manual. Definiti in weighting.py; la chiave cap è la famiglia (PAIRS/FADE/HONEST/SHAPE/TSM).
  • Default portfolios.yml: PORT06 (master+shape), weighting=cap pairs 0.33, leva 2x, ribilancio 1D. Backtest PORT06: FULL Sharpe 6.07 / OOS Sharpe 8.19, DD 4.9% full / 2.3% OOS.
  • Data layer Cerbero v2: get_historical_v2 unificato + get_instruments (naming robusto) + get_ticker_batch. Trading su Deribit.
  • SCOPE LIVE v1: il runner esegue gli sleeve con worker pronti = fade (MR01/02/07) + pairs (PR01) + shape (SH01, via MLWorkerWrapper con retraining). Gli sleeve honest (DIP01/TR01/ROT02) e TSM01 sono SALTATI nel live (nessun worker dedicato ancora) → restano solo nel backtest; il runner li logga come saltati e rinormalizza i pesi sugli sleeve eseguibili. Worker honest/TSM01 = fase 2.
  • Limite noto: al ribilancio le posizioni APERTE restano sul loro notional (non travasate); comportamento fedele al backtest daily-rebalanced entro il turnover infragiornaliero.

Multi-Strategy Paper Trader

Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna con €1000 USDC virtuali indipendenti.

Config: strategies.yml — due sezioni: strategies (single-leg: fade/honest) e pairs (a 2 gambe). Attive: 6 fade (MR01/MR02/MR07 × BTC/ETH) + 5 coppie PR01. Due worker: strategy_worker.py (single-leg) e pairs_worker.py (2 gambe, long A / short B sullo z-score del log-ratio, fee su 2 gambe). Persistenza: data/paper_trades/{worker_id}/ con trades.jsonl (append-only) + status.json (resume al restart). Hot-add: aggiungi riga YAML → docker compose restart → storico intatto. Exit strategia: se un Signal porta tp/sl/max_bars in metadata (come le fade), il worker esce su take-profit/stop-loss/time-limit; i pairs escono su |z|≤z_exit o max_bars. Naming Deribit (feed live): major = <COIN>-PERPETUAL (inverse); alt = <COIN>_USDC-PERPETUAL (lineari USDC). Vedi INSTRUMENT_MAP in multi_runner.py. Notifiche: Telegram per ogni trade (richiede .env con TELEGRAM_BOT_TOKEN e TELEGRAM_CHAT_ID).

Convenzioni

  • Strategie in scripts/strategies/ con codice univoco (MR01, ...).
  • Script scartati in scripts/waste/ (W01-W28 + famiglia squeeze).
  • Diario in docs/diary/YYYY-MM-DD.md. Aggiornare dopo ogni esperimento significativo.
  • Nessun dato sensibile nei commit (token, chiavi API). Usare .gitignore.
  • Verificare sempre assenza di data leakage prima di fidarsi dei risultati. In particolare: returns[i-w : i] include close[i] che è un candle nel futuro — usare returns[i-w : i-1].

Attenzione

  • Data leakage: è stata trovata e corretta nello script 05. Ogni volta che si usano rendimenti logaritmici (np.diff(np.log(close))), ricordare che returns[k] usa close[k+1]. I feature devono fermarsi a returns[i-2] se il prezzo corrente è close[i-1].
  • Fee: Deribit perp reale = taker ~0.05%/lato (0.10% round-trip), maker ~0%. Usare 0.10% RT come baseline (lo 0.20% storico era pessimista 2x). Includere SEMPRE nel backtest: sono vincolo di prim'ordine, molte operazioni = morte per fee. Il worker usa strategy.fee_rt (MR01 = 0.001).
  • Leva: testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown.
  • GBM: GradientBoostingClassifier di scikit-learn. Ensemble di alberi decisionali sequenziali. Walk-forward per evitare leakage temporale.
  • Cerbero get_historical (fix 2026-05-28): end_date come data nuda è inclusivo dell'intera giornata fino all'ultima candela chiusa (es. end=oggi arriva fino ad ora, non più a mezzanotte); accettati anche timestamp con orario (...T14:00:00, naive=UTC); nessun cap a ~5000 righe (paginazione interna). Il client passa già end=oggi, ora corretto. Prima del fix il paper trader restava a zero trade perché il feed era fermo a mezzanotte.
  • Dati ETH Deribit 15m: 14-30%/anno di candele flat (O=H=L=C, volume 0, run fino a ~54h) per bassa liquidità del perpetuo. Verificato (2026-05-28): escluderle NON cambia i backtest (Δacc ≤0.5pp) → edge robusto. Resta un caveat operativo (slippage/fill in trading reale, irrilevante per paper). BTC pulito eccetto picco ~8% nel 2024.