274 Commits

Author SHA1 Message Date
Adriano Dal Pastro bec2fb2089 chore(dashboard): monta .env.mainnet read-only nel container per lo "Shadow live"
Il token mainnet (sola lettura) abilita conto/posizioni REALI nel box Shadow della dashboard.
Montato a runtime, NON nell'immagine (.env.mainnet resta dockerignored). Solo letture: nessun
endpoint di trading e' raggiungibile da src/live/deribit.py. Verificato: conto reale $598.07 letto
dal container, TP01 flat -> 0 ordini.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:02:26 +00:00
Adriano Dal Pastro 9c48cdd884 feat(live): SHADOW MODE TP01 su Deribit mainnet (sola lettura) + dashboard 3-way
Validazione esecuzione di TP01 a RISCHIO ZERO: gira il loop live contro dati/conto/posizioni REALI
del mainnet, costruisce gli ordini di ribilancio esatti e li STAMPA invece di inviarli. Niente
testnet (e' la causa del reset v2.0.0: feed farlocco) -> shadow su mainnet reale + micro-test a
size minima come unica via per il fill (passo successivo).

- src/live/deribit.py  : client Deribit mainnet SOLA LETTURA (ticker/conto/posizioni via Cerbero MCP)
  + costruttore ordini deterministico (notional->contratti, step BTC $10/ETH $1, quantizzazione,
  delta vs posizione). Nessun metodo di trading, by design.
- src/live/shadow.py   : shadow_report() condiviso CLI+dashboard (niente drift); degrada con grazia
  se il mainnet non risponde.
- scripts/live/live_trend.py : CLI shadow (--no-net offline, --equity override). Verificato su
  mainnet reale: conto $598.07, posizioni flat, TP01 flat -> 0 ordini, parita' col paper OK.
- src/live/dashboard.py : box "Shadow live" + titolo/note al 3-way (TP01+XS01+VRP01).
- tests/test_live_shadow.py : 9 test deterministici (quantizzazione, sizing 50/50, entry/exit/None,
  parita' live==backtest). Suite 26/26.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:01:12 +00:00
Adriano Dal Pastro 9ed2ea4b13 fix(fetch_hyperliquid): taglia il backfill sintetico di cerbero MCP (vol=0) — universo 52->51
Cerbero MCP padda il periodo pre-quotazione su HL con barre SINTETICHE (volume 0, prezzi
copiati da Binance -> matchano cross-venue e non sono flat): asset listati dopo lo START
(es. AXS 83%, ALGO/SAND 37%) passavano i gate flat+cross-venue ed erano certificati PULITO
pur non essendo negoziabili. E' il caso v2.0.0 (edge su un book che non c'era).

Fix: il VOLUME e' il rivelatore del backfill -> (1) taglio del run iniziale a volume 0
(serie nativa), (2) gate storia nativa >=365g reali (AXS scartato), (3) gate vol=0 interno,
(4) cross-venue/flat ricalcolati solo sulle barre reali, (5) parquet scartati rimossi.

Verificato direttamente su cerbero MCP mainnet. I 19 major di XS01 hanno 0 backfill ->
strategia live invariata. Diario 2026-06-20-cerbero-backfill-fix.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 11:53:55 +00:00
Adriano c6236ed5d9 feat: integra VRP01 come sleeve del portafoglio (put credit spread + gate IV-rank)
src/portfolio/sleeves.py: _vrp_combo_returns + vrp_sleeve, self-contained in src/
(pricing BS + gate causali inline, DVOL da data/raw). Settimanale->giornaliero col
lump sul giorno di scadenza (preserva lo Sharpe annualizzato, peso costante).

Registry: TP01 0.55 / XS01 0.25 / VRP01 0.20 (TP01 resta maggioranza; VRP e' un
lead modellato, non deploy pieno). TP01+VRP01 monotono: FULL 1.30->1.44, HOLD
0.31->0.40 a peso 20%. Scorrelato a TP01 (+0.01).

Test tests/test_vrp_sleeve.py (5 pass). CLAUDE.md + diario aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 11:24:40 +02:00
Adriano 75e1aacd19 research: analisi strategie FinanceOld + VRP v2 (defined-risk spread + gate IV-rank)
Analisi 4 progetti FinanceOld. Solo il filone opzioni-VRP backtestabile sui dati
certificati (funding-arb senza dati storici; Polybot ticks corrotti/3gg/edge=latenza).

VRP v2 porta 3 idee di OptionsAgent nel framework, causale + fee-aware:
- put credit spread (rischio definito): worst-week -16.6%->-7.4%, DD 33%->21%
- gate IV-rank>0.30: ribalta HOLD-OUT da -0.25 a +0.28 (alpha = filtro regime)
- COMBO f=1.0: FULL Sh 1.10, HOLD 0.60, DD 12%, positiva/piatta ogni anno
- blend TP01 70/30 -> Sh 1.00, DD 7% (corr +0.07)

Lead quantificato, non deploy (premio modellato ATM, serve f di stress reale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 11:16:59 +02:00
Adriano Dal Pastro 92a63feb9c chore(monitor): cron giornaliero (refresh dati + avanza paper) + cleanup crontab/orfano
scripts/cron_daily.sh: rebuild_history BTC/ETH + fetch_hyperliquid (52 alt) + fetch_dvol +
paper_portfolio, ogni giorno 00:30 UTC -> tiene fresco il dato che il dashboard legge e avanza
il paper forward. fetch_hyperliquid END ora DINAMICO (oggi) per il refresh.

Cleanup: rimosso container orfano pythagoras-portfolio (vecchio runner pre-reset, exited);
crontab ripulito dai 4 job rotti del micro-test mainnet (hourly_report/drift/reconcile/
ledger_vs_backtest -> script archiviati in Old/), backup in logs/crontab.pre-reset.bak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 09:06:38 +00:00
Adriano Dal Pastro 26e977d338 feat(monitor): dashboard PAPER del portafoglio attivo (TP01+XS01) + paper forward loop
src/live/dashboard.py: web UI stdlib (:8787) che mostra metriche (FULL/HOLD Sharpe, DD, CAGR),
per-sleeve, posizioni correnti, equity (backtest + paper forward), ultimo dato. Solo MONITOR,
esecuzione REALE disabilitata. scripts/live/paper_portfolio.py: forward-only del portafoglio
(StrategyPortfolio su active_sleeves), stato persistente in data/paper_portfolio (gitignored).

Dockerfile + docker-compose.yml minimali (solo servizio dashboard; runner/esecuzione restano in
Old/). Container pythagoras-dashboard ricostruito col codice nuovo (il vecchio mostrava dati
pre-reset). Mount data/ read-only. .dockerignore esclude Old/data/.venv/.git/.env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:57:55 +00:00
Adriano Dal Pastro 0d9f483131 docs: aggiorna CLAUDE.md allo stato corrente (XS01 blend+gate, portafoglio FULL/HOLD 1.55)
XS01 ridescritta con affinamenti (blend lookback [30,90] + gate dispersione p30, standalone FULL
1.50). Portafoglio attivo TP01 70% + XS01 30% -> FULL Sh 1.55 / HOLD 1.55 / DD 4.4%. Aggiunte le
lezioni: espansione universo Hyperliquid NON aiuta XS (52/top-liq/trend-multiasset tutti peggiori,
i margini sono nel segnale); lead opzioni VRP quantificato (f reale ~1.0, non deploy). Struttura/
comandi aggiornati (scripts/research track A-I + options_vrp + fetch_dvol; scripts/portfolio xsec_*).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:29:24 +00:00
Adriano Dal Pastro 87dd56a9ce feat(XS01): + gate di dispersione (p30) — portafoglio FULL 1.48->1.55, HOLD 1.06->1.55
Momentum cross-sectional vive nella dispersione; gate: entra solo se la dispersione cross-section
del momentum supera il percentile ESPANDENTE causale (altrimenti flat). Plateau robusto p15-p35
(non knife-edge: il crollo a p40+ e' over-gating); scelto p30. XS01 standalone FULL 1.10->1.50,
HOLD 1.03->1.71, DD 14%->10.8%. Portafoglio TP01 70+XS 30: FULL 1.48->1.55, HOLD 1.06->1.55, DD
4.6%->4.4%. Il gate alza SIA FULL SIA hold-out (tiene XS attivo nei regimi dispersi, flat nei bull
compatti; causale). E' il concetto del vecchio XS01.

sleeves.XS_CFG disp_pct=30; engine _xsec_returns gatea su dispersione. 12 test ok.
Diario 2026-06-19-xsec-dispgate.md. Affinamenti del segnale (blend+gate) > espansione universo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:24:04 +00:00
Adriano Dal Pastro fd5a0bd3cf feat(XS01): affina con blend di lookback [30,90] — FULL 0.80->1.10, portafoglio 1.41->1.48
Come TP01 fonde gli orizzonti, XS01 ora fonde 30g+90g del momentum cross-sectional (z-score per
lookback, mediato). Sweep: [30,90] e' il sweet spot (fonde i due singoli robusti, anti-overfit):
XS01 standalone FULL 0.80->1.10, DD 21%->14%, corr a TP01 -0.06->-0.12, 100% anni+. Portafoglio
TP01 70 + XS01 30: FULL Sh 1.41->1.48, DD 5.2%->4.6%, ~€/g 1.65->1.78; hold-out 1.15->1.06 (calo
marginale dentro il rumore). Piu' robusto (due orizzonti) + diversifica meglio -> promosso.

sleeves.XS_CFG lookbacks=(30,90), engine _xsec_returns usa lo score blended. 12 test ok.
Diario 2026-06-19-xsec-blend.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:19:12 +00:00
Adriano Dal Pastro bf6ade51af research: strato trend multi-asset (52 alt) RIDONDANTE col trend TP01 -> non aggiunto
TSMOM CANONICAL applicato a ogni alt dei 52, equal-weight. Standalone FULL 0.66 ma HOLD-OUT -1.03
(long negli alt nel calo 2025-26), corr a TP01 +0.74 (stessa beta direzionale). Contributo al
portafoglio NEGATIVO (HOLD -0.16/-0.27). Broadenizzare il TREND non diversifica: e' la stessa
direzionalita' su asset piu' rumorosi. Solo il market-neutral (XS01) diversifica davvero.

Chiude il filone espansione-universo (XS-52, top-liquidita' dinamico, trend-52: tutti peggiori).
Configurazione validata invariata: TP01 70% + XS01 (19 major) 30%, FULL Sh 1.41 / HOLD 1.15.
I margini reali sono in un MECCANISMO diverso (opzioni VRP), non nell'universo crypto-direzionale.
Diario 2026-06-19-trend-multiasset.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:17:16 +00:00
Adriano Dal Pastro 182d4eeac2 research: universo top-liquidità DINAMICO per XS — anch'esso peggiore del fisso-19 (memecoin diluiscono)
xsec_dynuniverse.py: a ogni ribilancio top-N per dollar-volume 30g causale (ragged-aware), poi XS
momentum. Esito: best dinamico top12 FULL 0.65/OOS0.54 (un anno neg) vs fisso-19 FULL 0.80/OOS1.20
(100% anni+). Contributo TP01+DYN 1.10/0.60 vs TP01+XS19 1.25/1.15. La classifica per volume ammette
i MEMECOIN ad alto volume (WIF/ORDI/JUP) erratici -> diluiscono. Liquidità != qualità.

Conclusione: ne' 52-all ne' top-liquidità dinamico battono i 19 major curati. XS01 resta sui 19.
Portafoglio invariato TP01 70% + XS01 30% (FULL 1.41 / HOLD 1.15). 12 test ok. I 52 parquet restano
per ricerca futura. Diario 2026-06-19-xsec-universe-expansion.md aggiornato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:09:48 +00:00
Adriano Dal Pastro 8426d05f12 research: espandere universo XS01 a 52 asset DILUISCE (negativo) -> XS01 blindato sui 19 major
Esteso fetch_hyperliquid a 52 alt certificati (cross-venue vs Binance, flat 0%, 2024+; +gate
delistato per MKR/FXS). Ma il cross-sectional momentum sui 52 e' NEGATIVO (FULL -0.1..-0.6, k grande
non aiuta) vs +0.67/OOS0.91 sui 19 major (stessa finestra): i ~33 small-cap (WIF/JUP/ORDI/PYTH/TAO..)
sono idiosincratici/mean-reverting e rovesciano il momentum relativo. "Piu' asset = piu' robusto"
e' FALSO per l'XS momentum: la breadth utile e' quella dei major liquidi.

Fix: lo sleeve _xsec_returns usa XS_UNIVERSE esplicito (19 major), non glob-all (aggiungere parquet
certificati non lo rompe piu'). I 52 parquet restano su disco per ricerca futura, non per XS01.
Portafoglio ripristinato e invariato: TP01 70% + XS01 30%, FULL Sh 1.41 / HOLD 1.15. 12 test ok.
Diario 2026-06-19-xsec-universe-expansion.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:00:00 +00:00
Adriano Dal Pastro 53d0134cb1 research: calibra VRP su quote REALI cerbero-bite — f≈1.0 (non 1.29), lead DEBOLE confermato non-deploy
cerbero-bite GIA' accumula la catena reale mainnet (option_chain_snapshots, 2026-05->oggi) -> uso
quella (niente nuovo snapshotter). options_vrp_calibrate.py misura il fattore f reale su 223
snapshot/asset (put weekly delta-0.28, BID): BTC f median 1.03, ETH 0.97, skew reale +1.5..1.9 pt.
Il f reale e' ~1.0 NON 1.29 (lo snapshot singolo del branch era outlier ad alto skew). -> VRP sleeve
= punto f≈1.0 = Sharpe ~0.71 (conservativo), DD 33%, hold-out piatto: diversificatore DEBOLE (corr
+0.07) sotto TP01, coda severa. Calibrazione su ~10g densi, 1 regime calmo; f di stress non misurato.

Verdetto: la decorrelazione modesta NON giustifica il rischio di coda short-vol senza dato reale
multi-regime (serve che cerbero-bite copra un crash). Confermato NON-deploy. Portafoglio invariato
TP01 70% + XS01 30%. Diario 2026-06-19-options-vrp-lab.md aggiornato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:38:54 +00:00
Adriano Dal Pastro 8f9ce89039 research: imposta sleeve OPZIONI VRP — infrastruttura + prima validazione (LEAD reale, non deploy)
fetch_dvol.py: storia DVOL (IV Deribit) BTC/ETH 2021-2026 -> data/raw/dvol_*. options_vrp_lab.py:
backtest CSP settimanale, premio BS su DVOL reale + calibrazione f (skew/spread), payoff sul path
realizzato, causale; gauntlet (VRP, sweep f/delta, per-anno, worst-weeks, corr+contributo vs TP01).

Esiti (book 50/50 put delta-0.28): VRP reale (BTC IV>RV 78% del tempo). Sharpe DIPENDE da f:
0.71 conservativo (IV-ATM) -> 1.70 a f=1.29 (skew reale calm). CODA severa (DD 30-33%, settimane
-15..-26% su LUNA/FTX/crash; 2022 -9%, 2026-YTD -14%). Scorrelato a TP01 (+0.07) -> migliora il
portafoglio anche a premio conservativo (TP01 70%+OPT 30%: Sh settimanale 0.71->0.97).

VERDETTO: lead reale e diversificante, MA premio modellato (non catena reale) + calibrazione
ottimistica + coda short-vol non catturata nello stress. Regola: mai short-vol da modello in
deploy. NON aggiunto. Portafoglio invariato TP01 70% + XS01 30%. Prossimo: accumulo quote reali
multi-regime + stress crash + daily-MTM + paper testnet. Diario 2026-06-19-options-vrp-lab.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:30:08 +00:00
Adriano Dal Pastro 87af03955c research: porta artefatti da strategy-research-calendar (tracks F-I + eval crypto_backtest + lead OPZIONI/VRP)
Dal branch parallelo strategy-research-calendar (continuazione della linea TP01). Porta su main il
record di ricerca + la fondazione del lead opzioni (NIENTE blob dati, niente codice in conflitto):
- Tracks F/G/H/I (seasonality/calendar, prior-levels, volume-vol, momentum-reversal): tutti
  NEGATIVI/spurii -> confermano il soffitto Sharpe ~1.3 su BTC/ETH direzionale (calendar = buy&hold
  travestito; mean-reversion morta anche a fee 0). Diari + script.
- trackD_lookahead_audit.py: audit anti-look-ahead (stesso esito del nostro fix >=12h).
- eval-crypto-backtest-options.md: valutazione strategia esterna crypto_backtest. Cross-valida TP01
  (il loro sleeve spot 12h ~ TP01: due ricerche indipendenti, stessa conclusione). Identifica il
  LEAD: sleeve income OPZIONI (vendita put settimanali delta-0.28, VRP IV>RV), scorrelato ~0.22 al
  trend -> via per superare il soffitto ~1.3.
- options_real_quote_check.py + cerbero-bite-mainnet-verified.md: VERIFICATO su QUOTE REALI Deribit
  mainnet (cerbero-bite/MCP = mainnet, bit-identico a ccxt.deribit). Premio reale (BID, con skew) =
  1.29x il modellato -> il backtest SOTTOSTIMA il premio; il rischio vero e' la CODA (short-vol) +
  liquidita' di roll in stress, non la magnitudine.

NB: lo sleeve opzioni e' un LEAD, NON deployato: prezzato da modello (BS su DVOL) + 1 snapshot in
regime calmo. Serve validazione real-chain multi-regime + stress crash + paper su testnet prima di
aggiungerlo al portafoglio. Portafoglio attivo invariato: TP01 70% + XS01 30%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:24:16 +00:00
Adriano Dal Pastro 790caefd52 research: wave1 beat-TP01 (26 agenti BTC/ETH) — nessun 3o sleeve robusto, portafoglio invariato
26 agenti, 3 contender ri-verificati onesti: tsmom_12h scartato (corr +0.49 = TP01 veloce),
breakout_atr scartato (gonfia solo FULL storico, hold-out +0.05), highvol_rev in WATCHLIST
(scorrelato e migliora FULL+hold-out MA edge solo a REV_LB=1 = picco non-plateau, FULL mediocre
0.74, HOLD>>FULL = regime-luck alta-vol 2025-26, reversal+concept-flip). Stesso difetto del RV
bocciato -> non deployato. Portafoglio resta TP01 70% + XS01 30%. L'edge incrementale e' venuto
dall'espansione universo (Hyperliquid cross-sectional), non da altre trend-variant su 2 asset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:19:47 +00:00
Adriano Dal Pastro a5a61ac7e3 feat(portfolio): XS01 cross-sectional (Hyperliquid) BATTE il portafoglio -> TP01 70% + XS01 30%
Espansione universo (su input utente "storico da cerbero"): il Cerbero MCP col token MAINNET serve
Hyperliquid (230 perp REALI, storia nativa dal 2024). fetch_hyperliquid.py certifica 19 alt liquidi
a 1d (flat 0%, cross-venue 4-9 bps vs Binance) -> data/raw/hl_*_1d.parquet. Abilita le strategie
CROSS-SECTIONAL (impossibili a 2 asset).

XS01 = cross-sectional momentum market-neutral (long 5 forti / short 5 deboli su ret 30g, ogni 10g,
vol-target 20%). Validato onesto: plateau (config/k/subset), fee-robusto (0.3% RT), scorrelato a TP01
(-0.06), positivo OGNI anno 2024-26, meccanismo complementare (lavora nella dispersione quando TP01
e' in cash). Diverso dal regime-luck RV bocciato (19 asset, plateau, ogni anno+).

Contributo al portafoglio (outer-join + pesi rinormalizzati per sleeve a date diverse):
  TP01-solo FULL 1.30 / HOLD 0.31  ->  TP01 70% + XS01 30%: FULL 1.41 / HOLD 1.15, DD giu', ~ogni anno+.
-> XS01 BATTE il portafoglio esistente: inserito in active_sleeves.

Caveat (documentati): storia XS ~2.5 anni; STAT-MODE (book 19 gambe non eseguibile a 2k -> ~20k),
sleeve diagnostico/forward-monitor. portfolio.combine ora outer-join+renorm. 12 test passano.
Diario 2026-06-19-hyperliquid-xsec.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:05:45 +00:00
Adriano Dal Pastro 18f22160b2 research: caccia al 2o sleeve — nessun diversificatore robusto, TP01-only resta
Tool second_sleeve_hunt.py: giudica i candidati per CONTRIBUTO al portafoglio (non Sharpe
standalone). RV mean-rev ETH/BTC morto (come sempre). RV relative-momentum (ratio_trend ==
xs_momentum) sembrava promosso (hold-out portafoglio 0.31->1.51) MA il per-anno + plateau lo
smascherano come REGIME-LUCK 2025: FULL Sh mediocre 0.56, 2 anni consecutivi negativi
(2023 -17%, 2024 -19%), guadagno concentrato nel 2025 (+62%), hold-out Sh non-plateau (0.25-1.92
al variare dei parametri). Beneficio FULL robusto solo +0.09 (diversificazione di uno sleeve
scorrelato debole). NON promosso: la disciplina che boccia i falsi positivi in-sample boccia
anche i falsi positivi nel hold-out. Criterio aggiornato: breadth per-anno + plateau, non solo
hold-out. Relative-momentum in WATCHLIST. Diario 2026-06-19-second-sleeve-hunt.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:28:51 +00:00
Adriano Dal Pastro ef52ad6a79 feat(portfolio): contenitore di strategie ESTENSIBILE — TP01 primo sleeve
src/portfolio/: Sleeve (serie rendimenti netti per-barra, causale/fee-aware) + StrategyPortfolio
(combina N sleeve per peso su griglia giornaliera comune, metriche FULL/HOLD-OUT/per-anno +
standalone per-sleeve, vs buy&hold). Registry sleeve attivi in sleeves.py: per ora SOLO TP01
(peso 100%); aggiungere = una riga (dopo validazione col gauntlet).

Report (run_portfolio.py): TP01 FULL Sh 1.30 / DD 14.3% / ~€1.52/g, HOLD-OUT 0.31 / +3.5%
(buy&hold -0.32 / -39%). Posizione corrente flat (difensivo). tests/test_portfolio.py (6 test).
CLAUDE.md aggiornato (struttura + comando + come aggiungere uno sleeve).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:17:18 +00:00
Adriano Dal Pastro 12754c4908 fix(TP01): bug look-ahead ffill mixed-TF -> deploy a >=12h (1d), strategia DIFENSIVA
Segnalato: ffill MIXED-TIMEFRAME su barre open-labeled (resample label="left") gonfiava il 4h
(~1.60 -> reale ~1.1). Ri-verifica per-SINGOLO-TF leak-free (guard prefix-recompute, leak=0 su
4h/6h/12h/1d): FULL Sh piatto ~1.3, hold-out 2025-26 MIGLIORE a 1d (Sh 0.31 / +3.5% vs buy&hold
-39%). Conclusione adottata: NON scendere sotto le 12h (sotto, costi+overfit dominano senza vantaggio).

- trend_portfolio.py: canonica PORT LF1d; resample_tf/resample_1d (resample_4h deprecato deploy);
  docstring con nota look-ahead + natura DIFENSIVA (taglia DD ~6x, non alpha).
- paper_trend.py: deploy a 1d (resample_1d, build_bars). 5 test passano.
- CLAUDE.md: TP01 ridescritta (>=12h/1d, gotcha ffill mixed-TF, difensiva).
- tp01_lowfreq.py + diario 2026-06-19-tp01-lookahead-fix-lf.md.
Gotcha: mai ffill/combine mixed-TF su timestamp open-labeled (close propagata indietro = leak).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:04:38 +00:00
Adriano Dal Pastro 756a2bdf04 research: stress-test TP01 — robusto come strategia DIFENSIVA (DD-cut), edge ritorno hold-out sottile
Stress sul modulo integrato: FULL regge fee 0.40% + lag + ampio plateau parametri (orizzonti
20/60/120 fa Sh 1.61, non cherry-pick); deflated-Sharpe DSR 0.999 a N=100 (no multiple-testing
artifact). MA il ritorno nel hold-out 2025-26 e' SOTTILE (+2.8%/Sh0.27 a 0.10%, ~flat a 0.40%/lag2):
TP01 PROTEGGE il drawdown (8% vs 60% buy&hold) piu' di quanto profitti. Proprieta' robusta e
deployabile = taglio DD; alpha = no. Da monitorare col paper trader prima di scalare.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:57:22 +00:00
Adriano Dal Pastro d152941360 integra(TP01): merge ricerca branch strategy-research-2026-06 (squash) — strategia vincente + harness + track A-E
Integra il lavoro della linea di ricerca parallela (AdrianoDev), verificato indipendentemente
col mio gauntlet onesto (regge il hold-out 2025-26 su entrambi gli asset, plateau 1h/4h/1d):
- src/strategies/trend_portfolio.py  TP01 (TSMOM 30/90/180 vol-target 20% lev2x long-flat, 50/50 BTC+ETH)
- src/backtest/harness.py            harness onesto (load + backtest_signals no-leakage + OOS)
- scripts/research/track{A,B,C,D,E}_*.py + trackD_timing.py  (le 5 track della ricerca)
- scripts/live/paper_trend.py        paper trader forward-only di TP01 (no esecuzione reale)
- tests/test_trend_portfolio.py (5 test, passano) + 6 diari trackA-E + synthesis
- CLAUDE.md aggiornato con l'esito ricerca (TP01 vincente, mean-rev morto, onesta su €50/g)

Squash (non merge) per NON portare in git i ~68MB di data/_feed_backup/*.bak che il branch
aveva committato per errore: esclusi + data/_feed_backup/ e data/paper_trend/ ora gitignorati.
Storia granulare del branch conservata sul ref origin/strategy-research-2026-06.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:55:04 +00:00
Adriano Dal Pastro 55c28e51b2 research: verify TP01 (branch parallelo) col gauntlet onesto — REGGE il hold-out 2025-26
TP01 (TSMOM 30/90/180 vol-target 20% lev2x long-flat, 50/50 BTC+ETH) passa dove il mio trend 1h
era caduto: hold-out 2025-26 +2.8%/DD8% vs buy&hold -39%/DD60%, positivo su ENTRAMBI gli asset,
plateau 1h/4h/1d. La chiave e' il vol-targeting (esposizione ~1/vol -> cash nei crash) che non
avevo combinato col trend. Edge DIFENSIVO reale (Sharpe full 1.36 vs B&H 0.92, ma CAGR 16.6% vs 48%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:50:36 +00:00
Adriano Dal Pastro 38c8cdf25b research(v2.0.0): honest harness + fasi 0-3 + ricerca frattale 63 agenti — nessun edge robusto su BTC/ETH
Harness onesto research_lab.py (serie di posizione causale, fee-aware, null model a
rotazione circolare, hold-out 2025+ bloccato; self-test cheat/noise che valida il banco).
- Fase 1: triage superstiti (DIP, shape-ML) -> morti net-fee.
- Fase 2: esplorazione famiglie (reversal morta; solo trend long-only/MA-cross passa i gate base).
- Fase 3: conferma avversariale del trend -> regime-luck del toro, bocciato sul hold-out 2025-26.
- Ricerca frattale multi-agente (Workflow, 63 agenti, 52 ipotesi dai due documenti) con guard
  anti-look-ahead (eval_signal.py) + hold-out + test cross-asset -> 0 edge robusto (l'unico
  "confermato" su ETH fallisce su BTC con lo stesso codice).
- Analisi options: VRP reale +10/+14 vol pt ma finestra 6 sett. regime unico -> non validabile;
  ruolo solo overlay tail-cap, tenere cerbero-bite ad accumulare.

Quinta conferma indipendente: su BTC/ETH-solo-prezzo non c'e' un edge facile. Il processo
disciplinato ha evitato un falso "+49% vs -49%" che sul vecchio feed contaminato sarebbe
finito in produzione. Diari docs/diary/2026-06-19-research-phase0-1 / -phase2-options /
-phase3-confirm / -fractal-multiagent-search.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:37:05 +00:00
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:20:59 +00:00
Adriano Dal Pastro 8401a280b9 chore: bump 1.1.34 — GRID_BTC paper sleeve (Price Ladder shadow stage 1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:19:48 +00:00
Adriano Dal Pastro b4d4961d77 merge: Price Ladder research + GRID_BTC paper sleeve (shadow stage 1)
Porta su main: pulizia feed (clean_feed, 254 spike-print Binance), ricerca Price Ladder
(ladder_search/regate/sltp), GridWorker paper + validazione, wiring runner kind=grid +
paper_extra (PORT06 canonico INTATTO: 19 sleeve), GRID_BTC paper in portfolios.yml.
Tutto additive + validato; nessun ordine reale (grid sim-only).
2026-06-18 16:19:48 +00:00
Adriano Dal Pastro 264b9200ea feat(live): Stage 2 — GridWorker (Price Ladder) come PAPER sleeve nel runner
Wire del Price Ladder come sleeve PAPER (sim-only, fuori dal pool €500, NESSUN ordine reale):
- runner: kind="grid" -> GridWorker in build_worker_for; _spec_assets_tf grid; tick branch
  (w.tick(res[asset])); meccanismo PAPER_EXTRA (sleeve paper letti da overrides.paper_extra,
  NON da _defs.py -> NON entrano nel backtest canonico/regression-lock: PORT06 resta 19 sleeve).
  Parsing difensivo (un errore non crasha il runner mainnet). Loop dati estesi a paper_extra.
- GridWorker: bootstrap storia FULL (start fisso, come SH01) + mappatura capitale forward dal
  deploy (capital = initial*eq[-1]/base_norm) -> niente salti da finestra mobile; base_norm
  persistito (resume). grid_mtm robusto al df live (timestamp senza datetime; param df).
- portfolios.yml: GRID_BTC in paper_extra (regime range1.5, rd0.20/ru0.06, L6, sl0.10/tp0.03,
  position_size 0.15 PINNATO). Gira in data/portfolio_paper_stats/GRID_BTC/.
Validazione (validate_grid_worker.py): [A] logica n_trades==backtest, [B] forward-tracking
esatto, [C] resume esatto. Dry-test integrazione runner: import OK, build OK, tick OK, pos 0.15.
SICUREZZA: kind=grid mai eseguito reale (runner avvia ordini solo per single/ml); €500 intatti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:18:53 +00:00
Adriano Dal Pastro b3d4ab7150 feat(live): GridWorker (Price Ladder) SIM/PAPER + validazione replay==backtest — shadow stage 1
Stage 1 dello shadow per il Price Ladder (config finale: BTC 1h range1.5 rd0.20 ru0.06 L6
sl0.10 tp0.03). GridWorker (src/live/grid_worker.py) gira sul feed LIVE e contabilizza
l'equity mark-to-market col motore CANONICO grid_mtm (parita' col backtest per costruzione),
SENZA piazzare ordini reali (sim/paper). Stato persistente + resume. grid_mtm esteso con
param df=None (retro-compatibile: il feed live passa il df; None = _load come prima, gate
invariato — BTC ladder 10.8/5.9, PORT06 base 8.18 identici). Validazione
validate_grid_worker.py: [A] full-tick == grid_mtm esatto, [B] replay incrementale converge
esatto, [C] resume entro la persistenza (4 dec) -> VALIDAZIONE OK.

NB SICUREZZA: nessuna modifica a runner/portfolios.yml/_defs -> il sistema mainnet (€500
reali) e' INTATTO; il worker e' inerte finche' non wirato. L'esecuzione REALE (griglia di
LIMIT resting su Deribit, fill parziali/episodi) e' lo stage 2-3, dietro testnet +
autorizzazione esplicita. Il runner avvia ordini reali solo per kind in (single,ml);
kind=grid resta sim per costruzione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:58:11 +00:00
Adriano Dal Pastro 587fbc0f61 feat(analysis): ladder_sltp_study — 3 passi pre-deploy + SL/TP, config finale ladder
I 3 passi: [1] valutazione 2018-INCLUSIVE (il gate IDX2021+ e' cieco al 2018) -> il tail
vero del BTC ladder e' il 2018 (-27.7% regime-gated / -50% none); [2] fill maker 0% vs
taker 0.10% (Deribit = LIMIT = maker) -> maker leggermente MIGLIORE, harness conservativa,
nessun fee-cliff; [3] half-size (la coda 2018 si dimezza sul book). STUDIO SL/TP: sweet spot
sl_buf=0.10/tp_buf=0.03 cappa il 2018 a -23.5% (da -27.7) senza intaccare l'edge (oos 5.06).
Lezione (conferma prior progetto): SL troppo stretto PEGGIORA (redeploy nel coltello = falso
negativo MR), SL da solo senza regime-gate e' erratico -> il regime-gate e' il controllo
PRIMARIO della coda, il SL moderato fine-tuna. Config finale: BTC 1h range1.5 rd0.20 ru0.06
L6 sl0.10 tp0.03 half-size, PROMOSSO (OOS 10.86->11.0, corr 0.195). Unico passo residuo:
shadow ledger reale (operativo). Diario sez. 8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:48:39 +00:00
Adriano Dal Pastro 8abeb7a83f docs(diary)+feat(analysis): re-gate Price Ladder su dati puliti — tail vero = 2018
ladder_regate_clean.py ri-valuta i top candidati dopo clean_feed.py con gate PORT06 +
stress close_only + DD per anno. Esiti (sez. 7 diario): (1) l'obiezione "coda artefatto"
CADE -- il 54% BTC era spike-print 2024, ora DD gate (2021+) ~11-15%, tutti PROMOSSO half,
corr 0.22-0.29, reggono fee2x = candidati VERI; (2) emerge il tail REALE = 2018 (-44/-52%
standalone) che il gate NON vede (IDX parte dal 2021) -> lacuna metodologica generale; il
regime-gate stretto (BTC rd0.2 L6 range1.5) lo dimezza a -27.7%; (3) l'edge dipende dai fill
INTRABAR (close_only crolla 4.7->0.24): legittimo per LIMIT ma serve shadow ledger.
Verdetto: miglior candidato BTC 1h rd0.20 ru0.06 L6 range1.5 (PROMOSSO, corr 0.23, miglior
coda 2018); prima del deploy servono gate 2018-inclusive + shadow fill + half-size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:42:18 +00:00
Adriano Dal Pastro b9e3388f76 feat(data): clean_feed — ripara spike-print del feed Deribit coi dati reali Binance
La ricerca Price Ladder ha rivelato spike-print nei parquet storici (es. BTC 2024-02-13
low 38.580 / high fasulli, BTC reale ~50k) = stesso problema TP_PHANTOM/feed testnet di
CLAUDE.md, che avvelena i backtest (stop/entry su prezzi mai avvenuti) e gonfia le code.
Tool conservativo: DETECT (wick >15% oltre il cluster close locale) -> ARBITRA con Binance
spot (ccxt, gia' cablato): sostituisce O/H/L/C solo se Binance dissente >2% su high/low (un
wick VERO confermato da Binance resta intatto), backup in data/_feed_backup/ + scrittura
atomica + validazione. RIPARATE 254 barre (BTC 132, ETH 122) su 8 file BTC/ETH x TF; 2 wick
reali confermati da Binance e TENUTI (ETH 30m/1h flash-crash veri). Impatto validato: il
BTC ladder che dava FULL DD 53.69% (artefatto) ora ne da 10.8% (la "coda di trend" era spike
fasulli); PORT06 baseline FULL Sharpe 8.13->8.18, DD/OOS invariati (canonici stabili).
data/raw e' gitignored: e' committato il TOOL, non i parquet (rigenerabili + backup locale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:25:34 +00:00
Adriano Dal Pastro e631e1b89c docs(diary): price-ladder search 2026-06-18 — verdetto RETTIFICATO (nessun deploy)
Caccia multi-agente (34 agenti, 200 celle) a Price Ladder che passi il gate PORT06 su
Deribit. La sintesi iniziale trovava candidati "deployabili" (ETH 1h range-gated half-size,
OOS 10.86->11.0; BTC half-size OOS->11.34). Il critico + verifica dati ROVESCIANO il verdetto
(sez. 6): (1) la "coda BTC ~54%" e' DATI CORROTTI da spike-print (37 barre >20% intrabar, feb-2024
wick fasullo -23%, min 22.7k/max 104.7k vs BTC reale ~47-57k = stesso problema TP_PHANTOM/feed
testnet di CLAUDE.md) -> BTC non affidabile, lo stress-2022 non toccherebbe la coda vera;
(2) il +0.14 OOS Sharpe e' rumore al peso deployabile (~2.7% half, 592g regime calmo, no
significativita'); (3) il finalista ETH e' corr 0.305 (vicino allo 0.40 che fece bocciare la
griglia originale) e il regime_mask condivide il filtro trend con le fade ETH live; (4) FULL DD
standalone mascherata dal compounding (31x); (5) close_only disponibile ma non girato sui
finalisti. VERDETTO: nessun Price Ladder deployabile dimostrato; il multi-agente+critico ha
evitato un deploy ingiustificato. Config live INVARIATA. Harness validata resta come record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:16:54 +00:00
Adriano Dal Pastro d733196564 feat(analysis): ladder_search — harness caccia Price Ladder che passi il gate PORT06
Goal "decine di agenti a cercare strategie Price Ladder" (Deribit). Riusa il motore
grid_mtm mark-to-market ONESTO (SL gap-aware, flat-skip, fee 0.10% RT taker = CONSERVATIVO
su Deribit, dove i fill ai livelli LIMIT sono maker ~0%) ed espone:
  - eval  <asset tf rd ru levels sl_buf tp_buf max_bars [regime] [trend_max]>
  - scan  <asset tf [regime] [trend_max]>  (sub-griglia struttura, gate PORT06, baseline
    cachata) -> top celle con verdetto gate + max_corr coi 19 sleeve + FULL DD.
Leva NUOVA: regime-gate `range` (deploy_mask in grid_mtm, retro-compatibile) = deploya la
griglia SOLO in regime di range (|close-EMA200|/ATR < trend_max), dove la griglia vive, e
non in trend dove muore. Contesto della ricerca: la griglia ETH del gioco e' BOCCIATA
(ridondante, corr 0.40); i ladder BTC sono meno correlati (~0.18) e passano il gate, MA il
nodo e' la FULL DD (coda di trend 2021/22, ~60% standalone) che il verdetto del gate NON
controlla -> la harness la espone (full_dd standalone + full_full_dd di portafoglio).
grid_mtm: aggiunto param deploy_mask (None = comportamento storico, parita' col gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:29:25 +00:00
Adriano Dal Pastro 57758ffcb1 feat(analysis): leverage_sweep — sweep leva 1-10, stress 2022, leva max raccomandata
Risponde a "se porto a leva 10 cosa succede?". Sweep leva 1->10 su PORT06
(modello daily lineare == live) FULL/OOS + anno per anno (2021-2026), con le
non-linearita' che il modello daily NASCONDE: volatility drag/Kelly, rovina
intraday vs daily. Stress con shock REALI 2022 (BTC/ETH storici: LUNA -29/-36%,
3AC-giugno -44/-52%, FTX -26/-32%) sull'esposizione netta del book -> leva di
rovina per net-long beta. Verdetto: leva 10 NON sopravvive a un 2022-repeat
(rovina gia' a ~5-9x se net-long). Leva MAX raccomandata ~3x (DD<=50% recuperabile,
beta=0.6) == config live attuale. Non modifica nulla del live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:48:33 +00:00
Adriano Dal Pastro c8b956ab56 fix(dashboard): PnL totale dal ledger, non init hardcoded 2000
La KPI "PnL totale" sottraeva un capitale iniziale fisso di 2000 (retaggio
testnet) -> sul micro-test mainnet da 500 mostrava -1499 (-74.95%) invece di
+0.95 (+0.19%). Nuovo helper _ledger_pnl(): legge pnl_total dall'ultima riga
di equity.jsonl (il ledger lo scrive come equity-initial_capital) e ne deriva
l'iniziale -> la dashboard combacia col ledger per costruzione, qualunque sia
il capitale (500 mainnet / 2000 testnet), niente piu' valori fissi da
aggiornare a mano. Fallback: primo punto equity, poi l'equity stessa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:48:33 +00:00
Adriano Dal Pastro 3b60289cbd fix(gitignore): commento su riga propria per data/funds_watch.json
Il commento inline rendeva il pattern non-matchante (gitignore non
supporta i commenti a fine riga).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:16:13 +00:00
Adriano Dal Pastro e328836614 feat(live): switch a MAINNET — micro-test fade-only €500 + funds-watch
Conversione testnet -> mainnet Deribit (soldi veri). Vedi
docs/specs/mainnet-microtest-plan.md, Fase 1.

- portfolios.yml: total_capital 500, leva 3, weighting equal; eseguono
  REALE solo i 7 single-leg (6 fade MR01/02/07 x BTC/ETH 15m + DIP01 1h);
  pairs/SH01/multi-asset -> paper (rumore arrotondamento a €500); real_truth.
- docker-compose.yml: portfolio+dashboard caricano anche .env.mainnet
  (token mainnet, prevale su .env; .env.mainnet resta gitignored).
- reconcile_account.py: watermark FONDI (compute_funds_change) — rileva
  aumenti di capitale (deposito/top-up) e cali anomali (prelievo) sul
  balance USDC, alert Telegram FUNDS_INCREASE/FUNDS_DECREASE; soglia
  max(€25, 5%). Stato in data/funds_watch.json (host-writable).
- .gitignore: ignora data/funds_watch.json (stato runtime).

Ledger testnet archiviato in data/_reset_backup/pre_mainnet_*.tgz e azzerato.
Crons host ri-puntati al conto reale (sourcing .env + .env.mainnet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:15:55 +00:00
Adriano Dal Pastro a0842a61cc feat(analysis): trades_status — PnL live per posizione aperta (entry reale vs mark USDC)
Riusa la convenzione della dashboard (get_ticker_batch USDC perp, real_entry vs mark;
pairs a 2 gambe). Mostra unrealized per trade + Σ REALE, e segnala che il ledger
unrealized e' sul feed sim-decisione testnet DISLOCATO (lo scarto e' dislocazione,
non soldi). Fix sys.path: import src.* anche eseguito come script file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:56:22 +00:00
Adriano Dal Pastro 3a66907f43 feat(analysis): trades_status.py — report stato trades con POOL reale vs PAPER-STATS separati
La dir e' la fonte di verita': portfolio_paper/ = pool (ledger/equity), portfolio_paper_stats/
= TR01/ROT02/TSM01/XS01 solo statistica. Niente piu' glob su portfolio_paper* (matchava
entrambi -> +8.15 XS01 sim attribuiti per sbaglio all'equity il 2026-06-17). Mostra realizzato
POOL + unrealized aperti e riconcilia con Δequity del ledger. Caveat + comando in CLAUDE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:17:50 +00:00
Adriano Dal Pastro aee53161c3 docs: Fase 0 smoke mainnet VERDE (ETH->USDC limit) + aggiorna piano micro-test
Catena d'esecuzione validata su mainnet con soldi veri a costo ~0: LIMIT SELL ETH_USDC
0.0138 @ floor 1776.3 -> filled@1778.1 (=bid), fee 0, USDC 25->49.54. Consolidato l'ETH
stranded in margine USDC. Funding parziale ~$49.54 (sotto il min EUR500) -> Fase 1 in
attesa di top-up a ~EUR1000. Diario + STATO/checklist del piano aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:48:38 +00:00
Adriano Dal Pastro 7b3e554424 release: v1.1.33 2026-06-16 14:16:22 +00:00
Adriano Dal Pastro b576ee66ac feat(worker): guard TP-invertito — sopprime l'entry quando il TP e' gia' sfondato (wick-print)
Un wick transitorio fa calcolare un tp dal lato sbagliato dell'entry (donchian: segnale
su barra wickata, entry al prezzo recuperato oltre il proprio tp) -> l'exit intrabar
scatta a bars_held=0 in perdita (16-06: 8 giri MR02_BTC 15m, sim -17.9 / reale -2.3).
TP_PHANTOM non lo prende (niente resting oracle, prezzo oltre il livello). Gate
zero-parametri in StrategyWorker._open_position, solo path live. Test + diario + CLAUDE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:16:13 +00:00
Adriano Dal Pastro ef797f1ff0 release: v1.1.32 2026-06-15 08:44:42 +00:00
Adriano Dal Pastro 4c184d5cbc feat(runner): gate feed CONGELATO — sospende gli sleeve ETH-leg finche' il feed non si sblocca
Audit "stato ordini": il feed ETH-PERPETUAL congelato a 1661.95 (36h+) generava
perdite REALI (SH01_ETH -2.83 reali vs -0.09 sim su un close + riapertura; 4 pairs
con gamba ETH entrati su z-score spuri -3/-5/+5.6 = artefatto del log-ratio con ETH
pinnato mentre gli alt si muovono).

_frozen_assets + _feed_gated_sids: quando il feed di decisione 1h di un asset e'
congelato, gli sleeve concentrati (single/ml/pairs) che ne dipendono saltano il tick
(entry+exit) finche' non si sblocca (come outage; disaster-SL on-book = coda).
Auto-guarente: rilascio alla prima barra COMPLETA non-flat (NON l'entry-guard
post-flat bocciata). Detector guasto-vs-illiquido: conta la run di close INVARIATE
(ETH/BNB/DOGE run 40-64, 1-4 val/48h = morti; SOL/LTC/ADA run <=12, 5-31 val = vivi).
Soglia feed_freeze_gate_bars=24 -> gatea le 9 gambe ETH esatte, PR_BTCLTC e i
multi-asset restano attivi. Alert Telegram FEED_FROZEN_GATE GATED/RIPRESO.

Test test_freeze_gate.py (6, detector+scope+rilascio). Suite portfolio 140/140.
Cerotto testnet: il fix vero e' mainnet. Diario 2026-06-15-frozen-feed-gate.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:44:39 +00:00
Adriano Dal Pastro c21e1dc635 docs: timing-sweep pairs/honest (NO deploy) + stato mainnet + vincolo feed v2
- CLAUDE: feed ETH ancora congelato 36h+; vincolo Cerbero v2 (serve SOLO
  5m/15m/1h, 30m/10m -> 400, legacy 404; _SUBHOURLY "30m" speculativo mai
  testato); esito timing-sweep (5m non conviene: regime recente peggiore +
  flat ETH 29%; gate full+OOS necessario ma non sufficiente); stato mainnet
  (token in .env.mainnet verificato is_mainnet=True, conto VUOTO = blocco)
- spec mainnet-microtest: blocco STATO 2026-06-14, .env.mainnet separato +
  servizio dedicato, checklist aggiornata (token done, funding = blocco)
- nuovo diario 2026-06-14-timing-sweep-pairs-honest.md + harness riusabile
  scripts/analysis/timing_sweep_pairs_honest.py
- .gitignore: .env.mainnet (token mainnet mai in git)

Nessuna modifica a codice/config live: PORT06 invariato (19 sleeve).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:19:56 +00:00
Adriano Dal Pastro 47e92a0425 docs(CLAUDE): aggiorna memoria — dashboard, report ledger-vs-backtest, feed ETH congelato, micro-test mainnet
Fatti di sessione: dashboard web (porta 8787), cron ledger-vs-backtest (gate scaling),
caveat feed testnet ETH-PERPETUAL congelato (spiega 0-trade fade ETH 15m; fix=mainnet),
piano micro-test mainnet + token Cerbero env-configurabile (CERBERO_TOKEN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:46:29 +00:00
Adriano Dal Pastro faf08b3988 docs(spec): piano micro-test mainnet + token Cerbero env-configurabile
Piano operativo per validare l'edge su mainnet con poco denaro reale (il gate per
scalare). Fasi: smoke -> fade-only €1000 2-4 sett -> verdetto ledger-vs-backtest ->
espansione. Sizing motivato (fades €1000 = rumore arrotondamento 2.6% BTC; pairs
esclusi: 30% a quella taglia). Token ora da env CERBERO_TOKEN (default testnet
invariato) -> switch mainnet = solo .env, niente codice. is_mainnet() helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:36:26 +00:00
Adriano Dal Pastro 3ac8a46e6a feat(dashboard): trade attivi con data/ora ingresso + tempo in posizione
Nuove colonne 'Ingresso (UTC)' (entry_time) e 'In posizione' (durata dall'ingresso,
formattata m/h/g) con flag stantio. Sostituisce la colonna Eta (staleness) col
tempo di holding effettivo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:24:22 +00:00
Adriano Dal Pastro 7ee5b1767f feat(dashboard): solo dati REALI nei trade attivi + valore mercato reale (anche pairs) + attivi sotto il grafico
- entry/mark/unrealized SEMPRE reali (entry di esecuzione vs mark USDC live); il
  feed sim/decisione (testnet inverse dislocato) non e' piu' mostrato come prezzo
- pairs: valore di mercato reale di ENTRAMBE le gambe + PnL non realizzato reale a
  2 gambe (prima 'pairs (z)' senza valore)
- tabella attivi spostata subito sotto il grafico equity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:21:22 +00:00
Adriano Dal Pastro d5a83075f8 fix(dashboard): unrealized usa entry REALE (non sim) per coerenza col mark reale
Le posizioni eseguite mostravano l'entry SIM (feed decisione testnet inverse,
dislocato ~1.3% dal lineare USDC) confrontato col mark REALE -> unrealized gonfiato
(SH01_ETH -1.36% invece di ~-0.25%) e due trade ETH con lo stesso entry sim 1661.95.
Ora entry = real_entry_price/real_entry_a se eseguito; unrealized vs mark reale su
base coerente. Indicatore '⚠sim' quando il feed di decisione diverge dal reale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:13:48 +00:00
Adriano Dal Pastro 183209dbd4 feat(dashboard): grafico equity per famiglia + tabella strategie raggruppata per famiglia
- nuovo grafico multi-linea 'Equity per famiglia' (PnL cumulato realizzato di ogni
  famiglia su asse-tempo comune, colori per famiglia, legenda col totale)
- tabella strategie reali raggruppata per famiglia con intestazioni colorate +
  PnL di famiglia; attive prima delle ritirate dentro ogni gruppo
- backend: fam_curves (step-wise cumulato per famiglia dai CLOSE reali), tag fam
  sui trade chiusi

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:48:56 +00:00
Adriano Dal Pastro e311d00fe4 fix(dashboard): tooltip equity allineato al mouse (responsive canvas)
Forzare l'altezza del canvas via CSS !important con maintainAspectRatio default
sfalsava la risoluzione interna vs quella mostrata -> hit-test tooltip offset dal
mouse. Fix canonico: canvas in contenitore .chartbox ad altezza fissa + chart
responsive:true/maintainAspectRatio:false + interaction index su tutti e 3 i grafici
(equity, modal, paper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:35:15 +00:00
Adriano Dal Pastro 37ffe92498 docs(report): strategie_attive.html aggiornato a config live (fade 15m, leva 3x)
Era fermo al 2026-06-12 06:17 (pre-swap): diceva fade 'intraday 1h' e 'leva 2x'.
Rigenerato -> numeri post-swap (FULL Sh 8.13/DD 2.47%, OOS 10.86/2.09%) + fix
etichette hardcoded nel generatore (header FADE 1h->15m, leva 2x->3x, nota tabella).
E' la scheda linkata dal modal della dashboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:30:28 +00:00
Adriano Dal Pastro f6287fcd36 feat(dashboard): area PAPER distinta con equity separata
I 4 multi-asset (TR01/ROT02/TSM01/XS01, solo statistica fuori dal pool reale) ora
in una zona dedicata con la PROPRIA equity (capitale nozionale + PnL cumulato del
book paper) e tabella separata. La tabella/fam_pnl/closed dell'area principale
diventano SOLO reali. Curva paper Chart.js (ocra), cliccabile -> stesso modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:25:17 +00:00
Adriano Dal Pastro b6b465878a feat(dashboard): scheda per strategia (modal: curva PnL reale vs sim + trade + link grafici) + versione sistema/strategia + equity restyling
- click su una strategia -> modal con descrizione, versione di creazione/modifica,
  curva PnL CUMULATO reale vs sim (mostra il leakage), trade, e link alla scheda
  dettagliata strategie_attive.html (servita, docs/report montato ro)
- versione di sistema (APP_VERSION) nell'header; per ogni strategia la versione/
  origine documentata (mappa VERSIONS da CLAUDE.md)
- equity restyling: gradiente verde/rosso secondo segno, tooltip con EUR e delta,
  valore+delta in testata, assi in EUR, piu' alto. /api/strategy con guard
  path-traversal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:20:33 +00:00
Adriano Dal Pastro 22af19c7f9 feat(dashboard): badge strategie RITIRATE (staleness) + descrizioni per strategia
Le fade 1h sostituite dallo swap 15m sono marcate RITIRATA→15m (rilevate per
status.json stantio >30min, ordinate in fondo, riga barrata). Descrizioni concise
per ogni strategia (da strategie_attive.html) come tooltip + sezione legenda.
Fix rilevazione paper: per directory (portfolio_paper_stats), non per chiave
'weights' (TR01/TSM01/XS01 non ce l'hanno) -> ora i 4 multi-asset sono paper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:12:42 +00:00
Adriano Dal Pastro 31f08ddf32 feat(dashboard): frontend web PORT06 — stato live, PnL totale/per-strategia, grafici, trade
Server stdlib http.server (zero dipendenze nuove) che legge data/: KPI equity/PnL/DD,
grafico equity (Chart.js CDN + fallback), PnL per-strategia (barre, realizzato reale),
trade attivi in TEMPO REALE (mark Cerbero best-effort, PnL non realizzato, barre, eta
stantio) e chiusi (ultimi 50). Servizio docker-compose 'dashboard' porta 8787, stessa
immagine, monta data/, restart unless-stopped + healthcheck. Nessuna auth -> rete interna.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:46:47 +00:00
Adriano Dal Pastro dc22256e0e feat(report): ledger reale vs backtest — il gate per scalare il capitale
Per gli sleeve eseguiti sim==backtest per costruzione -> reale vs backtest =
fuga di esecuzione (slippage + fee + netting/phantom/sim_fallback). Misura
LEAKAGE sim-reale per-trade, slippage ingressi/uscite, fee reali, sim_fallback,
ledger per-sleeve; verdetto verde/giallo/rosso. Clean-start --since 2026-06-13
(la finestra mobile includerebbe l'incidente testnet pre-fix). Cron host
giornaliero 08:30 UTC --telegram. Read-only, niente rete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:24:19 +00:00
Adriano Dal Pastro 6fe45867f0 docs(CLAUDE): stato live v1.1.31 — leva 3x, swap fade 15m, lineage, stale-guard, ribilancio conservativo
Aggiorna il doc canonico: default PORT06 ora leva 3x + fade 15m (FULL 8.13/OOS
10.86), e i fix di sessione: INIT_LINEAGE, STALE_REAL_POSITION, conservazione
equity al ribilancio. TF sweep (1m/2m/5m/10m) annotato.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:21:27 +00:00
Adriano Dal Pastro ab9899b0c4 release: v1.1.31 2026-06-13 10:09:57 +00:00
Adriano Dal Pastro 432c1ad790 fix(ledger): ribilancio conserva l'equity (no doppio conteggio in-position)
Bug: i flat si dividevano l'INTERO total includendo il capitale dei worker in
posizione, che lo trattenevano in piu' -> equity gonfiata di Sigma(capital-alloc)
sugli in-pos. Caso MR02_BTC 15m seedato (181.19) e in posizione al ribilancio
00:01: +4.77 fantasma. allocate(weights, reserved={sid:cap}): gli in-pos
trattengono il loro, i flat si dividono total-Sigma(reserved) per peso
rinormalizzato -> Sigma alloc == total sempre. Parita' runner intatta
(5.8e-08). Test conservazione (ledger + runner). Suite verde.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:09:36 +00:00
Adriano Dal Pastro 85043bf2d3 feat(reconcile): guard STALE_REAL_POSITION — rileva posizioni reali non gestite
Caso MR02_BTC 1h (2026-06-13): ritirato dallo swap a 15m mentre short reale ->
short nudo (TP perso nel netting), reconciler cieco perche' lo status fermo
contava ancora come libro. compute_stale_real_positions: worker con
real_in_position e status.json fermo >15min = non gestito -> alert
STALE_REAL_POSITION. Discriminante = staleness (venue-agnostico: cattura
ritirati-da-swap/crashati). Gira dal cron host :40 (no rebuild). 3 test nuovi,
suite 132 passed. Orfano chiuso a mano (testnet, +0.85 netto).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:43:47 +00:00
Adriano Dal Pastro 643ff7f943 feat(live): INIT_LINEAGE — eredita' capitale al cambio timeframe + sweep migliorie serale
Worker nuovo (no status.json) eredita capital/real_capital dal gemello piu'
recente stessa strategia+asset (mai la posizione): niente piu' seed manuale
al prossimo swap. 3 test nuovi, suite 129 passed.
Ricerca: gate 10m ADD bocciato (OOS Sh 10.86->10.76, watchlist chiusa);
XSEC breadth 8->14 Deribit BOCCIATO (gambe flat 91-99% corrompono il ranking)
ma promettente su dati HL reali (-6%->+9% stessa finestra) -> sblocco = routing
dati Hyperliquid quando la storia sara' sufficiente.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:18:30 +00:00
Adriano Dal Pastro 12e71d4c8b research: fade TF sweep 1m/2m/5m/10m/30m — 15m confermato, 10m in watchlist, 1m/2m chiusi
Frontiera Sharpe monotona al scendere del tf ma margine fee si assottiglia:
MR02_BTC muore a fee2x a 5m (-1.70); MR02 sotto i 15m e' fee-death nel regime
corrente (1m -64%). 1m: flat share ETH 25.6% + niente storia full -> chiuso.
Corr col 15m live: 5m 0.46 / 10m 0.53. Fix resample unit-safe (pandas 2.x ms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:56:15 +00:00
Adriano Dal Pastro 378ec7fb43 release: v1.1.30 2026-06-12 20:47:45 +00:00
Adriano Dal Pastro f78380596a feat(live): SWAP fade 1h -> 15m su PORT06 (scelta utente, gate fade15m_port06_gate)
FADE specs tf=15m (sid invariati -> pesi/alloc intatti; DIP01 resta 1h);
builder canonico allineato (combine_portfolio.FADE_TF) -> parita' delle due
facce; regression-lock aggiornato al nuovo baseline (FULL Sh 7.34->8.13
DD 3.46->2.47, OOS Sh 10.07->10.86 DD 1.48->2.09). Runner invariato (fetch
15m gia' esistente dal blend pairs). Suite 126 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:47:44 +00:00
Adriano Dal Pastro 82a1c60e08 research(gate): fade 15m PROMOSSI al gate PORT06 (ADD: OOS Sh 10.07->10.48, DD giu' su entrambe le finestre)
Parita' builder esatta; corr 15m-1h media 0.26 (vera diversificazione);
flat-entry-skip OK su ETH (edge reale, non stale-print). Raccomandata ADD.
Deploy plumbing elencato nel diario (non eseguito).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:37:53 +00:00
Adriano Dal Pastro 515193a203 docs(research): XEX — discordanze Deribit testnet vs Hyperliquid
Spread D/H mean-reverting (half-life 3-7h); su BTC/ETH inverse il BOOK stesso
e' dislocato 1-2% (reale, eseguibile), su DOGE/SOL e' stale-print illusion
(book allineato a HL, 87%/35% barre flat). Edge testnet-only: non deployato,
record + telemetria. Vedi diario.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:29:11 +00:00
Adriano Dal Pastro 612f2bfced feat(live): reconcile resting + orphan single-leg + circuit-breaker venue-lock + FEED_BOOK_GAP
Codice della tornata v1.1.27/28 (gia' in produzione, mai committato):
- reconcile_account: estensione ordini RESTING (FILLED_UNBOOKED/MISSING/STALE,
  caso MR02_BTC: TP fillato di notte scoperto ore dopo) + expected_resting in books
- strategy_worker: orphan_legs su REAL_CLOSE_PARTIAL anche single-leg, persistito
- execution: circuit-breaker su venue-lock admin (stop ordini dopo errori ripetuti)
- runner/hourly_report: alert FEED_BOOK_GAP + timestamp closed trades
- cerbero_client: get_open_orders (merge all + trigger_all)
Test: 12 nuovi, suite completa 126 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:29:02 +00:00
Adriano Dal Pastro a2d581691a release: v1.1.29 2026-06-12 20:23:51 +00:00
Adriano Dal Pastro 5fe53841e9 feat(live): leva 2x -> 3x su PORT06 (scelta utente, frontiera ACCEL50)
PAIRS position_size_family 0.20 -> 0.13 per conservare l'esposizione validata
~0.40 (gate 2026-06-07): la leva accelera le famiglie con stop, non i no-stop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:23:43 +00:00
Adriano Dal Pastro e0257c6c88 docs(research): ACCEL50 — frontiera di leva PORT06 + probe fade 15m verso i 50 EUR/giorno
Leva 2->3/4 dimezza i tempi (OOS CAGR 111->206/343%, DD full 3.5->5.2/6.9%).
Fade 15m passa il probe (6/6 sleeve OOS+, fee 2x OK, BTC 15m > 1h a meta' DD).
Pairs nuove e PAXG bocciati: stale-print illusion (gambe alt 88-98% barre flat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:21:51 +00:00
Adriano Dal Pastro 6f294e3889 docs(diary): chiusura incidente testnet — flatten + orfani risolti, reconciler OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:04:56 +00:00
Adriano Dal Pastro 1a0b601fab release: v1.1.28 2026-06-12 12:35:49 +00:00
Adriano Dal Pastro 2b9adaeea3 docs(diary): reconcile resting + FEED_BOOK_GAP + epoca v1.1.26 (tornata v1.1.27)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:24:37 +00:00
Adriano Dal Pastro e7fd286fe0 release: v1.1.27 2026-06-12 12:22:10 +00:00
Adriano Dal Pastro 003875f2c3 docs(report): strategie_attive.html rigenerato su dati freschi (tutti gli 8 asset al 2026-06-12)
- parquet rinfrescati post-fix downloader: BTC+ETH 1h/15m/5m completi via v2,
  alt 1h incrementali (erano fermi al 28-29/05, panel multi-asset incoerenti)
- make_strategy_doc.yearly_stats: clamp di j (l'engine live-path lascia j>=n
  per il trade aperto al bordo serie -> IndexError coi dati freschi)
- header v1.1.26, card XS01 con phase-tranching, metodologia con verita'
  d'esecuzione/netting. Backtest canonico STABILE su +2 settimane di dati:
  FULL Sharpe 7.34 / DD 3.46 — OOS 10.07 / 1.48 (identico al 2026-06-11)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 06:17:39 +00:00
Adriano Dal Pastro e18adba4a6 fix(data): downloader su endpoint v2 + guard anti-troncamento (post-mortem refresh notturno)
Il refresh notturno e' rimasto 10h appeso e ha TRONCATO i parquet BTC al 2018:
cerbero-mcp ha rimosso l'endpoint legacy /mcp-deribit/tools/get_historical (404),
il downloader skippava in silenzio OGNI chunk (3 retry + sleep esponenziale x
migliaia di chunk) e a fine giro scriveva comunque il file con la sola Fase 1
storica. Aggravante: TIMEFRAMES includeva '1m' (1 giorno/richiesta = ~3000
richieste/asset). ETH salvato in tempo (kill prima della sovrascrittura);
BTC ripristinato via v2 (1h/15m/5m completi 2018->oggi in ~30 min).

- _fetch_deribit -> endpoint v2 /mcp/tools/get_historical (lo stesso del runner)
- guard chunk: >50% skippati = endpoint rotto -> RuntimeError, niente parziali
- guard anti-regressione in download_asset: mai sovrascrivere un parquet con
  dati che finiscono PRIMA dell'esistente
- '1m' fuori da TIMEFRAMES (refresh torna 5m/15m/1h; il 1m ad-hoc se serve)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 06:08:42 +00:00
Adriano Dal Pastro 36ac2426a1 release: v1.1.26 2026-06-11 21:36:30 +00:00
Adriano Dal Pastro 82c05f6f81 fix(exec): code-review serale — guard anti-posizione-nuda sul netting + verità per-frazione
7 finder paralleli sul diff della giornata (8adf388..HEAD), fix dei confermati:

CRITICI (money-path):
- close_amount: GUARD stato-stantio sul fallback netting — il residuo
  non-reduce-only e' consentito solo fino al gap (conto reale - libri degli
  altri worker - orfani) nella direzione della chiusura (src/live/books.py =
  fonte unica, usata anche dal reconciler). Senza guard, un close su stato
  stantio (DSL scattato in outage, flatten manuale) APRIVA una posizione nuda
  a taglia piena bookata come 'chiusura verificata'. Fail-safe se il gap non
  e' calcolabile. Check polvere PRIMA di _quantize_step (che clampa al lotto
  minimo: un residuo 1e-17 diventava un ordine nudo da un lotto).
- _real_close: market_amt = filled_amount anche a merged verified=False (i
  contratti chiusi dal reduce-only non si buttano se il leg netting fallisce);
  REAL_CLOSE_PARTIAL non piu' gateato su verified (era soppresso proprio nel
  caso parziale reale).
- pairs: verita' per-FRAZIONE di gamba (gross proporzionale al fillato, orfano
  = solo il residuo — prima falsava reconciler e real_capital della parte gia'
  chiusa); REAL_OPEN_PAIR booka filled_amount; docstring applied corretta.
- open_pair unwind: chiude il FILLATO, non il richiesto (senza il cap silenzioso
  del reduce-only avrebbe mangiato quota altrui).
- place_tp_limit: quantize CONSERVATIVO (sell=floor/buy=ceil) — il rounding al
  tick piu' vicino poteva mettere il resting oltre il TP sim -> tocco genuino
  classificato phantom sistematicamente.

ROBUSTEZZA/OSSERVABILITA':
- runner: WORKER_ERROR_STREAK a 5 e poi ogni 50 poll + recovery RIPRESO +
  flag real_in_position nell'alert (prima: one-shot a n==5, poi silenzio).
- _tp_phantom: TTL 120s sul verdetto (era ~50 HTTP/h per worker a barra
  fantasma); merge notes con entrambi gli order_id (audit-trail).
- reset_flatten: _quantize_step Decimal (round float produceva amount che
  Deribit rifiuta); hourly_report: book XS01 con gambe SHORT visibili (abs).
- validate_xsec_worker: POS/LEV importati (non hardcoded); xs01_tranche:
  regression-lock ESEGUITO vs xsec_sim; reconcile su src/live/books;
  drift_monitor: rolling vettoriale + exit code 1 su warn.

Test: +4 guard/dust, fixture filled_amount -> 114 passed.
Deferiti (TODO): resting esposti al netting, lifecycle orphan_legs, finestra
trade-history TP_PHANTOM, validazione feed a monte, dedup minori.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:36:30 +00:00
Adriano Dal Pastro 3f1feb1a6f docs(report): generatore aggiornato — tranching XS01 + verità esecuzione/netting in metodologia
- card XS01: paragrafo phase-tranching v1.1.21 (3 sub-book sfasati, gate plateau
  K=2/K=3, PORT06 OOS 10.07->10.15 DD 1.48->1.38)
- metodologia: bullet v1.1.23-25 (TP_PHANTOM conferma dal book reale, ledger su
  amount fillato, chiusure netting-aware, reconciler ACCOUNT_DRIFT, drift-monitor)

L'HTML rigenerato segue in un commit separato (in coda dietro il refresh parquet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:07:06 +00:00
Adriano Dal Pastro cfdd8f6a72 release: v1.1.25 2026-06-11 20:54:44 +00:00
Adriano Dal Pastro 844a9d0e22 feat(exec): netting delle chiusure market — il residuo reduce-only cappato/respinto va in market puro
close_amount ora: (1) tenta il market reduce-only (sicurezza storica: un bug di
stato filla 0 invece di aprire posizioni); (2) il residuo cappato/respinto dal
netting di conto (worker in direzioni opposte sullo stesso strumento) viene
rieseguito in MARKET PURO con label '|net' — muove il conto esattamente del
delta del libro = netta contro le quote opposte. Niente piu' gambe pairs orfane
ne' close cappati per costruzione (close_pair passa da close_amount).

- _merge_close_fills: il chiamante riceve UN Fill (prezzo medio pesato sui fill,
  fee sommate, filled_amount totale, verified se copre il richiesto, notes
  'netting' quando il fallback scatta)
- worker single-leg + pairs: evento NET_CLOSE (log jsonl + Telegram) a ogni
  fallback — osservabilita' della frequenza dei conflitti di netting
- sicurezza persa sul residuo coperta dal reconciler orario (ACCOUNT_DRIFT);
  orphan_legs/REAL_CLOSE_PARTIAL restano come ultima difesa
- 4 test nuovi (full r-o senza fallback, cappato, respinto, doppio fail) -> 110 passed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:54:43 +00:00
Adriano Dal Pastro 22b77668f1 feat(ops): reconciler read-only conto vs libri worker (primo passo verso il position-manager)
scripts/analysis/reconcile_account.py: per strumento USDC confronta l'atteso
(somma quote reali dai status.json: single-leg + pairs 2 gambe + orphan_legs
registrati = drift spiegato) col conto reale (get_positions, size/mark -> coin).
Tolleranza 1.5x step contratto, anti-race (ricontrollo a 10s), alert Telegram
ACCOUNT_DRIFT con --telegram. SOLO lettura, gira dall'host (cron orario :40,
nessun deploy). Al primo run: vero positivo su BTC (libro MR02 short 0.0028 vs
conto flat — TP fillato dallo spike reale, si riconcilia alla chiusura sim).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:43:17 +00:00
Adriano Dal Pastro 521447fce6 release: v1.1.24 2026-06-11 20:05:43 +00:00
Adriano Dal Pastro 429fa01c97 fix(exec): verità contabile sul netting — filled_amount, gambe orfane, tick isolato
Da audit live (3 indagini parallele, diario 2026-06-11-system-audit.md): il modello
quote-per-worker reduce-only si rompe con direzioni opposte sullo stesso strumento
(pairs long ETH vs fade short ETH) — close cappati bookati pieni, 3 gambe pairs mai
eseguite col PnL sim nel ledger reale, conto short 0.027 ETH oltre i libri
(riallineato a mano + DSL orfano cancellato).

- Fill.filled_amount (order.filled_amount): TUTTI i ledger usano il fillato reale
- REAL_CLOSE_PARTIAL (log+alert) su close cappato; residuo orfano dichiarato
- pairs: PnL solo per gambe verificate; gamba respinta -> orphan_legs persistito
  + alert PAIR_LEG_ORPHAN; applied solo con entrambe le gambe (else sim_fallback)
- REAL_DIVERGENCE anche su jsonl (prima solo Telegram)
- runner: tick isolato per-worker + WORKER_ERROR_STREAK a 5 fail consecutivi

Strutturale APERTO (decisione utente, opzioni nel diario): position-manager
centrale / sotto-conti per famiglia / status-quo monitorato.

Test: +2 (partial-close, orphan-leg), fixture filled_amount -> 106 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:05:42 +00:00
Adriano Dal Pastro 8a2b065dd7 chore(analysis): dedup engine gate PORT06 + drift monitor giornaliero + impact bfill
- _port06_gate_common.py: build_trades_variant/equity_from_trades/port_metrics/dd
  fattorizzati dai 3 gate exit16/trendmax/dip01 (-214 righe duplicate). Nessun
  copy-drift trovato; versione promossa = trendmax (superset con hurst_mask).
  Output dei 3 gate verificato BYTE-IDENTICO prima/dopo. dip_trades resta nel suo
  script (sibling deliberato long-only/orig_gap, non una copia).
- drift_monitor.py: rolling-return per famiglia vs distribuzione storica propria
  (warn sotto p5; oggi: FADE 120g al p2). In crontab host giornaliero 07:15 UTC
  con report Telegram. Osservabilita', non filtro di trading.
- daily_equity_bfill_impact.py: bug bfill _daily_equity QUANTIFICATO -> non
  materiale (OOS invariato per costruzione, FULL DD 3.46->3.67 col fix, nessun
  verdetto gate a rischio). Lasciato documentato in TODO, niente fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:04:23 +00:00
Adriano Dal Pastro 51baed22e4 release: v1.1.23 2026-06-11 19:32:24 +00:00
Adriano Dal Pastro 12625b3315 feat(exec): gate TP_PHANTOM — il tocco TP va confermato dal book reale, non dal feed
Il feed testnet stampa wick che 'toccano' il TP intrabar senza che il prezzo
abbia mai scambiato al livello: il sim bookava +4% fantasma a bars_held=0 e
_real_close chiudeva A MERCATO una posizione col resting TP a zero fill
(-fee/spread a giro, 14 giri l'11-06). Ora StrategyWorker._tp_phantom sopprime
l'exit take_profit quando: tocco sim + resting LIMIT zero-fill + prezzo corrente
che non ha raggiunto il livello (il limit sul book reale e' l'oracolo del tocco).
Zero parametri (verita' d'esecuzione, non filtro di strategia); SL close-confirm
e max_bars restano attivi nello stesso tick; fill parziale/prezzo oltre il
livello/worker non eseguito/errore rete -> comportamento storico. Log TP_PHANTOM
dedup per barra + alert Telegram una tantum. 5 test nuovi (104 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:32:24 +00:00
Adriano Dal Pastro 15520dda70 release: v1.1.22 2026-06-11 19:18:55 +00:00
Adriano Dal Pastro 2ae6d7afe6 fix(report): conta i CHIUSI col flag win del worker (PnL REALE), non col net_return sim
Col real-truth ledger net_return resta il numero SIM diagnostico: sui TP fantasma
da spike-print testnet (ETH, bars_held=0: lo spike genera il segnale E tocca il TP
intrabar; il reale chiude a mercato pagando fee/spread) il report Telegram diceva
26 positivi / 0 negativi mentre il reale era 11/15. Ora collect() usa ev.win
(real-truth-aware, fallback nr>0 per eventi storici); i PnL per-motivo erano gia'
reali. TODO: nota monitor sul churn da spike (niente filtri anti-spike nei segnali:
artefatto testnet, real-truth gia' contabilizza giusto).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:18:55 +00:00
Adriano Dal Pastro b208417f2d release: v1.1.21 2026-06-11 13:29:25 +00:00
Adriano Dal Pastro 9d15506b05 feat(stability): sweep stabilità — fix TR01 mean(rets), XS01 phase-tranching K=3, z-stop pairs bocciato
Audit anti-overfit su tutte le 19 sleeve (diario 2026-06-11-stability-sweep.md):

- FIX BasketTrendWorker: mean(rets) sui soli asset in posizione sovrappesava N/k
  a paniere parziale (1 long = 0.45 del capitale invece di 0.09) -> replay -44%
  vs ref +42%. Ora sum(rets)/N (convenzione canonica 1/N): replay +32% vs +42%
  (residuo = convenzione dichiarata). Solo statistica PAPER.
- XS01 PHASE-TRANCHING (gate xs01_tranche_gate: plateau K=2 E K=3 promossi,
  PORT06 OOS Sh 10.07->10.15 DD 1.48->1.38, FULL pari): la fase del roll e'
  timing-luck (Sharpe daily 1.52-2.33, DD 13.8-33% sulle 12 fasi). Worker con
  param tranches (default 1), 3 sub-book sfasati hold/3 su capitale comune,
  migrazione status legacy, last_bar_ts solo-avanti; runner forward del param;
  _defs tranches=3; hourly_report aggrega i sub-book; validatore esteso e
  PASSATO (K=1 == xsec_sim esatto, K=3 == unione fasi esatto).
- Disaster-cap z sui pairs: pre-registrato e BOCCIATO su tutti i criteri (coda
  OOS peggiora 4/6 coppie, Sharpe -10..-49%, plateau solo del danno; 5a conferma
  stop-su-MR). Record pairs_zstop_research.py; pairs restano senza stop.
- Audit drift: regression-lock trendmax OK (parita' 1.00000, plateau 2.5/3.0/3.5
  confermato), correlazioni cross-famiglia ~0 invariate; PORT06 rolling al
  19-28mo pct (normale) ma FADE 120g al 2o percentile storico -> monitor in TODO
  (nessun ritocco parametri).
- TODO: forming-bar ROT02/TSM01 era gia' fixato (v1.1.10), item chiuso.

Test: pytest 99 passed; validate_honest_workers OK; validate_xsec_worker OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:29:14 +00:00
Adriano Dal Pastro ba5ec7bd6b docs: aggiorna strategie_attive.html + CLAUDE/TODO a v1.1.20 (XS01, real-truth, 19 sleeve)
- make_strategy_doc: nuova famiglia XSEC — card XS01 (meccanismo, dispersion-gate
  disp_min=0.0313, gate PORT06 OOS 10.07->10.37) con grafico book+gate e tabella
  per anno (engine canonico ungated); pesi aggiornati a 19 sleeve (5.69/5.26/2.94%);
  header con real-truth ledger e 4 book paper; bullet metodologia real-truth;
  ripuliti i riferimenti 'oggi' (EXIT-16 su DIP01, fix punto-10 SH01)
- strategie_attive.html rigenerato: v1.1.20, backtest canonico FULL Sharpe 7.34 /
  DD 3.46% — OOS Sharpe 10.07 / DD 1.48% (19 sleeve, XS01 incluso)
- CLAUDE.md: sezione XS01 (famiglia XSEC, gate, dispersion-gate live-only, FC01
  scartata), copertura reale 15/19, varianti gioco options/session/grid in struttura
- TODO.md: stato esecuzione aggiornato (6 pairs, XS01 paper, real-truth)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:48 +00:00
Adriano Dal Pastro 8d69a0cef5 feat(games): sessioni 2-3 Blind Traders (opzioni/session/grid) + gate PORT06 e tooling reset
- Gioco GRID TRADERS (sessione 3, regola STRATEGIA_GRIGLIA.md): grid_engine
  (backtest causale fee-aware della griglia geometrica), grid_brief (digest
  anonimo per dimensionare la griglia), grid_arena (torneo 100 agenti);
  diario docs/diary/2026-06-10-grid-traders-game3.md
- Gioco OPZIONI: options_engine (BS + skew fittato + DVOL storica),
  options_arena, opt_calibrate (superficie premi REALE da cerbero-bite)
- Gioco SESSION: session_engine/session_arena (pattern orari intraday)
- arena: vincolo GAME_NO_LIVE=1 (vieta pairs e fade zscore/breakout/momentum
  gia' live, coercizione a trend/ma_cross) + normalize del candidato PRIMA
  della valutazione nel hill-climb
- Gate: grid_game_gate (griglia ETH vincitrice vs PORT06, mark-to-market),
  pairs30m_gate (ETH/BTC 30m ridondante col 15m gia' deployato?)
- reset_flatten: flatten one-shot del conto testnet per il reset portafoglio
- .gitignore: data/portfolio_paper_stats/ (stato runtime sleeve paper-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 09:49:17 +00:00
Adriano Dal Pastro 8adf388e86 release: v1.1.20 2026-06-10 21:42:12 +00:00
Adriano Dal Pastro e25d5db6ad feat(xsec): dispersion-gate XS01 live (disp_min=0.0313) — Sharpe 3.46, PORT06 OOS 10.07->10.37; FC01 funding-carry scartata
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:42:12 +00:00
Adriano Dal Pastro d1180ef25b release: v1.1.19 2026-06-10 21:24:00 +00:00
Adriano Dal Pastro 61fcc29c5b fix(xsec): cast numpy->Python in _save/_close_book — int64 rompeva json.dumps e bloccava il runner in error-streak
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:23:52 +00:00
Adriano Dal Pastro 5e557fe998 release: v1.1.18 2026-06-10 12:23:58 +00:00
Adriano Dal Pastro cc39c36c08 feat(live): REAL-TRUTH ledger — capital aggiornato dal PnL dei fill reali, sim ridotto a diagnostica
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:23:52 +00:00
Adriano Dal Pastro d2948d582b release: v1.1.17 2026-06-09 21:38:12 +00:00
Adriano Dal Pastro a85289d7c7 feat(xsec): XS01 reversione cross-sectional (8 asset) -> PORT06 PAPER
Famiglia NUOVA trovata in sessione (dopo aver scartato trend/breakout/seasonal/
opzioni/funding come rumore): ogni 12h long i perdenti relativi / short i vincenti
su 8 asset, market-neutral. Scorrelata (~0) da pairs e fade -> diversificatore.

- engine canonico scripts/strategies/XS01_cross_sectional.py (no look-ahead, plateau
  OOS Sharpe 2-3.9, 5/5 anni+, edge concentrato 2025, cost-sensitive ~0.35% RT).
- src/live/xsec_worker.py CrossSectionalWorker: validate_xsec_worker == backtest ESATTO
  (4993/1427 trade). Mirror della cadenza engine (entry-to-entry = hold+1).
- gate PORT06: +XS01 -> OOS Sharpe 9.66->10.07, FULL DD 3.68->3.46 (OOS DD +0.17pp,
  risk-contrib 2.2%). xsec_port06_gate.py.
- wiring: _defs XSEC in PORT06 (19 sleeve, family XSEC), build_everything, runner
  kind=xsec, asset_days da supported (fix fetch alt anche per paper sleeves), paper.
- 8 gambe -> niente exec reale -> gira PAPER. Regression-lock 18->19, FULL 7.20->7.34,
  OOS 9.66->10.07. 93 test verdi. Diario 2026-06-09-xs01-cross-sectional.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:38:05 +00:00
Adriano Dal Pastro d3dab57532 feat(games): sessione 2 del gioco Blind Traders su timing diversi (30m/2h/4h)
- engine: resampling (_RESAMPLE) per 30m/2h/4h/1d + TF_BPM esteso -> nuovi timing.
- arena/run_game: TIMEFRAMES estesi, out_name e GAME_SPECS_DIR/GAME_OUT parametrizzati
  (game 1 non sovrascritto).
- Risultato: 10 finalisti tutti 30m pairs ETH/BTC (vincitore #36: OOS Sh 12.3, 43 tr/mese).
  La regola >=10 trade/mese filtra i tf lunghi (4h: 4/33 qualificati). Conferma la
  frontiera frequenza-vs-edge. Diario 2026-06-09-blind-traders-game2.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:01:34 +00:00
Adriano Dal Pastro abc1c1cb80 docs(diary): analisi robustezza ETH/BTC 15m — robusto come segnale, fragile sui costi
Plateau 16/16, 8/9 anni+, OOS Sh 17.6, non-artefatto-flat (83% Sharpe), corr 0.37.
MA fee-sweep: il 15m va negativo a ~4x i costi mentre il 1h regge ~6x (frequenza 5x,
margine di costo sottile). Conferma mezza-size e NO allo swap; giudice = ledger reale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:34:08 +00:00
Adriano Dal Pastro 1a9032feaa docs(diary): statistiche per-anno delle 15 sleeve attive in real
Snapshot post-BLEND 15m (v1.1.16): PnL%/n-trade per anno + maxDD per ogni sleeve
che esegue reale (6 fade + DIP01 + 6 pairs + 2 SH01), engine path-live, fee incluse.
+ aggregato PORT06 per anno. Esclusi i 3 book PAPER (TR01/ROT02/TSM01).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:12:53 +00:00
Adriano Dal Pastro 90c4726a31 docs: aggiorna CLAUDE.md + strategie_attive.html col BLEND ETH/BTC 15m
- CLAUDE.md: default PORT06 (FULL 7.20/OOS 9.66, 18 sleeve), paragrafo BLEND 15m
  flat-skip (origine gioco Blind Traders, gate, validazione, caveat slippage),
  copertura reale ~83% (6 pairs), scripts/games/ + pairs15m_* nella struttura.
- make_strategy_doc.py: header dinamico dal backtest, colonna ETH/BTC·15m nella
  tabella pairs, card PR01 col blend, conteggi sleeve aggiornati -> rigenerato HTML.
- pairs_sim_flat: ritorna yearly_n (parita' con pairs_sim, usato dal doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:59:36 +00:00
Adriano Dal Pastro be72b157cf release: v1.1.16 2026-06-09 11:48:26 +00:00
Adriano Dal Pastro d25d897fd1 feat(pairs): attiva ETH/BTC 15m flat-skip in PORT06 (BLEND, mezza size)
Origine: gioco "Blind Traders" (100 agenti ciechi su BTC/ETH anonimizzati) ->
vincitore = spread ETH/BTC reversion a 15m. Testato sul serio col gate PORT06:
non duplicato (corr 1h vs 15m = 0.37), robusto (16/16 celle Sharpe>1), edge NON
artefatto delle candele flat ETH 15m (filtrandole resta l'83% dello Sharpe).

Percorso live costruito e validato:
- pairs_research.pairs_sim_flat: engine generalizzato con exit LIVE-REALIZABLE
  (arma exit_ready, esce alla 1a barra pulita); regression-lock a pairs_sim.
- PairsWorker: flat_skip + exit_ready + rilevamento flat da OHLC (1h byte-exact).
- runner: fetch diretto dei timeframe sub-orari + override position_size per-sleeve.
- validate_worker_pairs: replay worker == backtest a 15m (8452 vs 8453 trade).
- _defs/build_everything: sleeve PR_ETHBTC_15M (mezza size, pos 0.10) -> PORT06
  FULL 6.43->7.20, OOS 8.58->9.66, DD giu'. Rischio bilanciato col 1h.
- smoke live: Cerbero serve candele 15m fresche; worker ticca.

Diari docs/diary/2026-06-09-*. Caveat slippage: mezza size = blend-tilt prudente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:48:15 +00:00
Adriano Dal Pastro 5d45f4ef6e research(cerbero-bite): validazione edge credit-spread su prezzi reali
Backtest dell'edge credit-spread ETH di cerbero-bite con la chain reale
(data/options/). Esito: NON edge robusto su ciclo completo. Entry cw reale
0.106 (short 9.4% OTM, max-loss/credito 8.4x); hold-to-expiry EV -1.0 cr/trade
7/9 anni negativi; managed (skew) EV -0.02 cr win-rate 37%. Il "+0.48%/mese"
era artefatto di finestra calma; coda concentrata col fade ETH. TODO aperto:
calibrazione esatta credito (bid/ask + griglia) per EV managed definitivo.
Script riprendibile + diario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:08:53 +00:00
Adriano Dal Pastro cfc48cdef4 release: v1.1.15 2026-06-09 07:43:09 +00:00
Adriano Dal Pastro aa4361ddde feat(live): MR02/ETH stop-largo sl_atr 3.0 (miglioria gated, regola sl=0 rispettata)
Esito ricerca sostituto/miglioria MR02/ETH: nessun sostituto batte il live
EXIT-16; la miglioria deployabile e' allargare lo stop close-confirm da
sl_atr 2.0->3.0 sul solo MR02_ETH. Gate PORT06 (swap MR02_ETH): FULL Sh
6.73->6.77 DD 3.67->3.30, OOS 8.80->8.82 DD 1.23 (domina il live). Cattura
il beneficio del no-SL tenendo lo stop di catastrofe: worst-trade -50% vs
-65% del no-SL -> regola "mai sl=0" rispettata. Override scoped a MR02/ETH
(_fade_params); altri 5 fade invariati. 93/93 test ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:31:55 +00:00
Adriano Dal Pastro c997bce00e feat(options): attach_market() panel helper (regime panel, causal, no look-ahead)
attach_market(df, asset): merge_asof causale del pannello market_snapshots
(spot/VRP/funding/net-GEX/gamma-flip/liquidation) sul prezzo, pronto per
regime_lab. Fix look-ahead: merge su datetime tz-aware (ns-aligned), MAI
astype(int64) su datetime (darebbe ns -> match all'ultimo snapshot = leak).
Copertura reale documentata: net-GEX denso solo da ~2026-05-01 (~5-6 sett,
singolo regime) -> infrastruttura pronta, edge validabile solo forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:24:44 +00:00
Adriano Dal Pastro df8bfbceaa research(mr02eth): ricerca sostituto + integrazione dati opzioni cerbero-bite
Ricerca sostituto/miglioria a MR02/ETH (127 strategie + 18 overlay opzioni,
verifica avversariale, gate PORT06). Esito: miglioria = no-SL (gia' ~catturata
da EXIT-16 live); tail-hedge opzioni NO-GO empirico su prezzi reali.

Infrastruttura opzioni REALE (muro ARGO/GEX caduto):
- options_fetcher.py: importa lo storico per-strike + market_snapshots da
  cerbero-bite -> data/options/ (chain bid/ask/IV/greche/OI + pannello regime
  con net-GEX dealer/gamma-flip/VRP/funding/liquidation).
- options_chain.py: loader + skew_curve/premium_levels (aggregati reali) +
  quote() causale + load_market() (pannello regime).
- option_overlay_lab.py: overlay opzioni BS su DVOL (pricing sintetico).
- mr02eth_port06_gate.py / eth_collar_gate.py: gate swap-sleeve e collar.
- mr02eth_search/options.workflow.js: i 2 workflow.

Numeri reali: skew put 10%OTM ~1.1 (liquido), premio ~1.0%/mese; niente strike
10%OTM a 24h (overlay per-trade infattibile); collar standing paga nei crash ma
net-negativo a PORT06 (alza OOS DD). Diario + CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:05:00 +00:00
Adriano Dal Pastro cff40283f4 docs(report): PR01 + SH01 in esecuzione reale, pool real-only 14 sleeve
Aggiorna strategie_attive.html (e il generatore make_strategy_doc.py) allo
stato post v1.1.12/1.1.13/1.1.14:
- PR01 (5 coppie) e SH01 (BTC/ETH): badge SIMULATO -> ESECUZIONE REALE
- header: pool live real-only 14 sleeve (3 multi-asset in statistica)
- metodologia: esecuzione reale ~81% (fade+DIP single-leg, pairs 2 gambe,
  SH01 a orizzonte); restano simulati solo TR01/ROT02/TSM01

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:08:30 +00:00
Adriano Dal Pastro ce25be0475 release: v1.1.14 2026-06-08 11:29:00 +00:00
Adriano Dal Pastro 0e8ad9c5f0 feat(live): portafoglio REALE-only — i 2000EUR ai soli 14 sleeve eseguiti, paper fuori dal pool
Decisione utente: il conto reale deve mostrare il risultato puro. I 2000 si dividono
SOLO tra i 14 sleeve reali (6 fade + DIP01 + 5 pairs + SH01). I 3 multi-asset
(TR01/ROT02/TSM01), non eseguibili in reale, escono da pool/pesi/ledger e girano solo
per statistica.

- portfolios.yml: paper_sleeves [TR01,ROT02,TSM01].
- runner: live_specs (pool/ledger) vs paper_specs (capitale nozionale fisso = total/N,
  data/portfolio_paper_stats/, ticcati ma MAI in update_equity).
- hourly_report: sezione PAPER da portfolio_paper_stats, etichettata 'fuori dal conto reale'.

Pesi reale-only (14, cap): non-cappati 0.087, PAIRS 0.33, SHAPE 0.0588. Costo misurato
e accettato: FULL DD 3.96->5.34%, OOS DD 1.36->1.70% (perde i diversificatori PAPER,
corr 0.07); Sharpe ~invariato. Test 93/93. Diario docs/diary/2026-06-08-real-only-portfolio.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:29:00 +00:00
Adriano Dal Pastro 3d3ebd85d2 release: v1.1.13 2026-06-08 11:18:06 +00:00
Adriano Dal Pastro 0b900a5420 feat(live): SH01 in esecuzione reale shadow (diversificatore decorrelato)
L'infrastruttura no-TP esisteva gia': _place_real_tp ritorna subito senza TP,
_real_close chiude tutta la quota a market reduce-only (exit a orizzonte H=12).
Bastava: (1) _exec_for accetta kind 'ml', (2) SH01 in execution.sleeves.
Disaster-bracket on-book = unica protezione di coda di SH01 (esce a H=12 ben prima
del -30%). Motivo: SH01 e' il diversificatore piu' decorrelato (corr 0.07) — senza
i 5 sleeve PAPER il DD del portafoglio sale 3.96->5.35%, OOS Sharpe 8.58->8.27.

Test: SH01 open/close reale senza TP + disaster bracket (93/93). Copertura reale
ora ~81% (resta paper solo TR01/ROT02/TSM01, bloccati dal capitale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:18:06 +00:00
Adriano Dal Pastro c6970d96c9 release: v1.1.12 2026-06-08 10:26:23 +00:00
Adriano Dal Pastro 2da19c3a7e config(pairs): attiva pairs_enabled (esecuzione reale 2 gambe) a conto flat
Smoke testnet end-to-end verificato (v1.1.11). Conto Deribit flat su tutti gli
strumenti pair confermato in tempo reale prima dell'accensione. I 5 pairs ora
eseguono reale shadow a 2 gambe; shadow pulito dalla prossima apertura pair.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:26:23 +00:00
Adriano Dal Pastro 59901e236b release: v1.1.11 2026-06-08 10:19:56 +00:00
Adriano Dal Pastro c655604131 feat(live): executor a 2 gambe per i pairs (PairsExecutionClient, shadow) — pronto, spento
PairsExecutionClient (compone ExecutionClient): open_pair (2 market long A/short B,
verifica per-gamba, LEG-RISK unwind se una sola filla), close_pair (2 reduce-only via
close_amount, MAI close_position), register_contract (fetch spec USDC). Spec LTC/ADA/SOL
aggiunti. PairsWorker: ledger reale shadow a 2 gambe resume-safe (_real_open_pair/
_real_close_pair, PnL per gamba dir A=+d/B=-d, doppio arrotondamento riportato). Runner:
pairs_executor gated su execution.pairs_enabled (false di default).

Validazione: test 92/92 (open/close, leg-risk unwind, resume) + smoke testnet
end-to-end (open 2 gambe verificate, close reduce-only, PnL reale -0.039 vs sim -0.036,
conto flat). Smoke ora aborta se ci sono posizioni di produzione + usa solo close_amount.

NB incidente testnet documentato (diario): pulizia manuale con close_position ha flattato
le quote shadow dei fade sul conto condiviso -> auto-riconciliazione al prossimo close.
Lezione: mai close_position su strumenti condivisi.

pairs_enabled resta FALSE: accendere con finestra a conto flat + osservazione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:19:56 +00:00
Adriano Dal Pastro a2f1b960ec research: gate PORT06 index_comp_disp — promosso marginale, documentato e rimandato
Decorrela bene (corr 0.06 col MASTER, smentisce il timore ridondanza) ma beneficio
OOS nullo (Sharpe 8.58->8.56, DD 1.36->1.40); migliora solo FULL DD 3.96->3.73. Non
deployato (wiring + simulato per guadagno OOS nel rumore). Gate riusabile committato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:06:34 +00:00
Adriano Dal Pastro d277d02cf5 release: v1.1.10 2026-06-08 10:00:16 +00:00
Adriano Dal Pastro f509f51543 fix(live): ROT02/TSM01 scartano la barra 1d in formazione (forming-bar in _panel)
Code-review follow-up: i worker daily valutavano momentum/regime e bookavano il
return sulla candela 1d PARZIALE al primo poll dopo mezzanotte UTC, poi last_bar_ts
bloccava la rivalutazione a giorno chiuso (stessa classe del bug gia' fixato su
fade/TR01/Pairs). Fix in _panel (un punto solo -> ROT02 e TSM01 lo ereditano) via
src.live.bars.last_bar_is_forming. Replay honest invariato (dati storici: nessuna
barra in formazione). Test: 88/88 + parity drop forming daily bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:00:16 +00:00
Adriano Dal Pastro 7f4378190e research: ricerca dispersion/correlation index (165 agenti) — 2 edge su 60, nessun nuovo motore
Workflow 60 celle (15 famiglie x 4 finestre), verifica avversariale 2-skeptic.
Esito: 2 edge confermati causali (index_comp_disp W168, rel_idio_fade W24), ma
entrambi fade-BTC del residuo idiosincratico -> sovrapposti alle MR esistenti
(P migliora-PORT06 ~20%, lezione FR01). 13/15 famiglie rumore (overfit/regime,
non look-ahead). Edge preservati in scripts/analysis/dispersion_edges/; harness
riusabile dispersion_lab.py (gia' committato). Diario + TODO follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 03:32:31 +00:00
Adriano Dal Pastro cb122683fe feat(research): harness condiviso dispersion/correlation index (feature causali + cache)
Feature realized causali (avg pairwise corr, cross-sectional dispersion, beta vs
indice EW, componente idiosincratica) su universo 8-asset, finestra comune dal
2022-07. NO implied (opzioni non backtestabili). Check no-look-ahead OK. Cache su
disco per il fan-out di ricerca. Base per la ricerca multi-agente dispersion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:16:10 +00:00
Adriano Dal Pastro 9275eab4af docs: TODO follow-up post-sweep (executor 2-gambe pairs, SH01 reale, capitale, code-review minori)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:57:28 +00:00
Adriano Dal Pastro 2a97294d67 docs: statistiche per anno (trd/PnL%/maxDD) per strategia e mercato nel doc HTML
Ogni card ora include la tabella anno x mercato:
- fade MR01/02/07 (BTC+ETH) e DIP01: calcolate col PATH LIVE (EXIT-16 + trend 3.0),
  trades/PnL per anno di entry, DD per anno su equity compounding pos 0.15 + DD totale
- PR01: matrice anno x 5 coppie, cella = PnL% (n trade, DD anno) + riga TOT
  (pairs_sim espone ora anche yearly_n, modifica non-breaking)
- TR01/ROT02/TSM01: ret%/DD% per anno dall'equity canonica daily 2021+
- SH01: per anno dal walk-forward EXPANDING (regime validato e ora live)
Nota di convenzione su ogni tabella (leva 3x test vs 2x live, fee incluse) +
caveat: finestra canonica dal 2021, anni 2018-2020 mostrati per onesta' storica.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:32:00 +00:00
Adriano Dal Pastro bc0ad3e86f docs: documento HTML strategie attive PORT06 (descrizioni + grafici da episodi reali)
scripts/analysis/make_strategy_doc.py genera docs/report/strategie_attive.html
(autocontenuto, PNG base64): le 6 strategie attive per famiglia con tesi,
config live, badge sim/reale e 11 grafici costruiti su EPISODI REALI dai
parquet locali — fade con bande/canale/z e trade annotato (entry/TP/SL),
explainer EXIT-16 (wick che buca lo SL senza conferma sul close), DIP01,
TR01 EMA 4h, ROT02 (gate regime + ranking momentum), PR01 (gambe normalizzate
+ z-score del ratio con soglie), TSM01 (risk-off + heatmap consenso),
SH01 (finestra forma + schema walk-forward), pesi cap del portafoglio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:55:45 +00:00
Adriano Dal Pastro cc9a380a63 release: v1.1.9 2026-06-07 16:48:22 +00:00
Adriano Dal Pastro 8c5372c487 feat(SH01): bootstrap storia full-history + last_block_only (punto-10: regime live non robusto)
Ri-validazione sh01_trainwindow_validate.py (rolling train_window == regime live):
- tw=8760 (live 365g): BTC FULL +82% ma fee-2x -42% (6/9 anni), ETH Sharpe -0.02,
  trade-rate 21.7-26.4% vs 9.8% validato -> NON robusto. Diagnosi sweep confermata
  (LogReg over-confident su train corto, th 0.58 inerte).
- progressione MONOTONA con la memoria (2y: Sh 2.05; 3y: 3.09; expanding: ROBUSTO
  8/9) -> l'edge di SH01 e' la memoria lunga.
- sweep soglia nel regime corto: instabile/incoerente fra asset -> NON ri-tunare.

Fix (decisione utente): ripristinare in live il regime validato.
- ml_wf_entries(last_block_only=True): fitta/predice solo l'ultimo blocco del WF
  (confini deterministici start+k*retrain) -> entries IDENTICHE per costruzione
  al tail del WF completo (parity test esatto); 0.6s/tick su 73k barre vs ~140 fit.
- runner._with_history: tick degli sleeve ml su parquet locale + feed live
  (dedup timestamp, gap-guard con WARN una-tantum se parquet stantio).
- _defs.py: params {last_block_only: true} sugli sleeve SHAPE (solo path live;
  il backtest canonico resta WF completo).

Effetto atteso live: trade-rate SH01 ~25% -> ~10% delle barre, selettivita'
ripristinata. Manutenzione: parquet fresco via download_all (oggi 2026-05-28,
margine ~11 mesi col feed 365g).

Test: 87/87 (4 nuovi: parity last-block esatta, merge storia, gap fallback).
Diario docs/diary/2026-06-07-sh01-trainwindow.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:48:22 +00:00
Adriano Dal Pastro be85a454e0 release: v1.1.8 2026-06-07 16:19:25 +00:00
Adriano Dal Pastro ed2a9013aa feat(DIP01): EXIT-16 close-confirm SL (gate gap-aware 36/36 BTC, PORT06 promosso)
Punto 9 roadmap sweep. DIP01 era l'unico sleeve BTC con esecuzione reale
round-trip ancora sul branch SL intrabar wick-sensitive.

Gate dip01_exit16_impact.py (parita' canonica 1.00000; engine GAP-AWARE: orig
filla lo SL a worse(livello, open) per rimuovere il bias pro-stop-intrabar
dell'engine canonico sui gap-through):
- grid 3x3x2 BTC: EXIT-16 >= orig in 36/36 (train E oos), OOS Sharpe ~2-4x
  (canonica 1.47->3.48); ETH 35/36 (robustezza).
- plateau buffer piatto 0.4-1.0 (OOS DD 6.4% identico) -> 0.5 come le fade.
- PORT06: FULL Sh 6.43->6.61 DD 3.96->3.58 | OOS Sh 8.58->8.77 DD 1.36->1.34.
5a conferma del principio EXIT-16 (wick-stop = falsi negativi per mean-reversion).
hurst_max non valutato (ridondante-dannoso post-EXIT-16, gate trendmax).

Deploy: "sl_confirm_atr": 0.5 nei params DIP01_BTC (il worker legge gia').

Trappola di metodo documentata: la prima parita' falliva per l'ancora bfill di
_daily_equity (la serie a punti-trade reindexata su IDX aggancia il primo valore
al PRIMO trade in finestra, non al capitale portato avanti) — replicata per
parita'; finding aperto perche' tocca le metriche canoniche di tutti gli sleeve
a punti-trade. Diario docs/diary/2026-06-07-dip01-exit16.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:19:25 +00:00
Adriano Dal Pastro bf1cada33f release: v1.1.7 2026-06-07 14:59:58 +00:00
Adriano Dal Pastro f5173fba06 fix(live): 4 hardening da code-review — REAL_DIVERGENCE, outage su feed vuoto, bars.py condiviso, DSL cancel retry
Review multi-agente 7-angoli su 8c4e1cd..HEAD + check trades live:

1. Alert Telegram REAL_DIVERGENCE quando |slippage sim/reale| >= 100bps a
   open/close. Causa scatenante: spike print testnet 10:37 (candela 10:00
   H=65618, O/C ~62400) -> 3 fade BTC short su close fantasma 65266.5, reale
   fillato a 62395 (-440bps), sim +2.26 mai esistiti — passato in silenzio.
2. FEED_OUTAGE anche su feed degradato SENZA eccezione (HTTP 200 + candles
   vuote: i worker saltavano il tick in silenzio, streak a 0). Helper unico
   _outage_tick; chiavi payload uniformate (minuti su start e RIPRESO).
3. src/live/bars.py: detection forming-bar unificata (bar_ms_of /
   last_bar_is_forming / last_settled_idx) — era copiata in 4 punti
   (strategy_worker, basket, pairs, _check_stale_feed hardcoded 1h).
   E' l'invariante di sicurezza EXIT-16: ora una sola implementazione testata.
4. DSL cancel hardening in _real_close: retry su errore transitorio + alert
   REAL_DSL_CANCEL_FAIL se lo stop resta forse orfano sul book (prima l'id
   veniva dimenticato in silenzio); order_not_found = probabile trigger in
   outage -> solo log (il close a valle esce gia' verified=False).

Refutato il finding top dei finder ("stop_market senza trigger"): cerbero-mcp
traduce price->trigger_price+mark, e in produzione 2 DSL armati + 1 ciclo
completo pulito (MR07_BTC).

Test: 83/83 (9 nuovi: bars helper + DSL/divergence con executor finto).
Smoke testnet 4 scenari verdi, conto flat, zero falsi allarmi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:59:58 +00:00
Adriano Dal Pastro 7bf8b197e0 release: v1.1.6 2026-06-07 10:44:15 +00:00
Adriano Dal Pastro 6097205694 feat(pairs): position_size per-famiglia, PAIRS 0.5->0.20 (gate PORT06)
Punto 8 roadmap sweep. La famiglia pairs (SENZA stop, validata a pos 0.15 lev 3 =
esposizione 0.45) girava col position_size globale 0.5 a leva 2 = esposizione 1.0,
~2.2x il validato (ETH/BTC DD grezzo 78% a quella taglia; live: ADA/ETH -4.26%
sleeve in un trade il 2026-06-05).

Gate pairspos_port06_impact.py (pairs_sim alla leva live 2x, parita' canonica
1.00000 5/5, PORT06 pesi cap): griglia pos {0.5, 0.25, 0.20, 0.15}. Il gate
meccanico boccia ogni riduzione (costa OOS Sharpe — il compounding in-sample a 0.5
e' fantasia: +2.6e9% ETH/BTC), ma 0.20 e' il ginocchio del trade-off: OOS DD
3.40->1.26% (meglio anche di 0.15), famiglia DD 14->5.8%, costo OOS Sharpe
9.05->8.43. Decisione assicurativa (utente), come il precedente cap SHAPE.

- runner.pos_for_spec(sid, global, family_overrides): override per-famiglia
  (chiave weighting.family_of), plumbato in build_worker_for.
- portfolios.yml: position_size_family {PAIRS: 0.20} (globale 0.5 invariato).
- Test: pos_for_spec mapping + PairsWorker.position_size (74/74 verdi).

Nota transizione: la posizione LTC/ETH aperta verra' chiusa col pos nuovo
(one-time). Diario docs/diary/2026-06-07-pairspos-gate.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:44:15 +00:00
Adriano Dal Pastro 0aaab08612 release: v1.1.5 2026-06-07 10:29:57 +00:00
Adriano Dal Pastro e3bb622b90 feat(fade): swap filtro live hurst->trend_max 3.0 (gate PORT06 sul path live)
Punto 7 roadmap sweep. Gate trendmax_port06_impact.py (engine exit16_port06_impact
riusato, parita' canonica 1.00000; maschera hurst IDENTICA al live via
fade_base.hurst_skip_mask; PORT06 pesi cap, path live EXIT-16):

- CANDIDATO (hurst+trend) BOCCIATO: over-filtering (FULL Sharpe 7.23->7.11,
  meta' dei trade) nonostante DD 2.68->2.06.
- SCOPERTA: il loss-guard Hurst e' ridondante-DANNOSO post-EXIT-16 (NESSUNO
  batte LIVE: FULL Sh 8.07 vs 7.23). EXIT-16 ha eliminato i wick-stop che hurst
  evitava -> gli ingressi saltati (66% delle barre) sono tornati vincenti.
  Il test che promosse hurst (2026-06-02) era sull'engine PRE-EXIT-16.
- TREND-ONLY domina LIVE su tutte le metriche (FULL Sh 7.89 DD 2.46, OOS Sh 9.91
  DD 1.20) ed e' la config che la ricerca EXIT-16 aveva davvero promosso (entries
  trend-filtrate, no hurst) mai eseguita dal live. Plateau 2.5/3.0/3.5 robusto.

Decisione (utente): SWAP. _defs.py: trend_max=3.0 + ema_long=200 nelle 6 fade,
hurst_max rimosso (hurst_skip_mask resta in fade_base). hourly_report: monitor
stop-rate per epoca PRE->HURST->TREND (verdetto a n>=30). CLAUDE.md aggiornato
(paragrafo hurst marcato storico). Diario docs/diary/2026-06-07-trendmax-gate.md.

Lezione: ri-gateare ogni filtro quando cambia l'exit engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:29:57 +00:00
Adriano Dal Pastro 11ace196c7 release: v1.1.4 2026-06-07 09:36:17 +00:00
Adriano Dal Pastro 62e272255b feat(live): disaster-bracket on-book sui fade reali + alert FEED_OUTAGE + osservabilita' multi-asset
Punti 5-6 dell'improvement-sweep 2026-06-06 (protezione capitale + osservabilita'):

Punto 5 — disaster bracket:
- ExecutionClient.place_disaster_sl: STOP_MARKET reduce-only a ~-30% dall'ingresso
  (trigger mark price), piazzato a ogni REAL_OPEN (MR01/MR02/MR07/DIP01) e
  cancellato in _real_close. Assicurazione outage: il poll-loop in except lascia
  le posizioni reali senza valutazione exit (ETH gap max storico 33%/1h). In
  operativita' normale non scatta mai -> 0 costo Sharpe. real_dsl_order_id
  persistito (resume-safe). Config overrides.execution.disaster_sl_pct (0.30).
- NB: set_stop_loss di cerbero-mcp e' un private/edit Deribit (solo ordini APERTI)
  -> non usabile su market fillati; il bracket e' un trigger order autonomo via
  place_order(type=stop_market). Cancel di un trigger order risponde 'untriggered'
  (= successo, verificato testnet: re-cancel -> order_not_found).
- Runner: alert Telegram FEED_OUTAGE dopo 5 poll falliti consecutivi (elenco
  posizioni reali aperte) + notifica RIPRESO con durata.

Punto 6 — osservabilita':
- in_position nei _save() di TR01/ROT02/TSM01; hourly_report: sezione MULTI-ASSET
  (book | ultimo flip | freschezza status) — prima i 3 worker erano invisibili
  (collect() filtra su event/in_position che non emettevano); esclusi dalla
  tabella IN CORSO (assume entry/bars single-leg).
- live_shadow_smoke esteso: scenari C/D SHORT (TP-resting BUY mai esercitato
  prima) + disaster bracket in tutti gli scenari.

Verifiche: 72/72 test; smoke testnet 4 scenari verdi (DSL piazzato/cancellato due
lati, zero ordini orfani sul book, conto flat); multi_asset_section renderizza sui
dati live. Diario docs/diary/2026-06-07-sweep-fixes.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:36:11 +00:00
Adriano Dal Pastro bd5f1a6ea0 release: v1.1.3 2026-06-07 08:23:25 +00:00
Adriano Dal Pastro 3909646028 fix(live): parita' worker multi-asset — TR01 fee x leva, forming-bar su TR01/Pairs, WARN panel corto
Punti 2-4 dell'improvement-sweep 2026-06-06 (fix di parita', nessun cambio di strategia):

- TR01 (basket_trend_worker): fee per flip = POS * LEV * fee_rt/2 come il reference
  honest_improve2._tr_basket_daily:150 (mancava il fattore leva -> fee sotto-caricate 2x,
  bias ottimistico che gonfiava total_capital al ribilancio).
- TR01: crossover EMA e booking del return valutati SOLO su barre 4h COMPLETE
  (la riga -1 e' la candela in corso finche' non e' trascorsa la sua durata) — lezione
  EXIT-16; evidenza live: flip SOL 0->1->0 in 59min nella stessa finestra 4h.
- PairsWorker: entry ED exit valutati sul close di barra COMPLETA, come il backtest
  pairs_research (close settled). Stesso pattern bar_ms di strategy_worker.tick.
- TSM01/ROT02: il silent return su panel inner-join troncato sotto il lookback ora
  emette WARN log + Telegram PANEL_SHORT (helper _warn_panel_short condiviso), gated
  su "era gia' operativo" (no falsi positivi al cold-start), una notifica per episodio.

Test: 72/72 verdi (7 nuovi: forming-bar TR01/pairs + controllo positivo, fee con leva,
PANEL_SHORT warn/dedup/cold-start). Replay parity: validate_worker_pairs ESATTO
(ETH/BTC e BTC/LTC == backtest); validate_honest_workers invariato (TR01 -44% vs ref
+42% IDENTICO pre/post fix: e' la divergenza di convenzione capitale-unico vs
media-equity gia' documentata, da rivisitare a parte).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:23:19 +00:00
Adriano Dal Pastro 9ce43031ee fix(PORT06): ripristina cap SHAPE 0.0588 in portfolios.yml (P0 improvement-sweep)
L'override caps in portfolios.yml SOSTITUISCE interamente il dict di _defs.py
(setattr in base.py:load_active_portfolio) -> il cap SHAPE introdotto in v1.1.2
era inerte e SH01 (senza SL by-design) girava a 2x peso. Ridichiarato il dict
completo {PAIRS: 0.33, SHAPE: 0.0588} + commento sul footgun.

Verificato live post-restart: SH_BTC+SH_ETH = 0.0588, non-cappati a 0.0647,
sum = 1.0 (== backtest canonico FULL 6.43 / OOS 8.58).

Include il diario dello sweep multi-agente 2026-06-06 (106 agenti, 37 finding
validi): fonte dei P0/P1 in roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:07:00 +00:00
Adriano Dal Pastro 8c4e1cdf13 config(docker): escludi pythagoras-portfolio da Watchtower (label enable=false)
Watchtower gira sull'host (containrrr/watchtower): senza esclusione potrebbe
riavviare/aggiornare automaticamente il trader stateful con posizioni aperte.
I deploy passano SOLO da scripts/deploy.sh. Label aggiunta da root sull'host
il 2026-06-05 08:06, verificata e adottata su conferma esplicita dell'utente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:52:11 +00:00
Adriano Dal Pastro 35c0133e63 release: v1.1.2 2026-06-05 18:43:32 +00:00
Adriano Dal Pastro 2da3457ca7 fix(live): EXIT-16 confirm-SL valutato sul close di barra COMPLETATA + alert STALE_FEED
Il worker valutava il confirm sul prezzo della candela IN FORMAZIONE ad ogni poll,
reintroducendo la wick-sensitivity che EXIT-16 elimina (audit crash ETH 2026-06-05:
2 stop su 3 erano wick-stop che il backtest validato non avrebbe preso in quel
momento). Ora il confirm usa SOLO il close dell'ultima barra completata (riga -1 =
candela in corso finche' now < ts[-1]+bar_ms), buf dall'ATR della stessa barra,
fill al prezzo corrente (~ stress lag_close_exit OK in exit-lab), TP intrabar
invariato. Concausa feed-gap: non mitigabile lato exit (fill reali ~ sim);
entry-guard post-flat TESTATA e BOCCIATA (skippare i segnali dopo barre flat
peggiora tutti gli sleeve ETH: la candela-gap e' l'overshoot che la fade fada).
Aggiunto alert Telegram STALE_FEED (>=2 barre 1h flat -> notifica + gap % al
risveglio, dedup per episodio, solo osservabilita').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:32:14 +00:00
Adriano Dal Pastro ff2ff6de7d release: v1.1.1 2026-06-05 17:56:31 +00:00
Adriano Dal Pastro bd6232dc00 config(PORT06): cap SHAPE 0.0588 — SH01 resta senza SL (ricerca multi-agente: 11 famiglie di stop, 0 sopravvissute)
Crash ETH 2026-06-05: SH01 ETH −15.6% su un trade (exit solo a orizzonte, nessuna
protezione). Ricerca con harness dedicato sh01_exit_lab (cache walk-forward, engine
fill gap-aware worse(livello,open), parity esatta con explore_lab, train<=2023-11-01):
ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop,
disaster-cap close+intrabar, swing, vol-regime — NESSUNA passa il gate (ogni stop
stretto rompe BTC, ogni stop largo non tocca la coda ETH; nei crash il fill e' al gap).
Mitigazione: peso famiglia SHAPE 11.8%->5.9% in PORT06 (FULL 6.47->6.43 DD 4.10->3.96,
OOS 8.82->8.58 DD 1.30->1.36) — la prossima coda impatta il conto per meta'.
Regression-lock test aggiornato. Diario: docs/diary/2026-06-05-sh01-sl-research.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:56:16 +00:00
Adriano Dal Pastro 6f86c644bf docs: CLAUDE.md + README aggiornati — PORT06 canonici correnti (6.47/8.82, con EXIT-16 7.84/10.06), shadow reale 7 sleeve (DIP01 incluso), sezione esecuzione reale nel README, famiglia SHAPE in tabella, exit_lab in struttura/comandi
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:58:04 +00:00
Adriano Dal Pastro 2e9ec81567 config(live): DIP01 BTC aggiunto allo shadow reale (7° sleeve single-leg, wiring StrategyWorker invariato)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:45:27 +00:00
Adriano Dal Pastro 3bc42517b0 docs(diary): exit-lab — sezione implementazione+deploy v1.1.0 (complementarieta' hurst/trend/close-confirm)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:29:55 +00:00
Adriano Dal Pastro 59609a535a release: v1.1.0 2026-06-04 21:26:50 +00:00
Adriano Dal Pastro a2579d21bc feat(fade): EXIT-16 close-confirm SL — stop solo se il CLOSE sfonda sl∓0.5·ATR14 (immune ai wick)
Param sl_confirm_atr (None = comportamento storico) in FadeStrategy.backtest,
MR01.backtest e StrategyWorker.tick; attivo live a 0.5 sulle 6 fade in _defs.py.
TP intrabar al livello e max_bars invariati; in modalita' confirm il TP ha
priorita' nel bar. Ricerca exit-lab (34 agenti, 3 lenti avversariali + tail
audit): gli stop intrabar da wick sono falsi negativi per le fade. PORT06
canonico: FULL Sharpe 6.47->7.84 DD 4.10->2.60, OOS 8.82->10.06 DD 1.30->1.15.
NB path grezzo non filtrato: ret esplode ma DD per-sleeve sale — la riduzione
DD vive nel config live (trend_max+hurst) + diversificazione, come misurato.
5 test nuovi (59 verdi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:25:17 +00:00
Adriano Dal Pastro ad65a0b344 research(exit-lab): 34 agenti su exit dinamiche → EXIT-16 close-confirm SL PROMOSSO a livello PORT06
23 famiglie esplorate (harness condiviso exit_lab, train/OOS embargo nov-2023,
tutto lo storico 1h 2018-2026) + 10 verifiche avversariali + test PORT06.
'Cavalcare il prezzo' non esiste (4a conferma: oltre il TP=media non c'e' coda).
Scoperta: lo SL intrabar fisso e' il distruttore di valore n.1 delle fade
(stop da wick = falsi negativi). Forma robusta: SL solo su CHIUSURA oltre
sl0±0.5·ATR14 — PORT06 FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS 8.82→10.06.
Collaterali: bias gap-through dell'engine sugli stop stretti; ramo -2% del
worker morto con sl=0. Diario: docs/diary/2026-06-04-exit-lab.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:16:58 +00:00
Adriano Dal Pastro 3accc91f84 docs(diary): exit ladder 80/20 con runner+stop dinamico SCARTATO (il runner non corre: TP=media, ~3% oltre TP, −38% ret su MR02 ETH)
Test onesto scripts/analysis/partial_tp_ladder.py: stessi segnali, engine
intrabar fade_base, fee identiche. Il ladder compra win-rate/DD pagando i
winner migliori — stesso profilo del vol-target gia' scartato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:48:45 +00:00
Adriano Dal Pastro 7707cc17a0 release: v1.0.7 2026-06-04 19:24:20 +00:00
Adriano Dal Pastro abd396fa54 feat(live): TP reale = LIMIT reduce-only al livello (fix +235bps slippage exit)
All'apertura reale il worker piazza un limit reduce-only al TP della strategia
(quota del SOLO worker, prezzo quantizzato al tick); alla chiusura sim cancella
il resting, riconcilia i fill (anche parziali) via get_trade_history per
order_id e chiude a market solo il residuo. real_tp_order_id persistito
(resume-safe). SL resta market-on-poll (deliberato: trigger Deribit = nuovo
order_id al trigger, fill non verificabile; e il rimbalzo sul SL lavora a
favore). Smoke testnet 2 scenari OK (resting+cancel / fill immediato).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:24:07 +00:00
Adriano Dal Pastro e8075b6292 docs(diary): divergenza shadow sim/reale misurata (+235bps su TP) + TODO limit reduce-only al TP
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:20:07 +00:00
Adriano Dal Pastro a26397b1bf release: v1.0.6 2026-06-04 16:10:33 +00:00
Adriano Dal Pastro 74670dae05 fix(live): quantizzazione amount senza artefatti float (Decimal) — 72*0.001=0.07200000000000001 causava 'Invalid params' su Deribit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:10:28 +00:00
Adriano Dal Pastro 4f5bd32c38 release: v1.0.5 2026-06-03 18:01:30 +00:00
Adriano Dal Pastro 74c79aa6c1 feat(live): position_size configurabile da overrides (0.5 = max impiego dei 2K senza debito margine)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:01:30 +00:00
Adriano Dal Pastro 092e809946 docs(live): leva per-strumento NON impostabile su Deribit (set_leverage=400, verificato nei log Cerbero) — l'esposizione la decide la size
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:52:55 +00:00
Adriano Dal Pastro 92de7c9867 release: v1.0.4 2026-06-03 10:20:12 +00:00
Adriano Dal Pastro 719509fb2f feat(live): alert Telegram esecuzione reale + doc shadow-execution in CLAUDE.md
- telegram_notifier: eventi REAL_EXEC_LIVE (primo ordine reale verificato per worker)
  e REAL_OPEN_FAIL (apertura reale non verificata)
- StrategyWorker: notifica una-tantum al primo REAL_OPEN verificato + ogni FAIL;
  flag real_first_notified persistito
- CLAUDE.md: documentato lo shadow-execution (ExecutionClient, lineari USDC,
  verifica sul trade, fee reali, config, alert, smoke)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:20:08 +00:00
Adriano Dal Pastro fec15c07bb release: v1.0.3 2026-06-03 10:12:23 +00:00
Adriano Dal Pastro cb1b6ea46a feat(live): esecuzione REALE su Deribit testnet (shadow) per i 6 fade sui lineari USDC
- ExecutionClient: notional->amount (lineare USDC + inverse), open/close_amount
  reduce-only, verifica sul trade (order_id), fee reali lette dai trades[]
- CerberoClient: place_order market + reduce_only, get_trade_history
- StrategyWorker: shadow (REAL_OPEN/REAL_CLOSE accanto al sim), ledger reale
  parallelo persistito, confronto slippage/fee sim-vs-reale
- runner+portfolios.yml: config execution (6 fade MR01/MR02/MR07 x BTC/ETH su
  BTC_USDC/ETH_USDC-PERPETUAL), capitale 2000
- smoke: live_exec_smoke (layer) + live_shadow_smoke (catena worker), provati su testnet

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:11:26 +00:00
Adriano Dal Pastro 1f0c1ab02a release: v1.0.2 (revert fallback mainnet) 2026-06-02 19:50:47 +00:00
Adriano Dal Pastro b5de241ebc revert(live): rimuovi fallback mainnet — il paper deve usare il venue di esecuzione (testnet)
Il fallback a Deribit mainnet per i dati introduceva una DIVERGENZA paper<->esecuzione: il paper
simulava su prezzi mainnet ma gli ordini (place_order via Cerbero) andrebbero su TESTNET, i cui
prezzi/liquidità divergono ~9%. Un paper trader deve usare la STESSA fonte/venue dove gli ordini
verrebbero eseguiti, altrimenti non predice il comportamento reale. Durante un outage testnet il
runner si mette in pausa (corretto: senza il venue non si eseguirebbe comunque). Rimosso
get_historical_mainnet e il wiring nel runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:50:47 +00:00
Adriano Dal Pastro 5157cd544d release: v1.0.1 (fallback mainnet) 2026-06-02 16:58:47 +00:00
Adriano Dal Pastro 722837e599 Merge feat/mainnet-fallback: OHLCV da Deribit mainnet quando testnet e' giu'
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:58:14 +00:00
Adriano Dal Pastro 8e72d7ad02 feat(live): fallback OHLCV a Deribit MAINNET quando Cerbero/testnet e' giu'
Deribit testnet (test.deribit.com) va giu' periodicamente (502) e Cerbero lo rilancia ->
il runner si bloccava senza dati. Aggiunto CerberoClient.get_historical_mainnet (Deribit MAINNET
public, NO-AUTH, paginato sotto il cap ~5000 candele/chiamata) e fallback nel runner: try Cerbero
-> on fail/empty usa mainnet. Prezzi REALI (meglio del testnet farlocco per il paper). Verificato
durante l'outage: tutti gli 8 strumenti (BTC/ETH + alt _USDC) coperti su mainnet. Log una-tantum
all'attivazione/disattivazione del fallback.

Caveat: testnet e mainnet hanno prezzi diversi (~9%) -> al primo switch le posizioni aperte su
prezzi testnet vanno resettate (transizione pulita).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:58:14 +00:00
Adriano Dal Pastro 87b420f459 docs: monitor loss-guard nel report orario + effetto misurato (stop-loss -67%)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:17:05 +00:00
Adriano Dal Pastro 6605008f49 feat(report): monitor loss-guard Hurst nel report orario Telegram
Traccia lo stop-rate delle fade (MR01/MR02/MR07) PRIMA/DOPO l'attivazione del loss-guard
(2026-06-02 14:34 UTC). Verdetto automatico quando il campione DOPO >= 30 trade. Conferma gia'
visibile: stop-rate live PRIMA 42% (n=36) == backtest 42.1%. Gira host-side (cron), no rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:54:37 +00:00
Adriano Dal Pastro b5f644e52d docs: aggiorna CLAUDE.md e README (loss-guard Hurst, versione/deploy, fix SH01, regime infra)
- Loss-guard Hurst sulle fade (hurst_max=0.55, dimezza DD PORT06) — attivo live
- Sistema versione + deploy.sh (versione nei msg Telegram, +1 ad ogni deploy)
- Fix wiring SH01 (StrategyWorker reale, non MLWorkerWrapper squeeze)
- Fix StrategyWorker: exit orizzonte, is_win netto, min_tp_frac
- Infrastruttura regime (regime_lab/regime_fetcher, data/regime/)
- Esiti ricerca: ARGO/GEX no-go, frattali x regime (FR01 robusto ma non deployato)
- Nota deploy = up -d --build (non restart)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:39:59 +00:00
Adriano Dal Pastro 7da9dc36e4 Merge feat/version-tag: versione nei msg Telegram (+1 ad ogni deploy)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:34:18 +00:00
Adriano Dal Pastro 4770a8368f feat(deploy): tag di versione nei msg Telegram, +1 ad ogni deploy
File VERSION (semver, cotto nell'immagine) letto da src/version.py. Compare nelle notifiche trade
(telegram_notifier) e nel report orario -> sai quale codice ha generato quale msg.
scripts/bump_version.py incrementa la patch; scripts/deploy.sh = bump+commit+rebuild (versione
aumenta ad OGNI deploy). v1.0.0 = primo release versionato (include hurst loss-guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:34:18 +00:00
Adriano Dal Pastro d624fe74c9 Merge feat/fade-lossguard: loss-guard Hurst sulle fade (attivo live)
Sistema anti-perdite: skip fade in regime persistente (hurst>=0.55). Dimezza il DD del PORT06
(FULL 4.10%->2.39%, Sharpe 6.62->6.76, OOS 8.89->9.15). Solo l'Hurst supera il gate (ADX/vol-expansion
falliscono). Attivo live sulle 6 fade. Diagnostici + workflow 11 agenti + diario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:17:07 +00:00
Adriano Dal Pastro 29c2ea488f feat(live): attiva loss-guard Hurst sulle fade PORT06 (hurst_max=0.55)
Attivazione live: hurst_max=0.55 nei params delle 6 fade (MR01/MR02/MR07 x BTC/ETH) in _defs.py.
hurst_skip_mask step=6 (5.9s/fade su 10560 barre live, ~35s per le 6 -> OK su poll 60s, e coincide
con la cache di validazione). Calcolato dalle SOLE close -> nessun feed esterno, il worker lo computa
inline.

NB live/backtest: il filtro agisce solo sul path LIVE (spec.params); il backtest canonico
(build_everything/regression-lock, via risk_management) NON e' filtrato -> il live FARA' meglio del
backtest sul DD (FULL 4.10%->2.39% atteso). Divergenza intenzionale (miglioramento). Backtest-parity
aggiornabile in seguito. Suite: 54 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:17:07 +00:00
Adriano Dal Pastro ac6f3766b0 feat(fade): loss-guard Hurst (skip regime persistente) — dimezza il DD del PORT06
GOAL: limitare le perdite delle fade in regime sfavorevole. Diagnosi (3022 trade): le perdite/stop
si concentrano nel regime PERSISTENTE (hurst>0.55: stop-rate 43% vs 21% anti-persistente), NON in
bassa vol (low-vol e' net positivo). Ricerca web + workflow 11 agenti: l'UNICO meccanismo che riduce
DD senza uccidere l'edge e' il filtro Hurst (ADX, vol-expansion, time-stop, ER, vol-target falliscono
il gate FR01). Test esterni ADX/vol-expansion NON si replicano su queste fade crypto.

TEST DECISIVO PORT06 (gate FR01) SUPERATO: Hurst-skip h<0.55 sulle 6 fade ->
FULL Sharpe 6.62->6.76, FULL DD 4.10%->2.39% (quasi dimezzato), OOS Sharpe 8.89->9.15.
Migliora il portafoglio (a differenza di FR01 che diluiva).

Implementazione: hurst_skip_mask in fade_base.py (rolling-Hurst causale dalle SOLE close -> nessun
feed dati esterno, deployabile inline dal worker) + param hurst_max (default None=off) in
MR01/MR02/MR07. Test: test_hurst_lossguard.py. Default off -> zero impatto su backtest/parita'/live
finche' non attivato.

FIX collaterale: regime_fetcher/regime_lab scrivevano DVOL/funding/feature in data/raw/ ->
inquinavano la discovery asset del backtest (rompeva il regression-lock PORT06). Spostati in
data/regime/ (gitignored). Suite: 54 passed (lock incluso).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:08:15 +00:00
Adriano Dal Pastro 4d9f2af0c0 research(fade-lossguard): diagnostici perdite fade per regime + workflow anti-perdite
Diagnosi (3022 trade fade 2021+): perdite/stop concentrati in regime PERSISTENTE (hurst>0.55,
SL-rate 43% vs 21% anti-persistente), NON in bassa vol (low-vol e' net positivo). Ricerca web
conferma: filtro Hurst<0.45 / ADX<20 / vol-expansion ratio>1.5 (prevenne 72% perdite maggiori).
Workflow 11 agenti testa i meccanismi sulle fade reali.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:35:44 +00:00
Adriano Dal Pastro 26de94e57d Merge feat/fractal-argo-search: ricerca 100 agenti frattali x ARGO (record)
Esito: 15 strategie frattale x regime robuste e causali, ma NESSUNA migliora PORT06
(FR01 lo diluisce: OOS Sharpe 8.89->8.72). Prior ARGO 'VRP>0=range' smentito, edge su
VRP<0 + DVOL bassa. RECORD DI RICERCA: non deployato, infrastruttura regime riusabile.

Artefatti: regime_fetcher/regime_lab (dati DVOL+funding Deribit mainnet, feature regime+frattali
causali), fractal_argo_workflow.js (171 agenti), FR01_hurst_calm_fade.py (NON in MODULE_MAP/yml),
analisi per-anno + test MASTER, diario 2026-06-02.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:49:28 +00:00
Adriano Dal Pastro 8fd8f64368 test(research): FR01 NON migliora PORT06 (verdetto decisivo)
Contributo marginale al MASTER (equal-weight): OOS Sharpe 8.89->8.72 (diluisce),
OOS ret +175%->+156%. Corr FR01 vs MASTER +0.18/+0.23. Sharpe daily-return standalone
~1.85 (non il 3.73 per-trade) -> troppo basso per un PORT06 a 8.89. Ridondanza robusta.
ESITO: search a 100 agenti rigoroso, ma nessuna strategia migliora PORT06. Deploy abbandonato,
resta record di ricerca sul branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:48:02 +00:00
Adriano Dal Pastro 7005763517 feat(research): FR01 Hurst-Calm Fade + analisi per-anno/mercato (ricerca 100 agenti)
Esito ricerca frattali x regime ARGO (171 agenti):
- FR01_hurst_calm_fade.py: vincitore = fade gateato da hurst<0.55 (anti-persistente) +
  dvol_pct<0.4 (DVOL bassa). OOS Sharpe 3.73 BTC, 6/6 anni positivi su BTC+ETH, corr bassa
  coi fade esistenti (MR01 +0.17/MR02 +0.08/MR07 -0.03) -> diversificatore non ridondante.
- fractal_argo_peryear.py: analisi per-anno/regime-mercato dei top candidati.
- diario 2026-06-02: verdetto completo. Finding chiave: prior ARGO 'VRP>0=range=fade' SMENTITO,
  l'edge robusto e' su VRP<0 + DVOL bassa.

Diversificatori, NON spodestano PORT06 (OOS Sharpe 8.19). Branch di ricerca.
Deploy bloccato da: verifica corr sul MASTER intero + wiring DVOL live nel runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:22:10 +00:00
Adriano Dal Pastro 5f229cd66e fix(regime_lab): vrp annualizzato per timeframe + report() safe su 0 entries
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:08:33 +00:00
Adriano Dal Pastro 7169614506 feat(research): substrato ricerca frattali x regime ARGO
- regime_fetcher.py: fetch DVOL (2021+) + funding (2019+) BTC/ETH da Deribit mainnet public
- regime_lab.py: API condivisa, allineamento regime<->prezzo CAUSALE no-look-ahead,
  feature regime (dvol_pct/vrp/funding_z/dvol_chg) + frattali (hurst/higuchi/vratio/williams),
  cache feature precalcolate, report()=netto-fee OOS via explore_lab
- fractal_argo_workflow.js: workflow ~100 agenti (84 griglia + 8 wildcard + verifica + sintesi)

Branch di ricerca: nessun impatto su main/live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:05:14 +00:00
Adriano Dal Pastro 69619df4c7 chore: gitignore stato locale tooling (.claude/, .omc/)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:35:19 +00:00
Adriano Dal Pastro 056d18a3fd Merge feat/hourly-telegram-report: report orario PORT06 su Telegram
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:02:37 +00:00
Adriano Dal Pastro 99efc7a042 feat(report): report orario PORT06 su Telegram
Script standalone che legge lo stato persistito del paper trader a portafoglio
(data/portfolio_paper/*/ + data/portfolios/PORT06/status.json) e invia su
Telegram: (1) trade CHIUSI positivi/negativi NETTO fee con breakdown per motivo
e PnL; (2) tabella trade IN CORSO (posizioni aperte, single e pairs a 2 gambe);
(3) PnL realizzato totale + equity mark-to-market (con non realizzato).

Carica .env da solo (cron non eredita l'env del container), legge file
world-readable scritti dal container, non tocca lo stato del trader. Riusa
send_telegram di telegram_notifier.

Schedulazione: crontab host orario (minuto 0):
  0 * * * * uv run --project /opt/docker/PythagorasGoal python \
            scripts/portfolios/hourly_report.py
(uv run --project + path assoluto: nessun cd necessario.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:02:37 +00:00
Adriano Dal Pastro e44a310a3b Merge fix/sh01-live-wiring: SH01 esegue shape-ML reale (non squeeze)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:24:14 +00:00
Adriano Dal Pastro 83c4e7a334 fix(live): SH01 esegue shape-ML reale, non il wrapper squeeze scartato
Bug di wiring: runner.py avvolgeva lo sleeve SH01 nel MLWorkerWrapper legacy
di multi_runner, che usa SignalEngine (famiglia squeeze ML01 SCARTATA), apre
con Signal nudo ed esce a hold_bars=3 con tick() propria. Risultato: lo sleeve
"SH01" del portafoglio live NON eseguiva SH01_shape_ml (generate_signals mai
chiamata) e il fix horizon-exit era in un ramo morto -> SH01 continuava a
chiudere a hold_limit/3.

Fix: SH01 (kind="ml") gira come StrategyWorker normale. SH01_shape_ml.
generate_signals fa il walk-forward internamente ad ogni tick ed emette
metadata.max_bars=H=12 -> exit via StrategyWorker.tick (orizzonte H, fix
applicato). Rimosso l'import/uso di MLWorkerWrapper e il blocco train esterno.

ml_wf_entries ha train_min=4000 (>=4000 barre 1h per produrre segnali):
aggiunto _ML_LOOKBACK_DAYS=365 cosi gli asset di sleeve ml fetchano >=365g
(~8760 barre), senza dipendere dal fetch 440g di TSM01/ROT02. generate_signals
su 365g: 0,17-0,24s (logit) -> trascurabile sul poll 60s.

Test: test_build_ml_sh01_is_plain_strategyworker (StrategyWorker + strategy
SH01_shape_ml + niente engine squeeze). Suite: 51 passed.

Stato live: SH01 BTC/ETH flat -> contatori resettati (capitale preservato),
trade squeeze archiviati. Rebuild+recreate: 14 worker RESUME puliti, healthy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:24:13 +00:00
Adriano Dal Pastro 56565f6f73 Merge fix/win-net-and-tp-min-edge: win netto-fee + filtro TP edge-minimo
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:30 +00:00
Adriano Dal Pastro ab4f706057 fix(live): win netto-fee + filtro TP edge-minimo sulle fade
Due correzioni emerse da close live con win=True ma pnl<0.

1) Metrica win lorda -> netta. _close_position contava is_win=trade_return>0
   (lordo), gonfiando l'accuracy: un take-profit colpito con mossa < fee RT
   risultava "win" pur perdendo. 51 close live: 39 win (76,5%) -> 13 falsi win
   -> accuracy netta reale 52,9%. Fix: is_win = net > 0. Capitale/PnL erano
   già corretti (netti). Contatori persistiti riconciliati a parte (MR01/DIP01
   BTC 7->1).

2) Filtro edge-minimo min_tp_frac. I 13 falsi win erano tutti MR01/DIP01 BTC in
   regime piatto: TP (la media) entro il costo round-trip -> perdenti garantiti.
   Aggiunto param min_tp_frac (default 0.0=off) a tutte e 4 le fade (MR01 banda,
   MR02 midpoint, MR07 ATR, DIP01): salta i segnali col TP entro la soglia.
   Non si "allarga" il TP (rischierebbe di perdere di piu'): si evita la trade.
   Cablato live a 0.0015 (1,5x fee) in _defs.py.

Validazione backtest BTC+ETH 1h: neutro su tutte le fade (0-1 trade rimossi,
pnl invariato o +leggero su DIP01). I micro-scalp sotto-fee non esistono nello
storico -> artefatto del regime attuale. Filtro puro-upside.

Test: test_win_net_of_fees.py, test_min_tp_frac.py (monotonia + gap > soglia +
default-off invariato). Suite: 50 passed.

NB deploy: il sorgente e' COPY nell'immagine, non montato -> serve
`docker compose up -d --build`, non un semplice restart (vale anche per il fix
SH01 horizon-exit, andato live solo con questo rebuild). Volume ./data persiste,
14 worker in RESUME puliti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:30 +00:00
Adriano Dal Pastro 05ebd6754b Merge fix/sh01-horizon-exit: exit a orizzonte SH01 (H=12, non hold_bars=3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:06:49 +00:00
Adriano Dal Pastro b5d277478c docs(diary): bugfix exit a orizzonte SH01 (3 barre -> H=12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:06:49 +00:00
Adriano Dal Pastro 53e0965c4b fix(live): exit a orizzonte per strategie senza TP/SL (SH01)
Lo StrategyWorker onorava max_bars (orizzonte del Signal) solo nel ramo
`if self.tp and self.sl`. SH01 (shape-ML, H=12) non porta TP/SL, quindi
cadeva sul fallback legacy hold_bars=3 e chiudeva a 3 barre invece delle
12 validate: l'edge (asimmetria sull'orizzonte, non frequenza) non aveva
tempo di realizzarsi -> accuratezza live falsata (33%), tutti exit
"hold_limit" a bars_held=3.

Aggiunto un ramo `elif self.max_bars` che esce a "time_limit" quando
bars_held>=max_bars, prima del fallback hold_bars. Tocca solo le
strategie horizon-only (SH01); le fade con tp+sl+max_bars sono invariate.

Test: tests/portfolio/test_horizon_exit.py (resta in posizione a 3 barre
con max_bars=12; esce a 12 con reason time_limit). Suite: 43 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:05:53 +00:00
Adriano Dal Pastro aaf0221957 docs(diary): studio exit fade (scalping / TP dinamico / TP-ATR)
Tre alternative di uscita misurate su MR02 (fee-aware) vs baseline TP=centro canale:
- 15m "scalp": piu' PnL lordo ma fragile a fee/slippage (DD esplode a 0.20%).
- trailing/TP dinamico: win-rate 48%->36%, azzera l'edge (let-it-run = continuazione).
- TP-ATR: TP stretto m=0.5 -> win 77% ma edge ~0 (trappola scalping); nessun
  multiplo ATR batte il centro su avg/trade e Sharpe (FULL+OOS, BTC/ETH).
Verdetto: TP=centro canale e' ottimale (target adattivo alla struttura, gia'
scalato alla vol). Design blindato. Nota: win-rate alto != edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:52:44 +00:00
Adriano Dal Pastro 1177da33ea docs(diary): stato trade live PORT06 (snapshot verificato 2026-05-31)
~43h di paper trading: 10 trade chiusi (9W/1L, +EUR0.40 realizzato), 3 aperti,
equity MtM EUR1000.36, max DD 0.40%. Check: 0 anomalie (net=gross-fee, win
coerente, fee incluse), uscite pairs conformi a z_exit=0.75, riconciliazione
ledger spiegata (timing rebal). Campione a livello rumore ma sistema sano e
fedele al backtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:23:35 +00:00
Adriano Dal Pastro 03e8938a18 docs(diary): record anti-ripetizione strategie scartate (opzioni + TA classiche)
Conclusioni-only (codice testato e scartato, non conservato): perche' non
funzionano, cosi' da non ri-testarle.
- Opzioni: overlay PORT06 / OH01 direzionale / OH02 credit-spread / V5 debit
  spread (Casario) / V4 box. Nessun edge nuovo: trend e MR gia' catturati 50-100x
  meglio dai perp; VRP contro chi compra, code grasse contro chi vende.
- TA: SMA pullback / ORB / weakness rectangle -- tutte continuazione, negative
  anche a fee 0. Flip al fade: 2/3 segno positivo ma = MR01/MR02 inferiori, WR
  rumore. Riconferma: solo mean-reversion paga.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 07:17:10 +00:00
Adriano Dal Pastro c13773e762 test(portfolio): aggiorna regression-lock PORT06 dopo recupero dati BNB/DOGE/XRP
Il recupero dello storico BNB/DOGE/XRP (29 mag) ha ampliato la copertura del
backtest -> metriche migliorate, non una regressione:
  Sharpe FULL 6.07 -> 6.47, Sharpe OOS 8.19 -> 8.82, DD FULL 4.9% -> 4.1%.
Aggiornati i tre valori attesi (tolleranze invariate) + commento col motivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 07:10:40 +00:00
Adriano Dal Pastro 5a219ca8e5 feat(analysis): proiezione 3 anni capitale PORT06 (live 2x, esclude 2024)
Script di proiezione: partendo da 1000 EUR con capitale che compone e
puntata che cresce, stima guadagno giornaliero e traiettoria a 3 anni.
Riscalata sul sizing LIVE (pos 0.15 x 2x) vs backtest 3x; ROT02/TSM01
usano gross fisso (non riscalano). Esclude il 2024 (anno eccezionale)
e include haircut ~50% per lo scenario sobrio prudente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:29:07 +00:00
Adriano Dal Pastro 2a11728384 feat(docker): deploy PORT06 portfolio runner via docker compose
Wire the Docker image and compose service for the capital-pool portfolio
paper trader (src.portfolio.runner) instead of the single-leg multi_runner:

- Dockerfile: copy full scripts/ (runner imports scripts.analysis.* and
  scripts.portfolios._defs via sleeves.py) and portfolios.yml.
- docker-compose.yml: service "portfolio" / container pythagoras-portfolio,
  command override to src.portfolio.runner, mount portfolios.yml, healthcheck
  on data/portfolios/ status.json.
- .gitignore: ignore portfolio runtime state (data/portfolio_paper, data/portfolios).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 07:30:33 +00:00
Adriano 7b2e0049eb test(live): demo numerica exit intrabar fade -- worker replay ~= backtest
MR01 BTC 1h, 4000 barre, no filtro trend: backtest build_trades +3.5%/73 trade vs
worker replay intrabar +4.5%/78 trade -> gap +1.0pt (allineato). Conferma che il fix
exit intrabar chiude il gap live-vs-backtest delle fade (residuo = bar-timing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:43:38 +02:00
Adriano fb65df7861 merge: fix exit intrabar StrategyWorker (fade/DIP01 allineate al backtest)
Lo StrategyWorker esce su TP/SL toccati intrabar (high/low della barra, al livello,
SL prioritario) come il backtest, chiudendo il gap live-vs-backtest delle fade/DIP01.
+4 test. 39 test totali passano.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:02:14 +02:00
Adriano 49039ac286 fix(live): StrategyWorker esce intrabar su TP/SL (high/low, al livello) come il backtest
Chiude il gap live-vs-backtest delle fade/DIP01: prima il worker controllava solo il
close, ora controlla high/low della barra ed esce AL LIVELLO tp/sl (SL prioritario),
identico alla semantica intrabar del backtest. +4 test. Pairs/rotation/tsmom invariati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:00:12 +02:00
Adriano 9e91ad6335 merge: fase 2 portafogli - validazione runner + worker live honest/TSM01
A) PortfolioRunner certificato (pool/ribilancio/ledger == backtest).
B) worker live dedicati: DIP01 (Strategy), BasketTrendWorker (TR01), RotationWorker (ROT02),
   TsmomWorker (TSM01) + integrazione runner (resample 1h->4h/1d). PORT06 gira live completo.
Validati vs reference (TSM01 esatto, ROT02 canonico, TR01 stesso ordine). 35 test passano.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:52:47 +02:00
Adriano 924ed8eeff docs: fase 2 completata - tutti gli sleeve PORT06 girano live (worker dedicati + validazione) 2026-05-29 17:50:43 +02:00
Adriano fe8c272460 test(portfolio): valida worker honest/TSM01 vs backtest reference
TSM01 esatto (+98%==+98%); ROT02 riproduce il +1303% canonico (reference normalizzata
su finestra piu' corta = +984%); TR01 stesso ordine (+465 vs +591%, differenza di
convenzione capitale-unico-live vs media-equity-report, non un bug). Worker fedeli.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:50:08 +02:00
Adriano a7ada9f36c feat(portfolio): integra worker honest/TSM01 nel runner (PORT06 live completo)
build_worker_for gestisce basket/rotation/tsmom + DIP01 via StrategyWorker; run()
fetcha 1h e resampla a 4h/1d, lookback dimensionato sui daily (TSM01 252g); tick
multi-asset per kind. _defs marca TR01/ROT02/TSM01 col kind+universo. Niente piu'
sleeve saltati in PORT06.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:45:39 +02:00
Adriano 1e60835612 feat(live): TsmomWorker (TSM01) consenso TSMOM multi-orizzonte risk-gated 2026-05-29 17:42:44 +02:00
Adriano a40315563e feat(live): RotationWorker (ROT02) dual-momentum top-k risk-gated 2026-05-29 17:41:13 +02:00
Adriano e7e8041dae feat(live): BasketTrendWorker (TR01) EMA-cross long/flat multi-asset
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:39:11 +02:00
Adriano ce601c4507 feat(live): DIP01 dip-buy come Strategy single-asset (worker via StrategyWorker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:36:32 +02:00
Adriano dc63399cc7 docs(portfolio): piano fase 2-B worker honest/TSM01 dedicati (6 task) 2026-05-29 17:35:10 +02:00
Adriano e374cca103 test(portfolio): valida runner pool+ribilancio+ledger == backtest (identico)
Certifica il livello aggiunto dal PortfolioRunner (capitale pool, ribilancio
giornaliero, ledger aggregato): replay deterministico == port_returns del backtest
(errore 4.4e-08, floating-point). Fedeltà per-worker: pairs esatta, fade approssimata
(exit close live vs intrabar backtest = gap noto dello StrategyWorker), shape a tempo ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:28:03 +02:00
Adriano 2749553577 merge origin/main (discovery strumenti downloader) in shape+portfolios 2026-05-29 17:22:15 +02:00
Adriano 0bb14d1c6e merge: shape patterns (SH01) + cartella portfolios (PORT01-06, runner pool)
Ricerca pattern-forma (4/5 famiglie rumore, SH01 Shape-ML edge/diversificatore) +
cartella portfolios/ completa (portafogli pool, backtest+live, Cerbero v2, default PORT06).
21 test passano. Live v1 = fade+pairs+shape; honest/TSM01 backtest-only (fase 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:21:51 +02:00
Adriano 04f64c8f89 feat(portfolio): compare_all confronto PORT01-06 in un processo 2026-05-29 16:32:57 +02:00
Adriano 0f582db265 fix(portfolio): runner data_dir dedicata, no resize posizioni aperte, poll da config, +test cap/cluster_rp
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 16:22:33 +02:00
Adriano d02bc10ab5 docs(portfolio): documenta cartella portfolios, comandi, scope live e default PORT06
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 16:13:49 +02:00
Adriano a5547fb3d2 feat(portfolio): PortfolioRunner live (data v2, tick, ribilancio giornaliero, ledger)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:05:07 +02:00
Adriano 2b3d3e3ff8 feat(portfolio): build_worker_for (worker esecutori con capitale da alloc pool)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:02:35 +02:00
Adriano 169819fe31 feat(portfolio): portfolios.yml + load_active_portfolio (override operativi) 2026-05-29 16:00:54 +02:00
Adriano 7a4bdb74f0 feat(portfolio): PortfolioLedger (alloc, equity/DD, persistenza+resume)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:59:57 +02:00
Adriano eaf4800b6d feat(portfolio): definizioni PORT01-06 + report run() (default PORT06)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:58:33 +02:00
Adriano 3f6b0ccf91 feat(portfolio): SleeveSpec/Portfolio/backtest con parità verso report_families
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:55:29 +02:00
Adriano 9ff469cb8e feat(portfolio): builder unificato equity-per-sleeve (parità con report_families)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:51:38 +02:00
Adriano d99c9895bb feat(portfolio): schemi di peso (equal/manual/cap/inverse_vol/cluster_rp) 2026-05-29 15:49:27 +02:00
Adriano ea04dcd9d1 feat(portfolio): metodi Cerbero v2 (get_historical_v2, get_instruments, get_ticker_batch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:47:10 +02:00
Adriano 753d786bb5 docs(portfolios): piano di implementazione TDD (10 task)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 15:29:41 +02:00
Adriano 602c46e5bf docs(portfolios): design spec cartella portfolios (brainstorming)
Portafogli come oggetti di prima classe (pool condiviso, backtest+live unificati,
ribilancio periodico, 4 schemi pesi, data layer Cerbero v2). Default PORT06
(master+shape, cap pairs 33%, leva 2x). Include analisi accorpamento sleeve
(cluster per asset/regime, pairs=47% rischio) e fuori-scope (ledger unico,
Hyperliquid alt, cointegration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 15:24:17 +02:00
Adriano 9e1be75444 analysis(portfolio): clustering sleeve per correlazione + contributo rischio
I cluster naturali sono per ASSET/REGIME, non per famiglia (BTC-reversion,
ETH-reversion, trend TR01+TSM01, shape, rotation ROT02). Ridondanza lieve
(max corr 0.43). PAIRS = 47% del rischio a equal-weight -> conferma cap 30-35%.
Equal-weight batte inverse-vol/risk-parity in OOS calmo (pairs corrono liberi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:09:27 +02:00
Adriano e002968914 report(shape): integra SH01 (sleeve SHAPE) nel report per-anno e nell'integrazione MASTER
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:43:19 +02:00
Adriano 2596687679 feat(shape): SH01 Shape-ML validato come diversificatore + doc
Validazione dura del solo edge sopravvissuto alla ricerca shape (ML walk-forward
LogisticRegression sulle feature di forma). SH01 config W24 H12 th0.58:
- BTC robusto ovunque (expanding +219%/OOS+42% Sharpe2.72 8-9anni; rolling2y
  +166%/+96%; stress leva2x+slippage OK), ETH/ADA solo expanding, LTC/SOL/XRP no.
- Griglia 5/27 robuste su cresta W24/H8-12 -> overfit moderato, config conservativa.
- Free-lunch: corr +0.08 col MASTER, aggiungerlo migliora OOS (Sharpe 4.33->5.10,
  DD 4.7->4.2%). Diversificatore, non motore standalone. Regge fee 0.20% RT.

SH01 come Strategy (in MODULE_MAP) + run() riproducibile. shape_ml_research esteso
con walk-forward rolling (train_window). Live richiede worker con retraining.
Diario 2026-05-29-shape.md, CLAUDE.md famiglia SHAPE-ML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:31:26 +02:00
Adriano 4ac87ab385 research(shape): 5 famiglie di pattern-forma su harness onesto
Harness shape_lab (analog kNN causale, no look-ahead verificato) + 5 ricerche
parallele. 4/5 famiglie = RUMORE (confermano dominanza mean-reversion):
- analog kNN forma grezza: solo BTC-overfit, non robusto >=2 asset
- encoding candele UP/DOWN/DOJI + body/shadow: hit-rate ~50%, muore a fee
- DTW + template geometrici: DTW peggiora euclidea; template overfit
- PIP/pivot/zig-zag: 0/48 config robuste
1/5 = EDGE REALE: ML walk-forward (LogisticRegression) sulle feature di forma.
  BTC logit W24H12 th0.58: FULL +219% / OOS +42% / Sharpe 2.72 / 8-9 anni+ /
  regge fee 0.20% RT (+60/+26). Causalita' verificata. Da validare a fondo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:09:28 +02:00
Adriano Dal Pastro 2b01443efe Merge branch 'main' of ssh://git.tielogic.xyz:222/Adriano/PythagorasGoal
# Conflicts:
#	CLAUDE.md
#	README.md
2026-05-29 08:06:19 +00:00
Adriano Dal Pastro 5ac9ebed5b docs: documenta discovery/validazione strumenti e gate del downloader
CLAUDE.md, README.md, API_REFERENCE.md aggiornati per il nuovo layer
src/data/instruments.py: validazione strumenti per exchange (Deribit +
Hyperliquid; esclusi Alpaca e Bybit testnet), congruenza prezzo cross-exchange,
registry come allowlist, gate nel downloader. Aggiunti schemi param
get_instruments/get_markets/get_historical per exchange e convenzione simboli
Deribit (inverse vs USDC lineari). Universo dati esteso con SOL/LTC/ADA 1h.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 07:51:47 +00:00
Adriano Dal Pastro 1561005d41 feat(data): discovery + validazione strumenti con gate nel downloader
src/data/instruments.py: enumera i perpetui per exchange (Deribit, Hyperliquid;
esclusi Alpaca/stocks e Bybit per feed testnet farlocco) e valida ogni strumento
sui DATI STORICI realmente raccoglibili:
  - esistenza, congruenza OHLC, not-flat (scarta contratti morti)
  - liquidita' (volume daily) e congruenza prezzo cross-exchange via mediana
    del base-coin (scarta outlier come Deribit SOL-PERPETUAL=9.6 vs SOL ~82)
Produce data/instruments_registry.json con strumenti validi, timeframe e start-date.

Gate: _download_cerbero_range rifiuta strumenti non validati (override esplicito
allow_unvalidated). La raccolta dati e' possibile solo per strumenti validati.

Registry attuale (testnet): Deribit 18/106 validi (BTC dal 2018, alt dal 2022),
Hyperliquid 66/74. I major liquidi (BTC,ETH,SOL,LTC,ADA,XRP,DOGE,AVAX,BNB,...) passano.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 07:49:57 +00:00
Adriano b9e5176a5b docs: aggiorna README e CLAUDE.md allo stato attuale (4 famiglie + pairs worker)
README riscritto: 4 famiglie (FADE/HONEST/PAIRS/TSMOM) con profili netti OOS,
portafoglio MASTER e numeri sobri anti-overfit, tabella strategie completa, comandi
analisi/validazione, struttura aggiornata (pairs_worker), dati 8 asset + nota naming
Deribit (alt = _USDC-PERPETUAL), paper trading a 2 gambe con sezione pairs in YAML.
CLAUDE.md: struttura/comandi/sezione paper trader aggiornati (niente piu' "solo MR01").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:48:39 +02:00
Adriano a60ad30ac0 fix(live): naming Deribit corretto per alt -> tutte le 5 coppie pairs tradabili live
Gli alt su Deribit sono perp LINEARI USDC: "<COIN>_USDC-PERPETUAL" (storia dal 2022),
non "<COIN>-PERPETUAL" (vuoto per LTC/ADA, dati errati per SOL). INSTRUMENT_MAP corretto.
Smoke test live (live_smoke_pairs.py): tutte e 5 le coppie ricevono feed fresco (1448
barre, ultima ~0.4h) e ticcano. Riabilitate tutte le coppie in strategies.yml.
BTC/ETH restano inverse ("<COIN>-PERPETUAL"). CLAUDE.md / docstring PR01 aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:26:27 +02:00
Adriano bd31a15548 fix(live): smoke test REALE pairs -> live solo ETH/BTC (alt assenti su Deribit)
Test reale (scripts/analysis/live_smoke_pairs.py): fetch live Cerbero + tick vero per
coppia. Scoperta: l'endpoint Deribit serve solo BTC/ETH freschi; LTC/ADA-PERPETUAL sono
VUOTI e SOL ha pochi dati. Il backtest usava i parquet locali (8 asset completi), ma la
pipeline live no -> tradabile live SOLO ETH/BTC.

- strategies.yml: abilitata solo la coppia ETH/BTC; le altre 4 disabilitate con nota
  (valide a backtest, off finche' non si aggiunge un feed live per gli alt).
- live_smoke_pairs.py (nuovo): verifica end-to-end della pipeline live (no ordini reali).
- CLAUDE.md / docstring PR01: distinzione esplicita logica-validata vs live-disponibile.

Onesta': la validazione precedente era backtest-equivalence (ESATTA), NON test live;
il test live ha rivelato il limite del feed alt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:16:50 +02:00
Adriano 4dc0e77ee5 feat(live): worker a 2 gambe per i pairs (PR01 market-neutral)
src/live/pairs_worker.py: PairsWorker market-neutral (long A / short B sullo z-score
del log-ratio, exit |z|<=z_exit o max_bars, FEE SU 2 GAMBE = 2*fee_rt*lev, stato
persistente come StrategyWorker). multi_runner: sezione `pairs:` nello YAML, fetch di
entrambe le gambe, tick/status/shutdown; INSTRUMENT_MAP esteso agli alt. strategies.yml:
5 coppie PR01 (config universale n50 z2 zx0.75 mb72).

Validazione (scripts/analysis/validate_worker_pairs.py): replay live bar-per-bar ==
backtest pairs_sim ESATTAMENTE -> ETH/BTC capitale 2.870.429 = 2.870.429, 1754 trade,
win 74.1% identici. Caveat: shortabilita'/liquidita' del perp B sugli alt da verificare
in trading reale. CLAUDE.md / docstring PR01 aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:12:25 +02:00
Adriano 9a066eb76f feat(analysis): report_families.py - riepilogo strategie/famiglie per anno + integrazione
Report consolidato: (A) Ret%/anno per famiglia (FADE/HONEST/PAIRS/TSM01) e portafogli,
(B) Ret%/anno per ogni strategia singola (15 sleeve), (C) analisi di integrazione delle
nuove famiglie nel MASTER (+pairs/+TSM01/esteso) con correlazioni, (D) numeri sobri e
raccomandazione (leva 2x, cap pairs 30-35%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 08:20:23 +02:00
Adriano 7226946911 refactor(explore): irrobustimento anti-overfit di pairs/TSM01/master
Giro di validazione scettica (walk-forward, plateau, stress, scomposizione):

- PAIRS: config PER-COPPIA -> config UNIVERSALE (n50 z2 zx0.75 mb72), niente
  cherry-picking. Plateau confermato (heatmap 20/20 Sharpe>1) + walk-forward
  (ETH/BTC 11/12 finestre+, BTC/LTC 9/10). Scartata BNB/ETH (overfit). 5 coppie robuste.
- TSM01: gross 0.45->0.30 (stesso Sharpe, DD 22->15%); corr reale con ROT02 = 0.62
  (non 0.53); diversificatore, non motore. Robusto (36/36 config OOS+).
- Confluenza multi-TF SCARTATA: overfit (taglia 97% trade, ~40 in 8 anni, Sharpe crolla).
- MASTER: numeri sobri onesti -> OOS Sharpe 7.7/DD 2.3% e' regime calmo 2024-25
  (ottimistico ~50%); worst-DD 90g ~6%, Sharpe atteso ~5, regge leva 2x+slippage.
  Config robusta: equal-weight, leva 2x, cap pairs ~30-35% (sono ~57% del rischio).

Quanto trovato regge l'esame anti-overfit; numeri comunicati sobri. Doc/CLAUDE/memoria aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 08:11:33 +02:00
Adriano 945c2a2db6 feat(explore): 3a ondata - PAIRS a 6 coppie + verdetti su 4 nuove famiglie
Espansione dei meccanismi provati + 2 nuovi sondaggi (agenti paralleli, honest):
- PAIRS espansa a 6 coppie robuste: + BTC/LTC (robusta 1h E 4h, Sharpe 2.21, DD 24-34%),
  ETH/SOL e BNB/ETH (Sharpe 2.4+, solo 1h). Pattern: alt-liquido vs major.
- Fade su 6 alt: 0 robuste (mean-reversion vive solo su BTC/ETH; DOGE = artefatto).
- Low-vol anomaly: invertita in cripto (vince alta vol), ridondante -> scartata.
- Confluenza multi-TF: dimezza il DD di MR01 (ETH same Sharpe a DD 38% vs 63%) ->
  variante low-DD utile, non edge nuovo.

MASTER + 6 pairs (15 sleeve): CAGR 47->71%, OOS DD 4.7->2.3%, Sharpe OOS 4.33->7.71,
tutti gli anni positivi. Bilancio robusti: famiglia PAIRS (6) + TSM01 = 7 strategie nuove.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 01:13:54 +02:00
Adriano 33e3e2a603 feat(explore): esplora 9 famiglie alternative -> PAIRS (nuovo edge forte) + TSM01
Esplorazione onesta con agenti paralleli su harness condiviso (explore_lab.py):
ingresso close[i], netto fee, OOS, DD basso, attenzione fee. 7 famiglie su 9 sono
rumore (stagionalita' oraria/mensile, cross-sectional reversal, opening-range,
lead-lag BTC->alt, continuation intraday) e l'harness le rifiuta senza falsi positivi.

Due edge reali verificati indipendentemente:
- PR01 Pairs: spread reversion market-neutral su log-ratio z-score (ETH/BTC, LTC/ETH,
  ADA/ETH). ETH/BTC CAGR 144% Sharpe 4.04 OOS DD 17% 8/9 anni, corr mercato ~0.02,
  no-look-ahead verificato, regge fee 0.40%/coppia. Fee su 2 gambe (worker da estendere).
- TSM01: TSMOM multi-orizzonte 3/6/12m + risk-off, distinto da ROT02 (corr 0.53),
  DD 22%/12% OOS, mai un anno negativo, regge fee 0.40%.

Payoff: aggiungere i pairs (quasi scorrelati ~0.05) al MASTER -> CAGR 47->66%,
DD 5.2->3.8% full / 4.7->3.3% OOS, Sharpe OOS 4.33->6.86 (combine_v2.py).

Fix: explore_lab.get_df ora produce timestamp ms reale per 1d/4h (era placeholder).
Diario 2026-05-29-exploration.md + nota CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 01:07:08 +02:00
Adriano 666c906907 feat(analysis): report.py aggiornato con numero trade per anno
Report consolidato: (A) Ret% netto per anno di ogni strategia singola + portafogli
(FADE/HONEST/MASTER eq/5050), (B) numero trade per anno per strategia (ingressi per
fade/DIP01; ribilanciamenti per TR01/ROT02 a posizione continua), (C) riepilogo
portafogli FULL/OOS. Config deployata (MR03/ROT01 in waste, ROT02 top_k=3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:33:00 +02:00
Adriano 9ebdfc7a7a feat(ROT02): riduce il DD diversificando (top_k 2 -> 3)
ROT02 concentrava il book su 2 soli asset (DD 40%). top_k=3 dimezza quasi il DD
(40% -> 26%) e ALZA il ritorno full (+1095 -> +1303%, ret/DD da 27 a 50). Il
vol-target abbassa il DD ma sacrifica ritorno (de-leverage) -> tenuto top_k=3
senza VT. Caveat: OOS di ROT02 cala (+98 -> +68%, DD 12 -> 14%), ma il portafoglio
MASTER migliora lo Sharpe full (3.95 -> 4.23). Applicato a ROT02_dual_momentum.py
e a _rot_daily_equity (usata da PORT01/PORT03). Docstring/CLAUDE/diario aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:30:55 +02:00
Adriano bcccfde9a0 feat(strategie): portafogli master (PORT02/PORT03) + waste delle peggiori (MR03, ROT01)
Crea gli artefatti accorpati e migliorati:
- PORT02_fade_master: 3 fade (MR01/MR02/MR07) x BTC/ETH = 6 sleeve, filtro trend,
  equal-weight daily. DD 8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
- PORT03_all_master: portafoglio MASTER fade+honest (9 sleeve), varianti equal
  (max Sharpe: DD 5.2%/4.7% OOS, Sharpe 3.95/4.42) e 50/50 (min DD 5.1%/4.3%).

Sposta in scripts/waste/ le due peggiori:
- MR03 keltner_fade: fade piu' debole (BTC Sharpe 1.22), ridondante con MR01, il
  filtro trend la peggiorava; rimuoverla MIGLIORA il portafoglio fade.
- ROT01 xsect_rotation: strettamente dominata da ROT02 (stesso meccanismo, ROT02
  meglio su tutto), non usata da alcun portafoglio.

Sganciata MR03 da strategy_loader, strategies.yml e dal motore portafogli
(risk_management.STRATS). La funzione keltner_fade resta in strategy_research_v2
come record. CLAUDE.md aggiornato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:21:37 +02:00
Adriano 1af4addbdd feat(analysis): studio combinazione strategie fade + honest (diversificazione)
combine_portfolio.py: costruisce l'equity giornaliera di tutte le sleeve (8 fade +
3 honest) su indice comune 2021-2026, misura la correlazione cross-famiglia e
confronta i portafogli FULL/OOS (ret, CAGR, DD, Sharpe).

Risultato: le due famiglie sono quasi scorrelate (corr ~0.05). Combinarle migliora
il rischio/rendimento: equal-weight 11 sleeve -> DD 6.1% full / 4.6% OOS, Sharpe OOS
4.46 (vs honest-only 12% DD / 2.23 e fade-only 8.6% DD / 4.14), CAGR ~43% mantenuta.
Il 50/50 fra famiglie da' il DD piu' basso (5.5% full / 4.0% OOS). Diario 2026-05-29
e nota CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:14:14 +02:00
Adriano 22c6080873 chore(analysis): pulizia e accorpamento script di analisi (25 -> 15 file)
- accorpa risk_improvements.py + risk_portfolio.py -> risk_management.py
  (sezione A screening leve, sezione B filtro trend + portafoglio)
- rimuove 4 script legacy della famiglia squeeze (ormai in waste, non
  referenziati): compare_strategies, best_yearly, final_report, yearly_market_report
- rimuove 5 script honest_* di diagnostica/iterazione superati da honest_matrix
  (consolidato) e non importati: honest_diag, honest_diag2, honest_candidates,
  honest_yearly, honest_yearly2
- mantiene il core honest (lab/improve/improve2/rotation/trend) + canonici
  (final/matrix), tutta la ricerca fade (strategy_research[_v2]), validazione
  (oos_validation, validate_worker_mr01), intrabar_test (lezione squeeze)
- aggiorna riferimento in CLAUDE.md. Import-check: 14/14 moduli OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:08:24 +02:00
Adriano fd547748dd merge: strategy_free -> main (fade MR02/MR03/MR07 + filtro trend Acc/DD)
Integra il lavoro su branch strategy_free, indipendente dalle strategie oneste
(DIP/TR/ROT/PORT) gia' su main: nessun file in comune, merge pulito.

- 3 nuove fade mean-reversion validate OOS fee-aware: MR02 Donchian fade,
  MR03 Keltner fade, MR07 Return reversal (+ base condivisa fade_base).
- Filtro trend (trend_max/ema_long) su tutte le fade: alza Acc e riduce DD
  (drastico su ETH), edge OOS confermato; modello portafoglio equipesato.
2026-05-29 00:00:51 +02:00
Adriano a51129acf6 feat(analysis): matrice PnL/anno consolidata (confronto strategie + portafoglio)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:53:37 +02:00
Adriano 1b099bb47b feat(analysis): tabella per-anno (PnL/DD) versioni migliorate + portafoglio
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:52:25 +02:00
Adriano 783fa5546f feat(analysis): miglioramenti - ROT02 dual-momentum + portafoglio (DD 12%)
Obiettivo: alzare Acc, ridurre DD, migliorare PnL. Leve oneste, no tuning per-anno.

- ROT02: overlay absolute-momentum (cash se BTC<SMA100) su ROT01. Domina su tutte
  le metriche: FULL +679->+1095%, OOS +44->+98%, DD 53->40%.
- DIP01 market-gate (variante low-DD): alza Acc (ETH 52->57, SOL 49->52) e dimezza
  il DD (ETH 53->23), al costo di PnL. De-risking opzionale; su BTC il gate va evitato.
- PORT01: portafoglio equal-weight giornaliero delle 3 sleeve anti-correlate
  (DIP01+TR01+ROT02). DD 12% (sotto ogni sleeve), CAGR 45%, 2022 bear -1% (era -30%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:49:14 +02:00
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
Adriano ad141f080c feat(analysis): report per-anno (Trade/Acc/DD/PnL) delle 3 strategie
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:42:04 +02:00
Adriano 212427ffa1 feat(analysis): 3 strategie oneste validate OOS multi-crypto (DIP/TR/ROT)
Ricerca onesta post-squeeze su 8 crypto (2018-2026), engine fee-aware con
ingresso eseguibile a close[i], uscita TP/SL intrabar, OOS held-out, sweep fee.

Lezione madre: shortare cripto perde OOS sistematicamente (campione net-bull)
-> tutte le strategie robuste sono long-biased.

Tre meccanismi distinti e complementari:
- DIP01  dip-buy z-score reversion (long-only, 1h)  robusto BTC/ETH/SOL
- TR01   EMA 20/100 trend-following (long-only, 4h) robusto su 5/8 asset
- ROT01  rotazione cross-sectional momentum sul paniere (1d) OOS +44%, param-insensitive

Engine e validazione: scripts/analysis/honest_lab.py + honest_final.py
(+ honest_candidates/diag/diag2/trend/rotation). Diario in docs/diary/.

Onesto sull'obiettivo: €50/giorno su €1000 in pochi mesi non e' raggiungibile a
rischio sano (~1825%/anno); edge reali 30-60% OOS pluriennale. Via realistica:
portafoglio delle 3, leva moderata, crescita composta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:28:00 +02:00
Adriano 21d3ba609d feat(strategie): 3 nuove fade mean-reversion validate OOS fee-aware (MR02/MR03/MR07)
Trovate e promosse 3 strategie con edge netto distinto da MR01, stessa
metodologia (ingresso close[i], netto fee 0.10% RT + leva 3x, OOS ultimo 30%,
robustezza su griglia + sweep fee 0.00-0.20%):

- MR02 Donchian Fade: fade rottura canale H/L, TP al centro. BTC +172% OOS.
- MR03 Keltner Fade: canale ATR su EMA (indipendente da Bollinger). BTC +112%.
- MR07 Return Reversal: fade movimento di barra estremo (z dei rendimenti). BTC +105%.

Tutte positive netto OOS su entrambi gli asset e su tutto lo sweep fee, anche
0.20% RT pessimista (validate anche via oos_validation live-path). Scartate
MR04 (= MR01 riparametrizzato), MR05 (ADX non robusto), MR06 (RSI2 ETH neg).

Base condivisa fade_base.FadeStrategy (backtest intrabar TP/SL/max_bars).
Aggiunte a strategy_loader e strategies.yml (BTC+ETH 1h). Ricerca in
strategy_research_v2.py. Diario e CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:26:21 +02:00
Adriano Dal Pastro 48435f6858 feat(live): worker con exit TP/SL/max_bars per MR01 + doc aggiornata
StrategyWorker ora supporta exit guidati dalla strategia via Signal.metadata
(take-profit alla media / stop-loss ad ATR / time-limit), con fallback al
vecchio hold_bars/stop -2% per strategie senza metadata. Usa fee_rt della
strategia (MR01 = 0.10% RT reale Deribit, non piu' 0.20% hardcoded).
Persistenza di tp/sl/max_bars in status.json per resume.

Re-validato col worker reale (replay finestre mobili 1h, fee 0.10%):
  BTC 1h MR01: +196% OOS, ETH 1h: +251% OOS (nov 2023->mag 2026) — coerente col backtest.

README + CLAUDE.md riscritti: squeeze = artefatto di look-ahead -> waste,
MR01 mean-reversion unica attiva, metodologia anti-look-ahead e fee reali 0.10% RT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:46:35 +00:00
Adriano Dal Pastro 9879b46688 refactor(strategie): tieni solo MR01 mean-reversion, squeeze -> waste
L'analisi out-of-sample fee-aware ha dimostrato che l'intera famiglia
squeeze-breakout (SQ01-04, MT01, ML01, AD01, CM01, PD01) non ha edge:
le accuratezze storiche 76-82% erano un artefatto di look-ahead (ingresso
a close[i-1] con direzione decisa da close[i]). Sotto ingresso onesto a
close[i] e fee reali tutte perdono, anche a fee zero.

- nuova MR01_bollinger_fade (mean-reversion): edge netto validato OOS,
  robusto su griglia parametri e fino a 0.20% fee RT. BTC 1h n50 k2.5: +201% OOS, DD 15%
- 9 strategie squeeze spostate in scripts/waste/
- strategy_loader + strategies.yml: solo MR01 (BTC/ETH 1h)
- signal_engine.train: validazione OOS (accuratezza test + signal precision)
- scripts/analysis/strategy_research.py: harness di ricerca fee-aware

NOTA: lo StrategyWorker va aggiornato per usare gli exit TP/SL passati in
metadata prima di tradare MR01 dal vivo (ora esce solo a hold_bars/stop fisso).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:22:11 +00:00
Adriano Dal Pastro ca88e62a11 feat(analysis): validazione out-of-sample fee-aware delle strategie
oos_validation.py: backtest OOS fedele al worker live (non-overlap, hold,
stop, fee, leva) su finestra held-out. Mostra che l'edge storico 76-79%
e' un artefatto di look-ahead (ingresso a close[i-1]) e che nessuna regola
di direzione onesta supera il lancio di moneta; le fee sono secondarie
(4/6 config perdono anche a fee zero).

intrabar_test.py: ingresso intra-barra su 5m vs close 15m a parita' di exit.
Lo "scatto" del breakout e' avverso (rientro immediato alla media), quindi
la granularita' piu' fine non recupera edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:57:15 +00:00
Adriano Dal Pastro 8fd2c16cac fix(live): MT01 usa trend 1h live da Cerbero, non dal parquet statico
Il paper trader restava a zero trade: il feed Cerbero era fermo a
mezzanotte (bug end_date lato cerbero-mcp, poi risolto) e MT01 leggeva
il trend 1h da un parquet statico, di fatto congelandolo (gap ~15h sul
bar corrente). Ora il runner fa fetch 1h live per le strategie MTF e lo
passa a generate_signals via il parametro df_1h (fallback al parquet se
assente). Aggiornati CLAUDE.md, README e diario 2026-05-28.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:30:26 +00:00
Adriano 31be1b43aa docs: aggiorna README e CLAUDE.md con strategie MT01/PD01/CM01/AD01
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:50:58 +02:00
Adriano bdcef09057 chore: untrack paper_trades runtime data + report per anno/mercato
- data/paper_trades/ rimosso dal tracking (dati runtime, gitignored)
- scripts/analysis/yearly_market_report.py: accuracy/trades/PnL per anno×mercato

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:46:24 +02:00
Adriano d39c75b103 feat(strategy4): PD01 82.5%/DD2.9%, AD01 81.2%, CM01 81.9% — tutte battono SQ02
Nuove strategie che battono SQ02 (79.7% acc, DD 6.5%):
- PD01 price-volume divergence: 82.5% acc, DD 2.9%, worst year 80%
- CM01 cross-market momentum: 81.9% acc, DD 2.7%
- AD01 adaptive squeeze threshold: 81.2% acc, DD 3.4%
- MT01 (già committato): 82.7% acc, DD 5.9%

Tutte testate su BTC e ETH, 15m e 1h, 9 anni, con fee 0.2% RT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:13:17 +02:00
Adriano f42fec9fac feat(strategy4): MT01 squeeze+MTF 82.7% acc — batte SQ02, 6 strategie scartate
Nuova strategia MT01: squeeze 15m + momentum EMA 1h
  BTC 15m: 82.7% acc, 503 trades, DD 5.9%, 9/9 anni, worst 72%
  ETH 15m: 81.2% acc, 404 trades, DD 2.9%, 9/9 anni, worst 73%

Strategie testate e scartate (waste W23-W28):
  IB01 inside bar (58.7%, no edge)
  DC01 donchian (48%, sotto random)
  SB01 retest (52%, no edge)
  MR01 mean reversion RSI (62.9%, DD 29%)
  VO01 volume spike (64.2%, DD 34%)
  HY01 squeeze+MR (64.6%, DD 14.5%)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:38:11 +02:00
Adriano 56bad4741e docs: aggiorna README e CLAUDE.md con struttura attuale e multi-strategy runner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:23:07 +02:00
Adriano b79c87e4af feat: multi-strategy paper trader — N strategie in parallelo su testnet
- src/live/multi_runner.py: orchestratore con fetch raggruppato per asset/tf
- src/live/strategy_worker.py: worker indipendente con stato persistente JSONL
- src/live/strategy_loader.py: import dinamico classi Strategy
- strategies.yml: config dichiarativa con defaults e override per strategia
- Docker: container unico, strategies.yml montato come volume read-only
- Supporta hot-add: aggiungi riga YAML + restart, storico intatto
- Ogni strategia: €1000 USDC virtuale, equity tracking, Telegram notify

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:12:18 +02:00
480 changed files with 60679 additions and 1456 deletions
+11
View File
@@ -0,0 +1,11 @@
Old/
data/
.venv/
.git/
logs/
__pycache__/
**/__pycache__/
*.pyc
.env
.env.mainnet
docs/
+36
View File
@@ -16,3 +16,39 @@ data/processed/
*.pt *.pt
*.pth *.pth
notebooks/.ipynb_checkpoints/ notebooks/.ipynb_checkpoints/
data/paper_trades/
data/portfolio_paper/
data/portfolio_paper_stats/
data/portfolios/
# watermark fondi del reconciler (stato runtime, contiene il balance)
data/funds_watch.json
# stato locale di tooling (non condiviso)
.claude/
.omc/
# dati regime (DVOL/funding/feature cache, rigenerabili)
data/regime/
_disp_scratch/
data/regime/dispersion_features.parquet
# storico catena opzioni importato da cerbero-bite (rigenerabile: options_fetcher.py)
data/options/
data/_reset_backup/
# game artifacts (log/json di scripts/games e gate)
data/games/
.env.mainnet
# archived data (mirrors top-level data/ ignores, which are top-level-anchored)
Old/data/
Old/**/__pycache__/
# run logs (rigenerabili dagli script)
logs/
# cache della ricerca trackE (rigenerabile)
.cache_trackE_*.npy
# feed backup pre-rebuild (binari rigenerabili, NON in git) + stato paper trader (runtime)
data/_feed_backup/
data/paper_trend/
+195 -61
View File
@@ -1,94 +1,228 @@
# PythagorasGoal — Istruzioni per agenti # PythagorasGoal — Istruzioni per agenti
## Panoramica ## Stato del progetto — v2.0.0 RESET (2026-06-19)
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. **LEGGERE PRIMA DI TUTTO.** Il progetto è stato resettato dopo aver scoperto che l'intera
libreria di strategie "validata OOS" (FADE, PAIRS, DIP01, TR01, ROT02, TSM01, XS01, SH01) era
**artefatto di uno storico contaminato** — print fantasma del feed Cerbero **testnet** + storico
**Binance/USDT**. Ri-testate sul feed reale, tutte perdono ogni anno (vedi
`docs/diary/2026-06-19-deribit-history.md`, il documento di fondazione).
Cosa è cambiato:
- Lo storico è stato **ricostruito da Deribit mainnet** e **certificato**. Universo affidabile =
**solo BTC/ETH** (tutti i TF). Gli alt sono esclusi (illiquidi/divergenti/non certificabili).
- Tutto il codice vecchio (strategie, stack live, ~100 script di ricerca/gate, dati non
certificati, 60+ diari) è **archiviato in `Old/`** (preservato in git, non cancellato).
- L'esecuzione è **DISABILITATA**, il conto mainnet è flat. **Non c'è trading live attivo.**
- Si riparte dalla ricerca di strategie NUOVE, su dati certi, con la metodologia qui sotto.
### Ricerca post-reset (2026-06-19) — esito
Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condiviso
`src/backtest/harness.py`). Sintesi in `docs/diary/2026-06-19-research-synthesis.md`.
- **TP01 Trend Portfolio — strategia DIFENSIVA robusta (non alpha)** —
`src/strategies/trend_portfolio.py`. TSMOM multi-orizzonte (1-3-6 mesi) vol-targeted, long-flat,
50/50 BTC+ETH. Config canonica **PORT LF1d** (**>=12h, 1d raccomandato**, vol-target 20%, leva cap 2x):
**FULL Sharpe ~1.30, maxDD ~14%; HOLD-OUT 2025-26 Sharpe ~0.31 / +3.5%** mentre il buy&hold 50/50
faceva 39%/DD60%. Verificata indipendentemente col gauntlet onesto (hold-out + cross-asset +
plateau + deflated-Sharpe 0.999): **regge**. **Valore = taglio del drawdown ~6× vs buy&hold**, NON
generazione di ritorno (CAGR ~16% vs ~48% del buy&hold sul toro).
⚠️ **LOOK-AHEAD (2026-06-19):** un ffill MIXED-TIMEFRAME su barre open-labeled gonfiava il 4h
(~1.60 → reale ~1.1). Il calcolo per-singolo-TF è leak-free, ma **NON scendere sotto le 12h**:
costi+overfitting dominano senza vantaggio (FULL Sh piatto ~1.3 da 12h a 4h; hold-out migliore a 1d).
Deploy/paper a **1d**. Diari `2026-06-19-tp01-verification.md` / `-tp01-lookahead-fix-lf.md`.
Paper trader: `scripts/live/paper_trend.py` (1d). Test: `tests/test_trend_portfolio.py`.
Ri-verifica: `scripts/analysis/{verify_tp01,stress_tp01,tp01_lowfreq}.py`.
- **XS01 Cross-Sectional Momentum (Hyperliquid) — DIVERSIFICATORE che migliora il portafoglio** —
`src/portfolio/sleeves.py:_xsec_returns`. Market-neutral su **19 alt liquidi major** Hyperliquid (1d,
dal 2024): ogni 10g long i 5 più forti / short i 5 più deboli, vol-target 20%. **Scorrelato a TP01
(~0.12).** Affinato (2026-06-19): **(a) blend di lookback [30,90]** (z-score cross-sectional mediato,
come il multi-orizzonte di TP01); **(b) gate di dispersione p30** (entra solo se la dispersione
cross-section del momentum supera il percentile espandente causale, altrimenti flat — XS è rumore in
regime compatto). Standalone FULL Sh **1.50** / HOLD 1.71 / DD 11%, plateau robusto (lookback, gate
p15-35). **Caveat:** storia ~2.5 anni; STAT-MODE (book a 19 gambe non eseguibile a 2k, serve ~20k) →
monitor forward. NB il gate concentra XS nei regimi dispersi (2025-26 = hold-out alta-dispersione).
Ricerca `scripts/portfolio/{xsec_research,xsec_blend,xsec_dispgate}.py`. Diari `2026-06-19-hyperliquid-xsec`
/ `-xsec-blend` / `-xsec-dispgate` / `-xsec-universe-expansion` / `-trend-multiasset`.
- **PORTAFOGLIO ATTIVO = TP01 (55%) + XS01 (25%) + VRP01 (20%)** (`src/portfolio/sleeves.active_sleeves`):
TP01+XS01 combinato **FULL Sharpe 1.55, HOLD-OUT 1.55, DD 4.4%**. Aggiunto **VRP01** (options
short-vol, sotto): TP01+VRP01 da solo fa FULL Sh 1.30→1.44 / HOLD 0.31→0.40 a peso 20% (3-way da
validare locale con dati HL). Report `scripts/portfolio/run_portfolio.py`. Sleeve a date d'inizio
diverse → outer-join con pesi rinormalizzati (TP01 da solo 2019-20, VRP dal 2021, blend pieno dal 2024).
- **VRP01 Options Short-Vol — DIVERSIFICATORE da FinanceOld/OptionsAgent** — `src/portfolio/sleeves._vrp_combo_returns`.
Put credit spread settimanale (vendi put -0.28, compra put -0.10) gated su IV-rank. Idee portate da
`../FinanceOld/OptionsAgent` (Bear Call Spread + gate d'ingresso). Migliora il lead VRP nudo
(options_vrp_lab): **(a) defined-risk** taglia la coda (worst-week -16.6%→-7.4%, DD 33%→14%);
**(b) gate IV-rank>0.30** = vendi vol solo ricca → ribalta HOLD-OUT da -0.25 a +0.28 (l'alpha è il
filtro di regime). Standalone **FULL Sh 1.10, HOLD 0.60, DD 12%**, positivo/piatto ogni anno (2022
crash incluso). Scorrelato a TP01 (~+0.01-0.07). **CAVEAT:** premio MODELLATO su DVOL ATM (skew non
esplicito), book a 1d, f di stress reale non catturato → LEAD robusto, non deploy pieno. Ricerca
`scripts/research/options_vrp_v2.py` (vs baseline `options_vrp_lab.py`). Test `tests/test_vrp_sleeve.py`.
Diario `2026-06-20-financeold-analysis-vrp-v2.md`.
- **Universo Hyperliquid: ESPANDERLO NON aiuta XS01** (provato): 52-asset / top-liquidità dinamico /
trend-multi-asset → tutti peggiori (small-cap/memecoin diluiscono il momentum relativo; il trend
multi-asset è ridondante con TP01, corr 0.74). I margini su XS sono nella STRUTTURA DEL SEGNALE
(blend + gate), non nel numero di asset. I **51** parquet certificati restano per ricerca futura.
⚠️ Il test "52-asset = negativo" era in parte inquinato dal backfill sintetico (AXS 83%, ALGO/SAND
37% di barre vol=0) poi rimosso — vedi correzione estrazione 2026-06-20 sotto; resta comunque vero
che il long-tail diluisce XS01, ma il numero netto post-fix è 51.
- **Lead OPZIONI VRP (income short-vol) — quantificato, NON deploy** — `scripts/research/options_vrp_*.py`.
Vendita put settimanali che incassa il volatility risk premium (IV>RV), scorrelato al trend (~0.07).
Premio prezzato BS su DVOL reale (`fetch_dvol.py`) + calibrato su quote REALI cerbero-bite mainnet
(`options_vrp_calibrate.py`): **f reale ≈ 1.0** (non 1.29) → Sharpe ~0.71, DD 33%, coda severa
(settimane 15..26% su LUNA/FTX). Diversificatore DEBOLE a premio reale, e short-vol da modello.
**Regola: niente short-vol da modello in deploy.** Rivalutare quando cerbero-bite cattura un crash
(per il f di stress reale). Diari `2026-06-19-options-vrp-lab` / `-eval-crypto-backtest-options`.
- **Edge deboli/scartati:** ML walk-forward BTC (Sh ~0.57), trend 1h L/S (~1.0), RV ETH/BTC (Sh 0.27,
regime-luck), calendar/seasonality (buy&hold travestito), volume/vol e momentum-reversal (negativi).
- **MORTO/confermato artefatto:** mean-reversion / fade (negativo anche a fee zero — la vecchia
libreria +201%/+1238% era contaminazione); trend 5m/15m (fee).
- **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso:
cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55.
- **Onestà sul target €50/giorno:** NON raggiungibile su 2000 in 1-2 anni (servono ~130k di
capitale o un DD da rovina). La leva non è la scorciatoia; la via è target-vol + capitale +
tempo. La strategia che *guadagna* esiste, ma a ~+€1.5/giorno su 2000.
Script ricerca: `scripts/research/track{A,B,C,D,E}_*.py` + `trackD_timing.py`.
## Obiettivo
Ricerca: riconoscimento pattern frattali per trading algoritmico su crypto. Target dichiarato
€50/giorno partendo da €1.000. **Onestà prima di tutto**: nessun numero va creduto finché non è
netto fee, out-of-sample, robusto su griglia, e su dati certificati + liquidi + eseguibili.
## Stack ## Stack
- **Linguaggio:** Python 3.11+ - **Linguaggio:** Python 3.11+**Package manager:** uv (`pyproject.toml`, `uv.lock`)
- **Package manager:** uv (dipendenze in `pyproject.toml`, lock in `uv.lock`) - **Dati:** Parquet in `data/raw/` (gitignored). Solo BTC/ETH (5m/15m/1h).
- **Dati:** Parquet in `data/raw/` (non committati, ~70 MB) - **Analisi/ML:** numpy, pandas, scipy, scikit-learn
- **ML:** scikit-learn (GradientBoosting), PyTorch (LSTM) - **Fonte dati storici:** Deribit mainnet via `ccxt` (pubblico, tokenless)
- **Analisi:** numpy, pandas, scipy
- **API dati:** Cerbero MCP su `cerbero-mcp.tielogic.xyz` (Deribit, Bybit, Hyperliquid), ccxt/Binance come fallback
## Struttura ## Struttura (post-reset)
``` ```
src/data/ → download e caricamento dati (downloader.py) src/data/downloader.py → load_data(asset, tf): legge i parquet certificati da data/raw/
src/strategies/base.py → Strategy (ABC), Signal, BacktestResult, YearlyStats
src/strategies/indicators.py → indicatori condivisi (ema, atr, keltner, ...)
src/strategies/trend_portfolio.py → TP01: strategia DIFENSIVA robusta (PORT LF1d, >=12h), causale
src/portfolio/ → PORTAFOGLIO DI STRATEGIE estensibile (Sleeve + StrategyPortfolio)
portfolio.py → combina N sleeve per peso su griglia giornaliera; metriche FULL/hold-out/anno
sleeves.py → REGISTRY sleeve attivi: TP01 (55%) + XS01 (25%) + VRP01 (20%). Aggiungere = una riga
src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py) src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py)
src/backtest/ → engine di backtesting (engine.py) src/backtest/engine.py → engine di backtesting riusabile
src/strategies/ → classe base Strategy ABC + indicatori condivisi src/backtest/harness.py → harness ONESTO (load BTC/ETH, backtest_signals no-leakage, OOS)
base.py Strategy, Signal, BacktestResult, YearlyStats src/version.py → APP_VERSION (legge il file VERSION)
indicators.py → keltner_ratio, detect_squeezes, ema, atr, rv, correlation scripts/research/ → ricerca: track{A-I}_*.py + options_vrp_*.py + fetch_dvol.py
scripts/strategies/ → strategie attive (SQ01-SQ04, ML01) scripts/portfolio/ → run_portfolio.py (report) + xsec_*.py (ricerca/affinamento XS01)
scripts/waste/ → strategie scartate (W01-W22 + REF originali) scripts/live/paper_trend.py → paper trader forward-only di TP01 (1d) (no esecuzione reale)
scripts/analysis/ → script di confronto e report scripts/analysis/ → SOLO i tool dati certificati:
docs/diary/ → diario di ricerca giornaliero rebuild_history.py → (ri)costruisce lo storico da Deribit mainnet (base 5m + resample)
data/raw/ → file .parquet OHLCV (gitignored) certify_feed.py → certifica il feed (integrità, coerenza resample, spike, cross-venue)
data/processed/ → modelli salvati (gitignored) audit_feed.py → audit per-barra vs riferimento esterno
multi_source_check.py → cross-check multi-venue (quale venue è "vero")
data/raw/ → btc/eth × {5m,15m,1h} (gitignored). UNICO dato attivo.
data/instruments_registry.json → registry strumenti (reference)
docs/diary/ → diario di ricerca (1 voce: il reset; aggiungere dopo ogni esperimento)
Old/ → ARCHIVIO: tutto il vecchio (strategie, live, ricerca, dati, diari)
VERSION → semver (2.0.0)
``` ```
## Comandi ## Comandi
```bash ```bash
uv sync # installa dipendenze uv sync # installa dipendenze
uv run python -m src.data.downloader # scarica dati storici uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # (ri)costruisci storico da Deribit mainnet
uv run python scripts/strategies/SQ02_squeeze_antifake_vol.py # miglior strategia robusta uv run python scripts/analysis/certify_feed.py # certifica i feed (locale + cross-venue)
uv run python scripts/strategies/ML01_squeeze_gbm.py # squeeze + ML (GBM) uv run python scripts/analysis/certify_feed.py --local # solo check locali (veloce)
uv run python scripts/research/trackD_trendport.py # backtest strategia vincente (full report)
uv run python scripts/research/trackD_timing.py # vincitrice su 15m/1h/4h/1d + PnL/DD/trade per anno
uv run python scripts/analysis/fetch_hyperliquid.py # fetch+certify universo Hyperliquid (Cerbero mainnet) -> data/raw/hl_*
uv run python scripts/portfolio/xsec_research.py # ricerca cross-sectional su Hyperliquid (XS01)
uv run python scripts/portfolio/run_portfolio.py # report del PORTAFOGLIO attivo (TP01+XS01)
uv run python scripts/live/paper_trend.py # avanza il paper trader TP01 (forward-only, 1d)
uv run pytest # test uv run pytest # test
``` ```
## Dati storici
Scaricati e salvati localmente in Parquet. Per rigenerarli:
```python ```python
from src.data.downloader import download_all, load_data from src.data.downloader import load_data
download_all() # scarica BTC + ETH su 5m/15m/1h dal 2018 df = load_data("BTC", "1h") # OK. load_data("SOL", ...) -> FileNotFoundError (guardrail: solo dati certi)
df = load_data("ETH", "15m") # carica un asset/timeframe
``` ```
Fonte primaria: Cerbero MCP (endpoint `/mcp-deribit/tools/get_historical`). ## IL DATO — fonte di verità (regola di prim'ordine)
Token observer: nel file `secrets/observer.token` del progetto CerberoSuite.
## Strategia vincente - **La verità è Deribit mainnet**, perché è dove (in futuro) eseguiamo. Cross-check multi-venue:
Deribit mainnet è a 0-1 bps dal consenso. **Binance NON è la verità** (è USDT, ~10 bps fuori, e
sotto depeg USDT fino al 3% off) → usare Binance/Coinbase SOLO come audit indipendente, mai come
ancora per "ripulire" i dati.
- **Aggiornare lo storico SOLO con `rebuild_history.py`** (ccxt Deribit mainnet, base 5m unica +
resample → coerenza interna garantita). **MAI** il vecchio downloader Cerbero (token testnet =
feed farlocco: è la causa della contaminazione).
- **Certificare sempre** dopo un rebuild con `certify_feed.py` (integrità OHLC, zero gap, coerenza
resample maxΔ≈0, spike = solo crash reali, accordo cross-venue per-anno vs Coinbase USD).
**Squeeze + ML ibrida** (script 13): ### Universo ricercabile certificato
- **BTC / ETH**: puliti (2-6 bps vs Coinbase USD su tutta la storia), liquidi (~0% barre flat a 1h),
storia lunga (2018/2019→oggi) → **ogni timeframe (5m/15m/1h)**. È l'unico dato in `data/raw`.
- **Alt Deribit (SOL/XRP/ADA/LTC/DOGE/BNB): FUORI.** Illiquidi (LTC 5m 82% barre flat, run ~3 giorni),
divergenti, o non certificabili. Archiviati in `Old/data/raw`.
- **Universo Hyperliquid (Cerbero MCP MAINNET): 19 alt liquidi a 1d, dal 2024** — BTC/ETH/SOL/BNB/XRP/
DOGE/AVAX/LINK/LTC/ADA/ARB/OP/SUI/APT/INJ/TIA/SEI/NEAR/AAVE. Certificati (`fetch_hyperliquid.py`):
flat 0%, cross-venue 4-9 bps vs Binance, >1% ≈0% → `data/raw/hl_*_1d.parquet`. **Caveat:** storia
nativa solo **~2.5 anni** (2024-2026; pre-2024 = backfill, vol 0). Abilita le strategie
CROSS-SECTIONAL (impossibili a 2 asset). NB: Cerbero col token TESTNET = farlocco; col token
**mainnet** (`.env.mainnet`) = reale, ma SEMPRE da certificare (cross-venue + liquidità).
⚠️ **CORREZIONE estrazione (2026-06-20):** il backfill NON è solo pre-2024 — cerbero MCP padda con
barre SINTETICHE (volume 0, prezzi copiati da Binance → matchano cross-venue e non sono flat) ogni
asset listato su HL **dopo** lo START. Il `flat`+cross-venue da soli non lo vedono: il rivelatore è
il **VOLUME**. `fetch_hyperliquid.py` ora (1) taglia il run iniziale a volume 0, (2) scarta chi resta
< 365g reali (es. **AXS 83% sintetico → fuori**), (3) gata i gap vol=0 interni. Universo certificato
= **51** (era 52). I **19 major di XS01 hanno 0 backfill → invariati** (strategia live non toccata).
Verificato direttamente su cerbero MCP. Diario `2026-06-20-cerbero-backfill-fix.md`.
1. Rileva squeeze di volatilità (Bollinger dentro Keltner) ## Metodologia obbligatoria per ogni nuova strategia
2. Al rilascio dello squeeze, estrai feature strutturali dalla finestra
3. GradientBoosting predice direzione con walk-forward training
4. Trade solo se modello ha confidenza ≥ 70%
Configurazione migliore: ETH 15m, BBw=14, squeeze threshold=0.8, breakout=3 barre, leva 3x, position 15%. 1. **Ingresso eseguibile**: direzione e prezzo decisi con dati **fino a `close[i]`**, mai
`close[i-1]` con direzione presa da `i`; mai entry sull'estremo (high/low) di una candela.
2. **Backtest NETTO** dopo fee realistiche Deribit (**0.10% RT** taker; maker ~0%) + leva.
3. **Out-of-sample** held-out + robustezza su **griglia parametri** (entrambi gli asset, tutte le
celle positive) + **sweep fee** (0.00-0.20% RT, margine ampio).
4. **Liquidità & plausibilità** (lezione v2.0.0): incrociare ogni edge con la liquidità reale del
book (quota di barre flat) e con la plausibilità del prezzo (cross-venue). Un edge full+OOS
robusto su un book fermo o su wick fantasma NON è un edge.
5. Strategia in `scripts/strategies/` (codice univoco), test in `tests/`, diario aggiornato.
Risultato backtestato: 76.9% accuracy, 118% annuo, 4.2% drawdown, €13.78/giorno da €1.000. ## Lezioni critiche (da NON ripetere — la storia di questo progetto)
## Strategie attive - **Feed contaminato → libreria fasulla (v2.0.0).** Print fantasma testnet + Binance/USDT hanno
prodotto edge inesistenti (+201%/+1238%/+16492% "OOS"). Tutti spariti sul feed reale. Lezione: il
| Codice | Nome | Tipo | Accuracy | Note | dato viene prima della strategia; certificare sempre.
|--------|------|------|----------|------| - **Look-ahead squeeze (storico).** L'intera famiglia squeeze-breakout aveva accuratezze 76-82% che
| SQ01 | Squeeze Base | Regole | 76.7% | Squeeze breakout puro, baseline | erano artefatto: decideva la direzione con la candela di breakout `i` ma entrava a `close[i-1]`.
| SQ02 | Antifake+Vol | Regole | 79.7% | **Miglior robusto** — 9 anni, Sharpe 5.01 | Con ingresso onesto: lancio di moneta. (Dettagli nei diari in `Old/`.)
| SQ03 | All Filters | Regole | 79.2% | Cross-asset + timing + long squeeze | - **Entry sugli estremi di candela.** Strategie che entrano a `close` quando `close` è all'estremo
| SQ04 | Ultimate | Regole | 81.6% | Max accuracy ma concentrato 2018 | del range (≤0.1% o ≥99.9%) gonfiano i ritorni in modo irrealistico (ETH 2024: +30.848% → +2.725%
| ML01 | Squeeze+GBM | ML | 78.8% | Walk-forward, €12/day, DD basso | rimuovendoli). Spesso è un artefatto di dato o di entry non eseguibile.
- **Mean-reversion vs breakout.** Sui dati storici l'unica direzione che mostrava edge era la
Tutte le strategie estendono `src.strategies.base.Strategy` con interfaccia comune: mean-reversion (i breakout rientrano) — MA anche quegli edge erano per lo più artefatto del feed:
`generate_signals() → backtest() → report()`. da riverificare da zero su dati certi.
- **Fee** = vincolo di prim'ordine. 0.10% RT baseline. Molte operazioni = morte per fee.
- **Leva**: testare 3x; 5x raddoppia il drawdown. I numeri a leva alta NON sono il caso base.
- **Data leakage** con rendimenti log: `returns[k] = log(close[k+1]/close[k])` usa `close[k+1]`. I
feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`. Verificare SEMPRE.
## Convenzioni ## Convenzioni
- Strategie in `scripts/strategies/` con codice univoco (SQ01, ML01, ...). - Strategie in `scripts/strategies/` con codice univoco; scartate documentate nel diario.
- Script scartati in `scripts/waste/` con prefisso W01-W22. - Diario in `docs/diary/YYYY-MM-DD.md`, aggiornato dopo ogni esperimento significativo.
- Diario in `docs/diary/YYYY-MM-DD.md`. Aggiornare dopo ogni esperimento significativo. - **Nessun segreto nei commit** (token/chiavi). `.env` e `.env.mainnet` sono gitignored.
- Nessun dato sensibile nei commit (token, chiavi API). Usare `.gitignore`. - Versionamento: `VERSION` (semver) + `scripts/bump_version.py`. `src/version.py` lo legge.
- 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 ## Archivio `Old/`
- **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]`. Tutto il lavoro pre-reset (preservato in git per consultazione storica): strategie
- **Fee:** sempre 0.1% per lato (0.2% round-trip). Includere nel backtest. (`Old/scripts/strategies`), stack live e portafogli (`Old/src/live`, `Old/src/portfolio`,
- **Leva:** testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown. `Old/scripts/portfolios`), ricerca/gate (`Old/scripts/analysis`), dati non certificati
(`Old/data`), 60+ diari (`Old/docs/diary`), test (`Old/tests`). Consultabile come riferimento
("come facevamo X"), ma **nessun edge lì dentro è fidato** finché non è ri-validato su dati certi.
```
+4 -7
View File
@@ -1,14 +1,11 @@
FROM python:3.11-slim FROM python:3.11-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app WORKDIR /app
COPY pyproject.toml uv.lock ./ COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev RUN uv sync --frozen --no-dev
COPY src/ src/ COPY src/ src/
COPY scripts/ scripts/
COPY VERSION ./
VOLUME /app/data VOLUME /app/data
# Monitor PAPER del portafoglio attivo (TP01+XS01). Esecuzione REALE disabilitata.
CMD ["uv", "run", "python", "-m", "src.live.paper_trader"] CMD ["uv", "run", "python", "-m", "src.live.dashboard", "--port", "8787"]
+37
View File
@@ -292,3 +292,40 @@ curl -X POST http://localhost:9000/mcp-bybit/tools/get_ticker \
Per lo schema completo dei body di richiesta e risposta: Per lo schema completo dei body di richiesta e risposta:
<http://localhost:9000/apidocs>. <http://localhost:9000/apidocs>.
---
## 15. Discovery strumenti — schemi `get_instruments` / `get_markets` / `get_historical`
Schemi dei body verificati sull'OpenAPI live (usati da `src/data/instruments.py`).
### Lista strumenti
| Exchange | Tool | Body | Risposta (campi utili) |
|---|---|---|---|
| Deribit | `get_instruments` | `{currency:"any", kind:"future", offset:int, limit:100}` (paginato, `has_more`) | `instruments[].name` (es. `BTC-PERPETUAL`, `SOL_USDC-PERPETUAL`), `expiry`, `tick_size` |
| Bybit | `get_instruments` | `{category:"linear", symbol?}` | `instruments[]`: `symbol`, `status`, `base_coin`, `quote_coin` |
| Hyperliquid | `get_markets` | `{}` | lista `{asset, mark_price, funding_rate, open_interest, volume_24h, max_leverage}` |
### Storico OHLCV (`get_historical`, chiave `candles` uniforme `{timestamp(ms),open,high,low,close,volume}`)
| Exchange | Body |
|---|---|
| Deribit | `{instrument, start_date:"YYYY-MM-DD", end_date, resolution}` — resolution `1/5/15/60/1D` |
| Bybit | `{symbol, category:"linear", interval:"1/5/15/60/D", start:int_ms, end:int_ms, limit}` |
| Hyperliquid | `{asset|instrument, start_date, end_date, resolution:"1m/5m/15m/1h/1d", limit}` |
### Simboli Deribit
- BTC/ETH → perpetui **inverse**: `BTC-PERPETUAL`, `ETH-PERPETUAL`
- Altcoin → perpetui **lineari USDC**: `<COIN>_USDC-PERPETUAL` (es. `SOL_USDC-PERPETUAL`)
- Trappola: `LTC-PERPETUAL`/`ADA-PERPETUAL` non esistono; `SOL-PERPETUAL` esiste ma è un contratto sbagliato (prezzo ~9.6 vs SOL reale ~82).
### Validazione (lato progetto)
`src/data/instruments.py` valida ogni strumento sui dati storici realmente
raccoglibili — esistenza, congruenza OHLC, not-flat, liquidità (volume daily) e
**congruenza prezzo cross-exchange** (scostamento dalla mediana del base-coin ≤5%).
Solo gli exchange con feed affidabile sono inclusi: **Deribit** e **Hyperliquid**
(esclusi Alpaca/stocks e **Bybit**, il cui feed testnet è farlocco). Output in
`data/instruments_registry.json`; il downloader scarica **solo** strumenti validati.
> **Testnet.** Il token osservatore punta a testnet (`"testnet": true` nei ticker):
> i prezzi possono divergere dal mainnet. La congruenza cross-exchange via mediana
> è il filtro che scarta i feed incongrui prima di usarli per backtest/trading.
+18
View File
@@ -0,0 +1,18 @@
FROM python:3.11-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY src/ src/
COPY scripts/ scripts/
COPY strategies.yml portfolios.yml VERSION ./
VOLUME /app/data
# Default: paper trader multi-strategia. Il servizio "portfolio" in docker-compose
# sovrascrive il command per il runner a portafoglio (src.portfolio.runner).
CMD ["uv", "run", "python", "-m", "src.live.multi_runner"]
+212
View File
@@ -0,0 +1,212 @@
# Strategia di Grid Trading — Versione Corretta
> Documento di specifica della strategia. Descrive *cosa* deve fare il bot e *perché*,
> non l'implementazione. È il riferimento da cui partire per riscrivere il codice in
> modo sicuro e testabile.
---
## 1. Obiettivo
Estrarre profitto dalla **volatilità di un asset all'interno di un intervallo di prezzo
(range)**, comprando automaticamente quando il prezzo scende e vendendo quando risale,
secondo livelli predefiniti (la "griglia").
La griglia **non prevede** la direzione del mercato: monetizza le oscillazioni. Funziona
quando il prezzo oscilla; perde quando il prezzo prende un trend deciso. Tutta la
progettazione che segue serve a massimizzare il primo caso e a limitare i danni nel
secondo.
---
## 2. Principi corretti (cosa cambia rispetto al bot originale)
| Aspetto | Bot originale (sbagliato) | Versione corretta |
|---|---|---|
| Asset | Shitcoin illiquida (LAND) | Coppia liquida (es. ETH/USDT, BNB/USDT) |
| Passo griglia | Assoluto in USDT (`GRID_STEP=3`) | **Percentuale** sul prezzo |
| Livelli | Mobili, inseguono il prezzo | **Fissi** dentro un range definito |
| Protezione perdite | Nessuna | **Stop-loss** sotto il range + **take-profit** sopra |
| Slippage | `amountOutMin = 0` (nessuna) | Calcolato da `getAmountsOut` tolleranza |
| Break-even fee | Ignorato | Passo griglia **> costo round-trip** |
| Capitale | Tutto, senza limiti | Allocazione fissa, suddivisa per livello |
| Chiave privata | In chiaro nel `.env` | Keystore cifrato o input a runtime |
| Validazione | Nessuna | **Backtest** + **testnet** prima del capitale reale |
---
## 3. Definizione della griglia
### 3.1 Parametri di ingresso
| Parametro | Significato | Esempio |
|---|---|---|
| `PAIR` | Coppia da tradare (base/quote) | `ETH/USDT` |
| `RANGE_LOW` | Estremo inferiore del range | `2800` |
| `RANGE_HIGH` | Estremo superiore del range | `3400` |
| `GRID_LEVELS` | Numero di livelli nella griglia | `12` |
| `CAPITAL_QUOTE` | Capitale totale in quote (USDT) | `1200` |
| `STOP_LOSS` | Prezzo sotto cui il bot chiude tutto e si ferma | `2650` |
| `TAKE_PROFIT` | Prezzo sopra cui il bot chiude tutto e si ferma | `3550` |
| `SLIPPAGE_BPS` | Tolleranza slippage in basis point (50 = 0,5%) | `50` |
| `FEE_BPS` | Fee del DEX in basis point (PancakeSwap = 25) | `25` |
### 3.2 Costruzione dei livelli (griglia geometrica)
I livelli vanno spaziati in **percentuale**, non in valore assoluto. Una griglia
geometrica mantiene lo stesso rendimento percentuale per ogni gradino, indipendentemente
dal prezzo.
```
ratio = (RANGE_HIGH / RANGE_LOW) ^ (1 / GRID_LEVELS)
livello[i] = RANGE_LOW * ratio ^ i per i = 0 .. GRID_LEVELS
```
Esempio (`RANGE_LOW=2800`, `RANGE_HIGH=3400`, `GRID_LEVELS=12`):
`ratio ≈ 1,0163` → passo di circa **1,63% per gradino**.
### 3.3 Capitale per livello
```
quote_per_livello = CAPITAL_QUOTE / GRID_LEVELS
```
Ogni livello di acquisto impegna `quote_per_livello`. Il capitale è suddiviso in anticipo:
il bot **non** può comprare più di quanto allocato, e non scende mai sotto zero.
---
## 4. Vincolo di break-even (regola anti-fee)
Una griglia con passi troppo fitti perde: le fee di ogni round-trip (compra + vendi) si
mangiano il profitto. **Il passo percentuale della griglia deve superare il costo totale
di un round-trip.**
```
costo_round_trip ≈ 2 * (FEE_BPS + SLIPPAGE_BPS) / 10000 (in frazione)
passo_griglia = ratio - 1
VINCOLO: passo_griglia > costo_round_trip * margine_sicurezza (margine ≥ 1,5)
```
Esempio con `FEE_BPS=25`, `SLIPPAGE_BPS=50`:
`costo_round_trip ≈ 2 * (25+50)/10000 = 1,5%`. Con margine 1,5 → il passo deve essere
**≥ 2,25%**. Se la griglia geometrica dà 1,63%, **è troppo fitta**: vanno ridotti i
`GRID_LEVELS` o allargato il range finché il vincolo è rispettato. Il bot deve rifiutarsi
di partire se il vincolo non è soddisfatto.
---
## 5. Logica operativa
### 5.1 Inizializzazione
1. Validare i parametri: `RANGE_LOW < prezzo_attuale < RANGE_HIGH`, vincolo di break-even
rispettato, capitale disponibile sul wallet.
2. Verificare la coppia: liquidità sufficiente, contratto non honeypot (controllo
obbligatorio, non opzionale), token con decimali noti.
3. Costruire i livelli (§3.2) e marcare ognuno come `attivo`/`riempito`.
4. Allocare il capitale (§3.3).
### 5.2 Ciclo principale
A ogni tick (es. ogni nuovo blocco, o ogni N secondi):
```
prezzo = prezzo_corrente(PAIR)
# --- guardie di uscita: hanno priorità su tutto ---
SE prezzo <= STOP_LOSS:
vendi_tutta_la_posizione() # con slippage protetto
ferma il bot, log "STOP-LOSS"
SE prezzo >= TAKE_PROFIT:
vendi_tutta_la_posizione()
ferma il bot, log "TAKE-PROFIT"
# --- logica griglia ---
PER ogni livello L:
SE prezzo attraversa L verso il basso E L non è ancora riempito:
compra quote_per_livello # con amountOutMin protetto
marca L come riempito
SE prezzo attraversa L verso l'alto E il livello sotto è riempito:
vendi la quantità di quel livello # con amountOutMin protetto
marca quel livello come libero
```
I livelli sono **fissi** (calcolati una volta), non inseguono il prezzo. Questo rende il
comportamento prevedibile e backtestabile.
### 5.3 Calcolo `amountOutMin` (protezione slippage — obbligatoria)
Mai passare `0`. Prima di ogni swap:
```
atteso = router.getAmountsOut(amountIn, path)[ultimo]
amountOutMin = atteso * (10000 - SLIPPAGE_BPS) / 10000
```
Se la transazione non rientra nella tolleranza, deve **fallire** (revert), non eseguire a
qualsiasi prezzo.
---
## 6. Gestione del rischio
1. **Stop-loss obbligatorio** sotto `RANGE_LOW`. È la differenza tra "strategia" e
"gambling": senza stop-loss un trend ribassista svuota il wallet.
2. **Take-profit** sopra `RANGE_HIGH` per chiudere quando il prezzo esce dal range al
rialzo (la griglia avrebbe già venduto tutto; il take-profit evita di restare esposti).
3. **Capitale segregato**: usare un wallet dedicato, con solo il capitale destinato alla
strategia. Mai il wallet principale.
4. **Limite di gas e prezzo gas** ragionevoli, ricalcolati dinamicamente (no valori fissi
obsoleti).
5. **Kill-switch manuale**: comando per fermare il bot e liquidare in qualsiasi momento.
6. **Idempotenza/recupero**: se il bot si riavvia, deve ricostruire lo stato dei livelli
(riempiti/liberi) dal saldo on-chain, non ripartire da zero.
---
## 7. Validazione prima del capitale reale
Nessun fondo reale prima di aver superato, in ordine:
1. **Backtest** su dati storici della coppia (almeno alcuni mesi, includendo sia fasi
laterali sia un trend marcato), misurando: PnL netto **dopo** fee e slippage,
max drawdown, numero di trade, comportamento allo stop-loss.
2. **Paper trading / simulazione** in tempo reale, senza eseguire ordini veri.
3. **Testnet** (BSC testnet) con la stessa logica e router di test, per verificare
l'esecuzione on-chain end-to-end.
4. **Mainnet con capitale minimo** (es. l'equivalente di pochi euro) per la prima
settimana, poi scalare solo se i risultati combaciano col backtest.
---
## 8. Quando NON usare questa strategia
- Asset illiquido o a rischio rug-pull/honeypot.
- Mercato in trend forte e prolungato (la griglia perde: lo stop-loss limita il danno ma
non genera profitto).
- Passo griglia che non rispetta il vincolo di break-even (§4).
- Capitale che non puoi permetterti di perdere.
---
## 9. Parametri di esempio (configurazione di partenza prudente)
```
PAIR = BNB/USDT # coppia liquida su PancakeSwap
RANGE_LOW = 580
RANGE_HIGH = 720
GRID_LEVELS = 8 # passo ≈ 2,7% > break-even
CAPITAL_QUOTE = 400 # USDT, su wallet dedicato
STOP_LOSS = 545 # ~6% sotto RANGE_LOW
TAKE_PROFIT = 760 # ~5,5% sopra RANGE_HIGH
SLIPPAGE_BPS = 50 # 0,5%
FEE_BPS = 25 # PancakeSwap v2
```
Verifica break-even: passo ≈ 2,7% > `1,5 × (2×0,75%) = 2,25%`
---
*Questo documento descrive la strategia. L'implementazione (ethers v6, gestione sicura
della chiave, calcolo slippage, stato persistente, backtester) va sviluppata a parte,
con test, e validata secondo §7 prima di qualsiasi capitale reale.*
+49
View File
@@ -0,0 +1,49 @@
services:
portfolio:
build: .
container_name: pythagoras-portfolio
restart: unless-stopped
command: ["uv", "run", "python", "-m", "src.portfolio.runner"]
volumes:
- ./data:/app/data
- ./portfolios.yml:/app/portfolios.yml:ro
env_file:
- .env
- .env.mainnet # token MAINNET (soldi veri); prevale su .env (vedi micro-test plan)
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "python", "-c", "import os; assert any(f.endswith('status.json') for r,d,fs in os.walk('/app/data/portfolios') for f in fs)"]
interval: 120s
timeout: 10s
retries: 3
labels:
- com.centurylinklabs.watchtower.enable=false
# Dashboard web read-only (stato live, PnL totale/per-strategia, grafico equity,
# trade attivi+chiusi). Stessa immagine del runner, monta gli stessi data/ in sola
# lettura logica (legge equity.jsonl + status/trades dei worker). Porta 8787.
# NB: nessuna auth -> non esporre su internet pubblico, solo rete interna/VPN.
dashboard:
build: .
container_name: pythagoras-dashboard
restart: unless-stopped
command: ["uv", "run", "python", "-m", "src.live.dashboard", "--port", "8787"]
ports:
- "8787:8787"
volumes:
- ./data:/app/data
- ./portfolios.yml:/app/portfolios.yml:ro
- ./docs/report:/app/docs/report:ro # scheda strategie_attive.html (modal "scheda dettagliata")
env_file:
- .env
- .env.mainnet # token MAINNET (soldi veri); prevale su .env (vedi micro-test plan)
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8787/api/state', timeout=5)"]
interval: 120s
timeout: 10s
retries: 3
labels:
- com.centurylinklabs.watchtower.enable=false
+128
View File
@@ -0,0 +1,128 @@
# TODO — prossimi passi
> Raccolta dei follow-up aperti. Aggiornato 2026-06-11 (XS01 dispersion-gate live v1.1.20;
> real-truth ledger attivo; reset portafoglio 2026-06-10).
## Stato esecuzione per sleeve (REALE vs SIMULATO)
**REALE (shadow su Deribit testnet, REAL-TRUTH: il ledger segue i fill reali) — 15 sleeve:**
- 6 fade (MR01/MR02/MR07 × BTC/ETH) + DIP01_BTC → single-leg, v1.0.3+
- 6 pairs PR01 (ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL + ETH/BTC 15m) → 2 gambe,
v1.1.12 (2026-06-08); il 15m dal v1.1.16 (2026-06-09)
- SH01 × BTC/ETH → single-leg, exit a orizzonte (no TP/SL), v1.1.13 (2026-06-08)
**SIMULATO (paper, NON esegue reale) — 4 book multi-asset, fuori dal capitale-pool:**
- TR01 / ROT02 / TSM01 / XS01 → bloccati dal CAPITALE (rumore arrotondamento)
## Esecuzione reale — pezzi mancanti
- [x] ~~Executor a 2 gambe per i PAIRS~~ — FATTO + ATTIVO (v1.1.12, 2026-06-08).
- [x] ~~SH01 in reale~~ — FATTO (v1.1.13, 2026-06-08): l'infrastruttura no-TP esisteva già
(_place_real_tp no-op senza TP, _real_close chiude tutto market reduce-only a orizzonte).
Bastava accettare kind 'ml' in _exec_for + SH01 in execution.sleeves. Disaster-bracket
on-book = unica protezione di coda. Test SH01 open/close reale OK.
- [ ] **Multi-asset in reale** (SIMULATO: TR01/ROT02/TSM01/XS01) — **SBLOCCATO DA CAPITALE ≥ ~€20k**.
Strumenti USDC tutti esistenti (BNB/DOGE/XRP inclusi), ma a €2k il rumore di arrotondamento
per-asset è 20-30% (DOGE/XRP/SOL step grandi in $, esposizione frammentata su 5-8 asset) →
l'esecuzione reale misurerebbe il rumore dei lotti, non la strategia. Serve un
`BasketExecutionClient` (ribilancio delta multi-strumento, long-only, netting con fade+pairs
sui simboli condivisi). Beneficio limitato (~19% del portafoglio, diversificatori spesso
flat) → bassa priorità anche a capitale alto. Analisi: chat 2026-06-08.
## Capitale (decisione utente, non codice)
- [ ] **Valutare aumento capitale a €5.000-10.000** per togliere il rumore di arrotondamento.
A €2.000 il rumore BTC per trade è ~2.4% (morde); soglie: ~€4.800 (<1%), ~€9.500 (<0.5%,
"punto dolce"), ~€19.000 (trascurabile). Le commissioni invece sono % e neutre al capitale.
Col real-truth ledger il rumore di arrotondamento entra DIRETTAMENTE nell'equity → più
capitale = ledger più fedele alla strategia. Riguarda i 15 sleeve che eseguono reale.
## Follow-up dal code-review (bassa priorità, non urgenti)
- [x] ~~forming-bar su ROT02/TSM01~~ — GIÀ FIXATO (v1.1.10, 2026-06-08): `_panel` condiviso
scarta la barra in formazione via `last_bar_is_forming`. Item rimasto stantio nel TODO.
- [ ] **ancora bfill di `_daily_equity`** — QUANTIFICATO 2026-06-11 (`daily_equity_bfill_impact.py`):
**NON materiale, lasciare documentato**. PORT06 OOS invariato per costruzione (il bias vive
in testa alla serie, pre-SPLIT; ΔSharpe +0.001, ΔDD 0.000); FULL DD leggermente OTTIMISTICO
(3.46→3.67 col fix: il primo trade DIP01 2021, 3.75%, è nascosto dal bfill). Nessun verdetto
di gate a rischio (bias identico nei due bracci A-vs-B, si cancella). Unica eccezione OOS:
TSM01 (primo punto equity post-SPLIT, 0.014 Sh). Correggere SOLO in un eventuale refactor
del builder daily, OVUNQUE in un colpo (~12 file di gate replicano la convenzione) e
ri-baselinando i canonici nello stesso commit. CAVEAT per gate futuri: finestre IDX che
partono a metà storia amplificano il bug → usare lì la convenzione carry-forward corretta.
- [x] ~~convenzione TR01 worker vs reference~~ — ERA UN BUG, FIXATO (2026-06-11): `mean(rets)`
sui soli asset in posizione sovrappesava N/k a paniere parziale (con 1 long: 0.45 del
capitale invece di 0.09). Fix: `sum(rets)/len(universe)` (equal-weight 1/N canonico).
Replay post-fix: +32% vs reference +42% (residuo = convenzione capitale-unico vs
media-equity, dichiarata). Diario `2026-06-11-stability-sweep.md`.
- [x] ~~engine duplicato nei 3 gate~~ — FATTO (2026-06-11): fattorizzato in
`scripts/analysis/_port06_gate_common.py` (`build_trades_variant` versione trendmax =
superset con hurst_mask/trend_max parametrici, `equity_from_trades`, `port_metrics`,
`dd`); i 3 gate importano da lì. Nessuna drift di matematica trovata fra le copie
(solo firme/docstring). Output dei 3 gate verificato BYTE-IDENTICO prima/dopo.
`dip_trades` (DIP01) NON è una copia ma un sibling deliberato (long-only, orig_gap,
j clampato) → resta nel suo script, documentato nel modulo comune.
- [ ] **epoche hardcoded in `hourly_report.lossguard_section`** (LOSSGUARD_SINCE, TRENDSWAP_SINCE):
ogni nuova epoca-filtro richiede di editare la funzione. Derivarle da deploy history/config.
## Code-review 2026-06-11 sera (8a2b065..) — finding DEFERITI (i confermati critici sono fixati in v1.1.26)
- [ ] **RESTING reduce-only esposti al netting** (TP limit + disaster-SL): se un worker opposto
apre DOPO il piazzamento, il resting può fillare parziale o essere respinto — il fallback
netting copre solo i close sincroni. È il pezzo "position manager / sotto-conti" già noto;
lo scenario peggiore è il disaster-SL cappato proprio nel crash per cui esiste.
- [ ] **Lifecycle `orphan_legs`**: append-only — un orfano risolto a mano (o da reset_flatten)
resta nello status e MASCHERA per compensazione un drift vero futuro (falso negativo del
reconciler). Serve un comando di risoluzione (`--resolve-orphan`) e/o reset_flatten che
azzeri anche lo stato reale nei status.json. Idem: il PnL della chiusura manuale di un
orfano non viene mai bookato in real_capital (diagnostica shadow divergente).
- [ ] **TP_PHANTOM residuo**: `resting_fills` guarda solo le ultime 100 righe di trade-history
per strumento — su conto molto attivo un fill TP reale può scivolare fuori finestra →
falso phantom persistente (sim resta in posizione, reale flat). Mitigato dal quantize
conservativo (v1.1.26); fix vero = endpoint order-state in cerbero-mcp.
- [ ] **Validazione feed a monte** (altitudine): TP_PHANTOM copre solo i tocchi TP dei single-leg;
le ENTRY spike-driven, lo SL close-confirm su close spike e lo z dei pairs restano esposti
ai wick fantasma del feed testnet. Un validatore barre nel data layer coprirebbe tutti i
consumer con un meccanismo solo (su mainnet il fenomeno non esiste: priorità bassa finché
si resta su testnet, gli eventi TP_PHANTOM/NET_CLOSE ne misurano la frequenza).
- [ ] **Contratto dello schema status.json**: reconcile (`src/live/books.py`), hourly_report e i
worker condividono lo schema per convenzione implicita — books.py è ora la fonte unica per
i campi `real_*`, ma un helper `worker.real_book()` usato da _save e dai reader chiuderebbe
la classe di bug. Pulizia: `_tp_hit` helper per i 4 siti di tocco TP duplicati; port_metrics
ri-biforcato in xs01_tranche_gate/pairs30m_gate (importare da _port06_gate_common);
splits3/metriche duplicate nei games engine (estrarre in scripts/games/engine.py).
## Ricerca dispersion/correlation (2026-06-08, 165 agenti) — follow-up opzionale
- [x] ~~Gate PORT06 di `index_comp_disp` W=168~~ — FATTO (2026-06-08): PROMOSSO MARGINALE.
Decorrela bene (corr 0.06 col MASTER) ma OOS PIATTO (Sharpe 0.01). **Documentato e
rimandato** (non deployato): gate in `dispersion_edges/gate_index_comp_disp.py`,
riprendere solo se si costruisce una famiglia DISP più ampia. Diario aggiornato.
## Monitoraggio (osservare, non agire subito)
- [ ] **Churn da spike-print testnet su ETH (2026-06-11)**: il feed testnet stampa wick
anomali sulla barra 1h ETH che (a) generano segnali short MR01/MR07 (lo spike È lo
z-score estremo) e (b) "toccano" il TP intrabar della stessa barra → il sim booka
+4% fantasma a bars_held=0, il reale apre+chiude pagando solo fee/spread (~0.17€
a giro, 14 giri oggi ≈ 2.3€). Il real-truth ledger contabilizza GIUSTO (per questo
esiste) e il report orario ora conta i win dal flag reale. MITIGATO in v1.1.23:
gate `TP_PHANTOM` (il tocco TP deve essere confermato dal fill del resting sul book
reale, o dal prezzo oltre il livello) → niente più chiusure a mercato su wick
fantasma. Resta l'ENTRY spike-driven (il segnale stesso nasce dal wick): NON
filtrarlo nei segnali (= fit su artefatto testnet); osservare la frequenza dei log
TP_PHANTOM — se cresce, valutare un gate di QUALITÀ FEED nel data layer.
- [ ] **FADE in coda storica (2026-06-11)**: il rolling 120g equal-weight delle 6 fade è al
**2° percentile** della propria storia (1.0% vs p5 +0.4%); il PORT06 complessivo resta
in variazione normale (19-28° pct). NESSUN ritocco parametri (= fit sul regime corrente);
osservare se rientra. Follow-up tecnico: alert di drift per-FAMIGLIA nel `hourly_report`
(precomputare la distribuzione storica dei rolling-return di famiglia dal backtest
canonico → confrontare il rolling live; notifica sotto p5).
- [ ] **PnL live post-swap trend** (epoca TREND in `hourly_report`): verdetto stop-rate fade a n>=30.
- [ ] **SH01 post-bootstrap**: il trade-rate live deve scendere da ~25% a ~10% delle barre
(selettività della soglia ripristinata). Controllare nel report orario.
- [ ] **MR02_ETH**: ultima posizione aperta SENZA disaster-SL (aperta pre-v1.1.4). Si chiuderà
normalmente; le prossime aperture avranno il bracket.
- [ ] **parquet fresco per SH01 bootstrap**: oggi al 2026-05-28, il feed copre 365g → margine ~11
mesi. Rilanciare `download_all()` periodicamente (WARN automatico se il gap supera il lookback).
@@ -0,0 +1,136 @@
# 2026-05-28 — Ricerca onesta di nuove strategie (post-squeeze)
## Contesto e mandato
Dopo aver scoperto che l'intera famiglia squeeze-breakout era un artefatto di
look-ahead (accuratezze 76-82% svanite sotto ingresso eseguibile), il mandato è
stato: trovare in modo **onesto** almeno 3 strategie attendibili, testate su ~8
anni e su più criptovalute, con le fee incluse nella valutazione, partendo da
€1.000 con l'obiettivo (aspirazionale) di €50/giorno. Esplorare anche idee fuori
dal comune e l'uso combinato di più crypto e timeframe.
## Metodologia (engine onesto)
Tutto il lavoro usa un unico engine condiviso (`scripts/analysis/honest_lab.py`)
con questi vincoli anti-illusione:
1. **Ingresso eseguibile.** Ogni segnale alla barra `i` usa solo dati fino a
`close[i]` e l'ingresso avviene a `close[i]` (ciò che il worker live vede e
può eseguire). Disponibile anche l'ingresso più conservativo a `open[i+1]`.
2. **Uscita realistica.** Take-profit / stop-loss valutati intrabar su `high`/`low`,
in modo conservativo (SL prima del TP nello stesso bar), più time-limit.
Una posizione per volta (non-overlap), capitale composto.
3. **Fee di prim'ordine.** Tutto è NETTO dopo fee round-trip realistiche Deribit
(0.10% RT) moltiplicate per la leva (3x), con sweep fino a 0.20% RT.
4. **Validazione severa.** FULL + out-of-sample (ultimo 30%) + conteggio anni
positivi + sweep fee + griglia parametri + test su **8 crypto**
(BTC, ETH, SOL, BNB, XRP, LTC, DOGE, ADA, 2018→2026).
## Lezione madre
**Shortare le crypto perde OOS in modo sistematico in questo campione.** Sia la
mean-reversion sul lato short, sia il momentum short, crollano fuori campione: il
periodo 2018-2026 è net-bull e ogni rialzo "estremo" tende a continuare invece di
rientrare. Tutte le configurazioni che sopravvivono oneste sono **long-biased**.
È un fatto da dichiarare: parte della performance OOS è correlata al beta rialzista
delle crypto. Le strategie aggiungono *timing* sopra quel beta, non lo eliminano.
## Le 3 strategie selezionate (meccanismi distinti)
| Codice | Meccanismo | TF | Asset robusti | OOS netto (fee 0.10% RT) | DD | Anni+ |
|--------|-----------|----|---------------|--------------------------|----|-------|
| **DIP01** | Dip-buy z-score reversion (long-only) | 1h | BTC, ETH, SOL | BTC +59% · ETH +224% · SOL +13% | 23-55% | 6-7/9 |
| **TR01** | EMA 20/100 trend-following (long-only) | 4h | BNB, BTC, DOGE, SOL, XRP | BTC +27% · DOGE +53% · XRP +29% | 29-53% | 4-6/8 |
| **ROT01** | Rotazione cross-sectional momentum sul paniere | 1d | intero paniere (8) | **+44%** | 53% | 5/7 |
Dettagli e riproducibilità: `scripts/analysis/honest_final.py` (tabella di
validazione unica), `honest_rotation.py`, `honest_trend.py`, `honest_candidates.py`,
`honest_diag.py`/`honest_diag2.py` (diagnostica long/short e filtro trend).
### DIP01 — compra le capitolazioni
Long-only: entra quando lo z-score del prezzo rispetto alla media a 50 barre scende
sotto 2.5 (capitolazione), prende profitto al rientro verso la media, SL a 2.5·ATR.
È la versione robusta e onesta della famiglia mean-reversion: regge lo sweep fee
fino a 0.20% RT (BTC +45% OOS anche a 0.20%). Funziona sui major (BTC/ETH/SOL); sugli
alt molto parabolici (DOGE/BNB) un dip fisso continua a scendere e non ha edge.
### TR01 — cavalca i trend
Long-only: in posizione quando EMA(20) > EMA(100) sul 4h, altrimenti cash. Poche
operazioni (≈200 flip in 8 anni) ⇒ le fee non sono letali. È **complementare** a
DIP01: guadagna nei regimi di trend, dove la reversione soffre.
### ROT01 — la più affidabile e "fuori dal comune"
Una sola strategia che usa **tutto il paniere** in un unico book: ogni giorno ordina
le 8 crypto per momentum (rendimento a 60 giorni) e alloca a parti uguali alle 2
migliori con momentum positivo, il resto in cash. Cattura la *dispersione* tra
crypto (gli alt forti corrono molto più di BTC nei bull) senza shortare nulla.
È **param-insensitive** (tutte le combinazioni lookback/top-k sono positive OOS) e
regge le fee fino a 0.20% RT (+41% OOS). Risponde direttamente alla richiesta di
combinare più crypto e un timeframe diverso in un'unica strategia. Per-anno:
2020 +33% · 2021 +181% · 2022 29% (bear) · 2023 +43% · 2024 +59% · 2025 +6% · 2026 10% (YTD).
## Diversificazione
I tre meccanismi coprono regimi diversi e in larga misura anti-correlati:
reversione (DIP01), momentum di singolo asset (TR01), forza relativa cross-asset
(ROT01). Eseguirli insieme produce una curva di equity più liscia del singolo.
## Onestà sull'obiettivo €50/giorno
Va detto chiaramente: **€50/giorno su €1.000 in pochi mesi non è raggiungibile a
rischio sano.** Significa ~€18.250/anno, cioè ~1.825%/anno; gli edge onesti qui
trovati rendono il 30-60% OOS su orizzonti pluriennali. Le strade per avvicinare
quel numero sono: (a) far crescere il capitale per anni con interesse composto —
€50/giorno diventa plausibile solo quando il capitale è molto più grande; (b) alzare
la leva, che però aumenta proporzionalmente il drawdown (già 23-55%) ed espone a
rovina; (c) aggiungere capitale. Nessuna di queste è una scorciatoia. La proposta
onesta è un portafoglio delle 3 strategie a leva moderata, puntando alla
**sopravvivenza e alla crescita composta**, non al target giornaliero immediato.
## Miglioramenti (alzare Acc, ridurre DD, migliorare PnL)
Leve oneste e documentate, senza tuning sui singoli anni
(`scripts/analysis/honest_improve.py`, `honest_improve2.py`):
### ROT02 — dual-momentum overlay (migliora TUTTO)
Alla rotazione cross-sectional di ROT01 si aggiunge un overlay di *absolute
momentum*: cash quando BTC è sotto la sua media a 100 giorni (mercato risk-off).
Taglia i bear di sistema (gli unici anni rossi di ROT01).
| | FULL% | OOS% | DD% |
|---|---|---|---|
| ROT01 base | +679 | +44 | 53 |
| **ROT02 (SMA100)** | **+1095** | **+98** | **40** |
PnL su, DD giù: dominanza su tutte e tre le metriche. Param-insensitive (SMA100-150).
### DIP01 — market-gate (variante low-DD)
Comprare i dip solo quando BTC è risk-on alza l'**Acc** (ETH 52→57%, SOL 49→52%) e
**dimezza il DD** (ETH 53→23%, SOL 25→13%), al costo di parte della PnL (meno trade).
È de-risking, non un pasto gratis: utile per chi vuole una curva più liscia. Su BTC
il gate va evitato (i dip migliori di BTC arrivano proprio quando BTC è sotto la
propria SMA), quindi DIP01 base resta la versione di riferimento per BTC.
### PORT01 — portafoglio combinato (il vero motore di risk-reduction)
Equal-weight giornaliero ribilanciato delle 3 sleeve anti-correlate
(DIP01 BTC + TR01 basket + ROT02). La diversificazione porta il DD del portafoglio
**sotto** quello della sleeve meno rischiosa, mantenendo una CAGR alta.
| Sleeve | ret% | DD% | CAGR% |
|--------|------|-----|-------|
| DIP01 BTC | +322 | 15 | 31 |
| TR01 basket | +591 | 27 | 43 |
| ROT02 dual-mom | +771 | 40 | 49 |
| **PORTAFOGLIO** | **+642** | **12** | **45** |
Per-anno portafoglio: 2021 +203% · 2022 **1%** (bear neutralizzato, era 30% su ROT) ·
2023 +47% · 2024 +50% · 2025 +14% · 2026 2% (YTD). Nessun anno realmente negativo,
DD massimo 12%, CAGR 45%. È la configurazione di deployment raccomandata.
## Prossimi passi
- Integrare DIP01 nel worker (già compatibile: Signal con tp/sl/max_bars).
- Trailing-stop ad ATR per TR01 (per alzarne l'Acc e ridurne ulteriormente il DD).
- Estendere il worker per strategie position-based (TR01) e di portafoglio (ROT01).
- Backtest del portafoglio combinato con ribilanciamento del capitale.
- Walk-forward rolling (oltre al singolo split 70/30) per confermare la stabilità.
+193
View File
@@ -0,0 +1,193 @@
# 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.
+155
View File
@@ -0,0 +1,155 @@
# Diario — 2026-05-29 — Esplorazione di nuove famiglie di strategie
## Obiettivo
Trovare 5-10 nuove famiglie di strategie, diverse da quelle esistenti, migliori o
complementari, con DD basso e attenzione alle fee. Esplorazione onesta (no
look-ahead, netto fee, OOS) condotta con **agenti paralleli**, ognuno su una famiglia
indipendente, tutti sullo stesso harness condiviso (`scripts/analysis/explore_lab.py`).
Lavoro sul branch `strategy_explore`.
## Famiglie esplorate (9) ed esito onesto
| Famiglia | Esito | Note |
|---|---|---|
| **Pairs / spread reversion** | ✅ **VINCITORE** | Market-neutral, genuinamente nuova, decorrelata |
| **TSMOM multi-orizzonte** | ✅ diversificatore | Marginale ma distinto (corr 0.53 con ROT02), DD basso |
| Stagionalità settimanale | ⚠️ marginale/fragile | "Mercoledì-long-24h" 7/8 asset OOS+ ma effetto concentrato a 00:00 UTC |
| Vol-target BTC | ⚠️ marginale | Sharpe 0.94 vs 0.76 buy&hold, DD ancora 44% |
| Stagionalità intraday (ora) | ❌ rumore | L'edge orario muore sotto le fee |
| Stagionalità mensile/turn-of-month | ❌ rumore | Reale in-sample, morto OOS dal 2024 |
| Cross-sectional reversal | ❌ nessun edge | Perde vs equal-weight, corr 0.98 col momentum |
| Opening-range breakout | ❌ non generalizza | Solo BTC/ETH, alcuni regimi, fee-fragile |
| Lead-lag BTC→alt | ❌ nessun edge | Reazione contemporanea (corr lag+1 ≈ 0), non batte buy&hold |
| Momentum/continuation intraday | ❌ negativo | Conferma: il *fade* (mean-reversion) domina |
7 famiglie su 9 sono rumore — e l'harness le ha rifiutate senza produrre falsi
positivi (segnale che la metodologia onesta funziona). Due edge reali emergono.
## Vincitore 1 — PAIRS (market-neutral) — `PR01_pairs_reversion.py`
Scommette sul rientro del log-ratio di due cripto verso la media (z-score). Quando
`z ≤ 2` → long A / short B; `z ≥ +2` → l'opposto; esce al rientro (`|z| ≤ 0.5`) o a
tempo. Engine onesto verificato in `pairs_research.py` (test esplicito no-look-ahead:
`z[i]` invariato perturbando il futuro). Fee contate su **2 gambe** (0.20% RT/coppia).
Validazione (netto, leva 3x, OOS = ultimo 30%, 1h):
| Coppia | CAGR | Sharpe | OOS DD | anni+ |
|---|--:|--:|--:|--:|
| ETH/BTC | 144% | 4.04 | 17% | 8/9 |
| LTC/ETH | 71% | 2.52 | 10% | 7/8 |
| ADA/ETH | 77% | 2.16 | 11% | 7/8 |
Tutte le 10 coppie testate positive FULL+OOS, regge fee 0.40% RT/coppia, correlazione
col mercato ~0.02 (market-neutral confermato). DD pieno 42-49% (alto), ma OOS DD
10-17% (buono) e soprattutto **quasi-zero correlazione** col resto → diversificatore
eccezionale. Limite: 2 gambe (long+short), il worker live va esteso prima del live.
## Vincitore 2 — TSM01 (TSMOM multi-orizzonte) — `tsmom_research.py`
Long-only multi-crypto: tiene equal-weight gli asset con consenso pieno del segno di
momentum su 3/6/12 mesi, cash se BTC<SMA100. Distinto da ROT02 (persistenza assoluta
vs ranking relativo), corr 0.53. FULL +169% / OOS +80% / DD 22% / Sharpe 1.07,
**mai un anno negativo**, regge fee 0.40%. Verificato no-look-ahead (cheat-test
esplode a +575%). Marginale come stand-alone (rende meno di ROT02) ma utile in ensemble.
## Il payoff — combinare le nuove fonti col MASTER (`combine_v2.py`)
Le nuove sleeve sono quasi scorrelate col MASTER-9 (pairs ~0.02-0.08, TSM01 0.05).
Aggiungerle migliora nettamente il portafoglio:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + pairs (12)** | **66** | **3.8** | **5.67** | **3.3** | **6.86** |
| MASTER + TSM01 (10) | 44 | 4.7 | 4.21 | 4.2 | 4.33 |
| MASTER esteso (13) | 62 | 3.6 | 5.66 | 3.0 | 6.79 |
I **pairs** sono l'aggiunta decisiva: alzano la CAGR (47→66), **abbassano il DD**
(5.2→3.8 full, 4.7→**3.3** OOS) e portano lo Sharpe OOS a **6.86** — il free-lunch
della diversificazione da una fonte market-neutral scorrelata. TSM01 contribuisce
poco (diluisce il ritorno) ma abbassa lievemente il DD.
## Caveat onesti
- I pairs hanno DD pieno alto (42-49%) sull'1h; il vantaggio sta nella decorrelazione,
non nel DD stand-alone. Richiedono esecuzione a 2 gambe (short del perp B) — da
verificare shortabilità/liquidità sugli alt e raddoppio fee nel worker.
- Sharpe combinati 5-7 e CAGR 60%+ sono backtest a leva 3x su finestra 2021-2026 con
OOS ~1.6 anni e il 2024 cripto eccezionale: numeri ottimistici, da confermare in
paper trading live.
- TSMOM e le strategie honest condividono l'overlay risk-off SMA100: parte della loro
difensività è comune (non perfettamente indipendente).
## Terza ondata — espansione dei meccanismi provati + 2 nuovi sondaggi
Esplorate altre 4 direzioni con agenti paralleli:
- **Fade su 6 nuovi alt (ADA/BNB/DOGE/LTC/SOL/XRP)**: 0 robuste. La mean-reversion
fade vive solo su BTC/ETH (liquidi); sugli alt sparisce o è artefatto di pochi pump
(DOGE). Coerente con la lezione del progetto.
- **Espansione PAIRS** (tutte le 28 coppie): trovate **3 nuove coppie robuste**
BTC/LTC (robusta 1h *e* 4h, Sharpe 2.21, DD 24-34%, concentrazione PnL 9%), ETH/SOL
e BNB/ETH (Sharpe 2.4+, solo 1h). Pattern: sempre alt-liquido vs major, mai alt/alt.
PR01 ora ha **6 coppie**.
- **Low-volatility anomaly**: ❌ in cripto è INVERTITA (vince l'alta vol = alta beta),
ridondante con EW+risk-off/ROT02. L'anti-test high-vol stravince.
- **Confluenza multi-timeframe (fade 1h confermato da 4h)**: non crea edge nuovo e non
migliora lo Sharpe, ma **dimezza il DD** di MR01 (ETH: stesso Sharpe 3.17 a DD 38% vs
63%) e stabilizza l'OOS → utile variante low-DD, non strategia indipendente.
## Bilancio finale e MASTER esteso (6 pairs)
Robusti deployabili: **famiglia PAIRS (6 coppie) + TSM01** (+ confluenza MTF come variante
low-DD di MR01, + tilt stagionale mercoledì marginale). I 6 pairs sono quasi scorrelati col
MASTER (corr 0.02-0.08). MASTER + 6 pairs:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + 6 pairs (15)** | **71** | 5.7 | **5.93** | **2.3** | **7.71** |
| MASTER esteso +TSM01 (16) | 67 | 5.4 | 5.95 | **2.2** | 7.67 |
Aggiungere i 6 pairs porta l'**OOS DD a 2.2-2.3%** (da 4.7%) con Sharpe OOS ~7.7 e tutti
gli anni positivi: il guadagno di diversificazione da fonti market-neutral scorrelate.
## Quarto giro — validazione anti-overfitting e irrobustimento
Tre audit scettici paralleli (walk-forward, plateau, stress, scomposizione):
**Pairs — de-overfittati.** Sostituita la config per-coppia (cherry-picking di z_exit/n)
con **una config universale `n=50 z_in=2.0 z_exit=0.75 max_bars=72`**. Verifiche:
- plateau (non picco): heatmap n×z_in → 20/20 celle Sharpe>1 su ETH/BTC e BTC/LTC;
- walk-forward (train 2y / test 6m rolling): ETH/BTC 11/12 finestre positive, BTC/LTC
9/10 → edge distribuito su tutta la storia, non un regime singolo;
- **BNB/ETH scartata** (era robusta solo coi suoi parametri → overfit; crolla con la
universale e muore per prima allo stress costi). Famiglia ridotta a **5 pairs**.
- stress: 5/6 reggono fee+slippage realistici; solo ETH/BTC regge fee 6x (coda fee-fragile).
**Master — numeri sobri.** L'OOS Sharpe 7.7 / DD 2.3% è **ottimistico ~50%** perché l'OOS
cade nel bull calmo 2024-25. Numeri onesti da usare:
- worst-DD su finestra mobile 90g (2021-2026) = **5.7%** (bear FTX) → budget DD ~6%, non 2.3%;
- Sharpe per-semestre: mediana **~5** (min 1.2, max 12) → atteso ~5, non 7.7;
- ogni anno e ogni semestre dal 2021 positivo (anche il 2022 bear, grazie alle gambe short);
- equal-weight ≈ inverse-vol (non dipende da pesi fortunati);
- regge **leva 2x + slippage doppio** (CAGR 36%, Sharpe 5.1);
- **rischio concentrato: i pairs portano ~57% del rischio** → cap consigliato ~30-35%.
- Config robusta raccomandata: **MASTER-esteso, equal-weight, leva 2x, cap pairs ~30-35%**.
**TSM01 — confermato robusto** (36/36 config OOS+, walk-forward stabile) ma corr reale con
ROT02 = **0.62** (non 0.53), e gran parte del DD basso viene dall'overlay risk-off condiviso.
Tenuto come diversificatore con **gross 0.30** (stesso Sharpe, DD 22%→15%).
**Confluenza multi-TF — SCARTATA: era overfit.** Taglia il 97% dei trade (restano ~40 in
8 anni = non significativo), distrugge lo Sharpe (1.58→0.27 su BTC) e il caso "bello" non
sopravvive alle perturbazioni. Per abbassare il DD di MR01 meglio ridurne la leva, non il filtro 4h.
**Risultato del giro:** quanto trovato regge l'esame anti-overfit (NON è l'errore squeeze),
ma i numeri vanno comunicati sobri (Sharpe ~5, DD ~6%) e con leva 2x + cap pairs. Famiglia
pairs consolidata a 5 coppie con config universale; confluenza MTF rimossa dai vincitori.
## File creati (branch strategy_explore)
`scripts/analysis/explore_lab.py` (harness onesto condiviso), `pairs_research.py`
(verifica + ricerca pairs), `tsmom_research.py` (TSM01), `combine_v2.py` (master
esteso); `scripts/strategies/PR01_pairs_reversion.py` (artefatto pairs).
+84
View File
@@ -0,0 +1,84 @@
# Diario — 2026-05-29 — Pattern del segnale per FORMA (analog/shape forecasting)
## Obiettivo
Verificare se la **forma** del segnale (la morfologia recente del prezzo) permette di
prevedere l'andamento successivo, e ricavarne edge verso il target €1000 → €50/giorno.
Esplorazione onesta (no look-ahead, netto fee, OOS) con **agenti paralleli**, ognuno su
una famiglia di forma indipendente, tutti sullo stesso harness shape (`scripts/analysis/
shape_lab.py`, che riusa l'engine netto-fee+OOS di `explore_lab.py`). Branch `shape_patterns`.
## Harness
`shape_lab.py` — analog forecasting causale: a ogni barra `i` si guarda la forma recente
`W` (closes z-normalizzati fino a `close[i]`), si cercano nel passato le `K` finestre più
simili **il cui esito a `H` barre era già noto prima di `i`** (KDTree ricostruito ogni
`rebuild` barre → niente O(N²)), si prevede la direzione = segno del rendimento medio degli
analoghi. **No-look-ahead verificato** (perturbare il futuro non cambia la forma a `i`,
max diff 0.0). Baseline forma grezza: marginale e **muore sulle fee** (W24H12K50: FULL
+112% / OOS +48% ma a 0.20% RT → 72%; troppi trade, exp 74%, win 49.5%).
## Famiglie esplorate (5) ed esito onesto
| Famiglia | Esito | Note |
|---|---|---|
| Analog kNN (forma grezza, selettività) | ❌ RUMORE | Solo BTC-overfit, non robusto ≥2 asset |
| Encoding candele (UP/DOWN/DOJI + body/shadow) | ❌ RUMORE | Hit-rate condizionale ~50%, segno incoerente fra asset |
| DTW + template geometrici (M/W, testa-spalle, V, U) | ❌ RUMORE | DTW *peggiora* l'euclidea; template overfit (FULL ok, OOS crolla) |
| PIP / pivot / zig-zag (geometria svolte) | ❌ RUMORE | 0/48 config robuste; le rotture S/R rientrano (riconferma MR) |
| **Feature-vector + ML walk-forward** | ✅ **EDGE REALE** | LogisticRegression sulla forma, fee-robusto |
4 famiglie su 5 sono rumore: riconfermano che la forma grezza non contiene edge
direzionale eseguibile e che l'unico edge "classico" resta la mean-reversion (fade/pairs).
## L'edge: SH01 — Shape-ML
Una **LogisticRegression** legge 17 feature di forma (body/shadow ratio, rendimenti,
pendenza/curvatura del path, posizione di max/min, RSI, estensione) e predice il segno del
rendimento a `H` barre. **Walk-forward rigoroso**: scaler+modello fittati solo sul passato
con esito noto, poi predicono il blocco corrente; si entra a `close[i]` se la probabilità
≥ soglia. Causalità verificata con check espliciti (feature e predizioni invarianti al
futuro). Il GradientBoosting dà edge equivalente ma è ~60× più lento → si usa il logit.
A differenza della famiglia squeeze (che moriva anche a fee zero), **questo edge
sopravvive a fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria** (quando indovina
la direzione i moti sono più grandi), non nella frequenza.
### Validazione dura (config W24 H12 th0.58, netto fee, leva 3x, pos 0.15, OOS 30%)
- **Multi-asset expanding**: robusti **BTC** (FULL +219% / OOS +42% / Sharpe 2.72 / DD 23%
/ 8-9 anni+ / accOOS 56%), **ETH** (+80% / +144% / Sharpe 1.21, più volatile), **ADA**
(+707% / +57% / Sharpe 3.22). Scartati LTC/SOL/XRP (perdono netti).
- **Walk-forward rolling (train fisso 2 anni)**: regge **solo BTC** (+166% / +96% / Sharpe
2.05). L'edge si appoggia in parte alla memoria lunga → BTC è il più solido.
- **Stress leva 2x + slippage doppio (0.20% RT)**: BTC OK (+40% / +17% / Sharpe 1.24),
ETH marginale (+7% / +73% / Sharpe 0.37).
- **Griglia (W,H,thresh) su BTC**: **5/27 celle robuste**, su una **cresta** stretta (W24,
H8-12), non altopiano largo → rischio overfit moderato. Per prudenza si sceglie la config
robusta sul maggior numero di test (W24 H12 th0.58), non il PnL massimo (W24 H8 rende di
più ma accOOS ~49% = più drift che segnale).
### Il valore vero: diversificatore di portafoglio
Correlazione daily col MASTER **+0.08** (quasi scorrelato). Aggiungere lo sleeve shape
(BTC+ETH) al MASTER migliora l'OOS: **Sharpe 4.33 → 5.10, DD 4.7% → 4.2%** (FULL: Sharpe
4.23 → 4.37, DD 5.2% → 4.3%). Non è un motore standalone (per-asset troppo stretto fuori
da BTC), ma un **free-lunch** da aggiungere al paniere.
## Artefatti
- `scripts/analysis/shape_lab.py` — harness analog/forma causale.
- `scripts/analysis/shape_{analog,candle,template,pivot,ml}_research.py` — le 5 ricerche.
- `scripts/analysis/shape_ml_validate.py` — validazione dura del candidato ML.
- `scripts/strategies/SH01_shape_ml.py` — la strategia (Strategy + run() riproducibile).
- Aggiunta a `MODULE_MAP` (caricabile per backtest).
## Conclusione e prossimi passi
La forma del segnale **non** predice in modo grezzo (4/5 famiglie rumore), ma un modello
lineare sulle feature di forma in walk-forward onesto **sì**, soprattutto su BTC, e vale
come diversificatore quasi-scorrelato del MASTER. Da fare prima del live:
1. **Worker con retraining periodico** (lo StrategyWorker attuale è a regola fissa; SH01
riallena il modello → serve un loop tipo legacy signal_engine).
2. Validazione live-path (replay worker == backtest) come fatto per i pairs.
3. Decidere il peso nel MASTER-esteso (cap, leva) col paper trader.
+64
View File
@@ -0,0 +1,64 @@
# Diario di ricerca — 2026-05-29
## Combinare le strategie migliora i risultati?
**Domanda:** usare insieme le due famiglie di strategie presenti sul repo migliora
il profilo rischio/rendimento rispetto a usarle separatamente?
- **FADE** (mie): reversione intraday 1h, long/short, BTC/ETH — MR01 Bollinger,
MR02 Donchian, MR03 Keltner, MR07 Return-reversal (tutte col filtro trend 3.0 ATR).
- **HONEST** (altra sessione): long-only multi-regime multi-crypto — DIP01 (dip-buy
1h BTC), TR01 (EMA-trend 4h basket), ROT02 (dual-momentum rotation 1d).
**Metodo** (`scripts/analysis/combine_portfolio.py`): per ogni sleeve si costruisce
l'equity **giornaliera** normalizzata su un indice comune (2021-01-01 → 2026-05-26),
si passa ai rendimenti giornalieri, si misura la correlazione cross-famiglia e si
confrontano i portafogli equal-weight (ribilanciati ogni giorno), 50/50 fra famiglie
e inverse-vol. Metriche FULL e OOS (ultimo 30% della finestra comune, da 2024-10-12):
ritorno, CAGR, max DD, Sharpe annualizzato. Le curve honest sono riusate da
`honest_improve2.py`; quelle fade da `risk_management.build_trades`.
**Correlazione:** cross-famiglia **+0.05** (quasi indipendenti). Intra-fade +0.18,
intra-honest +0.05. L'unica coppia un po' correlata è MR01_BTC↔DIP01_BTC (+0.43),
entrambe mean-reversion su BTC. Famiglie scorrelate ⇒ diversificazione quasi ideale.
**Risultati (FULL | OOS):**
| Portafoglio | Ret% | CAGR | DD% | Sharpe | oDD% | oSharpe |
|---|---|---|---|---|---|---|
| FADE only (8) | +549 | 41 | 8.6 | 3.75 | 5.4 | 4.14 |
| HONEST only (3) | +642 | 45 | 12.0 | 1.90 | 6.5 | 2.23 |
| **ALL equal-weight (11)** | +589 | 43 | 6.1 | **3.95** | 4.6 | **4.46** |
| **ALL 50/50 famiglie** | +615 | 44 | **5.5** | 3.18 | **4.0** | 3.87 |
| ALL inverse-vol | +483 | 39 | 5.8 | 3.97 | 4.6 | 4.02 |
**Conclusione: sì, combinare conviene.**
- DD crolla: combinato 5.56.1% full / 4.04.6% OOS, contro 8.6% (fade) e **12%**
(honest) da sole → drawdown ridotto del 3550%.
- Sharpe sale: combinato OOS **4.46** vs honest 2.23 (raddoppia) e batte pure fade (4.14).
- CAGR resta ~4344% (≈ media delle due famiglie) ma con metà del rischio: è il
"free lunch" della diversificazione fra sorgenti di edge scorrelate.
- Best Sharpe = equal-weight degli 11 sleeve; best DD = 50/50 fra le due famiglie.
**Caveat onesti:** la finestra comune è 20212026 (5.4 anni), OOS ~2024-10→oggi
(1.6 anni) — pochi regimi. CAGR e Sharpe sono backtest a leva 3x; il 2024 cripto
favorevole pesa. Il target €50/giorno resta vincolato dal capitale: 43% CAGR su
€1000 non fa €50/giorno a breve, serve compounding pluriennale o più capitale.
Prossimo passo: confermare il portafoglio combinato nel paper trader live.
**File:** `scripts/analysis/combine_portfolio.py` (nuovo).
## Pulizia roster + miglioria ROT02
- **Waste delle peggiori:** MR03 Keltner (fade più debole, Sharpe 1.22, ridondante
con MR01 — rimuoverla *migliora* il portafoglio fade: DD 8.6→8.2, ret +549→+666)
e ROT01 (dominata da ROT02). Spostate in `scripts/waste/`.
- **Portafogli pronti:** `PORT02_fade_master` (6 sleeve fade) e `PORT03_all_master`
(9 sleeve fade+honest, varianti equal/5050).
- **ROT02 DD alto → migliorato:** la rotazione concentrava il book su 2 asset
(DD 40%). Sweep su `rot_improved`: `top_k=3` dimezza quasi il DD (40%→26%) e
*alza* il ritorno full (+1095→+1303%, ret/DD 27→50). Il vol-target abbassa il DD
ma sacrifica ritorno (de-leverage) → tenuto top_k=3 senza VT. Caveat onesto:
l'OOS di ROT02 cala un po' (+98→+68%, DD 12→14%), ma il MASTER (config deployata)
migliora lo Sharpe full 3.95→4.23. Applicato a `ROT02_dual_momentum.py` e
`_rot_daily_equity`. Sweep in `honest_improve.rot_improved`.
+68
View File
@@ -0,0 +1,68 @@
# 2026-05-31 — Studio sugli EXIT delle fade: scalping, TP dinamico, TP-ATR
> Innescato da una domanda operativa ("un TP è stato raggiunto, non si poteva
> scalpare / fare un TP dinamico?"). Studio fee-aware su MR02 (Donchian fade,
> segnali invariati `n=20 sl_atr=2.0 max_bars=24`, fee 0.10% RT, leva 3x). Tre
> alternative di uscita misurate contro il baseline attuale (**TP = centro del
> canale**). Verdetto: **il design attuale è già ottimale; nessuna alternativa lo batte.**
## 1. "Scalping" = timeframe più veloce (15m vs 1h)
A fee 0.10% il 15m rende di più in lordo (~4× più trade), MA è molto più **fragile**:
| | trade | PnL @0% | @0.10% | @0.20% | DD @0.10% |
|---|--:|--:|--:|--:|--:|
| BTC 1h | 2041 | +22.768 | +16.645 | +10.522 | 29% |
| BTC 15m | 8251 | +65.286 | +40.533 | +15.780 | 29% |
| ETH 15m | 9388 | +120.103 | +91.939 | +63.775 | **62%** |
Da 0% a 0.20% il 15m perde **~76%** del profitto (vs 54% del 1h) e il DD esplode
(ETH 15m → 93% a 0.20%). 4× più trade = 4× più fee + slippage (non modellato, ma
peggiore su book sottili). **L'1h è scelto per il margine di sicurezza, non per il PnL
lordo.** Lo scalping vero (<0.3% target) è in pieno territorio "morte da fee".
## 2. TP dinamico / trailing ("lascia correre il vincitore")
Stessi segnali, exit per trailing a k·ATR dal massimo favorevole invece del TP fisso:
| policy | BTC win% | ETH win% | equity |
|--------|---------:|---------:|--------|
| FIXED (centro, attuale) | **48%** | **49%** | 🟢 di gran lunga il migliore |
| TRAIL (lascia correre) | 36% | 36% | 🔴 azzerato |
| MID+TRAIL | 47% | 47% | 🔴 peggio |
Il win-rate crolla 48%→36%: i trade che avrebbero incassato il TP fanno andata-e-ritorno
e stoppano fuori. **Concettuale:** l'edge della fade è la reversione *fino* alla media;
una volta toccata, l'edge è esaurito. Lasciar correre *oltre* = scommettere sulla
continuazione, che sui perp crypto NON ha edge (rientra). È la stessa logica per cui
SMA/ORB/WR (continuazione) hanno fallito: **let-it-run = trend = il lato perdente.**
## 3. TP scalato all'ATR (TP = entry + dir·m·ATR, SL fisso 2 ATR → R:R = m/2)
| Config | win% | avg %/trade | Sharpe | sumRet% |
|--------|-----:|-----------:|-------:|--------:|
| **BTC MID (attuale)** | 48% | **0.816** | **3.8** | **1664** |
| BTC ATR m=0.5 (RR0.25) | **77%** | 0.081 | 1.0 | 217 |
| BTC ATR m=1.0 | 67% | 0.192 | 1.6 | 465 |
| BTC ATR m=2.0 | 53% | 0.563 | 3.0 | 1199 |
| BTC ATR m=3.0 | 46% | 0.679 | 3.0 | 1331 |
| **ETH MID (attuale)** | 49% | **1.738** | **7.5** | **4169** |
| ETH ATR m=0.5 | 77% | 0.041 | 0.5 | 134 |
| ETH ATR m=3.0 | 46% | 1.082 | 4.7 | 2515 |
OOS (ultimo 30%) identico: **MID** batte ogni `m` (BTC MID avg 1.14/Sh 3.2; ETH MID avg
4.43/Sh 10.9). Due lezioni:
- **TP stretto (m=0.5) = trappola dello scalping quantificata:** win-rate **77%** ma edge
**zero/negativo** (BTC 0.08%/trade). I rari stop a 2 ATR spazzano via le micro-vincite,
la fee mangia il resto. **Win-rate alto ≠ edge.**
- **Nessun multiplo ATR fisso batte il centro del canale**, su avg/trade E Sharpe, FULL e OOS,
entrambi gli asset.
## Verdetto unificato
Il **TP al centro del canale è ottimale** perché è un target *adattivo alla struttura*: un
multiplo fisso di ATR misura solo *quanta* vol c'è, ma ignora *dove* sta la media; il centro
adatta al punto reale di reversione **ed è già scalato alla volatilità** (il canale si allarga
in regime volatile). Per una mean-reversion il punto giusto dove chiudere è **la media — niente
prima, niente dopo.** Tre alternative escluse coi numeri (15m, trailing, TP-ATR) → la scelta
di design corrente è blindata.
> Nota metodologica ricorrente: diffidare del **win-rate alto**. Il segnale vero è
> rendimento-medio-per-trade × Sharpe; un TP stretto regala win-rate e nasconde l'assenza
> di edge. (Stesso tranello dei guru: backtest cherry-picked ad alta % di vincite.)
+70
View File
@@ -0,0 +1,70 @@
# 2026-05-31 — Stato trade LIVE PORT06 (paper trading)
> Snapshot verificato del paper trader a portafoglio (`src.portfolio.runner`, Docker
> `pythagoras-portfolio`). Dati da `data/portfolios/PORT06/` + log del container.
> Avvio container: 2026-05-29 18:37 UTC. Snapshot: 2026-05-31 13:20 UTC (~43h).
## Riepilogo capitale
| Metrica | Valore |
|---------|--------|
| Capitale iniziale | €1000.00 (17 sleeve equal-weight, ~€58.82 ciascuno) |
| `total_capital` (realizzato, ultimo rebal 00:00) | **€1000.09** (+0.09) |
| Equity mark-to-market (live) | **€1000.36** (+0.036%) |
| Peggior punto toccato | −€0.01 |
| **Max DD** | **0.40%** |
| Container | running, healthy, 0 restart |
## Trade chiusi (storia completa dallo startup: 10 trade, 9W/1L)
| # | Sleeve | Uscita | Net % | PnL € | Esito |
|---|--------|--------|------:|------:|:---:|
| 1 | PR01 ETH/SOL | mean_revert | +0.503 | +0.040 | W |
| 2 | PR01 ETH/SOL | mean_revert | +0.683 | +0.060 | W |
| 3 | SH01 BTC (ML) | hold_limit | 0.462 | 0.040 | L |
| 4 | SH01 BTC (ML) | hold_limit | +0.017 | +0.000 | W |
| 5 | PR01 ETH/SOL | mean_revert | +0.488 | +0.040 | W |
| 6 | PR01 ETH/SOL | mean_revert | +0.284 | +0.030 | W |
| 7 | PR01 LTC/ETH | mean_revert | +0.745 | +0.070 | W |
| 8 | PR01 BTC/LTC | mean_revert | +0.434 | +0.040 | W |
| 9 | MR02 ETH fade | take_profit | +0.995 | +0.090 | W |
| 10 | SH01 ETH (ML) | hold_limit | +0.742 | +0.070 | W |
| | **TOTALE** | | | **+0.400** | **90% win** |
### Aggregato per sleeve (trade chiusi)
| Sleeve | n | win | acc% | PnL € |
|--------|--:|----:|----:|------:|
| PR01 ETH/SOL | 4 | 4 | 100 | +0.170 |
| MR02 ETH fade | 1 | 1 | 100 | +0.090 |
| PR01 LTC/ETH | 1 | 1 | 100 | +0.070 |
| SH01 ETH (ML) | 1 | 1 | 100 | +0.070 |
| PR01 BTC/LTC | 1 | 1 | 100 | +0.040 |
| SH01 BTC (ML) | 2 | 1 | 50 | 0.040 |
Motore del PnL finora: **pairs PR01** (market-neutral, mean_revert rapidi 1-6 barre) +
una fade **MR02** su take_profit. Unica perdita: SH01 BTC (ML) su hold_limit (fisiologico,
edge nell'asimmetria, win-rate ~50%). Sleeve daily (ROT02/TSM01/TR01) e diverse fade non
hanno ancora chiuso trade (orizzonte più lungo / pochi segnali in ~2 giorni).
## Posizioni aperte (3)
| Sleeve | Dir | Entry | Capitale |
|--------|-----|------:|---------:|
| MR02 BTC fade | short | 73969.0 | €58.83 |
| MR02 ETH fade | long | 2016.15 | €58.92 |
| SH01 BTC (ML) | long | 73811.5 | €58.83 |
## Verifica (check 2026-05-31)
- **0 anomalie** sui 10 CLOSE: `net = gross fee` rispettato, flag `win` coerente col
PnL, fee sempre presente (pairs 0.4% su 2 gambe, fade 0.10% RT).
- **Uscite = backtest**: tutti i CLOSE pairs sono `mean_revert` con **|z| ≤ 0.75** al close
(0.363/0.605/0.684/0.619/0.656) = esattamente `z_exit=0.75` di PR01; MR02 esce a TP al
livello. Il worker live replica la regola del backtest.
- **Riconciliazione**: +0.40 realizzato vs +0.09 `total_capital` NON è un errore — è il
timing del ribilancio giornaliero (`total_capital` snapshotta a 00:00, le posizioni
aperte restano sul notional fino al rebal; CLAUDE.md). L'equity MtM live (+0.36) è il
numero corrente, confermato da `equity.jsonl`.
## Lettura onesta
Campione minuscolo (**~2 giorni, 10 trade**) → il PnL (+€0.40 realizzato, +€0.36 MtM) è a
livello di **rumore**: non se ne deduce performance. Quello che il check conferma a questo
stadio è che il sistema è **sano e fedele**: esecuzione corretta, costi reali inclusi,
uscite conformi al backtest, DD trascurabile (0.40%), 0 errori/restart. L'edge si
manifesterà solo su orizzonte settimane/mesi. Monitor Docker attivo per down/unhealthy/restart.
@@ -0,0 +1,51 @@
# 2026-05-31 — Copertura opzioni: idee testate e SCARTATE (record anti-ripetizione)
> Record delle conclusioni. Il **codice** di queste prove è stato testato e poi
> scartato (non conservato nel repo): qui restano i numeri e il *perché*, così da
> non ri-testare le stesse idee in futuro. Motore di pricing usato: Black-Scholes
> r=0 + IV stimata onestamente = RV × moltiplicatore VRP ≥ 1 (il compratore
> SOVRAPPAGA, come in W18-W21), fee Deribit reali (0.03%/gamba + ~0.10% slippage).
## TL;DR
**La copertura opzioni non genera edge nuovo per questo progetto.** I due edge
disponibili (trend e mean-reversion) sono già catturati **50-100× più a buon
mercato dai perp** (fee 0.10% RT) di quanto facciano le opzioni (premio + VRP +
asimmetria coda). Comprare premio perde contro il VRP crypto; venderlo paga le
code grasse. Cappare la perdita su una strategia senza expectancy positiva limita
solo *quanto* perdi: **non esiste il pasto gratis "leva alta + perdite coperte".**
## 1. Overlay opzioni su PORT06 — non fattibile
Mismatch di orizzonte: l'edge di PORT06 è intraday (hold fade ~9h). Carry ATM 9h
**0.96%** del notional vs edge fade per-trade **0.10-0.30%** → costo 3-10× l'edge.
La coda di PORT06 è già piccola (DD ~5%) e market-neutral (pairs ~57% del rischio):
poco da assicurare. La copertura giusta era già lì (diversificazione + stop), gratis.
## 2. Strategie nuove a copertura opzioni
- **OH01 — direzionale (TSMOM) + opzione protettiva / sola opzione.** Frontiera
iso-rischio: il **perp NON coperto domina a ogni livello di rischio** (Sharpe 0.90
vs 0.33-0.57; CAGR +33% vs negativo). Comprare protezione su un trend perde per il
carry/VRP (il trend-following è "long vol" nel *payoff*, non comprando opzioni).
- **OH02 — spread di credito su mean-reversion (vendi premio = VRP a favore).**
La copertura funziona (perdita cappata, DD basso, win-rate 73-80%: la reversione è
reale). Ma **expectancy ~0/leggermente negativa**: il 27% di trade dove il movimento
*continua* (code grasse) costa ~5× ogni vincita. Un trend filter porta solo *singole
celle* a +1-2% (overfit: config diversa per asset). Non robusto.
## 3. V5 — Bull Call Spread / debit spread (stile Casario)
È **la migliore struttura long-premium**: rischio definito funziona (worstRoll 13%
vs 64% della call secca, DD 54% vs 94%). **Ma net-negativo in crypto** (BTC 2.2%
full / 13.5% OOS) e il perp non coperto lo batte. Sweep larghezza: spread più larghi
rendono di più → **cappare l'upside toglie le code grasse che pagano il premio**.
**Verdetto:** valido **sulle AZIONI** (vol/VRP bassi, uptrend puliti da screener), NON
in crypto. Casario ha ragione nel suo dominio (equity), non trasferibile ai perp crypto.
## 4. V4 — Box strategy (max/min giorno prima, supply/demand) → SKIP
Core tradabile = **fadare gli estremi del canale = MR02** (già live). La candela di
conferma (doji/hammer/rejection) = pattern di rigetto = rumore (vedi diario TA). Nessun
edge nuovo: costruirlo ri-deriverebbe solo MR02.
## Cosa servirebbe per un vero edge a opzioni (fuori scope attuale)
Non direzione né reversione (già coperte dai perp), ma un edge *specifico delle opzioni*:
dislocazioni della superficie IV/skew, o gestione attiva (chiusura al 50% del credito,
roll). Richiede storico prezzi opzioni reale (qui assente, prezzi sintetici da BS) e un
feed greche/IV che il `CerberoClient` oggi non espone.
@@ -0,0 +1,43 @@
# 2026-05-31 — 3 strategie TA "classiche": testate e SCARTATE (record anti-ripetizione)
> Record delle conclusioni. Codice testato e poi scartato (non conservato nel repo).
> Strategie da contenuti trading-guru: (1) SMA20/200 trend+pullback, (2) Opening Range
> Breakout "ironclad", (3) "Weakness rectangle" reversal (ICT). Testate con la
> metodologia onesta del progetto: ingresso eseguibile a `close[i]`, SL/TP intrabar,
> fee Deribit 0.10% RT, leva 3x, OOS(ultimo 30%), griglia robustezza, sweep fee.
## TL;DR — tutte e 3 NO EDGE (negative anche a fee ZERO)
Tutte e tre **direzionali/continuazione**, tutte negative su BTC/ETH, su tutta la
griglia, **anche a fee 0%** → il problema è il *segnale* (avg_R per-trade ≤ 0), non i
costi. Riconfermano la lezione centrale: *sui perp crypto i breakout/continuazione
rientrano; l'unico edge robusto è la mean-reversion.*
| Strategia | Tipo | avg_R @ fee0 | Motivo |
|-----------|------|--------------|--------|
| **SMA01** MA-pullback | continuazione | 0.15 BTC / 0.07 ETH | win ~30% (serve ~40% a R:R 2) |
| **ORB01** opening-range breakout | breakout | 0.10…−0.19 | crypto 24/7: manca l'asta d'apertura, ragione d'essere dell'ORB |
| **WR01** weakness rectangle | reversal→continuazione | ≈ 0.05/0.00 | R:R "5:1" illusorio (win cala in proporzione); le weakness vengono travolte |
> Verificato indipendentemente (reimplementazione minima SMA01): a fee 0 avg_R
> 0.15/0.07. Il 100% di CAGR è solo l'edge negativo composto a leva 3x su migliaia
> di trade, non un bug.
## Tentativo di MIGLIORAMENTO — ribaltarle sul lato fade
Miglioramento *di principio* (non tuning): visto che perdono perché sono continuazione,
ribaltate sul **fade** (l'unico lato con edge in crypto).
| versione fade | edge? (avg_R@fee0) | verdetto |
|---------------|--------------------|----------|
| **SMA02** fade dell'estensione→SMA20 | **+** (0.04…0.36) | = **MR01 inferiore** (FULL 1h negativo, Sharpe 0.4-0.9 vs 2.7+) |
| **ORB02** fade del breakout del range | **+** (win 35%→50-66%) | = **MR02/MR07** senza controlli di rischio (DD 90-100%) |
| **WR02** weakness come reversione | **≈0** | **rumore**, non una fade-family nascosta |
- Il flip restituisce segno positivo a 2/3 (riconferma *fade > continuazione*) **ma nulla
di additivo**: SMA02/ORB02 sono ri-scoperte inferiori di strategie già live; WR02 è rumore.
- **Ipotesi "SMA200 piatta = meglio fadare" SMENTITA**: il regime *range* non batte il fade
semplice; semmai il regime *trend* dà avg_R migliore ma con time-in-market 0.5-9%.
## Lezione metodologica
La prova del nove è l'**avg_R a fee 0**: se una strategia perde anche senza costi, il
problema è il segnale e nessun tuning la salva. Le strategie che funzionano restano
MR01/MR02/MR07 (fade) + PR01 (pairs) + PORT06 — l'edge è mean-reversion + diversificazione.
@@ -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.
@@ -0,0 +1,71 @@
# 2026-06-01 — SH01 live eseguiva la strategia SBAGLIATA (squeeze scartato), non shape-ML
> Scoperto verificando perché SH01 continuava a chiudere a `hold_limit/3` **anche dopo**
> il rebuild col fix horizon-exit. Il fix era corretto ma in un **ramo morto**: SH01 live
> non passava da `StrategyWorker.tick()`.
## Sintomo
Dopo il deploy del fix SH01 (exit a H=12), un close SH01_BTC delle 12:00 era ancora
`reason=hold_limit bars=3` (perdita 1,27%). Il fix non aveva effetto sul path reale.
## Causa (bug di wiring, più grave del previsto)
`src/portfolio/runner.py` importava `MLWorkerWrapper` da **`src/live/multi_runner.py`** e
ci avvolgeva lo sleeve SH01:
```python
if spec.kind == "ml":
return MLWorkerWrapper(worker, {"retrain_hours": 24})
```
Ma quel wrapper è **legacy, per la famiglia squeeze ML01** (scartata, vedi CLAUDE.md):
- usa `SignalEngine` = squeeze-detection + GradientBoosting (NON SH01_shape_ml);
- ha una `tick()` propria che apre con un `Signal` **nudo** (niente tp/sl/max_bars) ed
esce con `if bars_held >= hold_bars: close("hold_limit")` → ignora del tutto la
strategia caricata e il fix horizon.
Quindi lo sleeve "SH01" del portafoglio live **non eseguiva shape-ML**: eseguiva il
motore squeeze scartato. I log `TRAIN OK / oos_signal_precision` venivano da lì. Il
`worker` con strategy=SH01_shape_ml era costruito ma la sua `generate_signals` non
veniva **mai** chiamata.
## Fix
SH01 (`kind="ml"`) ora gira come **StrategyWorker normale**: `SH01_shape_ml.generate_signals`
fa il walk-forward (retraining) **internamente** ad ogni tick (`ml_wf_entries`) ed emette
`metadata.max_bars=H=12` → gli exit passano per `StrategyWorker.tick()` e il fix horizon
si applica davvero.
```python
# runner.py: niente più MLWorkerWrapper per kind="ml"
return StrategyWorker(strategy=strategy, asset=spec.asset, tf=spec.tf, ...)
```
**Lookback dati.** `ml_wf_entries` ha `train_min=4000` → servono ≥4000 barre 1h prima di
produrre segnali (con 90g/2160 barre → 0 segnali, runtime 0.01s — il falso "muto"). Le
candele 1h di BTC/ETH già arrivano a 440g (le richiede TSM01/ROT02 a 1d), ma per non
dipendere da quella coincidenza ho aggiunto `_ML_LOOKBACK_DAYS=365`: gli asset usati da
sleeve ml fetchano ≥365g (~8760 barre). Costo `generate_signals` su 365g: **0,170,24s**
(modello logit) → trascurabile sul poll 60s.
**Verifica.** Build SH01 → `StrategyWorker` con `strategy.name=="SH01_shape_ml"`, niente
attributo `engine` (regression test `test_build_ml_sh01_is_plain_strategyworker`). Smoke
su 365g: 7661786 segnali, tutti `max_bars=12`; tick live 0,17s. `ml_wf_entries` non
predice mai l'ultima barra (`n-1`) ma fino a `n-2` = esattamente la condizione di apertura
del worker (`idx >= last_idx-1`) → apre quando il segnale è fresco. Suite: 51 passed.
**Stato live.** SH01 BTC/ETH erano flat: contatori resettati a 0 (capitale preservato
58,76/58,78), vecchi trade squeeze archiviati in `trades_squeeze_archive.jsonl`. Rebuild
+ recreate: 14 worker RESUME puliti, container healthy, nessun log TRAIN/squeeze, zero
errori.
## Lezione
1. **Verificare il path REALE, non solo il codice del fix.** Il fix horizon era giusto ma
SH01 non lo attraversava. Un fix non testato end-to-end sul percorso vivo è un fix
presunto. (Mi ero fidato del rebuild senza confermare il reason dei close SH01.)
2. Riusare un wrapper legacy "perché c'è" è un rischio: `MLWorkerWrapper` di multi_runner
era per la famiglia squeeze scartata, non per shape-ML.
3. Un modello ML "muto" può essere solo **fame di dati** (train_min), non un bug logico:
controllare sempre la dimensione della finestra prima di concludere.
+89
View File
@@ -0,0 +1,89 @@
# 2026-06-01 — "Win" che perdono: metrica netto-fee + filtro TP edge-minimo
> Partito da un'osservazione dell'utente sui trade live PORT06: **ci sono close con
> `win=True` ma `pnl` negativo**. Due problemi distinti, entrambi risolti.
## Problema 1 — la metrica `win` mentiva (lordo invece di netto)
In `strategy_worker.py::_close_position`:
```python
trade_return = price_change * direction # LORDO, prima delle fee
net = trade_return * leverage - fee_rt * leverage
pnl = capital * position_size * net # corretto (netto)
is_win = trade_return > 0 # BUG: usa il LORDO
```
`is_win` scattava appena il prezzo si muoveva di un soffio a favore, **prima delle
fee**. Capitale e PnL erano giusti (netti); solo la metrica `win`/`accuracy` era
gonfiata.
**Quantificazione (51 close live):** 39 win dichiarate (76,5%) → **13 falsi win**
(`win=True` ma `pnl≤0`) → accuratezza **netta reale 52,9%**. PnL realizzato +€0,77
(resta positivo: lo trascinano i pairs).
**Fix:** `is_win = net > 0`. + `tests/portfolio/test_win_net_of_fees.py` (mossa
sotto-fee = non win; oltre-fee = win; perdita = non win).
**Riconciliazione contatori persistiti:** i `total_wins` su disco erano gonfiati dal
vecchio conteggio lordo. Ricalcolati come `net_return>0` dai `trades.jsonl`:
**MR01_BTC 7→1, DIP01_BTC 7→1** (gli unici toccati; tutti gli altri già coerenti).
Capitale invariato.
## Problema 2 — i 13 falsi win erano tutti MR01_BTC / DIP01_BTC in take_profit
Causa: in `MR01_bollinger_fade` e `DIP01_dip_buy` il **TP è la media** (`tp = ma[i]`)
e l'entry è a `close[i]` appena fuori banda. Nel regime BTC **piatto** (inchiodato
~73.700 per ore, vol bassissima) la media è a pochi dollari dall'entry → il TP cade
**dentro** il costo round-trip (0,10%): colpire il TP = perdita netta garantita.
**Meccanismo del fix (importante).** "Spostare il TP più in là" NON garantisce di non
perdere: il prezzo rientra solo fino alla media, non oltre → si finirebbe su SL/time-
limit, perdendo di più. La mossa provabilmente non-perdente è un **filtro di edge
minimo**: se `|tp entry|/entry ≤ min_tp_frac` non si apre la trade. Break-even
esatto = `fee_rt` (= 0,10%, indipendente dalla leva, perché
`ret = mossa·lev fee_rt·lev > 0 ⇔ mossa > fee_rt`).
**Implementazione:** parametro `min_tp_frac` (default 0.0 = off) in **tutte le 4 fade**
(MR01 banda, MR02 midpoint canale, MR07 ATR-scaled) e DIP01; salta i segnali sotto
soglia. Cablato negli sleeve live a **0.0015 (1,5× fee)** in `_defs.py` (`MIN_TP_FRAC`).
**Validazione backtest (BTC+ETH 1h, config sleeve, min_tp_frac ∈ {0,.001,.0015,.002}):**
neutro su tutte e 4 le fade.
- MR01: 0 trade rimossi (BTC +8028€, ETH +10395€) — metriche identiche.
- DIP01 BTC: 1 trade a 0.002, **migliora** (+7492→+7522€, DD 26,3→25,9%).
- MR02 BTC: 1 trade a 0.0015 (pnl invariato +12198€), ETH 0 rimossi.
- MR07 BTC/ETH: 0 rimossi (TP ATR-scaled sempre ben oltre le fee nello storico).
Conclusione: i micro-scalp sotto-fee **non esistono nel campione storico** — sono un
artefatto del regime attuale. Il filtro è **puro upside**: neutro sul backtest validato,
protettivo dal vivo. (Le 12 trade live incriminate, tutte MR01/DIP01 BTC, avevano gap
~0,026%, ben sotto 0,15% → tutte bloccate.)
+ `tests/portfolio/test_min_tp_frac.py` (monotonia + ogni superstite ha gap > soglia
+ default-off invariato).
## Nota deploy — il codice è COTTO nell'immagine, non montato
Scoperta durante il deploy: `docker-compose.yml` monta solo `./data` e
`./portfolios.yml`; il sorgente (`src/`, `scripts/`) è `COPY` nel Dockerfile. Quindi
**`docker compose restart` NON ricarica le modifiche al codice** — serve
`docker compose up -d --build`. Conseguenza retroattiva: anche il fix SH01
horizon-exit di stamattina è andato live solo con questo rebuild. Da ricordare per ogni
futura modifica ai worker. Il volume `./data` persiste → i 14 worker fanno RESUME
puliti dopo il rebuild (capitale e posizioni intatti).
## Stato finale
- `is_win = net > 0` live; contatori riconciliati (MR01/DIP01 BTC 1/9).
- Filtro `min_tp_frac=0.0015` live su tutti i fade + DIP01 (attivo solo MR01/DIP01).
- Fix SH01 horizon-exit ora **effettivamente** live (rebuild).
- Suite: 49 passed. Container ricostruito, healthy, 14 sleeve in RESUME.
## Lezione
1. Una metrica di "win" deve essere **netto fee**, altrimenti l'accuracy è teatro.
2. Quando il TP è dentro il costo di transazione, la trade è persa in partenza: meglio
**non prenderla** che ritoccare il TP.
3. Per i worker live in Docker: **rebuild**, non restart. Il restart ricarica solo lo
stato dal volume, non il codice.
@@ -0,0 +1,67 @@
# 2026-06-02 — Loss-guard per le fade: filtro Hurst (regime persistente)
> Goal: limitare le perdite delle fade in "bassa vol". Diagnosi empirica + ricerca web + workflow
> 11 agenti + test decisivo a livello PORT06. Branch `feat/fade-lossguard`.
## Riformulazione del problema (la premessa era imprecisa)
Diagnosi su 3022 trade fade (MR01/MR02/MR07 × BTC/ETH, 2021+): **le perdite NON si concentrano in
bassa vol** — anzi il terzile low-DVOL è net positivo (+2,30%/trade). Il vero driver è il **regime
PERSISTENTE/trending**, misurato dall'Hurst:
- somma perdite peggiore: **hurst>0,55** (2695% in low-vol, dominante in ogni terzile vol)
- **stop-rate 43% per hurst>0,55 vs 21% per hurst<0,45** (anti-persistente) — 2x
- peggiori 1% trade: Hurst medio 0,61 (77% con hurst>0,55, solo 13% in bassa-DVOL)
## Ricerca web (confermata e smentita dai dati reali)
- **Hurst regime filter** (MR solo H<0,45, evitare H>0,55): **CONFERMATO** sui dati reali. ✅
- **ADX** (PF 1,62 sotto 20 vs 0,74 sopra 30): **NON si replica** — ADX-skip uccide l'edge
(Sharpe 4,82→0,99) e lo stop-rate non scende. ❌
- **vol-expansion ATR-ratio>1,5 (72% perdite)**: **NON si replica** — alza DD e stop-rate. ❌
- **time-stop ~15 barre**: riduce stop-rate ma alza il DD full → non passa standalone. ❌
## Workflow 11 agenti — meccanismi testati
| Meccanismo | OOS Sharpe (base→filt) | DD full | Buon loss-guard? |
|---|---|---|---|
| **Hurst-SKIP h<0,55** | 4,82→4,96 ↑ | 24,3→13,8% ↓ | **SÌ** |
| **Hurst-SIZE 1/0,5/0,25** | 4,65→5,32 ↑ (full) | 33,6→11,3% maxDD ↓ | **SÌ** |
| ADX-skip | 4,82→0,99 ✗ | — | NO (uccide edge) |
| vol-expansion vratio | 4,82→4,04 | 24,3→27,5% ✗ | NO |
| Kaufman ER, time-stop, vol-target, DVOL-rising, combo | tutti ↓ o DD↑ | — | NO |
**Solo l'Hurst** isola chirurgicamente il regime tossico; gli altri sono "dimmer uniformi" che
tagliano winner insieme ai loser (gate FR01 fallito).
## TEST DECISIVO a livello PORT06 — SUPERATO ✅
Applicato l'Hurst-skip alle 6 fade dentro il PORT06 intero (equal-weight, le altre 11 sleeve
invariate):
| Portafoglio | FULL Sharpe | FULL DD | OOS Sharpe | OOS DD | OOS ret |
|-------------|:--:|:--:|:--:|:--:|:--:|
| PORT06 baseline | 6,62 | 4,10% | 8,89 | 1,22% | +175% |
| **+ Hurst-skip h<0,55** | **6,76** | **2,39%** | **9,15** | 1,54% | +158% |
| + Hurst-skip h<0,50 | 6,61 | 2,08% | 9,02 | 1,54% | +150% |
**A differenza di FR01 (che diluiva), il filtro Hurst MIGLIORA il PORT06**: FULL Sharpe ↑, **FULL
DD quasi dimezzato (4,10→2,39%)**, OOS Sharpe ↑ (8,89→9,15). Costo: OOS DD +0,3pp (resta minuscolo),
OOS ret 17pp. **h<0,55 è il pick** (0,50 taglia più ritorno). Non aumenta il profitto: è puro
**rischio** — dimezza il DD mantenendo/alzando lo Sharpe.
## Implementazione
Aggiunto `hurst_skip_mask` in `src/strategies/fade_base.py` (rolling-Hurst causale dalle SOLE close)
+ parametro `hurst_max` (default None=off) in MR01/MR02/MR07. Test: `test_hurst_lossguard.py`.
**Vantaggio operativo decisivo vs FR01:** l'Hurst si calcola **dalle sole close** → nessun feed
DVOL/regime live necessario. Lo `StrategyWorker` lo computa inline dai dati che già ha → **deployabile
senza nuova infrastruttura**, basta settare `hurst_max: 0.55` nei params degli sleeve fade.
## Da fare per attivarlo live (deploy)
1. Settare `hurst_max: 0.55` nei params delle fade in `_defs.py` (sleeve live) + aggiornare i params
fade del backtest (`combine_portfolio`/`report_families`) per PARITÀ + rigenerare il
regression-lock PORT06 (i numeri canonici cambiano: DD 4,9→~2,4%).
2. Verificare che il rolling-Hurst live nel worker coincida col backtest (stessa finestra 100,
stesso stepping causale).
3. Rebuild immagine Docker (`up -d --build`, non restart) + verifica RESUME.
Default attuale: `hurst_max` OFF → zero impatto su backtest/parità/live finché non lo si attiva
esplicitamente. Il SISTEMA è trovato e validato; l'attivazione è una decisione di deploy.
@@ -0,0 +1,89 @@
# 2026-06-02 — Ricerca a 100 agenti: Frattali del segnale × Regime ARGO
> Workflow multi-agente (171 agenti, 8,4M token, ~7h) per cercare una strategia che combini
> un SEGNALE FRATTALE con un GATE/INTERAZIONE DI REGIME ARGO (DVOL/funding/VRP), validata OOS.
> Branch: `feat/fractal-argo-search`. Substrato e codice sul branch, niente impatto su main/live.
## Substrato costruito
- `regime_fetcher.py`: DVOL (2021-03→oggi) + funding (2019→oggi) BTC/ETH da **Deribit mainnet
public** (no-auth, OI/IV reali — non il testnet farlocco di Cerbero).
- `regime_lab.py`: allineamento regime↔prezzo **causale no-look-ahead** (merge_asof backward),
feature regime (dvol_pct, **vrp=dvolrv**, funding_z, dvol_chg) + frattali (rolling Hurst,
Higuchi FD, vol-ratio, Williams pivot), cache feature precalcolate, validazione netto-fee OOS
via `explore_lab`. Bug corretto in corsa: `vrp` annualizzava la realized-vol sempre come 1h →
rotta su 4h/1d (sempre negativa); fix per timeframe.
- `fractal_argo_workflow.js`: 84 agenti griglia (7 famiglie frattali × 6 angoli regime × BTC/ETH)
+ 8 wildcard + verifica avversariale dei survivor + sintesi.
## Verdetto
**Esistono edge frattale×regime reali, causali, robusti** (15 confermati dalla batteria
avversariale: audit look-ahead bit-esatto, cross-asset, split alternativo, fee 0.2% RT, plateau).
**MA nessuno batte PORT06 standalone** (OOS Sharpe 8,19 / DD 2,3%): sono **diversificatori a
bassa esposizione** (1,5-8%, ~100-460 trade), profilo SH01/pairs.
### Top candidati confermati
| Strategia | Asset/TF | OOS Sharpe | OOS DD | trade |
|-----------|----------|:--:|:--:|:--:|
| FRAC-VRP multiscala (chop × VRP<0) | ETH 1h | 5,55 | 11% | 184 |
| HigVRP-Fade (Higuchi alto × VRP<0) | BTC 1h | 4,55 | 7,2% | 286 |
| **HurstCalmFade (hurst<0,55 × dvol<0,4)** | BTC 1h | **3,73** | 5,1% | 198 |
| WILD8 Pivot-Hurst | BTC 4h | 3,87 | 10% | 482 |
| AnalogFundingFade (kNN forma × funding) | ETH 4h | 2,15 | 9,3% | 229 |
## Il finding chiave (controintuitivo): il prior ARGO è SMENTITO
La tesi naïve **"VRP>0 = GEX+ = range = fade" FALLISCE** sistematicamente. L'edge robusto e
ripetuto è su **VRP<0** (vol implicita *sottoprezzata* vs realizzata → mean-reversion whippy) e su
**DVOL bassa**, l'opposto del brief. Gate invertito VRP>0 → Sharpe 2,08/1,30 su entrambi gli
asset. È il risultato più solido di tutta la ricerca, look-ahead-clean (lag-1/3/6 robusto).
## Cosa aggiunge valore vs cosa è decorativo
- **Load-bearing (confermato per ablazione):** VRP<0; hurst-low × dvol-low (HurstCalmFade taglia
DD OOS 17%→5%); funding_z estremo |fz|≥1,8 (analog ETH 4h).
- **Decorativo (DD-reducer, non interazione):** dvol_chg, dvol_high/low e funding come gate spesso
riducono solo esposizione senza migliorare il segno.
## Cosa è RUMORE (conferma i priori del progetto)
- Frattali da soli (angolo=none): hurst/Higuchi/vratio/chop/candle/analog intraday → non robusti
(DD 30-90%, muoiono di fee). Conferma `shape_lab`.
- Momentum/breakout gateato (hurst>0,6, dvol-rising): catastrofico (Sharpe 2…−7) → riconferma
dominanza mean-reversion, i breakout rientrano.
- ARGO-GEX nella direzione attesa (VRP>0): perde. Coerente con W18/19/21 scartate.
- Pivot-fade non-laggato (frac_up[i]): artefatto squeeze-like, va sempre laggato a i2.
## Vincitore selezionato + test decisivo
**FR01 HurstCalmFade BTC 1h** (`scripts/strategies/FR01_hurst_calm_fade.py`): il più verificato,
DD più basso (5,1% OOS), generalizza a ETH. **Test di correlazione decisivo** (la domanda che
conta: aggiungerlo migliora il PORT06 o è ridondante?): correlazione daily-returns coi fade
esistenti **MR01 +0,17 / MR02 +0,08 / MR07 0,03****BASSA, quasi-ortogonale**, NON ridondante.
Passa il gate → vale l'inserimento come diversificatore.
## Onestà finale
L'edge frattale×regime è **reale, causale, robusto** ma è sempre **mean-reversion già nota
condizionata dal regime (VRP<0 / hurst-low / dvol-low)**, non un motore ortogonale nuovo. Valore =
**riduzione DD aggregato come sleeve a bassa esposizione**. La correlazione bassa lo qualifica come
diversificatore reale.
## TEST DECISIVO SUL MASTER — VERDETTO FINALE: NON deployare
Misurato il contributo marginale di FR01 al PORT06 intero (equal-weight, `master_corr`):
| Portafoglio | FULL Sharpe | OOS Sharpe | OOS DD | OOS ret |
|-------------|:--:|:--:|:--:|:--:|
| PORT06 (17 sleeve) | 6,62 | **8,89** | 1,2% | +175% |
| PORT06 + FR01 (19) | 6,55 | **8,72** | 1,1% | +156% |
**FR01 NON migliora il PORT06: lo DILUISCE** (OOS Sharpe 8,89→8,72, OOS ret +175%→+156%; DD
marginalmente meglio 1,2→1,1% ma a costo di Sharpe). Corr FR01 vs MASTER +0,18 (BTC)/+0,23 (ETH).
**Causa + nota di onestà metrica:** lo Sharpe "3,73" dei report del workflow è **per-trade/annuale**
(`explore_lab`); quello rilevante per il portafoglio è lo **Sharpe daily-return** (`combine`), che per
FR01 è solo **~1,85/1,53** — troppo basso per muovere un PORT06 a 8,89. È "ridondanza robusta":
mean-reversion regime-gated che si sovrappone a ciò che il MASTER già fa.
**ESITO: il search a 100 agenti ha trovato strategie robuste e causali, ma NESSUNA migliora il
PORT06.** Non deployare FR01 né i candidati gemelli. Valore del progetto resta nell'estendere
fade/pairs validati. Tutto resta come RECORD DI RICERCA sul branch (non si merge in produzione).
Wiring DVOL live e walk-forward: non necessari, deploy abbandonato.
+176
View File
@@ -0,0 +1,176 @@
# 2026-06-04 — EXIT LAB: ricerca massiva exit dinamiche (34 agenti) → EXIT-16 close-confirm SL PROMOSSO
## Obiettivo
Cercare exit migliori per le fade attive: TP dinamici, SL dinamici/protettivi,
meccanismi per "cavalcare il prezzo". Mandato: ≥20 agenti con specifiche
differenti, guardia anti-overfitting, test su tutto lo storico disponibile.
**Esito in una riga: "cavalcare" non esiste (17ª conferma), ma la ricerca ha
trovato una cosa più grossa — LO STOP-LOSS INTRABAR FISSO È IL COMPONENTE CHE
DISTRUGGE PIÙ VALORE NELLE FADE, e la forma robusta del fix (SL confermato in
chiusura, EXIT-16) è PROMOSSA a livello PORT06: OOS Sharpe 8.82→10.06,
FULL DD 4.10→2.60.**
## Infrastruttura (riusabile)
- `scripts/analysis/exit_lab.py` — harness CONDIVISO: cache segnali live-params
delle 3 fade (`data/cache/exit_lab_signals.pkl`, 73.6k barre 1h 2018→2026-05,
BTC+ETH), engine intrabar identico a `fade_base` (parità verificata al punto
percentuale su tutti e 6 gli sleeve), contratto anti-look-ahead esplicito
(`levels(j)` solo dati ≤ j-1; `after_bar(j)` sul close = eseguibile al poll),
protocollo train/OOS (selezione parametri SOLO su pre-2023-11; OOS letto una
volta), fill parziali, `ExitPolicy` pluggable.
- `scripts/analysis/exit_policies/` — 23 policy + 10 script di verifica.
- `scripts/analysis/exit16_port06_impact.py` — replay canonico `build_trades`/
`fade_daily_equity` con parità ESATTA (corr 1.00000) + innesto EXIT-16 +
ricalcolo PORT06 (cap pairs 0.33, leva 2x).
## Fase 1 — Explore: 23 agenti, una famiglia ciascuno
Tally: **3 PROMISING, 5 NEUTRAL, 15 REJECT.**
REJECT (la famiglia "cavalca il prezzo" è stata falcidiata): trail_atr_ride,
trail_pct, breakeven, sar_trail, tp_decay, tp_extend_momentum, tp_moving_mean,
partial_tp_trail, hurst_exit, giveback, loser_timestop, donchian_trail,
ema_cross_exit, vol_rescale, sl_tp_ride. Autopsia ricorrente: **il TP delle
fade sta ALLA MEDIA; lì il movimento è esaurito per costruzione → oltre quel
punto non c'è coda da cavalcare** (il runner finiva oltre il TP ~3% delle
volte). Caso da manuale `trail_atr_ride`: train 5/6 a favore, **OOS 0/6**
(MR01 ETH ret 134%→1%) — senza l'embargo train/OOS l'avremmo promosso.
NEUTRAL: ratchet, sl_tighten, z_overshoot, wide_sl_trail, swing_stop
(miglioramenti misti, mai 5/6).
PROMISING — tre meccanismi diversi, UNA stessa tesi ("gli stop intrabar da
wick sono falsi negativi: l'overshoot che buca lo stop è esattamente ciò che
la fade vuole fadare, e rientra"):
- **EXIT-02 trail_atr_keep_tp** (chandelier k=1.5 + TP fisso): 6/6 train, 5/6 OOS.
- **EXIT-16 close_confirm_sl** (SL solo se la barra CHIUDE oltre sl0±0.5·ATR):
**6/6 train E 6/6 OOS**, plateau.
- **EXIT-22 no_sl** (diagnostica: SL rimosso): 6/6+6/6, plateau monotono
perfetto `SL stretto < base < SL 2x < niente SL`.
## Fase 2 — Verify: 10 avversari (leakage / overfit / stress × 3 + tail-risk)
- **EXIT-02 CONFUTATO (2/3).** (a) Overfit: è **Pareto-dominato da no_sl a
ZERO parametri** su tutti i 6 sleeve OOS (Sharpe base→trail→noSL: MR01 BTC
2.77→3.08→5.67; MR02 ETH 12.35→11.41→17.24 — il trail PEGGIORA lo sleeve di
punta) → overfit di selezione: la forma parametrizzata sbagliata di una tesi
giusta. (b) Leakage: codice pulito MA **scoperta di metodo: il 54% degli
stop del trail è GAP-THROUGH** (l'open del bar è già oltre il livello;
gap mediano 62-174 bps): l'engine che filla gli stop "al livello" SOVRASTIMA
ogni policy a stop stretti; col fill realistico `worse(level, open)` il
confronto si ribalta. **Da oggi ogni ricerca con stop tight deve modellare
il gap-through.** Lo SL largo della base ne è quasi immune.
- **EXIT-16 SUPERA 4/4.** Leakage: contratto pulito, robusto a 1-3 bar di lag
e al fill a `open[j+1]` (esce deliberatamente tardi → il timing fine è
irrilevante per costruzione). Overfit: plateau monotono su buffer 0.4→1.0
FUORI griglia, il ponte SL-largo k=1.5→4 converge, 4 finestre temporali ok,
e **indipendente dal loss-guard Hurst** (segnali rigenerati senza guard:
la tesi regge anche nel regime trending). Stress: fee 2x 6/6, slippage
20 bps simmetrico 6/6, bear 2021-22 ok.
- **EXIT-22 invalidato SENZA mitigazione (tail-risk).** p99 MAE ~raddoppia
(MR01 ETH 38.8→−59.7%); finestra FTX 2022-11: MR02 ETH **39% (worst 48%,
MAE 70%)** dove la base faceva +2.7% uscendo in 2-3 barre. E un fatto di
wiring: con `sl=0` il worker cade nel ramo `elif max_bars` → **il fallback
2% è codice morto: no_sl live girerebbe SENZA ALCUNO stop**. Il disaster-SL
4× tiene ~95% del ritorno ma il worst pooled resta 47%.
- **Raccomandazione convergente del tail-auditor: EXIT-16** — neutralizza
quasi tutta la coda DA SOLO (p99/worst ≈ base; FTX: +2.4%) mantenendo
l'immunità ai wick. Mai usare uno stop stretto 2% come fallback (cappa
l'edge insieme alla coda).
## Fase 3 — Test decisivo a livello PORT06 (path canonico)
Replay di `build_trades`/`fade_daily_equity` con **parità ESATTA** (corr
1.00000, ritorni identici, PORT06 base == `Portfolio.backtest()` al centesimo:
FULL 6.47/4.10%, OOS 8.82/1.30% — i numeri 6.07/8.19 citati in CLAUDE.md sono
pre-refresh-dati). Poi SOLO il ramo SL sostituito col close-confirm 0.5·ATR:
| PORT06 | FULL Sharpe | FULL DD | FULL CAGR | OOS Sharpe | OOS DD | OOS CAGR |
|--------|------------|---------|-----------|-----------|--------|----------|
| base | 6.47 | 4.10% | 61% | 8.82 | 1.30% | 86% |
| **EXIT-16** | **7.84** | **2.60%** | **79%** | **10.06** | **1.15%** | **102%** |
Gate (lo stesso del loss-guard Hurst): **PROMOSSO** — migliora OGNI metrica,
FULL e OOS. Per-sleeve: ritorni FULL ~4-10x (MR01 BTC 299→1963%) e DD giù su
5/6 (eccezione MR02 ETH +3.4pp, assorbita in aggregato).
## Verdetto e piano di deploy (DA FARE, non ancora implementato)
**EXIT-16 close_confirm_sl, buffer 0.5·ATR14, SOLO sulle 6 fade.**
1. Param `sl_confirm_atr=0.5` nelle fade (default None = comportamento
attuale, per non rompere i backtest canonici/regression-lock).
2. `FadeStrategy.backtest` + `StrategyWorker.tick`: disattivare l'uscita
intrabar a sl0; aggiungere il ramo sul CLOSE del bar (long: esci se
`close < sl0 0.5·ATR14`; short speculare). TP intrabar e max_bars
INVARIATI. ATR14 causale già disponibile.
3. Shadow reale: nessun nuovo tipo d'ordine (il close-confirm esce al close
del worker → market reduce-only come oggi; il limit resting al TP di
v1.0.7 resta identico).
4. DIP01/pairs/rotation/tsmom/shape NON toccati.
## Caveat onesti
- L'uscita al close può essere PEGGIO di sl0 sui veri sfondamenti (già nei
numeri del backtest, ma la coda dipende dai gap: campione 1h crypto,
stress estremi sottorappresentati).
- Niente stop fisico sul book → rischio gap/disconnessione fra i poll
(mitigazione possibile in futuro: disaster-SL intrabar a 4× come cintura,
costa ~5% del guadagno).
- avg_bars +~18% (esposizione su; già nei numeri via non-overlap).
- I numeri per-sleeve FULL (ret 4-10x) vanno letti col solito sconto del
compounding su 8 anni; la decisione è presa sul gate PORT06, non su quelli.
## Scoperte collaterali da NON perdere
1. **Gap-through fill**: l'engine intrabar filla gli stop al livello anche
quando l'open è già oltre → bias sistematico PRO stop-stretti. Aggiungere
il fill `worse(level, open)` all'harness prima di future ricerche su stop.
2. **Wiring worker**: una strategia con `sl=0`+`max_bars` gira nel ramo
`elif max_bars` → il fallback 2% non si applica. Oggi nessuna strategia
attiva emette sl=0, ma il ramo va reso esplicito se mai si deployasse
una policy senza SL.
3. Il prior ladder di stamattina è confermato e generalizzato: 4 conferme
indipendenti che oltre il TP non c'è nulla da cavalcare.
## Costi della ricerca
34 agenti (23 explore + 10 verify + 1 PORT06), ~1.65M token subagente,
~70 minuti wall-clock su 2 core. Workflow: `exit-policy-explore`
(wf_4b23e922-f48) + `exit-policy-verify` (wf_f2bb98df-c43).
## IMPLEMENTATO E DEPLOYATO (sera stessa, v1.1.0)
Param `sl_confirm_atr` (default None = comportamento storico):
- `src/strategies/fade_base.py` `FadeStrategy.backtest` + `scripts/strategies/
MR01_bollinger_fade.py` (ha un backtest proprio, duplicato storico — patch
identica): ramo close-confirm al posto dell'SL intrabar; TP intrabar e
max_bars invariati; in modalità confirm il TP ha priorità nel bar (come la
policy validata in exit_lab).
- `src/live/strategy_worker.py` `tick`: con `sl_confirm_atr` nei params dello
sleeve, TP intrabar al livello come prima; stop SOLO se `close` sfonda
`sl ∓ buf·ATR14(df)`, uscita al prezzo corrente (reason `stop_loss`
invariata → il monitor stop-rate di `hourly_report` continua a funzionare).
- `scripts/portfolios/_defs.py`: `SL_CONFIRM_ATR = 0.5` sulle 6 fade (path
live, come `hurst_max`). Il backtest canonico resta NON filtrato.
- Test: `tests/portfolio/test_close_confirm_sl.py` (wick non stoppa; breach
sul close stoppa al close; TP intrabar resta; buffer regge; regressione
senza param). 59/59 verdi.
**Nota onesta dal sanity-check sul path GREZZO (nessun filtro):** il ritorno
esplode (MR02 ETH cap 366k→3.0M) ma il **DD per-sleeve SALE** (MR01 BTC
32→52%): senza trend-filter/hurst il close-confirm tiene posizione anche nei
trend profondi. La riduzione di DD vive nella config live (trend_max 3.0 +
hurst 0.55) e nella diversificazione — esattamente ciò che il test PORT06
canonico ha misurato (DD 4.10→2.60). Le tre protezioni sono COMPLEMENTARI:
hurst toglie l'esposizione al regime tossico, il trend-filter gli ingressi
sovra-estesi, il close-confirm i falsi stop da wick.
**Monitoraggio live:** lo stop-rate fade su `hourly_report` dovrebbe scendere
sotto il ~38% atteso post-hurst; gli stop avranno exit leggermente oltre il
livello (uscita al close), i TP restano al livello (e nello shadow reale al
limit reduce-only di v1.0.7).
Deploy: commit `a2579d2` (+ research `ad65a0b`), release v1.1.0.
@@ -0,0 +1,63 @@
# 2026-06-04 — Exit ladder (80% del TP → esci 80%, runner con stop dinamico): SCARTATO
## La proposta
"E se all'80% del TP usciamo con l'80% della posizione, mettiamo uno SL dinamico
su quella soglia (profitto lockato) e lasciamo correre il 20% restante?"
Idea: uscita scalare — bancare prima la maggior parte del profitto (la soglia
all'80% viene toccata più spesso del TP pieno) e tenere un runner free-ride con
worst-case alla soglia, best-case oltre il TP.
## Il test (onesto, stessi segnali)
`scripts/analysis/partial_tp_ladder.py`: replay intrabar degli STESSI segnali
delle 3 fade (params live: `trend_max=3.0, ema_long=200, hurst_max=0.55,
min_tp_frac=0.0015`), engine identico a `fade_base` (ingresso a `close[i]`,
SL pieno prioritario nello stesso bar, fee 0.10% RT × leva 3 — il ladder NON
paga fee extra: due uscite ma stesso notional). Convenzioni conservative: se il
bar che tocca la soglia chiude oltre (rientro), il runner è stoppato subito.
Griglia: base vs ladder (f=0.8 q=0.8 — la proposta —, f=0.8 q=0.5, f=0.5 q=0.5),
FULL + OOS (nov 2023→), BTC+ETH.
## Risultati (proposta f0.8 q0.8)
| Sleeve | ret% FULL base→ladder | DD% | ret% OOS | win% OOS |
|---|---|---|---|---|
| MR01 BTC | 92 → 92 | 13.8 → 10.0 | 41 → 39 | 60 → 65 |
| MR01 ETH | 194 → 195 | 16.5 → 15.3 | 134 → 147 | 70 → 75 |
| MR02 BTC | 129 → 112 | 19.0 → 17.2 | 66 → 54 | 55 → 59 |
| **MR02 ETH** | **2135 → 1319** | 16.2 → 12.7 | **893 → 651** | 75 → 80 |
| MR07 BTC | 78 → 78 | 6.8 → 5.2 | 43 → 40 | 64 → 66 |
| MR07 ETH | 115 → 96 | 10.6 → 10.6 | 68 → 64 | 66 → 70 |
## Verdetto: NO-GO (gate fallito)
1. **Il runner non corre.** Su centinaia di trade finisce oltre il TP quasi mai
(~3% dei partial; 0-15 casi per sleeve) e viene **stoppato alla soglia nel
~60% dei tocchi**. È la fisica mean-reversion: il TP delle fade sta ALLA
MEDIA — lì il movimento è esaurito per costruzione. Il runner è un
trend-follow della coda, e i breakout rientrano (lezione squeeze).
2. **Il costo è concentrato sui winner migliori**: ogni TP pieno ora esce
all'80% della strada sulla quota grossa → MR02 ETH (lo sleeve più forte)
38% ret FULL / 27% OOS. Sharpe per-trade ~piatto: non migliora il
rischio/rendimento, scambia ritorno con DD.
3. **Quello che compra** (win-rate +4-5pp, DD 1-4pp) è lo stesso profilo del
**vol-target sizing già scartato** ("abbassa il DD ma sacrifica ritorno");
il lavoro anti-DD lo fa già il loss-guard Hurst, che il gate l'ha passato
perché taglia SOLO i loser.
Nota dal grid: `f0.5 q0.5` su MR07 ETH fa Sharpe OOS 6.58 / win 82%, ma
distrugge MR02 (2135→531) → nessuna variante robusta su tutti gli sleeve,
niente cherry-picking per-sleeve.
Argomento esecutivo in più: il ladder raddoppierebbe la complessità dello
shadow appena deployato (v1.0.7: limit reduce-only al TP) — due resting order
per posizione, due riconciliazioni, e lo stop dinamico avrebbe il problema dei
trigger Deribit (nuovo order_id al trigger → fill non verificabile per
order_id).
**Conferma della lezione di famiglia:** i tweak d'exit che toccano i winner
falliscono il gate (time-stop, vol-target, ADX, vratio… e ora il ladder);
l'unico anti-perdite che passa resta il loss-guard Hurst, che riduce
l'esposizione nel regime tossico senza toccare le uscite.
@@ -0,0 +1,92 @@
# 2026-06-04 — Shadow execution: divergenza sim/reale misurata + TODO limit reduce-only
## Deploy v1.0.6
Fix float deployata (`74670da` + release): `_quantize_step` con `Decimal` in
`src/live/execution.py``72*0.001 = 0.07200000000000001` causava `Invalid params`
su Deribit (1 `REAL_OPEN_FAIL` su MR07 il 2026-06-03). Resume pulito al restart.
## Misura della divergenza sim vs reale (primi 7 close shadow)
**Bilancio: PnL sim +11.85 USD vs reale +0.62 USD → delta 11.22 USD.**
Le fee NON sono il problema ($0.63 totali = 0.05%/lato, esattamente l'assunto del
backtest). Il problema è tutto nel **timing delle uscite**.
- **Aperture allineate**: slippage da 8 a +27 bps, mediana ~±7 bps. OK.
- **Chiusure take-profit: +235 bps di slippage medio** (MR07 ETH short: +257/+267/+283/+333 bps).
Causa strutturale, non liquidità: il sim esce *al livello TP* intrabar (come il
backtest), il reale chiude **a mercato solo al poll del tick**, quando il prezzo è
già rientrato (TP toccato a ~1790, fill reale a ~1840). Sim +3 USD a trade, reale ~0.
- **Stop-loss: stessa meccanica ma a favore** (MR02 SL: 0.68 reale vs 2.87 sim).
Le fade però hanno più TP che SL → netto negativo.
- **2 `REAL_CLOSE` non verificate** (order_id null, fallback su stima sim). Venue flat
(`get_positions()=[]`), nessuna posizione orfana — ma sorvegliare i prossimi close.
**Implicazione onesta: l'edge di MR07 non sopravvive all'esecuzione market-on-poll.**
Il backtest assume il fill al livello TP; il reale incassa il prezzo post-rimbalzo.
## TODO (richiesto, da implementare)
Nello shadow-execution (`src/live/execution.py` + `StrategyWorker`): all'apertura
della posizione reale piazzare su Deribit un **LIMIT reduce-only appoggiato al
livello TP** (e idealmente stop-market per lo SL), invece di chiudere a mercato al
poll — così il fill reale replica l'assunzione intrabar del backtest.
Dettagli da considerare:
- cancellare/aggiornare l'ordine resting se il sim esce per `max_bars` o al restart (resume);
- i 3 fade BTC condividono lo strumento (posizioni nettate per conto): il limit
reduce-only deve coprire la SOLA quota del worker, come già fa `close_amount`;
- verificare il fill del resting order via `get_trade_history` (order_id), come per i market.
## IMPLEMENTATO (sera): LIMIT reduce-only al TP
**Flusso nuovo.** A ogni `REAL_OPEN` verificato il worker piazza subito un
**limit reduce-only al livello TP** (lato opposto, stesso `amount` della SOLA
quota del worker — strumento condiviso fra i 3 fade per coin) → evento
`REAL_TP_RESTING` (o `REAL_TP_FAIL` → si resta sul vecchio market-on-poll).
Alla chiusura sim, qualunque sia la ragione, `_real_close` ora:
1. **cancella** il resting (innocuo se già fillato — e dopo il cancel nessun
fill può più arrivare, niente race);
2. **riconcilia i fill** (anche parziali) dal `get_trade_history` per order_id
(stessa fonte autorevole dei market), con retry se il cancel risulta
"già fillato" ma la history è in ritardo;
3. chiude a **market reduce-only solo la quota residua** (exit non-TP:
stop_loss/time_limit, o TP non raggiunto sullo strumento d'esecuzione);
4. ledger reale su **prezzo d'uscita combinato** (media pesata fill-TP +
market) e fee sommate. `REAL_CLOSE` logga `tp_order_id`,
`tp_filled_amount`, `market_amount`.
**Dettagli del TODO coperti:**
- `max_bars`/`stop_loss` → il cancel è il passo 1 di ogni chiusura;
- restart → `real_tp_order_id` è **persistito** in `status.json`: al resume il
resting resta in book e la prima chiusura lo riconcilia normalmente;
- quota del worker → il limit usa lo stesso `amount` dell'apertura (mai
`close_position`); il residuo è ri-quantizzato allo step (Decimal) dentro
`close_amount` (niente artefatti float tipo `0.072-0.024`);
- prezzi limit quantizzati al **tick** dello strumento (`quantize_price`,
BTC_USDC 0.5 / ETH_USDC 0.05 — Deribit rifiuta prezzi off-tick).
**SL: niente stop-market, decisione deliberata.** (a) Su Deribit un trigger
order, quando scatta, genera l'ordine eseguito con un **nuovo order_id** → il
fill non sarebbe verificabile via `get_trade_history` per order_id (il
requisito di verifica); (b) la misura di stamattina mostra che sul SL il
market-on-poll lavora **a favore** (il rimbalzo riduce la perdita). Lo SL resta
quindi market-on-poll.
**Bonus fee.** Quando il TP filla da resting il fill è **maker** (~0% su
Deribit) invece di taker 0.05% — il reale farà meglio dell'assunto 0.10% RT
sulle uscite TP.
**Verifica.** `live_shadow_smoke.py` esteso ai due percorsi e passato su
testnet: (A) resting in book → exit `time_limit` → cancel + market fallback,
conto flat; (B) TP già crossato → fill immediato del limit, chiusura
riconciliata dalla history **senza ordine market** (`market_amount=0`),
prezzo persino migliore del TP (+48 bps, taker sul cross). 54 test pytest OK.
**Caveat residuo onesto.** Se il processo muore DOPO il fill del resting e
PRIMA della chiusura sim, il worker al resume riconcilia al prossimo exit sim
(corretto); ma nell'intervallo la quota reale è già chiusa mentre il sim la
crede aperta — finestra breve e solo contabile. Inoltre il TP del sim è sul
feed (`BTC-PERPETUAL` inverse) mentre il limit vive sul lineare USDC: la base
fra i due può far fillare/non fillare il limit a cavallo del livello — il
fallback market copre il caso.
@@ -0,0 +1,67 @@
# Diario — 2026-06-05 — Fix EXIT-16 live: confirm sul close di barra COMPLETATA + alert STALE_FEED
## Innesco
Crash ETH 2026-06-05: gli stop MR01/MR02 ETH sono scattati molto sotto il livello
nominale (MR02: sl 1636.28 → exit 1600.45; MR01: sl 1610.75 → exit 1594.35).
Analisi delle due concause dichiarate:
1. **EXIT-16 close-confirm** (by design): uscita al close oltre `sl ∓ 0.5·ATR14`,
non al livello — costo accettato e MODELLATO nel backtest validato (7.84/10.06).
2. **Feed gappato**: ETH testnet flat ~2h (1656.80/1655.55, 13:0014:50 UTC), poi
una candela 14:55 da 1634 a 1600 — nessun prezzo esisteva tra SL ed exit. I fill
REALI confermano (1600.2 / 1589.7, slippage 1.6/29 bps): non è un bug, è il mercato.
## Scoperta: divergenza implementativa REALE nel worker
Audit dei 3 stop ETH del 2026-06-04/05 contro le candele:
| Stop live | Close barra COMPLETATA al momento | Confirm | Verdetto |
|---|---|---|---|
| MR02 06:03 @1672.9 | 1682.25 (05:00) | ~1675.4 | **wick-stop**: il backtest NON avrebbe stoppato lì |
| MR02 15:00 @1600.45 | 1600.05 (14:00) | ~1621 | legittimo, fill ≈ backtest |
| MR01 15:01 @1594.35 | 1600.05 (14:00) | ~1594.4 | **wick-stop**: il backtest NON avrebbe stoppato lì |
**2 su 3 erano valutazioni sulla barra IN FORMAZIONE**: `tick` confrontava
`current_price` (close istantaneo della candela in corso al poll) con `sl buf`.
Il backtest (`fade_base`) valuta il confirm SOLO sul close di barra completata
`c[j]`. La differenza reintroduce live esattamente la wick-sensitivity che EXIT-16
elimina: un dip infrabarra sotto il confirm stoppava anche se la barra chiudeva
sopra (e nei dip-che-rientrano la fade vince — è il suo edge). Live sarebbe stato
strutturalmente PIÙ stop-prone del backtest validato.
## Fix (`strategy_worker.py`, ramo sl_confirm_atr)
- Il confirm è valutato sul **close dell'ultima barra COMPLETATA**: l'ultima riga
del df è la candela in corso finché non è trascorsa la sua durata
(`now_ms < ts[-1] + bar_ms`, `bar_ms` = mediana dei delta timestamp → regge 1h/4h/1d).
- Il `buf` usa l'ATR14 alla stessa barra completata (prima: barra in formazione).
- Il **fill resta al prezzo corrente** al poll (lag ≈ stress `lag_close_exit`,
superato in exit-lab). TP intrabar sulla barra in corso INVARIATO (= backtest).
- Test nuovi in `test_close_confirm_sl.py` (sezione FIX 2026-06-05): il dip della
candela in corso non stoppa; il breach della completata stoppa anche se rimbalzato;
TP intrabar invariato. NB fixture: timestamp in ms ESPLICITI
(`Timestamp.now()` è risoluzione µs → `astype int64 // 10**6` darebbe secondi).
## Concausa feed: entry-guard TESTATA e BOCCIATA, solo osservabilità
Quantificato (1h, storico completo): segnali fade subito dopo barre flat = BTC
0.20.9%, **ETH 3.411.8%**. Backtest con skip di quei segnali (config live,
sl_confirm 0.5): **PEGGIORA tutti gli sleeve ETH** (MR01 +1344→+1256, MR02
+2372→+2250, MR07 +740→+621; BTC ~invariato). La candela-gap post-flat è
l'overshoot che la fade fada con profitto — e i fill reali post-crash lo
confermano (riaperture a 1.9 bps di slippage). **Niente skip degli ingressi.**
Aggiunto invece **alert Telegram `STALE_FEED`** (`runner._check_stale_feed`):
notifica quando un asset ha ≥2 barre 1h complete flat (worker ciechi) e al
risveglio col **gap %** del primo prezzo reale. Una notifica per episodio
(dedup per asset). Solo osservabilità, zero effetto sulle decisioni.
## Cosa NON è stato toccato (e perché)
- Il **costo del confirm** (uscita al close, non al livello nominale) resta: è il
trade-off validato di EXIT-16 (FULL 6.47→7.84, OOS 8.82→10.06) — i wick-stop
evitati pagano molto più dei close-stop più profondi.
- Il **gap-through** non è mitigabile lato exit: nessun prezzo esisteva tra SL ed
exit (fill reali ≈ sim). L'unica protezione strutturale resta la diversificazione
(17 sleeve) + i cap di famiglia.
@@ -0,0 +1,99 @@
# Diario — 2026-06-05 — SL su SH01: ricerca multi-agente (verdetto: NO-SL, cap del peso)
## Innesco
Crash ETH del 2026-06-05 (1742→1546, 11%, con feed testnet FLAT ~2h e gap 1655→1600):
SH01 ETH si è preso la coda intera, **15.6% in un trade** (long 1727.8 → time_limit 1594.35,
leva 2x) — il singolo trade peggiore del portafoglio live. SH01 non ha TP/SL per design
(exit solo a orizzonte H=12). Domanda: esiste uno SL che taglia le code senza distruggere
l'edge (win ~50%, edge tutto nell'asimmetria dei winner)?
## Numeri storici per anno (config live W24 H12 th0.58, netto fee 0.10% RT, leva 3x)
Somma per-trade in pp (leva 3x); equity-level ≈ ×0.15.
| Anno | BTC | ETH |
|---|---:|---:|
| 2018 | 65.8 | +73.7 |
| 2019 | +87.7 | 19.3 |
| 2020 | +194.0 | **293.1** |
| 2021 | +301.3 | +67.4 |
| 2022 | +64.3 | +79.1 |
| 2023 | +17.4 | +20.6 |
| 2024 | +110.0 | +108.1 |
| 2025 | +76.8 | +539.7 |
| 2026 | +59.0 | 29.5 |
BTC = motore robusto (8/9 anni+). ETH = fragile (DD 61%, win 48.8%): la coda non
protetta è un problema STRUTTURALE di ETH, non solo dell'episodio live.
## Infrastruttura (riusabile)
`scripts/analysis/sh01_exit_lab.py` — harness onesto stile exit-lab:
- cache segnali walk-forward (`data/cache/sh01_exit_lab.pkl`, 7248 BTC + 7386 ETH entries);
- engine intrabar con **fill GAP-AWARE**: stop fillato a `worse(livello, open[j])`, non
"al livello" — chiude il bias PRO-stop-stretti documentato il 2026-06-04 (54% di fill
ottimisti) e modella il caso reale del crash (gap 1655→1600 senza scambi intermedi);
- modalità stop `intrabar` e `close` (close-confirm stile EXIT-16), `after_bar` per
policy discrezionali, stress `lag_close_exit` (poll in ritardo);
- parity ESATTA con `explore_lab.simulate` (BTC +218.50%/1531 trade, ETH +80.13%/1257);
- protocollo: TRAIN fino 2023-11-01 (selezione SOLO lì, plateau ≥3 celle), OOS una volta.
Baseline (orizzonte puro): BTC TRAIN +127%/dd23/shrp2.09/worst5.5 | OOS +41%/8/2.18/3.1.
ETH TRAIN **26%/dd61/shrp0.16/worst14.9** | OOS +143%/7/**3.60**/4.6.
## Esito: 11 famiglie testate, 0 sopravvissute
Policy in `scripts/analysis/sh01_exit_policies/` (01-10 dagli agenti + 11 dal
completeness-probe della sintesi):
| # | Famiglia | Verdetto |
|---|---|---|
| 01 | ATR fisso intrabar (k 15) | NO: 0/7 celle migliorative sul train |
| 02 | ATR fisso close-confirm (k 15) | NO: k=4 migliora ETH train (shrp 0.16→+0.62, DD 61→41, worst 8.4) MA ETH-OOS peggiora (shrp 3.60→2.32, worst 6.6) |
| 03 | % fisso (15%, 2 modi) | NO: BTC↓, ETH-OOS ret 52% del baseline |
| 04 | Chandelier trailing | NO: cella isolata; ETH-OOS tutto peggio (4ª bocciatura della famiglia trailing) |
| 05 | Breakeven ratchet | NO: 0 celle migliorative |
| 06 | Giveback (profit-protection) | NO: dormiente sulle code (worst invariato), taglia winner BTC |
| 07 | Loser time-stop (m,x) | NO: ETH-OOS shrp↓ DD↑, BTC↓ |
| 08 | Disaster-cap LARGO close-confirm (k 36) | NO: k=4 plateau sul train ma ETH-OOS DD 7→11-15%, ret 59-74%; FABBRICA short da stop a 44/54% |
| 09 | Swing strutturale | NO: 0/18 celle |
| 10 | Stop condizionale vol-regime | NO: il migliore dei falliti (ETH-OOS shrp 3.28 vs 3.60) ma BTC train <95% |
| 11 | Disaster-cap LARGO **intrabar** (k 512) | NO: k=10 cella isolata; BTC-OOS shrp 2.18→**0.92**, worst 3.1→−11.4 (il fill gap-aware rende lo stop più tossico del no-stop) |
**Pattern (5ª conferma della lezione exit-lab):** ogni stop abbastanza stretto da toccare
la coda ETH intacca il motore BTC; ogni stop abbastanza largo da risparmiare BTC non
arriva mai alla coda ETH. Per un segnale win~50% con edge nell'asimmetria dei winner,
OGNI SL taglia i winner-in-drawdown insieme ai loser. In più il fill gap-aware mostra
che proprio nei crash (quando lo stop servirebbe) il fill è al gap, non al livello:
lo stop intrabar largo PEGGIORA la coda OOS di BTC invece di proteggerla.
## Decisione
- **NO SL su SH01** (né `sl``sl_confirm_atr` per SH_BTC/SH_ETH: corretto E sicuro —
il fallback 2% del worker si applica solo se `sl` è presente).
- **La leva giusta è il PESO**: cap della famiglia SHAPE nel weighting di PORT06
(`weighting.py` supporta già `caps` per famiglia, come PAIRS 0.33). Dimezzare la quota
SHAPE ≈ dimezza l'impatto della prossima coda 15% sul conto, a costo ~nullo di Sharpe
(SH01 è un diversificatore, corr +0.08 col MASTER).
- **APPLICATO E DEPLOYATO (stesso giorno):** `caps={"PAIRS": 0.33, "SHAPE": 0.0588}` in
`_defs.py` (SHAPE 11.8%→5.9%, ≈ mezzo sleeve equal; SH_BTC=SH_ETH=0.0294, somma pesi 1.0,
verificato). Quantificato sul backtest PORT06:
| Config | FULL Sharpe | FULL DD | CAGR | OOS Sharpe | OOS DD |
|---|---:|---:|---:|---:|---:|
| precedente (SHAPE 11.8%) | 6.47 | 4.10% | 61.2% | 8.82 | 1.30% |
| **cap SHAPE 5.9% (scelto)** | 6.43 | 3.96% | 62.1% | 8.58 | 1.36% |
| SHAPE rimosso | 6.30 | 3.88% | 63.0% | 8.26 | 1.41% |
Il costo (0.24 OOS Sharpe) è il premio dell'assicurazione su una coda che il backtest
daily NON modella (gap di feed, flat testnet). Rimuovere SHAPE costa troppo (8.26).
## Direzioni future (fuori scope SL, annotate dalla sintesi)
1. **Liquidity/staleness-gate sull'ENTRY** (skip ingressi dopo finestre di feed flat —
il flat 2h ha preceduto il gap del 2026-06-05): la direzione più promettente per il
gap-through specifico, merita uno studio dedicato.
2. Vol-target sizing per-trade su SH01 (sulle fade fallì, su SH01 mai testato).
3. SL solo-ETH: scartato anche concettualmente (il beneficio coda ETH è marginale/nullo
OOS in TUTTE le famiglie — non è un problema di uniformità del meccanismo).
@@ -0,0 +1,71 @@
# 2026-06-06 — Improvement sweep multi-agente (review live PORT06)
> Ricerca multi-agente del 2026-06-06 (106 agenti, 196 finding grezzi, 60 verificati avversarialmente, 37 validi / 23 uccisi).
> Workflow: port06-improvement-sweep (wf_56b7d4de-a06).
## 1. Quadro d'insieme
Il sistema PORT06 gira correttamente nella struttura (17 sleeve, runner pool/ledger == backtest), ma la review ha trovato **due classi sistematiche di problemi**, non bug isolati.
**(a) Divergenze live-vs-backtest per omissione di protezione.** Il pattern documentato del progetto è "il live AGGIUNGE protezione che il backtest canonico non ha" (Hurst, EXIT-16) → il live fa meglio del backtest sul DD. Qui si trova il contrario in più punti: il filtro `trend_max=3.0` è ON nel backtest canonico ma **OFF in produzione su tutte e 6 le fade** (base.py:91 / _defs.py:35-38); il cap `SHAPE=0.0588` v1.1.2 è **inerte** (sovrascritto da portfolios.yml) → SH01 gira a **2x peso** senza stop-loss; EXIT-16 non è esteso a DIP01 (l'unico sleeve BTC che esegue davvero); la forming-bar lesson non è portata a TR01 né a PairsWorker. Diversi diari (2026-06-04-partial-tp-ladder.md:15, 2026-06-04-exit-lab.md) documentano config che il codice NON implementa.
**(b) Calibrazione/sizing che diverge dal validato.** SH01 fa fuoco sul 25% delle barre vs 2.9% validato (th0.58 inerte per train-window troppo corto); i pairs girano a `pos=0.5` mentre tutta la validazione PR01 è `pos=0.15` (DD ~3.3x); TR01 sotto-carica le fee 2x (manca il fattore leva).
Tema ricorrente onesto: **la maggior parte delle "scoperte" sono ripristini di config già validate, non nuovo alpha.** I bug di config (cap, trend_max) vanno corretti subito; ogni cambio di *strategia* deve passare il gate PORT06/OOS (precedente FR01: robusto-individualmente ≠ migliora PORT06).
## 2. P0 — Protezione capitale / gap live
| Titolo | Cosa fare | Evidenza | Effort |
|---|---|---|---|
| **Cap SHAPE 0.0588 inerte → SH01 a 2x peso** | `load_active_portfolio` deve fare **deep-merge** dei caps (partire da `p.caps` e aggiornare con `ov["caps"]`), oppure ridichiarare il dict completo in `portfolios.yml:7` come `{PAIRS:0.33, SHAPE:0.0588}`. Ri-snapshot pesi, confermare sum=1.0. SH01 non ha SL by-design e ha già subìto un 15.6% live: il cap è l'UNICA protezione coda. | `src/portfolio/base.py:91-93` setattr sovrascrive caps; `portfolios.yml:7` `{PAIRS:0.33}`; `scripts/portfolios/_defs.py:75` `{PAIRS:0.33,SHAPE:0.0588}`; live `data/portfolios/PORT06/status.json` SH_BTC=SH_ETH=0.0588 (totale 0.1176 = 2.0x intended). Backtest canonico usa il cap giusto (FULL 6.43/OOS 8.58) → è una rottura di parità. | low |
> Nota: la stessa correzione risolve anche il sotto-peso ~9% di tutti gli altri non-pairs (0.0588 live vs 0.0647 intended); il rischio materiale è solo SHAPE.
## 3. P1 — Alto valore, testabile subito
| Titolo | Cosa fare | Evidenza | Effort |
|---|---|---|---|
| **`trend_max=3.0` OFF in produzione su tutte le 6 fade** | Aggiungere `"trend_max":3.0, "ema_long":200` alla SleeveSpec FADE `_defs.py:36-37`. Ripristina la config del backtest canonico (look-ahead-free, EMA200 causale). MR02/MR07 lo onorano (non lo ignorano). Ri-validare a livello PORT06 (template `exit16_port06_impact.py`) perché Hurst+trend si sovrappongono. | `_defs.py:35-38` (no trend_max); `MR01_bollinger_fade.py:61` `get('trend_max')`→None; `combine_portfolio.py:47` canonico usa trend_max=3.0; `risk_management.py:242` lo applica a tutte e 3. Live: MR01_ETH/MR02_ETH hanno fadato il crash ETH 06-05 più volte (long@1658.9 poi long@1592.8 in caduta 1727→1517), tutti stoppati (trades.jsonl). Misurato (live-hurst-on, 3x): MR02_ETH DD 27.3%→16.2%, WR +8pp, Sharpe +1.34 (return ~dimezzato — trade-off DD/return). | low |
| **Disaster-bracket on-book durante outage feed** | Il poll-loop intero è in un try/except: in un outage le posizioni REALI restano **senza stop sul book e senza valutazione exit**. Piazzare un bracket lontano (~30%) su Deribit per i fade eseguiti [MR01,MR02,MR07,DIP01] via `cerbero_client.py:137 set_stop_loss` (esiste, non cablato). Non spara mai in operatività normale → 0 costo Sharpe; pura assicurazione. | `runner.py:246-298` (try/except unico, sleep su errore); ETH gap p99.9=996bps, max=3301bps, peggior 3h 3492bps (`data/raw/eth_1h.parquet`); REAL_OPEN 06-05 verificati; `docs/diary/2026-06-04-exit-lab.md:119-121` lo segnala, mai implementato. Caveat: nei crash il fill è al gap, non al livello → cappa ma non al trigger. | low-med |
| **SH01 th0.58 inerte: fa fuoco sul 25% delle barre vs 2.9% validato** | **Ri-validare SH01 OOS con train-window pari al regime live (~8760 barre)** invece dell'expanding full-history. La LogReg su train piccolo è over-confident (mean max-proba 0.582 vs 0.536). NON limitarsi ad alzare th (la calibrazione resta sbagliata). Se l'edge non sopravvive nel regime live, riconsiderare SH01. | misurato: small-train frac≥0.58 = 44.8% vs full 3.8%; live-window 1197 entry (49.9% long) vs 139 (71.9% long), trade-rate 25.1% vs 2.9%. `SH01_shape_ml.py:60` th=0.58; tutta la validazione (`shape_ml_validate.py`) usa training expanding. È la causa meccanica del WR live 1/9. Blast radius limitato dal cap SHAPE (vedi P0). | low (test) |
| **Pairs a pos=0.5 vs validazione pos=0.15 (DD ~3.3x)** | Introdurre un `position_size` per-famiglia (plumb `SleeveSpec`/`build_worker_for`, runner.py:43-89) e portare i pairs a **0.15-0.25**. Ri-eseguire `combine` alla pos live prima del deploy. I pairs sono la famiglia a DD-grezzo più alto (ETH/BTC 47.6%) e **senza stop** (exit solo |z|≤z_exit o max_bars). | `portfolios.yml:14` pos=0.5; `pairs_worker.py:157` pnl=cap*pos*net; `pairs_research.py:29` POS=0.15; live ADA/ETH 06-05 net 8.52% = 4.26% sleeve (sarebbe 1.28% a 0.15). Choice deliberata (commento yaml), ma diverge dal validato. | low-med |
| **TR01: fee sotto-caricate 2x (manca leva)** | Moltiplicare il termine fee per `self.leverage` (basket_trend_worker.py:69) per allinearlo al return leveraged (riga 67) e al reference `fee=FEE_RT/2*LEV` (honest_improve2.py:150). | worker fee/flip 1.5e-05 vs corretto 3.0e-05 (POS0.15/LEV2/N5) = 2.0x. tsmom/rotation_worker sono consistenti → TR01 è l'unico difettoso. Drag assoluto piccolo (low turnover) ma bias ottimistico mono-direzionale che gonfia total_capital nel rebalance. | low |
| **PairsWorker agisce sulla barra IN FORMAZIONE** | Portare la EXIT-16 forming-bar logic ai pairs: calcolare `bar_ms` da `median(diff(ts))` e valutare z sull'ultima barra COMPLETA (entry+exit), come `strategy_worker.py:445-447`. | `pairs_worker.py:185` i=len(m)-1, :195 exit, :204-207 entry su barra in formazione; backtest `pairs_research.py:69-75` solo close settled; `validate_worker_pairs.py` non esercita il path → divergenza non testata. | low |
| **TR01 valuta crossover EMA sulla barra 4h in formazione** | In `tick` scartare la barra in formazione (`k=-1 if now_ms≥ts[-1]+bar_ms else -2`) e valutare il segnale sull'ultima 4h COMPLETA; correggere ANCHE il booking del return (riga 66 legge la finestra in formazione). | `basket_trend_worker.py:60-72` legge `[-1]` (forming); reference `honest_improve2.py:152` `range(n-1)` solo completi. Evidenza: SOL flip 0→1@85.66 poi 1→0@77.72 a 59min, stessa finestra 08-12 UTC (9.3% intra-window glitch). Fix incompleto se si tocca solo il segnale. | low |
| **DIP01 senza EXIT-16 (ed è l'unico sleeve BTC che esegue reale)** | Aggiungere `"sl_confirm_atr":0.5` (e valutare `hurst_max`) ai params DIP01 `_defs.py:41-42` (il worker lo legge già, zero codice). **Ri-validare sul grid OOS proprio di DIP01 + a livello PORT06 con engine gap-aware** prima del deploy (DD standalone sale BTC 23→37%). | `_defs.py:41-42` DIP01 ha solo {min_tp_frac}; worker gira il branch intrabar wick-sensitive (`strategy_worker.py:466`) invece del close-confirm (:429). Riproducibile: BTC FULL 299→726/OOS 60→168, ETH FULL 187→898/OOS 221→255. I 3 fade BTC sono flat (0 ordini reali); DIP01_BTC è l'UNICO BTC con round-trip reale → gap non teorico. | low (fix) / med (validazione) |
| **TSM01 252d lookback: shortfall su un asset tronca l'inner-join sotto need=253, tick() esce in silenzio** | Aggiungere un WARN/Telegram quando `panel<need` (gated su "era già operativo" per evitare falsi positivi al cold-start). Coprire anche RotationWorker (stesso silent return). | `runner.py:37` 1d=440; `tsmom_worker.py:53-55` need=253, return muto; `rotation_worker._panel how='inner'` tronca al più corto; live margine 188 (OK ora) → latente, non attivo. | low |
| **Osservabilità: TR01/ROT02/TSM01 invisibili in hourly_report** | `hourly_report.collect()` filtra su chiavi (`event`, `in_position`) che i worker multi-asset non emettono → 100% delle loro righe scartate. Aggiungere `in_position` ai `_save()` dei 3 worker e/o esporre `status_summary` (long correnti + giorni-dall'ultimo-flip). Distingue "flat in chop" da "wiring rotto". | TR01 2 record (entrambi 06-03, lo SOL whipsaw), flat da allora; ROT02/TSM01 0 trade (rispettivamente cash / risk-off da 06-01, by-design). `hourly_report.py:105,111`; worker `_log`/`_save` senza `event`/`in_position`. | low |
| **MR07 reale: ripristino-fill non esercitato (short-side TP-resting mai testato)** | Smoke testnet `live_shadow_smoke.py` con `direction=-1` per esercitare il limit reduce-only BUY sotto mercato (può crossare subito → path `resting_fills`). MR07 (+ tutti i worker) ha 0 TP-resting filled finora → reconciliation completamente non testata in produzione. | MR07_ETH 5-6 REAL_OPEN tutti 06-03 PRE-fix (TP-resting 2026-06-04); +257/267/283/333bps slippage legacy (sunk, non ricorrente). Tutti i REAL_TP_RESTING verificati sono side=buy (long); il path short non è mai stato esercitato né in ledger né negli smoke. `strategy_worker.py:253` `_place_real_tp`. | low |
## 4. P2 — Filoni di ricerca
- **MR01 ETH k=2.0 vs k=2.5 deployato.** k=2.0 è uniformemente meglio OOS su ETH (no-filtro +1238% vs +487%; trend-filtrato +569% vs +196%) e neutro su BTC sotto filtro live. Non è nuovo overfit: è il config per-asset *documentato* (MR01:15-16) che il k=2.5 universale ha silenziosamente abbandonato. Da confermare a livello PORT06 con i param live (hurst/sl_confirm). `risk_management.py:40,44`.
- **Limit-order maker entry sui fade — NO-GO confermato.** Strutturalmente infattibile: il segnale fade fa fuoco quando il close ha già bucato la banda (mediana 33bps sotto); un buy-limit al livello è marketable→taker; un limit più profondo manca i winner che rimbalzano in 1-4 min. Negativo-EV, non implementare. `MR01_bollinger_fade.py:84,93`; live MR07_ETH 5 open→TP in ~1.4min.
## 5. P3 / Scartati in verifica
- **Commento errato in `_defs.py:17-18`** ("MR02/MR07 ignorano min_tp_frac") — falso (lo leggono e applicano, MR02:46,72), ma il filtro è di fatto inerte su MR02/MR07 nel campione → solo igiene doc, no PnL.
- **TSM01 inerte (0 trade da 06-03)** — corretto by-design (BTC sotto SMA100 dal 06-01 + tutti gli asset consensus negativo). Solo diagnostica; NON redistribuire il capitale (riduce la diversificazione quando il regime gira).
- **Time-limit fade NON sono un leak** — myth-busting: il bucket max_bars è net-positivo OOS (+118÷+195bps, +154%/+335% totale); il vero loser è lo SL (già gestito). NON accorciare max_bars.
- **Fill max_bars next-open vs close** — wash misurato (5/6 sleeve identici); il worker già filla ~next-open. Nessuna azione.
- **SH01 H=12 fuori scope `exit_lab.py`** — routing: usare `sh01_exit_lab.py` (gap-aware) per qualsiasi studio orizzonte SH01. Clarificazione, non bug.
- **PairsWorker bars_held conta poll-con-nuovo-ts non barre reali** — latente (0 time_limit su 9 close), bite solo dopo outage >2h con z ancora oltre soglia; rischio di introdurre exit-anticipato se bar_ms via median-diff su feed testnet flaky → usare bar_ms fisso. Igiene, bassa priorità.
## 6. Piano consigliato (2 settimane)
**Settimana 1 — protezione capitale e parità (config-only, niente cambio di strategia):**
1. **Giorno 1 — P0 cap SHAPE.** Deep-merge dei caps in `base.py:91`; ri-snapshot pesi PORT06, confermare SH totale=0.0588, sum=1.0, nessuno sleeve affamato. Verificare contro il backtest canonico (FULL 6.43/OOS 8.58). Questo dimezza l'esposizione SH01-no-SL già colpito dalla coda.
2. **Giorno 1-2 — TR01 fee 2x + forming-bar (TR01 e Pairs).** Fix puramente di parità/contabilità, nessun gate OOS necessario (avvicinano il live al backtest validato). Coprire il silent-return TSM01/Rotation con WARN.
3. **Giorno 2-3 — disaster-bracket on-book + osservabilità outage.** Cablare `set_stop_loss` lontano (~30%) sui 4 fade eseguiti; aggiungere alert su N poll falliti consecutivi con `real_in_position=True` e log del gap% al risveglio. Smoke testnet (€0). Pura assicurazione coda, 0 costo Sharpe.
4. **Giorno 3 — osservabilità report** (TR01/ROT02/TSM01 in hourly_report) + smoke MR07 short-side TP-resting.
**Settimana 2 — ripristini di config validata, ognuno dietro il gate PORT06/OOS:**
5. **Giorno 4-6 — `trend_max=3.0` sulle 6 fade.** Implementare in `_defs.py`, poi **ri-eseguire il gate PORT06** (template `exit16_port06_impact.py`) misurando trend-ON+hurst+EXIT-16 vs attuale trend-OFF: deploy SOLO se Sharpe OOS non peggiora e DD scende (sovrapposizione hurst+trend va quantificata).
6. **Giorno 6-8 — pos pairs per-famiglia.** Plumbing `SleeveSpec.position_size`; ri-eseguire `combine` a pos live (target 0.15-0.25); scegliere il punto sul trade-off DD/ritorno; deploy.
7. **Giorno 8-10 — DIP01 EXIT-16+hurst.** Validare sul grid OOS proprio di DIP01 (famiglia honest, meccanismi tarati su fade) **con engine gap-aware**, poi gate PORT06. Deploy solo se passa entrambi.
8. **Giorno 10-12 — SH01 ri-validazione train-window.** Ri-eseguire validazione con train cap ~8760 barre (regime live). Se l'edge sopravvive → tarare th/calibrazione su slice held-out; se non sopravvive → riconsiderare SH01 (NON ri-tunare il th a forza). **Mai impostare sl/sl_confirm_atr su SH.**
Filoni P2 (MR01 k ETH) e cleanup P3 in coda, opportunistici. Regola trasversale: i punti 1-4 sono fix di bug/parità e vanno per primi; 5-8 toccano il comportamento di trading e **nessuno va in produzione senza superare il gate OOS + PORT06** (precedente FR01: robusto individualmente ≠ migliora il portafoglio).
+49
View File
@@ -0,0 +1,49 @@
# 2026-06-07 — DIP01 EXIT-16: PROMOSSO (gate gap-aware, il più netto della serie)
Punto 9 della roadmap sweep: DIP01 era l'unico sleeve BTC con esecuzione REALE
round-trip e girava ancora sul branch SL intrabar wick-sensitive. EXIT-16 era
validato SOLO sulle fade: estensione a una strategia honest dietro doppio gate
(grid proprio di DIP01 + PORT06), con engine GAP-AWARE come da lezione exit-lab.
## Metodo (`scripts/analysis/dip01_exit16_impact.py`)
- Parità con la equity canonica: corr **1.00000**, ret +322% identico.
- **Engine gap-aware**: nel ramo orig lo SL filla a `worse(livello, open[j])`
l'engine canonico filla "al livello" anche su gap-through (54% dei casi per stop
tight) e regalerebbe al ramo intrabar un vantaggio fittizio.
- Grid 3×3×2 (z_in × sl_atr × max_bars) su BTC (deployato) ed ETH (robustezza);
train (pre 2024-10-12) e OOS separati. Buffer plateau {0.4, 0.5, 0.75, 1.0}.
- NB `hurst_max` NON valutato su DIP01: il gate trendmax dello stesso giorno ha
mostrato che è ridondante-dannoso post-EXIT-16.
### Trappola di metodo trovata: l'ancora bfill di `_daily_equity`
La prima parità falliva (corr 0.9956, 16pp) con **engine identici** (662 trade,
stessi return, stesso capitale finale). Causa: la convenzione canonica
`_daily_equity` (serie a punti trade-exit → `reindex(IDX).ffill().bfill()`)
**ancora il primo valore della finestra al PRIMO trade dentro IDX** (es. 942.54
dopo le perdite di gennaio 2021), non al capitale portato avanti dall'ultimo trade
del 2020 (979.23). Il mio replay hourly era più "corretto" ma per parità va
replicata la convenzione. **Finding aperto**: questo bias tocca le metriche
canoniche di TUTTI gli sleeve a punti-trade (honest/pairs/tsm) — l'ancoraggio
distorce leggermente il punto di partenza della finestra. Da valutare a parte se
correggerlo OVUNQUE in un colpo solo (cambierebbe i numeri canonici di riferimento).
## Risultati
- **Grid BTC: 36/36** (EXIT-16 ≥ orig gap-aware in train E OOS in tutte le 18
celle). OOS Sharpe ~2-4x (cella canonica 1.47→3.48), DD giù in 14/18 celle.
- **Grid ETH: 35/36** (unica eccezione la cella estrema z3.0/sl3.0/mb48, marginale).
- **Plateau buffer piatto**: OOS DD 6.4% identico da 0.4 a 1.0; scelto 0.5 (== fade).
- **PORT06**: FULL Sharpe 6.43→6.61, DD 3.96→3.58 | OOS Sharpe 8.58→8.77, DD
1.36→1.34. Migliora tutto. **PROMOSSO**.
È la 5ª conferma indipendente del principio EXIT-16: gli stop intrabar da wick
sono falsi negativi per le strategie mean-reversion — l'overshoot che buca lo
stop è il movimento che si sta comprando.
## Deploy
`_defs.py`: `"sl_confirm_atr": 0.5` nei params DIP01_BTC (il worker legge già il
param — zero codice). Live path: TP intrabar invariato, SL solo su close confermato,
disaster-bracket on-book già attivo per gli outage.
@@ -0,0 +1,46 @@
# 2026-06-07 — Gate position_size PAIRS: 0.5 → 0.20 (assicurazione, come il cap SHAPE)
Punto 8 della roadmap improvement-sweep: i pairs giravano col `position_size`
GLOBALE 0.5 a leva 2 (esposizione 1.0 della fetta) mentre TUTTA la validazione PR01
è a pos 0.15 lev 3 (esposizione 0.45) — ~2.2x il validato su una famiglia **senza
stop** (exit solo |z|≤z_exit o max_bars). Scelta deliberata (commento yml) ma mai
gateata. Evidenza live: ADA/ETH 2026-06-05 net 8.52% = 4.26% di sleeve in un trade.
## Metodo
`scripts/analysis/pairspos_port06_impact.py`: le 5 equity pairs ricostruite con
`pairs_sim` alla **leva live (2x)** su griglia pos {0.50, 0.25, 0.20, 0.15},
innestate nel PORT06 canonico (pesi cap PAIRS 0.33 + SHAPE 0.0588). Parità
verificata: `pairs_sim(0.15, lev3)` == equity canoniche (corr 1.00000, 5/5).
## Risultati
| pos | esp. | FULL Sh / DD | CAGR | OOS Sh / DD | fam DD | worst pair DD grezzo |
|---|---|---|---|---|---|---|
| 0.50 (live) | 1.00 | 6.69 / 4.77% | 130% | 9.05 / 3.40% | 14.0% | ETH/BTC 78% |
| 0.25 | 0.50 | 6.50 / 4.04% | 67% | 8.69 / 1.54% | 7.2% | 51% |
| **0.20** | **0.40** | 6.33 / 3.90% | 57% | 8.43 / **1.26%** | 5.8% | 44% |
| 0.15 | 0.30 | 6.03 / 3.78% | 47% | 7.99 / 1.33% | 4.3% | 35% |
Letture:
- Il gate meccanico (OOS Sharpe non peggiora) **boccia ogni riduzione**: ovvio —
ridurre l'esposizione di una famiglia che in-sample non è mai esplosa costa sempre
Sharpe. Ma il +130% CAGR / +2.6e9% compounded di ETH/BTC a pos 0.5 è fantasia da
compounding: dice solo che il tail event non è ancora successo in-sample.
- **0.20 è il ginocchio**: OOS DD 1.26% batte anche 0.15 (1.33% — oltre il ginocchio
si taglia ritorno senza guadagnare DD); esposizione 0.40 ≈ il validato 0.45.
- Decisione di tipo assicurativo, identica al precedente cap SHAPE (adottato pagando
0.24 OOS Sharpe).
## Decisione (utente): pos PAIRS = 0.20
Implementazione:
- `runner.pos_for_spec(sid, global_ps, family_overrides)` — override per-famiglia
(chiave = `weighting.family_of`), plumbato in `build_worker_for`.
- `portfolios.yml`: `position_size_family: {PAIRS: 0.20}` (globale 0.5 invariato
per fade/dip/shape).
- Test: `test_pos_for_spec_family_override`, `test_build_pairs_worker_position_size`.
Nota di transizione: la posizione LTC/ETH aperta al deploy (entrata a sizing 0.5)
verrà chiusa col pos nuovo 0.20 → il pnl % sleeve di quel singolo trade è bookato
alla taglia nuova (one-time, conservativo).
@@ -0,0 +1,51 @@
# 2026-06-07 — SH01 punto-10: il regime live (train 365g) NON è robusto → bootstrap full-history
Punto 10 della roadmap sweep: ri-validare SH01 col train-window del regime live
(~8760 barre, il `_ML_LOOKBACK_DAYS=365` del runner) invece dell'expanding
full-history usato da tutta la validazione storica.
## Ri-validazione (`scripts/analysis/sh01_trainwindow_validate.py`)
`ml_wf_entries(train_window=...)` rolling su storia piena ≈ il regime live.
Protocollo explore_lab (FULL / OOS 30% / sweep fee / anni positivi), W24 H12 th0.58.
| BTC train | trade-rate | FULL / OOS | Sharpe | fee 0.2%: FULL/OOS | anni+ | robusto |
|---|---|---|---|---|---|---|
| 8760 (live) | **21.7%** | +82 / +74 | 1.31 | **42** / +20 | 6/9 | ❌ |
| 17520 (2y) | 15.4% | +166 / +96 | 2.05 | +9 / +47 | 6/9 | ❌ |
| 26280 (3y) | 11.8% | +348 / +95 | 3.09 | +109 / +61 | 7/9 | ❌ |
| EXPANDING | 9.8% | +219 / +42 | 2.72 | +60 / +26 | 8/9 | ✅ |
ETH a tw=8760: **FULL 33%, Sharpe 0.02** (lancio di moneta che paga fee).
Conferme: (a) la diagnosi del sweep era esatta (trade-rate 22-26% live vs 10%
validato — soglia inerte per over-confidence su train piccolo); (b) la
progressione è MONOTONA: l'edge di SH01 *è* la memoria lunga; (c) lo sweep della
soglia nel regime corto dà risultati instabili e incoerenti fra asset → ri-tunare
th sarebbe curve-fitting sul rumore di calibrazione (come da warning del piano).
## Decisione (utente): bootstrap full-history
Ripristinare in live ESATTAMENTE il regime validato (expanding full-history):
1. **`ml_wf_entries(last_block_only=True)`** — fitta/predice SOLO l'ultimo blocco
del walk-forward. I confini dei blocchi sono deterministici (start + k·retrain)
e al worker servono solo i segnali recenti → le entries dell'ultimo blocco sono
**identiche per costruzione** al tail del WF completo (parity test esatto in
`tests/portfolio/test_sh01_last_block.py`). Costo live: **0.6 s/tick** su 73k
barre (1 fit LogReg) vs ~140 fit del WF completo.
2. **Bootstrap storia nel runner**: per gli sleeve `ml` il tick riceve
`concat(parquet_locale < feed_start, feed_live)` (`runner._with_history`).
Gap-guard: se il parquet è stantio oltre il lookback del feed → solo feed +
WARN una-tantum (meglio il regime corto dichiarato che una serie col buco).
Manutenzione: tenere fresco il parquet con `download_all()` (oggi al 2026-05-28,
il feed copre 365g → margine ~11 mesi).
3. `_defs.py`: `params={"last_block_only": True}` sugli sleeve SHAPE (solo path
live; il backtest canonico resta WF completo).
Effetto atteso live: trade-rate SH01 da ~25% a ~10% delle barre, selettività
della soglia ripristinata, WR atteso ≈ validazione (acc OOS ~56% BTC).
Il monitor naturale è il report orario (SH01 farà MENO trade).
Nota: `_ML_LOOKBACK_DAYS` resta 365 (il feed serve comunque per le barre recenti
e come fallback se il parquet è stantio).
+101
View File
@@ -0,0 +1,101 @@
# 2026-06-07 — Implementazione fix improvement-sweep (P0 + punti 2-6, settimana 1)
Implementati e deployati i fix "settimana 1" del piano dello sweep 2026-06-06
(tutti fix di parità/protezione/osservabilità, nessun cambio di strategia → nessun
gate OOS richiesto). Versioni: v1.1.3 (punti 1-4), v1.1.4 (punti 5-6).
## P0 — cap SHAPE 0.0588 ripristinato (v1.1.3)
`portfolios.yml` ridichiara il dict caps COMPLETO `{PAIRS: 0.33, SHAPE: 0.0588}`
(l'override sostituisce interamente i caps di `_defs.py` via setattr in
`base.py:load_active_portfolio` — footgun ora commentato nel yml). Verificato live
post-restart: SH_BTC+SH_ETH = 0.0588 totale (prima 0.1176 = 2x intended), non-cappati
risaliti a 0.0647, sum = 1.0 — parità col backtest canonico (FULL 6.43 / OOS 8.58).
## Punti 2-4 — parità worker multi-asset (v1.1.3)
- **TR01 fee × leva**: `POS · LEV · fee_rt/2` per flip come il reference
`honest_improve2._tr_basket_daily:150` (prima sotto-caricata 2x).
- **TR01 forming-bar**: crossover EMA E booking del return solo su barre 4h COMPLETE
(riga -1 = candela in corso finché non è trascorsa la sua durata, pattern EXIT-16).
Evidenza live che non può più ripetersi: flip SOL 0→1→0 in 59min stessa finestra 4h.
- **PairsWorker forming-bar**: entry ED exit sul close di barra COMPLETA, come il
backtest `pairs_research` (close settled).
- **TSM01/ROT02 silent-return**: WARN log + Telegram `PANEL_SHORT` quando il panel
inner-join è sotto il lookback (helper condiviso `_warn_panel_short` in
rotation_worker), gated su "era già operativo", una notifica per episodio.
Verifiche: 72 test (7 nuovi), `validate_worker_pairs` ESATTO, `validate_honest_workers`
invariato. **Nota onesta**: TR01 replay worker 44% vs reference +42% è IDENTICO
pre/post fix → divergenza di convenzione pre-esistente (capitale-unico con
`mean(rets)` solo sugli asset in posizione vs media-equity 1/N del reference).
Da rivisitare a parte: "stesso ordine di grandezza" oggi non regge più.
## Punto 5 — disaster-bracket on-book + alert outage (v1.1.4)
Durante un outage (poll-loop in except) le posizioni REALI restavano senza stop sul
book e senza valutazione exit. Ora:
- **`ExecutionClient.place_disaster_sl`**: STOP_MARKET reduce-only a ~30%
dall'ingresso (trigger sul mark), piazzato a ogni REAL_OPEN sui fade eseguiti
(MR01/MR02/MR07/DIP01) e cancellato in `_real_close` (prima del TP-reconcile).
In operatività normale non scatta mai → 0 costo Sharpe; nei crash il fill è al
gap (cappa la coda, non la elimina). Config: `overrides.execution.disaster_sl_pct`
(default 0.30, 0 = off). `real_dsl_order_id` persistito (resume-safe).
- **SCOPERTA**: il `set_stop_loss` di cerbero-mcp è un `private/edit` Deribit (solo
ordini APERTI) → inutilizzabile su market già fillati; il bracket va piazzato come
trigger order autonomo via `place_order(type="stop_market")` (già supportato).
- **Semantica cancel trigger order** (verificata su testnet): la cancel risponde con
lo stato AL MOMENTO della cancel (`untriggered` = successo; il re-cancel dà
`order_not_found`); `error` = non più in book (probabile trigger scattato → il
market a valle filla 0 e REAL_CLOSE esce verified=False).
- **Alert `FEED_OUTAGE`**: dopo 5 poll falliti consecutivi (~5 min) Telegram con
l'elenco delle posizioni reali aperte; notifica di ripresa con durata.
Smoke testnet (`live_shadow_smoke.py`, esteso): 4 scenari long+short — il path
**short-side TP-resting** (BUY limit reduce-only) non era MAI stato esercitato.
Tutti verdi: resting/cross-immediato due lati, DSL piazzato/cancellato (verificato
zero ordini orfani sul book), conto flat a fine smoke.
## Punto 6 — osservabilità multi-asset (v1.1.4)
- `in_position` aggiunto ai `_save()` di TR01/ROT02/TSM01.
- `hourly_report`: nuova sezione **MULTI-ASSET** (book corrente | ultimo flip |
freschezza status) — prima i 3 worker erano invisibili nel report (collect()
filtra su event/in_position che non emettevano): impossibile distinguere
"flat/risk-off by-design" da "wiring rotto". I multi-asset sono esclusi dalla
tabella IN CORSO (assume entry/bars single-leg).
## Code review multi-agente del giorno stesso (v1.1.7)
Review a 7 angoli su `8c4e1cd..HEAD` + check trades live. Il candidato top dei finder
("stop_market senza trigger") è REFUTATO: cerbero-mcp traduce `price→trigger_price +
trigger=mark_price` e in produzione 2 DSL erano armati + 1 ciclo completo pulito.
Quattro fix applicati:
1. **Alert `REAL_DIVERGENCE`** (|slippage sim/reale| ≥ 100bps a open/close). Scoperta
dal check trades: alle 10:37 uno **spike print testnet** (candela 10:00 H=65618 con
O/C~62400) ha fatto shortare alle 3 fade BTC un close fantasma a 65266.5 — il reale
ha fillato correttamente a ~62395 (440bps) ma il sim ha bookato +2.26 mai esistiti
(MR07 reale 0.13). Prima passava in silenzio.
2. **`FEED_OUTAGE` anche su feed degradato senza eccezione** (HTTP 200 con candles
vuote → i worker saltavano il tick in silenzio e lo streak restava 0). Helper unico
`_outage_tick` (fix anche dell'incoerenza chiavi minuti/durata_min).
3. **`src/live/bars.py`**: detection forming-bar UNIFICATA (era copiata in 4 punti,
con `_check_stale_feed` che hardcodava 1h). È l'invariante di sicurezza di EXIT-16:
ora vive in un posto solo, testato (`test_bars.py`).
4. **DSL cancel hardening**: retry su errore transitorio + alert Telegram
`REAL_DSL_CANCEL_FAIL` se lo stop resta forse ORFANO sul book (prima l'id veniva
dimenticato in silenzio → lo stop stantio poteva colpire la posizione successiva);
`order_not_found` = probabile trigger durante outage → solo log (il close a valle
esce già verified=False). Test con executor finto (`test_real_close_dsl.py`).
Finding noti NON ancora fixati (in coda): ROT02/TSM01 valutano la candela 1d in
formazione al primo poll dopo mezzanotte (stessa classe del fix TR01/Pairs, pre-esistente);
engine dei gate copiato fra i 3 script `*_port06_impact.py`; epoche hourly_report hardcoded.
## Resta in roadmap (settimana 2, OGNUNO dietro gate PORT06/OOS)
trend_max=3.0 sulle 6 fade → pos pairs 0.15-0.25 per-famiglia → DIP01 EXIT-16
(validazione gap-aware) → SH01 ri-validazione train-window ~8760 barre.
Follow-up tecnico: divergenza convenzione TR01 worker vs reference (sopra).
@@ -0,0 +1,64 @@
# 2026-06-07 — Gate trend_max sulle fade live: BOCCIATA l'aggiunta, PROMOSSO lo swap hurst→trend
Punto 7 della roadmap improvement-sweep: ripristinare `trend_max=3.0`/`ema_long=200`
sulle 6 fade live (ON nel backtest canonico, OFF in produzione). Il piano imponeva il
gate PORT06 perché hurst e trend si sovrappongono. Esito sorprendente ma coerente.
## Metodo
`scripts/analysis/trendmax_port06_impact.py` — engine riusato da `exit16_port06_impact`
(replay esatto del path canonico, parità verificata: corr 1.00000 e diff 0.00% su tutte
e 6 le fade) + maschera Hurst IDENTICA al live (`fade_base.hurst_skip_mask`, close-only,
w=100 step=6 — non la cache regime_lab). Tutte le varianti girano sul **path live**
(EXIT-16 close-confirm 0.5 attivo); PORT06 con pesi cap canonici (PAIRS 0.33,
SHAPE 0.0588), OOS da 2024-10-12.
Nota di metodo: il test PORT06 del loss-guard Hurst (`fade_lossguard_port_test`,
2026-06-02) era su entries GIÀ trend-filtrate E sull'engine PRE-EXIT-16 (SL intrabar).
La config live reale (hurst senza trend, con EXIT-16) non era MAI stata gateata.
## Risultati (PORT06, fade in path live EXIT-16)
| Variante | FULL Sh | FULL DD | FULL CAGR | OOS Sh | OOS DD | OOS CAGR |
|---|---|---|---|---|---|---|
| LIVE (hurst, no trend) | 7.23 | 2.68% | 68% | 9.35 | 1.68% | 92% |
| CANDIDATO (hurst+trend) | 7.11 | 2.06% | 59% | 9.36 | 1.62% | 83% |
| **TREND-ONLY 3.0** | 7.89 | 2.46% | 82% | **9.91** | **1.20%** | 103% |
| NESSUNO (solo EXIT-16) | **8.07** | 3.35% | **105%** | 9.72 | 1.38% | 119% |
| TREND-2.5 | 7.75 | 2.46% | 76% | 9.76 | 1.20% | 96% |
| TREND-3.5 | 7.87 | 2.46% | 84% | 9.64 | 1.20% | 103% |
## Letture
1. **CANDIDATO BOCCIATO** (gate formale): il DD scende (2.68→2.06) ma FULL Sharpe
0.12 e CAGR 9pp — over-filtering, i due filtri tagliano lo stesso regime
(n trade 50%, es. MR02_ETH 911→436).
2. **Il loss-guard Hurst è ridondante-dannoso post-EXIT-16**: NESSUNO batte LIVE su
Sharpe e ritorno ovunque. Spiegazione meccanica: hurst evitava i regimi stop-heavy
saltando gli ingressi; EXIT-16 ha eliminato i wick-stop alla radice → gli ingressi
che hurst salta sono in maggioranza tornati vincenti. Il 66% delle barre è oltre
soglia hurst → il filtro toglieva metà dell'esposizione per un beneficio che ormai
non c'è.
3. **TREND-ONLY domina LIVE su tutte e 4 le metriche** ed è ESATTAMENTE la config che
la ricerca EXIT-16 del 2026-06-04 aveva promosso (entries trend-filtrate, no hurst:
FULL 7.84/2.60, OOS 10.06/1.15 — i 7.89/2.46 e 9.91/1.20 di oggi combaciano al
netto di refresh dati e cap SHAPE). Il live non aveva mai eseguito quella config.
4. **Plateau robusto**: trend_max 2.5/3.0/3.5 quasi indistinguibili (OOS DD 1.20
identico su tutte) → non è una soglia tunata.
5. Il trend filter vs NESSUNO è un trade-off deliberato: 23pp CAGR FULL per
FULL DD 3.35→2.46 e OOS Sharpe 9.72→9.91. Coerente con la filosofia del progetto
(DD control > ritorno marginale).
## Decisione (utente, 2026-06-07)
**SWAP hurst→trend nelle 6 fade live** (`_defs.py`): `trend_max=3.0, ema_long=200`,
`hurst_max` rimosso. `hurst_skip_mask` resta in `fade_base` (param opzionale).
Monitor live aggiornato: `hourly_report` ora traccia lo stop-rate per epoca
PRE (nessun filtro) → HURST → TREND, verdetto a n≥30 nell'epoca TREND.
## Lezione
Ogni meccanismo anti-perdite va RI-gateato quando cambia l'exit engine: hurst era
genuinamente il migliore sull'engine a SL intrabar (unico su ~10 candidati), ma
EXIT-16 ne ha assorbito il lavoro. I filtri si valutano sul path live corrente,
non sui numeri storici di promozione.
@@ -0,0 +1,76 @@
# 2026-06-08 — Ricerca multi-agente: dispersion / correlation index (165 agenti)
Workflow `dispersion-correlation-research` (run wf_72edef49-6d9): 60 celle esplorate
(15 famiglie × 4 finestre di correlazione W=24/72/168/336), 165 agenti totali, ~10M
token, verifica avversariale a 2 skeptic su ogni candidato robusto + sintesi.
Infrastruttura riusabile: `scripts/analysis/dispersion_lab.py` — feature REALIZED
causali (avg pairwise correlation, dispersione cross-sectional, beta vs indice EW,
componente idiosincratica `rel_A = ret_A ret_idx`), universo 8-asset, finestra
comune dal 2022-07-22, cache su disco, no-look-ahead verificato. NB: **niente
implied** (opzioni non backtestabili — stesso muro ARGO/GEX).
## Esito: 2 edge confermati su 60, 13 famiglie su 15 sono RUMORE
Nessun look-ahead "hard" stile-squeeze (le feature hanno retto la perturbazione del
futuro ovunque). Il fallimento dominante è **overfit/concentrazione di regime**, non
leakage. Tre pattern ricorrenti di falso positivo:
1. **OOS circolare**: le feature esistono solo dal 2022-07 → lo split OOS (ultimo 30%)
cade nel bull/calm 2024-25 (il regime che il progetto documenta come ottimistico
~50%); FULL≈OOS non era robustezza ma "quasi tutti i trade in OOS".
2. **Il gate corr/disp non è quasi mai load-bearing**: in ablazione, togliere il gate
spesso MIGLIORA i numeri → è un throttle di esposizione, non alpha (e Hurst-guard +
EXIT-16 fanno già quel lavoro).
3. **Riconferma del prior**: l'edge è sempre mean-reversion del residuo, mai
momentum/continuation/breakout cross-sectional.
## I 2 edge reali (entrambi fade del residuo idiosincratico)
- **`index_comp_disp` W=168 (BTC)** — il vero "dispersion-trading realizzato": fade
della componente idiosincratica estrema verso l'indice, **gated da alta dispersione**
(`disp_168 ≥ quantile 0.7`). rel_len=12, z_thr=1.5, TP=1.0·ATR, SL=1.5·ATR, mb=24.
FULL +63% / OOS +36% (fee0.2% +23%), win 66%, Sharpe 2.7-3.0, DD 7-9%, 5/5 anni.
È l'UNICA famiglia dove il gate dispersione fa davvero qualcosa.
- **`rel_idio_fade` W=24** — fade dello z-score di `rel_A` su 24h, exit a tempo (no
TP/SL). Cella più pulita BTC z3.0 mb12: win 58.5%, Sharpe 4.40, DD 7%, OOS +98%.
Più forte ma è quasi MR07 ri-etichettata in chiave cross-sectional.
Script preservati: `scripts/analysis/dispersion_edges/{index_comp_disp,rel_idio_fade}.py`.
## Raccomandazione: bassa priorità
**Entrambi sono fade-BTC mean-reversion → sovrapposti alle fade MR già deployate.**
P(migliora PORT06): ~20-25% per index_comp_disp, ~15-20% per rel_idio_fade. Vale la
**lezione FR01** (robusto-individualmente ≠ migliora-il-portafoglio: FR01 era robusto
e causale ma diluiva PORT06 OOS Sharpe 8.89→8.72). index_comp_disp ha il profilo trade
più distinto (win 66%, hold corto, TP vicino) → è l'unico che merita eventualmente un
gate `combine_v2`/PORT06 formale, misurando corr col MASTER e ΔSharpe/ΔDD. Se non
decorrela, scartare.
**13 famiglie rumore** (record in `_disp_scratch/`, gitignored): corr_gate_fade/pairs,
disp_xs_reversal/momentum, corr_spike_btc, corr_meanrev, beta_disp, disp_regime_rot,
corr_vol_interact, leadlag_corr, corr_trend, disp_compression_breakout, corr_disp_combo
— tutte MR/fade ri-etichettate, concentrazione 2024-25, o artefatti di feed morto
(ADA/LTC 97% candele flat). disp_compression_breakout riconferma la lezione squeeze
(i breakout rientrano).
**Conclusione onesta: nessun nuovo motore di ritorno.** Il dispersion-trading realizzato
funziona solo come l'ennesima faccia della mean-reversion già sfruttata.
## Gate PORT06 del candidato n.1 (2026-06-08) — PROMOSSO MARGINALE, NON deployato
`scripts/analysis/dispersion_edges/gate_index_comp_disp.py` (config W=168: rel_len=12,
z_win=336, z_thr=1.5, disp_q=0.7, TP=1.0ATR, SL=1.5ATR, mb=24; equity daily innestata
come 18° sleeve, pesi cap):
- **Sorpresa positiva**: decorrela DAVVERO. corr daily col candidato: MR01_BTC +0.01,
MR02_BTC +0.05, MR07_BTC +0.06, DIP01_BTC +0.02, MASTER(EW) +0.06. Il timore di
ridondanza con le fade BTC era infondato (gate dispersione + TP vicino = profilo trade
distinto). Standalone 311 trade, FULL +67% / OOS +30%.
- **Ma beneficio nel rumore**: PORT06 BASE→+DISP: FULL Sharpe 6.43→6.47, FULL DD
3.96→3.73 (migliora), **OOS Sharpe 8.58→8.56 (0.01), OOS DD 1.36→1.40 (+0.04)**
l'OOS è PIATTO. Passa il gate tecnico ma il guadagno è solo nel FULL (regime storico).
- **Decisione (utente): documenta e rimanda.** NON deployato — wiring (nuova Strategy+worker,
famiglia DISP, peso cap) + resterebbe simulato (no executor), per un beneficio OOS nullo.
Gate script committato e pronto: riprendere SOLO se si costruisce una famiglia DISP più
ampia (più asset/sleeve) che insieme sposti l'OOS. Esito ~20% previsto, confermato.
@@ -0,0 +1,130 @@
# 2026-06-08 — Ricerca sostituto/miglioria a MR02/ETH (127 strategie + opzioni)
## Contesto
MR02/ETH è lo sleeve a DD più alto della famiglia fade ed è il maggior drag
dell'esecuzione reale (testnet: 19€ su 7 trade, 0 win, faded un downtrend 14%
in long poi shortato il rimbalzo — whipsaw classico). Obiettivo: trovare un
sostituto o una miglioria con ≥100 agenti su strategie differenti, metodologia
onesta (ingresso eseguibile, fee 0.10% RT, OOS, gate PORT06).
## Cosa è stato fatto
- **Workflow 1 — 109 strategie ETH distinte** (`mr02eth_search.workflow.js`):
gated-fade, mean-reversion alternative, exit, trend-following, volatilità,
cross-asset, ML/shape, microstruttura, statistico-robusto. Harness onesto
`explore_lab`. Poi **verifica avversariale** dei top-24 (audit look-ahead +
re-implementazione indipendente + robustezza). → 26 survivor, **6 confermati**.
- **Workflow 2 — 18 overlay di OPZIONI** (`mr02eth_options.workflow.js`) +
sintesi con audit crash reali. Harness nuovo `option_overlay_lab.py`: opzioni
prezzate Black-Scholes sulla **DVOL** reale di Deribit (storica/gratuita,
`data/regime/eth_dvol.parquet`), premio dedotto, ipotesi **conservative**
(skew_mult sui comprati, uscita anticipata solo a intrinseco).
- **Gate PORT06** (`mr02eth_port06_gate.py`): swap di MR02_ETH col candidato,
equity giornaliera su IDX 2021-2026, ri-misura FULL+OOS Sharpe/DD (cap
weighting). Sanity check: MR02 ricostruito col mio engine == canonico (Δ 0.00).
## Risultato (gate PORT06; baseline canonico FULL 6.43/3.96, OOS 8.58/1.36)
| variante (swap MR02/ETH) | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| EXIT-16 (config LIVE) | 6.73 | 3.67 | 8.80 | 1.23 |
| no-SL nudo | 6.76 | 3.68 | 8.87 | 1.23 |
| no-SL + put/call 10% OTM | 6.78 | 3.64 | 8.86 | 1.23 |
| varratio_gate (PROMOSSO vs canonico) | 6.41 | 3.73 | 8.53 | 1.36 |
| choppiness_donch | 6.37 | 3.35 | 8.47 | 1.32 |
| vrp_neg_dvol_low | 6.36 | 3.22 | 8.37 | 1.43 |
## MIGLIORIA TROVATA (gate vs LIVE EXIT-16, non vs canonico)
Riferimento onesto = config LIVE EXIT-16 (FULL 6.73/3.67 — OOS 8.80/1.23), non
l'intrabar-SL canonico che nessuno gira.
| sleeve ETH | ΔFULL Sh | ΔFULL DD | ΔOOS Sh | ΔOOS DD | esito |
|---|---|---|---|---|---|
| **no-SL + put/call 10% OTM** | **+0.05** | **0.03** | **+0.06** | 0.00 | ✅ MIGLIORIA deployabile |
| no-SL nudo | +0.04 | +0.02 | +0.07 | 0.00 | ✅ migliora ma sl=0 NON deployabile |
| EXIT16 + put10 | 0.01 | +0.03 | 0.00 | 0.00 | = pari (assicurazione gratis) |
| varratio / vrp / chop / blend | negativi | | | | peggiorano vs EXIT-16 |
**La miglioria = MR02/ETH no-SL + floor a opzioni 10% OTM** (put sui long, call
sugli short). Batte il live EXIT-16 in modo PARETO su 3/4 metriche PORT06 (4ª
piatta). Meccanismo: **rimuovere lo stop** (intrabar/close-confirm su una fade
mean-reverting sono falsi negativi — overshoot che rientra) e dare la funzione
dello stop (cap della catastrofe) a un'**opzione comprata** = protezione a
rischio-definito SENZA il costo whipsaw/falso-stop. Standalone il salto è grande
(Sharpe 8.45→12.14, OOS DD 13%→7%); a portafoglio è modesto (+0.05/+0.06 Sharpe)
perché è 1 sleeve su 17, ma è un miglioramento MISURATO e verificato (soglia
gate +0.03, ottenuto +0.05/+0.06), non rumore.
## Conclusioni (oneste)
1. **Sostituti/blend: tutti peggiorano vs il live EXIT-16.** I candidati gated
(varratio/vrp/choppiness) e i blend within-sleeve battono solo lo strawman
canonico, non il live; le loro basse esposizioni diluiscono il motore di
ritorno (Sharpe 0.08/0.44). Lo Sharpe ~20 nell'harness era artefatto di
`sqrt(N_trade)` (onesto ~4.3). → **non sostituire né blendare.**
2. **L'unica miglioria è la coppia no-SL + opzione.** Il no-SL recupera il drag
dello stop; l'opzione cappa la coda da **gap-through** che EXIT-16 NON protegge
(esce al close DOPO il buco → prende la perdita piena). Audit crash: FTX nov-22
48%→−30%, LUNA-era 2022-09 63%→−31%, 2018/19 65%→−30%. Premio ~break-even
(paga 156% / incassa 149%). È ciò che rende deployabile il no-SL.
## Azione raccomandata
- **Deployare MR02/ETH come no-SL + floor a opzioni 10% OTM** (put long / call
short, per-trade o settimanale ammortizzato). Batte il live su 3/4 metriche +
cappa esplicitamente la coda. **Prima del capitale**: validare prezzi/liquidità/
skew per-strike REALI su Deribit testnet (qui pricing BS sintetico sulla DVOL =
ATM-IV; stress skew ×1.5 gonfia il premio FULL ma l'OOS regge). Stesso muro
non-backtestabile di ARGO/GEX sul per-strike, aggirato col proxy DVOL.
- Step intermedio a costo zero: **aggiungere subito il floor opzione all'EXIT-16
live** (= "pari" sui numeri, ma assicurazione gratis sul gap-through) mentre si
valida la transizione a no-SL.
## Addendum 2026-06-09 — validazione su PREZZI REALI + integrazione cerbero-bite
- Probe live Deribit mainnet (singolo snapshot): sembrava skew ×1.6 e liquidità
pessima sul 10% OTM a 1g → l'overlay per-trade pareva bocciato.
- **cerbero-bite** (container accanto) ha però **lo storico per-strike reale** in
`option_chain_snapshots` (SQLite, dal 2026-05-01, ~110k righe/asset, bid/ask/IV/
greche/OI, cadenza ~12min). Importato in `data/options/` via `options_fetcher.py`;
loader `options_chain.py` (skew_curve/premium_levels/quote causale).
- **Correzione dai dati reali (5.5 settimane):** skew put 10% OTM = **×1.1** (liquido,
spread ~7%) — il ×1.6 del probe era un artefatto del deep-OTM a 1g illiquido. Premio
reale 10% OTM ≈ **1.0%/mese**. → la miglioria no-SL+opzione **regge** sui prezzi veri.
- **MA vincolo strutturale reale:** gli strike **10% OTM a 24h NON esistono** (n=0 in 5.5
settimane; Deribit lista solo near-ATM sui tenor corti). L'overlay **per-trade a 24h è
infattibile**; l'unica struttura eseguibile è una **put settimanale/mensile standing**
(catastrofe-cap di sleeve), da gateare a parte coi premi reali.
- **Infrastruttura sbloccata:** il muro ARGO/GEX ("opzioni non-backtestabili") è caduto
per il periodo recente — d'ora in poi le tesi opzioni si validano su prezzi VERI.
Importato anche il pannello `market_snapshots` (spot/VRP/funding/**net-GEX dealer**/
gamma-flip/liquidation, dal 2026-03-26) → `data/options/`, loader `options_chain.load_market`.
## Addendum 2 — gate del COLLAR standing (catastrophe-cap auto-finanziato) — NO-GO
Premi reali → collar = put 13% (1.0%/m) finanziata da call +10% (1.05%/m) ≈ premio netto 0.
Gate PORT06 (`eth_collar_gate.py`, BS calibrato skew_put 1.12 / skew_call 1.0, sweep OTM/hedge_frac):
- Il collar **PAGA nei crash** (ETH 40% dic-21 → +6.1%, 30% feb-25 → +6.6%): protezione reale.
- MA **net-negativo a portafoglio**: taglia il FULL DD (0.2/0.3pp) ma **costa Sharpe** (FULL e OOS)
e **ALZA l'OOS DD** (+0.18/+0.28pp) — nell'OOS (no crash profondo, qualche rally) la call corta
perde e il premio erode. Peggiora il DD che doveva ridurre.
- **Verdetto: il tail-hedge a opzioni NON conviene a PORT06** (né per-trade né collar). Strutturale:
lo sleeve ETH pesa 6.5% → la **diversificazione è già la protezione di coda** (trade 65% =
4% aggregato). La cap auto-finanziata era giusta sul *costo* (~break-even) ma la protezione non
vale il costo Sharpe/OOS-DD. Le opzioni si chiudono come **NO-GO empirico su prezzi reali**.
## Deploy della miglioria (2026-06-09): MR02/ETH stop-largo sl_atr 3.0
Nessun SOSTITUTO batte il live; la MIGLIORIA deployabile e' allargare lo stop EXIT-16.
Gate stop-width (EXIT-16 close-confirm, swap solo MR02_ETH):
| sl_atr | FULL Sh | FULL DD | OOS Sh | OOS DD | worst-trade |
|---|---|---|---|---|---|
| 2.0 (LIVE) | 6.73 | 3.67 | 8.80 | 1.23 | -48% |
| **3.0** | **6.77** | **3.30** | **8.82** | 1.23 | **-50%** |
| 4.0 | 6.78 | 3.49 | 8.82 | 1.23 | -62% |
| no-SL (sl=0) | 6.76 | 3.68 | 8.87 | 1.23 | -65% (vietato) |
`sl_atr=3.0` domina il live (FULL DD -0.37pp, Sh +0.04/+0.02) e cattura il beneficio del no-SL
(gli stop di meta'-discesa sono falsi negativi) MANTENENDO lo stop di catastrofe (worst -50%, non
-65%) -> regola "mai sl=0" rispettata; 4.0 inizia a perdere la coda. Plateau 2.5-4.0 (tutti > live
su Sharpe). Override SCOPED a MR02/ETH in `_defs.py` (`_fade_params`); gli altri 5 fade non
ri-gateati -> invariati. 93/93 test passano. Deploy: `./scripts/deploy.sh` (cambio live).
## Artefatti riusabili
- `scripts/analysis/option_overlay_lab.py` — overlay opzioni (pricing BS sintetico su DVOL).
- `scripts/analysis/options_fetcher.py` — import storico opzioni reale da cerbero-bite → data/options/.
- `scripts/analysis/options_chain.py` — loader + skew/premi reali + lookup causale (`OptionChain`).
- `scripts/analysis/mr02eth_port06_gate.py` — gate swap-sleeve PORT06.
- `scripts/analysis/mr02eth_search.workflow.js`, `mr02eth_options.workflow.js`.
@@ -0,0 +1,53 @@
# 2026-06-08 — Executor a 2 gambe per i pairs (PairsExecutionClient)
Implementato il pezzo di esecuzione reale più impegnativo rimasto: l'esecuzione
shadow a 2 gambe per la famiglia PAIRS (29% del portafoglio, finora solo simulata).
## Cosa
- **`execution.PairsExecutionClient`**: compone l'`ExecutionClient` single-leg.
- `open_pair(inst_a, inst_b, direction, notional)`: piazza le 2 gambe (long A/short B
o viceversa) a market, verifica per-gamba sul trade; **LEG-RISK**: se UNA sola gamba
filla → UNWIND (richiude la fillata reduce-only) per non restare direzionali →
verified=False.
- `close_pair(...)`: chiude entrambe reduce-only (solo `close_amount` della quota,
MAI `close_position`), riconcilia fee/prezzi.
- `register_contract`: fetch dinamico spec USDC da Deribit per strumenti non hardcodati.
- Strumenti = lineari USDC (payoff lineare = matematica del backtest a 2 gambe).
Aggiunti spec LTC/ADA/SOL_USDC (step 0.1/0.2/0.1).
- **`PairsWorker` shadow**: ledger reale parallelo (`real_capital`, `real_dir`,
`real_amount_a/b`, `real_entry_a/b`, `real_notional_a/b`, fee), persistito e resume-safe;
`_real_open_pair`/`_real_close_pair` agganciati a `_open`/`_close`; PnL reale per gamba
(dir A=+d, dir B=d). Doppio arrotondamento → piccolo sbilanciamento notional, riportato.
- **runner**: `pairs_executor` (PairsExecutionClient su stesso ExecutionClient dei fade),
`_pairs_exec_for`, gate su `execution.pairs_enabled`. Config `portfolios.yml`:
instruments estesi (LTC/ADA/SOL) + `pairs_enabled: false` (capability pronta, SPENTA).
## Validazione
- Test (executor finto): 92/92 — open/close a 2 gambe, **leg-risk unwind**, ledger reale
persiste e resume.
- **Smoke testnet end-to-end** (`live_pairs_smoke.py`, €0): open 2 gambe verificate
(long ETH 0.011 @1666 / short BTC 0.0003 @63263, fee $0.019), close 2 gambe reduce-only,
riconciliazione PnL reale 0.039 vs sim 0.036 (coerente), **conto flat dopo**.
## Incidente operativo (testnet, €0) — da ricordare
Durante la prima esecuzione, lo smoke è crashato (bug del test: prezzi sim=0 → divisione
per zero nel ramo sim) lasciando una posizione aperta. Per pulirla ho usato a mano
`ExecutionClient.close()` (= `close_position`) che **flatta l'intero strumento** → ho
chiuso anche le 3 posizioni reali SHADOW dei fade del runner (BTC/ETH_USDC condivisi).
Impatto: testnet, shadow (non guida le decisioni), €0; 3 worker fade con
`real_in_position=True` su conto flat → si auto-riconciliano al prossimo close sim
(close_amount su flat → verified=False, reset; "shadow pulito dalla prossima apertura").
I TP resting erano già stati cancellati da close_position (cancel → order_not_found).
**Lezione (CLAUDE.md la documentava già)**: MAI `close_position` su strumenti condivisi.
Fix permanente: lo smoke ora (a) usa solo `close_amount` della quota, (b) ABORTA se ci
sono posizioni di produzione aperte sugli strumenti, (c) usa prezzi sim reali.
## Stato: PRONTO ma SPENTO (`pairs_enabled: false`)
L'executor è validato (test + smoke). NON attivato in produzione: accenderlo richiede
una finestra a conto flat e un periodo di osservazione, dato il conto condiviso coi fade.
Quando si accende: i 5 pairs eseguiranno reale a 2 gambe accanto al sim.
@@ -0,0 +1,42 @@
# 2026-06-08 — Portafoglio live REALE-only: i €2000 ai soli sleeve eseguiti
Decisione utente: il portafoglio live deve mostrare il **risultato reale puro**. I €2000
si dividono SOLO tra i 14 sleeve che eseguono davvero (6 fade + DIP01 + 5 pairs + SH01);
i 3 multi-asset (TR01/ROT02/TSM01), non eseguibili in reale (bloccati dal capitale),
escono dal pool e girano **solo per statistica**.
## Implementazione (runner-level, non rompe le definizioni)
- `portfolios.yml`: `paper_sleeves: [TR01, ROT02, TSM01]`.
- `runner`: separa `live_specs` (pool/pesi/ledger) da `paper_specs`. I paper:
- capitale NOZIONALE fisso = `total_capital / N_sleeve_totali` (la fetta equal che
avrebbero avuto), NON dal pool;
- girano in `data/portfolio_paper_stats/` (binario separato);
- ticcati per statistica ma MAI in `ledger.update_equity` → non toccano l'equity reale.
- `hourly_report`: la sezione multi-asset ora legge `portfolio_paper_stats/` ed è
etichettata "PAPER — solo statistica, FUORI dal conto reale".
## Pesi del portafoglio reale-only (14 sleeve, cap)
sum=1.0; non-cappati 0.0873 (erano 0.0647 su 17), PAIRS 0.33 (cap), SHAPE 0.0588 (cap,
SH ciascuno 0.0294). Il cap SHAPE resta valido e protettivo anche su 14.
## Il costo, misurato e accettato
| | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| PORT06 completo (17, validato) | 6.43 | 3.96% | 8.58 | 1.36% |
| **REALE-only (14, live ora)** | 6.49 | **5.34%** | 8.54 | **1.70%** |
Perdendo i 3 diversificatori PAPER (corr 0.07), il DD del portafoglio reale sale
~3.96→5.34% FULL e 1.36→1.70% OOS, Sharpe sostanzialmente invariato. È il **prezzo di
vedere il risultato reale puro** invece di una curva mista reale+paper: scelta
consapevole dell'utente. I PAPER restano misurati per ri-integrarli quando saranno
eseguibili (SH01 è già stato integrato oggi; i multi-asset attendono capitale ~€20k).
## Note di transizione
- Il ledger `PORT06` (code invariato) ora rappresenta i 14 reali: la curva equity ha
una discontinuità di composizione da questo deploy (atteso).
- I 3 paper ripartono puliti in `portfolio_paper_stats/` con capitale nozionale uniforme
(statistica comparabile); i vecchi status in `portfolio_paper/` restano come storico.
@@ -0,0 +1,56 @@
# 2026-06-09 — Gioco "Blind Traders": 100 agenti ciechi
## Setup
100 agenti LLM (haiku) ricevono due serie anonime **X** e **Y** — in realta'
**BTC** e **ETH** 1h/15m/5m, mai etichettate — e devono proporre UNA regola che
"anticipi" i movimenti per un PnL netto positivo (fee 0.10% RT) con **>=10
trade/mese**. Non sanno cosa siano i dati. L'orchestratore (engine deterministico)
valuta ogni strategia, assegna un punteggio su **PNL + %win**, da' **90 epoche di
elaborazione** (hill-climb dei parametri) e **ogni 10 epoche blocca il 10% meno
profittevole** -> restano i **10 piu' profittevoli**.
Infrastruttura in `scripts/games/`:
- `engine.py` — dati anonimizzati, 6 famiglie segnale (zscore/breakout/ma_cross/
rsi/momentum/pairs), backtester causale fee-aware, scoring (>=10 tpm o squalifica).
- `agent_brief.py` — digest ANONIMO (stat aggregate + finestra normalizzata) + menu.
- `arena.py` — torneo a **3 finestre**: TRAIN (hill-climb), VALID (cull+rank
dell'orchestratore), TEST (OOS puro, mai ottimizzato). Anti-overfit.
- `run_game.py` — carica le 100 spec degli agenti e lancia il torneo.
## Risultato emergente
I 100 agenti ciechi, leggendo SOLO le statistiche anonime (autocorrelazione
negativa, "after_big_move_continues_pct" ~30-40% => le mosse estreme rientrano),
hanno **riscoperto da soli che il mercato e' mean-reverting**: 100/100 reversion,
67 hanno scelto il detector pairs, 30 zscore. Esattamente la lezione storica del
progetto (edge = reversione; pairs ETH/BTC il piu' robusto) — senza sapere che
fosse crypto.
## Classifica finale (top 10) — tutti PAIRS su 15m
Vincitore **agente #91** (15m, pairs market-neutral sul log-ratio X/Y):
- TEST/OOS puro: **PnL +3126%**, **win 77%**, **108.9 trade/mese**, **Sharpe 20.3**
- Full-period: PnL +8052%, win 70%, 94 tpm, Sharpe 12.2 (9604 trade)
- params: lookback 66, entry 1.67σ, exit 1.0σ, max_bars 35
- ipotesi (cieca): "Y altamente reversivo, X/Y log-ratio strong mean-reversion
(-0.43 autocorr), bassa correlazione cross-asset -> pairs market-neutral".
Tutti i 10 finalisti: pairs 15m, TEST Sharpe medio 19.9, tpm 66-109 (>>10).
## Caveat onesti
- Numeri OOS ottimistici: PnL additivo a notional fisso, **niente slippage sulle 2
gambe**, finestra OOS calma, 15m molti trade. Coerente col caveat PR01 del
progetto (Sharpe reale atteso ~4-5, non 20). Il valore del gioco e' il **metodo**
(scoperta cieca + selezione anti-overfit), non il livello assoluto di Sharpe.
- La convergenza su pairs conferma robustezza ma riduce la diversita': i 10 finalisti
sono varianti della stessa idea (ETH/BTC spread). Per un portafoglio servirebbe
diversificare (gia' fatto altrove: fade + honest + shape).
## Re-run "sobrio" con slippage (0.05%/lato)
`GAME_SLIP=0.0005` -> i pairs pagano +0.20% RT extra (4 lati). Lo slippage spinge
l'ottimizzatore verso **meno churn**: tpm dei finalisti 66-109 -> **40-47**, Sharpe
top-10 ~20 -> ~13.5. Vincitore **#43** (15m pairs): TEST PnL **+2091%**, win 77%,
**46.9 tpm**, Sharpe **15.6**. La gerarchia (pairs 15m domina) e la robustezza
reggono lo stress; lo Sharpe reale atteso resta ~4-5 (OOS calmo + PnL additivo).
Log: `data/games/game_slip.log`.
Artefatti: `data/games/tournament_result.json`, `data/games/specs/agent_*.json`,
`engine.set_slippage()` (env `GAME_SLIP`).
@@ -0,0 +1,41 @@
# 2026-06-09 — Gioco "Blind Traders" sessione 2: timing diversi (30m/2h/4h)
Seconda sessione del gioco (vedi `2026-06-09-blind-traders-game.md`), stesso protocollo
(100 agenti ciechi su BTC/ETH anonimi, scoring PNL+%win, ≥10 trade/mese, 90 epoche, cull
10% ogni 10 epoche → 10 finalisti, split a 3 anti-overfit) ma su **timeframe diversi**:
game 1 = 5m/15m/1h; **game 2 = 30m/2h/4h** (medio-lunghi). Engine con resampling aggiunto
(`engine._RESAMPLE`: 30m←15m, 2h/4h←1h). Specs in `data/games/specs2/`, risultato
`data/games/tournament_result2.json`.
## Diversita' proposte (di nuovo: riscoperta cieca della mean-reversion)
100 agenti: **74 pairs, 25 zscore, 1 breakout; 100% reversion**; tf 34/33/33. Come nel
game 1, leggendo solo le statistiche anonime (autocorrelazione negativa del log-ratio,
continuazione post-mossa ~40%) gli agenti convergono sulla reversione senza sapere che
sia crypto.
## Classifica finale — tutti 30m pairs
Vincitore **agente #36** (30m, pairs ETH/BTC):
- TEST/OOS: **PnL +1451%, win 77%, 43.4 trade/mese, Sharpe 12.3**.
- I 10 finalisti sono TUTTI 30m pairs (TEST Sharpe ~12, win 76-77%, tpm 43-49).
## Finding chiave: la regola ≥10 trade/mese e' un FILTRO sul timeframe
Quanti agenti per tf superano la soglia di attivita' + qualita':
| tf | agenti | ≥10 trade/mese | positivi OOS | miglior OOS Sharpe (pnl/win/tpm) |
|---|---|---|---|---|
| 30m | 34 | **34 (100%)** | 26 | **11.6** (1405% 76% 56) |
| 2h | 33 | 29 (88%) | 17 | 6.1 (512% 79% 17) |
| 4h | 33 | **4 (12%)** | 6 | 1.4 (103% 68% 14) |
A 4h solo 4/33 agenti riescono a fare ≥10 trade/mese (le barre sono troppo rade per
la reversione pairs); e l'edge cala col timeframe (Sharpe 11.6→6.1→1.4). Per questo i
finalisti sono tutti 30m.
## Lezione cross-game (game 1 + game 2)
Esiste una **frontiera frequenza-vs-edge**: la regola ≥10 trade/mese mette un *pavimento*
sul timeframe (i lunghi non fanno abbastanza trade), il costo/edge mette un *soffitto* (i
cortissimi sono cost-fragili). Il punto ottimo e' **il timeframe piu' corto con edge ancora
robusto**: game 1 (con 15m disponibile) → vince 15m; game 2 (senza 15m) → vince 30m. Sempre
**ETH/BTC spread reversion**. Coerente con l'analisi di robustezza del 15m
(`2026-06-09-pairs15m-robustezza.md`): piu' corto = piu' trade = piu' edge di backtest, ma
piu' fragile ai costi. Il gioco trova l'edge; la prudenza di deploy (mezza size) gestisce
la fragilita'. Artefatti: `scripts/games/`, `data/games/tournament_result2.json`.
@@ -0,0 +1,47 @@
# 2026-06-09 — Validazione edge credit-spread cerbero-bite (prezzi reali)
## Contesto
cerbero-bite (container accanto, `/opt/docker/cerbero-bite`) vende credit-spread su
ETH (bull-put primario, short delta ~0.18, DTE 18, Quarter-Kelly 13%, PT 50% / stop
2.5x credito / delta-breach 0.30 / vol-stop +10 DVOL / time-stop 7 DTE; testnet,
propose-only). Tune "Profilo B" del 2026-06-09: short delta alzato a 0.18 (da 0.10-0.15)
e `credit_to_width_ratio_min` 0.30->0.08 perche' a delta basso 0 spread erano eleggibili.
Domanda: l'edge regge su un ciclo ETH completo, o e' profittevole solo nei campioni calmi?
Validato con l'infrastruttura opzioni REALE (data/options/, importata da cerbero-bite).
Script riprendibile: `scripts/analysis/cerbero_bite_credit_spread.py`.
## Risultati
1. **Economia d'ingresso reale** (chain, 3145 spread): cw a delta 0.18 = **0.106** (p25 0.085),
eleggibilita' **65%**, short strike **~9.4% OTM** (NON 18% — quello era il vecchio delta basso),
**max-loss/credito = 8.4x**.
2. **Tail model-free** (8 anni ETH reali, cw 0.106, hold-to-expiry, niente modello opzioni):
win-rate 74%, **EV -1.0 crediti/trade, 7/9 anni NEGATIVI**, ETH <-13.4% a 17g (max-loss) il
**17.8%** delle volte. Un max-loss (-8.4cr) cancella 17 PT-winner. Gap 1g p5 = -25% (salta lo stop).
3. **Managed (skew calibrato sulle IV reali)**: win-rate **37%** (delta-breach esce sul 62% dei
trade a piccola perdita), **EV -0.02 cr/trade** (gia' a economia favorevole), worst -4.6 (2023).
2021+ EV -0.022.
## Caveat di calibrazione (TODO aperto)
Il mark mid+skew da **cw 0.228 vs 0.106 reale** -> sovrastima il credito ~2x (manca bid/ask
incrociato sulle 2 gambe + griglia strike reale). Quindi l'EV managed mostrato (-0.02) e' a
economia **2x troppo favorevole**: l'EV vero e' **<=**. Per il numero esatto: modellare bid/ask
reale + griglia (entrambi nella chain) cosi' entry cw -> 0.106. RIPRENDERE da qui.
## Verdetto
- **NON edge robusto su ciclo completo.** Il "+0.48%/mese netto" citato era **artefatto di
finestra calma** (mag-giu 2026, nessun crash): li' il body vince (EV +0.6), ma su ciclo
completo (mesi -13% al 18% di frequenza) e' breakeven-to-negativo in 2 stime indipendenti.
- **L'82% PoP e' ingannevole**: o esci presto sul delta-breach (win 37%, grind di piccole
perdite), o tieni e prendi la coda. Non c'e' la "macchina da 82% di vittorie".
- **Il tune Profilo B PEGGIORA la coda** (vendere a 9.4% OTM mette il max-loss in zona di
pullback ordinario). Strutturale: il mercato non paga per vendere lontano, vendere vicino
espone alla coda.
- **Coda CONCENTRATA con PythagorasGoal**: il gap che salta lo stop = il crash ETH, lo stesso
evento che colpisce lo sleeve fade. I due sistemi non diversificano, concentrano.
- **Azione**: cerbero-bite resta testnet/paper finche' non c'e' un campione che include un crash
vero; valutare un long deep-OTM put (0.33%/mese reale) come cap della coda del bull-put-spread.
## Stato
DA RIPRENDERE: calibrazione esatta credito (bid/ask + griglia) -> EV managed definitivo.
Tutto il resto e' chiuso e documentato.
@@ -0,0 +1,82 @@
# 2026-06-09 — Percorso live 15m per ETH/BTC pairs: COSTRUITO e VALIDATO
Seguito di `2026-06-09-pairs15m-port06-gate.md` (il gate passa, edge reale e non
artefatto flat). Qui si costruisce e VALIDA l'infrastruttura per eseguire il pairs
ETH/BTC a 15m con flat-skip, alla pari del backtest (disciplina validate_worker_pairs).
## 1. Engine canonico (regression-locked)
`scripts/analysis/pairs_research.py`: aggiunti `aligned_ohlc`, `is_flat_ohlc`,
`pairs_sim_flat(..., flat_skip, scan_buffer)`. Regola di uscita **LIVE-REALIZABLE**:
la condizione (|z|<=z_exit O bars>=max_bars) ARMA `exit_ready`; si esce al CLOSE della
PRIMA barra PULITA successiva (mai a un prezzo passato come faceva il prototipo push-back).
- **Regression-lock**: `pairs_sim_flat(flat_skip=False)` == `pairs_sim` ESATTO
(ETH/BTC 1h 1756 trade, 15m 9388 trade, ret/dd/sharpe identici al bit).
## 2. PairsWorker esteso (retrocompatibile)
`src/live/pairs_worker.py`: param `flat_skip`, stato `exit_ready` (persistito), tick
ora fa merge OHLC e rileva le candele flat (O=H=L=C in UNA gamba). Entry saltato su barra
stale; uscita con la stessa regola exit_ready dell'engine. **Default off = comportamento
1h storico invariato** (se mancano le colonne OHLC, flat=False).
## 3. Runner: fetch sub-orario (inerte finche' non c'e' uno sleeve 15m)
`src/portfolio/runner.py`: `_SUBHOURLY={5m,15m,30m}`, `_LOOKBACK_DAYS` esteso; il loop
fetcha DIRETTO da Cerbero i timeframe sub-orari per (asset,tf) (non resamplabili dal 1h) e
un router `_series_for` instrada la serie giusta a ogni worker. Zero impatto sul live
attuale: nessuno sleeve e' 15m → `subhourly_needs` vuoto → ramo morto.
## 4. VALIDAZIONE (validate_worker_pairs.py) — TUTTO OK
Replay bar-per-bar del worker == backtest:
| caso | worker | backtest | match |
|---|---|---|---|
| ETH/BTC 1h | 1756 trd, cap 2.886.616 | 1756, 2.886.616 | **OK esatto** |
| BTC/LTC 1h | 599 trd, cap 16.861 | 599, 16.861 | **OK esatto** |
| **ETH/BTC 15m-flat** | **8452 trd** | **8453 trd** (cap entro 0.15%) | **OK** |
(1 trade di differenza = posizione finale aperta non chiusa nel replay, atteso.)
## 5. Gate finale (engine == worker) — PROMOSSO
`pairs15m_gate_final.py` (corr 1h vs 15m = 0.372, 3201 ingressi flat saltati):
| variante ETH/BTC | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| baseline 1h | 6.43 | 3.96 | 8.58 | 1.36 |
| **SWAP 15m-flat** | 7.31 | 3.55 | **9.95** | **1.26** |
| **BLEND 1h+15m** | 7.03 | 3.66 | 9.57 | 1.24 |
Entrambi PROMOSSI (a fee backtest). Caveat slippage del gate precedente invariato → il
BLEND e' la forma raccomandata (meta' allocazione sul 1h pulito, slippage-robusto).
## Stato e attivazione (NON fatta — decisione di deploy)
Tutto il PERCORSO e' pronto e validato, ma il 15m **non e' attivo nel portafoglio live**:
attivarlo cambia il trading reale e va deciso esplicitamente. Per accenderlo:
1. `_defs.py`: aggiungere SleeveSpec pairs ETH/BTC a 15m (tf="15m",
params={n:66,z_in:1.674,z_exit:1.0,max_bars:35,flat_skip:True}) — come SWAP della 1h o
come 2a sleeve (BLEND) sotto il cap PAIRS.
2. `report_families.build_everything` / `sleeves`: l'equity del nuovo sleeve dal
`pairs_sim_flat(tf=15m, flat_skip=True)` (per parita' backtest==report).
3. Shadow smoke su testnet (come `live_smoke_pairs.py`) prima del paper reale.
4. `deploy.sh` (bump+rebuild) — il runner gia' fetcha 15m e passa flat_skip via spec.params.
Test suite: nessuna regressione (1h byte-exact). Artefatti: pairs_research.py,
pairs_worker.py, runner.py, validate_worker_pairs.py, pairs15m_gate_final.py.
## ATTIVAZIONE IN REALE (2026-06-09) — BLEND, mezza size
Deciso: BLEND (sleeve 15m ACCANTO al 1h, non swap). Implementato:
- `_defs.py`: SleeveSpec `PR_ETHBTC_15M` (tf=15m, flat_skip, params.position_size=0.10
= meta' del family PAIRS 0.20) in PAIRS -> entra in PORT04/05/06.
- `report_families.build_everything`: equity da `pairs_sim_flat(tf=15m, flat_skip=True, pos=0.075)`
(mezza size, == intento live) con sid PR_ETHBTC_15M.
- `runner.pos_for_spec`: override PER-SLEEVE (params.position_size) > famiglia > globale.
- **Mezza size perche'** a peso pieno il 15m pesava il 25.8% del rischio PORT06 (vs 9.5% del
1h): dimezzato -> 11.5% vs 10.6%, bilanciato. Disciplina come la cap SHAPE; rispetta il
caveat slippage (il 15m non domina il book).
**PORT06 col BLEND (mezza size)**: FULL Sharpe **6.43->7.20** DD **3.96->3.68**,
OOS Sharpe **8.58->9.66** DD **1.36->1.31**. Migliora tutto.
**Smoke live 15m** (`pairs15m_live_smoke.py`): Cerbero serve candele 15m FRESCHE per
ETH e BTC (ultima barra 0 min fa, flat live 2-3%), worker flat-skip ticca OK. Esecuzione
reale a 2 gambe gia' coperta da `live_pairs_smoke.py` (livello strumento, tf-indipendente).
**Regression-lock aggiornati** (miglioria attesa, non regressione): test_definitions
(17->18 sleeve), test_backtest_parity_cap (FULL 6.47->7.20, OOS 8.82->9.66). Suite verde.
Live: il runner fetcha 15m diretto, costruisce il PairsWorker(flat_skip) col pos 0.10,
e lo esegue reale a 2 gambe (pairs_enabled). Attivazione via deploy (bump+rebuild).
@@ -0,0 +1,89 @@
# 2026-06-09 — ETH/BTC pairs a 15m: gate PORT06 (dal gioco Blind Traders)
## Origine
Il gioco "Blind Traders" (100 agenti ciechi) ha eletto come vincitore una variante
ETH/BTC pairs su **15m** (config #43: n=66 z_in=1.67 z_exit=1.0 max_bars=35). Domanda:
e' un vero miglioramento o un duplicato piu' veloce della sleeve PR01 ETH/BTC gia'
deployata a 1h? Testato sul serio con l'engine di PRODUZIONE `pairs_sim` + gate PORT06.
Script: `scripts/analysis/pairs15m_port06_gate.py`.
## Risultati
- **Parita' OK** (corr 1.00000): l'harness riproduce esattamente il sleeve canonico
PR_ETHBTC → gate affidabile.
- **CORRELAZIONE 1h vs 15m = 0.349** (rendimenti giornalieri). **SMENTISCE la mia
ipotesi iniziale "duplicato ridondante"**: a 15m cattura eventi di reversione DIVERSI
→ e' un diversificatore reale, non una doppia scommessa sullo stesso spread.
- **Robustezza 15m**: griglia n×z_in → **16/16 celle Sharpe>1** (9-12), plateau non picco.
Non e' un punto overfit del gioco.
- **Standalone**: 15m fa 9388 trade (vs 1756 a 1h), Sharpe 11.7 (vs 4.36), DD 54% (vs 48%),
8/9 anni+ . (Le % FULL sono esplose dal compounding pos0.15·lev3 su 9k trade → guardare
Sharpe/DD/anni, non il livello %.)
## Gate PORT06 (pos0.15 lev3 canonico, OOS da 2024-10-12)
| variante ETH/BTC | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| **baseline 1h** | 6.43 | 3.96 | 8.58 | 1.36 |
| **SWAP 15m** | 7.64 | 3.49 | **10.39** | **1.26** |
| **BLEND 1h+15m** | 7.30 | 3.63 | 9.95 | 1.24 |
A fee di backtest (0.20% RT/coppia) **entrambe PROMOSSE**: Sharpe su e DD giu' ovunque.
## Stress slippage a livello PORT06 (il vero rischio: 15m = 5× i trade)
| fee_rt | RT/coppia | PORT06 FULL Sh | FULL DD | OOS Sh | OOS DD | std Sh | std oDD |
|---|---|---|---|---|---|---|---|
| baseline 1h | 0.20% | 6.43 | 3.96 | 8.58 | 1.36 | 4.36 | 16% |
| 15m | 0.20% | 7.64 | 3.49 | 10.39 | 1.26 | 11.7 | 13% |
| 15m | 0.40% | 7.04 | 4.08 | 9.78 | 1.45 | 8.5 | 27% |
| 15m | 0.60% | 6.43 | 4.67 | 9.15 | 1.66 | 5.3 | 47% |
**Degradazione graziosa ma reale**: il vantaggio di **Sharpe** sopravvive fino a slippage
pessimista (OOS 9.15 > 8.58 anche a 0.60%), ma il vantaggio di **DD si perde gia' a 0.40%**
(FULL DD 4.08 > 3.96 baseline; standalone oDD esplode 13→27→47%). La regola del progetto
("ri-gateare ogni filtro quando cambiano i costi") qui taglia: la frequenza 5× rende la
sleeve slippage-sensitive.
## Verdetto
- **NON un duplicato** (corr 0.35) e **NON overfit** (16/16 robusto) → la mia liquidazione
iniziale era SBAGLIATA, lo dico chiaro.
- **Passa il gate a fee di backtest, marginale sotto slippage**: migliora Sharpe sempre, ma
sotto slippage realistico (≥0.40% RT) peggiora leggermente il DD di portafoglio.
- **Due rischi di produzione NON ancora quantificati**: (a) qualita' dati ETH 15m (14-30%/anno
candele flat O=H=L=C → fill non eseguibili che gonfierebbero il backtest), (b) fill/liquidita'
reale a 2 gambe a 15m (5× ordini). Il worker pairs e' validato a 1h, non a 15m.
**Raccomandazione**: NON swap diretto in live. Candidato promettente → percorso forward:
preferire il **BLEND 1h+15m** (tiene il DD pulito del 1h e raccoglie il rendimento
decorrelato del 15m) **dopo** un check sull'impatto delle candele flat 15m sui pairs.
Allineato a come il progetto tratta FR01 (robusto ma non deployato finche' non domina pulito).
Resta come record di ricerca; deploy solo se il check flat-candle e' pulito.
## CHECK FLAT-CANDLE (pairs15m_flatcheck.py) — PULITO
Rischio: ETH 15m ha molte candele flat (O=H=L=C) → close stale che gonfia z-score →
reversione FINTA non eseguibile. Test:
- **Prevalenza**: ETH 15m **16.4% medio** (fino 30% nel 2022); BTC 15m solo 3.5%. Reale.
- **Fill toccati**: 12.9% degli entry e 15.2% degli exit cadono su una barra flat.
- **Test decisivo** (entry/exit SOLO su barre pulite, non-flat in entrambe le gambe):
rimuove 11.2% dei trade, **Sharpe trattenuto all'83%** (11.74→9.70; OOS Sharpe 18.4).
Se l'edge fosse un artefatto flat, filtrando crollerebbe → **NON crolla. NON e' artefatto.**
- **Gate PORT06 col 15m FLAT-FILTRATO** (corr 1h vs 15m-flat = 0.366, ancora decorrelato):
- SWAP 15m-flat: FULL 7.32/3.55, OOS **9.99/1.26** → PROMOSSO
- BLEND 1h+15m-flat: FULL 7.05/3.66, OOS **9.60/1.24** → PROMOSSO
## Conclusione (3 box su 4 puliti)
✅ NON duplicato (corr 0.35-0.37) ✅ robusto (16/16) ✅ NON artefatto flat (83% Sharpe)
⚠️ slippage-sensitive: a fee backtest passa pulito; a slippage ≥0.40% RT il vantaggio di
Sharpe regge ma il DD-edge si assottiglia. Il **BLEND** mitiga (meta' allocazione resta sul
1h pulito e slippage-robusto) → e' la forma deployabile.
## Realta' del deploy (perche' NON tocco ancora il live)
Il gate passa a livello BACKTEST. Ma il live NON puo' eseguire un sleeve 15m oggi:
- la live pairs gira SOLO a 1h (`PairsWorker`, validato da `validate_worker_pairs` a 1h);
il runner risampla a 1h/4h/1d, non gestisce un leg pairs a 15m.
- un BLEND richiede DUE sotto-sleeve ETH/BTC (1h + 15m) dentro il cap PAIRS, e il
**flat-skip va replicato nel worker live** (altrimenti il live tradera' le barre stale che
il backtest esclude → divergenza backtest-vs-live, la classe di bug che il progetto teme).
Editare `_defs.py` cambierebbe solo il backtest/report, NON il live → sarebbe ingannevole.
**Percorso deploy corretto** (da confermare): (1) estendere `PairsWorker`/runner al 15m +
flat-skip; (2) `validate_worker_pairs` a 15m (replay == backtest esatto); (3) aggiungere lo
sleeve 15m sotto il cap PAIRS; (4) shadow su testnet prima del paper. Finche' (1)-(2) non
sono fatti e validati, resta **record di ricerca PROMOSSO ma non live**.
@@ -0,0 +1,45 @@
# 2026-06-09 — ETH/BTC 15m: analisi di robustezza (il limite è il COSTO)
Domanda: lo sleeve `PR_ETHBTC_15M` (attivato in real, v1.1.16) è robusto? Risposta dai
dati: **sì su parametri, tempo e qualità-dati; fragile sui COSTI** — e quel limite è
strutturale (alta frequenza). Engine `pairs_research.pairs_sim_flat` (live-realizable).
## ✅ Robusto su parametri, tempo, dati
- **Plateau parametrico**: griglia n×z_in (40-80 × 1.5-2.5) → **16/16 celle Sharpe>1**
(range 9-12). Non un picco overfit. (`pairs15m_port06_gate.py`)
- **Consistenza temporale**: 2018-2021 +1874% (3/4 anni+, solo 2018 negativo);
2022-2026 +12958% (**5/5 anni+**); **OOS ultimo 30% Sharpe 17.6, DD 13%, +**. L'edge
è distribuito, non un singolo regime.
- **Non artefatto dei dati**: filtrando le candele flat ETH 15m (16% storico) resta
l'**83% dello Sharpe** (`pairs15m_flatcheck.py`).
- **Decorrelato dal 1h** (corr rendimenti giornalieri **0.37**): segnale diverso, non un relabel.
## ❌ Fragile sui COSTI — il vero tallone (frequenza 5× il 1h)
Sharpe in funzione del costo all-in RT/coppia (fee + slippage):
| costo RT/coppia | **15m Sharpe** | 1h Sharpe |
|---|---|---|
| 0.20% (reale, 1×) | **9.34** | 4.36 |
| 0.40% (2×) | 6.15 | 3.52 |
| 0.60% (3×) | 2.95 | 2.68 |
| 0.80% (4×) | **0.24** | 1.84 |
| 1.20% (6×) | 6.63 | 0.16 |
**Il 1h regge ~6× i costi; il 15m va negativo già a ~4×.** Tanti trade piccoli (8453 vs
1756) → margine di costo sottile. Lo **Sharpe 9.34 è un numero a costo basso**: appena lo
slippage reale a 2 gambe porta l'all-in verso 0.40-0.60%, scende a 3-6 (ancora positivo,
ma l'edge enorme del backtest è in gran parte illusione da bassi costi).
## Implicazioni (coerenti con la config deployata)
1. **La mezza size era giusta**: non si dà al 15m il peso che il backtest a costo basso
suggerirebbe. È un blend-tilt, non una scommessa.
2. **NO allo swap** (togliere il 1h, tenere solo 15m): sostituirebbe l'àncora cost-robust
(1h, regge 6×) con la sleeve cost-fragile. Confronto PORT06: swap OOS 9.95 vs blend 9.66
— +0.3 di Sharpe di backtest pagati in robustezza reale. Non vale.
3. **Giudice finale = ledger reale shadow**: misurerà lo slippage vero a 15m. Soglia di
lettura: all-in <0.40% (Sharpe ≥6) → ottimo diversificatore, valutare size piena;
verso 0.60%+ → vale appena la mezza-size che ha (già protetto).
**In una riga**: robusto come *segnale*, fragile come *esecuzione* → sta nel portafoglio a
metà size accanto al 1h, non al suo posto. Riproducibile: fee-sweep + sub-periodo +
OOS via `pairs_sim_flat`; plateau/flat-check negli script `pairs15m_*.py`.
@@ -0,0 +1,73 @@
# 2026-06-09 — Statistiche per-anno di tutte le sleeve attive in REAL
Snapshot post-attivazione BLEND ETH/BTC 15m (v1.1.16). Breakdown per-anno delle **15
sleeve che eseguono ordini reali** su Deribit testnet (escluse le 3 PAPER multi-asset
TR01/ROT02/TSM01). Engine **path-live**: EXIT-16 + filtro trend 3.0 per le fade,
walk-forward expanding per SH01, flat-skip per il pairs 15m. **PnL% = somma dei
rendimenti netti per-trade levered ×3, fee 0.10-0.20% RT incluse** — NON il contributo
al portafoglio (che applica sizing pos, cap-weighting, leva 2x, ribilancio 1D); serve a
confrontare edge grezzo e DD per sleeve.
Riproducibile: famiglie pairs via `pairs_research.pairs_sim`/`pairs_sim_flat`; fade/DIP/SH01
via le funzioni `stats_fades`/`stats_dip`/`stats_sh01` di `scripts/analysis/make_strategy_doc.py`.
## FADE (6) + DIP01 — PnL% per anno (n trade)
| anno | MR01 BTC | MR01 ETH | MR02 BTC | MR02 ETH | MR07 BTC | MR07 ETH | DIP01 BTC |
|---|---|---|---|---|---|---|---|
| 2018 | 78 (48) | 85 (42) | +69 (127) | +10 (127) | 12 (38) | +15 (25) | 78 (79) |
| 2019 | 84 (57) | +21 (46) | +18 (118) | +120 (114) | +55 (52) | 40 (35) | 104 (62) |
| 2020 | 29 (68) | 35 (55) | +100 (112) | 94 (135) | 11 (53) | +39 (43) | +67 (56) |
| 2021 | +326 (82) | +238 (60) | +332 (136) | +160 (127) | +245 (45) | +72 (24) | +315 (69) |
| 2022 | +567 (108) | +749 (76) | +728 (106) | +650 (91) | +446 (82) | +472 (58) | +407 (83) |
| 2023 | +318 (122) | +248 (73) | +235 (96) | +524 (146) | +168 (67) | +337 (58) | +228 (83) |
| 2024 | +413 (113) | +888 (126) | +868 (149) | +1952 (180) | +377 (75) | +645 (87) | +332 (81) |
| 2025 | +368 (90) | +361 (86) | +386 (136) | +931 (151) | +190 (60) | +226 (55) | +303 (85) |
| 2026* | +69 (29) | 9 (24) | +88 (37) | +25 (41) | +58 (17) | +40 (17) | +7 (29) |
| **TOT** | +1870 (717) | +2376 (588) | +2823 (1017) | +4278 (1112) | +1517 (489) | +1806 (402) | +1476 (627) |
| **maxDD%** | 32 | 23 | 19 | 31 | 12 | 23 | 37 |
## SHAPE SH01 (2) — PnL% per anno (n trade)
| anno | SH01 BTC | SH01 ETH |
|---|---|---|
| 2018 | 66 (237) | +74 (239) |
| 2019 | +88 (318) | 19 (365) |
| 2020 | +194 (240) | 293 (219) |
| 2021 | +301 (224) | +67 (146) |
| 2022 | +64 (142) | +79 (91) |
| 2023 | +17 (118) | +21 (17) |
| 2024 | +110 (144) | +108 (47) |
| 2025 | +77 (85) | +540 (108) |
| 2026* | +59 (23) | 30 (25) |
| **TOT** | +845 (1531) | +547 (1257) |
| **maxDD%** | 23 | 61 |
## PAIRS (6) — PnL% per anno (n trade) | 15m a mezza size (pos 0.075)
| anno | ETH/BTC 1h | LTC/ETH | ADA/ETH | BTC/LTC | ETH/SOL | ETH/BTC 15m |
|---|---|---|---|---|---|---|
| 2018 | 343 (177) | — | — | — | — | 492 (913) |
| 2019 | +233 (198) | — | — | — | — | +792 (963) |
| 2020 | +323 (211) | — | — | — | — | +452 (867) |
| 2021 | +549 (202) | — | — | — | — | +1122 (949) |
| 2022 | +1344 (206) | +292 (45) | +899 (157) | +328 (58) | +1243 (147) | +2136 (898) |
| 2023 | +464 (213) | +56 (94) | +341 (173) | +61 (106) | +125 (168) | +689 (976) |
| 2024 | +1661 (253) | +1024 (195) | +1078 (225) | +638 (186) | +1302 (199) | +6313 (1353) |
| 2025 | +1173 (225) | +962 (196) | +1046 (187) | +686 (178) | +1264 (202) | +3664 (1123) |
| 2026* | +61 (71) | +279 (76) | +140 (71) | +265 (71) | +270 (88) | +155 (411) |
| **TOT** | +5464 (1756) | +2614 (606) | +3504 (813) | +1978 (599) | +4204 (804) | +14832 (8453) |
| **maxDD%** | 48 | 14 | 19 | 21 | 24 | 34 |
| **Sharpe** | 4.36 | 4.22 | 4.90 | 2.72 | 4.61 | 9.34 |
\* 2026 parziale (fino al 28 mag). Coppie alt (LTC/ADA/SOL) hanno storia solo dal 2022.
## Aggregato PORT06 (BLEND attivo, 18 sleeve def.)
**FULL Sharpe 7.20 / DD 3.68% / CAGR 76% — OOS Sharpe 9.66 / DD 1.31%**. Per anno:
2021 +19.0% · 2022 +73.3% · 2023 +39.8% · 2024 +220.1% · 2025 +110.5% · 2026* +7.7%.
Nessun anno negativo dal 2021.
## Letture
- Il PnL% grezzo del 15m e' grande perche' fa ~5x i trade del 1h (8453 vs 1756), non
per-trade: il confronto equo e' Sharpe (9.34 vs 4.36) e DD (34% vs 48%). Nel portafoglio
entra a mezza size -> ~11.5% del rischio (== il 1h).
- MR02 ETH = singolo motore piu' forte (+4278%, 2024 +1952%); MR07 il piu' selettivo
(DD 12-23%); SH01 ETH il piu' rischioso (DD 61%, coda 2020 293%) -> mitigato dal cap SHAPE.
- I 3 book multi-asset (TR01/ROT02/TSM01) restano PAPER, fuori da questo elenco.
@@ -0,0 +1,49 @@
# 2026-06-09 — XS01: reversione cross-sectional (famiglia nuova, trovata + deployata PAPER)
## Origine
Dopo aver scartato (alla cieca, coi giochi) trend/breakout/seasonal/opzioni/funding come
rumore o EV, ho cercato io un meccanismo *diverso* dalla mean-reversion pairwise. Trovato:
**XS01 — reversione CROSS-SECTIONAL** su 8 asset (BTC/ETH/LTC/ADA/SOL/BNB/XRP/DOGE).
## Meccanismo
Ogni HOLD=12 ore: classifica gli 8 asset per rendimento su LB=48 ore, pesi
w = (ret media_cross-section), normalizzati a gross 1 → **long i perdenti relativi /
short i vincenti**, market-neutral. Roll non sovrapposto (entry-to-entry = hold+1 barre).
Fee 0.10% RT/book. Cattura il FATTORE reversione trasversale, distinto dai pairs (pairwise).
## Verifica (engine canonico `scripts/strategies/XS01_cross_sectional.py`)
- **No look-ahead** verificato (segnale invariato perturbando il futuro).
- **Robusto**: plateau OOS Sharpe **23.9** su lb 1272 × hold 624.
- **Scorrelato**: corr **0.006 / 0.035** da PR01 ETH/BTC, 0.028 dai fade → diversificatore.
- Per-anno (entry): 2022 +34, 2023 +6, 2024 +21, **2025 +225**, 2026 +85 (5/5 anni+).
- **Caveat**: edge concentrato sul 2025; cost-sensitive (muore ~0.35% RT/book); 8 gambe;
storia dal 2022 (no 2018-2020).
## Worker validato (== backtest esatto)
`src/live/xsec_worker.py` `CrossSectionalWorker`: book market-neutral che rolla ogni HOLD
barre, stessa formula pesi e cadenza dell'engine. `validate_xsec_worker.py`: replay
bar-per-bar == backtest **ESATTO** (worker 4993/1427 trade/49.8% == backtest 4993/1427/49.8%).
Bug risolto: il primo prototipo rollava 1 barra troppo tardi (cooldown extra) → rimosso,
guard a lb+1, entry-to-entry = hold+1.
## Gate PORT06 — PROMOSSO (con asterisco)
| | corr | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|---|
| ATTUALE (19→ senza XS01) | — | 7.20 | 3.68 | 9.66 | 1.31 |
| **+XS01** | 0.006 | **7.34** | **3.46** | **10.07** | 1.48 |
Migliora 3 metriche su 4 (OOS Sharpe **+0.41**, il salto più grande dal 15m; FULL DD giù).
Unico neo: OOS DD +0.17pp. Risk-contrib XS01 solo **2.2%** (diversificatore a bassa vol).
## Deploy (v?, 2026-06-09) — PAPER
8 gambe → niente esecuzione reale (come TR01/ROT02/TSM01) → XS01 gira **PAPER**
(`paper_sleeves`), fuori dal pool, raccoglie statistica forward. Wiring: `_defs.XSEC` in
PORT06 (19 sleeve, family XSEC via prefix "XS"), `build_everything` (equity da xsec_sim),
`runner` kind="xsec" → CrossSectionalWorker, `asset_days` ora include i paper (fix: gli alt
BNB/DOGE/XRP ora vengono fetchati anche per TR01/ROT02/TSM01). Regression-lock aggiornati
(18→19 sleeve, FULL 7.20→7.34, OOS 9.66→10.07, DD 3.68→3.46). 93 test verdi.
**Direzione futura:** se la statistica forward conferma, costruire l'esecuzione reale a
N gambe (oggi inesistente) per portarlo nel pool. Per ora: candidato validato che gira
PAPER e si osserva. Artefatti: `scripts/strategies/XS01_cross_sectional.py`,
`src/live/xsec_worker.py`, `scripts/analysis/{validate_xsec_worker,xsec_port06_gate}.py`.
@@ -0,0 +1,46 @@
# 2026-06-10 — FC01 funding-carry market-neutral: SCARTATA (il carry non paga le fee)
## Idea
Carry NEUTRALE sul funding Deribit (meccanismo mai esplorato: W12 era lo short
direzionale su funding alto, bocciato): short della gamba col funding alto /
long della gamba bassa (ETH vs BTC, dollar-neutral), incassando il differenziale
di funding con esposizione residua = solo lo spread ETH/BTC.
Dati REALI: `data/regime/{btc,eth}_funding.parquet` — funding orario effettivo
dic 2019 → giu 2026 (56.938 ore), `interest_1h` + index_price.
Harness: `scripts/analysis/funding_carry_research.py` (causale: decisione al
close t, accrual da t+1; fee 0.10% RT per gamba; TRAIN ≤2023-11-01 / OOS dopo).
## Numeri che uccidono l'idea (prima ancora del backtest)
- Funding annualizzato: BTC mean +7.2% / med +0.3%; ETH +5.2% / +0.05% —
la MEDIANA è ~zero: il funding alto è episodico, non strutturale.
- Spread ETHBTC: p10 19% / p90 +12.6% annualizzato, MA autocorr del
24h-smooth a 72h = 0.20 → poca persistenza.
- **Episodi |spread|>10% ann: durata mediana 19h** (p90 60h) → carry
incassabile per episodio ≈ 10%/8760×19h ≈ **0.02-0.05%**, contro **0.20%**
di fee (2 gambe). Strutturalmente morto: il carry è un ordine di grandezza
sotto i costi, e il price-leg dello spread ETH/BTC (vol ben maggiore del
carry) domina il PnL.
## Backtest (conferma)
- FC-A spread-carry, griglia smooth {24,72,168}h × thr {5,10,20}% ann:
TRAIN quasi tutto negativo (Sharpe 0.6…+0.03), OOS negativo ovunque
tranne una cella (s24 thr5: OOS +38% con TRAIN 45% = rumore/sign-flip).
Sweep fee: negativa anche a 0.05% RT/gamba. Annuale: 2021 93%, nessuna
stabilità.
- FC-B direzionale single-asset (confronto onesto): negativa ovunque,
riconferma W12.
## Verdetto
SCARTATA. Su Deribit BTC/ETH il funding non è un carry harvestabile: troppo
piccolo, troppo breve, e l'hedge cross-asset introduce più rischio del carry
che raccoglie. Eventuale rivisita SOLO se: (a) si aggiungono alt ad alto
funding cronico (Hyperliquid, registry già validato) dove i livelli sono
5-10x, o (b) si usa il funding come FEATURE/gate di strategie esistenti
(es. filtro sulle entry PR01) invece che come fonte di PnL.
Script: `scripts/analysis/funding_carry_research.py` (resta come record negativo).
@@ -0,0 +1,113 @@
# 2026-06-10 — Gioco "Blind Traders" sessione 3: GRID TRADERS (regola: STRATEGIA_GRIGLIA.md)
## Setup
Terza sessione del gioco dei trader ciechi, con una regola nuova: ogni agente deve
implementare la strategia descritta in `STRATEGIA_GRIGLIA.md` (grid trading a griglia
geometrica). 100 agenti (haiku, via Workflow) ricevono SOLO un digest anonimo di due
serie X/Y (in realtà BTC/ETH, mai rivelato) sul loro timing assegnato
(25×15m, 20×30m, 20×1h, 15×2h, 12×4h, 8×1d) + 5 archetipi di stile a rotazione
(prudente / aggressivo / asimmetrico-rialzista / asimmetrico-ribassista / data-driven),
e propongono la CONFIGURAZIONE della griglia: `range_down/up %`, `grid_levels`,
`sl_buf`, `tp_buf`, `max_bars`, serie.
Infrastruttura nuova in `scripts/games/`:
- **`grid_engine.py`** — backtest deterministico, causale, fee-aware della spec:
griglia geometrica `ratio=((1+ru)/(1-rd))^(1/L)` costruita sul close di deploy,
capitale 1/L per livello (§3.3), buy su attraversamento ↓ di un livello non
riempito, sell del livello su attraversamento ↑ del successivo (§5.2), SL sotto
il range e TP sopra che liquidano tutto (§6), redeploy a fine episodio
(SL/TP/max_bars). **Vincolo break-even §4 implementato alla lettera**: passo
≤ 1.5×costo RT → il motore si rifiuta di partire (`refused`, fitness 2e6);
`_normalize` dell'arena riduce i livelli al massimo legale. Fill intrabar lungo
il percorso O→L→H→C / O→H→L→C; fee 0.10% RT per round-trip + slippage opzionale.
- **`grid_arena.py`** — torneo identico alle sessioni 1-2: split 60/20/20
TRAIN/VALID/TEST, 90 epoche di hill-climb sul TRAIN, cull del 10% in VALID ogni
10 epoche → 10 superstiti; TEST = OOS puro mai ottimizzato.
- **`grid_brief.py`** — digest anonimo con statistiche per dimensionare una griglia:
escursione max/min rolling (w100/500/2000, mediana e p90) e probabilità di fuga
da un range ±5/10/20% entro 500 barre.
## Esito
`data/games/grid_result.json` (+ log `grid_tournament.log`). 100/100 spec da agenti
reali (nessun sostituto random).
- **Alla proposta, 80/100 agenti scelgono X (=BTC)**: dai soli numeri anonimi capiscono
che la griglia sopravvive meglio sulla serie meno volatile (escape ±20% in 500 barre:
BTC 34.5% vs ETH 53.2% a 1h).
- **L'evoluzione ribalta la scelta: tutti i 10 superstiti finiscono su Y (=ETH)** —
nel periodo VALID/TEST la vol più alta di ETH paga di più i round-trip, e il rischio
trend è gestito non dal range stretto ma dalla FORMA della griglia (sotto).
- **Convergenza fortissima della forma** (9/10 superstiti): griglia **asimmetrica
ribassista** — range profondo sotto (13/20%), corto sopra (+4/+8%), **livelli al
minimo (4)** → passo largo ~4.5-5.5%, SL buffer profondo (5-15%), max_bars lunghi.
Tradotto: **compra i dip di ETH in 4 tranche distanziate ~5%, rivendi ogni tranche
al rimbalzo di un passo, stop catastrofale a ~25/30%**. Il gioco ha ri-scoperto
per la terza volta la mean-reversion (qui in forma di dip-buying a tranche), e ha
imparato da solo la lezione anti-fee: meglio pochi passi larghi che griglie fitte.
**Vincitore: agente #11, ETH 15m**, griglia 17.1%/+4.6% × 4 livelli, SL buf 12.4%,
TP buf 4.8%, max 2143 barre. TEST(OOS): **PnL +891% (additivo), win 97%, 38.5
trade/mese, Sharpe 10.1**. FULL 2018-2026 (include bear 2018 e 2022): +4284%, Sharpe 9.6.
Stress slippage TEST: 0.05%/lato → 871%, 0.10%/lato → 850% (il passo ~5.4% ⋙ costi).
## Caveat onesti (perché NON è un candidato deploy così com'è)
1. **Il PnL è additivo per-trade e non misura il drawdown UNREALIZED**: l'engine
somma i round-trip realizzati; mentre la griglia tiene 4 tranche dentro un
drawdown 17%, l'equity vera è sott'acqua (fino a ~15% di episodio + SL 25/30%
quando scatta). Il win-rate 97% è il profilo classico della griglia: tante
micro-vincite, perdite rare ma grandi (stessa famiglia di rischio del
short-vol/martingala). Per un gate serio servirebbe l'equity curve mark-to-market.
2. **Long-only su ETH con VALID/TEST 2023-2026** (regime rialzista/oscillante):
l'asimmetria 17/+4.6 è anche un fit al regime. Il FULL positivo (bear inclusi)
è incoraggiante ma il grosso del PnL resta nei periodi di reversione rialzista.
3. Le candele flat ETH 15m (14-30%/anno) e i fill intrabar "al livello" condividono
i caveat noti del progetto (bias pro-stop-stretti dell'engine intrabar; qui gli
stop sono larghi, quindi l'effetto è minore).
## Lezioni
- La spec STRATEGIA_GRIGLIA.md è implementabile in modo causale e onesto; il suo
vincolo break-even (§4) è esattamente la "lezione fee" del progetto, e il torneo
l'ha confermato spingendo i livelli al minimo (passo massimo).
- La griglia evoluta è un parente povero delle fade MR già in live: stesso edge
(reversione ETH), ma incassato con inventory risk crescente invece che con
TP/SL per-trade. Non sostituisce le MR; eventuale interesse solo come variante
"a tranche" da gateare con equity mark-to-market (e confronto con MR01/MR02 a
parità di rischio) PRIMA di pensarci per il PORT06.
## GATE "si puo' inserire?" (stessa sera) — NO-GO: edge = artefatto delle wick testnet
`scripts/analysis/grid_game_gate.py`: engine **mark-to-market** dedicato (equity
per barra = capitale + inventario al close, SL gap-aware, flat-skip, fee 0.10% RT),
metriche standard del progetto + gate PORT06. Primo passaggio ingannevole: standalone
WINNER 15m FULL Sharpe 5.61 DD 15.8%, corr max coi sleeve 0.34, plateau 16/16,
e il criterio formale promuoveva il half-size (OOS Sh 10.07->10.12, DD 1.48->1.36).
MA il breakdown annuale mostrava **+4946% nel 2022** (ETH 70%): impossibile.
**Verifica avversariale decisiva**: il feed Deribit (testnet) e' pieno di **spike
print** — ETH 15m ha 1064 barre con wick ≥6% sotto i close adiacenti che rientrano
subito (fino a 19% in 15 minuti; BTC ha spike a 54% nel feb 2024). La griglia
intrabar compra su quelle wick FINTE e rivende al rimbalzo: free money in sim,
infillabile dal vivo. Stress **close-only** (fill solo su attraversamento del
close): WINNER 15m CAGR 1544%->21%, Sharpe 5.61->0.92, OOS +2738%->+32%,
trades 3803->1156; top3 1h CAGR -1.7%, OOS 18%. **~99% dell'edge vive nelle
wick.** La versione onesta (Sharpe 0.92, DD 27%) e' molto sotto ogni sleeve
deployato → **NON inseribile**, in nessuna size.
Lezioni aggiuntive:
- Il torneo ha massimizzato esattamente l'artefatto: la convergenza su ETH 15m
range profondo era la firma del **wick harvesting**, non di un edge.
- I motori intrabar dei giochi (`grid_engine`, e in misura minore i TP intrabar
di `engine.py`) vanno SEMPRE stressati con una variante close-only prima di
promuovere qualunque vincitore: aggiungere il check ai prossimi giochi.
- Gli sleeve in produzione NON sono toccati da questo artefatto nello stesso modo:
pairs entra/esce sui close, le fade hanno EXIT-16 close-confirm sullo SL, e
soprattutto il ledger REAL-TRUTH usa i fill reali (che gli spike non fillano).
Artefatti: `scripts/games/grid_{engine,arena,brief}.py`, spec agenti in
`data/games/specs_grid/`, digest `data/games/grid_digests.json`, risultato
`data/games/grid_result.json`, log `data/games/grid_tournament.log`, gate
`scripts/analysis/grid_game_gate.py` (param `close_only` per lo stress anti-wick).
@@ -0,0 +1,57 @@
# 2026-06-10 — REAL-TRUTH: il ledger segue i fill reali (sim → diagnostica)
## Richiesta
L'utente vuole che sim e reale coincidano — o meglio, che il sim non esista come
verità: «voglio vedere dati che hanno avuto movimento reale su exchange». La
ricognizione dei 77 eventi reali (dal 2026-06-03) ha mostrato che il gap
sim/reale NON era slippage (piccolo, es. 0.15 bps su un TP) ma **contabile**:
1. **Ledger separati.** Il `capital` che guida portafoglio/ribilanci/sizing era
aggiornato dal PnL SIM; il PnL reale finiva in un `real_capital` parallelo che
non guidava nulla. MR01 BTC: sim +28.93 vs reale +1.98 sugli stessi 4 trade.
2. **Prezzi sim da candele testnet** (spike print, es. 2026-06-07 sim short BTC a
65266.5 con mark reale 62395): il sim bookava PnL che il reale non vede. Con
il ledger sim come verità, l'equity del portafoglio accumulava questa fantasia.
Nota: il *notional* reale era GIÀ derivato dalla formula sim
(`capital·ps·lev` passato a `_real_open`/`_real_open_pair`) — il punto 1 della
richiesta (sizing allineato) si chiude da solo una volta che `capital` è reale.
## Implementazione (REAL-TRUTH)
- `StrategyWorker` e `PairsWorker` accettano `real_truth: bool` (default False =
shadow storico). Con flag attivo e esecuzione abilitata:
- `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)`;
`applied=True` se ci sono fill reali (o chiusura verificata).
- `_close_position`/`_close` chiamano la chiusura reale PRIMA dell'update
ledger: `capital += real_pnl` (fee reali incluse); `is_win = pnl_reale > 0`.
- Il sim resta nel log CLOSE come diagnostica: `pnl_source` ("real" |
"sim_fallback"), `sim_pnl`, `real_pnl`.
- **Fallback al sim** SOLO se il trade reale non è mai esistito/fillato
(REAL_OPEN_FAIL, fill zero) — dichiarato nel log, mai silenzioso.
- Runner: `overrides.execution.real_truth` (yml) → `build_worker_for(...,
real_truth=)`. `portfolios.yml`: **`real_truth: true`**.
- Conseguenza a catena: equity ledger → pesi → allocazioni → notional dei
prossimi ordini derivano ora dai soldi veri sul conto. Il `real_capital`
parallelo resta come ledger puro-reale di confronto.
## Test
`tests/portfolio/test_real_truth.py` (6 test): capital segue i fill reali
(single-leg e pairs), una divergenza sim-win/reale-loss viene contata come LOSS,
fallback sim dichiarato su REAL_OPEN_FAIL/leg-fail, e modalità shadow invariata
senza flag. Suite completa: 99 passed.
## Limiti onesti
- I multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione (paper
sleeves fuori dal pool, capitale insufficiente per i book multi-leg).
- La STORIA del ledger non è riscritta: l'equity attuale (~2154) ingloba il PnL
sim accumulato fino a oggi (reale realizzato era ≈ 15.6 dal 3/6). Da ora la
divergenza smette di accumularsi; se si vuole un azzeramento (reset equity al
conto reale) è un'operazione separata e deliberata.
- Le DECISIONI di trading (entry/exit) restano guidate dai prezzi del feed
(candele testnet): real-truth corregge la contabilità, non i segnali. Gli
spike print del feed possono ancora generare entry/exit subottimali — ma ora
il loro effetto si misura in PnL reale, non in PnL immaginario.
@@ -0,0 +1,51 @@
# 2026-06-10 — XS01 dispersion-gate: PROMOSSO e LIVE (entry solo con dispersione da fare rientrare)
## Domanda
L'edge di XS01 (reversione cross-sectional 8 asset) era concentrato (2025 domina,
2023 quasi piatto) e cost-sensitive. La reversione cross-sezionale va accesa solo
quando c'e' dispersione da far rientrare?
## Metodo (anti multiple-testing): `scripts/analysis/xs01_dispersion_gate.py`
3 feature di regime CAUSALI calcolate dallo stesso panel closes (nessun feed
esterno): `g_disp` = std cross-section del momentum lb (la grandezza che si fada),
`g_corr` = correlazione media pairwise 72h (identita' della varianza dell'indice),
`g_vol` = vol BTC 168h. Diagnostica per quintili (quintili dal TRAIN, 70/30) sul
net per-trade dell'engine canonico NON gateato, TRAIN e OOS separati: si procede
solo con relazione monotona e concorde nelle due finestre.
## Esito diagnostica
- **g_disp: monotona e concorde** — Q1 NEGATIVO (10 bps TRAIN / 8 OOS) →
Q5 +26/+280. Senza dispersione i trade sono solo fee. PROMOSSA.
- g_corr e g_vol: non monotone / segno incoerente → BOCCIATE (niente fishing).
## Gate (sweep soglie = percentili TRAIN, side dal TRAIN)
Plateau pieno p30-p70, niente picco: TRAIN Sh 1.51 → 2.0-2.3, OOS Sh 5.73 →
6.2-7.5. Scelta **p50 (disp_min = 0.0313)**, ~47% delle ore aperte:
- Standalone: trade 1427→859 (40% turnover → meta' fee), win 50→53%,
**Sharpe 2.50→3.46**, DD 16.2→15.8%. **Ogni anno migliora**: 2022 +34→+40,
2023 +6→+28, 2024 +21→+44, 2025 +225→+237, 2026 +85→+108 — risolve la
concentrazione, il punto debole della validazione originale.
- Fee stress 2x (0.20% RT/book): OOS Sh 6.76 — la cost-sensitivity e' mitigata
(il gate taglia proprio i trade che pagavano fee senza edge).
- **Gate PORT06** (swap equity sleeve): FULL Sh 7.34→7.41 DD pari,
**OOS Sh 10.07→10.37 DD 1.48→1.47** → PROMOSSO (criterio standard).
## Implementazione (solo path LIVE, come trend/hurst sulle fade)
- `src/live/xsec_worker.py`: param opzionale `disp_min` (None = off), check in
`_open_book` su `nanstd(logC[i] logC[ilb])`. Default off → la validazione
`validate_xsec_worker` (replay == backtest) resta esatta.
- `src/portfolio/runner.py`: pass-through di `disp_min` (il runner costruiva il
dict params esplicitamente e l'avrebbe perso).
- `scripts/portfolios/_defs.py`: `disp_min: 0.0313` nella spec XS01.
- Il backtest canonico (`build_everything`) resta NON filtrato → il live fara'
meglio del backtest, coerente con le altre guardie.
Unit check: gate blocca panel piatto / apre panel disperso / default off invariato.
99/99 test. Nota macro della giornata: FC01 funding-carry SCARTATA
(diario separato) — il protocollo promuove ~1 idea su molte, come deve.
@@ -0,0 +1,86 @@
# 2026-06-11 — Sweep di stabilità su tutte le strategie (anti-overfit)
Obiettivo: analisi di tutte le 19 sleeve cercando miglioramenti/correzioni/protezioni che
aumentino la STABILITÀ, con disciplina anti-overfit (ipotesi pre-registrate, griglie fissate
prima di guardare i numeri, verdetti su TRAIN E OOS con plateau, gate PORT06; nessun re-test
di idee già bocciate: ADX/vol-target/time-stop/hurst/stop SH01/multi-TF/entry-guard ecc.).
## Audit di drift (nessun fitting)
- `report_families` sui dati correnti: correlazioni cross-famiglia ancora ≈0 (pairs 0.03-0.11,
XS01 0.01, SH 0.03-0.04 vs MASTER-9) → la diversificazione NON è driftata.
- **Regression-lock trend_max** (`trendmax_port06_impact`) rieseguito su dati freschi:
parità 1.00000 su tutte e 6 le fade (engine live-path integro) e **plateau trend_max
2.5/3.0/3.5 confermato** (OOS Sh 11.14/11.24/10.98, DD 1.33 identico).
- **Percentile del rolling-return PORT06** (cap weights, storia 2021+): finestra corrente
60g al 19°, 120g al 21°, 160g al 28° percentile → periodo fiacco ma in variazione normale.
- ⚠️ **FADE in coda storica**: il rolling 120g equal-weight delle 6 fade è a **1.0% =
2° percentile** della propria storia (p5 = +0.4%). È il tratto peggiore mai attraversato
dalla famiglia. Decisione esplicita: NESSUN ritocco ai parametri (sarebbe fit sul regime
corrente, l'errore che il progetto evita da sempre); la protezione è la diversificazione
(già in atto: il portafoglio regge al 19-28° pct) + monitoraggio. Follow-up in TODO:
alert di drift per-famiglia nel hourly_report (distribuzione storica precomputata).
## Correzione: bug contabile TR01 worker (FIXATO)
`BasketTrendWorker.tick` usava `mean(rets)` sui SOLI asset in posizione → con paniere
parziale sovrappesa N/k (con 1 solo long: 0.45 del capitale invece di 0.09). Era l'origine
della divergenza replay 44% vs reference +42% annotata nel TODO. La convenzione canonica
(backtest PORT06 via `_tr_basket_daily`) è equal-weight 1/N sull'universo: fix 1 riga
(`sum(rets)/len(universe)`). Replay post-fix: **TR01 +32% vs reference +42%** (stesso segno
e ordine di grandezza = gate del validatore; il residuo è la differenza dichiarata
capitale-unico vs media-equity). ROT02 +171%==ref, TSM01 +5%==ref invariati. Solo
statistica PAPER: nessun effetto su pool/ordini. NB: il forming-bar su ROT02/TSM01 segnato
nel TODO era GIÀ fixato (v1.1.10, `_panel` scarta la barra in formazione) — TODO aggiornato.
## Protezione 1 (pre-registrata): disaster-cap z sui pairs → **NO-GO**
Ipotesi: exit immediata se |z| ≥ z_stop dopo l'ingresso taglia la coda da structural-break
senza toccare i trade normali. Griglia fissata: z_stop {3.0,3.5,4.0,5.0} × 5 coppie 1h +
{2.5,3.0,3.5,4.0} sul 15m, train <2023-11-01 / OOS, engine con regression-lock ESATTO su
`pairs_sim`/`pairs_sim_flat`. Esito (potere statistico AMPIO, centinaia di trigger):
**bocciata su tutti e 3 i criteri** — il DD peggiora quasi ovunque, il worst-trade OOS
peggiora su 4 coppie su 6 (ETH/BTC 62→−168%!), Sharpe OOS cala oltre il 10% relativo in
TUTTE le celle, e il "plateau" esiste solo per il danno (monotono: più stretto, peggio).
Meccanismi: (i) lo stop realizza la perdita al massimo overshoot — il movimento che la
strategia fada (stessa lezione EXIT-16/SH01, **5ª conferma**); (ii) l'engine non-overlap
rientra subito nello spread ancora divergente → churn di fee a 2 gambe e stop ripetuti.
I pairs restano senza stop by design; la mitigazione resta la taglia
(`position_size_family` PAIRS 0.20). Record: `scripts/analysis/pairs_zstop_research.py`.
## Protezione 2 (pre-registrata): phase-tranching XS01 → **PROMOSSA e LIVE**
Diagnosi: il roll non-sovrapposto di XS01 ha una FASE arbitraria (dipende da quando il
worker parte) e l'esito ne dipende parecchio — sulle 12 fasi possibili: Sharpe daily FULL
1.52-2.33, DD per-trade 13.8-33.1% (`xs01_tranche_research.py`). È timing-luck puro: il
backtest canonico (fase 0) è una delle estrazioni FORTUNATE sul FULL (DD 15.4 vs mediana
~21). Rimedio senza parametri fittati: **ensemble di fase** — K sub-book sfasati di hold/K
barre su capitale comune (PnL/K). Gate onesto su equity daily (`xs01_tranche_gate.py`):
- standalone OOS: Sharpe 3.79→4.27 (K=2) →4.85 (K=3), DD 7.99→7.25→5.56;
- FULL standalone: il DD sale verso la mediana di fase (~21%) — il "peggioramento" è la
RIMOZIONE della fortuna della fase 0, non un costo;
- PORT06 swap-sleeve: FULL invariato (7.33/3.46), OOS Sh 10.07→10.11 (K=2) →10.15 (K=3),
OOS DD 1.48→1.43→1.38. **Plateau: K=2 E K=3 entrambi promossi** (non best-pick).
Implementazione: `CrossSectionalWorker` param `tranches` (default 1 = storico; live K=3 in
`_defs.py`), books indipendenti con sfasamento iniziale `wait`, capitale comune, migrazione
automatica dello status legacy (il vecchio book → tranche 0), `last_bar_ts` solo-avanti
(robustezza ai panel accorciati da feed in ritardo). Solo path LIVE come `disp_min` (il
backtest canonico resta single-phase → il confronto live/backtest va letto con la mediana
di fase in mente). **Validatore esteso e PASSATO**: K=1 replay == `xsec_sim` ESATTO
(1427 trade, cap 4993==4993); K=3 replay == unione fasi 0/4/8 ESATTO (4279 trade,
4512==4512). Osservabilità: `hourly_report` aggrega i sub-book nel book medio.
## Non toccati (per evidenza, non per pigrizia)
- **Fade/DIP01**: exit-lab già esaustivo (23+11 famiglie); plateau e parità riconfermati oggi.
- **SH01**: 11 famiglie di stop già bocciate; cap famiglia + monitor trade-rate in essere.
- **ROT02/TSM01**: replay == reference; forming-bar già fixato.
- **TR01 come sleeve** (3 anni negativi su 6): rimosso? NO — sarebbe una decisione guidata
dal regime recente (recency bias); il suo ruolo è catturare i trend che le fade non
prendono. Resta, con la statistica ora contabilizzata correttamente.
## Esito test
`pytest`: 99 passed. Validatori: honest workers OK, xsec K=1/K=3 OK, trendmax lock OK.
+92
View File
@@ -0,0 +1,92 @@
# 2026-06-11 — Audit di sistema + verità contabile sul netting (v1.1.24)
Seconda tornata della giornata (dopo lo sweep strategie): analisi del SISTEMA live
(esecuzione, stato, feed, processo) con 3 indagini parallele + fix.
## Audit live (dal reset 2026-06-10T21:24Z)
- **Churn phantom-TP quantificato**: 20 round-trip fantasma sui fade ETH (2 finestre,
13:06Z e 17:32-17:58Z, feed che stampava wick ~1640 con mercato a 1675-90 per 25 min).
Somma real_pnl 2.35 USD vs sim_pnl +80 USD: il real-truth ledger ha contenuto, il gate
TP_PHANTOM (v1.1.23) copre il pattern da stasera.
- **Il conto NON quadrava coi libri**: short 0.027 ETH in più (l'hedge long ETH del pair
ETH/SOL mangiato dai close reduce-only dei fade) e BTC flat con MR02_BTC convinto di
essere short (TP resting fillato da uno spike REALE del book a 3.8%, +6.6$ non bookati,
che il worker riconcilierà alla chiusura sim) col **disaster-SL residuo sul book a
posizione flat**. Bonifica eseguita: DSL `USDC-SLMB-26521` cancellato, riallineo
+0.027 ETH reduce-only (fill 1682.5) → conto == libri (verificato).
## Root-cause strutturale
Le **quote per-worker con ordini reduce-only su un conto a NETTING si rompono quando due
worker hanno direzioni opposte sullo stesso strumento** (pairs long ETH vs fade short ETH):
- un close reduce-only può essere **cappato** (Deribit riduce l'amount in silenzio) → il
ledger bookava la chiusura PIENA perché `Fill.amount` era il richiesto, non il fillato;
- un close reduce-only nel verso "sbagliato" rispetto al netto viene **respinto** → la
gamba pairs resta orfana sul conto, ma il worker bookava il PnL al prezzo sim e azzerava
lo stato (3 volte oggi: PnL fantasma nel ledger real-truth, ETH/SOL di fatto short nudo).
## Fix (v1.1.24) — verità, non workaround
1. **`Fill.filled_amount`** (da `order.filled_amount`, fallback trades/history): tutti i
ledger usano il fillato; nota "FILL PARZIALE" nel Fill.
2. **`REAL_CLOSE_PARTIAL`** (log + Telegram): close che filla meno del residuo → residuo
orfano dichiarato, `REAL_CLOSE verified=false`.
3. **Pairs per-gamba**: PnL bookato SOLO per gambe con fill verificato; gamba respinta →
record in `orphan_legs` (persistito nello status) + alert `PAIR_LEG_ORPHAN`;
`applied=True` (real-truth) solo con ENTRAMBE le gambe → altrimenti fallback sim
DICHIARATO (meglio del numero mezzo-reale di prima).
4. **`REAL_DIVERGENCE` anche su jsonl** (prima solo Telegram: l'audit ha dovuto
ricostruire gli episodi dai REAL_CLOSE).
5. **Runner: tick isolato per-worker** — un'eccezione in un worker non salta più gli
altri né l'update equity; streak per-worker con alert `WORKER_ERROR_STREAK` a 5.
Test: 2 nuovi (partial-close, orphan-leg) + fixture aggiornate → 106 passed.
## Decisione di design APERTA (per l'utente)
Finché pairs e fade condividono strumenti in direzioni opposte, le chiusure possono
orfanizzarsi: ora è VISIBILE e contabilizzato giusto, ma non eliminato. Opzioni:
A) **Position-manager centrale per strumento** (il runner netta i delta di tutti i
worker e manda UN ordine; ricostruisce le quote contabilmente) — corretto ma
invasivo, da progettare con calma;
B) **Sotto-conti Deribit separati** per famiglia (pairs vs fade) — pulito, richiede
setup conto e client multi-token;
C) **Status quo monitorato**: alert PAIR_LEG_ORPHAN + riallineo manuale (oggi: 1 giro
in 2 minuti) — accettabile su testnet, NON per capitale vero.
Raccomandazione: C ora, A prima di passare a capitale reale.
**Secondo passo — FATTO (v1.1.25): NETTING delle chiusure market.** `close_amount`
tenta il reduce-only e riesegue il residuo cappato/respinto in market puro (= il
netting contro le quote opposte: il conto si muove del delta esatto del libro).
Un solo punto di fix (anche `close_pair` ci passa). Fill combinato per il chiamante
(prezzo pesato, fee sommate), evento `NET_CLOSE` su log+Telegram a ogni fallback,
4 test dedicati. Niente più orfani per costruzione; `orphan_legs` resta come ultima
difesa se fallisce anche il market puro. Effetto collaterale benefico: la chiusura
futura della gamba ETH di ETH_SOL#2 (che sarebbe stata respinta di nuovo) ora
eseguirà correttamente. La scelta A-vs-B-vs-C resta aperta solo per la parte
RESTING (TP/DSL su book condiviso) e per i multi-asset.
**Primo passo verso A — FATTO (sera stessa): reconciler read-only.**
`scripts/analysis/reconcile_account.py`: per ogni strumento USDC confronta
atteso (Σ quote reali dai status.json: single-leg + pairs 2 gambe + orphan_legs
registrati = drift SPIEGATO) vs conto reale (`get_positions`, size/mark → coin),
tolleranza 1.5×step, anti-race (ricontrollo a 10s prima di segnalare). In crontab
host ORARIO (:40) con alert Telegram `ACCOUNT_DRIFT`. Al primo run ha beccato un
vero positivo: BTC libro short 0.0028 vs conto flat (il TP di MR02_BTC fillato
dallo spike reale delle 12:22Z, che il worker riconcilierà alla chiusura sim) —
esattamente la classe di divergenza che prima restava invisibile per ore.
## Altri esiti della tornata
- **Dedup engine gate** (TODO chiuso): `_port06_gate_common.py`, output 3 gate
byte-identici, nessun copy-drift trovato (la paura era fondata ma non ancora avverata).
- **Bug bfill `_daily_equity` quantificato** (TODO aggiornato): NON materiale — OOS
invariato per costruzione, FULL DD 3.46→3.67 col fix (l'attuale è lievemente ottimista),
nessun verdetto di gate a rischio. Lasciato documentato.
- **Drift monitor in produzione**: `drift_monitor.py` in crontab host (07:15 UTC,
Telegram): rolling 60/120g per famiglia vs distribuzione storica propria, warn < p5.
Oggi: FADE 120g al p2 (coda storica, nessun intervento), resto normale, XSEC p84.
- **Dati cerbero-bite refreshati**: catena opzioni a ~153k righe/asset (fino a oggi
19:30), pannello regime denso (net-GEX 644/673 ultimi 7g). Sempre un solo regime:
niente validazione edge, valore forward.
+74
View File
@@ -0,0 +1,74 @@
# 2026-06-12 — ACCEL50: cosa accelera davvero verso €50/giorno
**Domanda.** Quali strategie/leve accorciano il tempo per arrivare a €50/g da ~€2k?
Script: `scripts/analysis/accel50_research.py`.
## Il quadro onesto
A €2k, anche col CAGR OOS del PORT06 (~111% a lev 2), il PnL atteso è ~€4/g: il
collo di bottiglia NON è l'edge (Sharpe OOS 10), è la **taglia**. Le vie testate,
in ordine di impatto:
## 1. LEVA — l'acceleratore dominante (nessuna ricerca nuova)
Frontiera su daily return canonici PORT06 (scala lineare, fee pro-quota):
| lev | CAGR full | DD full | CAGR OOS | DD OOS | anni a €50/g da 2k |
|-----|-----------|---------|----------|--------|--------------------|
| 2 (attuale) | 74% | 3.5% | 111% | 1.5% | 3.3 |
| 3 | 128% | 5.2% | 206% | 2.2% | 1.9 |
| 4 | 200% | 6.9% | 343% | 2.9% | 1.2 |
| 5 | 293% | 8.6% | 539% | 3.7% | 0.9 |
Anche scontando l'OOS del 50% (regime calmo, caveat noto), lev 3-4 dimezza i
tempi tenendo il DD sotto il 10%. Caveat: il modello è lineare — non cattura
margine, code grasse, slippage che cresce col notional, e gli sleeve senza stop
(PAIRS/SH01) a leva alta hanno code peggiori del modello. Proposta sobria:
**lev 2→3 subito** (DD full 5.2% = ancora metà del PORT02 storico), rivalutare
4 dopo un mese di ledger reale pulito.
## 2. FADE 15m — la candidata nuova che PASSA il probe
MR01/02/07 a 15m, parametri live 1h non ri-tunati (anti-overfit), fee 0.10% RT:
- **Tutti e 6 gli sleeve positivi**, OOS 2025-26 positivo ovunque, fee 2x OK
(Sh 1.6-2.9 — margine ampio).
- **BTC 15m domina il suo 1h**: MR01 Sh 3.37 vs 2.76 con META' del DD
(15.1% vs 31.7%); MR02 3.49 vs 3.29 (DD 10.8 vs 18.7).
- ETH 15m leggermente sotto il 1h in Sharpe ma OOS molto più grande in valore
assoluto (4x trade = compounding più veloce; MR02 +22052 vs +9560).
- Infrastruttura live 15m GIA' esistente (fetch sub-orario del BLEND pairs).
**Prossimi passi obbligati prima del deploy** (metodologia standard):
gate PORT06 con correlazione 15m↔1h (se ~1 sostituire, se bassa aggiungere),
griglia parametri al 15m, validazione worker, caveat flat ETH 15m (14-30%
storico — per le fade single-leg il fill flat è meno tossico che per i pairs,
ma va guardato con flat-aware engine).
## 3. PAIRS nuove — BOCCIATE (stale-print illusion, di nuovo)
Sweep onesto delle 19 coppie mai testate (config universale pre-registrata
n=50 z2.0/0.5 max72): 8 candidate con Sh 1.5-4.3... MA le gambe alt hanno
88-98% barre flat (ADA 98%, LTC 97%, DOGE 91%, XRP 88%, BNB 88%) e con
`flat_skip=True` muoiono quasi tutte (BTC/ADA 4.33→0.17, ETH/DOGE 3.79→0.46;
migliore superstite ETH/XRP 1.34 < le 5 deployate). Identica classe di
illusione del XEX su DOGE/SOL (stessa giornata, `xex_divergence_research.py`).
**PAXG idem**: 92% flat su Deribit → chiuso anche il ramo "oro".
NB: questo getta anche una luce nuova sulle gambe alt dei pairs GIA' deployati
(ADA/LTC/SOL hanno flat share altissime nel parquet) — il loro ledger reale
shadow è il banco di prova giusto e finora regge, ma teniamolo d'occhio.
## 4. CAPITALE — domina tutto
A config attuale: €50/g ≈ €24k di capitale. Ogni € aggiunto accorcia
linearmente; nessuna ricerca batte un deposito. In più, a ~€20k si sbloccano
in esecuzione reale i 4 book multi-asset oggi solo paper (TR01/ROT02/TSM01/XS01).
## Sintesi operativa
Ordine d'impatto: **capitale > leva (2→3) > fade 15m (da gateare) >>** tutto il
resto. Le vie "nuova strategia esotica" (pairs nuove, PAXG, XEX) sono tutte
morte oggi sotto il test di esecuzione realistica — la lezione del giorno è che
su questo testnet ogni edge va validato col **book/flat-aware engine** prima di
crederci.
@@ -0,0 +1,57 @@
# 2026-06-12 — FADE TF SWEEP: 1m / 2m / 5m / 10m / 30m (post-swap 15m)
Richiesta utente: estendere l'analisi timeframe dei fade oltre il 15m appena
deployato (v1.1.30). Script: `scripts/analysis/fade_tf_sweep.py`.
Dati: parquet locale (5m/15m/30m full-history; 10m = resample dal 5m, unit-safe);
1m/2m da Cerbero (120 giorni recenti — la storia 1m locale non esiste: esclusa
dal refresh notturno per costo, 2m/10m non sono intervalli nativi del v2).
## A. Storia completa (engine canonico, OOS da 2024-10, fee 0.10% RT)
OOS Sharpe per timeframe (e OOS Sharpe a fee 2x del peggiore):
| tf | MR01_BTC | MR02_BTC | MR07_BTC | MR01_ETH | MR02_ETH | MR07_ETH | worst f2x |
|----|---------|---------|---------|---------|---------|---------|-----------|
| 5m | 3.66 | 1.90 | 4.12 | 5.31 | 6.54 | 5.52 | **MR02_BTC 1.70** |
| 10m | 2.62 | 2.69 | 3.31 | 5.32 | 6.49 | 5.59 | MR02_BTC 0.32 |
| 15m (live) | 1.94 | 2.30 | 2.37 | 4.94 | 6.40 | 4.44 | MR02_BTC 0.60 |
| 30m | 1.35 | 2.32 | 1.56 | 3.25 | 5.23 | 2.81 | MR02_BTC 1.40 |
**La frontiera è monotona**: più il tf scende, più Sharpe sale (MR01/MR07)… e più
il margine fee si assottiglia. A fee 2x MR02_BTC muore a 5m e resta fragile a 10m.
MR02 (donchian) fa 3-6x i trade degli altri: è la strategia più esposta al churn.
## B. Finestra comune recente (2026-02-12 → 06-12, il regime CORRENTE)
- **MR02 sotto i 15m è un disastro**: 1m 64%, 2m 44%, 5m 22% (fee-death).
- **MR01 a 1m brilla** (ETH +60.6%, Sh 5.7; BTC +33.5%) ma **muore a fee 2x**
(unico sopravvissuto MR01_ETH +16.5%): margine troppo sottile per fidarsi.
- Flat share a 1m: ETH 25.6%, BTC 13.3% → rischio stale-print alto (la lezione
del giorno: pairs-alt/XEX/PAXG).
- Il regime recente è CALMO: anche il 5m vi è fiacco (+4.8/22.9/+3.7 BTC).
I tf veloci pagano nella volatilità, non nella calma — il loro vantaggio
full-history viene dai regimi mossi (2021-22, 2024).
## C. Correlazione col 15m live (daily, storia completa)
5m↔15m media **0.46**, 10m↔15m media **0.53** (range 0.28-0.81). Diversificazione
parziale: un eventuale ADD del 10m avrebbe senso ma è meno pulito del salto
1h→15m (che era a 0.26).
## Verdetto
- **1m / 2m: CHIUSI.** Fee-margin nullo a stress, microstruttura flat pesante,
validazione full-history impraticabile. Non deployare mai MR02 sotto i 15m.
- **5m: no-swap.** L'edge c'è ma MR02_BTC muore a fee 2x — viola il criterio di
robustezza fee che tutte le strategie deployate rispettano.
- **10m: in WATCHLIST.** Quasi l'edge del 5m con più margine (f2x 0.32 resta
sotto la soglia di comfort per MR02_BTC; MR01/MR07 reggono bene). Possibile
ADD selettivo (solo MR01/MR07?) da gateare su PORT06 più avanti — NON ora:
il 15m è live da poche ore, un cambio alla volta e si lascia parlare il
ledger reale.
- **15m: confermato** come ginocchio della frontiera margine-fee/rendimento.
Collaterale tecnico: bug di resample scoperto e fixato nello sweep — pandas 2.x
conserva `datetime64[ms]` da `to_datetime(unit="ms")`, quindi `.view(int64)//10**6`
divide due volte e manda i timestamp nel 1970 (equity piatta silenziosa). Usare
`(index - EPOCH) // pd.Timedelta(milliseconds=1)`.
+78
View File
@@ -0,0 +1,78 @@
# 2026-06-12 — GATE PORT06: fade 15m PROMOSSI (tutte e tre le varianti)
Seguito del probe ACCEL50: i 6 sleeve fade (MR01/02/07 × BTC/ETH) a 15m,
parametri live 1h NON ri-tunati, engine canonico `build_trades`/`fade_daily_equity`
parametrizzato sul timeframe. Script: `scripts/analysis/fade15m_port06_gate.py`.
## Risultati
**[1] Parità.** Builder locale a 1h == sleeve canonico, diff 0.00 esatto su tutti e 6.
**[2] Standalone (daily, pos 0.15 lev 3, fee 0.10% RT).** Il 15m batte il twin 1h
quasi ovunque; OOS Sharpe: MR01_ETH 4.94 (vs 1.10), MR02_ETH 6.40 (vs 4.72),
MR07_ETH 4.44 (vs 1.97), MR07_BTC 2.37 (vs 1.59). Fee 2x: regge ovunque tranne
MR02_BTC (OOS 0.60 — il piu' fee-sensitive, ma a fee reali e' il migliore BTC).
**[3] Correlazione 15m↔1h: media 0.26** (range 0.16-0.41) — più decorrelato del
pairs 15m promosso a 0.37. NON è la stessa scommessa più veloce: è un edge a
orizzonte diverso.
**[4] Gate PORT06** (cap PAIRS 0.33 / SHAPE 0.0588):
| variante | FULL CAGR | FULL DD | FULL Sh | OOS CAGR | OOS DD | OOS Sh |
|----------|-----------|---------|---------|----------|--------|--------|
| BASELINE (1h) | 74% | 3.46% | 7.34 | 111% | 1.48% | 10.07 |
| **ADD (+6 sleeve 15m)** | 85% | **2.73%** | 8.02 | 125% | **1.38%** | **10.48** |
| SWAP (15m al posto 1h) | **101%** | 2.47% | 8.13 | **163%** | 2.09% | 10.86 |
| BLEND 50/50 | 87% | 2.31% | 8.06 | 136% | 1.77% | 10.82 |
Tutte e tre PROMOSSE dal criterio standard (OOS Sharpe non peggiora E DD scende).
**[5] Anti-illusione flat (il check che ha ucciso pairs-alt e XEX/DOGE).**
ETH 15m ha 14-30%/anno di candele flat. Con flat-entry-skip l'edge SOPRAVVIVE:
MR01_ETH Sh 3.11→2.85 (OOS 4.94→4.60), MR02_ETH 4.72→4.63, MR07_ETH 3.76→3.55;
BTC invariato (≤0.01). Coerente col finding registrato 2026-05-28. La quota di
ritorno persa (~30-40% del FULL ETH) è il caveat slippage onesto: il ledger
reale shadow sarà il giudice, come per il pairs 15m.
## Raccomandazione
**ADD** è la promozione più pulita: migliora TUTTE le metriche senza buttare il
track record live dei 6 sleeve 1h (SWAP rinuncia alla config validata dal vivo;
il suo OOS DD peggiora 1.48→2.09). In subordine BLEND (miglior FULL DD 2.31%).
## Per il deploy (non fatto qui)
1. `_defs.py`: 6 SleeveSpec nuovi `MR0x_{asset}_15M` con `tf: 15m` nei params.
2. Runner: estendere il fetch sub-orario (già esistente per PR_ETHBTC_15M) ai
fade 15m; verificare lookback (EMA200 a 15m = 50h → bastano ~2200 barre).
3. Validazione worker a 15m (replay == backtest, come validate_worker_pairs).
4. Sizing: a peso pieno i 6 sleeve 15m raddoppiano la famiglia FADE; valutare
position_size ridotto come il blend pairs (il gate ADD qui è già a peso pieno
e migliora comunque, ma il caveat slippage 15m suggerisce prudenza).
5. MR02_BTC 15m è il più fee-sensitive (fee2x OOS 0.60): monitorare le fee reali.
## Addendum — DEPLOY: SWAP SECCO (stessa giornata, scelta utente)
L'utente ha scelto **SWAP** (la variante a CAGR più alto), consapevole dei due
costi discussi: OOS DD 1.48→2.09% e perdita della maturità operativa 1h (la
meccanica live — EXIT-16 confirm, TP_PHANTOM, TP resting — gira 4x più veloce
su un feed testnet con wick anomali; il gate misura l'edge, non la robustezza
operativa). Proposta alternativa staged (ADD temporaneo 1-2 settimane, poi
spegnere il 1h) declinata: "swap secco".
Implementazione (deliberatamente minima, sid INVARIATI → pesi/alloc/epoche
intatti):
- `_defs.py`: FADE specs → `tf="15m"` (DIP01 resta 1h: non era nel gate).
- `combine_portfolio.py`: `FADE_TF="15m"` nel builder canonico → le due facce
(backtest/live) restano sulla STESSA definizione; il lock di parità
(`test_backtest_parity`) confronta le facce fra loro e resta verde.
- Runner: NESSUNA modifica (il fetch sub-orario BTC/ETH 15m esisteva già per il
blend pairs; `_spec_assets_tf`/`_series_for` sono generici).
- Worker: nuove dir `*__15m` (stato 1h preservato in `*__1h`, worker flat al
momento dello switch). Esecuzione reale invariata (match per nome MR01/02/07).
- max_bars=24 barre ora = 6h; EXIT-16 confirm sulla barra 15m completata.
Monitor post-deploy: stop-rate e divergenza sim/reale dei fade 15m
nell'hourly report; fee reali su MR02_BTC; STALE_FEED (le barre flat 15m
sono più frequenti che a 1h).
@@ -0,0 +1,51 @@
# 2026-06-12 — Sweep migliorie/strategie nuove (sera, post-swap 15m)
Richiesta: cercare altre migliorie da implementare o strategie nuove. Tre
esperimenti + una miglioria di codice.
## 1. INIT_LINEAGE — eredità capitale al cambio timeframe (IMPLEMENTATO)
`StrategyWorker._inherit_lineage_capital`: al primo avvio (niente status.json)
il worker eredita `capital`/`real_capital` dal worker più recente di stessa
strategia+asset su altro tf (glob `{strategy}__{asset}__*`). MAI la posizione.
Nato dallo swap 1h→15m di oggi: i worker nuovi partivano dall'allocazione del
pool scartando il PnL del gemello (16.8 di equity fantasma, riallineata a mano
col seed). Il prossimo swap non avrà bisogno di seed manuale.
Test: `tests/portfolio/test_capital_lineage.py` (eredita / no-sibling / resume).
## 2. Gate 10m ADD selettivo (MR01/MR07) — BOCCIATO, watchlist chiusa
Baseline aggiornata al PORT06 post-swap (fade 15m): ADD di 4 sleeve 10m
(MR01/MR07 × BTC/ETH, MR02 escluso perché fee-fragile) dà FULL Sh 8.13→8.33 e
DD 2.47→2.28, ma **OOS Sharpe 10.86→10.76** → fallisce il criterio standard.
Il 15m cattura già quasi tutto l'alpha veloce (corr 10m↔15m 0.53). Chiuso.
## 3. XSEC breadth (universo 8 → 14/15) — direzione GIUSTA, venue SBAGLIATO
La breadth è la leva classica delle strategie cross-sectional. Due banchi:
- **Hyperliquid 15 coin** (dati REALI, ma profondità v2 limitata a ~207 giorni,
regime calmo recente): CORE-8 **6.2%** (Sh 1.38, coerente col
dispersion-gate live che tiene XS01 fuori in questo regime) vs FULL-15
**+9.4%** (Sh 1.58, WR 41→51%). La breadth trasforma un book perdente in
vincente — **sui prezzi veri**.
- **Deribit 14 coin** (storia piena 2022-10→2026-06, +AVAX/DOT/TRX/LINK/BCH/UNI,
parity del sim verificata ESATTA vs `xsec_sim`): FULL Sh 1.48→**1.22**, OOS
4.66→**3.41**, fee 2x da 0.33 a **1.45**. PEGGIO dell'8: le 6 gambe nuove
hanno chiusure flat al **91-99%** e il loro "momentum" è rumore stale che
corrompe il ranking cross-section (3ª conferma della lezione del giorno:
pairs-alt, XEX/DOGE, ora XSEC).
**Conclusione:** l'espansione dell'universo XS01 è promettente ma bloccata dalla
qualità dati del venue. Sblocco strategico: **routing dati Hyperliquid** nel
runner (il client v2 già supporta `exchange="hyperliquid"`) + accumulo storia
HL in avanti. Rivalutare quando HL avrà ≥12-18 mesi di storia utile.
## Direzioni aperte residue (non attaccate oggi)
- **Put settimanale standing** (catastrofe-cap): unica struttura opzioni
eseguibile, da gateare coi premi reali cerbero-bite (~1%/mese il 10% OTM).
Harness `option_overlay_lab.py` pronto.
- **Hyperliquid come venue di esecuzione** (oltre che dati): aprirebbe fades su
alt liquidi con fill realistici; lavoro infrastrutturale grosso.
- 10m fade: chiuso oggi. 1m/2m/5m: chiusi oggi. Pairs nuove/PAXG/XEX: chiusi oggi.
@@ -0,0 +1,108 @@
# 2026-06-12 — Reconcile resting + guard feed↔book + epoca report (v1.1.27)
## Contesto: la notte ha validato (di nuovo) il tema osservabilità
Due eventi nelle 24h hanno motivato la tornata:
1. **MR02_BTC (06:00):** il TP resting LIMIT ha fillato sul **book reale** a 60481
nella notte (+6.55 USD reali) mentre il **feed sim** non è mai sceso sotto 63285
(443 bps di divergenza, sim chiuso a time_limit con 1.25). Il disaster-SL è
risultato `order_not_found` alla cancel (probabile auto-cancel a posizione flat).
Caso SPECULARE del TP_PHANTOM: lì il feed stampa wick che il book non ha; qui il
book si muove e il feed non lo vede. Real-truth ha bookato il reale (corretto),
ma il sistema se n'è accorto solo ORE dopo, al close sim.
2. **Incidente venue (dalle ~09:47):** il conto Deribit testnet ha iniziato a
rifiutare ordini (`locked_by_admin`, `invalid_reduce_only_order`) e le posizioni
sul conto sono cambiate da sole (ETH LONG 0.141 con avg 1849.5 — prezzo vecchio
di settimane): **rollback/reset admin del testnet**, non un nostro bug. Difese OK:
`REAL_CLOSE_PARTIAL`, leg-fail con unwind, orfani registrati (ETH 0.026,
SOL +0.7), `sim_fallback` dichiarato. MA: il reconciler delle 11:40 è **morto in
silenzio** su un 502 — il guardiano che non suona è indistinguibile dal tutto-ok.
## Fatto
### 1. Reconcile degli ordini RESTING (cron :40, read-only)
- **cerbero-mcp 9a74052:** nuovo endpoint Deribit `get_open_orders`
(`private/get_open_orders_by_currency`; per i trigger untriggered interrogare
anche `type='trigger_all'` e merge per order_id). Pattern già presente per
Hyperliquid/IBKR. Rebuild + smoke su testnet (book vuoto == stato atteso).
- **`books.expected_resting()`:** TP/DSL attesi dai libri dei worker single-leg
in posizione reale (i pairs non hanno resting).
- **`reconcile_account.compute_resting_drift()`** — tre classi:
`FILLED_UNBOOKED` (atteso non in book + fill nel trade history col worker ancora
in posizione = il caso MR02), `MISSING` (atteso, non in book, zero fill — per il
DSL triggered il fill ha order_id NUOVO → appare qui e il drift posizioni
completa), `STALE` (in book con label di un nostro worker ma nessun libro:
fillerebbe a sorpresa). Anti-race come per le posizioni; alert `RESTING_DRIFT`.
- **`RECONCILE_FAIL`:** il main è ora guardato — su eccezione alert Telegram +
exit 2 (fix del crash silenzioso delle 11:40).
### 2. Guard FEED_BOOK_GAP nel runner
`_check_feed_book_gap`: ad ogni poll confronta il close della candela in corso col
**mark dello strumento d'ESECUZIONE** (USDC, `get_ticker_batch`, 1 chiamata);
oltre soglia (`overrides.feed_book_gap_bps`, default 150) → alert per episodio,
recovery con isteresi a soglia/2, fail-open su errori di rete. Le decisioni
restano sul feed (il sim è la verità che guida): questo dice solo QUANDO i fill
reali possono divergere dal sim. Avrebbe segnalato il caso MR02 in tempo reale.
### 3. Epoca v1.1.26 nell'hourly_report
I conteggi CHIUSI cumulativi erano inquinati dal churn TP-fantasma dell'11-06
17:3217:58 (~24 giri pre-fix). Nuova riga `epoca v1.1.26+ (TP_PHANTOM attivo)`
da `EPOCH_V1126 = 2026-06-11T21:40` (deploy del gate): oggi legge **7/1** contro
il 18/17 storico — è il numero da confrontare col backtest da qui in poi.
## Test e deploy
`pytest` 121 passed (114 + 7 nuovi: `test_reconcile_resting.py`,
`test_feed_book_gap.py`). Deploy **v1.1.27** (runner nel container; reconciler e
hourly_report girano da host → già attivi). cerbero-mcp rebuildato (endpoint).
## Aperto / lezioni
- **Drift ETH +0.167 non spiegato** dal rollback testnet: quando il conto si
sblocca serve `reset_flatten` + riallineamento ledger (decisione utente).
- I **residui dei close single-leg falliti** (`REAL_CLOSE_PARTIAL`, es. MR07
0.102) NON finiscono in `orphan_legs` → il reconciler li vede come drift NON
spiegato. Estendere la registrazione orfani anche al single-leg è il naturale
passo successivo (punto in analisi sviluppi).
- Il testnet può **riscrivere il conto sotto i piedi** (lock + rollback): nessuna
invariante "il conto cambia solo per nostri ordini" è affidabile. Il reconciler
orario è l'unico rilevatore; valutare cadenza più fitta in presenza di
posizioni reali aperte.
## Addendum (pomeriggio, v1.1.28): orfani single-leg + circuit-breaker venue-lock
I due punti promossi dall'analisi sviluppi, entrambi figli dell'incidente di oggi:
1. **Orfani anche per i close single-leg.** `REAL_CLOSE_PARTIAL` ora registra la
quota residua in `StrategyWorker.orphan_legs` (stessa semantica dei pairs,
persistita in status.json, letta da `books.real_books`) → il reconciler vede
il drift come SPIEGATO invece che anomalo (il caso MR07 0.102 ETH delle 09:47
restava invisibile ai libri). Inoltre `_close_position` ora persiste lo stato
a fine booking (prima solo al save del tick: un crash li' in mezzo perdeva
capital e orfano). NB: nessun save a meta' chiusura — con `real_in_position`
ancora true l'orfano conterebbe doppio nei libri.
2. **Circuit-breaker venue-lock in `ExecutionClient`.** Dopo `lock_trip=3` reject
'locked' consecutivi (es. `locked_by_admin` di oggi) le APERTURE sono sospese
senza toccare l'API (Fill failed → path REAL_OPEN_FAIL/sim_fallback esistente;
per i pairs entrambe le gambe rifiutate localmente → niente leg parziali da
unwindare, niente fee sprecate). Le CHIUSURE si tentano SEMPRE (path gia'
sicuro: partial/orphan/netting) e un loro reject 'locked' rinfresca il
cooldown. Riarmo: dopo `lock_cooldown_s=900` la prossima apertura fa da probe.
Alert `VENUE_LOCK` al trip + RIENTRATO al primo ordine accettato.
Test: `test_single_leg_orphan.py`, `test_venue_lock_breaker.py` (126 passed).
## Chiusura incidente (13:05): testnet sbloccato, conto riallineato
Sequenza eseguita a venue sbloccato: stop runner → `reset_flatten` (chiusi i due
artefatti del rollback: ETH long 0.141 @1664.25, SOL long 0.7 @66.651, conto FLAT)
→ svuotati gli `orphan_legs` di ETH_SOL (le quote orfane non esistono piu' sul
conto post-flatten; edit a container fermo, altrimenti il worker li riscrive
dalla memoria) → restart. **Reconciler: OK — conto allineato ai libri** (posizioni
e resting), runner healthy, log puliti. Il ledger NON e' stato toccato: i booking
dell'incidente erano gia' dichiarati (`sim_fallback`/real) e l'equity (€2020.82)
resta la verita' contabile del paper.
@@ -0,0 +1,64 @@
# 2026-06-12 — XEX: discordanze Deribit testnet vs Hyperliquid
**Domanda.** I prezzi Deribit (testnet, dove eseguiamo) e Hyperliquid (feed
realistico, proxy della realtà) divergono? La divergenza è usabile per fare trade?
**Strumenti comuni validati:** 9 base-coin (ADA, AVAX, BNB, BTC, DOGE, ETH, PAXG,
SOL, TRUMP). Analisi su BTC/ETH (inverse, liquidi) + SOL/DOGE (lineari USDC).
Script: `scripts/analysis/xex_divergence_research.py`.
## Misure (1h, 2026-03-01 → 2026-06-12, ~2470 barre)
| Coin | spread medio | std | half-life | flat Deribit | ΔbookHL live |
|------|--------------|-----|-----------|--------------|----------------|
| BTC | 0.29% | 1.25% | 7.3h | 6% | **0.97%** |
| ETH | 0.55% | 1.33% | 5.1h | 9% | **1.54…−2.16%** |
| SOL | 0.38% | 3.60% | 5.2h | 35% | 0.05% (allineato) |
| DOGE | +0.34% | 1.88% | 4.2h | 87% | +0.16% (allineato) |
Per standard mainnet questi spread sono enormi (reale <0.05%): la divergenza è
l'artefatto del feed testnet che periodicamente si stacca dalla realtà e rientra.
## Findings
1. **Lo spread è mean-reverting e il gap viene chiuso ANCHE da Deribit** (il lato
che possiamo tradare): beta del ritorno futuro Deribit sullo spread negativo e
crescente con l'orizzonte (ETH 0.36, BTC 0.23 a 24h). Non è solo HL che si
muove.
2. **Trappola smascherata — stale print arb.** Il backtest su DOGE dava Sharpe
6.7 (OOS 9.1!) e SOL 2.7, MA il book live di entrambi sta attaccato a HL
(±0.16%) mentre i print restano vecchi (DOGE 87% barre flat): l'edge era
**finzione** — i fill reali avverrebbero al prezzo vero, non al print stantio.
Stessa classe di illusione del look-ahead squeeze: il backtest compra un
prezzo a cui nessuno fa fill.
3. **Su BTC/ETH inverse la dislocazione è REALE: è il book stesso a essere
spostato** (misurato live: bid/ask 0.97% / 1.54…−2.16% sotto HL con depth
>$1M sui primi 5 livelli, spread bid/ask 1-3 bps). Lì si può davvero comprare
sotto/vendere sopra la realtà.
4. **Edge netto moderato e timing-sensitive** (fee 0.10% RT, entry |s|≥1%, exit
|s|≤0.25% o 24 barre): BTC FULL Sh 1.0 / OOS 2.05; ETH FULL 2.28 / OOS 1.37.
Con entry ritardato di 1 barra (stress staleness) BTC FULL→0, ETH OOS→0.1:
con half-life di 3-7 ore, un'ora di ritardo erode quasi tutto. Un'eventuale
implementazione live deve leggere il **book in tempo reale** (non il close 1h)
— a quel punto il lag reale è minuti, non un'ora, e l'edge eseguibile sta fra
lag0 e lag1.
## Segnale live al momento dell'analisi
ETH book Deribit **2.16%** sotto HL (z=1.7, 6.7° percentile storico), BTC
0.97%: entrambi in zona "long Deribit" per il fade dello spread. (Solo
osservazione: nessun deploy senza gate.)
## Verdetto e prossimi passi
- Discordanza tradabile trovata: **fade dello spread D/H su BTC/ETH-PERPETUAL,
segnale dal book, soglia ~1%, exit a convergenza o 24h**. Da NON estendere a
DOGE/SOL (illusione stale-print).
- È un **edge di testnet** (non trasferibile a mainnet): legittimo per il
paper/shadow corrente, ma va dichiarato per quello che è.
- Prima di qualsiasi deploy, metodologia standard: gate PORT06 (correlazione con
le fade esistenti, che già fadano in parte questi stessi spike testnet —
rischio doppio conteggio), robustezza griglia, validazione worker.
Script: `scripts/analysis/xex_divergence_research.py` (fetch v2 due exchange,
tabella convergenza, backtest lag0/lag1, book reality-check live).
+40
View File
@@ -0,0 +1,40 @@
# 2026-06-13 — Dashboard web PORT06 (stato live + PnL + grafici + trade)
Richiesta utente: frontend per visualizzare lo stato con PnL totale e per-strategia,
grafici, e liste trade (attivi in tempo reale + chiusi).
## Cosa
`src/live/dashboard.py` — server **stdlib `http.server`** (zero nuove dipendenze),
legge i file `data/` e serve:
- `GET /api/state` → JSON con tutto lo stato calcolato
- `GET /` → single-page HTML (vanilla JS, polling ogni 5s)
Contenuto della pagina:
- **KPI**: equity, PnL totale (€ e %), max DD, peak
- **Grafico equity** (Chart.js da CDN, fallback testuale se offline) dalla
`equity.jsonl` del ledger (downsample a 400 punti)
- **PnL per strategia** (barre verdi/rosse): realizzato netto fee = Σ `pnl` reali
dai CLOSE (REAL-TRUTH), n trade, win-rate, capitale; tag `paper` per i
multi-asset non eseguiti, `•aperta` se in posizione
- **Trade attivi in tempo reale**: lato, entry, **mark corrente** (Cerbero
best-effort, cache 20s), **PnL non realizzato** (€ e %, da `real_entry_notional`),
barre/max_bars, distanza al TP, età dello status (⚠ se >15min = stantio)
- **Trade chiusi** (ultimi 50): ora, strategia, motivo, PnL reale, sim, esito
## Deploy
Servizio docker-compose `dashboard` (stessa immagine del runner, monta gli stessi
`data/`, porta **8787**), `restart: unless-stopped` + healthcheck sull'API.
Accesso: `http://<host>:8787`. **Nessuna auth** → solo rete interna/VPN, non
esporre pubblicamente. Avvio: `docker compose up -d --build dashboard` (il runner
non viene toccato).
uv run python -m src.live.dashboard --port 8787 # anche standalone su host
## Note
- Il PnL per-strategia usa il PnL REALE (real_truth), coerente col report orario.
- I 6 fade 1h ritirati dallo swap restano in lista (hanno storico CLOSE): flat,
mostrano il loro PnL realizzato storico accanto ai gemelli 15m attivi.
- Unrealized € sui pairs non mostrato (posizione a 2 gambe, z-based) → "pairs (z)".
@@ -0,0 +1,49 @@
# 2026-06-13 — Report ricorrente LEDGER REALE vs BACKTEST (il gate per scalare)
## Perché
Domanda dell'utente: "come cresco il capitale a 12k". Risposta: il prerequisito
prima di mettere soldi veri è che il **ledger reale combaci col backtest**
soprattutto lo slippage del 15m appena deployato. Questo report rende quel gate
un dato osservabile, non un'opinione.
Insight chiave: per gli sleeve eseguiti (6 fade 15m, DIP01, 6 pairs, SH01) il
**sim del worker == backtest canonico PER COSTRUZIONE** (validato). Quindi
"reale vs backtest" = "reale vs sim" = la **fuga di esecuzione**: slippage +
fee reali vs assunte + effetti netting/phantom/sim_fallback.
## Cosa misura
`scripts/analysis/ledger_vs_backtest.py` (read-only: solo trades.jsonl +
status.json, nessuna rete → affidabile in cron):
- PnL realizzato sim vs reale (Σ e per-trade) → **LEAKAGE** € e per-trade (bottom line)
- slippage ingressi (REAL_OPEN) e uscite-a-mercato (REAL_CLOSE; escluse le uscite
da TP resting, fill maker al livello = no slippage)
- fee reali vs assunte (0.10% RT)
- trade `sim_fallback` (reale mai eseguito/fillato) = quota NON coperta dal reale
- ledger per-sleeve: real_capital vs capital (sim)
- **verdetto** 🟢/🟡/🔴: <10 trade = campione piccolo; leakage basso+slippage ≤15bps
= verde (si può pensare a scalare); slippage >40bps = rosso (edge erode, NON scalare)
## Clean-start (importante)
Una finestra mobile pura includerebbe l'**incidente testnet pre-fix**: a 7g il
report dà sim +82 vs reale +5 (🔴) — ma è gonfiato dai +4% FANTASMA che il sim
bookava e il reale no, prima di TP_PHANTOM (v1.1.23), netting (v1.1.25) e
ribilancio-conservativo (v1.1.31). Lo scheduler usa **`--since 2026-06-13`** →
accumula SOLO dati post-fix, e diventa statisticamente significativo coi giorni.
Finestra pulita oggi: 1 trade, leakage +0.07, slippage ingresso 12-29 bps.
## Scheduling
Cron host (come reconcile/hourly_report), **giornaliero 08:30 UTC**:
`ledger_vs_backtest.py --since 2026-06-13 --telegram` → Telegram + log in
`~/port06_ledger_vs_backtest.log`. Invio verificato.
## Come usarlo
Quando il campione supera ~10-20 trade reali e il verdetto è 🟢 stabile per
qualche giorno (leakage per-trade piccolo, slippage medio ≤15 bps), allora il
15m regge l'esecuzione e si può passare da testnet a piccolo reale → poi scalare.
Se resta 🔴/🟡, l'edge si erode sui fill e NON va scalato: prima si capisce dove
perde (slippage ingressi? uscite a mercato? sim_fallback frequenti?).
@@ -0,0 +1,41 @@
# 2026-06-13 — Orfano da swap: incidente + guard nel reconciler
## Incidente
Lo `stato trades` del mattino ha scoperto una **posizione reale orfana**: il
worker fade **MR02_BTC 1h** aveva aperto uno short reale (0.0028 BTC @ 64135.5)
ieri alle 15:01; lo **swap a 15m (v1.1.30, ~20:48) lo ha rimosso dal config
mentre era ancora in posizione** → da allora nessun runner lo gestiva. Stato:
- conto Deribit: short 0.0028 BTC (il long di apertura del gemello 15m aveva
aperto e chiuso nettando via il resto)
- il **TP limit (63387.75) era sparito** dal book (cancellato durante il netting
della chiusura 15m) → short NUDO, protetto solo dal disaster-SL a +30%
- il **reconciler NON allarmava**: lo `status.json` del worker morto dichiarava
ancora `real_in_position: true` → conto == libri. Punto cieco: il reconciler
leggeva i libri dagli status ma non sapeva quali worker fossero VIVI.
Chiusura manuale (testnet): buy 0.0028 reduce-only @63766.5 (~+$0.85 netto sullo
short), cancel disaster-SL, worker marcato flat, PnL bookato (real_capital
181.18→182.03). Conto verificato flat su BTC; SH01_ETH short intatto.
## Guard implementato
`reconcile_account.compute_stale_real_positions(max_age_min=15)`: segnala i
worker che dichiarano `real_in_position` ma il cui `status.json` è fermo da
oltre 15 min. Un worker vivo riscrive lo status a ogni poll (~60s) → la
**staleness** è il discriminante robusto e venue-agnostico (cattura
ritirati-da-swap, crashati, worker rimossi dal config). Alert Telegram
`STALE_REAL_POSITION` (con `--telegram`), incluso nell'exit code e nel verdetto.
Gira già al prossimo cron host (:40) — nessun rebuild (lo script gira dal
working tree). Test: `tests/portfolio/test_reconcile_resting.py` (stantio
flaggato / fresco no / flat-vecchio no).
## Causa radice e direzione
La feature `INIT_LINEAGE` di ieri trasferisce il *capitale* al gemello del nuovo
timeframe, ma non la *posizione*. Il guard di oggi è la **rete di sicurezza**
(rileva e allarma entro un'ora). La **prevenzione** vera — flattare/consegnare la
posizione reale del worker ritirato al boot del runner — resta da implementare
(va fatta lato runner, con cautela: piazza ordini reali all'avvio). Per ora:
swap a conto fade-flat quando possibile, e il reconciler copre il resto.
@@ -0,0 +1,56 @@
# 2026-06-13 — Equity gonfiata dal ribilancio: +4.77 fantasma (causa + fix)
## Sintomo
Report "dall'ultimo reset": equity 2020.53 → 2025.80 (+5.27), ma un solo trade
chiuso (MR02_BTC 15m, reale +0.50). Il +4.77 residuo è comparso di colpo a
00:01:52 (primo poll dopo mezzanotte UTC = ribilancio giornaliero), senza alcuna
chiusura dietro.
## Causa — doppio conteggio del capitale degli in-position al ribilancio
`rebalance_allocations` faceva:
1. `total_capital = Σ capital di tutti i worker`
2. `alloc[sid] = peso × total_capital` per OGNI sid
3. ai worker in posizione NON applicava l'alloc (la posizione tiene il suo notional)
4. ai flat: `capital = alloc[sid]`
`update_equity` (ogni poll) = Σ capital. Dopo il ribilancio la somma diventa:
Σcapital = Σ(peso_i × total) [flat] + Σ(capital_j) [in-pos]
= total Σalloc_j + Σcapital_j
= total + Σ(capital_j alloc_j) ← inflazione
Cioè i flat si dividevano l'INTERO total (che includeva anche il capitale degli
in-position), e gli in-position lo tenevano in più → doppio conteggio.
Al ribilancio delle 00:01, **MR02_BTC 15m era in posizione** col capitale seedato
**181.19** (eredità del gemello 1h, INIT_LINEAGE di ieri) mentre la sua allocazione
era ~176.42 → **+4.77** di equity dal nulla. Lo scatenante è stato il seed (capitale
ben sopra l'allocazione media) combinato con la posizione aperta al ribilancio; ma
il bug era latente da sempre (ogni worker in-pos che devia dall'alloc gonfiava/sgonfiava
l'equity al ribilancio successivo).
## Fix — conservazione (ledger.allocate `reserved`)
`allocate(weights, reserved={sid: capitale})`: i worker in posizione TRATTENGONO il
loro capitale (deployato, non spostabile); i flat si dividono `total Σreserved`
per peso **rinormalizzato sui soli flat**. Così `Σalloc == total_capital` sempre →
**l'equity è conservata dal ribilancio** (un ribilancio sposta capitale fra sleeve,
non crea valore). `runner.rebalance_allocations` calcola `reserved` dai worker
`in_position` e lo passa. Senza `reserved` (default) = comportamento storico
(corretto solo a worker tutti flat, es. allocazione iniziale).
Test: `tests/portfolio/test_ledger.py::test_alloc_conserves_total_with_reserved`.
## Correzione dello stato (rimozione del +4.77 già accumulato)
Equity riportata a **2021.03** = baseline post-seed verificata (2020.53) + unico
trade reale (MR02_BTC 15m +0.50). Capitali dei worker del pool scalati ×
(2021.03/2025.79) e peak del ledger resettato a 2021.03 (il 2025.80 non è mai
stato un massimo legittimo → niente DD fantasma). L'orfano MR02_BTC 1h chiuso a
parte (+0.85) resta sul worker ritirato, fuori dal pool.
## Esito
Equity = soldi veri: dal reset il trading ha prodotto **+0.50** (1 trade), non +5.27.
Deploy con codice fixato (rebuild) così il prossimo ribilancio conserva.
@@ -0,0 +1,149 @@
# 2026-06-14 — Timing sweep: PAIRS & HONEST su 5/10/15/30m (vs live)
## Domanda (goal utente)
Dopo lo swap fade 1h→15m (v1.1.30), i **pairs** e le **honest** beneficiano dello stesso
trattamento — girare su timeframe più veloci (5/10/15/30 min)?
Script: `scripts/analysis/timing_sweep_pairs_honest.py` (riusa i motori canonici
`pairs_sim_flat`, replica DIP intrabar verificata == `dip_market_gated(market_n=0)`, gate
PORT06 == `pairs30m_gate`/`dip01`). Niente re-tuning dei parametri al cambio TF (anti-overfit,
come lo swap fade). Tutto netto, leva 3x, OOS held-out (da 2024-10).
## Vincolo dati (hard — definisce lo scope)
Solo **BTC/ETH** hanno 5m/15m/30m in locale (10m = resample causale da 5m). **TUTTI gli alt**
(ADA/BNB/DOGE/LTC/SOL/XRP) sono **SOLO 1h**. Quindi:
- **PAIRS:** solo **ETH/BTC** è sweepabile sub-orario. Gli altri 4 pair (gambe alt: LTC/ETH,
ADA/ETH, BTC/LTC, ETH/SOL) restano 1h per sempre senza dati alt sub-orari.
- **HONEST:** solo **DIP01** (BTC, mean-reversion) ha senso + dati. **TR01** (trend EMA20/100 su
4h, basket alt) e **ROT02** (rotazione dual-momentum 1d, momentum 60g, universo alt) sono lente
(orizzonte multi-giorno/mese) E multi-asset-su-alt → sub-orario **infattibile** (dati) e
**insensato** (momentum a 60 giorni su barre da 5 min). Nessuno sweep.
## Flat-share (print stale O=H=L=C, = rischio fill) — sale al scendere del TF
| asset | 5m | 10m | 15m | 30m | 1h |
|-------|----|-----|-----|-----|-----|
| BTC | 10.0% | 4.8% | 3.5% | 1.5% | 0.9% |
| ETH | **29.0%** | 17.3% | 16.4% | 9.0% | 6.7% |
ETH 5m al 29% è un allarme operativo serio. Nota: **30m ETH (9%) < 15m (16.4%)** → il 30m ha
*meno* rischio stale-print del 15m già live.
## PAIRS ETH/BTC — il vero free-lunch
Config UNIVERSALE 1h (n=50, z_in=2.0, z_exit=0.75, max_bars=72), flat_skip live-realizable:
| tf | Sharpe FULL | OOS Sh | OOS Sh fee2x | CAGR | win% |
|----|-------------|--------|--------------|------|------|
| 5m | 9.54 | 17.19 | 11.98 | 2071% | 56.0 |
| 10m | 9.26 | 16.54 | **13.03** | 1214% | 64.1 |
| 15m (live) | 8.10 | 14.40 | 11.74 | 673% | 66.7 |
| 30m | 6.20 | 11.17 | 9.59 | 312% | 71.7 |
| 1h (live) | 3.74 | 7.11 | 6.19 | 119% | 72.4 |
(I CAGR a quattro cifre sono compounding leva su singolo sleeve, irrealistici — il portafoglio
normalizza/cappa; il segnale robusto è lo **Sharpe** e il **gate PORT06**.)
- Sharpe **monotono** al scendere del TF, e **regge le fee 2x** (a differenza delle fade, dove
MR02 muore a 5m). Pairs market-neutral: lo spread rientra più spesso ad alta frequenza, e
ETH/BTC è la coppia più liquida.
- Corr daily fra TF: 5m↔1h 0.28, 10m↔1h 0.32, 30m↔1h 0.51; 10m↔15m 0.67 → i TF veloci sono
**fasi diverse dello stesso edge**, diversificanti, non duplicati.
**Gate PORT06 (add half-size al BLEND 1h+15m, OOS da 2024-10):**
| config | FULL Sh | FULL DD% | OOS Sh | OOS DD% | verdetto |
|--------|---------|----------|--------|---------|----------|
| ATTUALE (1h+15m) | 8.13 | 2.47 | 10.86 | 2.09 | — |
| +30m | 8.24 | 2.28 | 10.87 | 1.97 | **MIGLIORA** (già `pairs30m_gate`, mai deployato) |
| +10m | 8.44 | 2.28 | 11.04 | **1.92** | **MIGLIORA** (domina tutto) |
| +5m | 8.52 | 2.30 | 11.00 | 1.95 | **MIGLIORA** (FULL Sh max) |
Tutte e tre dominano su FULL Sh, FULL DD, OOS Sh, OOS DD. **10m** è il candidato più pulito
(miglior OOS DD, flat-share moderato 17%); **30m** è il più sicuro (flat-share 9% < 15m live).
## DIP01 (BTC) — più veloce, ma lo swap NON domina
Engine canonico (n=50, z_in=2.5, sl_atr=2.5, max_bars=24), parità 1h verificata:
| tf | OOS Sh BTC | OOS Sh fee2x BTC | corr↔1h | OOS Sh ETH |
|----|------------|------------------|---------|------------|
| 5m | 4.91 | 2.41 | 0.13 | 8.43 |
| 10m | 3.47 | 1.99 | 0.17 | 6.51 |
| 15m | 2.40 | 1.40 | 0.22 | 4.49 |
| 30m | 1.34 | **0.77** | 0.41 | 3.54 |
| 1h (live) | 1.69 | 1.36 | — | 2.68 |
Standalone Sharpe sale al scendere del TF e i TF veloci sono **fortemente diversificanti**
(corr 0.130.22 con l'1h). MA: **BTC fee-fragile** (30m f2x 0.77), e il **gate-swap non domina**:
| config | FULL Sh | FULL DD% | OOS Sh | OOS DD% |
|--------|---------|----------|--------|---------|
| DIP01 1h (live) | 8.13 | 2.47 | 10.86 | 2.09 |
| DIP01 30m | 8.16 | 2.27 | 10.78 | 2.03 |
| DIP01 15m | 8.16 | 2.30 | 10.82 | 2.14 |
| DIP01 10m | 8.31 | 2.49 | 10.85 | 2.15 |
| DIP01 5m | 8.46 | 2.27 | 11.02 | 2.16 |
Tutti migliorano i FULL ma **alzano l'OOS DD** (e 30m abbassa l'OOS Sharpe) → nessuno domina
sui 4 criteri. DIP01 5m dà il miglior OOS Sharpe (11.02) ma OOS DD 2.16 vs 2.09. La bassa corr
suggerirebbe un **ADD** (DIP01 5m come secondo sleeve diversificante) più che uno swap, ma
aumenta la concentrazione BTC-rev e non è il free-lunch netto che sono i pairs.
## Caveat (perché backtest ≠ via libera a scalare)
1. **Slippage su feed flat-alto** = il vero banco di prova, già flaggato per il 15m blend
("il vero banco di prova è il ledger reale shadow"). A 5m/10m è peggio (ETH 5m 29% flat).
Il backtest usa flat_skip (timing live-realizable) ma NON modella lo slippage attorno ai
print stali. → gateare via shadow ledger PRIMA di sizing, specie il 5m.
2. **OOS = un solo regime** (2024-10→, calmo). Lo Sharpe monotono-col-TF può essere in parte
un artefatto di regime calmo (la MR veloce prospera nel range a bassa vol; lo sweep fade
trovò "i tf veloci pagano nella volatilità, non nella calma"). I pairs sono market-neutral
→ meno esposti dei fade ai crash direzionali, ma il caveat resta.
## Verdetto del backtest (gate full + OOS 2024→)
- **PAIRS ETH/BTC: il gate diceva SÌ.** Estendere il BLEND con TF più veloci migliorava PORT06
su tutte le metriche (10m domina, 30m il più sicuro, 5m FULL Sh max), fee-2x robusto.
- **DIP01: marginale.** Più veloce è diversificante ma lo swap non domina e BTC è fee-fragile.
- **TR01/ROT02: NO.** Dati alt 1h-only + orizzonte lento → fuori scope per costruzione.
## Tentativo di deploy → due muri, NESSUN deploy (2026-06-14)
L'utente ha scelto di deployare un blend pairs ETH/BTC più veloce. Il wiring è stato fatto e
validato (sleeve def, backtest face, worker replay == backtest esatto, regression-lock
aggiornato) PRIMA di scoprire due blocchi che hanno ribaltato la decisione:
1. **MURO FEED (30m e 10m).** Il feed live **Cerbero v2 (`/mcp/tools/get_historical`) serve
solo 5m/15m/1h** — il 30m e il 10m danno 400 in OGNI formato (30m/30/30min/1800), e
l'endpoint legacy è 404 (rimosso). La voce `"30m"` in `runner._SUBHOURLY` era **speculativa,
mai testata live**. → un blend 30m/10m NON è drop-in: richiede di derivarlo resamplando il
feed 15m/5m nel runner (modifica al data-path live condiviso). **Lezione: il _SUBHOURLY del
runner ≠ ciò che il feed serve davvero — smoke-testare il fetch prima di promettere un TF.**
2. **MURO REGIME (5m).** Il 5m è l'unico TF veloce nativo (drop-in), ma il gate full-history
(Sharpe 9.54) è un **relitto dei regimi volatili vecchi**. Nel regime ATTUALE è il PEGGIORE
(ETH/BTC pairs half-size, ret%/Sharpe/DD):
| tf | ultimi 30g | ultimi 90g | ultimi 180g |
|----|-----------|-----------|------------|
| 5m | 10.5% / 11.5 / 11.6% | 2.0% / 0.2 / 14.1% | +78% / 2.93 / **19.5%** |
| 15m | 4.4% / 6.2 / 5.8% | 3.1% / 0.9 / 5.8% | +56% / 3.80 / 7.9% |
| 1h | 1.0% / 1.1 / 3.0% | +3.0% / +1.3 / 3.0% | +25% / **3.95** / 3.0% |
Il 5m perde su ogni finestra recente e ha **DD 3-6× il 1h**; persino sui 180g il suo Sharpe
(2.93) è SOTTO 15m/1h. Sommato al **flat-share ETH 5m 29%** (slippage reale, non modellato
dal backtest) e al fatto che girerebbe **reale**, il guadagno modesto di backtest (FULL
Sharpe +0.39) non vale il rischio. **5m: non conviene.**
**ESITO: nessun deploy. Config live invariata (19 sleeve, pairs 1h+15m).** I file tracciati
ripristinati a HEAD; restano solo questo diario + `timing_sweep_pairs_honest.py` come record.
**Lezione di metodo:** il gate PORT06 full+OOS è necessario ma non sufficiente — va incrociato
con (a) la fattibilità del FEED live e (b) il regime RECENTE. Un edge full-history può essere
un relitto di volatilità passata; "più veloce = più Sharpe storico" si rovescia in "più veloce
= più DD nel regime calmo attuale". Se in futuro si vorrà un blend più veloce: il **30m** resta
il migliore on-paper (flat 9%), ma serve prima derivarlo dal 15m nel runner + ri-verificarlo
nel regime del momento.
@@ -0,0 +1,85 @@
# 2026-06-15 — Gate del feed CONGELATO (freeze gate)
## Problema (da "stato ordini")
Audit degli ordini live: 5 sleeve in posizione reale, **4-5 contaminati dal feed ETH
congelato**. `ETH-PERPETUAL` (inverse, il feed di DECISIONE del runner) è fermo a
**1661.95 da 36h+** (100% barre flat, 57/57 nel windows 2g, **1 solo valore distinto**),
mentre il book reale dove si ESEGUE (`ETH_USDC-PERPETUAL`, lineare) si muove ~1717-1722.
Effetto misurato sui soldi reali (log container):
- **SH01_ETH** ha aperto short a sim 1661.95 / real 1717.10 (slippage 332 bps), chiuso a
time_limit con `sim_exit 1661.95` ma `real_fill 1719.21`**2.83 USD reali vs 0.09 sim**
→ e ha **riaperto subito** lo stesso short sulla trappola.
- I 4 pairs con gamba ETH (ETH/BTC 1h+15m, ETH/SOL, LTC/ETH, ADA/ETH) sono entrati con
z-score **estremi** (3.10, 2.96, +5.58, 2.56): artefatto MECCANICO del log-ratio con
ETH pinnato a 1661.95 mentre gli alt si muovono → falsa divergenza → carico di spread long-ETH.
È un GUASTO testnet, non di strategia: in mainnet l'arbitraggio tiene inverse ≈ lineare ≈ realtà.
## Decisione utente
> "congela gli sleeve ETH-leg finché il feed non si sblocca"
## Soluzione: gate del feed congelato nel runner (auto-guarente)
`src/portfolio/runner.py`: quando il feed di decisione 1h di un asset è **congelato**, gli
sleeve CONCENTRATI (single/ml/pairs) che ne dipendono **saltano il tick** (entry E exit)
finché il feed non si sblocca. Come un outage (i worker non valutano gli exit, protezione =
disaster-SL on-book). Si rilascia DA SOLO alla prima barra completa non-flat.
### Distinguere il GUASTO dall'ILLIQUIDITÀ (il punto critico)
Un detector flat-bar ingenuo sarebbe SBAGLIATO: sul feed reale (2026-06-15) gli alt sono
flat ma VIVI. Firma misurata (run finale di close invariate / valori distinti in 48h):
| Asset | run | distinti/48h | natura |
|-------|-----|--------------|--------|
| ETH | 64 | **1** | MORTO (guasto) |
| BNB | 64 | 2 | MORTO (testnet, solo paper) |
| DOGE | 42 | 4 | MORTO (testnet, solo paper) |
| XRP | 12 | 3 | illiquido |
| ADA | 11 | 5 | illiquido ma VIVO |
| LTC | 10 | 5 | illiquido ma VIVO |
| SOL | 1 | 31 | VIVO |
→ il detector conta la **run di close INVARIATE** (prezzo che non cambia MAI), non le barre
flat. Soglia **24** (`feed_freeze_gate_bars`, configurabile, 0=off): un giorno intero di
prezzo immobile separa nettamente le due popolazioni. ETH/BNB/DOGE (run 40-64) gateati,
ADA/LTC/XRP/SOL (run ≤12) no. **PR_BTCLTC resta attivo** (BTC vivo + LTC solo illiquido NON
deve sospendere il pair: si perderebbero segnali reali sulla gamba BTC).
### NON è l'entry-guard post-flat (BOCCIATA)
Si gatea **DURANTE** il freeze (ultima barra completa ferma). La barra di RIPRESA è non-flat
→ la run si azzera → il tick riprende SU di essa. NON si salta la candela-gap di ripresa
(quella è l'overshoot che la fade fada con profitto — l'entry-guard post-flat è bocciata,
CLAUDE.md / 2026-06-05). Rilascio solo su barra **completata** non-flat (la forming non basta,
coerente con EXIT-16).
### Scope
Solo sleeve `single`/`ml`/`pairs`. I multi-asset (basket/rotation/tsmom/xsec, tutti PAPER)
NON sono gateati: diversificati su 8 asset, un feed fermo non li compromette (e ROT02 ha ETH
nell'universo ma resta attivo). Osservabilità: log `FEED_FROZEN_GATE` + alert Telegram
GATED/RIPRESO, dedup per episodio.
## Esito (smoke feed reale, soglia 24)
- CONGELATI: ETH, BNB, DOGE.
- **GATEATI: le 9 gambe ETH esatte** — MR01/MR02/MR07_ETH, SH_ETH, PR_ETHBTC, PR_ETHBTC_15M,
PR_LTCETH, PR_ADAETH, PR_ETHSOL. BTC-only, PR_BTCLTC e i multi-asset restano attivi.
Le 5 posizioni ETH già aperte NON vengono flattate (freeze = pausa, non liquidazione): un
close forzato passerebbe per lo stesso feed congelato (= il close 2.83 di SH01). Restano
gestite alla ripresa del feed; protezione di coda = disaster-SL on-book a 30%.
Test: `tests/portfolio/test_freeze_gate.py` (detector guasto-vs-illiquido, rilascio su barra
completata, scope sui veri sleeve PORT06). Suite portfolio: 140/140 verdi.
## Lezione
Tre livelli di osservabilità del feed ora distinti: STALE_FEED (≥2 barre flat, alert umano),
FEED_BOOK_GAP (feed≠book d'esecuzione, alert), **FEED_FROZEN_GATE (≥24 barre invariate, AGISCE)**.
La firma che separa guasto da illiquidità è la **run di prezzo invariato**, non la barra flat:
gli alt illiquidi stampano barre flat ma il prezzo si muove ogni ~10 barre.
@@ -0,0 +1,66 @@
# 2026-06-16 — Guard TP-invertito (churn MR02_BTC 15m)
## Sintomo
Burst di **8 trade in 15 minuti** su `MR02_donchian_fade__BTC__15m` (09:0009:15 UTC),
tutti `long`, tutti chiusi `take_profit` a `bars_held=0-1` **in perdita**, accuracy
crollata 60%→27%. TP **fisso** a 66189.25, **sotto** ogni entry (6670066834):
```
09:00 OPEN long 66780.5 tp=66189.25 sl=64218.21 ← tp E sl ENTRAMBI sotto l'entry
09:02 CLOSE take_profit exit=66189.25 held=0 sim=-2.63 real=-0.12
... ×8 ... +7× REAL_TP_FAIL: invalid_reduce_only_order
```
I trade MR02 sani prima del cluster hanno il TP dal lato giusto (long → tp>entry:
65033>63414, 64148>63775, 66079>65717). Il cluster delle 09:00 è l'unico **invertito**.
## Causa radice — segnale da wick transitorio (NON feed congelato, NON spike d'ingresso)
BTC alle 09:00 era **davvero** ~66780 (MR01 short@66919, MR07 short@66780.5 nello stesso
istante, slippage reale 0.72.5 bps → feed sano). Il difetto è a monte:
1. Una barra con **wick verso il basso** (~64218 = il livello SL) fa scattare il donchian
"rottura canale basso" → segnale **LONG**, con `tp`=centro canale (66189) e `sl`=64218
calcolati **dal contesto wickato**.
2. Il wick rientra; il worker entra `long` al prezzo **recuperato** 66780 — cioè **sopra
il proprio TP=66189**.
3. L'exit intrabar (`bar_high >= tp`) è banalmente vera all'istante → chiude "take_profit"
a 66189 = **perdita garantita a held=0**. Si ripete 8× perché il livello donchian
wickato resta nel lookback.
Differenza chiave da TP_PHANTOM: lì il wick è sull'**exit** (tocco fantasma del TP);
qui il wick produce un **tp dal lato sbagliato dell'entry** → la posizione non doveva
proprio aprirsi.
## Perché i guard esistenti non l'hanno preso
- **TP_PHANTOM** richiede un limit reduce-only resting come oracolo. Qui il limit è
**respinto** (`invalid_reduce_only_order`: una sell reduce-only a 66189 sotto il mercato
è invalida) → niente `real_tp_order_id` → fail-open → l'exit passa. E il prezzo corrente
(66780) ha **realmente** superato il livello → non è un tocco fantasma.
- **Freeze-gate (v1.1.32)**: BTC non è congelato (prezzi che si muovono) → non scatta.
## Danno reale: contenuto (il real-truth ledger ha funzionato)
- Σ **reale** del cluster ≈ **−€2.29** (solo slippage+fee: il fill reale è al prezzo vero
~66700-66730, non al TP fantasma).
- Σ **sim** sarebbe stato ≈ **−€17.9** → il real-truth ledger ha evitato che ~**€15.6** di
perdita fantasma colpisse l'equity.
- Costi collaterali: 8 round-trip di fee, 7 TP reduce-only respinti, 8 disaster-SL da
piazzare/cancellare, sim accuracy/report inquinati.
- Auto-esaurito alle 09:15 (sleeve poi flat).
## Fix — guard "TP invertito" (v1.1.33, zero parametri)
`StrategyWorker._open_position`: **prima** di aprire, se il TP è già sfondato all'ingresso
(`long & tp<=entry` oppure `short & tp>=entry`) il segnale è malformato (artefatto wick)
**NON si apre** (niente ordine reale, niente disaster-SL). Verità d'esecuzione, non un
filtro di strategia (stessa filosofia di TP_PHANTOM). Log `INVERTED_TP_SKIP` (dedup
per-barra) + alert Telegram una-tantum per processo. Agisce **solo sul path live**: il
backtest canonico entra al close del bar del segnale (entry == base di tp/sl → mai
invertito) e resta non filtrato → il live farà meglio del backtest, come loss-guard /
trend / TP_PHANTOM.
Test: `tests/portfolio/test_inverted_tp.py` (long/short invertiti skippati, controlli
positivi long/short validi aprono, no-tp apre, dedup per-barra). Suite portfolio 146/146.
## Lezione
Cerotto testnet (il feed wicka): il fix vero è **mainnet**, dove l'arbitraggio elimina i
wick-print. Ma il guard è corretto in assoluto — un segnale con TP dal lato sbagliato
dell'entry non va mai eseguito, su qualsiasi venue.
@@ -0,0 +1,57 @@
# 2026-06-16 — Fase 0 smoke mainnet VERDE (ETH→USDC, limit non market)
## Contesto
Il conto Deribit mainnet, fino al 2026-06-14 **vuoto** (€0, unico blocco al micro-test), ha
ricevuto un deposito iniziale di **~$50**: 25.00 USDC + 0.013878 ETH. Occasione per eseguire la
**Fase 0** del piano (`docs/specs/mainnet-microtest-plan.md`): validare la catena d'esecuzione su
soldi VERI con rischio ~0, abbinandola a un'operazione utile — **consolidare l'ETH stranded in
margine USDC**.
Perché consolidare: noi eseguiamo sui perp **lineari USDC** (`*_USDC-PERPETUAL`), marginati in
**USDC**. I 0.0138 ETH stavano nel wallet ETH (collateral dell'inverse, che NON usiamo per la
matematica lineare del backtest) → margine effettivo solo i $25 USDC. Convertendo, l'intero ~$50
diventa USDC utilizzabile.
## Ordine (LIMIT, non market — istruzione utente)
Check capability read-only prima: spot `ETH_USDC` esiste (bid 1780.3/ask 1780.9, `testnet:false`,
price tick 0.1). Il listing strumenti NON espone lo spot (kind=spot vuoto, endpoint v1 404) ma il
ticker basta. `place_order` supporta `order_type="limit"` + `price`.
- **LIMIT SELL `ETH_USDC` 0.0138 ETH @ floor 1776.3** (~0.1% sotto il bid) — *marketable limit con
cap di slippage*: crossa e filla al bid corrente, ma con un floor sotto cui NON può fillare.
Soddisfa "non a market" (prezzo protetto, niente slippage incontrollato) garantendo il fill.
- Step 0.0001 ok al primo colpo (niente fallback 0.013); fallback previsti 0.013/0.012.
### Esito
```
order_state = filled filled_amount = 0.0138
average_price = 1778.1 (= bid al momento dell'invio; il bid era sceso 1780.3->1778.1 tra
quote e invio, ma > floor 1776.3 -> zero slippage negativo)
fee = 0.0 (maker, costo nullo)
order_id = ETH_USDC-8488088107
```
Riconciliazione conto (before→after):
| Wallet | Prima | Dopo |
|---|---|---|
| USDC | 25.00 | **49.538** (+24.538) |
| ETH | 0.013878 | 0.000078 (dust ~$0.14, sotto il min → non convertibile) |
| BTC | 0 | 0 |
| posizioni / ordini resting | — | nessuna / nessuno |
## Cosa è dimostrato
Catena end-to-end su **mainnet** (`testnet:false`), a costo ~0: routing live + auth + `place_order`
limit + fill + verifica via `get_trade_history` + riconciliazione `get_account_summary`. La
meccanica d'esecuzione reale FUNZIONA su soldi veri. **Fase 0 ✅.**
## Limite residuo (resta il blocco vero)
~$49.54 USDC è **sotto il minimo €500** del piano: a questo margine il rumore di arrotondamento
BTC (step lineare ~$6.6) soffoca il segnale → NON si può ancora testare l'**edge**. Per la **Fase 1**
(fade-only, €1000, 2-4 settimane, verdetto ledger-vs-backtest) serve un **top-up a ~€500-1000 USDC**.
Stato piano aggiornato: token+smoke ✅, funding 🟡 parziale, Fase 1 in attesa di capitale.
## Note operative
- Operazione MANUALE one-off (non il runner): `.env.mainnet` sourcato a mano, client puntato a
mainnet solo per questa sessione. Il runner di produzione resta su **testnet** (`.env` condiviso
invariato) → nessun rischio di flip accidentale.
- La marketable-limit con floor è il pattern giusto per conversioni one-shot a prezzo controllato
(vs il TP resting/limit-al-livello degli sleeve): fill immediato + cap di slippage, fee maker.
@@ -0,0 +1,265 @@
# 2026-06-18 — Price Ladder (griglia) vs gate PORT06
**Branch:** `price_ladder_research` · **Harness:** `scripts/analysis/ladder_search.py`
(engine `grid_mtm`: mark-to-market onesto, SL gap-aware, flat-skip, fee 0.10% RT taker).
**Baseline PORT06:** FULL Sharpe 8.13 / DD 2.47% — OOS Sharpe 10.86 / DD 2.09%.
**Metodo:** caccia multi-agente su asset × TF (15m/30m/1h) × regime-gate (none / range
trend_max 1.5/2.0/2.5), poi `verify` avversariale sui finalisti (plateau, fee 2x, corr,
FULL DD di portafoglio). **Goal di progetto:** trovare un Price Ladder che *diversifichi*
(PASSI il gate) senza accendere la coda di trend.
---
## 1. Cosa era già noto
Il gioco-griglia precedente aveva trovato una **griglia ETH fortissima standalone**
(Sharpe ~5.61) ma **BOCCIATA al gate PORT06**: corr ~0.40 con le fade ETH già deployate
**ridondante**, non un diversificatore. La lezione: lo standalone Sharpe non basta, conta
quanto il ladder *aggiunge ortogonalmente* al portafoglio. Questa ricerca cerca proprio un
ladder che PASSI il gate = che abbia corr bassa con gli sleeve esistenti.
---
## 2. Risultato NUOVO — i ladder PASSANO il gate
Sì. Su **tutti gli asset liquidi** (BTC e ETH) e su quasi tutte le combinazioni TF×regime,
i ladder migliorano l'OOS Sharpe di portafoglio (10.86 → ~11.011.35) **con corr bassa**:
- **BTC** (15m/30m/1h, range o none): corr **0.150.27**, molto più ortogonale della
griglia ETH bocciata (0.40). OOS di portafoglio 11.111.35 (il guadagno OOS più grosso).
- **ETH** (15m/30m/1h): corr **0.210.57** (15m la peggiore, fino a 0.57; a 1h scende a
0.220.39). OOS di portafoglio ~10.911.1.
- **SOL/LTC**: SOL passa solo 2 celle, half-only, edge standalone quasi nullo
(full_sh ~0.2); **LTC è negativo ovunque** (full_sh 0.4…−0.8, bocciato) → solo BTC/ETH
liquidi danno un ladder vivo, come atteso.
Il gate **misura corr + OOS**, e su questi due assi i ladder BTC vincono nettamente.
### Migliori candidati (dai `verify` avversariali)
| Asset/TF/regime/struttura | OOS base→new | FULL DD standalone | FULL DD portafoglio (full / half) | corr | fee2x OOS | robust? |
|---|---|---|---|---|---|---|
| **ETH 1h range2 rd0.16 ru0.04 L4** | 10.86→**11.00** | 11.79% | **2.70** / 2.52 | 0.305 | 4.89 | ✅ sì |
| **ETH 1h range2 rd0.16 ru0.06 L6** | 10.86→11.01 | 12.09% | 2.74 / 2.54 | 0.328 (al filo) | 5.05 | ✅ sì |
| **ETH 1h range2 rd0.16 ru0.06 L4** | 10.86→10.98 | 12.64% | **2.68** / 2.51 | 0.249 | 4.44 | ✅ sì |
| **ETH 30m range2 rd0.16 ru0.04 L3** | 10.86→10.93 | 12.56% | **2.48** / 2.41 | 0.223 | 4.21 | ✅ sì |
| **ETH 1h none rd0.20 ru0.06 L6** | 10.86→11.03 | 17.89% | 2.59 / 2.46 | 0.271 | 5.43 | ✅ sì |
| **ETH 30m none rd0.16 ru0.06 L4** | 10.86→10.92 | 18.68% | **2.37** / 2.35 | 0.319 | 5.42 | ✅ sì |
| **ETH 1h none rd0.16 ru0.06 L4** | 10.86→10.96 | 23.13% | **2.40** / 2.37 | 0.224 | 4.59 | ✅ sì |
| **BTC 1h none rd0.08 ru0.06 L3** | 10.86→**11.34** | **53.69%** | 3.11 / **2.48** | 0.216 | 5.78 | ⚠ half-only |
| **BTC 30m range2.5 rd0.08 ru0.06 L3** | 10.86→11.22 | 55.33% | 3.27 / **2.50** | 0.265 | 5.19 | ⚠ half-only |
| **BTC 30m none rd0.08 ru0.06 L3** | 10.86→11.32 | 55.33% | 3.27 / **2.44** | 0.220 | 5.37 | ⚠ half-only (tail) |
| **BTC 1h range1.5 rd0.20 ru0.06 L6** | 10.86→11.13 | 54.43% | 3.19 / **2.33** | **0.161** | 5.46 | ⚠ half-only (tail) |
`rd` = spacing down, `ru` = spacing up, `L` = livelli. Tutti i finalisti hanno plateau
robusto (47 vicini di struttura restano PROMOSSO) e fee2x ~= OOS standalone.
---
## 3. IL NODO — la FULL DD (che il gate IGNORA)
**Il gate verdict NON guarda la FULL DD.** Misura solo corr + OOS Sharpe/DD. Una griglia
long-only ha una coda di trend 2021/22 (viene comprata fino in fondo mentre il prezzo crolla
in un trend persistente) → la **FULL DD standalone** è il vero rischio nascosto. Il filtro
onesto è la **FULL DD di PORTAFOGLIO** (`full_full_dd` / `half_full_dd`), con soglia di
tail-risk **~+0.8pp sopra il baseline 2.47%** (cioè restare sotto ~3.3%).
Due famiglie nette emergono:
- **BTC** — FULL DD standalone **~5467%** (la coda long-only è devastante, come temuto).
Il regime-gate range *taglia gli ingressi nei trend* ma **non immunizza** dalle posizioni
già aperte → a FULL size la FULL DD di portafoglio sale a **3.16.6%** (+0.6/+4.0pp,
**oltre la soglia tail** in quasi tutte le celle). **A HALF size invece rientra a 2.332.54%**
(~baseline) preservando l'OOS ~11.011.3. → **BTC deployabile SOLO a half-size**, e il
guadagno OOS più grosso (11.3+) viene proprio da queste celle a coda alta.
- **ETH** — FULL DD standalone **molto più bassa** del previsto: **1233%** (1h range scende
a **~12%**), NON il ~60% temuto. Lo SL-buffer + flat-skip + il regime-gate range tengono la
griglia fuori dai trend estesi. Conseguenza: la FULL DD di portafoglio resta **2.372.74%**
(+0.0/+0.27pp, **sotto soglia anche a FULL size**) — in diversi casi *scende* sotto il
baseline (es. ETH 30m none L4 → 2.37%). → **ETH è la famiglia tail-safe.** Il prezzo è una
corr più alta (0.220.33 sui finalisti 1h/30m, fino a 0.57 sul 15m da scartare).
**Lettura chiave:** il regime-gate *abbassa* la FULL DD ma su BTC **non a sufficienza** a
full size (la coda 54% è troppo grande); è l'**half-size** a fare il lavoro. Su ETH la coda
è già piccola e il portafoglio l'assorbe. Quindi il discriminante reale è **asset (ETH vs
BTC) + size**, non solo il regime-gate.
---
## 4. Vantaggio Deribit — fill MAKER
Un Price Ladder è una griglia di **ordini LIMIT ai livelli** → su Deribit i fill sono
**MAKER (~0% fee)**, non taker. L'harness assume **0.10% RT taker** ovunque → è
**CONSERVATIVA**: la stessa griglia live pagherebbe meno fee e potrebbe rendere PIÙ del
backtest. Lo confermano i `verify`: il **fee2x** (raddoppio fee) lascia l'OOS Sharpe quasi
intatto (~98% trattenuto su quasi tutti i finalisti) → nessun fee-cliff, e con i fill maker
reali c'è upside, non downside, sul lato costi. (Il rischio resta la coda di trend e lo
slippage/fill in esecuzione reale, non le fee.)
---
## 5. Verdetto operativo
**C'è un Price Ladder deployabile.** Il candidato più pulito su tutti gli assi
(PROMOSSO + FULL DD di portafoglio controllata a full size + plateau robusto + corr genuina):
> **ETH 1h, regime=range trend_max 2.0, rd=0.16 / ru=0.04 / 4 livelli, sl_buf 0.12 / tp_buf 0.05, max_bars 720.**
> OOS 10.86→11.00, FULL DD portafoglio **2.70% a full / 2.52% a half** (standalone 11.79%),
> corr 0.305, fee2x OOS 4.89, plateau 4 vicini PROMOSSO. **Deploy raccomandato a HALF size**
> (FULL DD +0.05pp, marginale; il caveat slippage live giustifica la prudenza).
**Alternative ETH equivalenti** (stessa banda di sicurezza, scegliere per corr/coda):
`ETH 1h range2 rd0.16 ru0.06 L4` (corr 0.249, la più bassa) e `ETH 30m range2 rd0.16 ru0.04 L3`
(FULL DD portafoglio 2.48, ~baseline). Tutte ETH 1h/30m **range-gated rd≥0.16** → NON scendere
sotto rd 0.16 (rd 0.12 fa risalire la corr verso 0.38 = ricomincia a somigliare alle fade ETH;
il 15m ha corr 0.430.57 = troppo ridondante, scartare).
**I BTC NON a full size.** Danno il guadagno OOS più grande (fino a 11.34) e la corr più bassa
(0.160.22 = la diversificazione migliore), MA la FULL DD standalone ~54% li rende
**tail-risky a full size** (FULL DD portafoglio 3.16.6%, oltre soglia). **Deployabili SOLO a
half-size** (FULL DD portafoglio 2.332.50), dove restano un diversificatore eccellente. Il
migliore BTC half-size è `BTC 1h range1.5 rd0.20 ru0.06 L6` (corr **0.161**, FULL DD portafoglio
half **2.33**).
**BLOCCO prima di qualsiasi deploy:** il gate PORT06 (corr + OOS) è **necessario ma non
sufficiente** — non controlla la FULL DD, e l'OOS held-out è un **regime calmo** (202425). La
coda di trend 2021/22 è reale e standalone è enorme su BTC. **Nessun deploy senza un gate
stressato sul 2022** (verificare la FULL DD di portafoglio attraverso il trend duro, non solo
l'OOS calmo) e una conferma su **ledger reale shadow** per slippage/fill maker. Il verdetto del
gate va sempre incrociato con la FULL DD di portafoglio (filtro vero) + fattibilità feed live +
regime recente — la stessa lezione del timing-sweep pairs/honest (2026-06-14).
**Sintesi:** ETH 1h/30m range-gated rd≥0.16 a half-size è il **deploy-candidate sano e
tail-safe**; BTC è il diversificatore migliore ma **solo a half-size** per la coda di trend.
Prossimo passo obbligatorio: stress-test 2022 + shadow ledger reale (i fill maker possono solo
migliorare il risultato sul lato fee).
---
## 6. ⚠️ RETTIFICA CRITICA — il verdetto §5 è ROVESCIATO (critico + verifica dati)
Il critico di completezza + una verifica diretta dei dati **ribaltano la raccomandazione di
deploy della §5**. Messo a verbale: la §5 è SUPERATA da quanto segue.
**(1) I risultati BTC poggiano su DATI CORROTTI (spike-print), verificato.** La "coda di trend
BTC ~54%" su cui ruota la §3 NON è un crollo di mercato: è **feed sporco**. Misurato su `btc_1h`:
**37 barre con range intrabar >20%**, concentrate a **feb-2024** (es. 2024-02-13: low=38.580 con
close~49.968 = wick fasullo 23% che rimbalza nella stessa barra), con min 22.706 / max 104.688 in
un trimestre dove BTC stava ~4757k. È lo **stesso spike-print testnet documentato ovunque in
CLAUDE.md** (TP_PHANTOM / feed congelato). L'engine SL-gap-aware filla su questi wick fantasma e la
griglia "compra" i dip fasulli → la coda BTC ~54% e parte dell'edge OOS sono **artefatto, non
mercato**. Conseguenza: i candidati BTC NON sono affidabili, e lo "stress 2022" proposto NON
toccherebbe la coda vera (che è a feb-2024, da dati sporchi). Serve `close_only` + pulizia feed.
**(2) Il guadagno OOS è RUMORE al peso deployabile.** +0.14 OOS Sharpe su base 10.86, con il ladder
che pesa **~5.3% full / ~2.7% half** in un portafoglio già a Sharpe ~11, su **592 giorni di un
solo regime calmo** (2024-10→2026-05, niente 2022). Nessun test di significatività
(block-bootstrap). A half-size il contributo è quasi inerte (a volte l'OOS DD *migliora*): è dentro
l'errore di campionamento, non un edge dimostrato.
**(3) La diversificazione ETH è più sottile di quanto venduto.** Il finalista ETH "raccomandato" è
**corr 0.305** (non 0.150.20: quella è BTC half-only non deployabile), già vicino allo **0.40 che
fece bocciare la griglia ETH originale**. Peggio: il `regime_mask` usa **lo stesso filtro
trend-distance** (|closeEMA200|/ATR) delle fade ETH già live → la "diversificazione" è in parte
illusoria (gate e sleeve condividono il filtro). La corr misurata è **daily in regime calmo**, NON
co-tail: una griglia long-only e una fade reversion possono co-crollare in un trend (entrambe
"comprano il calo") — la metrica che conterebbe (co-movimento nella coda) non è calcolata.
**(4) La FULL DD standalone è mascherata dal compounding.** Il finalista ETH compone a **~31x
(+3.1M%) in 5.4 anni**: una "FULL DD 11.79%" accanto a un ritorno di milioni-% è artefatto di
notional reinvestito, e il daily-resample **nasconde l'inventory-risk intrabar** che il docstring
di `grid_game_gate.py` (righe 57) avverte esplicitamente di non vedere.
**(5) Stress disponibile NON usato.** `ladder_search`/`grid_mtm` hanno `close_only=True` (filla solo
sul close, neutralizza gli spike-print): NON è stato girato sui finalisti — proprio il test che
smonterebbe la coda BTC fasulla.
### Verdetto rettificato
**NON c'è un Price Ladder deployabile dimostrato.** La ricerca ha prodotto (a) una **harness onesta
e validata** (`ladder_search.py` + regime-gate, riproduce esatto il gate noto), (b) un **risultato
negativo/guardingo** robusto: ogni candidato o è ridondante (ETH, corr→0.40), o poggia su dati
sporchi (BTC spike-print), o è rumore al peso deployabile. **Il multi-agente + critico ha evitato
un deploy ingiustificato** — esattamente la lezione anti-squeeze del progetto.
**Prima di riconsiderare QUALSIASI ladder servono, in ordine:** (1) **pulire il feed BTC** dagli
spike-print + girare `close_only` sui finalisti; (2) **test di significatività** del delta OOS
(block-bootstrap) al peso reale; (3) **co-tail / corr nei crash** (2022), non corr daily calma;
(4) isolare e spiegare l'**evento feb-2024**. Config live **invariata**.
---
## 7. RE-GATE su DATI PULITI (post clean_feed.py) — quadro onesto finale
Dopo `clean_feed.py` (254 spike-print riparati con Binance) ho ri-gateato i top candidati con
le verifiche che il critico chiedeva (`ladder_regate_clean.py`): gate PORT06 + stress
`close_only` + **DD per anno** sulla storia completa.
| Candidato | DD (gate, 2021+) | OOS Sh | corr | fee2x | close_only OOS | gate½ | **DD 2018 (tail vero)** |
|---|---:|---:|---:|---:|---:|---|---:|
| ETH 1h rd0.16 ru0.04 L4 range2 | 12.4% | 4.70 | 0.29 | 4.62 | 0.24 | PROMOSSO | 52.4% |
| ETH 1h rd0.16 ru0.06 L4 range2 | 12.6% | 4.25 | 0.23 | 4.17 | 0.20 | PROMOSSO | 49.9% |
| BTC 1h rd0.08 ru0.06 L3 none | 10.8% | 5.90 | 0.24 | 5.78 | 0.60 | PROMOSSO | 50.3% |
| **BTC 1h rd0.20 ru0.06 L6 range1.5** | 14.8% | 5.58 | 0.23 | 5.46 | 0.89 | PROMOSSO | **27.7%** |
| BTC 30m rd0.08 ru0.06 L3 none | 14.8% | 5.47 | 0.25 | 5.37 | 1.07 | PROMOSSO | 36.6% |
**Cosa cambia coi dati puliti:**
1.**L'obiezione "coda artefatto" CADE.** Il 54% del BTC era spike-print 2024: ora la DD del
gate (2021+) è **~11-15%** e tutti i candidati restano PROMOSSO a half-size, corr 0.22-0.29,
reggono fee2x. Sono candidati *veri*, non artefatti.
2. 🔴 **Emerge il tail VERO: il 2018** (44/52% standalone), che **il gate NON VEDE** perché
l'IDX del progetto parte dal 2021-01-01. Una griglia long-only sarebbe stata sventrata nel
bear 2018 (BTC 84% sull'anno). È il nuovo rischio portante — e una **lacuna metodologica
generale**: tutte le metriche "FULL" del progetto escludono il 2018. Mitigante reale: il
**regime-gate stretto** (BTC rd0.20 L6 **range1.5**) dimezza il tail 2018 a **27.7%** (vs
50% senza gate) → il gate-di-regime fa il suo lavoro contro i bear sostenuti.
3. 🟡 **L'edge dipende dai fill INTRABAR.** `close_only` (fill solo sul close) fa crollare l'OOS
(4.7→0.24 ETH, 5.9→0.6 BTC). Per ordini LIMIT i fill intrabar sono legittimi (close_only è
troppo severo come stress), ma il gap segnala forte **sensibilità all'ipotesi di fill**
il ledger shadow reale è indispensabile prima di fidarsi.
4. 🟡 Restano: corr 0.22-0.29 (moderata), guadagno OOS ai limiti del rumore al peso deployabile
(~2.7% half), OOS singolo regime calmo.
**Verdetto finale (clean data):** la pulizia ha rimosso l'obiezione FALSA (coda artefatto) e
promosso i candidati a *genuini*, ma ha anche scoperto il tail REALE (2018, gate-cieco) e
confermato due cautele (fill intrabar, OOS rumoroso). **Il candidato più sensato è ora
`BTC 1h rd0.20 ru0.06 L6 regime=range trend_max1.5`**: PROMOSSO, corr 0.23, e la **miglior coda
2018 (27.7%)** grazie al regime-gate. **Prima di QUALSIASI deploy**: (1) backtest che INCLUDA il
2018 nel gate (non solo IDX 2021+), (2) ledger shadow reale per i fill intrabar/maker, (3) half-size.
La pulizia del feed resta il guadagno trasversale più grande (irrobustisce ogni backtest del progetto).
---
## 8. I 3 PASSI PRE-DEPLOY + STUDIO SL/TP (ladder_sltp_study.py)
**[1] Valutazione 2018-INCLUSIVE** (standalone, tutta la storia; il gate IDX2021+ è cieco al 2018):
| Candidato (sl0.12/tp0.05) | full_sh | full_dd | oos_sh | DD 2018 | DD 2021 |
|---|---:|---:|---:|---:|---:|
| BTC 1h L6 **range1.5** | 3.18 | 27.7% | 4.95 | **27.7%** | 14.8% |
| BTC 1h L3 none | 2.93 | 60.2% | 5.51 | **50.3%** | 10.8% |
Il regime-gate già dimezza il tail vero (2018: 50%→−27.7%). Senza gate la griglia long-only è insostenibile (60%).
**Studio SL/TP** (sweep sl_buf × tp_buf sul candidato regime-gated, full 2018+):
- **Sweet spot: `sl_buf=0.10, tp_buf=0.03` → DD 2018 23.5%** (da 27.7%), oos_sh 5.06, full_sh 2.92. Migliora il tail SENZA intaccare l'edge.
- **Lezione (conferma il prior del progetto):** un SL **troppo stretto PEGGIORA** (sl 0.06 → 33/35%): stoppa e rideploya più in basso nel coltello che cade = la trappola del falso-negativo mean-reversion. Un SL **troppo largo** (0.20) idem (29/31%, oos più basso). Il bene sta in mezzo (0.10).
- **Il SL da solo (senza regime-gate) è ERRATICO** (tabella L3 none non-monotona: 20% → 52% → 43% al variare di pochi punti): è il **regime-gate** il controllo primario della coda; il SL moderato fine-tuna. Catastrophe-control = "non deployare nel bear" (regime) + "cappa l'episodio" (SL 0.10), NON uno stop stretto.
**[2] Fill maker vs taker** (Deribit: i livelli sono LIMIT → maker):
| fee | oos_sh | DD 2018 | gate½ |
|---|---:|---:|---|
| taker 0.10% RT (harness) | 5.06 | 23.5% | PROMOSSO (OOS 10.86→11.0, corr 0.195) |
| **maker 0%** (Deribit reale) | 5.18 | 22.7% | PROMOSSO (OOS→11.0, corr 0.194) |
Il maker è leggermente MIGLIORE → la harness taker è conservativa, nessun fee-cliff. (La parte live = shadow ledger reale resta il passo operativo finale.)
**[3] Half-size:** il gate "half" è già a metà size e PROMOSSO; la coda 2018 standalone 23.5% si dimezza sul book (~12% in un 2018-repeat a half-size) = reale ma gestibile.
### CONFIG FINALE raccomandata (con SL/TP aggiunti)
> **BTC 1h · regime=range trend_max 1.5 · rd 0.20 / ru 0.06 / 6 livelli · sl_buf 0.10 / tp_buf 0.03 · HALF-size.**
> - Tail 2018-inclusive: 23.5% standalone (~12% sul book a half) — reale ma controllato dal regime-gate + SL moderato.
> - Gate PORT06: PROMOSSO (OOS 10.86→11.0, corr 0.195); maker Deribit lo migliora; nessun fee-cliff.
> - **Unico passo residuo: shadow ledger reale** (fill intrabar/maker su Deribit) — operativo, non backtestabile.
Caveat onesti residui: l'OOS gain al peso deployabile è modesto (10.86→11.0); il tail 2018 non è zero (12% a half); l'edge dipende dai fill intrabar (per i LIMIT è legittimo, ma lo shadow lo deve confermare).
File diff suppressed because one or more lines are too long
@@ -0,0 +1,174 @@
# Multi-Strategy Paper Trader — Design Spec
## Obiettivo
Eseguire N strategie di trading in parallelo su Deribit testnet (paper trading locale), ognuna con capitale virtuale indipendente di €1000 USDC. Lo storico trade di ogni strategia persiste tra restart. Nuove strategie aggiungibili in corso d'opera via config YAML senza perdere lo storico delle esistenti.
## Architettura
Un singolo container Docker esegue un orchestratore (`MultiStrategyRunner`) che gestisce N `StrategyWorker`. Ogni worker è indipendente: proprio capital, propri trade, proprio stato.
```
Docker Container
├── MultiStrategyRunner (orchestratore, loop principale)
│ ├── StrategyWorker[SQ02_BTC_15m] → paper trade → JSONL
│ ├── StrategyWorker[ML01_ETH_15m] → paper trade → JSONL
│ └── ...altri worker da YAML
├── CerberoClient (condiviso, fetch prezzi)
└── TelegramNotifier (condiviso)
```
## Componenti
### 1. `strategies.yml` — Configurazione
```yaml
defaults:
capital: 1000
position_size: 0.15
leverage: 3
hold_bars: 3
poll_seconds: 60
retrain_hours: 24
strategies:
- name: SQ02_antifake_vol
asset: BTC
tf: 15m
enabled: true
- name: SQ02_antifake_vol
asset: ETH
tf: 15m
enabled: true
- name: ML01_squeeze_gbm
asset: ETH
tf: 15m
enabled: true
position_size: 0.20
params:
ml_threshold: 0.70
bb_window: 14
sq_threshold: 0.8
```
Ogni entry eredita `defaults`. Override per-strategia possibile su tutti i campi. Il campo `params` passa kwargs a `generate_signals()` o al backtest ML.
### 2. `StrategyWorker` — Worker per singola strategia
Responsabilità:
- Importa la classe Strategy corrispondente da `scripts/strategies/`
- Mantiene stato: capital, posizione aperta, equity
- Al startup: ricarica `status.json` se esiste (resume), altrimenti inizia da zero
- Ad ogni tick: riceve DataFrame candele, genera segnali, paper-trade
- Logga ogni evento in `trades.jsonl` (append-only)
- Aggiorna `status.json` ad ogni tick
Stato persistente (`status.json`):
```json
{
"capital": 1023.45,
"in_position": true,
"direction": "long",
"entry_price": 2534.20,
"entry_time": "2026-05-27T14:30:00Z",
"bars_held": 1,
"total_trades": 15,
"total_wins": 12,
"started_at": "2026-05-27T10:00:00Z"
}
```
Trade log (`trades.jsonl`), append-only:
```json
{"ts": "2026-05-27T14:30:00Z", "event": "OPEN", "direction": "long", "price": 2534.20, "size": 0.18, "capital": 1023.45}
{"ts": "2026-05-27T15:15:00Z", "event": "CLOSE", "reason": "hold_limit", "entry": 2534.20, "exit": 2560.10, "pnl": 3.45, "fee": 0.92, "net_pnl": 2.53, "capital": 1025.98}
```
### 3. `MultiStrategyRunner` — Orchestratore
Loop principale:
1. Carica `strategies.yml`
2. Per ogni entry, crea `StrategyWorker` (o riprende se già esiste)
3. Ogni 60s:
a. Fetch candele live da Cerbero (una volta per asset/tf unico)
b. Passa DataFrame a ogni worker
c. Ogni worker valuta segnali e gestisce posizione
d. Worker ML: retrain ogni 24h
4. Notifica Telegram per ogni trade
Ottimizzazione: fetch candele raggruppato per (asset, tf). Se 3 strategie usano BTC 15m, fetch una volta sola.
### 4. Persistenza
```
data/paper_trades/
SQ02_antifake_vol__BTC__15m/
trades.jsonl
status.json
SQ02_antifake_vol__ETH__15m/
trades.jsonl
status.json
ML01_squeeze_gbm__ETH__15m/
trades.jsonl
status.json
```
Directory naming: `{strategy_name}__{asset}__{tf}` con double underscore separatore.
Volume Docker: `./data:/app/data` — persiste tra restart.
### 5. Aggiunta strategia in corso
1. Aggiungi entry in `strategies.yml`
2. `docker compose restart`
3. Runner carica YAML, trova nuova entry senza `status.json` → parte da €1000
4. Strategie esistenti riprendono da `status.json` → storico intatto
### 6. Docker
`Dockerfile` — invariato, aggiunge `strategies.yml` alla COPY.
`docker-compose.yml`:
```yaml
services:
paper-trader:
build: .
container_name: pythagoras-multi
restart: unless-stopped
volumes:
- ./data:/app/data
- ./strategies.yml:/app/strategies.yml:ro
env_file:
- .env
environment:
- PYTHONUNBUFFERED=1
```
`CMD` cambia a: `uv run python -m src.live.multi_runner`
### 7. Strategia-specifica: ML01
ML01 richiede training del modello GBM. Il worker ML01:
- Al primo avvio: train su storico (365 giorni via Cerbero)
- Ogni `retrain_hours`: retrain
- Usa `SignalEngine` esistente per check_signal()
- Le strategie SQ* non hanno training — solo regole deterministiche
### 8. File da creare/modificare
Nuovi:
- `src/live/multi_runner.py` — orchestratore
- `src/live/strategy_worker.py` — worker per singola strategia
- `strategies.yml` — config
- `src/live/strategy_loader.py` — import dinamico classi Strategy
Modifiche:
- `docker-compose.yml` — nuovo CMD, volume strategies.yml
- `Dockerfile` — COPY strategies.yml
Invariati:
- `src/live/cerbero_client.py`
- `src/live/telegram_notifier.py`
- `src/live/signal_engine.py` (usato da ML01 worker)
+181
View File
@@ -0,0 +1,181 @@
# Piano operativo — Micro-test MAINNET (Deribit)
**Obiettivo.** Rispondere all'unica domanda che testnet e backtest non possono:
*quando si passa ai fill VERI, quanto edge sopravvive?* Si misura con poco denaro
reale (€5001500) per 24 settimane, leggendo il report **ledger-vs-backtest**.
Costo del verdetto: poche centinaia di € invece di scoprirlo a €5k.
**Perché serve (e perché testnet non basta).** Testnet ha feed farlocchi — il
2026-06-13 `ETH-PERPETUAL` inverse era **congelato a 1661.95 da 12h+** mentre il
prezzo reale si muoveva (gap 1.3%); BTC era vivo. Su mainnet l'arbitraggio tiene
inverse ≈ lineare ≈ realtà (entro bps): **il micro-test risolve il problema feed
per costruzione**, oltre a dare fill/slippage/fee reali. (Il 2026-06-14 `ETH-PERPETUAL`
testnet era ancora congelato a 1661.95 da 36h+ → conferma definitiva: testnet è inaffidabile.)
---
## STATO (2026-06-16)
-**Token mainnet wired e VERIFICATO.** `MAINNET_TOKEN` (da cerbero-mcp, che ha già le chiavi
Deribit LIVE) copiato in **`.env.mainnet`** (chmod 600, gitignored), SEPARATO dal `.env`
condiviso. Verifica read-only: `is_mainnet()=True`, auth OK su `get_account_summary` (nessun
ordine). Il `.env` condiviso resta testnet → il runner attuale NON flippa a mainnet a un riavvio.
-**FASE 0 SMOKE VERDE (2026-06-16).** Catena d'esecuzione validata su mainnet con soldi VERI a
costo ~0: LIMIT SELL `ETH_USDC` spot 0.0138 ETH @ floor 1776.3 (marketable, NON market) →
`order_state=filled`, average_price **1778.1** (= bid, zero slippage negativo), **fee 0.0**.
Riconciliazione conto: USDC 25.00→**49.54** (+24.54), ETH→0.000078 dust, nessun ordine resting.
Provato end-to-end: routing live + auth + place_order limit + fill + verifica trade + balance.
(Doppio scopo: consolidare l'ETH stranded in margine USDC + smoke.) Diario
`docs/diary/2026-06-16-mainnet-fase0-smoke.md`.
- 🟡 **FINANZIAMENTO PARZIALE: ~$49.54 USDC** (deposito iniziale ~$50: 25 USDC + 0.0138 ETH, ora
consolidati). **Sotto il minimo €500** del piano → basta per la meccanica (Fase 0 ✅) ma NON per
testare l'edge (a $50 di margine il rumore di arrotondamento BTC soffoca il segnale). **Serve
top-up a ~€500-1000 USDC** prima della Fase 1.
- ⏭️ Appena finanziato a ~€1000: avviare il micro-test come **servizio separato** (env_file
`.env.mainnet`, dir dati pulita, `portfolios.yml` fade-only) → Fase 1.
---
## Prerequisiti (umani, fuori dal codice)
1. **Conto Deribit MAINNET** (KYC) finanziato con **~€1000** in USDC (vedi sizing).
2. **MAINNET_TOKEN Cerbero** abilitato al trading mainnet sul conto (lato CerberoSuite).
3. Decidere il capitale: **€1000 raccomandato** (vedi sezione Sizing).
> NB sicurezza: il MAINNET_TOKEN dà accesso a soldi veri. Va in `.env` (gitignored),
> mai committato. Un bot-tag dedicato (`pythagoras-mainnet`) per isolarlo nell'audit.
---
## Come si punta a mainnet (già pronto, 1 sola modifica .env)
Il client legge il token da env (default = testnet). Il token mainnet vive in un file
**`.env.mainnet`** DEDICATO (NON nel `.env` condiviso, che resta testnet → il runner attuale
non flippa a mainnet a un riavvio):
```
# .env.mainnet (gitignored, chmod 600) — caricato SOLO dal servizio micro-test mainnet
CERBERO_TOKEN=<MAINNET_TOKEN>
CERBERO_BOT_TAG=pythagoras-mainnet
```
Il servizio micro-test usa `env_file: [.env, .env.mainnet]` (le chiavi di `.env.mainnet`
prevalgono → Telegram da `.env`, token mainnet da `.env.mainnet`).
**Nessuna modifica di codice.** `CerberoClient.is_mainnet()` lo rileva; il runner,
l'ExecutionClient e gli smoke usano lo stesso client. Per tornare a testnet: togliere
le due righe. (Verificato: default invariato testnet, override flippa il rilevamento.)
> Consiglio: **non riusare il container/data testnet**. Avviare il micro-test su una
> dir dati pulita (`data/` separata o nuovo volume) così i ledger reali mainnet non
> si mescolano allo storico testnet, e il report parte clean-start.
---
## Sizing & taglia — perché €1000 e SOLO le fade in fase 1
Notional = `alloc_sleeve × position_size × leverage`; amount = notional/prezzo,
quantizzato allo step Deribit. Rumore di arrotondamento = step/amount.
**Fades-only (6 sleeve equal, pos 0.5 × leva 3) — rumore arrotondamento:**
| Capitale | notional/fade | round% BTC | round% ETH |
|----------|---------------|-----------|-----------|
| €500 | $125 | 5.2% | 1.3% |
| **€1000** | **$250** | **2.6%** | **0.7%** |
| €1500 | $375 | 1.7% | 0.4% |
**€1000 è il punto giusto**: BTC ~2.6% di rumore (leggibile), abbastanza piccolo
da non far male. €500 funziona ma BTC sale al 5%; €1500 è il più pulito.
**Perché NIENTE pairs/SH01 in fase 1:**
- **Pairs** (pos 0.13, 2 gambe): a €1000 il notional/gamba è ~$21 → arrotondamento
**~30%** (la gamba alt anche peggio): a questa taglia misureresti l'arrotondamento,
non l'edge. Servono ~€58k per pairs puliti. Inoltre l'executor a 2 gambe ha
leg-risk: più superfici d'errore. → fuori dalla fase 1.
- **SH01**: bootstrap full-history dal parquet inverse; su mainnet va ri-derivato il
parquet da prezzi mainnet. Diversificatore, non core. → fuori dalla fase 1.
Le **6 fade single-leg (MR01/02/07 × BTC/ETH)** sono il **core edge**, l'esecuzione
più semplice (open market + TP limit resting + disaster-SL), e quelle col track
record validato col worker reale. Sono il test giusto.
---
## Fasi
### Fase 0 — Smoke mainnet (€0 di rischio reale, 1 ordine minimo)
Con il token mainnet attivo, eseguire `live_exec_smoke.py` (ordine→verifica→fee) e
`live_shadow_smoke.py` su **size minima** per confermare: auth mainnet OK, strumenti
USDC risolti, fill verificato per order_id, fee reali lette. Se uno fallisce, STOP.
### Fase 1 — Solo fade, €1000, 24 settimane
`portfolios.yml` per il micro-test:
```yaml
overrides:
total_capital: 1000
leverage: 3 # config che vorremmo deployare (misura il vero)
position_size: 0.5
paper_sleeves: [TR01, ROT02, TSM01, XS01, PR01..., SH01...] # tutto tranne le 6 fade -> paper
execution:
enabled: true
sleeves: [MR01, MR02, MR07] # solo fade (DIP01 opz.: 1h BTC, core honest)
pairs_enabled: false # niente 2-gambe in fase 1
disaster_sl_pct: 0.30 # rete di coda invariata
real_truth: true
```
> Alternativa prudente settimana-1: `leverage: 2` per dimezzare il DD mentre si
> verifica l'esecuzione, poi 3.
Avviare il runner mainnet (container separato o stessa immagine con `.env` mainnet),
+ reconciler orario + report ledger-vs-backtest giornaliero `--since <data-start>`.
### Fase 2 — Verdetto (dal report ledger-vs-backtest)
Aspettare **≥1020 trade reali** (su mainnet le fade ETH/BTC tradano regolarmente, no
feed congelato). Criterio (lo stesso del report, soglie già nel verdetto 🟢/🟡/🔴):
- 🟢 **slippage medio ≤15 bps E leakage per-trade < €0.30** in relativo → l'edge regge,
si scala.
- 🟡 leakage moderato → estendere il campione, capire dove perde.
- 🔴 slippage >40 bps o leakage che mangia l'edge → **NON scalare**; l'edge non
sopravvive all'esecuzione (hai perso ~€2050, non €5k).
### Fase 3 — Espansione (solo se 🟢)
1. Alzare il capitale (€1000→€2k→€5k) — i pairs diventano puliti sopra ~€5k.
2. Riaccendere pairs (a conto flat) e SH01 (con parquet mainnet).
3. Continuare a leggere il report ad ogni step.
---
## Rischi & safeguard (già implementati)
- **Disaster-SL on-book 30%** ad ogni apertura → protegge da outage del runner.
- **Reconciler orario** (`reconcile_account --telegram`) → drift conto-vs-libri +
guard `STALE_REAL_POSITION` (worker non gestito).
- **Netting close** (v1.1.25) → niente gambe orfane/close cappati.
- **Real-truth ledger** → equity = soldi veri sul conto.
- **Kill-switch**: `execution.enabled: false` (smette di aprire reali, gestisce le
uscite) oppure stop del container + flatten manuale (procedura già rodata).
- Perdita massima plausibile in fase 1: qualche % di €1000 = **€2080**. È il prezzo
del verdetto.
---
## Cosa NON fare
- Non partire con pairs/SH01 (rumore/complessità).
- Non scalare prima del verdetto 🟢 stabile per più giorni.
- Non mescolare i dati testnet e mainnet nello stesso ledger.
- Non committare mai il MAINNET_TOKEN.
---
## Checklist di avvio
- [x] MAINNET_TOKEN Cerbero abilitato + verificato (auth OK, is_mainnet=True) — 2026-06-14
- [x] Token in `.env.mainnet` (separato, gitignored, chmod 600) + `CERBERO_BOT_TAG=pythagoras-mainnet`
- [x] Fase 0 smoke mainnet VERDE (LIMIT SELL ETH_USDC filled, fill@bid, fee 0, balance riconciliato) — 2026-06-16
- [~] Conto Deribit mainnet finanziato: **~$49.54 USDC** (parziale, sotto il min €500) ← **TOP-UP a ~€1000 = BLOCCO Fase 1**
- [ ] Servizio micro-test separato (env_file `.env.mainnet`, dir dati pulita)
- [ ] `portfolios.yml` micro-test (fade-only, €1000, exec sleeves MR01/02/07)
- [ ] runner mainnet + reconciler + report ledger-vs-backtest schedulati
- [ ] dopo ≥1020 trade: leggere il verdetto → scala / aspetta / stop
@@ -0,0 +1,377 @@
# Fase 2-B — Worker live honest/TSM01 (dedicati) — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development o executing-plans. Steps con checkbox `- [ ]`.
**Goal:** Costruire i worker live mancanti perché PORT06 giri live al completo (oltre a fade+pairs+shape già pronti): DIP01, TR01 (basket), ROT02 (rotation), TSM01 (tsmom rotation), e integrarli nel `PortfolioRunner`.
**Architecture:** Worker DEDICATI per ogni strategia (scelta utente). DIP01 è single-asset → Strategy subclass + `StrategyWorker` esistente. TR01/ROT02/TSM01 sono multi-asset/rotation → tre classi worker nuove in `src/live/` con stato per-asset persistente, ciascuna fedele alla rispettiva funzione di backtest in `scripts/analysis/{honest_improve2,tsmom_research}.py`. Integrazione in `src/portfolio/runner.py::build_worker_for` + tick.
**Tech Stack:** Python 3.11, pandas/numpy, pytest. Riusa CerberoClient v2 (multi-asset fetch), PortfolioLedger, e le funzioni di riferimento honest/tsm.
**Branch:** `portfolio_phase2`. **Spec madre:** `docs/superpowers/specs/2026-05-29-portfolios-design.md` (§ scope live, fase 2).
**Riferimenti di logica (NON modificare, sono la verità del backtest):**
- DIP01 → `honest_improve2.dip_market_gated` (z-score dip, gate BTC>SMA, TP=SMA/SL=ATR/max_bars, intrabar).
- TR01 → `honest_improve2._tr_basket_daily` (per asset 4h: EMA20>EMA100 long/flat; basket equal-weight).
- ROT02 → `honest_improve2._rot_daily_equity` (panel 1d, mom 60g, top-3 se mom>0 e BTC>SMA100, gross 0.45 split, ribilancio giornaliero).
- TSM01 → `tsmom_research.tsmom_sim` (panel 1d, Σ sign(P/P[-h]) h∈{63,126,252} ≥ thr=1.0, gate BTC>SMA100, gross 0.30 split).
---
## File structure
| File | Responsabilità |
|------|----------------|
| `scripts/strategies/DIP01_dip_buy.py` | Strategy `Dip01DipBuy` (single-asset; metadata tp/sl/max_bars + gate) |
| `src/live/basket_trend_worker.py` | `BasketTrendWorker` (TR01): N asset 4h, EMA cross, long/flat per asset |
| `src/live/rotation_worker.py` | `RotationWorker` (ROT02): panel 1d, dual-momentum top-k, gross split |
| `src/live/tsmom_worker.py` | `TsmomWorker` (TSM01): panel 1d, consenso segni multi-orizzonte |
| `src/live/strategy_loader.py` | **mod**: aggiungi `DIP01_dip_buy` a MODULE_MAP |
| `src/portfolio/runner.py` | **mod**: `build_worker_for` gestisce kind "basket"/"rotation"/"tsmom"; tick multi-asset |
| `src/portfolio/base.py` (`_defs.py`) | **mod**: SleeveSpec degli honest/tsm con `kind` e `universe` corretti |
| `tests/portfolio/test_honest_workers.py` | unit per ciascun worker + replay==backtest su finestra |
**Universi:** TR01 = [BNB,BTC,DOGE,SOL,XRP] (4h); ROT02/TSM01 = `available_assets()` (1d). I worker multi-asset ricevono il dict {asset: df} dal runner.
---
## Task 1: DIP01 come Strategy single-asset
**Files:** Create `scripts/strategies/DIP01_dip_buy.py`; Modify `src/live/strategy_loader.py`; Test `tests/portfolio/test_dip01.py`.
- [ ] **Step 1: Test (fallisce)**`tests/portfolio/test_dip01.py`:
```python
import pandas as pd
from src.data.downloader import load_data
from scripts.strategies.DIP01_dip_buy import Dip01DipBuy
def test_dip01_generates_long_signals_with_exits():
df = load_data("BTC", "1h").iloc[-5000:].reset_index(drop=True)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
sigs = Dip01DipBuy().generate_signals(df, ts, asset="BTC", tf="1h")
assert len(sigs) > 0
s = sigs[0]
assert s.direction == 1 # dip-buy è solo long
assert {"tp", "sl", "max_bars"} <= set(s.metadata)
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_dip01.py -v` → FAIL (ModuleNotFoundError).
- [ ] **Step 3: Implementa `scripts/strategies/DIP01_dip_buy.py`.** Replica ESATTA della logica di `dip_market_gated` (default `market_n=0` = senza gate, come lo sleeve DIP01_BTC del portafoglio: vedi combine_portfolio che usa `market_n=0`). Genera Signal long quando `z[i] <= -z_in and z[i-1] > -z_in`, con metadata `tp=SMA[i]`, `sl=c[i]-sl_atr*atr[i]`, `max_bars`. fee_rt=0.001, leverage 3, position 0.15.
```python
"""DIP01 — Dip-buy mean-reversion single-asset (z-score sotto-banda). Honest family.
Replica live della logica validata in scripts/analysis/honest_improve2.dip_market_gated
(con market_n=0, come lo sleeve DIP01_BTC del portafoglio): compra quando lo z-score del
prezzo rispetto a SMA(n) incrocia sotto -z_in; esce a TP=SMA, SL=close-sl_atr*ATR, o max_bars.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.strategies.base import Strategy, Signal # noqa: E402
def _atr(df, n=14):
h, l, c = df["high"].values, df["low"].values, df["close"].values
pc = np.roll(c, 1); pc[0] = c[0]
tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
return pd.Series(tr).rolling(n).mean().values
class Dip01DipBuy(Strategy):
name = "DIP01_dip_buy"
description = "Dip-buy mean-reversion single-asset (z-score), exit TP=SMA/SL=ATR/max_bars"
default_assets = ["BTC"]
default_timeframes = ["1h"]
fee_rt = 0.001
leverage = 3.0
position_size = 0.15
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
n: int = 50, z_in: float = 2.5, sl_atr: float = 2.5,
max_bars: int = 24, **params) -> list[Signal]:
c = df["close"].values
ma = pd.Series(c).rolling(n).mean().values
sd = pd.Series(c).rolling(n).std().values
a = _atr(df, 14)
z = (c - ma) / np.where(sd == 0, np.nan, sd)
out: list[Signal] = []
for i in range(n + 14, len(c)):
if np.isnan(z[i]) or np.isnan(a[i]) or np.isnan(ma[i]):
continue
if z[i] <= -z_in and z[i - 1] > -z_in:
out.append(Signal(idx=i, direction=1, entry_price=float(c[i]),
metadata={"tp": float(ma[i]),
"sl": float(c[i] - sl_atr * a[i]),
"max_bars": int(max_bars)}))
return out
```
- [ ] **Step 4: Registra nel loader.** In `src/live/strategy_loader.py` MODULE_MAP aggiungi:
```python
"DIP01_dip_buy": ("DIP01_dip_buy", "Dip01DipBuy"),
```
- [ ] **Step 5:** `uv run pytest tests/portfolio/test_dip01.py -v` → 1 passed.
- [ ] **Step 6: Commit**
```bash
git add scripts/strategies/DIP01_dip_buy.py src/live/strategy_loader.py tests/portfolio/test_dip01.py
git commit -m "feat(live): DIP01 dip-buy come Strategy single-asset (worker via StrategyWorker)"
```
**Nota:** DIP01 nel runner usa lo StrategyWorker esistente (kind="single", name="DIP01"). Aggiorna `_STRAT_MODULE` in `runner.py` con `"DIP01": "DIP01_dip_buy"` e in `_defs.py` lo SleeveSpec DIP01_BTC resta kind="single". Il backtest dello sleeve DIP01_BTC continua a venire da `build_everything` (parità invariata).
---
## Task 2: `BasketTrendWorker` (TR01)
**Files:** Create `src/live/basket_trend_worker.py`; Test `tests/portfolio/test_basket_worker.py`.
- [ ] **Step 1: Test (fallisce)** — verifica che, dato un dict {asset: df 4h}, il worker calcoli posizione long/flat per asset secondo EMA20>EMA100 e aggiorni il capitale equal-weight:
```python
import numpy as np
import pandas as pd
from src.live.basket_trend_worker import BasketTrendWorker
def _ramp_df(n=300, slope=1.0):
c = np.linspace(100, 100 + slope * n, n)
ts = (pd.date_range("2024-01-01", periods=n, freq="4h", tz="UTC").astype("int64") // 10**6)
return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0})
def test_basket_goes_long_in_uptrend(tmp_path):
w = BasketTrendWorker(universe=["AAA", "BBB"], tf="4h", capital=1000.0, data_dir=tmp_path)
data = {"AAA": _ramp_df(slope=1.0), "BBB": _ramp_df(slope=1.0)}
w.tick(data)
assert w.positions["AAA"] == 1.0 and w.positions["BBB"] == 1.0 # EMA20>EMA100 in salita
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_basket_worker.py -v` → FAIL.
- [ ] **Step 3: Implementa `src/live/basket_trend_worker.py`.** Stato: capitale totale + dict `positions` (asset→0/1) + persistenza. `tick(data: dict[str,df])`: per ogni asset calcola EMA20/EMA100 sull'ultima barra; target = 1.0 se ef>es else 0.0; applica fee `FEE_RT/2*LEV` sul turnover |Δpos|; aggiorna capitale equal-weight col rendimento di barra di ogni asset attivo (`POS*LEV*ret*pos/len(universe)`... mantieni la convenzione di `_tr_basket_daily`: ogni asset è uno sleeve normalizzato, equal-weight → applica `mean` dei rendimenti per-asset). Persisti `status.json` (capitale, positions, last_bar_ts per asset) e logga `trades.jsonl`. fee_rt=0.001, leverage 3, position 0.15.
```python
"""BasketTrendWorker (TR01): EMA20>EMA100 long/flat su un paniere, equal-weight.
Replica live di honest_improve2._tr_basket_daily."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import pandas as pd
FEE_RT, LEV, POS = 0.001, 3.0, 0.15
def _ema(x, n):
return pd.Series(x).ewm(span=n, adjust=False).mean().values
class BasketTrendWorker:
def __init__(self, universe, tf="4h", capital=1000.0, position_size=POS,
leverage=LEV, fee_rt=FEE_RT, name="TR01_basket",
data_dir=Path("data/portfolio_paper")):
self.universe = list(universe)
self.tf = tf
self.initial_capital = capital
self.capital = capital
self.position_size = position_size
self.leverage = leverage
self.fee_rt = fee_rt
self.worker_id = f"{name}__{'-'.join(self.universe)}__{tf}"
self.work_dir = Path(data_dir) / self.worker_id
self.work_dir.mkdir(parents=True, exist_ok=True)
self.status_path = self.work_dir / "status.json"
self.trades_path = self.work_dir / "trades.jsonl"
self.positions = {a: 0.0 for a in self.universe}
self.last_bar_ts = {a: 0 for a in self.universe}
self.in_position = False # per il ribilancio del runner (skip se True)
self._load()
def _load(self):
if self.status_path.exists():
s = json.loads(self.status_path.read_text())
self.capital = s.get("capital", self.capital)
self.positions = {**self.positions, **s.get("positions", {})}
self.last_bar_ts = {**self.last_bar_ts, **s.get("last_bar_ts", {})}
self.in_position = any(v > 0 for v in self.positions.values())
def _save(self):
self.status_path.write_text(json.dumps({
"capital": round(self.capital, 2), "positions": self.positions,
"last_bar_ts": self.last_bar_ts,
"ts": datetime.now(timezone.utc).isoformat()}, indent=2))
def tick(self, data: dict):
rets = []
for a in self.universe:
df = data.get(a)
if df is None or len(df) < 110:
continue
c = df["close"].values
ef, es = _ema(c, 20)[-1], _ema(c, 100)[-1]
target = 1.0 if ef > es else 0.0
bar_ts = int(df["timestamp"].iloc[-1])
prev = self.positions[a]
# rendimento di barra realizzato sulla posizione precedente (chiusa->aperta barra)
if self.last_bar_ts[a] and bar_ts > self.last_bar_ts[a] and prev > 0:
r = (c[-1] - c[-2]) / c[-2]
rets.append(self.position_size * self.leverage * r * prev)
if target != prev:
self.capital -= self.capital * self.position_size * (self.fee_rt / 2) * abs(target - prev) / len(self.universe)
self._log(a, prev, target, float(c[-1]))
self.positions[a] = target
self.last_bar_ts[a] = bar_ts
if rets:
self.capital = max(self.capital * (1 + float(np.mean(rets))), 10.0)
self.in_position = any(v > 0 for v in self.positions.values())
self._save()
def _log(self, asset, frm, to, price):
with open(self.trades_path, "a") as f:
f.write(json.dumps({"ts": datetime.now(timezone.utc).isoformat(),
"asset": asset, "from": frm, "to": to,
"price": round(price, 6), "capital": round(self.capital, 2)}) + "\n")
@property
def status_summary(self):
longs = [a for a, v in self.positions.items() if v > 0]
return f"{self.worker_id}: cap={self.capital:.0f} long={longs}"
```
- [ ] **Step 4:** `uv run pytest tests/portfolio/test_basket_worker.py -v` → 1 passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/basket_trend_worker.py tests/portfolio/test_basket_worker.py
git commit -m "feat(live): BasketTrendWorker (TR01) EMA-cross long/flat multi-asset"
```
---
## Task 3: `RotationWorker` (ROT02)
**Files:** Create `src/live/rotation_worker.py`; Test `tests/portfolio/test_rotation_worker.py`.
- [ ] **Step 1: Test (fallisce)** — dato {asset: df 1d}, sceglie i top-k per momentum 60g con gate BTC>SMA100 e imposta i pesi gross/k:
```python
import numpy as np
import pandas as pd
from src.live.rotation_worker import RotationWorker
def _df(n=200, slope=1.0):
c = np.linspace(100, 100 + slope * n, n)
ts = (pd.date_range("2023-01-01", periods=n, freq="1D", tz="UTC").astype("int64") // 10**6)
return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0})
def test_rotation_picks_top_momentum_when_risk_on(tmp_path):
w = RotationWorker(universe=["BTC", "AAA", "BBB"], top_k=2, gross=0.45, data_dir=tmp_path)
data = {"BTC": _df(slope=1.0), "AAA": _df(slope=3.0), "BBB": _df(slope=0.1)}
w.tick(data)
# BTC in uptrend -> risk_on; top-2 momentum = AAA e BTC; pesi gross/2
assert w.weights["AAA"] > 0 and abs(sum(w.weights.values()) - 0.45) < 1e-9
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_rotation_worker.py -v` → FAIL.
- [ ] **Step 3: Implementa `src/live/rotation_worker.py`.** Replica di `_rot_daily_equity`: panel di close 1d allineato; `risk_on = BTC[-1] > SMA100(BTC)[-1]`; `mom = P[-1]/P[-61]-1`; `chosen = [top_k per mom con mom>0] se risk_on else []`; pesi `gross/len(chosen)`; turnover fee `FEE_RT/2 * Σ|Δw|`; capitale aggiornato col rendimento di portafoglio del giorno successivo (live: al tick si realizza il rendimento dell'ultima barra sui pesi correnti, poi si ricalcolano i pesi). Persisti capitale+weights+last_ts. `in_position = bool(weights)`.
(Implementazione analoga a BasketTrendWorker: stato persistente, `tick(data)` allinea i panel per timestamp comune, calcola momentum/gate, applica fee sul turnover e rendimento di barra. Mantieni `top_k=3, gross=0.45` come default — i valori dello sleeve ROT02_rot del portafoglio.)
- [ ] **Step 4:** test → 1 passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/rotation_worker.py tests/portfolio/test_rotation_worker.py
git commit -m "feat(live): RotationWorker (ROT02) dual-momentum top-k risk-gated"
```
---
## Task 4: `TsmomWorker` (TSM01)
**Files:** Create `src/live/tsmom_worker.py`; Test `tests/portfolio/test_tsmom_worker.py`.
- [ ] **Step 1: Test (fallisce)** — consenso segni multi-orizzonte: sceglie gli asset con `Σ sign(P/P[-h]) ≥ thr` (h∈{63,126,252}) sotto gate, pesi gross/k.
- [ ] **Step 2-3: Implementa `src/live/tsmom_worker.py`** replicando `tsmom_sim`: `score[j] = mean_h sign(P[-1,j]/P[-1-h,j]-1)`; `chosen = [j: score>=thr] se risk_on`; pesi `gross/len(chosen)` con `gross=0.30`. Stessa struttura di RotationWorker (panel 1d, fee turnover, rendimento di barra, persistenza). Default `horizons=(63,126,252), thr=1.0, regime_n=100, gross=0.30`.
- [ ] **Step 4:** test → passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/tsmom_worker.py tests/portfolio/test_tsmom_worker.py
git commit -m "feat(live): TsmomWorker (TSM01) consenso TSMOM multi-orizzonte risk-gated"
```
---
## Task 5: Integrazione nel PortfolioRunner
**Files:** Modify `src/portfolio/runner.py`, `scripts/portfolios/_defs.py`, `src/portfolio/base.py`; Test `tests/portfolio/test_runner_honest.py`.
- [ ] **Step 1:** In `_defs.py`, marca gli SleeveSpec multi-asset col `kind` giusto e l'universo:
- DIP01 → `kind="single", name="DIP01"` (resta StrategyWorker via _STRAT_MODULE["DIP01"]="DIP01_dip_buy").
- TR01 → `kind="basket"`, aggiungi campo universo (riusa `params={"universe": ["BNB","BTC","DOGE","SOL","XRP"], "tf": "4h"}`).
- ROT02 → `kind="rotation"`, `params={"top_k":3, "gross":0.45, "tf":"1d"}`.
- TSM01 → `kind="tsmom"`, `params={"horizons":[63,126,252], "thr":1.0, "gross":0.30, "tf":"1d"}`.
(Aggiungi `universe`/campi a SleeveSpec se serve, default None.)
- [ ] **Step 2:** In `runner.py::build_worker_for` aggiungi i rami `kind in ("basket","rotation","tsmom")` che costruiscono i rispettivi worker con `capital=alloc_capital` e `data_dir=DATA_DIR`. Aggiorna `_STRAT_MODULE` con `"DIP01": "DIP01_dip_buy"`. Rimuovi DIP01/TR01/ROT02/TSM01 dalla lista "saltati": ora sono supportati.
- [ ] **Step 3:** In `runner.run()` il tick deve passare ai worker multi-asset un dict {asset: df} (fetch di tutti gli asset dell'universo). Estendi la raccolta `keys` e il dispatch del tick: per kind basket/rotation/tsmom costruisci `data = {a: cache[(a, tf)] for a in universe}` e chiama `w.tick(data)`. Per `_worker_equity` i nuovi worker espongono `.capital` (già ok). Per il ribilancio, espongono `.in_position` (skip se True).
- [ ] **Step 4: Test** `tests/portfolio/test_runner_honest.py`: `build_worker_for` ritorna il tipo giusto per ogni kind con capitale = alloc; e `run()` con PORT06 non lascia più sleeve "saltati" (mocka il fetch o testa solo build).
- [ ] **Step 5:** `uv run pytest tests/portfolio/ -m "not network" -v` → tutti verdi.
- [ ] **Step 6: Commit**
```bash
git add src/portfolio/runner.py scripts/portfolios/_defs.py src/portfolio/base.py tests/portfolio/test_runner_honest.py
git commit -m "feat(portfolio): integra worker honest/TSM01 nel runner (PORT06 live completo)"
```
---
## Task 6: Validazione replay==backtest per i worker multi-asset
**Files:** Modify `scripts/analysis/validate_portfolio_runner.py` (o nuovo `validate_honest_workers.py`).
- [ ] **Step 1:** Per ogni worker multi-asset, replay bar-by-bar su dati storici (load_data) e confronto dell'equity finale con la funzione di riferimento (`_tr_basket_daily`, `_rot_daily_equity`, `tsmom_sim`) entro tolleranza. ROT02/TSM01 sono daily → replay veloce (poche migliaia di barre). TR01 4h → medio. Atteso: match stretto (differenze solo da bar-timing/cadenza). DIP01 ha il gap intrabar noto come le fade (documenta, non assert esatto).
- [ ] **Step 2: Commit**
```bash
git add scripts/analysis/validate_honest_workers.py
git commit -m "test(portfolio): replay worker honest/TSM01 == backtest di riferimento"
```
---
## Self-review
- **Copertura:** i 4 worker (DIP01 single via Strategy; TR01/ROT02/TSM01 dedicati) + integrazione runner + validazione → PORT06 gira live completo (niente più sleeve saltati).
- **Parità backtest:** invariata (gli sleeve del backtest vengono ancora da `build_everything`; i worker sono il path LIVE). La validazione replay==backtest (Task 6) certifica i worker live.
- **Gap noto:** DIP01, come le fade, ha exit intrabar nel backtest ma close-based nel live → gap strutturale documentato (non un bug). TR01/ROT02/TSM01 non hanno TP/SL intrabar (entry/exit a chiusura barra/giorno) → replay atteso stretto.
- **Tipi:** i nuovi worker espongono `.capital` e `.in_position` (richiesti da `_worker_equity`/`rebalance_allocations`); `tick(data: dict)` per i multi-asset vs `tick(df)`/`tick(dfa,dfb)` esistenti → il runner dispatcha per `kind`.
- **Rischio:** la convenzione di capitale/rendimento dei worker multi-asset deve combaciare con le funzioni di riferimento; la validazione Task 6 è il gate che lo verifica — se diverge, allineare la formula (non la reference).
> **Punto aperto:** verificare la disponibilità su Cerbero v2 dei timeframe 4h/1d per tutti gli asset dell'universo (TR01 usa 4h; ROT02/TSM01 usano 1d, oggi resample da 1h in get_df). Il runner live dovrà resamplare 1h→4h/1d dal feed v2 o fetchare nativamente — da decidere in Task 5/Step 3.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
# Design — Cartella `portfolios/`: portafogli come oggetti di prima classe
**Data:** 2026-05-29
**Stato:** approvato in brainstorming, pronto per il piano di implementazione
**Branch:** `shape_patterns` (o branch dedicato `portfolios`)
## 1. Obiettivo e contesto
Oggi le strategie del progetto vivono come *sleeve* indipendenti: ogni worker del paper
trader (`StrategyWorker`, `PairsWorker`) gestisce un conto autonomo da €1000, con capitale
e stato propri in `data/paper_trades/{worker_id}/`. I "portafogli" `PORT01-03` esistenti
sono soltanto script di **report offline**: normalizzano le equity storiche dei singoli
sleeve e ne calcolano metriche equipesate. Non esiste un livello che gestisca davvero un
capitale condiviso, i pesi, il ribilanciamento e il PnL aggregato in tempo reale.
Questo design introduce una cartella `portfolios/` in cui il **portafoglio è un oggetto di
prima classe** che gestirà il trading e lo stato PnL. Un portafoglio possiede un capitale
totale, lo alloca ai propri sleeve secondo uno schema di pesi, dimensiona le posizioni,
ribilancia periodicamente e mantiene il ledger aggregato. La stessa definizione serve sia
al backtest sia al live, garantendo coerenza fra ciò che si misura e ciò che si tradia.
L'obiettivo strategico resta invariato: partire da €1000 e arrivare verso €50/giorno con un
paniere diversificato delle famiglie validate (fade, honest, pairs, TSMOM, shape-ML).
## 2. Decisioni di brainstorming
1. **Modello di capitale: pool condiviso.** Il portafoglio possiede il capitale totale, lo
alloca ai sleeve secondo i pesi, ridimensiona le posizioni e tiene lo stato/PnL
aggregato. I worker diventano esecutori.
2. **Scope: backtest + live unificati.** Un'unica classe `Portfolio` come fonte di verità,
capace sia di backtest/report storico sia di gestione live.
3. **Ribilanciamento periodico.** Il capitale viene riallocato ai pesi target a cadenza
fissa (giornaliera di default, configurabile), coerente con tutte le metriche misurate
finora.
4. **Schemi di peso supportati (tutti):** `equal` (default), `cap` (tetto per
famiglia/cluster, es. pairs 33% — configurazione sobria raccomandata), `inverse_vol`,
`cluster_rp` (equal fra cluster naturali poi inverse-vol dentro), `manual`.
5. **Scope live v1: tutti gli sleeve** — fade, honest, pairs (2 gambe) e shape-ML (SH01 via
worker con retraining periodico, sfruttando il `MLWorkerWrapper` esistente).
6. **Data layer Cerbero v2.** Il runner live adotta gli endpoint unificati v2: `get_historical`
unificato, `get_instruments` (naming robusto, niente `INSTRUMENT_MAP` hardcoded),
`get_ticker_batch` (fetch multi-gamba efficiente). Venue di trading = Deribit come ora.
### Analisi di accorpamento (a supporto delle decisioni)
`scripts/analysis/sleeve_clustering.py` ha mostrato che:
- i **cluster naturali** delle 17 sleeve non coincidono con le famiglie ma con
asset/regime: BTC-reversion, ETH-reversion, trend (TR01+TSM01), shape (SH_BTC+SH_ETH),
rotation (ROT02);
- la **ridondanza è lieve** (correlazione massima 0.43 MR01_BTC↔DIP01_BTC, 0.37 TR01↔TSM01):
nessuno sleeve è davvero fondibile, ognuno aggiunge diversificazione;
- a equal-weight i **pairs pesano il 47% del rischio** → giustifica lo schema `cap`;
- in OOS calmo equal-weight batte inverse-vol e risk-parity (i pairs ad alto rischio/ritorno
corrono liberi), ma è un risultato di regime → il cap resta la scelta prudente.
Il campo `cluster` di `SleeveSpec` codifica questi gruppi naturali per gli schemi `cap` e
`cluster_rp`.
## 3. Architettura e layout
Si rispecchia la struttura delle strategie (`src/strategies/` base + `scripts/strategies/`
concrete):
```
src/portfolio/
__init__.py
base.py # Portfolio (definizione + .backtest()), SleeveSpec, PortfolioResult
sleeves.py # costruzione UNIFICATA delle equity-per-sleeve (backtest);
# centralizza la logica oggi in combine_portfolio + report_families
weighting.py # schemi pesi: equal, cap, inverse_vol, cluster_rp, manual
ledger.py # PortfolioLedger: capitale, allocazioni, equity, PnL, peak/DD, persistenza
runner.py # PortfolioRunner (live): pool capital, sizing, ribilancio, aggregazione
scripts/portfolios/
PORT01_honest.py PORT02_fade.py PORT03_master.py
PORT04_master_pairs.py PORT05_master_esteso.py PORT06_master_shape.py
# definizioni concrete (lista SleeveSpec + schema pesi); run() = report backtest
portfolios.yml # config LIVE: portafoglio attivo, capitale, schema pesi, cap, cadenza, leva
```
**Integrazione col codice esistente:**
- Il backtest riusa i builder di equity-per-sleeve (`build_all_sleeves`, `pairs_sim`,
`shape_daily_equity`), centralizzati in `src/portfolio/sleeves.py`; `combine_portfolio.py`
e `report_families.py` diventano consumer sottili (niente duplicazione).
- Il live riusa da `multi_runner`: il fetch candele, `build_workers`,
`build_pairs_workers`, `MLWorkerWrapper`. `multi_runner` resta entrypoint legacy
single-sleeve finché `PortfolioRunner` non lo sostituisce.
- I vecchi `PORT01-03` di `scripts/strategies/` vengono migrati in `scripts/portfolios/`
come definizioni della nuova classe.
## 4. Definizione del portafoglio (schema)
```python
@dataclass
class SleeveSpec:
kind: str # "single" | "pairs" | "ml"
name: str # "MR01_bollinger_fade" | "PR01_pairs_reversion" | "SH01_shape_ml"
asset: str | None = None # single/ml
a: str | None = None # pairs: gamba long
b: str | None = None # pairs: gamba short
tf: str = "1h"
params: dict = field(default_factory=dict)
cluster: str = "" # BTC-rev | ETH-rev | trend | shape | rotation
@dataclass
class Portfolio:
code: str # "PORT06"
label: str # "Master + shape"
sleeves: list[SleeveSpec]
weighting: str = "equal" # equal | cap | inverse_vol | cluster_rp | manual
weights: dict | None = None # solo manual (sleeve-id -> peso)
caps: dict | None = None # solo cap: chiave = FAMIGLIA (derivata da kind/name:
# PAIRS/FADE/HONEST/SHAPE/TSM), es. {"PAIRS": 0.33}.
# cluster_rp usa invece il campo `cluster` degli sleeve.
total_capital: float = 1000.0
leverage: float = 3.0 # nota: 2x raccomandata per il live reale
rebalance: str = "1D"
vol_lookback: int = 90 # giorni per inverse_vol / cluster_rp
def backtest(self, ...) -> PortfolioResult: ...
def weight_vector(self, sleeve_returns) -> dict[str, float]: ...
```
Gli schemi di peso (in `weighting.py`) restituiscono un dict `sleeve-id -> peso` che somma a
1. `equal/cap/manual` sono statici; `inverse_vol/cluster_rp` si ricalcolano a ogni ribilancio
sulla finestra trailing `vol_lookback`, identicamente in backtest e live.
## 5. Faccia backtest
`Portfolio.backtest()` riusa la macchina che ha prodotto tutte le metriche viste finora,
centralizzata in `src/portfolio/sleeves.py`:
```
build_sleeve_equity(spec) -> pd.Series # equity daily normalizzata su IDX comune
kind="single" -> fade/honest daily equity builders
kind="pairs" -> pairs_sim -> daily
kind="ml" -> shape_daily_equity
```
Poi: `weight_vector()` → pesi → `port_returns()` con ribilancio giornaliero → `metrics()`
FULL/OOS + `yearly_returns()`. Restituisce un `PortfolioResult` con ret/CAGR/DD/Sharpe
(FULL e OOS), tabella per-anno e contributo al rischio per sleeve e per cluster. Lo `run()`
di ogni `scripts/portfolios/PORTxx.py` stampa questo report.
## 6. Faccia live (`PortfolioRunner`)
Loop a poll:
1. **Data layer v2.** All'avvio `get_instruments` risolve i nomi reali di ogni asset/coppia
(fallback a una mappa statica se l'endpoint non risponde). Per tick: `get_historical`
unificato per le candele + `get_ticker_batch` per i prezzi correnti di tutte le gambe in
un'unica chiamata.
2. **Costruzione sleeve→worker.** Riusa `build_workers` / `build_pairs_workers` /
`MLWorkerWrapper` (SH01). I worker sono esecutori, non possiedono più €1000 fissi.
3. **Capitale pool + sizing.** Il `PortfolioLedger` tiene `total_capital`. A ogni worker
viene assegnato `alloc_i = peso_i × total_capital`; il worker dimensiona il notional come
`alloc_i × position_size × leverage` (si riusa il campo `capital` del worker come base di
allocazione).
4. **Ribilancio (cadenza `rebalance`, default giornaliera).** `total_capital = Σ equity_sleeve`
(capitale + PnL realizzato); ricalcolo dei pesi (vol-based sulla finestra trailing o
statici); riallineo `alloc_i`.
5. **Aggregazione.** Dopo ogni tick il ledger aggiorna equity totale, peak, max_dd, PnL
aggregato e per-sleeve/cluster.
### Approssimazione dichiarata (limite noto)
Il ribilancio cambia la base di sizing delle posizioni **future**; le posizioni già aperte
restano sul notional con cui sono nate (nessun travaso forzato a metà trade). Per il paper
trading questo è fedele al backtest daily-rebalanced entro lo scarto dovuto al turnover
infragiornaliero. È un compromesso accettato per non introdurre la contabilità a ledger
unico (approccio C scartato in brainstorming), rimandata a quando si passerà a capitale
reale su un singolo conto-margine.
## 7. Persistenza e stato PnL
Stato del portafoglio separato dai singoli worker, in `data/portfolios/{code}/`:
```
data/portfolios/PORT06/
status.json # resume: total_capital, equity, peak, max_dd, pesi correnti,
# alloc+capitale+PnL per sleeve, ultimo ribilancio, ts
equity.jsonl # append-only: una riga per tick/giorno (ts, equity, dd, pnl_day) -> curva live
events.jsonl # append-only: ribilanci (pesi prima/dopo), milestone, errori
```
- I worker continuano a scrivere il proprio `trades.jsonl`/`status.json` in
`data/paper_trades/{worker_id}/` (storico per-sleeve intatto). Il portafoglio aggrega
sopra, non duplica i trade.
- **Resume:** al restart il runner ricarica lo `status.json` del portafoglio e gli stati
dei worker → riprende capitale, pesi e posizioni senza perdere storico.
- **Indicatori target:** il ledger espone `pnl_total`, `pnl_today`, `€/day` medio e DD
corrente.
- **Notifiche Telegram:** riepilogo a livello portafoglio (equity, PnL giorno, DD, ribilanci)
oltre alle notifiche per-trade dei worker.
## 8. Portafogli forniti e default
| Codice | Label | Sleeve | Pesi |
|--------|-------|--------|------|
| PORT01 | Honest | DIP01·TR01·ROT02 | equal |
| PORT02 | Fade master | MR01/02/07 × BTC/ETH (6) | equal |
| PORT03 | Master | fade+honest (9) | equal / manual 50-50 |
| PORT04 | Master + pairs | 9 + 5 pairs | equal · cap pairs 0.33 |
| PORT05 | Master esteso | 9 + pairs + TSM01 | equal · cap pairs |
| **PORT06** | **Master + shape** *(default)* | 9 + pairs + TSM01 + SH01 (BTC/ETH) | **cap pairs 0.33** |
**Default raccomandato:** PORT06 con `weighting="cap"` (pairs ~33%), `leverage=2` (sobrio),
`rebalance="1D"`. È la combinazione col miglior profilo OOS dell'analisi (Sharpe più alto,
DD più basso) e contiene tutte le famiglie validate. `portfolios.yml` seleziona il
portafoglio attivo e i suoi override.
## 9. Test
- **Unit** — `weighting.py` (somma pesi = 1, cap rispettato e ridistribuito,
inverse-vol/cluster corretti); `ledger.py` (capitale/PnL/DD, resume da status.json).
- **Parità backtest↔report** — `Portfolio.backtest()` di PORT03/04/05/06 riproduce
*esattamente* i numeri di `report_families.py` (regressione, stessa fonte).
- **Parità live↔backtest** — replay del `PortfolioRunner` su dati storici con ribilancio
giornaliero ≈ `Portfolio.backtest()` entro tolleranza (lo scarto è il turnover
infragiornaliero dichiarato), sullo stesso schema della validazione dei pairs.
- **Smoke live** — un tick reale end-to-end via Cerbero v2 (get_instruments +
get_historical + ticker_batch), nessun ordine reale, verifica ledger/persistenza/resume.
## 10. Fuori scope (note per il futuro)
- **Ledger unico / conto-margine reale** (approccio C): rinviato al passaggio a capitale
reale.
- **Hyperliquid come venue per gli alt** dei pairs (perp lineari nativi, evita i trap di
naming Deribit) — opzione abilitata dal data layer v2, non in v1.
- **Validazione pairs live via `get_cointegration_pairs`** e feature da macro/sentiment
(funding, liquidation, OI) per strategie future.
- **`run_backtest` server-side** di Cerbero come check incrociato.
+78
View File
@@ -0,0 +1,78 @@
# Config LIVE del trader a portafoglio. Seleziona UN portafoglio attivo
# (definito in scripts/portfolios/_defs.py) e ne fa l'override dei parametri operativi.
#
# ============ MICRO-TEST MAINNET — soldi VERI (Fase 1, 2026-06-17) ============
# Conversione da testnet a mainnet Deribit (vedi docs/specs/mainnet-microtest-plan.md).
# Capitale REALE €500 (periodo di prova; poi si scala col verdetto ledger-vs-backtest).
# Eseguono reale SOLO le 6 fade (MR01/MR02/MR07 x BTC/ETH, 15m) + DIP01 (BTC 1h);
# pairs/SH01/multi-asset -> PAPER (sola statistica, fuori dal pool/conto). Il token
# mainnet arriva da .env.mainnet (env_file del servizio). Ledger ripartito da ZERO:
# lo storico testnet e' archiviato in data/_reset_backup/pre_mainnet_20260617-205943.tgz.
active: PORT06
overrides:
# CAPITALE REALE del micro-test (era 2000 su testnet). €500 = minimo del piano
# (rumore arrotondamento BTC ~5-6%); si sale dopo il verdetto ledger-vs-backtest.
total_capital: 500
# equal-weight: nel pool REALE restano solo FADE (6) + DIP01 (1) -> 1/7 ciascuno.
# I cap PAIRS/SHAPE non servono piu' (quelle famiglie sono PAPER, fuori dal pool).
weighting: equal
# Leva 3x (scelta utente 2026-06-17 per il micro-test = config che vogliamo deployare,
# frontiera ACCEL50). NB su soldi veri al capitale minimo: DD pieno (alt prudente 2x).
leverage: 3
rebalance: 1D
poll_seconds: 60
# Gate feed CONGELATO: su mainnet l'arbitraggio tiene il feed vivo (niente freeze come
# sul testnet), lo lascio attivo come rete di sicurezza (non scattera' su BTC/ETH
# liquidi). 0 = disattivo.
feed_freeze_gate_bars: 24
# SLEEVE PAPER (fuori dal pool/pesi/ledger): i €500 si dividono SOLO tra i 7 sleeve
# REALI (6 fade + DIP01). Pairs (PR01, 2 gambe) e SH01 fuori dalla Fase 1: a €500 il
# rumore di arrotondamento li soffoca (pairs ~30%/gamba; servono ~€5-8k) e aggiungono
# superfici d'errore (leg-risk). Multi-asset (TR01/ROT02/TSM01/XS01) paper come sempre.
paper_sleeves: [TR01, ROT02, TSM01, XS01, PR01, SH01]
# Frazione di capitale-sleeve per posizione (0.5 con leva 3 = 1.5x la fetta impegnata).
position_size: 0.5
# Override per-famiglia: irrilevante per il conto reale (i pairs sono PAPER), tenuto
# solo perche' i worker pairs in sola-statistica dimensionino come da gate storico.
position_size_family: {PAIRS: 0.13}
# PAPER_EXTRA (2026-06-18): sleeve paper definiti SOLO qui (NON in _defs.py/PORT06) ->
# NON entrano nel backtest canonico/regression-lock. Shadow STAGE 1 del Price Ladder:
# GridWorker SIM-only su feed Deribit BTC 1h (NESSUN ordine reale; kind=grid non e' mai
# eseguito reale per costruzione). Config = re-gate su dati puliti (branch
# price_ladder_research): regime-gate range trend_max 1.5, rd0.20/ru0.06, 6 livelli,
# sl0.10/tp0.03. position_size 0.15 PINNATO (canonico validato; senza, erediterebbe il
# 0.5 globale del micro-test). Gira in data/portfolio_paper_stats/GRID_BTC/.
paper_extra:
- sid: GRID_BTC
kind: grid
name: GRID
asset: BTC
tf: "1h"
cluster: BTC-rev
params: {tf: "1h", range_down: 0.20, range_up: 0.06, levels: 6,
sl_buf: 0.10, tp_buf: 0.03, max_bars: 720,
regime: range, trend_max: 1.5, position_size: 0.15}
# Esecuzione REALE su Deribit MAINNET. Solo i 7 single-leg con TP/SL in metadata:
# 6 fade (MR01/MR02/MR07 x BTC/ETH 15m) + DIP01 (BTC 1h). Ordini sui LINEARI USDC
# (payoff lineare = matematica del backtest; fee/PnL in USDC).
execution:
# DISATTIVATA 2026-06-18: la verifica su dati REALI (Binance spot + perp Deribit
# MAINNET) ha mostrato che le 6 fade NON hanno edge sui prezzi veri (tutte negative
# ogni anno, Sharpe -3..-5) -> il +1000% "validato OOS" era un artefatto dei print
# fantasma del feed testnet Cerbero. Micro-test fermato e conto flattato a mano.
# Tenuta a false come blindatura: un 'docker compose up'/deploy NON deve riprendere
# a tradere reale finche' non ri-validiamo l'intera libreria su dati reali.
enabled: false
# SOLO fade + DIP01 in Fase 1 (SH01 e pairs -> paper, vedi paper_sleeves).
sleeves: [MR01, MR02, MR07, DIP01]
instruments:
BTC: BTC_USDC-PERPETUAL
ETH: ETH_USDC-PERPETUAL
# niente esecuzione a 2 gambe in Fase 1 (pairs sono paper).
pairs_enabled: false
# Disaster-bracket on-book (~-30%) a ogni apertura: assicurazione per gli outage
# del runner. In operativita' normale non scatta mai -> 0 costo. 0 = disattivo.
disaster_sl_pct: 0.30
# REAL-TRUTH: equity/ribilanci/sizing derivano dai FILL REALI (fee reali incluse),
# non dal sim. Il sim resta solo diagnostica nel log CLOSE.
real_truth: true
+142
View File
@@ -0,0 +1,142 @@
"""REGRESSION-LOCK COMUNE dei gate PORT06 live (exit16 / trendmax / dip01).
Queste funzioni erano copiate quasi-verbatim in exit16_port06_impact.py,
trendmax_port06_impact.py e dip01_exit16_impact.py. Sono il regression-lock
delle DECISIONI LIVE (EXIT-16, swap hurst->trend, DIP01 EXIT-16): la copy-drift
fra le copie avrebbe corrotto i verdetti, quindi vivono qui in un'unica copia.
NON cambiare la matematica: i gate devono restare riproducibili byte-a-byte.
Se un nuovo gate richiede un comportamento diverso, PARAMETRIZZARE (come fu
fatto per hurst_mask/trend_max), mai biforcare una copia.
Contenuto:
build_trades_variant : replay ESATTO di risk_management.build_trades sulle
fade (mode="orig" == canonico), con i rami varianti
EXIT-16 (mode="exit16"), filtro trend (trend_max) e
loss-guard Hurst (hurst_mask) parametrici.
equity_from_trades : trade -> equity giornaliera normalizzata su IDX
(stesso flusso di combine_portfolio.fade_daily_equity).
port_metrics : metriche FULL/OOS del portafoglio con la STESSA
matematica pesi di Portfolio.backtest (weight_vector
su tutti gli sleeve, ribilancio come port_returns).
dd : max drawdown % di una equity.
NB: l'engine DIP01 (dip_trades in dip01_exit16_impact.py) NON e' una copia di
build_trades_variant ma un sibling deliberatamente diverso (long-only, mode
"orig_gap" gap-aware, j clampato a n-1 a fine serie, niente filtri trend/hurst)
-> resta nel suo script.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.strategy_research import atr
from scripts.analysis.risk_management import FEE_RT, LEV, POS, INIT
from scripts.analysis.combine_portfolio import (
_norm, IDX, port_returns, metrics, SPLIT,
)
from src.portfolio import weighting as W
BUFFER = 0.5 # EXIT-16 close-confirm (come in produzione)
EMA_LONG = 200
def build_trades_variant(ents, df, mode, trend_max, hurst_mask=None,
buffer=BUFFER, lev=LEV, fee_rt=FEE_RT, ema_long=EMA_LONG):
"""Replica ESATTA di risk_management.build_trades, con i rami varianti.
mode="orig" : SL intrabar al livello (SL prima del TP) == canonico.
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
SL solo se il CLOSE sfonda sl0 -/+ buffer*ATR14[j], fill a close[j].
trend_max : None = filtro OFF; 3.0 = config live.
hurst_mask : bool[i]=True -> salta l'ingresso (loss-guard storico).
"""
h, l, c = df["high"].values, df["low"].values, df["close"].values
n = len(c)
a = atr(df, 14)
el = pd.Series(c).ewm(span=ema_long, adjust=False).mean().values
fee = fee_rt * lev
out = []
last = -1
for e in ents:
i, d = e["i"], e["d"]
if i <= last or i + 1 >= n:
continue
if hurst_mask is not None and hurst_mask[i]:
continue
if trend_max is not None and a[i] and abs(c[i] - el[i]) / a[i] > trend_max:
continue
entry = c[i]
tp, sl0, mb = e["tp"], e["sl"], e["max_bars"]
exit_p = c[min(i + mb, n - 1)]
j = min(i + mb, n - 1)
for k in range(1, mb + 1):
j = i + k
if j >= n:
exit_p = c[n - 1]
break
if mode == "orig":
hs = (d == 1 and l[j] <= sl0) or (d == -1 and h[j] >= sl0)
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
if hs:
exit_p = sl0
break
if ht:
exit_p = tp
break
if k == mb:
exit_p = c[j]
else: # exit16
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
if ht:
exit_p = tp
break
aj = a[j] if np.isfinite(a[j]) else 0.0
confirm = (d == 1 and c[j] < sl0 - buffer * aj) or \
(d == -1 and c[j] > sl0 + buffer * aj)
if confirm:
exit_p = c[j]
break
if k == mb:
exit_p = c[j]
ret = (exit_p - entry) / entry * d * lev - fee
out.append((i, j, ret))
last = j
return out
def equity_from_trades(df, trades):
"""Trade -> equity giornaliera su IDX (flusso di combine_portfolio.fade_daily_equity)."""
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
n = len(df)
eq = np.full(n, INIT, dtype=float)
cap = INIT
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq[j:] = cap
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
return _norm(s)
def port_metrics(members: dict[str, pd.Series], p):
"""Metriche (FULL, OOS) del portafoglio p con la STESSA matematica pesi cap
di Portfolio.backtest."""
ids = p.sleeve_ids
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
def dd(s):
"""Max drawdown % di una serie equity."""
pk = s.cummax()
return float(((pk - s) / pk).max() * 100)
+117
View File
@@ -0,0 +1,117 @@
"""ACCEL50 — Ricerca acceleratori verso l'obiettivo €50/giorno (2026-06-12).
Domanda: quali strategie/leve portano PIU' VELOCEMENTE a €50/g partendo da ~€2k?
Diario: docs/diary/2026-06-12-accel50.md. Esiti:
1. LEVA su PORT06 (acceleratore dominante, zero ricerca nuova).
La frontiera (scala lineare dei daily return canonici, fee pro-quota) mostra
che a Sharpe ~7-10 il vincolo non e' il rischio ma la taglia: lev 2->4 porta
gli anni-a-target da 3.3 a 1.2 con FULL DD 3.5->6.9%. Vedi lev_frontier().
2. FADE 15m (candidata NUOVA, validazione preliminare PASSATA).
MR01/MR02/MR07 a 15m con i parametri live 1h (trend_max=3, ema_long=200,
sl_confirm_atr=0.5, fee 0.10% RT): tutti e 6 gli sleeve positivi, OOS
2025-26 positivo ovunque (spesso > del 1h: 4x trade = compounding piu'
rapido), reggono fee 2x (Sh 1.6-2.9). BTC 15m MIGLIORA il 1h (MR01 Sh
3.37 vs 2.76 con meta' DD). Prossimo passo obbligato: gate PORT06
(correlazione col gemello 1h, parita' worker — infra 15m gia' esistente
dal BLEND pairs). Vedi fade15m_probe().
3. PAIRS NUOVE: BOCCIATE (stale-print illusion).
Lo sweep delle 19 coppie mai testate (config universale pre-registrata)
dava 8 candidate con Sharpe 1.5-4.3, MA le gambe alt hanno 88-98% di barre
flat (LTC 97%, ADA 98%, DOGE 91%, XRP 88%, BNB 88%) e con flat_skip=True
(fill solo su barre pulite) muoiono quasi tutte (BTC/ADA 4.33->0.17,
ETH/DOGE 3.79->0.46). Migliore superstite ETH/XRP a 1.34: inferiore alle
5 deployate -> niente. Stessa classe di illusione del XEX su DOGE/SOL
(vedi xex_divergence_research.py). PAXG idem: 92% flat su Deribit.
4. CAPITALE: a config attuale servono ~€24k per €50/g; ogni € aggiunto
accorcia linearmente (non e' una strategia ma domina ogni altra leva).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
def lev_frontier() -> None:
"""Frontiera di leva su PORT06: CAGR/DD/Sharpe e anni-a-€50/g per lev 1-6.
Modello: scala lineare dei daily return del backtest canonico (strumenti
lineari, fee proporzionali al notional). NON modella margine/code grasse."""
from scripts.portfolios._defs import PORTFOLIOS
from scripts.analysis.combine_portfolio import port_returns, SPLIT
from src.portfolio.sleeves import all_sleeve_equities, sleeve_returns_df
p = PORTFOLIOS["PORT06"]
eq = all_sleeve_equities()
members = {sid: eq[sid] for sid in p.sleeve_ids}
w = p.weight_vector(sleeve_returns_df(p.sleeve_ids))
base = port_returns(members, w) # == live a lev 2 (parita' validata)
def dd(x):
c = (1 + x).cumprod()
return ((c - c.cummax()) / c.cummax()).min() * 100
def cagr(x):
c = (1 + x).cumprod()
return ((c.iloc[-1]) ** (365 / len(x)) - 1) * 100
print("lev CAGR_full% DD_full% CAGR_oos% DD_oos% K_per_50/g anni_da_2k")
for f, lev in [(0.5, 1), (1.0, 2), (1.5, 3), (2.0, 4), (2.5, 5), (3.0, 6)]:
r = base * f
roos = r.iloc[SPLIT:]
co = cagr(roos)
daily = (1 + co / 100) ** (1 / 365) - 1
k = 50 / daily if daily > 0 else float("inf")
anni = np.log(k / 2020) / np.log(1 + co / 100) if co > 0 else float("inf")
print(f"{lev:>3} {cagr(r):>11.0f} {dd(r):>9.2f} {co:>10.0f} {dd(roos):>8.2f} "
f"{k:>11,.0f} {max(anni, 0):>11.1f}")
def fade15m_probe() -> None:
"""MR01/02/07 a 15m vs 1h, parametri live, fee 0.10% e stress 2x."""
import importlib.util
import inspect
from src.strategies.base import Strategy
LIVEP = dict(trend_max=3.0, ema_long=200, sl_confirm_atr=0.5)
paths = {
"MR01": "scripts/strategies/MR01_bollinger_fade.py",
"MR02": "scripts/strategies/MR02_donchian_fade.py",
"MR07": "scripts/strategies/MR07_return_reversal.py",
}
for code, rel in paths.items():
spec = importlib.util.spec_from_file_location(code.lower(), PROJECT_ROOT / rel)
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
cls = next(o for _, o in vars(m).items()
if inspect.isclass(o) and issubclass(o, Strategy) and o.__module__ == m.__name__)
s = cls()
for asset in ("BTC", "ETH"):
line = f"{code} {asset}: "
for tf in ("1h", "15m"):
r = s.backtest(asset, tf, **LIVEP)
if r is None:
line += f"{tf}: no-sig | "
continue
oos = sum(y.pnl for y in r.yearly if y.year >= 2025)
old = s.fee_rt
s.fee_rt = 0.002
r2 = s.backtest(asset, tf, **LIVEP)
s.fee_rt = old
line += (f"{tf}: Sh{r.sharpe:5.2f} DD{r.max_dd:5.1f}% n={r.trades:4d} "
f"oos25-26={oos:+8.0f} fee2x_Sh{r2.sharpe:5.2f} | ")
print(line)
if __name__ == "__main__":
print("=== 1. Frontiera di leva PORT06 ===")
lev_frontier()
print("\n=== 2. Fade 15m probe ===")
fade15m_probe()
+100
View File
@@ -0,0 +1,100 @@
"""CONFERMA su feed PURO Binance spot — la fade ha edge reale o era artefatto-print?
Il clean close-aware ha spliciato barre Binance-spot dentro la serie Deribit-perp:
il crollo del backtest potrebbe (a) rivelare la verita' (l'edge era print) o (b) essere
un artefatto dello splice (basis perp/spot ai punti di giunzione). Test decisivo:
girare lo STESSO engine fade su una serie 100% Binance spot (sorgente coerente, niente
splice). Se anche qui la fade e' negativa -> edge confermato finto.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd, ccxt
from scripts.analysis.risk_management import build_trades, strats_for
EX = ccxt.binance({"enableRateLimit": True})
SYM = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}
START = "2020-06-01" # warmup per EMA200/ATR; il report usa 2021+
YEARS = [2021, 2022, 2023, 2024, 2025, 2026]
def fetch(asset, tf="15m"):
start_ms = int(pd.Timestamp(START, tz="UTC").timestamp() * 1000)
end_ms = int(pd.Timestamp("2026-05-26", tz="UTC").timestamp() * 1000)
tf_ms = 15 * 60 * 1000
rows = []
since = start_ms
while since <= end_ms:
r = EX.fetch_ohlcv(SYM[asset], tf, since=since, limit=1000)
if not r:
break
rows += r
nxt = int(r[-1][0]) + tf_ms
if nxt <= since:
break
since = nxt
df = pd.DataFrame(rows, columns=["timestamp", "open", "high", "low", "close", "volume"])
df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True)
return df[df["timestamp"] <= end_ms]
def yearly(rows_by_year_ret, ts, trades, pos=0.15):
# per-anno compound
yr = {y: 1000.0 for y in YEARS}
# ricostruisco compound per anno separato (reset capitale ogni anno per ret% annuo)
by = {y: [] for y in YEARS}
for i, j, r in trades:
y = ts.iloc[i].year
if y in by:
by[y].append(r)
out = {}
for y in YEARS:
cap = 1000.0
for r in by[y]:
cap = max(cap + cap * pos * r, 10.0)
out[y] = (cap / 1000 - 1) * 100
return out
def full_oos(ts, trades, pos=0.15, split_date="2024-10-12"):
sd = pd.Timestamp(split_date, tz="UTC")
def comp(sub):
cap = 1000.0; rets = []
for i, j, r in sub:
cap = max(cap + cap * pos * r, 10.0); rets.append(r * pos)
return cap, rets
capF, rF = comp(trades)
oos = [(i, j, r) for i, j, r in trades if ts.iloc[i] >= sd]
capO, rO = comp(oos)
shF = float(np.mean(rF) / np.std(rF) * np.sqrt(len(rF))) if len(rF) > 1 and np.std(rF) > 0 else 0.0
shO = float(np.mean(rO) / np.std(rO) * np.sqrt(len(rO))) if len(rO) > 1 and np.std(rO) > 0 else 0.0
return (capF / 1000 - 1) * 100, shF, (capO / 1000 - 1) * 100, shO
def main():
print(f"Fetch Binance 15m (da {START})...\n")
data = {a: fetch(a) for a in ("BTC", "ETH")}
print("=" * 92)
print(" FADE su PURO Binance spot 15m | RET% per anno (pos 0.15, leva 3x, trend 3.0)")
print("=" * 92)
print(f" {'sleeve':<12s}" + "".join(f"{y:>9d}" for y in YEARS) + " | FULL% Shrp | OOS% Shrp")
print(" " + "-" * 88)
for asset in ("BTC", "ETH"):
df = data[asset].copy()
df = df[pd.to_datetime(df["timestamp"], unit="ms", utc=True).dt.year >= 2021].reset_index(drop=True) \
if False else df # tengo il warmup, filtro nei trade
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
for code in ("MR01", "MR02", "MR07"):
fn, params = strats_for(asset)[code]
trades = build_trades(fn(df, **params), df, trend_max=3.0)
trades = [(i, j, r) for i, j, r in trades if ts.iloc[i].year >= 2021]
yr = yearly(None, ts, trades)
fF, shF, fO, shO = full_oos(ts, trades)
print(f" {code+'_'+asset:<12s}" + "".join(f"{yr[y]:>+9.0f}" for y in YEARS) +
f" | {fF:>+8.0f} {shF:>5.2f} | {fO:>+6.0f} {shO:>5.2f}")
if __name__ == "__main__":
main()
@@ -0,0 +1,144 @@
"""Validazione dell'edge del credit-spread di cerbero-bite sui PREZZI REALI.
cerbero-bite (container accanto) vende credit spread su ETH (bull-put primario,
short delta ~0.18, DTE 18, PT 50% / stop 2.5x credito / delta-breach 0.30 / vol-stop
+10 DVOL / time-stop 7 DTE). Domanda: l'edge regge su un CICLO ETH completo, o e'
profittevole solo nei campioni calmi?
Tre analisi (riprendibili):
1) entry_economics() -> economia d'ingresso REALE dalla chain (data/options/eth_chain.parquet):
credit/width effettivo a delta 0.18 dai bid/ask veri, eleggibilita' sotto i gate liquidita'.
2) tail_model_free() -> esito terminale dai prezzi ETH reali (2018-2026), cw reale 0.106,
NESSUN modello opzioni (niente errore BS): win-rate, EV, frequenza max-loss.
3) managed_backtest() -> lifecycle CON management; mark con skew calibrato sulle IV reali.
ESITO (2026-06-09):
- cw reale a delta 0.18 = 0.106 (short ~9.4% OTM, NON 18%), max-loss/credito = 8.4x, eleggibilita' 65%.
- hold-to-expiry @0.106: EV -1.0 crediti/trade, 7/9 anni NEGATIVI, max-loss 17.8% delle volte.
- managed (skew): EV -0.02 cr/trade, win-rate 37% (delta-breach esce sul 62% dei trade a piccola perdita).
- VERDETTO: NON edge robusto su ciclo completo. Il "+0.48%/mese" era artefatto di finestra calma
(mag-giu 2026, no crash). Premium-selling a skew negativo: vince nei campioni calmi, restituisce
tutto (o piu') nei crash. Tune "Profilo B" (vendere a 9.4% OTM) PEGGIORA la frequenza di max-loss.
Coda CONCENTRATA col fade ETH di PythagorasGoal (stesso crash colpisce entrambi).
TODO APERTO (per nail-are l'EV managed esatto): la calibrazione non e' ancora perfetta
(mark mid+skew da cw 0.228 vs 0.106 reale -> sovrastima il credito ~2x). Manca: modellare
bid/ask reale incrociato sulle 2 gambe + griglia strike reale (entrambi nella chain) cosi'
l'entry cw scende a 0.106 e l'EV managed diventa esatto. Allora chiudere il sì/no definitivo.
uv run python scripts/analysis/cerbero_bite_credit_spread.py
"""
from __future__ import annotations
import sys, math, collections
from pathlib import Path
import numpy as np, pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.options_chain import OptionChain, load_market
from scripts.analysis.explore_lab import get_df
from scripts.analysis.option_overlay_lab import bs_put, _ncdf, dvol_for
SHORT_OTM, LONG_OTM, DTE = 0.094, 0.134, 17 # da chain reale (delta 0.18, width 4%)
CW_REAL = 0.106
def entry_economics():
oc = OptionChain("ETH"); ch = oc.df
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
p = ch[ch["option_type"] == "P"].copy()
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
cand = p[(p["tenor_d"] >= 14) & (p["tenor_d"] <= 21)].dropna(subset=["delta", "bid", "ask", "strike", "spot"])
rows = []
for (ts, exp), g in cand.groupby(["timestamp", "expiry"]):
spot = g["spot"].iloc[0]
sc = g[(g["delta"] <= -0.12) & (g["delta"] >= -0.22)]
if sc.empty: continue
short = sc.iloc[(sc["delta"] + 0.18).abs().argmin()]
Ks = short["strike"]; longc = g[g["strike"] < Ks]
if longc.empty: continue
longp = longc.iloc[(longc["strike"] - (Ks - spot * 0.04)).abs().argmin()]
W = Ks - longp["strike"]
if W <= 0: continue
credit = short["bid"] - longp["ask"]
def ok(o):
sp = (o["ask"] - o["bid"]) / ((o["ask"] + o["bid"]) / 2) if (o["ask"] + o["bid"]) > 0 else 9
return (o["open_interest"] or 0) >= 100 and sp <= 0.15 and o["bid"] > 0
cw = credit / (W / spot)
rows.append(dict(cw=cw, credit=credit, elig=ok(short) and ok(longp) and cw >= 0.08 and credit > 0,
short_otm=(spot - Ks) / spot, delta=short["delta"]))
r = pd.DataFrame(rows)
print(f"[ENTRY] {len(r)} spread | eleggibili {r['elig'].mean()*100:.0f}% | cw mediano {r['cw'].median():.3f} "
f"| short OTM {r['short_otm'].median()*100:.1f}% | max-loss/credito {((1-r['cw'].median())/r['cw'].median()):.1f}x")
def tail_model_free():
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); H = DTE * 24
res = []
for i in range(200, n - H - 1, 24 * 2):
S0 = c[i]; Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
Sx = c[i + H]; intr = min(max(Ks - Sx, 0.0), W); credit = CW_REAL * W
res.append((ts.iloc[i].year, 1 - intr / credit, Sx < Kl))
R = pd.DataFrame(res, columns=["y", "pnl", "maxloss"]); P = R["pnl"].values
print(f"[TAIL model-free @cw0.106] win {(P>0).mean()*100:.0f}% | EV {P.mean():+.2f}cr | max-loss {R['maxloss'].mean()*100:.0f}% "
f"| anni neg {(R.groupby('y')['pnl'].mean()<0).sum()}/{R['y'].nunique()}")
def _skew_fit():
oc = OptionChain("ETH"); ch = oc.df
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
p = ch[ch["option_type"] == "P"].copy()
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
p = p.dropna(subset=["iv", "strike", "spot", "delta", "tenor_d"])
p = p[(p["tenor_d"] >= 7) & (p["tenor_d"] <= 35) & (p["iv"] > 0)]
p["dd"] = (p["delta"] + 0.5).abs()
atm = p.sort_values("dd").groupby("timestamp")["iv"].first()
p["atm_iv"] = p["timestamp"].map(atm); p = p.dropna(subset=["atm_iv"])
p["k"] = np.log(p["strike"] / p["spot"]); p["ratio"] = p["iv"] / p["atm_iv"]
p = p[(p["k"] > -0.35) & (p["k"] < 0.15) & (p["ratio"] > 0.5) & (p["ratio"] < 3)]
coef, *_ = np.linalg.lstsq(np.c_[p["k"], p["k"]**2], p["ratio"] - 1.0, rcond=None)
return coef # a, b
def managed_backtest():
a, b = _skew_fit()
def ivol(S, K, atm):
k = math.log(K / S); return max(atm * (1 + a * k + b * k * k), 0.05)
def put_delta(S, K, T, sig):
if T <= 0 or sig <= 0: return -1.0 if S < K else 0.0
return _ncdf((math.log(S / K) + 0.5 * sig * sig * T) / (sig * math.sqrt(T))) - 1.0
def mark(S, Ks, Kl, T, atm):
return bs_put(S, Ks, T, ivol(S, Ks, atm)) - bs_put(S, Kl, T, ivol(S, Kl, atm))
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); dvol = dvol_for(df, "ETH")
H = DTE * 24; STEP = 6; cw = []; tr = []
for i in range(200, n - H - 1, 24 * 2):
S0 = c[i]; atm0 = dvol[i] if not np.isnan(dvol[i]) else 0.6
Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
credit = mark(S0, Ks, Kl, DTE / 365.0, atm0)
if credit <= 0: continue
cw.append(credit / W); pnl = why = None
for k in range(STEP, H + 1, STEP):
j = i + k; Trem = max((H - k) / (24 * 365.0), 1e-6); Sj = c[j]
atmj = dvol[j] if not np.isnan(dvol[j]) else atm0; mk = mark(Sj, Ks, Kl, Trem, atmj)
if mk <= 0.5 * credit: pnl, why = 1 - mk / credit, "PT"; break
if mk >= 2.5 * credit: pnl, why = 1 - mk / credit, "stop"; break
if put_delta(Sj, Ks, Trem, ivol(Sj, Ks, atmj)) <= -0.30: pnl, why = 1 - mk / credit, "delta"; break
if atmj - atm0 >= 0.10: pnl, why = 1 - mk / credit, "vol"; break
if k >= (DTE - 7) * 24: pnl, why = 1 - mk / credit, "time"; break
if pnl is None:
Sx = c[i + H]; intr = min(max(Ks - Sx, 0), W); pnl, why = 1 - intr / credit, "expiry"
tr.append((ts.iloc[i].year, pnl, why))
P = np.array([t[1] for t in tr])
print(f"[MANAGED skew] cw@entry {np.median(cw):.3f} (vs 0.106 reale: sovrastima ~2x, EV vero <=) | "
f"win {(P>0).mean()*100:.0f}% | EV {P.mean():+.3f}cr | worst {P.min():.1f} | "
f"uscite {dict(collections.Counter(t[2] for t in tr))}")
R = pd.DataFrame({"y": [t[0] for t in tr], "p": P})
print(f" 2021+: EV {R[R.y>=2021]['p'].mean():+.3f}cr/trade")
if __name__ == "__main__":
entry_economics()
tail_model_free()
managed_backtest()
+77
View File
@@ -0,0 +1,77 @@
"""PROBE CERBERO MCP — quali exchange/fonti serve davvero? (cerca IBKR & alt)
Non si fida del commento nel codice: interroga il server v2 (/mcp/tools/get_historical
con `exchange=...`) su una matrice di nomi exchange + naming strumento, e riporta chi
risponde con candele vere. Cerca in particolare IBKR e Alpaca (spot US reale).
uv run python scripts/analysis/cerbero_probe.py
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import requests
from src.live.cerbero_client import CerberoClient, is_mainnet
C = CerberoClient()
START, END, INTERVAL = "2026-05-20", "2026-05-27", "1h"
# exchange -> naming strumento BTC da provare (varianti)
EXCHANGES = {
"deribit": ["BTC-PERPETUAL"],
"hyperliquid": ["BTC"],
"bybit": ["BTCUSDT", "BTC-PERPETUAL", "BTCUSD"],
"alpaca": ["BTC/USD", "BTCUSD", "BTC/USDT", "BTC"],
"ibkr": ["BTC", "BTC.USD", "BTCUSD"],
"interactivebrokers": ["BTC", "BTCUSD"],
"binance": ["BTCUSDT", "BTC/USDT", "BTC-PERPETUAL"],
"coinbase": ["BTC-USD", "BTC/USD", "BTCUSD"],
"kraken": ["XBTUSD", "BTC/USD", "BTCUSD"],
"okx": ["BTC-USDT", "BTC-USD-SWAP"],
}
def try_v2(exchange: str, instrument: str) -> str:
try:
candles = C.get_historical_v2(instrument, START, END, INTERVAL, exchange=exchange)
if candles:
c0, c1 = candles[0], candles[-1]
return f"OK {len(candles):>4} candele close {c1.get('close')}"
return "vuoto (0 candele)"
except requests.HTTPError as e:
code = e.response.status_code if e.response is not None else "?"
return f"HTTP {code}"
except Exception as e:
return f"{type(e).__name__}: {str(e)[:50]}"
def list_tools() -> None:
"""Tenta di enumerare i tool/endpoint del server (best-effort)."""
for path in ("/mcp/tools", "/mcp/tools/list", "/tools", "/mcp"):
try:
r = requests.post(f"{C.base_url}{path}", headers=C._headers(), json={}, timeout=10)
print(f" POST {path:<18} -> HTTP {r.status_code} {str(r.text)[:200]}")
except Exception as e:
print(f" POST {path:<18} -> {type(e).__name__}: {str(e)[:60]}")
def main():
print("=" * 80)
print(f" PROBE CERBERO MCP @ {C.base_url} (mainnet={is_mainnet()})")
print("=" * 80)
print("\n[1] Enumerazione endpoint/tool (best-effort):")
list_tools()
print(f"\n[2] get_historical_v2 BTC {INTERVAL} {START}->{END} per exchange:")
print(f" {'exchange':<20s}{'instrument':<16s}esito")
print(" " + "-" * 70)
for ex, syms in EXCHANGES.items():
for sym in syms:
res = try_v2(ex, sym)
print(f" {ex:<20s}{sym:<16s}{res}")
if res.startswith("OK"):
break # trovato il naming giusto, basta
if __name__ == "__main__":
main()
+84
View File
@@ -0,0 +1,84 @@
"""RI-ESECUZIONE FADE sul feed PULITO (data/raw ricostruito da Deribit mainnet, 2026-06-19).
Dopo il rebuild (scripts/analysis/rebuild_history.py) i parquet canonici in data/raw
sono storia Deribit mainnet reale (ccxt pubblico), certificata vs Coinbase USD. Qui giro
le 6 fade (MR01/MR02/MR07 x BTC/ETH) con l'ENGINE CANONICO (risk_management.build_trades,
strats_for) sul feed pulito, su ENTRAMBI i timeframe:
- 1h = config dei claim storici "validati OOS" (CLAUDE.md: MR01 BTC +201% / ETH +1238%)
- 15m = config LIVE attuale (swap 1h->15m, v1.1.30)
Stessi parametri del live: pos 0.15, leva 3x, trend_max 3.0, fee 0.10% RT. OOS = ultimo 30%
per indice (convenzione OOS_FRAC del progetto). Read-only, nessuna scrittura.
uv run python scripts/analysis/clean_fade_rerun.py
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
from src.data.downloader import load_data
from scripts.analysis.risk_management import strats_for, build_trades, POS, LEV, OOS_FRAC
TFS = ["1h", "15m"]
YEARS = list(range(2018, 2027))
def metrics(ts, trades, split_idx, pos=POS):
"""trades = [(i, j, r_netto)]. Ritorna (per-anno%, FULL%, FULL Sharpe, OOS%, OOS Sharpe)."""
by = {y: 0.0 for y in YEARS}
capF = capO = 1000.0
rF, rO = [], []
for i, j, r in trades:
y = ts.iloc[i].year
if y in by:
by[y] += r * pos * 1000.0 # contributo lineare per la riga annuale
capF = max(capF + capF * pos * r, 10.0)
rF.append(r * pos)
if i >= split_idx:
capO = max(capO + capO * pos * r, 10.0)
rO.append(r * pos)
yr = {y: by[y] / 1000.0 * 100 for y in YEARS}
shF = float(np.mean(rF) / np.std(rF) * np.sqrt(len(rF))) if len(rF) > 1 and np.std(rF) > 0 else 0.0
shO = float(np.mean(rO) / np.std(rO) * np.sqrt(len(rO))) if len(rO) > 1 and np.std(rO) > 0 else 0.0
return yr, (capF / 1000 - 1) * 100, shF, (capO / 1000 - 1) * 100, shO
def main():
years_present = set()
results = {}
for tf in TFS:
for asset in ("BTC", "ETH"):
df = load_data(asset, tf)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
years_present |= set(ts.dt.year.unique().tolist())
split_idx = int(len(df) * (1 - OOS_FRAC))
cov = f"{ts.iloc[0].date()} -> {ts.iloc[-1].date()} ({len(df)} barre, OOS da {ts.iloc[split_idx].date()})"
for code in ("MR01", "MR02", "MR07"):
fn, params = strats_for(asset)[code]
trades = build_trades(fn(df, **params), df, trend_max=3.0)
results[(tf, asset, code)] = (metrics(ts, trades, split_idx), len(trades), cov)
years = [y for y in YEARS if y in years_present]
for tf in TFS:
print("\n" + "=" * (62 + 9 * len(years)))
print(f" FADE su FEED PULITO (Deribit mainnet) — {tf} | pos {POS}, leva {LEV:.0f}x, trend 3.0, fee 0.10% RT")
# mostra la copertura una volta per asset
for asset in ("BTC", "ETH"):
print(f" {asset}: {results[(tf, asset, 'MR01')][2]}")
print("=" * (62 + 9 * len(years)))
print(f" {'sleeve':<11s}" + "".join(f"{y:>9d}" for y in years) +
f"{'Trd':>7s}{'FULL%':>9s}{'Shrp':>7s}{'OOS%':>8s}{'Shrp':>7s}")
print(" " + "-" * (60 + 9 * len(years)))
for asset in ("BTC", "ETH"):
for code in ("MR01", "MR02", "MR07"):
(yr, fF, shF, fO, shO), ntr, _ = results[(tf, asset, code)]
print(f" {code+'_'+asset:<11s}" + "".join(f"{yr[y]:>+9.0f}" for y in years) +
f"{ntr:>7d}{fF:>+9.0f}{shF:>7.2f}{fO:>+8.0f}{shO:>7.2f}")
print()
if __name__ == "__main__":
main()
+274
View File
@@ -0,0 +1,274 @@
"""CLEAN FEED — ripara gli spike-print del feed Deribit/Cerbero coi dati reali di Binance.
Motivo (2026-06-18): la ricerca Price Ladder ha rivelato che data/raw/btc_1h.parquet (e gli
altri TF/asset) contengono barre con WICK FASULLI (es. BTC 2024-02-13: low 38.580 con
close ~49.968, BTC reale ~50k) — lo stesso spike-print testnet documentato in CLAUDE.md
(TP_PHANTOM / feed congelato). Sono pochi (decine per file) ma avvelenano i backtest
(stop/entry su prezzi mai avvenuti) e gonfiano le code (la "FULL DD BTC ~54%" del ladder era
in gran parte questo).
Metodo (conservativo, fonte di verita' = Binance spot via ccxt, gia' cablato nel progetto):
1. DETECT: barra sospetta = high/low che sfora >15% il cluster di close locale [i-1,i,i+1]
(close sano + wick fasullo). Soglia larga: tanto e' Binance ad arbitrare.
2. ARBITRA: per ogni sospetta, scarica la barra Binance reale (BTC/USDT, ETH/USDT) allo
stesso tf/timestamp. Sostituisce O/H/L/C SOLO se Binance dissente materialmente (>2% su
high o low) -> un wick VERO confermato da Binance resta intatto. Volume/timestamp invariati.
3. BACKUP (data/_feed_backup/) + scrittura atomica + VALIDAZIONE (re-scan = 0 sospette,
n righe invariato). Log dettagliato di ogni barra riparata (old OHLC -> new).
uv run python scripts/analysis/clean_feed.py [ASSET_TF ...] # default: tutti BTC/ETH x TF
uv run python scripts/analysis/clean_feed.py BTC_1h # un solo file
"""
from __future__ import annotations
import shutil
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import _parquet_path, DATA_DIR
BACKUP = PROJECT_ROOT / "data" / "_feed_backup"
SYMBOL = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}
WICK_THR = 0.15 # detect: wick oltre 15% il cluster di close locale
REPLACE_THR = 0.02 # arbitra: sostituisci solo se Binance dissente >2% su high/low
CLOSE_THR = 0.01 # close-aware: sostituisci la barra se il CLOSE Deribit dista >1% da Binance
TF_MS = {"5m": 5, "8m": 8, "13m": 13, "15m": 15, "19m": 19, "30m": 30, "1h": 60}
_EX = None
def _binance():
global _EX
if _EX is None:
import ccxt
_EX = ccxt.binance({"enableRateLimit": True})
return _EX
def suspect_mask(df: pd.DataFrame) -> np.ndarray:
c = df["close"].to_numpy(float); h = df["high"].to_numpy(float); l = df["low"].to_numpy(float)
cp = np.roll(c, 1); cp[0] = c[0]; cn = np.roll(c, -1); cn[-1] = c[-1]
locmax = np.maximum.reduce([c, cp, cn]); locmin = np.minimum.reduce([c, cp, cn])
return (h > locmax * (1 + WICK_THR)) | (l < locmin * (1 - WICK_THR))
def _binance_bar(symbol: str, tf: str, ts_ms: int):
"""OHLC reale Binance alla barra ts_ms (None se assente)."""
try:
rows = _binance().fetch_ohlcv(symbol, tf, since=ts_ms - 1, limit=3)
except Exception as e:
print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}")
return None
for r in rows:
if int(r[0]) == ts_ms:
return float(r[1]), float(r[2]), float(r[3]), float(r[4])
return None
def clean_file(asset: str, tf: str) -> dict:
path = _parquet_path(asset, tf)
if not path.exists():
return {"file": f"{asset}_{tf}", "skip": "no-file"}
df = pd.read_parquet(path)
mask = suspect_mask(df)
idx = np.where(mask)[0]
n0 = len(df)
if len(idx) == 0:
return {"file": f"{asset}_{tf}", "suspect": 0, "repaired": 0, "kept_real": 0,
"missing_binance": 0, "rows_before": n0, "rows_after": n0,
"still_suspect": 0, "log": []}
repaired, kept, missing = 0, 0, 0
log = []
for i in idx:
ts = int(df.iloc[i]["timestamp"])
b = _binance_bar(SYMBOL[asset], tf, ts)
oh, ol = float(df.iloc[i]["high"]), float(df.iloc[i]["low"])
if b is None:
missing += 1
continue
bo, bh, bl, bc = b
if abs(oh - bh) / bh > REPLACE_THR or abs(ol - bl) / max(bl, 1e-9) > REPLACE_THR:
df.iat[i, df.columns.get_loc("open")] = bo
df.iat[i, df.columns.get_loc("high")] = bh
df.iat[i, df.columns.get_loc("low")] = bl
df.iat[i, df.columns.get_loc("close")] = bc
repaired += 1
ts_s = pd.to_datetime(ts, unit="ms", utc=True).strftime("%Y-%m-%d %H:%M")
log.append(f" {ts_s} H {oh:,.0f}->{bh:,.0f} L {ol:,.0f}->{bl:,.0f}")
else:
kept += 1 # Binance conferma il wick: barra reale, intatta
if repaired:
BACKUP.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.bak")
tmp = path.with_suffix(".parquet.tmp")
df.to_parquet(tmp, index=False)
tmp.replace(path)
# validazione
df2 = pd.read_parquet(path)
still = int(suspect_mask(df2).sum())
return {"file": f"{asset}_{tf}", "suspect": len(idx), "repaired": repaired,
"kept_real": kept, "missing_binance": missing, "rows_before": n0,
"rows_after": len(df2), "still_suspect": still, "log": log}
def _binance_series(asset: str, tf: str, start_ms: int, end_ms: int) -> dict:
"""OHLC reale Binance per l'intero range -> dict ts_ms -> (o,h,l,c). Bulk paginato."""
ex = _binance()
tf_ms = TF_MS[tf] * 60 * 1000
out: dict[int, tuple] = {}
since = start_ms
while since <= end_ms:
try:
rows = ex.fetch_ohlcv(SYMBOL[asset], tf, since=since, limit=1000)
except Exception as e:
print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}")
break
if not rows:
break
for r in rows:
out[int(r[0])] = (float(r[1]), float(r[2]), float(r[3]), float(r[4]))
nxt = int(rows[-1][0]) + tf_ms
if nxt <= since:
break
since = nxt
if len(rows) < 1000 and since > end_ms:
break
return out
def clean_file_close(asset: str, tf: str, thr: float = CLOSE_THR, backup_dir: Path | None = None) -> dict:
"""CLOSE-AWARE: sostituisce O/H/L/C con Binance per ogni barra il cui CLOSE Deribit
dista > thr da Binance (1% default). Cattura i print 'silenziosi' che il wick-check
>15% non vede (close fantasma su barra di range piccolo). Fonte di verita' = Binance
spot (il feed storico e' perp testnet -> inaffidabile; lo spot ~ mainnet via arbitraggio)."""
if tf not in TF_MS:
return {"file": f"{asset}_{tf}", "skip": "tf-non-binance"}
path = _parquet_path(asset, tf)
if not path.exists():
return {"file": f"{asset}_{tf}", "skip": "no-file"}
df = pd.read_parquet(path)
n0 = len(df)
tms = df["timestamp"].to_numpy("int64")
c = df["close"].to_numpy(float)
bz = _binance_series(asset, tf, int(tms[0]), int(tms[-1]))
col = {k: df.columns.get_loc(k) for k in ("open", "high", "low", "close")}
fixed, by_year, missing = 0, {}, 0
log = []
for i in range(n0):
b = bz.get(int(tms[i]))
if b is None:
missing += 1
continue
bo, bh, bl, bc = b
if bc <= 0:
continue
orig = float(c[i]) # cattura PRIMA della scrittura (to_numpy puo' essere una view)
if abs(orig - bc) / bc > thr:
df.iat[i, col["open"]] = bo
df.iat[i, col["high"]] = bh
df.iat[i, col["low"]] = bl
df.iat[i, col["close"]] = bc
fixed += 1
y = pd.to_datetime(int(tms[i]), unit="ms", utc=True).year
by_year[y] = by_year.get(y, 0) + 1
if len(log) < 10:
ts_s = pd.to_datetime(int(tms[i]), unit="ms", utc=True).strftime("%Y-%m-%d %H:%M")
log.append(f" {ts_s} C {orig:,.2f}->{bc:,.2f} ({abs(orig-bc)/bc*100:.1f}%)")
if fixed:
bdir = backup_dir or BACKUP
bdir.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, bdir / f"{asset.lower()}_{tf}.parquet.bak")
tmp = path.with_suffix(".parquet.tmp")
df.to_parquet(tmp, index=False)
tmp.replace(path)
# validazione: ri-scan, 0 barre residue oltre soglia (fra quelle coperte da Binance)
df2 = pd.read_parquet(path)
c2 = df2["close"].to_numpy(float)
still = sum(1 for i in range(len(df2))
if (b := bz.get(int(tms[i]))) and b[3] > 0 and abs(c2[i] - b[3]) / b[3] > thr)
return {"file": f"{asset}_{tf}", "covered": n0 - missing, "fixed": fixed,
"missing_binance": missing, "rows_before": n0, "rows_after": len(df2),
"still_over_thr": still, "by_year": by_year, "log": log}
def main():
args = [a for a in sys.argv[1:] if not a.startswith("--")]
close_mode = "--close" in sys.argv
dry = "--dry" in sys.argv
if close_mode:
targets = args or [f"{a}_{tf}" for a in ("BTC", "ETH") for tf in ("5m", "15m", "1h")]
stamp = time.strftime("%Y%m%d-%H%M%S")
bdir = BACKUP / f"pre_close_clean_{stamp}"
print(f"CLEAN FEED (close-aware vs Binance, thr={CLOSE_THR*100:.0f}%) — "
f"{'DRY-RUN (nessuna scrittura)' if dry else f'backup in {bdir}'}\n")
grand = 0
for t in targets:
asset, tf = t.split("_", 1)
if dry:
# dry: conta soltanto, niente scrittura
r = _dry_close(asset, tf)
else:
r = clean_file_close(asset, tf, backup_dir=bdir)
if r.get("skip"):
print(f" {t:<9} SKIP ({r['skip']})"); continue
grand += r.get("fixed", 0)
yr = " ".join(f"{y}:{n}" for y, n in sorted(r.get("by_year", {}).items()))
print(f" {r['file']:<9} coperte={r.get('covered',0):>7} riparate={r.get('fixed',0):>4} "
f"no-binance={r.get('missing_binance',0):>5} | righe {r['rows_before']}=={r.get('rows_after',r['rows_before'])} "
f"residue>thr={r.get('still_over_thr','-')}")
if yr:
print(f" per anno: {yr}")
for line in r.get("log", []):
print(line)
print(f"\n TOTALE barre riparate (close-aware): {grand}")
return
targets = args or [f"{a}_{tf}" for a in ("BTC", "ETH") for tf in ("5m", "15m", "30m", "1h")]
print(f"CLEAN FEED — backup in {BACKUP}\n")
grand = 0
for t in targets:
asset, tf = t.split("_", 1)
r = clean_file(asset, tf)
if r.get("skip"):
print(f" {t:<9} SKIP ({r['skip']})"); continue
grand += r.get("repaired", 0)
print(f" {r['file']:<9} sospette={r['suspect']:>3} riparate={r['repaired']:>3} "
f"reali-tenute={r.get('kept_real',0):>3} no-binance={r.get('missing_binance',0):>2} "
f"| righe {r['rows_before']}=={r['rows_after']} residue-sospette={r['still_suspect']}")
for line in r.get("log", [])[:8]:
print(line)
if len(r.get("log", [])) > 8:
print(f" ... (+{len(r['log'])-8} altre)")
print(f"\n TOTALE barre riparate: {grand}")
def _dry_close(asset: str, tf: str, thr: float = CLOSE_THR) -> dict:
"""Conta soltanto quante barre verrebbero riparate (nessuna scrittura)."""
if tf not in TF_MS:
return {"file": f"{asset}_{tf}", "skip": "tf-non-binance"}
path = _parquet_path(asset, tf)
if not path.exists():
return {"file": f"{asset}_{tf}", "skip": "no-file"}
df = pd.read_parquet(path)
tms = df["timestamp"].to_numpy("int64"); c = df["close"].to_numpy(float)
bz = _binance_series(asset, tf, int(tms[0]), int(tms[-1]))
fixed, by_year, missing = 0, {}, 0
for i in range(len(df)):
b = bz.get(int(tms[i]))
if b is None:
missing += 1; continue
if b[3] > 0 and abs(c[i] - b[3]) / b[3] > thr:
fixed += 1
y = pd.to_datetime(int(tms[i]), unit="ms", utc=True).year
by_year[y] = by_year.get(y, 0) + 1
return {"file": f"{asset}_{tf}", "covered": len(df) - missing, "fixed": fixed,
"missing_binance": missing, "rows_before": len(df), "by_year": by_year, "log": []}
if __name__ == "__main__":
main()
+164
View File
@@ -0,0 +1,164 @@
"""Studio: combinare TUTTE le strategie (fade + honest) migliora i risultati?
Due famiglie con meccanismi e orizzonti diversi:
FADE (intraday 1h, long/short, BTC/ETH): MR01 boll, MR02 donchian, MR07
return-reversal — tutte col filtro trend 3.0 ATR. (MR03 keltner -> waste.)
HONEST (long-only, multi-regime, multi-crypto): DIP01 (dip-buy 1h BTC),
TR01 (EMA trend 4h basket), ROT02 (dual-momentum rotation 1d).
Metodo: per ogni sleeve si costruisce l'equity GIORNALIERA normalizzata su un
indice comune (2021-01-01 -> 2026-05-26), si passa ai rendimenti giornalieri,
si misura la correlazione cross-famiglia e si confrontano i portafogli
equal-weight (ribilanciati ogni giorno) e inverse-vol. Metriche FULL e OOS
(ultimo 30% della finestra comune): ritorno, CAGR, max DD, Sharpe annualizzato.
Tutto NETTO (fee gia' incluse nelle sleeve), leva 3x, pos 15% per sleeve.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.risk_management import strats_for, build_trades, INIT
# curve daily honest gia' pronte nell'altra famiglia
from scripts.analysis.honest_improve2 import (
_daily_equity, _norm, dip_market_gated, _tr_basket_daily, _rot_daily_equity,
)
IDX = pd.date_range("2021-01-01", "2026-05-26", freq="1D", tz="UTC")
OOS_FRAC = 0.30
SPLIT = int(len(IDX) * (1 - OOS_FRAC)) # confine OOS sulla finestra comune
OOS_DATE = IDX[SPLIT].date()
ANN = 365.0 # giorni/anno per annualizzare
# ---------------- equity giornaliere ----------------
# SWAP fade 1h -> 15m (2026-06-12, scelta utente). Gate fade15m_port06_gate.py: parametri
# 1h NON ri-tunati trasferiti a 15m, corr 15m-1h media 0.26, SWAP promosso (FULL CAGR
# 74->101% DD 3.46->2.47%, OOS Sh 10.07->10.86; OOS DD 1.48->2.09 accettato), edge ETH
# regge il flat-entry-skip. Il canonico segue il deployato per tenere la parita' delle
# due facce. Diario docs/diary/2026-06-12-fade15m-gate.md.
FADE_TF = "15m"
def fade_daily_equity(asset: str, fn, params, tf: str = FADE_TF) -> pd.Series:
"""Equity giornaliera di uno sleeve fade: trade (filtro trend 3.0) -> equity -> daily."""
df = load_data(asset, tf)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
trades = build_trades(fn(df, **params), df, trend_max=3.0)
n = len(df); eq = np.full(n, INIT, dtype=float); cap = INIT
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * 0.15 * ret, 10.0)
eq[j:] = cap
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
return _norm(s)
def build_all_sleeves() -> dict[str, pd.Series]:
sleeves: dict[str, pd.Series] = {}
# --- FADE: 6 sleeve (15m dal 2026-06-12, vedi FADE_TF) ---
for asset in ["BTC", "ETH"]:
for nm, (fn, params) in strats_for(asset).items():
sleeves[f"{nm}_{asset}"] = fade_daily_equity(asset, fn, params)
# --- HONEST: 3 sleeve (riuso le funzioni dell'altra famiglia) ---
d = dip_market_gated("BTC", market_n=0, return_equity=True)
sleeves["DIP01_BTC"] = _norm(_daily_equity(d["eq_ts"], d["eq_v"], IDX))
sleeves["TR01_basket"] = _norm(_tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX))
sleeves["ROT02_rot"] = _norm(_rot_daily_equity(IDX))
return sleeves
# ---------------- metriche ----------------
def metrics(daily_ret: pd.Series, lo: int = 0, hi: int | None = None) -> dict:
r = daily_ret.iloc[lo:hi]
eq = (1 + r).cumprod()
peak = eq.cummax(); dd = float(((peak - eq) / peak).max() * 100)
yrs = len(r) / ANN
tot = (eq.iloc[-1] - 1) * 100
cagr = ((eq.iloc[-1]) ** (1 / yrs) - 1) * 100 if yrs > 0 else 0.0
sharpe = float(r.mean() / r.std() * np.sqrt(ANN)) if r.std() > 0 else 0.0
return dict(ret=tot, cagr=cagr, dd=dd, sharpe=sharpe)
def yearly_returns(daily_ret: pd.Series) -> dict[int, float]:
"""Rendimento % netto per anno solare dai rendimenti giornalieri composti."""
g = daily_ret.groupby(daily_ret.index.year).apply(lambda x: ((1 + x).prod() - 1) * 100)
return {int(y): float(v) for y, v in g.items()}
def port_returns(members: dict[str, pd.Series], weights: dict[str, float] | None = None) -> pd.Series:
"""Rendimenti giornalieri di un portafoglio ribilanciato ogni giorno ai pesi dati."""
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in members.items()})
if weights is None:
return dr.mean(axis=1)
w = pd.Series(weights); w = w / w.sum()
return (dr * w).sum(axis=1)
def inv_vol_weights(members: dict[str, pd.Series], lo=0, hi=None) -> dict[str, float]:
"""Pesi inversamente proporzionali alla volatilita' (stimata sulla finestra train)."""
vol = {k: v.pct_change().iloc[lo:hi].std() for k, v in members.items()}
inv = {k: (1.0 / s if s and s > 0 else 0.0) for k, s in vol.items()}
tot = sum(inv.values())
return {k: x / tot for k, x in inv.items()}
# ---------------- report ----------------
def row(label, dr):
f = metrics(dr); o = metrics(dr, lo=SPLIT)
print(f" {label:<26s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['cagr']:>7.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
def main():
print("Costruzione equity giornaliere (puo' richiedere ~1 min)...")
S = build_all_sleeves()
fade = {k: v for k, v in S.items() if k.startswith("MR")}
honest = {k: v for k, v in S.items() if not k.startswith("MR")}
# --- correlazione cross-famiglia ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in S.items()})
corr = dr.corr()
fade_k, hon_k = list(fade), list(honest)
cross = corr.loc[fade_k, hon_k]
print("\n" + "=" * 92)
print(f" CORRELAZIONE rendimenti giornalieri — FADE (righe) vs HONEST (colonne) | {IDX[0].date()}->{IDX[-1].date()}")
print("=" * 92)
print(f" {'':<12s}" + "".join(f"{c:>13s}" for c in hon_k))
for f in fade_k:
print(f" {f:<12s}" + "".join(f"{cross.loc[f,c]:>13.2f}" for c in hon_k))
intra_fade = corr.loc[fade_k, fade_k].values[np.triu_indices(len(fade_k), 1)].mean()
intra_hon = corr.loc[hon_k, hon_k].values[np.triu_indices(len(hon_k), 1)].mean()
print(f"\n Corr media intra-FADE {intra_fade:+.2f} | intra-HONEST {intra_hon:+.2f} | "
f"cross-famiglia {cross.values.mean():+.2f} (piu' bassa = piu' diversificazione)")
# --- confronto portafogli ---
print("\n" + "=" * 92)
print(f" PORTAFOGLI equal-weight (ribil. giornaliero) | OOS da {OOS_DATE} | leva3x pos15%/sleeve")
print("=" * 92)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oCAGR':>7s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 88)
row("FADE only (8 sleeve)", port_returns(fade))
row("HONEST only (3 sleeve)", port_returns(honest))
row("ALL equal-weight (11)", port_returns(S))
# 50/50 fra le due famiglie (ogni famiglia equipesata al suo interno)
fr, hr = port_returns(fade), port_returns(honest)
row("ALL 50/50 famiglie", (fr + hr) / 2)
# inverse-vol sul train, applicato a tutti gli 11 sleeve
w = inv_vol_weights(S, lo=0, hi=SPLIT)
row("ALL inverse-vol", port_returns(S, w))
print(" " + "-" * 88)
print(" Sharpe annualizzato sui rendimenti giornalieri. Confronta DD e Sharpe:")
print(" se il combinato ha DD piu' basso e Sharpe piu' alto delle singole famiglie, combinare conviene.")
if __name__ == "__main__":
main()
+87
View File
@@ -0,0 +1,87 @@
"""Combina i NUOVI edge (pairs + TSM01) col MASTER esistente: migliora il portafoglio?
Aggiunge al MASTER a 9 sleeve (6 fade + 3 honest) due nuove fonti scoperte
nell'esplorazione, poco correlate:
- PAIRS market-neutral (ETH/BTC, LTC/ETH, ADA/ETH) -> corr ~0 col mercato
- TSM01 (TSMOM multi-orizzonte + risk-off) -> corr ~0.53 con ROT02
Misura correlazione delle nuove sleeve vs esistenti e confronta MASTER-9 vs
MASTER-esteso su Ret/CAGR/DD/Sharpe, FULL e OOS (finestra comune 2021-2026).
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import (
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
from scripts.analysis.honest_improve2 import _daily_equity, _norm
from scripts.analysis.pairs_research import pairs_sim
from scripts.analysis.tsmom_research import tsmom_sim
def daily_from(eq_ts, eq_v):
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def main():
print("Costruzione equity (puo' richiedere ~1-2 min)...\n")
S = build_all_sleeves() # 9 sleeve esistenti
# nuove sleeve: i 6 pairs robusti di PR01 + TSM01
from scripts.strategies.PR01_pairs_reversion import PAIRS
new = {}
for a, b, p in PAIRS:
r = pairs_sim(a, b, **p)
new[f"PR_{a}{b}"] = daily_from(r["eq_ts"], r["eq_v"])
t = tsmom_sim()
new["TSM01"] = daily_from(t["eq_ts"], t["eq_v"])
allS = {**S, **new}
# --- correlazione nuove vs esistenti ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in allS.items()})
corr = dr.corr()
old_k = list(S); new_k = list(new)
print("=" * 88)
print(" CORRELAZIONE rendimenti giornalieri — NUOVE (righe) vs media esistenti")
print("=" * 88)
for nk in new_k:
avg = corr.loc[nk, old_k].mean()
mx = corr.loc[nk, old_k].abs().max()
print(f" {nk:<12s} corr media col MASTER-9 = {avg:+.2f} |max| = {mx:.2f}")
# --- confronto portafogli ---
def line(label, members):
pr = port_returns(members)
f, o = metrics(pr), metrics(pr, lo=SPLIT)
print(f" {label:<26s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
return pr
print("\n" + "=" * 96)
print(f" MASTER-9 vs MASTER-ESTESO (con pairs+TSM01) | OOS da {OOS_DATE} | equal-weight daily")
print("=" * 96)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 92)
pairs_only = {k: v for k, v in new.items() if k.startswith('PR_')}
line(f"MASTER-9 (base)", S)
line(f"MASTER +pairs ({len(S)+len(pairs_only)})", {**S, **pairs_only})
line(f"MASTER +TSM01 ({len(S)+1})", {**S, "TSM01": new["TSM01"]})
pr_all = line(f"MASTER-esteso ({len(allS)})", allS)
print(" " + "-" * 92)
pa = yearly_returns(pr_all)
print(" MASTER-esteso per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in pa.items()))
print("\n Se il MASTER-esteso ha DD piu' basso e/o Sharpe piu' alto del MASTER-9, le nuove")
print(" famiglie aggiungono valore (diversificazione da fonti scorrelate).")
if __name__ == "__main__":
main()
@@ -0,0 +1,209 @@
"""ANALISI DI IMPATTO (sola lettura, da docs/TODO.md): bug bfill di `_daily_equity`.
IL BUG (scripts/analysis/honest_improve2.py:30):
daily = s.resample("1D").last().reindex(idx).ffill().bfill()
La serie `s` e' a PUNTI-TRADE (un valore di capitale per ogni exit). Il `reindex(idx)`
taglia PRIMA di forward-fillare: i giorni di IDX precedenti al primo trade DENTRO la
finestra restano NaN (il ffill non ha un valore precedente in-finestra da propagare) e
il `.bfill()` finale li riempie col capitale DOPO il primo trade in-finestra. Effetti:
1. l'ancora a idx[0] e' il capitale post-primo-trade-in-finestra, NON il capitale
portato avanti dall'ultimo trade PRIMA della finestra;
2. il rendimento del primo trade in-finestra viene CANCELLATO dalla serie daily
(la testa e' piatta al valore post-trade -> pct_change = 0 anche il giorno del trade).
CORREZIONE (qui, solo per confronto): ffill PRIMA del reindex (carry-forward su tutta la
storia trade) + testa pre-primo-trade-assoluto = capitale iniziale 1000. MAI valori dal futuro.
Sleeve canonici interessati (serie a punti-trade -> testa di IDX scoperta):
DIP01_BTC, PR_ETHBTC, PR_ETHBTC_15M, PR_LTCETH, PR_ADAETH, PR_BTCLTC, PR_ETHSOL,
TSM01, XS01 (questi due quasi-densi: punti daily/12h -> impatto atteso ~0).
TR01_basket / ROT02_rot passano da _daily_equity ma con punti PER-BARRA (densi dal
2018) -> verificati comunque qui via monkeypatch runtime (nessun file canonico toccato).
I fade (combine_portfolio.py:52) e SH01 (shape_ml_validate.py:124) usano lo stesso
pattern reindex+bfill ma su equity PER-BARRA con dati che iniziano prima di IDX[0]
-> il bfill e' un no-op (verificato: nessun NaN in testa).
NB: le metriche OOS canoniche affettano la STESSA serie daily a SPLIT (metrics(dr,
lo=SPLIT)); la distorsione sta solo in testa (2021) -> l'OOS e' invariato per
costruzione se il primo trade in-finestra precede lo SPLIT. Questo script lo misura.
Uso: uv run python scripts/analysis/daily_equity_bfill_impact.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import scripts.analysis.honest_improve2 as hi2
from scripts.analysis.honest_improve2 import _norm, dip_market_gated
from scripts.analysis.combine_portfolio import IDX, SPLIT, OOS_DATE, metrics, port_returns
from scripts.analysis.pairs_research import pairs_sim, pairs_sim_flat
from scripts.analysis.tsmom_research import tsmom_sim
from scripts.strategies.PR01_pairs_reversion import PAIRS as PAIR_DEFS
from scripts.strategies.XS01_cross_sectional import xsec_sim
from scripts.portfolios._defs import PORTFOLIOS
from src.portfolio import weighting as W
INIT = 1000.0
# ---------------- le due convenzioni ----------------
def daily_equity_buggy(ts_list, cap_list, idx):
"""Replica ESATTA di honest_improve2._daily_equity (per parity-check)."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
return s.resample("1D").last().reindex(idx).ffill().bfill()
def daily_equity_fixed(ts_list, cap_list, idx, init=INIT):
"""CORRETTA: ancora = capitale portato avanti dall'ultimo trade PRIMA della
finestra (ffill prima del reindex); pre-primo-trade assoluto = capitale iniziale."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
daily = s.resample("1D").last().ffill() # carry-forward su TUTTA la storia
daily = daily.reindex(idx).ffill() # coda oltre l'ultimo trade
return daily.fillna(init) # testa pre-primo-trade: capitale iniziale
def head_info(ts_list, cap_list, idx):
"""(primo giorno con trade dentro IDX, rendimento di testa perso dal bfill %)."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
raw = s.resample("1D").last().reindex(idx) # senza fill: NaN = nessun trade quel giorno
first = raw.first_valid_index()
if first is None:
return None, 0.0
fixed = daily_equity_fixed(ts_list, cap_list, idx)
lost = (fixed.loc[first] / fixed.iloc[0] - 1) * 100 # ritorno idx[0]->primo trade-day
return first.date(), float(lost)
def m2(eq: pd.Series):
dr = eq.pct_change().fillna(0.0)
return metrics(dr), metrics(dr, lo=SPLIT)
def fmt_pair(label, b, f):
d_sh = f["sharpe"] - b["sharpe"]
d_dd = f["dd"] - b["dd"]
d_rt = f["ret"] - b["ret"]
return (f" {label:<22s}"
f"Sh {b['sharpe']:6.2f}->{f['sharpe']:6.2f} ({d_sh:+.3f}) "
f"DD {b['dd']:6.2f}->{f['dd']:6.2f} ({d_dd:+.3f}pp) "
f"ret {b['ret']:+9.1f}->{f['ret']:+9.1f} ({d_rt:+8.2f}pp)")
def main():
print("=" * 110)
print(" IMPATTO bug bfill _daily_equity (honest_improve2.py:30) — attuale vs corretto")
print(f" IDX {IDX[0].date()} -> {IDX[-1].date()} | OOS da {OOS_DATE} (slice a SPLIT={SPLIT} sui rendimenti daily)")
print("=" * 110)
# ---------------- [1] baseline canonica (bfill cosi' com'e') ----------------
print("\n[1] build_everything() canonico (2-3 min)...")
from scripts.analysis.report_families import build_everything
S, pairs, tsm, shape = build_everything()
base = {**S, **pairs, **tsm, **shape}
# ---------------- [2] ri-simula gli sleeve a punti-trade ----------------
print("[2] ri-simulazione sleeve a punti-trade (parity-check + versione corretta)...")
raw: dict[str, tuple] = {}
d = dip_market_gated("BTC", market_n=0, return_equity=True)
raw["DIP01_BTC"] = (d["eq_ts"], d["eq_v"])
for a, b_, p in PAIR_DEFS:
r = pairs_sim(a, b_, **p)
raw[f"PR_{a}{b_}"] = (r["eq_ts"], r["eq_v"])
r15 = pairs_sim_flat("ETH", "BTC", tf="15m", n=66, z_in=1.674, z_exit=1.0,
max_bars=35, flat_skip=True, pos=0.075)
raw["PR_ETHBTC_15M"] = (r15["eq_ts"], r15["eq_v"])
t = tsmom_sim()
raw["TSM01"] = (t["eq_ts"], t["eq_v"])
x = xsec_sim()
raw["XS01"] = (x["eq_ts"], x["eq_v"])
fixed: dict[str, pd.Series] = {}
print(f"\n {'sleeve':<16s}{'parity(max|diff|)':>18s}{'1o trade in IDX':>17s}{'ret testa perso%':>18s}")
for k, (ts, v) in raw.items():
bug = _norm(daily_equity_buggy(ts, v, IDX))
par = float((bug - base[k]).abs().max())
fixed[k] = _norm(daily_equity_fixed(ts, v, IDX))
first, lost = head_info(ts, v, IDX)
flag = "" if par < 1e-9 else " <-- PARITY FAIL"
print(f" {k:<16s}{par:>18.2e}{str(first):>17s}{lost:>+18.3f}{flag}")
# TR01/ROT02: passano da _daily_equity ma con punti per-barra (densi) ->
# ricalcolo con monkeypatch RUNTIME della funzione (nessun file toccato).
orig_de = hi2._daily_equity
try:
hi2._daily_equity = daily_equity_fixed
tr_f = _norm(hi2._tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX))
rot_f = _norm(hi2._rot_daily_equity(IDX))
finally:
hi2._daily_equity = orig_de
for k, sf in (("TR01_basket", tr_f), ("ROT02_rot", rot_f)):
diff = float((sf - base[k]).abs().max())
print(f" {k:<16s}{'(denso)':>18s}{'':>17s}{diff:>18.2e} (diff fixed-vs-base: atteso ~0)")
fixed[k] = sf
# ---------------- [3] metriche per sleeve: attuale vs corretto ----------------
print("\n" + "=" * 110)
print(" (3) SLEEVE a punti-trade — FULL e OOS, attuale(bfill) -> corretto(carry-forward)")
print("=" * 110)
rows_oos_delta = {}
for k in fixed:
bf, bo = m2(base[k])
ff, fo = m2(fixed[k])
print(fmt_pair(f"{k} FULL", bf, ff))
print(fmt_pair(f"{k} OOS ", bo, fo))
rows_oos_delta[k] = (ff["sharpe"] - bf["sharpe"], ff["dd"] - bf["dd"],
fo["sharpe"] - bo["sharpe"], fo["dd"] - bo["dd"])
# ---------------- [4] PORT06: attuale vs corretto ----------------
print("\n" + "=" * 110)
print(" (4) PORT06 (cap PAIRS 0.33 + SHAPE 0.0588) — attuale vs corretto")
print("=" * 110)
p = PORTFOLIOS["PORT06"]
def port_m(members):
ids = p.sleeve_ids
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
members_fix = {**base, **fixed}
bf, bo = port_m(base)
ff, fo = port_m(members_fix)
print(fmt_pair("PORT06 FULL", bf, ff))
print(fmt_pair("PORT06 OOS ", bo, fo))
# ---------------- [5] verdetto ----------------
print("\n" + "=" * 110)
print(" (5) VERDETTO (soglie materialita': >0.1 Sharpe o >0.5pp DD su PORT06 OOS)")
print("=" * 110)
d_sh_oos = abs(fo["sharpe"] - bo["sharpe"])
d_dd_oos = abs(fo["dd"] - bo["dd"])
d_sh_full = abs(ff["sharpe"] - bf["sharpe"])
d_dd_full = abs(ff["dd"] - bf["dd"])
materiale = d_sh_oos > 0.1 or d_dd_oos > 0.5
print(f" PORT06 OOS : dSharpe {fo['sharpe']-bo['sharpe']:+.4f} dDD {fo['dd']-bo['dd']:+.4f}pp"
f" -> {'MATERIALE' if materiale else 'NON materiale'}")
print(f" PORT06 FULL: dSharpe {ff['sharpe']-bf['sharpe']:+.4f} dDD {ff['dd']-bf['dd']:+.4f}pp")
worst = sorted(rows_oos_delta.items(), key=lambda kv: -abs(kv[1][0]) - abs(kv[1][1]) / 10)
print(" Sleeve piu' toccati (dSharpe FULL, dDD FULL, dSharpe OOS, dDD OOS):")
for k, (ds, dd_, dso, ddo) in worst[:5]:
print(f" {k:<16s} FULL {ds:+.3f} / {dd_:+.3f}pp OOS {dso:+.3f} / {ddo:+.3f}pp")
print("\n Nota strutturale: l'OOS canonico e' uno slice a SPLIT della stessa serie daily;")
print(" la distorsione bfill vive solo in testa (prima del primo trade in IDX) -> se il")
print(" primo trade in-finestra precede lo SPLIT, l'OOS e' INVARIATO per costruzione.")
if __name__ == "__main__":
main()
@@ -0,0 +1,146 @@
"""PIANO STORICO DERIBIT — quanta storia copre davvero il venue dove ESEGUIAMO?
Obiettivo: scegliere la fonte migliore per ricostruire lo storico di backtest, dato
che si esegue su Deribit. Principio (gia' misurato in multi_source_check): l'ancora
giusta e' il VENUE DI ESECUZIONE, non Binance/USDT. Qui rispondo con i numeri a:
1. COPERTURA: da quando esiste OHLCV su Deribit MAINNET (ccxt pubblico, no token) per
gli strumenti che tradiamo — inverse (BTC/ETH-PERPETUAL) e lineari USDC.
2. TIMEFRAME nativi disponibili su Deribit.
3. FEDELTA' inverse-vs-lineare (stesso indice? -> posso usare l'inverse, storia lunga,
come price-series e i lineari recenti sono ridondanti per il PREZZO).
4. GAP pre-Deribit: quanto indietro vanno le strategie e cosa manca -> da gap-fillare
con Coinbase USD (spot, NON USDT).
Tutto via ccxt pubblico Deribit (= api.deribit.com mainnet, reale). Non modifica nulla.
uv run python scripts/analysis/deribit_history_plan.py
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
import ccxt
DERIBIT = ccxt.deribit({"enableRateLimit": True})
COINBASE = ccxt.coinbase({"enableRateLimit": True})
def earliest(symbol: str, tf: str = "1d") -> tuple[str | None, int, str | None]:
"""Trova la prima candela disponibile (probe since 2016) + n candele totali stimate."""
since = int(pd.Timestamp("2016-01-01", tz="UTC").timestamp() * 1000)
try:
rows = DERIBIT.fetch_ohlcv(symbol, tf, since=since, limit=5000)
except Exception as e:
return None, 0, f"{type(e).__name__}: {str(e)[:60]}"
if not rows:
return None, 0, "no-data"
first = pd.to_datetime(int(rows[0][0]), unit="ms", utc=True)
last = pd.to_datetime(int(rows[-1][0]), unit="ms", utc=True)
return f"{first.date()} -> {last.date()}", len(rows), None
def list_perps() -> dict:
"""Risolve i simboli ccxt reali dei perp Deribit per BTC/ETH (inverse + lineari)."""
DERIBIT.load_markets()
found = {}
for sym, m in DERIBIT.markets.items():
if not m.get("swap"):
continue
base = m.get("base")
if base not in ("BTC", "ETH"):
continue
settle = m.get("settle")
kind = "inverse" if m.get("inverse") else "linear"
found[f"{base}-{kind}({settle})"] = sym
return found
def fetch_series(ex, symbol, tf, start, end, limit=1000):
start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000)
end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000)
tf_ms = ex.parse_timeframe(tf) * 1000
out, since = {}, start_ms
while since <= end_ms:
try:
rows = ex.fetch_ohlcv(symbol, tf, since=since, limit=limit)
except Exception:
break
if not rows:
break
for r in rows:
if start_ms <= int(r[0]) <= end_ms and r[4]:
out[int(r[0])] = float(r[4])
nxt = int(rows[-1][0]) + tf_ms
if nxt <= since:
break
since = nxt
if len(rows) < limit and since > end_ms:
break
return pd.Series(out)
def dev_bps(a: pd.Series, b: pd.Series) -> tuple[int, float, float, float]:
df = pd.concat([a.rename("a"), b.rename("b")], axis=1, join="inner")
if len(df) == 0:
return 0, 0, 0, 0
d = (df["a"] - df["b"]).abs() / df["b"] * 1e4
return len(df), float(d.median()), float(d.quantile(.95)), float(d.max())
def main():
print("=" * 84)
print(" PIANO STORICO DERIBIT MAINNET (ccxt pubblico, reale)")
print("=" * 84)
print("\n[1] Simboli perp Deribit BTC/ETH risolti:")
perps = list_perps()
for k, v in perps.items():
print(f" {k:<22s} -> {v}")
print("\n[2] COPERTURA storica (1d, probe da 2016):")
print(f" {'strumento':<22s}{'range disponibile':<28s}{'giorni':>8s}")
cov = {}
for k, sym in perps.items():
rng, n, err = earliest(sym, "1d")
cov[k] = (sym, rng, n)
print(f" {k:<22s}{(rng or err or '-'):<28s}{n:>8d}")
print("\n[3] TIMEFRAME nativi Deribit (test su BTC inverse, oggi):")
bsym = next((s for k, s in perps.items() if k.startswith("BTC-inverse")), "BTC/USD:BTC")
tfs = []
for tf in ("1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "3h", "4h", "6h", "12h", "1d"):
try:
r = DERIBIT.fetch_ohlcv(bsym, tf, limit=3)
tfs.append(tf if r else f"{tf}:vuoto")
except Exception:
tfs.append(f"{tf}:NO")
print(f" ok: {[t for t in tfs if ':' not in t]}")
print(f" ko: {[t for t in tfs if ':' in t]}")
print("\n[4] FEDELTA' inverse-vs-lineare USDC (close 1h, ultimi ~40g):")
for base in ("BTC", "ETH"):
inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None)
lin = next((s for k, s in perps.items() if k.startswith(f"{base}-linear")), None)
if not inv or not lin:
print(f" {base}: manca inverse o lineare"); continue
a = fetch_series(DERIBIT, inv, "1h", "2026-04-15", "2026-05-27")
b = fetch_series(DERIBIT, lin, "1h", "2026-04-15", "2026-05-27")
n, med, p95, mx = dev_bps(a, b)
print(f" {base}: barre={n} inverse-vs-lineare med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}")
print("\n[5] GAP pre-Deribit: Deribit inverse vs Coinbase USD su finestra PROFONDA (2020-06, 1d):")
for base in ("BTC", "ETH"):
inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None)
a = fetch_series(DERIBIT, inv, "1d", "2020-06-01", "2020-09-01")
b = fetch_series(COINBASE, f"{base}/USD", "1d", "2020-06-01", "2020-09-01", limit=300)
n, med, p95, mx = dev_bps(a, b)
cov_first = cov.get(f"{base}-inverse(BTC)" if base == "BTC" else f"{base}-inverse(ETH)", (None, "?", 0))[1]
print(f" {base}: Deribit-vs-Coinbase barre={n} med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}")
if __name__ == "__main__":
main()
+227
View File
@@ -0,0 +1,227 @@
"""GATE DIP01 + PORT06: estendere EXIT-16 (close-confirm SL) a DIP01 (sweep punto 9).
DIP01 e' l'unico sleeve BTC con esecuzione REALE round-trip, e gira ancora col
branch SL intrabar wick-sensitive. EXIT-16 e' stato validato SULLE FADE: estenderlo
a una strategia honest richiede la validazione sul grid proprio di DIP01, con
engine GAP-AWARE (lezione exit-lab: l'engine canonico filla gli stop "al livello"
anche su gap-through -> bias PRO stop intrabar stretti; il confronto onesto filla
lo SL a worse(livello, open)).
Protocollo:
[1] parita': replay engine 'orig' (fill al livello) == equity canonica DIP01_BTC
[2] grid 3x3x2 (z_in x sl_atr x max_bars) su BTC (deployato) ed ETH (robustezza):
orig GAP-AWARE vs EXIT-16(buf 0.5), ret/DD/Sharpe train (pre-OOS) e OOS
[3] plateau buffer {0.4, 0.5, 0.75, 1.0} sulla cella canonica
[4] gate PORT06: DIP01_BTC exit16 innestato nel canonico, pesi cap
-> PROMOSSO se OOS Sharpe non peggiora E FULL/DD non degradano materialmente.
NB hurst_max NON valutato: il gate trendmax (2026-06-07) ha mostrato che il
loss-guard Hurst e' ridondante-dannoso POST-EXIT-16 (stesso regime target).
uv run python scripts/analysis/dip01_exit16_impact.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.strategy_research import atr
from scripts.analysis.combine_portfolio import _norm, IDX, metrics, SPLIT, OOS_DATE
from scripts.analysis._port06_gate_common import port_metrics
from scripts.portfolios._defs import PORTFOLIOS
FEE_RT, LEV, POS, INIT = 0.001, 3.0, 0.15, 1000.0
BUFFER = 0.5
GRID_Z = (2.0, 2.5, 3.0)
GRID_SL = (2.0, 2.5, 3.0)
GRID_MB = (24, 48)
CANON = dict(n=50, z_in=2.5, sl_atr=2.5, max_bars=24)
def dip_entries(df, n=50, z_in=2.5, sl_atr=2.5, max_bars=24):
"""Entries DIP01 == honest_improve2.dip_market_gated (market_n=0): crossing
di z sotto -z_in. Ritorna [{i, tp, sl, mb}] (long-only)."""
c = df["close"].values
ma = pd.Series(c).rolling(n).mean().values
sd = pd.Series(c).rolling(n).std().values
a = atr(df, 14)
z = (c - ma) / np.where(sd == 0, np.nan, sd)
out = []
for i in range(n + 14, len(c)):
if np.isnan(z[i]) or np.isnan(a[i]):
continue
if z[i] <= -z_in and z[i - 1] > -z_in:
out.append({"i": i, "tp": ma[i], "sl": c[i] - sl_atr * a[i],
"mb": max_bars})
return out
def dip_trades(ents, df, mode, buffer=BUFFER):
"""Engine exit DIP01 (long-only), non-overlap come il canonico.
mode="orig" : SL intrabar fill AL LIVELLO (== canonico, per la parita')
mode="orig_gap" : SL intrabar fill a worse(livello, open[j]) — gap-aware
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
stop solo se close[j] < sl - buffer*ATR14[j], fill a close[j]
"""
h, l, c, o = df["high"].values, df["low"].values, df["close"].values, df["open"].values
n = len(c)
a = atr(df, 14)
fee = FEE_RT * LEV
out = []
last = -1
for e in ents:
i = e["i"]
if i <= last or i + 1 >= n:
continue
tp, sl, mb = e["tp"], e["sl"], e["mb"]
exit_p = c[min(i + mb, n - 1)]
j = min(i + mb, n - 1)
for k in range(1, mb + 1):
j = i + k
if j >= n:
j = n - 1
exit_p = c[j]
break
if mode in ("orig", "orig_gap"):
if l[j] <= sl:
exit_p = sl if mode == "orig" else min(sl, o[j])
break
if h[j] >= tp:
exit_p = tp
break
if k == mb:
exit_p = c[j]
else: # exit16
if h[j] >= tp:
exit_p = tp
break
aj = a[j] if np.isfinite(a[j]) else 0.0
if c[j] < sl - buffer * aj:
exit_p = c[j]
break
if k == mb:
exit_p = c[j]
ret = (exit_p - c[i]) / c[i] * LEV - fee
out.append((i, j, ret))
last = j
return out
def daily_equity(df, trades):
"""Equity giornaliera con la convenzione CANONICA honest (_daily_equity su punti
trade-exit). NB: la serie a punti-trade reindexata su IDX ancora il primo valore
al PRIMO trade dentro IDX (bfill), non al capitale portato avanti da prima —
convenzione discutibile ma e' quella di build_everything: per la parita' (e il
confronto col PORT06 canonico) va replicata esattamente."""
from scripts.analysis.honest_improve2 import _daily_equity
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
cap = INIT
eq_ts, eq_v = [], []
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq_ts.append(ts.iloc[j])
eq_v.append(cap)
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def cell_metrics(eq):
dr = eq.pct_change().fillna(0.0)
return metrics(dr), metrics(dr, lo=SPLIT)
def main():
p = PORTFOLIOS["PORT06"]
print("=" * 104)
print(" GATE DIP01 EXIT-16 (close-confirm 0.5 ATR) — grid gap-aware + PORT06")
print(f" OOS da {OOS_DATE} | fee {FEE_RT*100:.2f}%RT x lev{LEV:.0f} | pos {POS}")
print("=" * 104)
print("\n[1] build_everything() canonico (cache)...")
from src.portfolio.sleeves import all_sleeve_equities
eq_base = dict(all_sleeve_equities())
dfs = {a: load_data(a, "1h") for a in ("BTC", "ETH")}
# --- parita' ---
ents = dip_entries(dfs["BTC"], **CANON)
rep = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "orig"))
base = eq_base["DIP01_BTC"]
corr = base.pct_change().fillna(0).corr(rep.pct_change().fillna(0))
rb = (base.iloc[-1] / base.iloc[0] - 1) * 100
rr = (rep.iloc[-1] / rep.iloc[0] - 1) * 100
print(f"\n[1] PARITA' orig vs canonico: corr={corr:.5f} ret {rb:+.0f}% vs {rr:+.0f}%")
if not (corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01)):
print(" >>> PARITA' FALLITA: STOP.")
return
# --- [2] grid gap-aware ---
for asset in ("BTC", "ETH"):
df = dfs[asset]
print(f"\n[2] GRID {asset} — orig GAP-AWARE vs EXIT-16 (train | OOS: ret% e Sharpe)")
print(f" {'cella':<16s}{'tr retO':>9s}{'tr retE':>9s} {'oos retO':>9s}{'oos retE':>9s}"
f" {'oos ShO':>8s}{'oos ShE':>8s} {'ddO':>6s}{'ddE':>6s} esito")
wins_tr = wins_oos = cells = 0
for z in GRID_Z:
for slm in GRID_SL:
for mb in GRID_MB:
ents = dip_entries(df, n=50, z_in=z, sl_atr=slm, max_bars=mb)
eo = daily_equity(df, dip_trades(ents, df, "orig_gap"))
ee = daily_equity(df, dip_trades(ents, df, "exit16"))
fo, oo = cell_metrics(eo)
fe, oe = cell_metrics(ee)
tr_o = fo["ret"] - oo["ret"]; tr_e = fe["ret"] - oe["ret"] # ~train (full-oos, approssimato su ret composti: usare segni)
# train ret esatto: equity al SPLIT
tr_o = (eo.iloc[SPLIT] / eo.iloc[0] - 1) * 100
tr_e = (ee.iloc[SPLIT] / ee.iloc[0] - 1) * 100
cells += 1
w_tr = tr_e >= tr_o
w_oos = oe["ret"] >= oo["ret"]
wins_tr += w_tr
wins_oos += w_oos
tag = ("OK" if (w_tr and w_oos) else "tr-" if w_oos else "oos-" if w_tr else "KO")
print(f" z{z} sl{slm} mb{mb:<3d}{tr_o:>9.0f}{tr_e:>9.0f} "
f"{oo['ret']:>9.0f}{oe['ret']:>9.0f} {oo['sharpe']:>8.2f}{oe['sharpe']:>8.2f}"
f" {fo['dd']:>6.1f}{fe['dd']:>6.1f} {tag}")
print(f" -> EXIT-16 >= orig-gap: train {wins_tr}/{cells}, OOS {wins_oos}/{cells}")
# --- [3] plateau buffer (BTC, cella canonica) ---
print("\n[3] Plateau buffer EXIT-16 (BTC, cella canonica):")
ents = dip_entries(dfs["BTC"], **CANON)
for buf in (0.4, 0.5, 0.75, 1.0):
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16", buffer=buf))
fe, oe = cell_metrics(ee)
print(f" buf {buf:<5}FULL ret {fe['ret']:>+7.0f}% DD {fe['dd']:>5.1f} Sh {fe['sharpe']:>5.2f}"
f" | OOS ret {oe['ret']:>+6.0f}% DD {oe['dd']:>5.1f} Sh {oe['sharpe']:>5.2f}")
# --- [4] gate PORT06 ---
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16"))
members_b = dict(eq_base)
members_e = dict(eq_base)
members_e["DIP01_BTC"] = ee
f_b, o_b = port_metrics(members_b, p)
f_e, o_e = port_metrics(members_e, p)
print("\n" + "=" * 104)
print(f" [4] PORT06 (pesi cap {p.caps}) — DIP01_BTC orig vs EXIT-16")
print("=" * 104)
print(f" {'variante':<10s}{'FULL Sh':>9s}{'FULL DD%':>10s}{'CAGR':>6s} | {'OOS Sh':>7s}{'OOS DD%':>8s}{'CAGR':>6s}")
for nm, (f, o) in (("BASE", (f_b, o_b)), ("EXIT-16", (f_e, o_e))):
print(f" {nm:<10s}{f['sharpe']:>9.2f}{f['dd']:>10.2f}{f['cagr']:>5.0f}% | "
f"{o['sharpe']:>7.2f}{o['dd']:>8.2f}{o['cagr']:>5.0f}%")
oos_ok = o_e["sharpe"] >= o_b["sharpe"] - 0.02 and o_e["dd"] <= o_b["dd"] + 0.20
full_ok = f_e["sharpe"] >= f_b["sharpe"] - 0.02 and f_e["dd"] <= f_b["dd"] + 0.20
promoted = oos_ok and full_ok
print(f"\n GATE: OOS {'OK' if oos_ok else 'KO'} | FULL {'OK' if full_ok else 'KO'}")
print(" VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO <<<"))
if __name__ == "__main__":
main()
@@ -0,0 +1,156 @@
"""GATE PORT06 del candidato index_comp_disp W=168 (ricerca dispersion 2026-06-08).
Edge confermato avversarialmente: fade della componente idiosincratica di BTC verso
l'indice EW, gated da alta dispersione. Config: rel_len=12, z_win=336, z_thr=1.5,
disp_168 >= quantile rolling 0.7 (win 720), TP=1.0*ATR14, SL=1.5*ATR14, max_bars=24.
Domanda del gate (lezione FR01: robusto != migliora-il-portafoglio):
1) correlazione daily col MASTER e con le fade BTC esistenti (e' un diversificatore?)
2) PORT06 BASE (17 sleeve) vs +DISP (18 sleeve) con pesi cap: DeltaSharpe/DeltaDD FULL e OOS.
PROMOSSO solo se decorrela E migliora (o non degrada) l'OOS.
uv run python scripts/analysis/dispersion_edges/gate_index_comp_disp.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.dispersion_lab import features, align_to
from scripts.analysis.explore_lab import get_df, atr
from scripts.analysis.combine_portfolio import _norm, IDX, port_returns, metrics, SPLIT, OOS_DATE
from scripts.analysis.honest_improve2 import _daily_equity
from scripts.portfolios._defs import PORTFOLIOS
from src.portfolio import weighting as W
FEE_RT, LEV, POS, INIT = 0.001, 3.0, 0.15, 1000.0
CFG = dict(rel_len=12, z_win=336, z_thr=1.5, disp_q=0.7, disp_q_win=720,
tp_atr=1.0, sl_atr=1.5, max_bars=24)
def _last_rank(x):
if x.shape[0] < 2:
return np.nan
return float((x[:-1] < x[-1]).mean())
def build_trades(asset="BTC"):
"""Entries CAUSALI + exit intrabar (TP/SL/max_bars) -> [(i, j, ret_netto)]."""
df = get_df(asset, "1h")
F = features()
fa = align_to(F, df)
c, h, l = df["close"].values, df["high"].values, df["low"].values
n = len(c)
a14 = atr(df, 14)
rel = fa[f"rel_{asset}"].values.astype(float)
disp = fa["disp_168"].values.astype(float)
# somma rolling rel su rel_len, z-score causale (mean/std rolling z_win shift 1)
rs = pd.Series(rel).rolling(CFG["rel_len"]).sum()
rmean = rs.rolling(CFG["z_win"]).mean().shift(1)
rstd = rs.rolling(CFG["z_win"]).std().shift(1)
z = ((rs - rmean) / rstd.replace(0, np.nan)).values
dpct = pd.Series(disp).rolling(CFG["disp_q_win"]).apply(_last_rank, raw=True).values
fee = FEE_RT * LEV
out = []
last = -1
for i in range(n - 1):
if i <= last or not np.isfinite(z[i]) or not np.isfinite(dpct[i]):
continue
if dpct[i] < CFG["disp_q"] or abs(z[i]) < CFG["z_thr"]:
continue
ai = a14[i]
if not np.isfinite(ai) or ai <= 0:
continue
d = -1 if z[i] > 0 else 1
tp = c[i] + d * CFG["tp_atr"] * ai
sl = c[i] - d * CFG["sl_atr"] * ai
mb = CFG["max_bars"]
j = min(i + mb, n - 1)
exit_p = c[j]
for k in range(1, mb + 1):
j = i + k
if j >= n:
j = n - 1; exit_p = c[j]; break
if d == 1:
if l[j] <= sl: exit_p = sl; break
if h[j] >= tp: exit_p = tp; break
else:
if h[j] >= sl: exit_p = sl; break
if l[j] <= tp: exit_p = tp; break
if k == mb: exit_p = c[j]
out.append((i, j, (exit_p - c[i]) / c[i] * d * LEV - fee))
last = j
return df, out
def daily_equity(df, trades):
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
cap = INIT; eq_ts, eq_v = [], []
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq_ts.append(ts.iloc[j]); eq_v.append(cap)
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def pmetrics(members, p, extra=None):
ids = list(p.sleeve_ids) + ([extra] if extra else [])
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
if extra:
caps = dict(p.caps); caps["DISP"] = caps.get("DISP", None)
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters={**{i:(p.clusters or {}).get(i,i) for i in p.sleeve_ids},
**({extra:"disp"} if extra else {})},
lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
def main():
p = PORTFOLIOS["PORT06"]
print("=" * 100)
print(" GATE PORT06 — candidato index_comp_disp W=168 (BTC) | famiglia DISP nuova")
print(f" config {CFG} | OOS da {OOS_DATE}")
print("=" * 100)
from src.portfolio.sleeves import all_sleeve_equities
eq_base = dict(all_sleeve_equities())
df, trades = build_trades("BTC")
disp_eq = daily_equity(df, trades)
fr = (disp_eq.iloc[-1] / disp_eq.iloc[0] - 1) * 100
o = disp_eq.iloc[SPLIT:]; ofr = (o.iloc[-1] / o.iloc[0] - 1) * 100
print(f"\n[1] candidato standalone: {len(trades)} trade | FULL {fr:+.0f}% | OOS {ofr:+.0f}%")
# correlazione daily col MASTER e con le fade BTC
dr_cand = disp_eq.pct_change().fillna(0.0)
print("\n[2] correlazione daily col candidato (decorrela?):")
for sid in ["MR01_BTC", "MR02_BTC", "MR07_BTC", "DIP01_BTC"]:
corr = dr_cand.corr(eq_base[sid].pct_change().fillna(0.0))
print(f" {sid:<12} corr {corr:+.3f}")
master_dr = pd.DataFrame({i: eq_base[i].pct_change().fillna(0.0) for i in p.sleeve_ids}).mean(axis=1)
print(f" {'MASTER(EW)':<12} corr {dr_cand.corr(master_dr):+.3f}")
# PORT06 base vs +DISP
f_b, o_b = pmetrics(eq_base, p)
members = dict(eq_base); members["DISP_BTC"] = disp_eq
f_e, o_e = pmetrics(members, p, extra="DISP_BTC")
print("\n[3] PORT06 BASE (17) vs +DISP (18):")
print(f" {'':<10}{'FULL Sh':>9}{'FULL DD%':>10}{'OOS Sh':>9}{'OOS DD%':>9}")
print(f" {'BASE':<10}{f_b['sharpe']:>9.2f}{f_b['dd']:>10.2f}{o_b['sharpe']:>9.2f}{o_b['dd']:>9.2f}")
print(f" {'+DISP':<10}{f_e['sharpe']:>9.2f}{f_e['dd']:>10.2f}{o_e['sharpe']:>9.2f}{o_e['dd']:>9.2f}")
print(f" {'DELTA':<10}{f_e['sharpe']-f_b['sharpe']:>+9.2f}{f_e['dd']-f_b['dd']:>+10.2f}"
f"{o_e['sharpe']-o_b['sharpe']:>+9.2f}{o_e['dd']-o_b['dd']:>+9.2f}")
promoted = (o_e['sharpe'] >= o_b['sharpe'] - 0.02 and o_e['dd'] <= o_b['dd'] + 0.20
and f_e['sharpe'] >= f_b['sharpe'] - 0.02)
print("\n VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO (diluisce, come FR01) <<<"))
if __name__ == "__main__":
main()
@@ -0,0 +1,191 @@
"""FAMIGLIA index_comp_disp (W=24) — dispersion trading REALIZZATO.
Idea: l'indice EW vs le componenti. Quando la dispersione cross-sectional rolling
(disp_24) e' ALTA, le componenti idiosincratiche estreme (rel_A = ret_A - ret_idx)
tendono a RIENTRARE verso l'indice -> fade della componente idiosincratica estrema:
- se rel_A e' molto positivo (A ha sovraperformato l'indice oltre soglia) -> SHORT A
- se rel_A e' molto negativo (A ha sottoperformato l'indice oltre soglia) -> LONG A
condizionato a disp_24 sopra una soglia (regime di alta dispersione).
CAUSALE: la decisione a close[i] usa SOLO feature note a i:
- rel_A[i] = log-ret di A meno log-ret indice nella barra [i-1->i] (nota a close[i])
- disp_24[i] = media rolling 24 della disp cross-sectional fino a i (nota a close[i])
Ingresso eseguibile a close[i]. Niente uso di i+1 nella decisione.
Per rendere le soglie comparabili fra asset/tempo, rel_A si normalizza con la sua
deviazione standard rolling CAUSALE (rolling 168h, shiftata di 1 per non includere i).
disp_24 si normalizza col suo quantile rolling causale (percentile rolling).
Exit: time-stop max_bars (con TP/SL ATR opzionali). Il fade verso l'indice e' un
ritorno alla media -> orizzonte breve.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.dispersion_lab import features, align_to, UNIVERSE # noqa: E402
from scripts.analysis.explore_lab import get_df, evaluate, robust, atr # noqa: E402
W = 24 # finestra famiglia
def _last_rank(x: np.ndarray) -> float:
"""Frazione dei valori (esclusi l'ultimo) strettamente < dell'ultimo. Causale:
l'ultimo elemento e' la barra i, confrontata coi 719 valori precedenti."""
if x.shape[0] < 2:
return np.nan
return float((x[:-1] < x[-1]).mean())
def _causal_signals(asset: str, df: pd.DataFrame, fa: pd.DataFrame):
"""Precalcola (una volta per asset) rel_z, disp_pctl, atr — feature CAUSALI."""
a14 = atr(df, 14)
rel = fa[f"rel_{asset}"].values.astype(float)
disp = fa["disp_24"].values.astype(float)
# z-score CAUSALE di rel: media/std rolling 168h SHIFTATA di 1 (solo barre <= i-1)
rel_s = pd.Series(rel)
rmean = rel_s.rolling(168).mean().shift(1)
rstd = rel_s.rolling(168).std().shift(1)
rel_z = ((rel_s - rmean) / rstd.replace(0, np.nan)).values
# percentile rolling CAUSALE di disp_24 (rank di disp[i] vs i 720 valori fino a i).
# vettoriale via rank rolling: pos dell'ultimo elemento / (win-1).
win = 720
dr = pd.Series(disp).rolling(win).apply(_last_rank, raw=True)
disp_pctl = dr.values
return rel_z, disp_pctl, a14
def build_entries(asset: str, df: pd.DataFrame, fa: pd.DataFrame,
rel_z_thr: float, disp_pctl_thr: float,
max_bars: int, tp_atr: float | None, sl_atr: float | None,
precomp=None) -> list[dict]:
"""Costruisce entries CAUSALI per il fade della componente idiosincratica."""
n = len(df)
c = df["close"].values
if precomp is None:
rel_z, disp_pctl, a14 = _causal_signals(asset, df, fa)
else:
rel_z, disp_pctl, a14 = precomp
entries: list[dict] = []
for i in range(n - 1):
z = rel_z[i]
dp = disp_pctl[i]
if not np.isfinite(z) or not np.isfinite(dp):
continue
if dp < disp_pctl_thr: # solo regime di alta dispersione
continue
if abs(z) < rel_z_thr: # solo componente idio estrema
continue
d = -1 if z > 0 else +1 # FADE: rel alto -> short A; rel basso -> long A
a = a14[i]
if not np.isfinite(a) or a <= 0:
tp = sl = None
else:
tp = c[i] + d * tp_atr * a if tp_atr else None
sl = c[i] - d * sl_atr * a if sl_atr else None
entries.append({"i": i, "d": d, "max_bars": max_bars, "tp": tp, "sl": sl})
return entries
def check_no_lookahead(asset: str, df: pd.DataFrame, fa: pd.DataFrame) -> bool:
"""Perturba i PREZZI dopo un indice T e verifica che le entries con i<=T non cambino.
Qui ricostruiamo la entry-rule su una copia di df/fa col futuro alterato e confrontiamo
le entries (i, d) con i<=T."""
n = len(df)
T = int(n * 0.6)
base = build_entries(asset, df, fa, rel_z_thr=2.0, disp_pctl_thr=0.6,
max_bars=12, tp_atr=None, sl_atr=None)
# perturba i prezzi dopo T: alza del 50% close/high/low/open
df2 = df.copy()
for col in ("open", "high", "low", "close"):
df2.loc[df2.index > T, col] = df2.loc[df2.index > T, col] * 1.5
# perturba anche le feature riferite a barre > T (rel_<asset>, disp_24)
fa2 = fa.copy()
for col in (f"rel_{asset}", "disp_24"):
fa2.loc[fa2.index > T, col] = fa2.loc[fa2.index > T, col] * 1.5
pert = build_entries(asset, df2, fa2, rel_z_thr=2.0, disp_pctl_thr=0.6,
max_bars=12, tp_atr=None, sl_atr=None)
base_le = {(e["i"], e["d"]) for e in base if e["i"] <= T}
pert_le = {(e["i"], e["d"]) for e in pert if e["i"] <= T}
ok = base_le == pert_le
print(f"[no-look-ahead] entries con i<=T={T} invarianti al futuro: "
f"{'OK' if ok else 'VIOLATO'} (base={len(base_le)} pert={len(pert_le)})")
if not ok:
diff = (base_le ^ pert_le)
print(f" differenze: {sorted(diff)[:10]}")
return ok
def main():
F = features()
print(f"feature caricate: {F.shape[0]} barre")
# asset single-asset da esplorare (i piu' liquidi + qualche alt)
assets = ["BTC", "ETH", "SOL", "ADA", "BNB", "DOGE", "LTC", "XRP"]
# griglia piccola di soglie
rel_z_grid = [1.5, 2.0, 2.5]
disp_pctl_grid = [0.5, 0.7]
mb_grid = [6, 12, 24]
# exit: prima senza tp/sl (puro time-stop), poi con un TP/SL ATR moderato
exit_grid = [
(None, None),
(1.5, 2.0),
]
best = None
# no-look-ahead check una volta (su ETH)
df_eth = get_df("ETH", "1h")
fa_eth = align_to(F, df_eth)
la_ok = check_no_lookahead("ETH", df_eth, fa_eth)
print()
for asset in assets:
df = get_df(asset, "1h")
fa = align_to(F, df)
precomp = _causal_signals(asset, df, fa) # una volta per asset (costoso)
for rz in rel_z_grid:
for dp in disp_pctl_grid:
for mb in mb_grid:
for (tp_a, sl_a) in exit_grid:
ents = build_entries(asset, df, fa, rz, dp, mb, tp_a, sl_a,
precomp=precomp)
if len(ents) < 30:
continue
tag = f"{asset} rz{rz} dp{dp} mb{mb} tp{tp_a} sl{sl_a}"
res = evaluate(tag, ents, df)
rb = robust(res)
# criterio "migliore": OOS ret, poi sharpe; preferisci robuste
score = (rb, res["oos"]["ret"], res["full"]["sharpe"])
if best is None or score > best[0]:
best = (score, tag, res, rb,
dict(asset=asset, rz=rz, dp=dp, mb=mb, tp=tp_a, sl=sl_a))
print("\n=== MIGLIORE ===")
if best is None:
print("nessuna cella con abbastanza trade")
return
score, tag, res, rb, cfg = best
print(f"config: {tag}")
print(f"robust={rb} lookahead_ok={la_ok}")
print(f"FULL ret={res['full']['ret']:+.0f}% OOS ret={res['oos']['ret']:+.0f}% "
f"DD={res['full']['dd']:.0f}% Sharpe={res['full']['sharpe']:.2f}")
print(f"fee0.2% OOS={res['sweep_oos'][0.002]:+.0f}% anniPos={res['pos_yrs']}/{res['n_yrs']}")
if __name__ == "__main__":
main()
@@ -0,0 +1,152 @@
"""rel_idio_fade (W=24): fade della componente idiosincratica rel_A vs indice.
Idea: rel_A = ret(A) - ret(indice EW) e' il rendimento idiosincratico (residuo di
mercato). Quando l'asset diverge troppo dall'indice (z-score di rel_A su finestra
W=24 elevato), si fada il residuo verso l'indice: se A ha sovraperformato troppo
(z alto) -> SHORT A; se ha sottoperformato (z basso) -> LONG A. Mean-reversion del
residuo.
ENTRY CAUSALE: la decisione a close[i] usa SOLO rel_A fino a i incluso. Lo z-score
e' costruito con media/std rolling su [i-W+1 .. i] (causale). Ingresso eseguibile a
close[i]; exit a tempo (max_bars), opzionale TP/SL ad ATR.
Esegui: cd /opt/docker/PythagorasGoal && PYTHONPATH=. uv run python \
scripts/analysis/_disp_scratch/rel_idio_fade_24.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.dispersion_lab import features, align_to, UNIVERSE # noqa: E402
from scripts.analysis.explore_lab import get_df, evaluate, robust, atr # noqa: E402
W = 24 # finestra correlazione/dispersione (richiesta dalla famiglia)
ASSETS = ["BTC", "ETH", "ADA", "BNB", "DOGE", "LTC", "SOL", "XRP"]
Z_GRID = [1.5, 2.0, 2.5, 3.0]
MB_GRID = [12, 24, 48]
TP_ATR = None # exit a tempo puro per il primo screening
SL_ATR = None
def build_entries(asset: str, df: pd.DataFrame, fa: pd.DataFrame,
z_thr: float, max_bars: int,
tp_atr=None, sl_atr=None) -> list[dict]:
"""Entries CAUSALI per il fade del residuo idiosincratico.
z[i] = (rel[i] - mean(rel[i-W+1..i])) / std(rel[i-W+1..i]) -> usa solo dati <= i.
rel[i] e' gia' causale (deriva da log-ret fino a close[i]). Quando |z[i]|>=thr:
z>0 (A ha sovraperformato l'indice) -> SHORT (d=-1), fade verso l'indice
z<0 (A ha sottoperformato) -> LONG (d=+1)
"""
rel = fa[f"rel_{asset}"].values.astype(float)
s = pd.Series(rel)
mu = s.rolling(W).mean().values
sd = s.rolling(W).std(ddof=0).values
z = (rel - mu) / np.where(sd > 0, sd, np.nan)
a = atr(df, 14)
c = df["close"].values
n = len(c)
entries: list[dict] = []
for i in range(W, n - 1):
zi = z[i]
if not np.isfinite(zi) or abs(zi) < z_thr:
continue
d = -1 if zi > 0 else 1 # fade del residuo
e = {"i": i, "d": d, "max_bars": max_bars}
if tp_atr is not None and np.isfinite(a[i]):
e["tp"] = c[i] + d * tp_atr * a[i] # TP nella direzione del fade
if sl_atr is not None and np.isfinite(a[i]):
e["sl"] = c[i] - d * sl_atr * a[i]
entries.append(e)
return entries
def check_no_lookahead(asset: str, df: pd.DataFrame, fa: pd.DataFrame,
z_thr: float, max_bars: int) -> bool:
"""Perturba i prezzi DOPO un indice T e verifica che le entries con i<=T non
cambino (la entry-rule usa solo dati <= close[i])."""
rel = fa[f"rel_{asset}"].values.astype(float)
n = len(rel)
T = int(n * 0.6)
def z_of(relv):
s = pd.Series(relv)
mu = s.rolling(W).mean().values
sd = s.rolling(W).std(ddof=0).values
return (relv - mu) / np.where(sd > 0, sd, np.nan)
z0 = z_of(rel)
rel2 = rel.copy()
rel2[T + 1:] = rel2[T + 1:] + 0.05 # shock del futuro
z2 = z_of(rel2)
def ents_from(z):
out = []
for i in range(W, n - 1):
if i > T:
break
zi = z[i]
if np.isfinite(zi) and abs(zi) >= z_thr:
out.append((i, -1 if zi > 0 else 1))
return out
ok = ents_from(z0) == ents_from(z2)
print(f" [no-look-ahead {asset}] entries i<=T={T} invarianti al futuro: "
f"{'OK' if ok else 'VIOLATO'}")
return ok
def main():
F = features()
print(f"feature caricate: {F.shape[0]} barre, {F.shape[1]} colonne")
best = None
look_ok_all = True
for asset in ASSETS:
df = get_df(asset, "1h")
fa = align_to(F, df)
# un check no-look-ahead per asset (config centrale)
look_ok_all &= check_no_lookahead(asset, df, fa, z_thr=2.0, max_bars=24)
print(f"--- {asset} ---")
for z_thr in Z_GRID:
for mb in MB_GRID:
ents = build_entries(asset, df, fa, z_thr, mb, TP_ATR, SL_ATR)
if len(ents) < 30:
continue
name = f"{asset} z{z_thr} mb{mb}"
res = evaluate(name, ents, df)
rb = robust(res)
score = res["oos"]["ret"] + res["full"]["ret"]
cand = {
"asset": asset, "z": z_thr, "mb": mb,
"full": res["full"]["ret"], "oos": res["oos"]["ret"],
"fee02_oos": res["sweep_oos"][0.002],
"dd": res["full"]["dd"], "sharpe": res["full"]["sharpe"],
"pos_yrs": res["pos_yrs"], "n_yrs": res["n_yrs"],
"robust": rb, "score": score, "trades": res["full"]["trades"],
}
# preferisci robuste; a parita' di robustezza, score piu' alto
if best is None or (cand["robust"], cand["score"]) > (best["robust"], best["score"]):
best = cand
print("\n=== CELLA MIGLIORE ===")
if best:
print(f" asset={best['asset']} z={best['z']} mb={best['mb']} trades={best['trades']}")
print(f" FULL={best['full']:+.0f}% OOS={best['oos']:+.0f}% "
f"fee0.2%OOS={best['fee02_oos']:+.0f}% DD={best['dd']:.0f}% "
f"Sharpe={best['sharpe']:.2f} anniPos={best['pos_yrs']}/{best['n_yrs']} "
f"robust={best['robust']}")
print(f" no-look-ahead tutti gli asset: {'OK' if look_ok_all else 'VIOLATO'}")
return best
if __name__ == "__main__":
main()
+165
View File
@@ -0,0 +1,165 @@
"""Harness CONDIVISO per la ricerca dispersion/correlation index (crypto).
Feature CAUSALI (dalle sole close, nessun feed opzioni — la dispersion IMPLICITA
non e' backtestabile, muro ARGO/GEX documentato). Calcolate sull'universo comune
e allineabili a ogni singolo asset. Tutte note a close[i] (nessun look-ahead):
- avg_corr[W] : correlazione media a coppie dei log-rendimenti, rolling W (causale)
- disp[W] : dispersione cross-sectional (std cross-asset del rendimento di barra),
media rolling W
- idx_ret : rendimento dell'"indice" equal-weight (proxy mercato)
- beta_<A>[W] : beta rolling dell'asset A vs indice
- rel_<A> : rendimento di A meno rendimento indice (componente idiosincratica)
Uso dagli agenti di ricerca:
from scripts.analysis.dispersion_lab import features, align_to, UNIVERSE, COMMON_START
from scripts.analysis.explore_lab import get_df, evaluate, robust
F = features() # DataFrame indicizzato per timestamp(ms)
df = get_df("ETH", "1h")
fa = align_to(F, df) # feature riallineate alle barre di df (ffill causale)
# ... costruisci entries causali (entry decisa con dati <= close[i]) ...
res = evaluate("nome", entries, df); robust(res)
Check no-look-ahead: `python -m scripts.analysis.dispersion_lab` (perturba il futuro
e verifica che le feature fino a T non cambino).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.explore_lab import get_df # noqa: E402
UNIVERSE = ["ADA", "BNB", "BTC", "DOGE", "ETH", "LTC", "SOL", "XRP"]
COMMON_START = "2022-07-22" # ultimo asset a entrare (LTC) -> universo completo
WINDOWS = [24, 72, 168, 336] # 1g, 3g, 1sett, 2sett in barre 1h
_CACHE: dict | None = None
def _panel():
"""{asset: close-series} allineato sui timestamp comuni dell'universo (1h)."""
frames = {}
for a in UNIVERSE:
d = get_df(a, "1h")
frames[a] = pd.Series(d["close"].values, index=d["timestamp"].values, name=a)
P = pd.concat(frames, axis=1).dropna()
P = P[P.index >= int(pd.Timestamp(COMMON_START, tz="UTC").timestamp() * 1000)]
return P
def _avg_pairwise_corr(R: np.ndarray, win: int) -> np.ndarray:
"""Media delle correlazioni a coppie dei log-rendimenti su finestra rolling.
CAUSALE: la riga i usa R[i-win+1 .. i]. Vettoriale via media/var rolling delle
scommatorie (corr di Pearson per coppia, poi media off-diagonale)."""
n, m = R.shape
out = np.full(n, np.nan)
# somme rolling per asset
df = pd.DataFrame(R)
s = df.rolling(win).sum().values # Σx
ss = (df * df).rolling(win).sum().values # Σx²
for i in range(win - 1, n):
w = R[i - win + 1:i + 1] # (win, m)
mean = s[i] / win
var = ss[i] / win - mean * mean
sd = np.sqrt(np.clip(var, 1e-18, None))
# matrice di covarianza della finestra
cov = (w.T @ w) / win - np.outer(mean, mean)
corr = cov / np.outer(sd, sd)
iu = np.triu_indices(m, k=1)
vals = corr[iu]
vals = vals[np.isfinite(vals)]
if vals.size:
out[i] = float(np.mean(vals))
return out
_CACHE_FILE = PROJECT_ROOT / "data" / "regime" / "dispersion_features.parquet"
def features(use_disk: bool = True) -> pd.DataFrame:
"""DataFrame indicizzato per timestamp(ms) con le feature causali. Cache di
processo + cache su disco (i molti agenti di ricerca la caricano invece di
ricalcolarla; la corr rolling e' costosa)."""
global _CACHE
if _CACHE is not None:
return _CACHE
if use_disk and _CACHE_FILE.exists():
_CACHE = pd.read_parquet(_CACHE_FILE)
return _CACHE
P = _panel()
logp = np.log(P.values)
R = np.vstack([np.zeros((1, P.shape[1])), np.diff(logp, axis=0)]) # log-ret per barra
R[0] = 0.0
idx_ret = R.mean(axis=1) # indice EW
out = pd.DataFrame(index=P.index)
out["idx_ret"] = idx_ret
# dispersione cross-sectional (std cross-asset del rendimento di barra) + medie rolling
xs = R.std(axis=1)
out["disp_bar"] = xs
for w in WINDOWS:
out[f"avg_corr_{w}"] = _avg_pairwise_corr(R, w)
out[f"disp_{w}"] = pd.Series(xs, index=P.index).rolling(w).mean().values
# componente idiosincratica e beta rolling vs indice (per ogni asset)
ir = pd.Series(idx_ret, index=P.index)
for k, a in enumerate(UNIVERSE):
ra = pd.Series(R[:, k], index=P.index)
out[f"rel_{a}"] = (ra - ir).values
for w in (72, 168):
cov = ra.rolling(w).cov(ir)
var = ir.rolling(w).var()
out[f"beta_{a}_{w}"] = (cov / var.replace(0, np.nan)).values
if use_disk:
_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
out.to_parquet(_CACHE_FILE)
_CACHE = out
return out
def align_to(F: pd.DataFrame, df: pd.DataFrame) -> pd.DataFrame:
"""Riallinea le feature (indicizzate per ts comuni) alle barre di `df` (un asset),
con ffill CAUSALE (riempie in avanti: la feature a i usa l'ultima nota <= ts[i])."""
f = F.reindex(F.index.union(df["timestamp"].values)).sort_index().ffill()
return f.reindex(df["timestamp"].values).reset_index(drop=True)
def _check_no_lookahead() -> bool:
"""Perturba il FUTURO dei prezzi e verifica che le feature fino a T non cambino."""
global _CACHE
_CACHE = None
F0 = features().copy()
P = _panel()
T = int(len(P) * 0.6)
# perturbo le close DOPO T per tutti gli asset
P2 = P.copy()
P2.iloc[T + 1:] = P2.iloc[T + 1:] * 1.5
# ricostruisco le feature da P2 inline (stessa logica)
_CACHE = None
saved = globals()["_panel"]
globals()["_panel"] = lambda: P2
_CACHE = None
F1 = features()
globals()["_panel"] = saved
_CACHE = None
cols = [c for c in F0.columns if c.startswith(("avg_corr", "disp", "beta"))]
a = F0[cols].iloc[:T - max(WINDOWS)].values
b = F1[cols].iloc[:T - max(WINDOWS)].values
ok = np.allclose(np.nan_to_num(a), np.nan_to_num(b), atol=1e-9)
print(f"[no-look-ahead] feature fino a T={T} invarianti al futuro: {'OK' if ok else 'VIOLATO'}")
return ok
if __name__ == "__main__":
F = features()
P = _panel()
print(f"universo {UNIVERSE}")
print(f"finestra comune: {pd.to_datetime(P.index[0], unit='ms', utc=True).date()} "
f"-> {pd.to_datetime(P.index[-1], unit='ms', utc=True).date()} ({len(P)} barre)")
print(f"feature: {list(F.columns)}")
print(F[[f'avg_corr_{w}' for w in WINDOWS]].describe().round(3).to_string())
_check_no_lookahead()
+106
View File
@@ -0,0 +1,106 @@
"""Drift monitor per-famiglia — il rolling-return corrente di ogni famiglia vs la
DISTRIBUZIONE STORICA dei propri rolling-return (stessa finestra, storia 2021+).
Non è un filtro di trading: è OSSERVABILITÀ (la protezione giusta contro il drift è
accorgersene presto, non ritoccare i parametri — lezione 2026-06-11: le FADE al 2°
percentile sul 120g sono state trovate a mano; questo script lo rende ripetibile).
Percentile basso = la famiglia sta attraversando uno dei suoi tratti peggiori:
- sotto P_WARN (5%): segnalato — coerente con la coda storica, OSSERVARE;
- il PORT06 complessivo sotto P_WARN è più serio (la diversificazione non copre).
Equity dal builder canonico (all_sleeve_equities → parità coi gate).
uv run python scripts/analysis/drift_monitor.py # stampa
uv run python scripts/analysis/drift_monitor.py --telegram # + invio Telegram
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import port_returns
from scripts.portfolios._defs import PORTFOLIOS
from src.portfolio.sleeves import all_sleeve_equities
from src.portfolio import weighting as W
WINDOWS = (60, 120) # giorni
P_WARN = 5.0 # percentile sotto cui segnalare
def family_returns():
"""Rendimenti daily per famiglia (equal-weight intra-famiglia) + PORT06 (pesi cap)."""
p = PORTFOLIOS["PORT06"]
eq = dict(all_sleeve_equities())
ids = list(p.sleeve_ids)
fams: dict[str, list] = {}
for i in ids:
fams.setdefault(W.family_of(i), []).append(i)
out = {}
for f, members in sorted(fams.items()):
out[f] = port_returns({i: eq[i] for i in members},
{i: 1 / len(members) for i in members})
dr = pd.DataFrame({i: eq[i].pct_change().fillna(0.0) for i in ids})
w = W.weight_vector("cap", ids, dr, caps=p.caps, clusters=p.clusters)
out["PORT06"] = port_returns({i: eq[i] for i in ids}, w)
return out
def drift_rows():
rows = []
for name, r in family_returns().items():
for win in WINDOWS:
# vettoriale (log1p+rolling sum) invece di apply(np.prod): identico
# numericamente, ~100x piu' veloce del callback Python per-finestra
roll = np.expm1(np.log1p(r).rolling(win).sum())
roll = roll.dropna()
if len(roll) < 100:
continue
cur = float(roll.iloc[-1])
pct = float((roll < cur).mean() * 100)
rows.append(dict(name=name, win=win, cur=cur * 100, pct=pct,
p5=float(roll.quantile(0.05) * 100),
med=float(roll.median() * 100)))
return rows
def build_report(rows) -> tuple[str, bool]:
warn = [r for r in rows if r["pct"] < P_WARN]
L = ["📉 <b>Drift monitor</b> — rolling-return vs storia propria (2021+)"]
L.append("<pre>" + f"{'famiglia':<9}{'win':>5}{'corr%':>8}{'pct':>6}{'p5%':>8}{'med%':>7}")
for r in rows:
flag = " ⚠️" if r["pct"] < P_WARN else ""
L.append(f"{r['name']:<9}{r['win']:>4}g{r['cur']:>+8.1f}{r['pct']:>5.0f}%"
f"{r['p5']:>+8.1f}{r['med']:>+7.1f}{flag}")
L.append("</pre>")
if warn:
names = ", ".join(f"{r['name']} {r['win']}g (p{r['pct']:.0f})" for r in warn)
L.append(f"⚠️ sotto il p{P_WARN:.0f} storico: {names} — coda storica della famiglia: "
"OSSERVARE, non ritoccare i parametri (drift ≠ rottura; "
"vedi docs/diary/2026-06-11-stability-sweep.md)")
else:
L.append(f"✅ tutte le famiglie sopra il p{P_WARN:.0f} storico")
return "\n".join(L), bool(warn)
def main():
rows = drift_rows()
report, warned = build_report(rows)
import re
print(re.sub(r"</?(b|pre)>", "", report))
if "--telegram" in sys.argv:
from src.live.telegram_notifier import send_telegram
ok = send_telegram(report)
print(f"[telegram] inviato: {ok}")
return warned
if __name__ == "__main__":
# exit code 1 su warn: utilizzabile da cron/script come canale d'allarme
# (coerente con reconcile_account; prima il bool era calcolato e buttato via)
sys.exit(1 if main() else 0)
+126
View File
@@ -0,0 +1,126 @@
"""Gate del CATASTROPHE-CAP auto-finanziato (collar standing) sullo sleeve ETH no-SL.
Tesi: lo sleeve ETH no-SL ha la coda da crash (un long-fade puo' perdere -50/-65% in un
gap). Un COLLAR standing rollato mensilmente — put lunga ~13% OTM finanziata da call corta
~10% OTM — cappa quella coda a premio netto ~zero (validato sui premi REALI di cerbero-bite:
put -13%≈1.0%/m IV55, call +10%≈1.05%/m IV49). Pricing BS calibrato sul reale: skew_put 1.12,
skew_call 1.0. Caveat: il collar aggiunge delta SHORT-ETH con dead-zone -p/+c -> cappa anche
l'upside; nei mesi tranquilli (ETH dentro la banda) costa ~zero. Il gate dice se aiuta netto.
uv run python scripts/analysis/eth_collar_gate.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.explore_lab import get_df
from scripts.analysis.combine_portfolio import IDX, SPLIT, OOS_DATE, metrics, port_returns, _norm
from scripts.analysis.option_overlay_lab import dvol_for, bs_put, bs_call
from scripts.analysis.mr02eth_port06_gate import (
gen_donchian_base, build_trades, build_trades_exit16, daily_equity, port_metrics, CAPS)
from src.portfolio.sleeves import all_sleeve_equities
HY = 24 * 365.0
def collar_daily_returns(df, dvol, p_otm=0.13, c_otm=0.10, skew_put=1.12, skew_call=1.0,
roll_h=24 * 30) -> pd.Series:
"""Collar standing rollato ogni roll_h ore. Ritorna la SERIE di rendimenti GIORNALIERI
(frazione del notional collar): MTM = d(intrinseco) - theta (premio netto amortizzato)."""
c = df["close"].values; ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
n = len(c)
val = np.zeros(n) # valore collar (frac notional) marcato a intrinseco - premio residuo
T = roll_h / HY
k = 0
while k < n - 1:
S0 = c[k]; sig = dvol[k] if not np.isnan(dvol[k]) else 0.6
Kp = S0 * (1 - p_otm); Kc = S0 * (1 + c_otm)
prem = bs_put(S0, Kp, T, sig * skew_put) / S0 - bs_call(S0, Kc, T, sig * skew_call) / S0
end = min(k + roll_h, n - 1)
span = end - k
for j in range(k, end + 1):
intr = (max(Kp - c[j], 0.0) - max(c[j] - Kc, 0.0)) / S0
frac_elapsed = (j - k) / span if span else 1.0
val[j] = intr - prem * (1 - frac_elapsed) # premio pagato up-front, amortizzato a 0 a scadenza
k = end
s = pd.Series(val, index=ts)
daily = s.resample("1D").last().reindex(IDX).ffill().bfill()
return daily.diff().fillna(0.0) # rendimento giornaliero (frac notional)
def combine(fade_eq: pd.Series, collar_dr: pd.Series, hedge_frac: float) -> pd.Series:
"""sleeve = fade no-SL + hedge_frac * collar. Combina i rendimenti giornalieri."""
fr = fade_eq.pct_change().fillna(0.0)
return _norm((1 + fr + hedge_frac * collar_dr).cumprod())
def crash_audit(df, dvol, p_otm, c_otm, hedge_frac):
"""P&L del collar nei mesi di crollo ETH peggiori (frac notional)."""
cr = collar_daily_returns(df, dvol, p_otm=p_otm, c_otm=c_otm)
# ETH daily ret mensile
cdf = pd.Series(df["close"].values, index=pd.to_datetime(df["timestamp"], unit="ms", utc=True)).resample("1D").last().reindex(IDX).ffill()
mret = cdf.resample("30D").last().pct_change()
collar_m = (1 + cr).resample("30D").apply(lambda x: x.prod()) - 1
worst = mret.nsmallest(5)
print(f" {'mese (fine)':>12}{'ETH 30g%':>10}{'collar P&L%':>13}")
for t, r in worst.items():
cm = collar_m.reindex([t], method="nearest").iloc[0] * hedge_frac * 100
print(f" {str(t.date()):>12}{r*100:>9.0f}%{cm:>12.1f}%")
def main():
print("=" * 96)
print(f" GATE collar standing (catastrophe-cap) sullo sleeve ETH no-SL | OOS da {OOS_DATE}")
print("=" * 96)
df = get_df("ETH", "1h"); dvol = dvol_for(df, "ETH")
eq = dict(all_sleeve_equities())
ids = [k for k in eq if k in {"MR01_BTC","MR02_BTC","MR07_BTC","MR01_ETH","MR02_ETH","MR07_ETH",
"DIP01_BTC","TR01_basket","ROT02_rot","PR_ETHBTC","PR_LTCETH","PR_ADAETH","PR_BTCLTC","PR_ETHSOL","TSM01","SH_BTC","SH_ETH"}]
base_ents = gen_donchian_base(df, n=20, sl_atr=2.0, trend_max=3.0)
nosl_ents = gen_donchian_base(df, n=20, sl_atr=2.0, trend_max=3.0, use_sl=False)
def pm(ce):
m = dict(eq); m["MR02_ETH"] = ce; return port_metrics(m, ids)
f_l, o_l = pm(daily_equity(build_trades_exit16(base_ents, df, sl_confirm=0.5), df))
fade_nosl = daily_equity(build_trades(nosl_ents, df), df)
f0, o0 = pm(fade_nosl)
print(f"\n {'sleeve ETH':<30s}{'FULL Sh':>8s}{'FULL DD':>8s} |{'OOS Sh':>8s}{'OOS DD':>8s}")
print(" " + "-" * 78)
print(f" {'LIVE EXIT-16 (rif)':<30s}{f_l['sharpe']:>8.2f}{f_l['dd']:>8.2f} |{o_l['sharpe']:>8.2f}{o_l['dd']:>8.2f}")
print(f" {'no-SL nudo':<30s}{f0['sharpe']:>8.2f}{f0['dd']:>8.2f} |{o0['sharpe']:>8.2f}{o0['dd']:>8.2f}")
configs = [
("put13/call10 hf0.45", 0.13, 0.10, 0.45),
("put13/call10 hf0.30", 0.13, 0.10, 0.30),
("put15/call12 hf0.45", 0.15, 0.12, 0.45),
("put20/call15 hf0.45", 0.20, 0.15, 0.45),
("put13/call08 hf0.45", 0.13, 0.08, 0.45),
]
rows = []
for name, p, cc, hf in configs:
cr = collar_daily_returns(df, dvol, p_otm=p, c_otm=cc)
ce = combine(fade_nosl, cr, hf)
f_c, o_c = pm(ce)
rows.append((name, p, cc, hf, f_c, o_c))
print(f" {'no-SL + '+name:<30s}{f_c['sharpe']:>8.2f}{f_c['dd']:>8.2f} |{o_c['sharpe']:>8.2f}{o_c['dd']:>8.2f}")
print("\n " + "=" * 90)
print(f" vs LIVE EXIT-16 (FULL {f_l['sharpe']:.2f}/{f_l['dd']:.2f} OOS {o_l['sharpe']:.2f}/{o_l['dd']:.2f}) e vs no-SL")
print(" " + "-" * 90)
for name, p, cc, hf, f_c, o_c in rows:
print(f" {name:<22s} Δ vsEXIT16 FULL {f_c['sharpe']-f_l['sharpe']:+.2f}/{f_c['dd']-f_l['dd']:+.2f} "
f"OOS {o_c['sharpe']-o_l['sharpe']:+.2f}/{o_c['dd']-o_l['dd']:+.2f} | "
f"Δ vsNoSL DD {f_c['dd']-f0['dd']:+.2f}")
print("\n --- audit crash: P&L collar (hf-scaled) nei 5 mesi ETH peggiori (put13/call10 hf0.45) ---")
crash_audit(df, dvol, 0.13, 0.10, 0.45)
if __name__ == "__main__":
main()
@@ -0,0 +1,143 @@
"""TEST DECISIVO: impatto di EXIT-16 (close_confirm_sl, buffer=0.5 ATR) sul PORT06,
nel PATH CANONICO del backtest (NON exit_lab).
EXIT-16: lo SL intrabar e' DISATTIVATO; si esce al close del bar j solo se il close
ha sfondato il livello di buffer*ATR14:
long (d=1): esci a close[j] se close[j] < sl0 - 0.5*atr14[j]
short (d=-1): esci a close[j] se close[j] > sl0 + 0.5*atr14[j]
TP intrabar al livello e max_bars al close restano INVARIATI.
Metodo (come fu fatto per il loss-guard Hurst):
1. build_everything() canonico -> equity giornaliere di TUTTI gli sleeve (cache intatta).
2. ricostruisco le 6 equity fade in variante EXIT-16 replicando ESATTAMENTE
fade_daily_equity/build_trades (stessi segnali fn(df,**params), trend_max=3.0,
fee 0.10%RT*lev3, pos 0.15, compounding, non-overlap), cambiando SOLO il ramo SL.
3. PARITA': con la SL-rule originale il replay deve riprodurre le equity canoniche.
4. PORT06 base vs EXIT-16 con la STESSA matematica dei pesi (Portfolio.backtest):
weighting cap, caps PAIRS 0.33, ribilancio 1D, metriche FULL e OOS.
NB: la leva 2x del portfolios.yml NON entra nel backtest (Portfolio.backtest la ignora;
e' un knob live). Le equity fade gia' includono lev=3 dentro build_trades.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.risk_management import strats_for
from scripts.analysis.combine_portfolio import OOS_DATE
from scripts.analysis._port06_gate_common import (
build_trades_variant, equity_from_trades, port_metrics, dd as _dd,
)
from scripts.portfolios._defs import PORTFOLIOS
def fade_equity_variant(asset, fn, params, mode):
"""Stesso flusso di combine_portfolio.fade_daily_equity ma con build_trades_variant."""
df = load_data(asset, "1h")
trades = build_trades_variant(fn(df, **params), df, mode=mode, trend_max=3.0)
return equity_from_trades(df, trades)
def main():
p = PORTFOLIOS["PORT06"]
fade_ids = [s.sid for s in p.sleeves if s.sid.startswith("MR")]
print("=" * 96)
print(" TEST DECISIVO EXIT-16 (close_confirm_sl buffer=0.5 ATR) su PORT06 — path canonico")
print(f" fade sleeve: {fade_ids}")
print("=" * 96)
# --- 1. equity canoniche di TUTTI gli sleeve (cache intatta) ---
print("\n[1] build_everything() canonico (pesante, ~2-3 min)...")
from src.portfolio.sleeves import all_sleeve_equities
eq_base = dict(all_sleeve_equities()) # {sid: equity giornaliera}
print(f" sleeve totali: {len(eq_base)}")
# --- 2. PARITA': replay 'orig' deve riprodurre le equity canoniche ---
print("\n[2] PARITA' replay (mode=orig) vs canonico (fade_daily_equity):")
print(f" {'sleeve':<10s}{'corr':>10s}{'ret_canon%':>14s}{'ret_replay%':>14s}{'diff%':>9s}")
parity_ok = True
eq_orig, eq_e16 = {}, {}
for asset in ("BTC", "ETH"):
for nm, (fn, params) in strats_for(asset).items():
sid = f"{nm}_{asset}"
if sid not in fade_ids:
continue
eq_orig[sid] = fade_equity_variant(asset, fn, params, mode="orig")
eq_e16[sid] = fade_equity_variant(asset, fn, params, mode="exit16")
base = eq_base[sid]
rep = eq_orig[sid]
corr = base.pct_change().fillna(0).corr(rep.pct_change().fillna(0))
rb = (base.iloc[-1] / base.iloc[0] - 1) * 100
rr = (rep.iloc[-1] / rep.iloc[0] - 1) * 100
diff = rr - rb
flag = "" if (corr > 0.999 and abs(diff) <= max(1.0, abs(rb) * 0.01)) else " <-- MISMATCH"
if flag:
parity_ok = False
print(f" {sid:<10s}{corr:>10.5f}{rb:>14.1f}{rr:>14.1f}{diff:>+9.2f}{flag}")
print(f"\n PARITA' {'OK' if parity_ok else 'FALLITA'} "
f"(corr>0.999 e ret finale entro 1%).")
if not parity_ok:
print("\n >>> Parita' non raggiunta: NON forzo. Diagnostico sopra. STOP.")
return
# --- 3. PORT06 base vs EXIT-16: stessi pesi cap, stessa matematica ---
members_base = dict(eq_base)
members_e16 = dict(eq_base)
for sid in fade_ids:
members_e16[sid] = eq_e16[sid] # sostituisco SOLO le 6 colonne fade
# pesi cap canonici (gli stessi che usa Portfolio.backtest) dentro port_metrics
f_b, o_b = port_metrics(members_base, p)
f_e, o_e = port_metrics(members_e16, p)
print("\n" + "=" * 96)
print(f" [3] PORT06 — pesi={p.weighting} caps={p.caps} | OOS da {OOS_DATE} | leva3x interna fade, pos0.15")
print("=" * 96)
print(f" {'variante':<14s}{'FULL Sh':>9s}{'FULL DD%':>10s}{'FULL CAGR':>11s}"
f" | {'OOS Sh':>8s}{'OOS DD%':>9s}{'OOS CAGR':>10s}")
print(" " + "-" * 90)
print(f" {'BASE':<14s}{f_b['sharpe']:>9.2f}{f_b['dd']:>10.2f}{f_b['cagr']:>10.0f}%"
f" | {o_b['sharpe']:>8.2f}{o_b['dd']:>9.2f}{o_b['cagr']:>9.0f}%")
print(f" {'EXIT-16':<14s}{f_e['sharpe']:>9.2f}{f_e['dd']:>10.2f}{f_e['cagr']:>10.0f}%"
f" | {o_e['sharpe']:>8.2f}{o_e['dd']:>9.2f}{o_e['cagr']:>9.0f}%")
print(" " + "-" * 90)
print(f" {'DELTA':<14s}{f_e['sharpe']-f_b['sharpe']:>+9.2f}{f_e['dd']-f_b['dd']:>+10.2f}"
f"{f_e['cagr']-f_b['cagr']:>+10.0f}% | {o_e['sharpe']-o_b['sharpe']:>+8.2f}"
f"{o_e['dd']-o_b['dd']:>+9.2f}{o_e['cagr']-o_b['cagr']:>+9.0f}%")
# --- per-sleeve fade: differenze principali ---
print("\n Per-sleeve fade (equity FULL ret%, EXIT-16 vs orig-replay):")
print(f" {'sleeve':<10s}{'orig ret%':>12s}{'exit16 ret%':>14s}{'delta%':>10s}"
f"{'orig DD%':>10s}{'e16 DD%':>10s}")
for sid in fade_ids:
ro = eq_orig[sid]; re = eq_e16[sid]
rro = (ro.iloc[-1] / ro.iloc[0] - 1) * 100
rre = (re.iloc[-1] / re.iloc[0] - 1) * 100
print(f" {sid:<10s}{rro:>12.1f}{rre:>14.1f}{rre-rro:>+10.1f}"
f"{_dd(ro):>10.1f}{_dd(re):>10.1f}")
# --- GATE ---
print("\n" + "=" * 96)
print(" GATE (stesso del loss-guard): PROMOSSO se OOS Sharpe migliora/pari E DD non peggiora")
print(" materialmente, E in FULL non degrada.")
print("=" * 96)
oos_sh_ok = o_e['sharpe'] >= o_b['sharpe'] - 0.02
oos_dd_ok = o_e['dd'] <= o_b['dd'] + 0.20 # no peggioramento materiale DD
full_ok = f_e['sharpe'] >= f_b['sharpe'] - 0.02 and f_e['dd'] <= f_b['dd'] + 0.20
promoted = oos_sh_ok and oos_dd_ok and full_ok
print(f" OOS Sharpe {o_b['sharpe']:.2f} -> {o_e['sharpe']:.2f} "
f"({'OK' if oos_sh_ok else 'KO'})")
print(f" OOS DD% {o_b['dd']:.2f} -> {o_e['dd']:.2f} "
f"({'OK' if oos_dd_ok else 'KO'})")
print(f" FULL Sharpe {f_b['sharpe']:.2f} -> {f_e['sharpe']:.2f} | "
f"FULL DD {f_b['dd']:.2f} -> {f_e['dd']:.2f} ({'OK' if full_ok else 'KO'})")
print("\n VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO <<<"))
if __name__ == "__main__":
main()
+259
View File
@@ -0,0 +1,259 @@
"""EXIT LAB — harness onesto e CONDIVISO per la ricerca di policy di uscita
(TP dinamico, SL dinamico/trailing, partial, ride) sulle fade attive.
Ricerca 2026-06-04 (>=20 agenti): ogni agente implementa una ExitPolicy in
scripts/analysis/exit_policies/<id>_<nome>.py e la valuta QUI, sugli STESSI
segnali (cache su disco) e con lo stesso engine intrabar di fade_base.
CONTRATTO ANTI-LOOK-AHEAD (vincolante, verra' verificato da agenti avversari):
- i livelli attivi nel bar j (`levels(j)`) possono usare SOLO dati <= j-1
(il worker live li fissa al close del bar precedente, poi il bar j li tocca);
- `after_bar(j)` decide sul CLOSE del bar j (eseguibile al poll del tick);
- indicatori: usare l'indice j-1 degli array causali (es. ctx["atr14"][j-1]).
PROTOCOLLO ANTI-OVERFIT (vincolante):
- TRAIN = storico fino al 2023-11-01, OOS = dopo. La SELEZIONE dei parametri
si fa SOLO sul train; l'OOS si guarda una volta, per il verdetto.
- gate: il miglioramento deve tenere su ENTRAMBI gli asset e su TUTTE e 3 le
strategie (train E oos), con plateau sulla griglia (non una cella isolata).
- fee 0.10% RT x leva su tutto il notional; nessuna fee scontata sui limit.
Baseline = exit attuale (TP/SL fissi dall'entrata + max_bars): la parita' con
`partial_tp_ladder.py --base` e' verificata da `parity_check()`.
uv run python scripts/analysis/exit_lab.py # build cache + parity check
"""
from __future__ import annotations
import pickle
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data # noqa: E402
from src.live.strategy_loader import load_strategy # noqa: E402
LIVE_PARAMS = dict(trend_max=3.0, ema_long=200, hurst_max=0.55, min_tp_frac=0.0015)
OOS_START_MS = int(pd.Timestamp("2023-11-01", tz="UTC").value // 1e6)
LEV, POS, FEE_RT = 3.0, 0.15, 0.001
CODES = ["MR01_bollinger_fade", "MR02_donchian_fade", "MR07_return_reversal"]
ASSETS = ("BTC", "ETH")
CACHE = PROJECT_ROOT / "data" / "cache" / "exit_lab_signals.pkl"
HARD_CAP = 240 # bound assoluto ai bar in posizione (policy "ride" comprese)
# ----------------------------------------------------------------------------- dati
def _atr14(h: np.ndarray, l: np.ndarray, c: np.ndarray) -> np.ndarray:
pc = np.roll(c, 1); pc[0] = c[0]
tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
return pd.Series(tr).rolling(14).mean().values
def load_sleeves(refresh: bool = False) -> dict:
"""{(code, asset): sleeve} con cache. sleeve = {signals, open, high, low,
close, ts_ms, atr14}. signals = [(i, d, tp0, sl0, mb), ...] dai params LIVE."""
if CACHE.exists() and not refresh:
with open(CACHE, "rb") as f:
return pickle.load(f)
out = {}
for code in CODES:
strat = load_strategy(code)
for asset in ASSETS:
df = load_data(asset, "1h")
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
sigs = strat.generate_signals(df, ts, **LIVE_PARAMS)
h = df["high"].values.astype(float)
l = df["low"].values.astype(float)
c = df["close"].values.astype(float)
out[(code, asset)] = {
"signals": [(int(s.idx), int(s.direction), float(s.metadata["tp"]),
float(s.metadata["sl"]), int(s.metadata["max_bars"]))
for s in sigs],
"open": df["open"].values.astype(float),
"high": h, "low": l, "close": c,
"ts_ms": df["timestamp"].values.astype(np.int64),
"atr14": _atr14(h, l, c),
}
print(f" cache {code} {asset}: {len(sigs)} segnali, {len(c)} barre "
f"({ts.iloc[0].date()} -> {ts.iloc[-1].date()})")
CACHE.parent.mkdir(parents=True, exist_ok=True)
with open(CACHE, "wb") as f:
pickle.dump(out, f)
return out
# ----------------------------------------------------------------------------- policy
class ExitPolicy:
"""Baseline = exit live attuale. Le sottoclassi ridefinisco levels/after_bar.
Una ISTANZA per trade. `ctx` e' il dict sleeve (array completi + indicatori
aggiunti da prepare()): per contratto si legge SOLO fino a j-1 in levels(j)
e fino a j in after_bar(j)/on_partial(j).
"""
name = "base"
@classmethod
def prepare(cls, ctx: dict, **params) -> None:
"""Pre-calcola array causali per-sleeve (una volta), es. SMA/EMA."""
def __init__(self, ctx: dict, i: int, d: int, entry: float,
tp0: float, sl0: float, mb: int, **params):
self.ctx, self.i, self.d, self.entry = ctx, i, d, entry
self.tp0, self.sl0, self.mb = tp0, sl0, mb
self.horizon = mb # le sottoclassi possono estendere (cap HARD_CAP)
def levels(self, j: int):
"""Livelli ATTIVI nel bar j -> (tp, sl, tp_frac). None = livello assente.
tp_frac = quota del RESIDUO che esce al tocco del TP (1.0 = tutta)."""
return self.tp0, self.sl0, 1.0
def on_partial(self, j: int, price: float, remaining: float) -> None:
"""Notifica del fill parziale al TP nel bar j (aggiorna lo stato qui)."""
def after_bar(self, j: int) -> bool:
"""True = chiudi il residuo al close[j] (decisione sul close, eseguibile)."""
return False
# ----------------------------------------------------------------------------- engine
def simulate(policy_cls, sleeve: dict, params: dict | None = None,
start_ms: int | None = None, end_ms: int | None = None) -> dict:
"""Replay intrabar dei segnali dello sleeve con la policy. SL prioritario
sul TP nello stesso bar (conservativo); fill parziali pesati; max_bars/
horizon esce al close; non-overlap (una posizione per volta)."""
params = params or {}
h, l, c, ts = sleeve["high"], sleeve["low"], sleeve["close"], sleeve["ts_ms"]
n = len(c)
ctx = dict(sleeve)
policy_cls.prepare(ctx, **params)
fee = FEE_RT * LEV
capital = peak = 1000.0
max_dd = 0.0
last_exit = -1
trades = wins = 0
bars_tot = 0
rets = []
for (i, d, tp0, sl0, mb) in sleeve["signals"]:
if start_ms is not None and ts[i] < start_ms:
continue
if end_ms is not None and ts[i] >= end_ms:
continue
if i <= last_exit or i + 1 >= n:
continue
entry = c[i]
pol = policy_cls(ctx, i, d, entry, tp0, sl0, mb, **params)
horizon = min(int(pol.horizon), HARD_CAP)
fills: list[tuple[float, float]] = []
remaining = 1.0
j = i
for step in range(1, horizon + 1):
j = i + step
if j >= n:
j = n - 1
fills.append((remaining, c[j])); remaining = 0.0
break
tp, sl, tpfrac = pol.levels(j)
hit_sl = sl is not None and ((d == 1 and l[j] <= sl) or (d == -1 and h[j] >= sl))
hit_tp = tp is not None and ((d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp))
if hit_sl: # conservativo: SL prima del TP
fills.append((remaining, sl)); remaining = 0.0
break
if hit_tp:
f = min(max(tpfrac, 0.0), 1.0) * remaining
if f > 0:
fills.append((f, tp)); remaining -= f
if remaining <= 1e-9:
break
pol.on_partial(j, tp, remaining)
if pol.after_bar(j):
fills.append((remaining, c[j])); remaining = 0.0
break
if step == horizon:
fills.append((remaining, c[j])); remaining = 0.0
if remaining > 1e-9: # safety (non dovrebbe accadere)
fills.append((remaining, c[j]))
ret = sum(f * (p - entry) for f, p in fills) / entry * d * LEV - fee
capital = max(capital + capital * POS * ret, 10.0)
peak = max(peak, capital)
max_dd = max(max_dd, (peak - capital) / peak)
last_exit = j
trades += 1
wins += ret > 0
bars_tot += j - i
rets.append(ret)
if trades == 0:
return {}
r = np.array(rets)
return {
"ret_pct": (capital / 1000.0 - 1) * 100,
"dd_pct": max_dd * 100,
"trades": trades,
"win_pct": wins / trades * 100,
"avg_ret_bps": r.mean() * 1e4,
"sharpe_t": float(r.mean() / r.std() * np.sqrt(len(r))) if r.std() else 0.0,
"avg_bars": bars_tot / trades,
}
# ----------------------------------------------------------------------------- report
def evaluate(policy_cls, grid: list[dict], data: dict | None = None,
quiet: bool = False) -> dict:
"""Protocollo train/OOS su tutta la griglia. La selezione dei parametri va
fatta SUL TRAIN (l'OOS si riporta, non si ottimizza). Ritorna dict
{params_str: {sleeve: {train: {...}, oos: {...}}}} + baseline."""
data = data or load_sleeves()
out: dict = {}
rows = [("base", ExitPolicy, {})] + [
(", ".join(f"{k}={v}" for k, v in g.items()) or "default", policy_cls, g)
for g in grid]
for tag, cls, g in rows:
out[tag] = {}
for (code, asset), sleeve in data.items():
key = f"{code.split('_')[0]} {asset}"
tr = simulate(cls, sleeve, g, end_ms=OOS_START_MS)
oo = simulate(cls, sleeve, g, start_ms=OOS_START_MS)
out[tag][key] = {"train": tr, "oos": oo}
if not quiet:
print(f"{tag:<28}{key:<10}"
f"TRAIN ret{tr.get('ret_pct', 0):>7.0f}% dd{tr.get('dd_pct', 0):>5.1f} "
f"sh{tr.get('sharpe_t', 0):>5.2f} n{tr.get('trades', 0):>4} | "
f"OOS ret{oo.get('ret_pct', 0):>6.0f}% dd{oo.get('dd_pct', 0):>5.1f} "
f"sh{oo.get('sharpe_t', 0):>5.2f} n{oo.get('trades', 0):>4} "
f"bars{oo.get('avg_bars', 0):>5.1f}")
return out
def parity_check() -> None:
"""La baseline qui deve riprodurre i numeri FULL di partial_tp_ladder (base):
MR01 BTC ~92%/13.8dd, MR01 ETH ~194%/16.5dd, MR02 ETH ~2135%/16.2dd..."""
data = load_sleeves()
print("\nParity check baseline (FULL, atteso = partial_tp_ladder base):")
expected = {("MR01_bollinger_fade", "BTC"): 92, ("MR01_bollinger_fade", "ETH"): 194,
("MR02_donchian_fade", "BTC"): 129, ("MR02_donchian_fade", "ETH"): 2135,
("MR07_return_reversal", "BTC"): 78, ("MR07_return_reversal", "ETH"): 115}
ok = True
for key, sleeve in data.items():
r = simulate(ExitPolicy, sleeve)
exp = expected[key]
match = abs(r["ret_pct"] - exp) < 1.0
ok &= match
print(f" {key[0].split('_')[0]} {key[1]}: ret {r['ret_pct']:.0f}% "
f"(atteso ~{exp}) {'OK' if match else 'MISMATCH'}")
print("PARITY", "OK" if ok else "FAILED")
if __name__ == "__main__":
load_sleeves(refresh="--refresh" in sys.argv)
parity_check()
@@ -0,0 +1,75 @@
"""EXIT-01 — trail_atr_ride: TP RIMOSSO, cavalcata pura con SL trailing chandelier.
Idea: le fade mean-reversion escono oggi al TP fisso (alla media) + SL + max_bars.
Qui togliamo il TP e lasciamo correre il trade, proteggendolo con un SL trailing
"chandelier" a k*ATR dal massimo favorevole raggiunto. Lo stop puo' solo stringersi
(mai allargarsi). Orizzonte esteso (cap HARD_CAP=240) per dare spazio al runner.
Long: stop(j) = max( sl0, max(high[i..j-1]) - k*atr14[j-1] ) (sale, mai scende)
Short: stop(j) = min( sl0, min(low[i..j-1]) + k*atr14[j-1] ) (scende, mai sale)
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
- massimo/minimo favorevole sullo slice [i .. j-1] (mantenuto incrementalmente,
aggiornato col bar j-1 prima di calcolare lo stop attivo nel bar j);
- atr14[j-1] (indice causale).
Nessun TP -> nessun fill parziale. after_bar non usato (chiusura solo a orizzonte/SL).
GRID: k in {2.0, 3.0, 4.0} x horizon_mult in {2, 4} (6 celle). horizon = mult*mb cap 240.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from exit_lab import ExitPolicy, evaluate, HARD_CAP
class TrailAtrRide(ExitPolicy):
name = "trail_atr_ride"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=3.0, horizon_mult=4, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
self.k = float(k)
self.horizon = min(int(horizon_mult) * mb, HARD_CAP)
# estremo favorevole sullo slice [i..j-1]; inizializzato al bar di entrata i
# (il primo bar valutato e' j=i+1, dove lo slice [i..j-1]=[i..i] e' noto).
self.fav_high = ctx["high"][i]
self.fav_low = ctx["low"][i]
self._last_seen = i # ultimo indice gia' incorporato nell'estremo
# stop trailing monotono: parte da sl0 e puo' solo stringersi
self.cur_stop = sl0
def levels(self, j):
h = self.ctx["high"]
l = self.ctx["low"]
atr = self.ctx["atr14"]
# incorpora i bar fino a j-1 (dati causali, gia' chiusi al poll del bar j)
while self._last_seen < j - 1:
self._last_seen += 1
if h[self._last_seen] > self.fav_high:
self.fav_high = h[self._last_seen]
if l[self._last_seen] < self.fav_low:
self.fav_low = l[self._last_seen]
a = atr[j - 1]
if a != a: # NaN nei primi 14 bar -> resta sullo stop corrente
return None, self.cur_stop, 1.0
if self.d == 1:
cand = self.fav_high - self.k * a
if cand > self.cur_stop: # lo stop long puo' solo SALIRE (stringersi)
self.cur_stop = cand
else:
cand = self.fav_low + self.k * a
if cand < self.cur_stop: # lo stop short puo' solo SCENDERE (stringersi)
self.cur_stop = cand
return None, self.cur_stop, 1.0 # TP rimosso
GRID = [
{"k": k, "horizon_mult": m}
for k in (2.0, 3.0, 4.0)
for m in (2, 4)
]
if __name__ == "__main__":
evaluate(TrailAtrRide, GRID)
@@ -0,0 +1,68 @@
"""EXIT-02 — trail_atr_keep_tp.
Chandelier trailing stop a k*ATR dall'estremo favorevole RAGGIUNTO dall'entrata,
MA il TP fisso tp0 dall'entrata RESTA attivo: si esce al PRIMO dei due (il TP al
livello, oppure il trail). horizon = max_bars (invariato).
Stop attivo nel bar j (solo dati <= j-1, anti-look-ahead):
long : chand = max(high[i..j-1]) - k*atr14[j-1]; sl = max(sl0, chand)
short: chand = min(low[i..j-1]) + k*atr14[j-1]; sl = min(sl0, chand)
Il max(sl0, chand) (per il long) tiene la protezione iniziale a sl0 e lascia che
il trail TIGHTEN solo quando il prezzo corre a favore -> "ride" controllato che
non allenta mai il rischio iniziale. Il TP non viene toccato: una fade che torna
alla media esce comunque al TP come la base; il trail morde solo quando il TP non
viene raggiunto e il prezzo ha prima corso a favore e poi ritracciato.
GRID: k in {1.5, 2.0, 3.0, 4.0} (4 celle).
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from exit_lab import ExitPolicy, evaluate # noqa: E402
class TrailATRKeepTP(ExitPolicy):
name = "trail_atr_keep_tp"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
self.k = float(params.get("k", 2.0))
self.high = ctx["high"]
self.low = ctx["low"]
self.atr = ctx["atr14"]
# estremo favorevole running (solo barre <= j-1); init = barra d'entrata i
self.run_hi = self.high[i]
self.run_lo = self.low[i]
self.last_seen = i # ultimo indice gia' incorporato nel running extremum
def _update_running(self, upto: int) -> None:
"""Incorpora le barre (last_seen, upto] nell'estremo running. upto = j-1,
quindi NON tocca il bar j (anti-look-ahead)."""
while self.last_seen < upto:
self.last_seen += 1
if self.high[self.last_seen] > self.run_hi:
self.run_hi = self.high[self.last_seen]
if self.low[self.last_seen] < self.run_lo:
self.run_lo = self.low[self.last_seen]
def levels(self, j: int):
self._update_running(j - 1) # solo dati <= j-1
a = self.atr[j - 1]
if a is None or a != a: # NaN -> nessun trail, usa sl0
return self.tp0, self.sl0, 1.0
if self.d == 1:
chand = self.run_hi - self.k * a
sl = max(self.sl0, chand) # piu' protettivo (stop piu' alto)
else:
chand = self.run_lo + self.k * a
sl = min(self.sl0, chand) # piu' protettivo (stop piu' basso)
return self.tp0, sl, 1.0
GRID = [{"k": 1.5}, {"k": 2.0}, {"k": 3.0}, {"k": 4.0}]
if __name__ == "__main__":
evaluate(TrailATRKeepTP, GRID)

Some files were not shown because too many files have changed in this diff Show More