Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7eb0f67956 | |||
| 8d1fe173f7 | |||
| de72e3ce1f | |||
| 8e46a62e67 | |||
| c7c07f4c35 | |||
| 2d8faf3896 | |||
| 64d98a070d | |||
| 237ca8da13 | |||
| 856a02fcc5 | |||
| 010d1f0733 | |||
| 1c15c3c1be | |||
| 3a3bdd5c7b | |||
| f983cc2447 | |||
| 389573e517 | |||
| 3b552a92da | |||
| 09c8bb7de8 | |||
| c4bc336a53 | |||
| 55c337144e | |||
| d916520f2c | |||
| d2d535cf6a | |||
| 67850b0dd8 | |||
| 437cf11199 | |||
| c9b89739c1 | |||
| 03267b8fc3 | |||
| 1c0b5f1869 | |||
| 61180637eb | |||
| 745ba7d066 | |||
| 043f141bf1 | |||
| b388c462e8 | |||
| 8514c096ea | |||
| b5db59bea9 | |||
| 5cce7acfe1 | |||
| d5dd6f4b72 | |||
| 4ae3b42442 | |||
| 24565974c0 | |||
| 62d3b23cc6 | |||
| 0adc69a357 | |||
| 1afb1014c9 | |||
| f5d30d88b9 | |||
| 9612560479 | |||
| 5ac4e16af8 | |||
| bf84bc91e2 | |||
| 3cba5bb9d0 | |||
| e5e2d3ec9b | |||
| 4650aa71a2 | |||
| bc9e322d0d | |||
| a3d6b97db6 | |||
| cddea50c5a | |||
| c00f6016df | |||
| 715f197cf2 | |||
| bec2fb2089 | |||
| 9c48cdd884 | |||
| 9ed2ea4b13 | |||
| c6236ed5d9 | |||
| 75e1aacd19 | |||
| 92a63feb9c | |||
| 26e977d338 | |||
| 0d9f483131 | |||
| 87dd56a9ce | |||
| fd5a0bd3cf | |||
| bf6ade51af | |||
| 182d4eeac2 | |||
| 8426d05f12 | |||
| 53d0134cb1 | |||
| 8f9ce89039 | |||
| 87af03955c | |||
| 790caefd52 | |||
| a5a61ac7e3 | |||
| 18f22160b2 | |||
| ef52ad6a79 | |||
| 12754c4908 | |||
| 756a2bdf04 | |||
| d152941360 | |||
| 55c28e51b2 | |||
| 38c8cdf25b | |||
| 14522262e6 | |||
| 8401a280b9 | |||
| b4d4961d77 | |||
| 264b9200ea | |||
| b3d4ab7150 | |||
| 587fbc0f61 | |||
| 8abeb7a83f | |||
| b9e3388f76 | |||
| e631e1b89c | |||
| d733196564 | |||
| 57758ffcb1 | |||
| c8b956ab56 | |||
| 3b60289cbd | |||
| e328836614 | |||
| a0842a61cc | |||
| 3a66907f43 | |||
| aee53161c3 | |||
| 7b3e554424 | |||
| b576ee66ac | |||
| ef797f1ff0 | |||
| 4c184d5cbc |
@@ -0,0 +1,11 @@
|
||||
Old/
|
||||
data/
|
||||
.venv/
|
||||
.git/
|
||||
logs/
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
.env.mainnet
|
||||
docs/
|
||||
@@ -0,0 +1,7 @@
|
||||
# Credenziali IB Gateway PAPER per la ricerca dati (account paper, es. DUQ513966).
|
||||
# COPIA questo file in .env.ibgw (gitignored) e riempi i valori REALI.
|
||||
# cp .env.ibgw.example .env.ibgw && chmod 600 .env.ibgw && nano .env.ibgw
|
||||
# NON committare mai .env.ibgw. Sono credenziali del CONTO PAPER (nessun denaro reale),
|
||||
# l'API e' comunque READ_ONLY (solo dati storici, nessun ordine).
|
||||
TWS_USERID=il_tuo_username_paper
|
||||
TWS_PASSWORD=la_tua_password_paper
|
||||
+29
@@ -6,6 +6,8 @@ build/
|
||||
.venv/
|
||||
.env
|
||||
!.env.example
|
||||
.env.ibgw
|
||||
!.env.ibgw.example
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
@@ -20,6 +22,8 @@ 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/
|
||||
@@ -37,3 +41,28 @@ 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/
|
||||
data/paper_portfolio/
|
||||
|
||||
# output grezzo dello sweep di ricerca xsec (rigenerabile dagli script in runs/)
|
||||
scripts/research/xsec/runs/out/
|
||||
|
||||
# blind-signal derived data (regenerable via make_blind.py)
|
||||
data/blind/
|
||||
scripts/research/blind/leaderboard.json
|
||||
|
||||
# forward-monitor runtime state (regenerable, forward-only)
|
||||
data/paper_prevday/
|
||||
data/paper_combo/
|
||||
|
||||
@@ -1,627 +1,290 @@
|
||||
# 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 6–8 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.~~
|
||||
**AGGIORNATO 2026-06-20: l'esecuzione di TP01 è ARMATA e LIVE su Deribit mainnet** —
|
||||
`config/live.json` `execution_enabled=true` + cron giornaliero `live_execute.py --execute`
|
||||
(cablato in `scripts/cron_daily.sh`). Guardrail: cap **$300 notional/asset**, min order $5,
|
||||
**disaster-SL on-book −30%**, alert Telegram su esecuzione/errori. **Capitale reale ≈ $600**
|
||||
(NON i €2000 nominali del paper trader). Stato corrente: **flat** (target TSMOM risk-off →
|
||||
BTC/ETH 0.0x, nessun ordine). Solo TP01 è eseguito; XS01/VRP01 restano paper/STAT-MODE.
|
||||
- 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 (41.25%) + XS01 (18.75%) + VRP01 (15%) + SKH01 (25%)** (`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). **Aggiunto SKH01-V2-DD @25% effettivo (2026-06-23, sotto):** i tre
|
||||
preesistenti scalati nel restante 0.75 (rapporto 55:25:20). Il portafoglio a **4 sleeve** fa
|
||||
**FULL Sharpe 1.68→2.13, HOLD-OUT 1.63→2.30, DD full 14.3%→7.8%** (Skyhook è quasi-ortogonale,
|
||||
corr ~0.09). Report `scripts/portfolio/run_portfolio.py`. Sleeve a date d'inizio
|
||||
diverse → outer-join con pesi rinormalizzati (TP01/SKH01 dal 2019, VRP dal 2021, XS dal 2024).
|
||||
- **SKH01-V2-DD "Skyhook" — DIVERSIFICATORE quasi-ortogonale (research)** — `src/strategies/skyhook.SKH01_V2_DD`,
|
||||
sleeve `src/portfolio/sleeves._skyhook_returns`. Sistema dual-TF (segnale 690m / exec 230m) regime
|
||||
(BuzVola/BuzVolume tipo-Chande) AND pattern (Donchian breakout), NON trend-follower, L/S. Vincitrice
|
||||
di 2 onde multi-agente (la 2ª = DD-reduction): exit a **percentuale fissa ASIMMETRICA** (long sl4%/tp10%,
|
||||
short sl2%/tp8% più stretto) → standalone **maxDD BTC 21% / ETH 27% (<30%)**, minFull +0.99, minHold
|
||||
+1.26, causale (0/400), fee-surviving 0.40%RT. Marginal vs TP01 **ADDS** (corr 0.09, has_insample_edge,
|
||||
robust_oos multicut 7/7, is_hedge=False); blend 0.75·TP01+0.25·SKH **hold-out 0.31→1.17**. Verificato
|
||||
leak-free + 2 scettici. **CAVEAT:** equity daily-step (Sharpe lens), ETH DD margine sottile, book 230m
|
||||
(costi ribilanciamento da verificare a deploy) → research win, forward-monitor. Diario `2026-06-23-skyhook.md`.
|
||||
- **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.
|
||||
- **Sweep "strategie alternative" (2026-06-20) — 104 ipotesi / 153 agenti / NIENTE di nuovo regge.**
|
||||
Ricerca onesta a largo spettro su BTC/ETH+DVOL (harness condiviso vettoriale leak-free
|
||||
`scripts/research/alt/altlib.py`, 104 script in `scripts/research/alt/runs/`): 11 famiglie
|
||||
(breakout, trend non-TSMOM, mean-rev gated, DVOL/vol, cross-asset pairs, stagionalità, overlay
|
||||
rischio, opzioni modellate, microstruttura, ML walk-forward, combo). 16 promettenti, **1 sola**
|
||||
sopravvissuta alla verifica avversariale (3 scettici) e comunque NON deployabile. Conferma forte
|
||||
del soffitto ~1.3: ogni PASS era hold-out-fitting o **TP01/TSMOM travestito** (trend-beta del
|
||||
toro). Unico LEAD: **STA05** (EWMA-cross ensemble, **long-short**) — leak-free, plateau, corr
|
||||
hold-out **0.53** a TP01, il blend 0.75·TP01+0.25·STA05 alza l'hold-out 0.31→0.59 (full 1.30→1.24,
|
||||
DD 14→16%); MA hold-out corto (536g) → **forward-monitor, non sleeve.** Lezione harness: valutare
|
||||
lo Sharpe **MARGINALE vs baseline TP01** (non assoluto) + esigere plateau e jackknife
|
||||
drop-one-month sull'hold-out prima di PASS (hanno ucciso 13/14 falsi positivi). Diario
|
||||
`2026-06-20-alt-strategies-100agent-sweep.md`.
|
||||
- **MARGINAL SCORER (implementato 2026-06-20)** — la lezione "Sharpe marginale, non assoluto" è
|
||||
ora codice in `scripts/research/alt/altlib.py`: `study_marginal(name, target_fn)` valuta un
|
||||
candidato direzionale BTC/ETH **sia** in assoluto **sia** rispetto al baseline `tp01_baseline_daily()`
|
||||
(corr, uplift del blend OOS, beta+alpha residua) e ritorna `earns_slot = (abs!=FAIL) AND
|
||||
(marginal==ADDS)`. **Regola: una nuova strategia direzionale si giudica su `earns_slot`, non sullo
|
||||
Sharpe assoluto** (gli overlay-su-TSMOM ereditano lo Sharpe di trend e prendono PASS fasulli —
|
||||
es. CMB04 PASS assoluto → NEUTRAL marginale). Demo `marginal_demo.py`, test `tests/test_marginal_scorer.py`.
|
||||
⚠️ **INDURITO 2026-06-21 (onda ortho):** la versione fisso-HOLDOUT + jackknife-mese era
|
||||
ingannabile — 17/18 book relative-value "ADDS" su una sola finestra 2025 (ETH-bleed dove TP01 è
|
||||
debole). Tre gate nuovi in `marginal_vs_tp01`: **(1) persistenza multi-cut** (uplift positivo a più
|
||||
date di taglio, non solo 2025); **(2) edge in-sample** (`has_insample_edge`: lo Sharpe standalone
|
||||
PRE-holdout dev'essere ≥0.5 — un low-corr a Sharpe ~0.3 "aggiunge" solo matematica di
|
||||
diversificazione, riportata via `null_pctl_*` vs un asset-rumore a corr-zero); **(3) hedge vs
|
||||
alpha** (`is_hedge`: un low-corr che paga SOLO quando TP01 è debole — `corr(Sharpe-TP01, uplift
|
||||
annuo)` molto negativa — è un hedge, non alpha). Verdetti nuovi: HEDGE, NOISE. Sull'onda ortho lo
|
||||
scorer indurito collassa 17/18 → **1** (`dvol_spread`, unico con edge in-sample reale; comunque
|
||||
forward-monitor per multiple-testing/storia DVOL corta). Lezione: un nuovo sleeve si giudica su
|
||||
edge-in-sample + persistenza multi-cut + non-hedge, non sull'uplift di una finestra fortunata.
|
||||
- **HARNESS REALISM (codificato 2026-06-21, onda intraday)** — due gate nuovi in `altlib.py`,
|
||||
test `tests/test_harness_realism.py`:
|
||||
- **`day_boundary_robust(target_fn, tf)`** — un effetto ora/sessione/giorno il cui uplift
|
||||
marginale **si inverte** spostando il confine del giorno UTC di poche ore è un **artefatto di
|
||||
etichettatura calendario** (ha ucciso `open_drive`: +0.23 a 00:00 → −0.33 a +8h → ARTIFACT-RISK).
|
||||
Un segnale di prezzo è INVARIANT (spread 0); un effetto calendario vero è ROBUST (resta positivo;
|
||||
es. `prevday_range_breakout`). **Regola: ogni segnale calendar/session/hour passa questo test
|
||||
prima di crederci.**
|
||||
- **`eval_weights_smallcap(df, target, capital=600, min_order=5)`** — a ~$600 un ribilanciamento
|
||||
di nozionale < min_order **non si esegue**; la fee proporzionale che `eval_weights` applica a
|
||||
migliaia di micro-trade sub-dollaro (tipici di un overlay vol-target) è **finzione**. Salta i
|
||||
sub-min_order e riporta lo **Sharpe haircut** reale vs modellato. **Vale per OGNI sleeve a questo
|
||||
capitale, TP01 incluso** — lo Sharpe netto onesto a $600 è quello small-cap, non quello modellato.
|
||||
- **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
|
||||
|
||||
- **Linguaggio:** Python 3.11+
|
||||
- **Package manager:** uv (dipendenze in `pyproject.toml`, lock in `uv.lock`)
|
||||
- **Dati:** Parquet in `data/raw/` (non committati, ~70 MB)
|
||||
- **ML:** scikit-learn (GradientBoostingClassifier)
|
||||
- **Analisi:** numpy, pandas, scipy
|
||||
- **API dati:** Cerbero MCP su `cerbero-mcp.tielogic.xyz` (Deribit, Bybit, Hyperliquid), ccxt/Binance come fallback
|
||||
- **Config:** pyyaml per `strategies.yml`
|
||||
- **Linguaggio:** Python 3.11+ — **Package manager:** uv (`pyproject.toml`, `uv.lock`)
|
||||
- **Dati:** Parquet in `data/raw/` (gitignored). Solo BTC/ETH (5m/15m/1h).
|
||||
- **Analisi/ML:** numpy, pandas, scipy, scikit-learn
|
||||
- **Fonte dati storici:** Deribit mainnet via `ccxt` (pubblico, tokenless)
|
||||
|
||||
## Struttura
|
||||
## Struttura (post-reset)
|
||||
|
||||
```
|
||||
src/data/ → download e caricamento dati
|
||||
downloader.py → download/caricamento parquet (gate: solo strumenti validati)
|
||||
instruments.py → discovery + validazione strumenti per exchange, registry
|
||||
src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py)
|
||||
src/backtest/ → engine di backtesting (engine.py)
|
||||
src/strategies/ → classe base Strategy ABC + indicatori condivisi
|
||||
base.py → Strategy, Signal, BacktestResult, YearlyStats
|
||||
indicators.py → keltner_ratio, detect_squeezes, ema, atr, rv, correlation
|
||||
src/live/ → paper trading live multi-strategia
|
||||
multi_runner.py → orchestratore: carica YAML (strategies + pairs), fetch candele, tick worker
|
||||
strategy_worker.py → worker single-leg: capital, trade log, stato persistente.
|
||||
Exit guidati da strategia (TP/SL/max_bars via Signal.metadata),
|
||||
fallback hold_bars/stop -2%. Usa fee_rt della strategia.
|
||||
pairs_worker.py → worker a 2 GAMBE per PR01 (market-neutral): long A / short B sullo
|
||||
z-score del log-ratio, exit |z|<=z_exit o max_bars, fee su 2 gambe.
|
||||
strategy_loader.py → import dinamico classi Strategy da scripts/strategies/
|
||||
cerbero_client.py → client HTTP per Cerbero MCP (Deribit testnet): dati + ordini
|
||||
(place_order market/reduce_only, get_trade_history, get_positions)
|
||||
execution.py → ExecutionClient: esecuzione REALE su Deribit (shadow). notional→
|
||||
amount (lineari USDC + inverse), open/close_amount reduce-only,
|
||||
verifica sul trade (order_id), fee reali dai trades[]
|
||||
signal_engine.py → squeeze + ML real-time (legacy ML01, ora in waste) + validazione OOS
|
||||
telegram_notifier.py → notifiche Telegram per trade
|
||||
src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+live)
|
||||
base.py → SleeveSpec, Portfolio (.backtest), load_active_portfolio
|
||||
weighting.py → schemi pesi: equal/cap/inverse_vol/cluster_rp/manual
|
||||
sleeves.py → builder unificato equity-per-sleeve (fonte unica, parità report)
|
||||
ledger.py → PortfolioLedger: capitale/PnL/DD/persistenza+resume
|
||||
runner.py → PortfolioRunner live (data Cerbero v2, sizing, ribilancio)
|
||||
src/version.py → APP_VERSION (legge il file VERSION) — mostrato nei msg Telegram
|
||||
src/strategies/fade_base.py → FadeStrategy + helper: atr, trend_distance, hurst_skip_mask (loss-guard),
|
||||
exit close-confirm SL via param sl_confirm_atr (EXIT-16)
|
||||
scripts/strategies/ → strategie con edge validato OOS: FADE (MR01/MR02/MR07),
|
||||
HONEST (DIP01/TR01/ROT02), PAIRS (PR01), TSMOM + portafogli (PORT01/02/03);
|
||||
FR01_hurst_calm_fade.py = record ricerca (robusto ma NON deployato)
|
||||
scripts/portfolios/ → definizioni PORT01-06 (_defs.py) + report run() + hourly_report.py (Telegram)
|
||||
scripts/waste/ → strategie scartate (W01-W28 + famiglia squeeze SQ/MT/ML/AD/CM/PD)
|
||||
scripts/analysis/ → ricerca/validazione OOS fee-aware (strategy_research, oos_validation, ...);
|
||||
regime_fetcher.py + regime_lab.py (DVOL/funding/feature regime per la ricerca);
|
||||
exit_lab.py + exit_policies/ (harness ricerca exit: cache segnali, train/OOS);
|
||||
options_fetcher.py + options_chain.py (storico opzioni REALE da cerbero-bite);
|
||||
option_overlay_lab.py (overlay opzioni prezzato; mr02eth_port06_gate.py = gate swap-sleeve);
|
||||
pairs15m_*.py (gate/flatcheck/smoke ETH/BTC 15m -> sleeve PR_ETHBTC_15M)
|
||||
scripts/games/ → gioco "Blind Traders": 100 agenti ciechi su BTC/ETH anonimi (engine.py,
|
||||
arena.py epoche+cull, agent_brief.py digest, run_game.py). Origine del BLEND 15m.
|
||||
Varianti: options_* (strutture in opzioni, BS+skew+DVOL; opt_calibrate da
|
||||
cerbero-bite), session_* (pattern orari), grid_* (griglie da STRATEGIA_GRIGLIA.md;
|
||||
gate scripts/analysis/grid_game_gate.py). arena: GAME_NO_LIVE=1 vieta le
|
||||
strategie gia' deployate (pairs, fade zscore/breakout/momentum)
|
||||
scripts/bump_version.py, scripts/deploy.sh → versionamento e deploy (bump+commit+rebuild)
|
||||
VERSION → versione semver (cotta nell'immagine, +1 ad ogni deploy)
|
||||
strategies.yml → config multi-strategy paper trader
|
||||
docs/diary/ → diario di ricerca giornaliero
|
||||
docs/specs/ → specifiche di design
|
||||
data/raw/ → file .parquet OHLCV (gitignored) | data/regime/ → DVOL+funding+feature (gitignored)
|
||||
data/options/ → storico catena opzioni per-strike (bid/ask/IV/greche/OI) importato da cerbero-bite (gitignored)
|
||||
data/instruments_registry.json → allowlist strumenti validati (gate del downloader)
|
||||
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/backtest/engine.py → engine di backtesting riusabile
|
||||
src/backtest/harness.py → harness ONESTO (load BTC/ETH, backtest_signals no-leakage, OOS)
|
||||
src/version.py → APP_VERSION (legge il file VERSION)
|
||||
scripts/research/ → ricerca: track{A-I}_*.py + options_vrp_*.py + fetch_dvol.py
|
||||
scripts/portfolio/ → run_portfolio.py (report) + xsec_*.py (ricerca/affinamento XS01)
|
||||
scripts/live/paper_trend.py → paper trader forward-only di TP01 (1d) (no esecuzione reale)
|
||||
scripts/analysis/ → SOLO i tool dati certificati:
|
||||
rebuild_history.py → (ri)costruisce lo storico da Deribit mainnet (base 5m + resample)
|
||||
certify_feed.py → certifica il feed (integrità, coerenza resample, spike, cross-venue)
|
||||
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
|
||||
|
||||
```bash
|
||||
uv sync # installa dipendenze
|
||||
uv run python -m src.data.downloader # scarica dati storici (solo strumenti validati)
|
||||
uv run python -m src.data.instruments # (ri)costruisci il registry strumenti validati
|
||||
uv run python scripts/strategies/MR01_bollinger_fade.py # backtest una strategia (es. fade)
|
||||
uv run python scripts/strategies/PR01_pairs_reversion.py # backtest pairs market-neutral
|
||||
uv run python scripts/analysis/strategy_research.py # ricerca strategie fee-aware OOS
|
||||
uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata
|
||||
uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie
|
||||
uv run python scripts/analysis/validate_worker_pairs.py # replay worker 2 gambe == backtest
|
||||
uv run python -m src.live.multi_runner # paper trading live multi-strategia (strategie + pairs)
|
||||
uv run python scripts/portfolios/PORT06_master_shape.py # report backtest portafoglio (default)
|
||||
uv run python -m src.portfolio.runner # paper trading a PORTAFOGLIO (capitale pool)
|
||||
uv run python scripts/analysis/smoke_portfolio.py # smoke live data layer Cerbero v2
|
||||
uv run python scripts/analysis/live_exec_smoke.py # smoke ESECUZIONE reale (ordine→verifica→fee, testnet)
|
||||
uv run python scripts/analysis/live_shadow_smoke.py # smoke catena shadow nel worker (open/close reali)
|
||||
uv run python scripts/analysis/regime_fetcher.py # fetch DVOL+funding (Deribit mainnet) -> data/regime/
|
||||
uv run python scripts/analysis/exit_lab.py # (ri)costruisci cache segnali exit-lab + parity check
|
||||
uv run python -m src.live.dashboard --port 8787 # dashboard web (servizio compose 'dashboard', porta 8787)
|
||||
uv run python scripts/analysis/ledger_vs_backtest.py --since 2026-06-13 # report fuga di esecuzione (reale vs sim)
|
||||
./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker
|
||||
uv run pytest # test
|
||||
uv sync # installa dipendenze
|
||||
uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # (ri)costruisci storico da Deribit mainnet
|
||||
uv run python scripts/analysis/certify_feed.py # certifica i feed (locale + cross-venue)
|
||||
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
|
||||
```
|
||||
|
||||
> **Dashboard web (`src/live/dashboard.py`, servizio compose `dashboard`, porta 8787).**
|
||||
> Stdlib http.server, legge `data/`: equity (restyling), PnL totale e per-strategia,
|
||||
> **grafico equity per famiglia**, trade attivi in tempo reale (entry/mark/PnL **REALI** —
|
||||
> il feed sim-decisione testnet è dislocato, vedi sotto) + chiusi, modal per-strategia
|
||||
> (curva reale vs sim + scheda `strategie_attive.html`), **area PAPER distinta** (equity
|
||||
> propria dei 4 multi-asset), strategie **ritirate** marcate (staleness >30min) + versione
|
||||
> sistema/strategia. Nessuna auth → solo rete interna. `docker compose up -d --build dashboard`.
|
||||
|
||||
> **Cron host (monitoraggio, già schedulati):** `hourly_report.py` (orario), `drift_monitor.py`
|
||||
> (07:15), `reconcile_account.py --telegram` (:40), **`ledger_vs_backtest.py --since 2026-06-13
|
||||
> --telegram` (08:30)** = il GATE per scalare il capitale (per gli sleeve eseguiti sim==backtest
|
||||
> per costruzione → reale-vs-sim = fuga di esecuzione: slippage+fee+netting; verdetto 🟢/🟡/🔴).
|
||||
|
||||
> **Deploy.** Il sorgente è **COPY nell'immagine Docker** (non montato) → `docker compose restart`
|
||||
> NON ricarica il codice: serve **`docker compose up -d --build`** (o `./scripts/deploy.sh`, che bumpa
|
||||
> la versione, committa e rebuilda). Il volume `data/` persiste → i worker fanno RESUME dello stato.
|
||||
> La **versione** (file `VERSION`, semver, +1 ad ogni deploy via `deploy.sh`) compare nei messaggi
|
||||
> Telegram (notifiche trade + report orario) → correli ogni msg al codice che l'ha generato.
|
||||
|
||||
## Dati storici
|
||||
|
||||
Scaricati e salvati localmente in Parquet. Per rigenerarli:
|
||||
|
||||
```python
|
||||
from src.data.downloader import download_all, load_data
|
||||
download_all() # scarica BTC + ETH su 5m/15m/1h dal 2018
|
||||
df = load_data("ETH", "15m") # carica un asset/timeframe
|
||||
from src.data.downloader import load_data
|
||||
df = load_data("BTC", "1h") # OK. load_data("SOL", ...) -> FileNotFoundError (guardrail: solo dati certi)
|
||||
```
|
||||
|
||||
Fonte primaria: Cerbero MCP (endpoint `/mcp-deribit/tools/get_historical`).
|
||||
Token observer: nel file `secrets/observer.token` del progetto CerberoSuite.
|
||||
## IL DATO — fonte di verità (regola di prim'ordine)
|
||||
|
||||
### Strumenti & validazione (gate raccolta dati)
|
||||
- **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).
|
||||
|
||||
`src/data/instruments.py` scopre e **valida** gli strumenti per ogni exchange
|
||||
implementato — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e **Bybit**,
|
||||
il cui feed testnet è farlocco). Per ogni perpetuo enumera via `get_instruments`
|
||||
/`get_markets` e verifica sui **dati storici realmente raccoglibili**:
|
||||
esistenza, congruenza OHLC, not-flat (scarta contratti morti), liquidità (volume
|
||||
daily) e **congruenza prezzo cross-exchange** (scostamento dalla mediana del
|
||||
base-coin ≤ 5% → scarta outlier come `SOL-PERPETUAL`=9.6 vs SOL reale ~82).
|
||||
### 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`.
|
||||
|
||||
Output: `data/instruments_registry.json` (strumenti validi, timeframe, start-date).
|
||||
**Gate:** `_download_cerbero_range` rifiuta gli strumenti non validati (override
|
||||
`allow_unvalidated=True` solo per casi eccezionali). Rigenera con
|
||||
`python -m src.data.instruments`.
|
||||
## Metodologia obbligatoria per ogni nuova strategia
|
||||
|
||||
> **NB testnet.** Il token Cerbero punta a testnet; la congruenza cross-exchange
|
||||
> è il filtro che distingue i feed realistici (Deribit, Hyperliquid) da quelli
|
||||
> farlocchi (Bybit). Simboli Deribit: BTC/ETH = `<COIN>-PERPETUAL` (inverse);
|
||||
> alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Registry attuale: Deribit 18/106,
|
||||
> Hyperliquid 66/74 validi (major liquidi: BTC dal 2018, alt dal 2022).
|
||||
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.
|
||||
|
||||
## Strategie attive
|
||||
## Lezioni critiche (da NON ripetere — la storia di questo progetto)
|
||||
|
||||
> **LEZIONE CRITICA (2026-05-28).** L'intera famiglia squeeze-breakout (SQ01-04,
|
||||
> MT01, ML01, AD01, CM01, PD01) è stata **scartata in `scripts/waste/`**: le
|
||||
> accuratezze storiche 76-82% erano un **artefatto di look-ahead**. Quei backtest
|
||||
> decidono la direzione con `sign(close[i]-close[i-1])` (la candela di breakout `i`)
|
||||
> ma entrano a `close[i-1]` — cioè comprano *prima* della candela che usano per
|
||||
> scegliere la direzione. Dal vivo il worker scopre il breakout solo a `close[i]`
|
||||
> ed entra lì: l'edge sparisce (win-rate ~47%, lancio di moneta). Sotto ingresso
|
||||
> onesto e fee reali **tutte perdono, anche a fee zero**. Inoltre i breakout
|
||||
> *rientrano* (mean-reversion > continuation). Vedi `scripts/analysis/oos_validation.py`
|
||||
> e `intrabar_test.py`.
|
||||
|
||||
Tutte le strategie estendono `src.strategies.base.Strategy`
|
||||
(`generate_signals() → backtest()`). Le strategie mean-reversion condividono
|
||||
`src.strategies.fade_base.FadeStrategy` (backtest intrabar TP/SL/max_bars).
|
||||
**Strategie con edge netto validato OOS fee-aware (tutte fade/mean-reversion):**
|
||||
|
||||
| Codice | Nome | Meccanismo | Edge OOS netto (1h, fee 0.10% RT) | DD | Note |
|
||||
|--------|------|-----------|-----------------------------------|----|------|
|
||||
| **MR01** | Bollinger Fade | banda std attorno a SMA | BTC +201% / ETH +1238% | 15-72% | Fada la banda, TP alla media, SL ad ATR |
|
||||
| **MR02** | Donchian Fade | estremi canale H/L | BTC +172% / ETH enorme | 30-42% | Fada la rottura del canale, TP al centro |
|
||||
| **MR07** | Return Reversal | z dei rendimenti di barra | BTC +105% / ETH +195% | 25-46% | Fada il movimento estremo, exit in ATR; esposizione ~8% |
|
||||
|
||||
> **MR03 Keltner Fade** spostata in `scripts/waste/`: era la fade più debole
|
||||
> (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa
|
||||
> idea di banda). Rimuoverla dal portafoglio ne ha *migliorato* le metriche.
|
||||
> La funzione `keltner_fade` resta in `strategy_research_v2.py` come record.
|
||||
|
||||
**Lezione confermata:** l'edge è sempre *mean-reversion* (i breakout rientrano).
|
||||
Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro
|
||||
(RSI revert, ADX-filtered fade) perdono netti → restano scartati.
|
||||
|
||||
Ogni strategia è robusta su **tutta** la sua griglia parametri (entrambi gli asset
|
||||
→ tutte positive OOS) e su **tutte** le fee 0.00-0.20% RT (margine ampio).
|
||||
MR01 validato col worker reale: BTC +196% / ETH +251% OOS (nov 2023→mag 2026).
|
||||
Ricerca completa: `scripts/analysis/strategy_research.py` (MR01) e
|
||||
`scripts/analysis/strategy_research_v2.py` (MR02/MR03/MR07).
|
||||
Validazione live-path: `scripts/analysis/oos_validation.py`.
|
||||
|
||||
**Filtro trend (riduzione DD + aumento Acc).** Tutte le fade accettano i parametri
|
||||
opzionali `trend_max` / `ema_long`: saltano i segnali quando il prezzo è troppo
|
||||
esteso rispetto al trend di fondo (`|close − EMA(ema_long)| / ATR(14) > trend_max`),
|
||||
cioè quando si starebbe fadando un trend/crollo estremo. Con `trend_max=3.0`,
|
||||
`ema_long=200` (default in `strategies.yml`): accuratezza su tutti gli sleeve
|
||||
e DD giù drasticamente su ETH (MR01 71%→26%, MR02 42%→25%, MR03 66%→34%,
|
||||
MR07 46%→21%), edge OOS confermato (vedi `scripts/analysis/risk_management.py`).
|
||||
Unica eccezione: MR03 BTC, dove il filtro peggiora entrambe → lasciato disattivo.
|
||||
Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano).
|
||||
|
||||
**SWAP filtri fade: hurst→trend (2026-06-07).** Il gate PORT06 sul path live
|
||||
(`scripts/analysis/trendmax_port06_impact.py`, parità 1.00000 col canonico) ha mostrato che
|
||||
**post-EXIT-16 il loss-guard Hurst è ridondante-dannoso**: EXIT-16 ha eliminato i wick-stop che
|
||||
hurst evitava → gli ingressi saltati (66% delle barre!) sono in maggioranza tornati vincenti.
|
||||
Su PORT06: LIVE hurst-only FULL Sh 7.23 / OOS 9.35-DD 1.68 vs **TREND-ONLY 7.89 / 9.91-DD 1.20**
|
||||
(domina su tutte le metriche; hurst+trend insieme over-filtra: 7.11, metà dei trade; plateau
|
||||
trend_max 2.5/3.0/3.5 robusto). TREND-ONLY è la config che la ricerca EXIT-16 aveva davvero
|
||||
promosso (entries trend-filtrate, no hurst) e che il live non aveva mai eseguito. **Live ora:
|
||||
`trend_max=3.0, ema_long=200` nelle 6 fade di `_defs.py`, `hurst_max` rimosso** (la maschera resta
|
||||
in `fade_base`). Monitor: `hourly_report` traccia lo stop-rate per epoca PRE→HURST→TREND.
|
||||
Lezione: ri-gateare ogni filtro quando cambia l'exit engine. Diario
|
||||
`docs/diary/2026-06-07-trendmax-gate.md`. Il paragrafo sotto resta come record storico:
|
||||
|
||||
**Loss-guard Hurst (storico: live dal 2026-06-02 al 2026-06-07, poi sostituito dal filtro trend).** Le fade accettano `hurst_max`: saltano i segnali in
|
||||
regime PERSISTENTE/trending (rolling-Hurst ≥ soglia), dove si concentrano stop-loss e perdite
|
||||
(diagnosi: stop-rate 43% per hurst>0.55 vs 21% anti-persistente; i peggiori 1% trade hanno hurst
|
||||
medio 0.61). Helper `src.strategies.fade_base.hurst_skip_mask` (rolling-Hurst causale **dalle sole
|
||||
close** → nessun feed esterno; step=6 per velocità live). **`hurst_max=0.55` attivo sulle 6 fade in
|
||||
`_defs.py`**: il test decisivo a livello PORT06 lo conferma — **FULL DD 4.10%→2.39% (quasi dimezzato),
|
||||
Sharpe 6.62→6.76, OOS Sharpe 8.89→9.15**. È l'UNICO meccanismo anti-perdite che supera il gate (ADX,
|
||||
vol-expansion/vratio, efficiency-ratio, time-stop, vol-target FALLISCONO: tagliano i winner insieme ai
|
||||
loser; i claim esterni ADX/ATR-ratio non si replicano su queste fade crypto). NB: il filtro agisce solo
|
||||
sul path LIVE (`spec.params`); il backtest canonico (`build_everything`/regression-lock) NON è filtrato
|
||||
→ il live farà MEGLIO del backtest sul DD. Ricerca: `scripts/analysis/fade_lossguard_workflow.js`,
|
||||
diagnosi `fade_loss_by_regime.py`, diario `docs/diary/2026-06-02-fade-lossguard.md`.
|
||||
**Effetto misurato (backtest):** stop-loss fade −67% in numero (1881→621), perdite totali −68%, coda
|
||||
−61%→−48% (lo stop-RATE per-trade scende poco, 42→38%: il guard lavora riducendo l'ESPOSIZIONE nel
|
||||
regime tossico, non rendendo sicuro ogni trade). **Monitor live:** `hourly_report.py` traccia lo
|
||||
stop-rate fade PRIMA/DOPO l'attivazione (14:34 UTC del 2026-06-02) e dà il verdetto su Telegram quando
|
||||
il campione DOPO ≥30 (già confermato: stop-rate live PRIMA 42% == backtest 42.1%).
|
||||
|
||||
**FIX EXIT-16 live — confirm su barra COMPLETATA (2026-06-05).** Il worker valutava il confirm-SL
|
||||
sul prezzo della candela IN FORMAZIONE ad ogni poll → reintroduceva la wick-sensitivity che EXIT-16
|
||||
elimina (audit: 2 stop su 3 del crash ETH erano wick-stop che il backtest non avrebbe preso in quel
|
||||
momento). Ora `tick` valuta il confirm SOLO sul close dell'ultima barra completata (detection: la
|
||||
riga -1 del df è la candela in corso finché `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. La concausa
|
||||
feed-gap NON è mitigabile lato exit (fill reali ≈ sim) e l'entry-guard post-flat è BOCCIATA (skippare
|
||||
i segnali dopo barre flat PEGGIORA tutti gli sleeve ETH: la candela-gap è l'overshoot che la fade
|
||||
fada). Aggiunto alert Telegram `STALE_FEED` (≥2 barre 1h flat → notifica + gap % al risveglio, solo
|
||||
osservabilità). Diario `docs/diary/2026-06-05-confirm-sl-forming-bar.md`.
|
||||
|
||||
**EXIT-16 close-confirm SL (ATTIVO LIVE, 2026-06-04; esteso a DIP01 il 2026-06-07).** Le fade E
|
||||
DIP01 accettano `sl_confirm_atr` (live: 0.5 in `_defs.py`): lo SL **intrabar è disattivato** e lo stop scatta solo se il **CLOSE** della barra
|
||||
sfonda `sl ∓ 0.5·ATR14`, con uscita al close (TP intrabar al livello e max_bars invariati; in modalità
|
||||
confirm il TP ha priorità nel bar). Scoperta della ricerca exit-lab (34 agenti, 23 famiglie esplorate +
|
||||
10 verifiche avversariali + test PORT06): **gli stop intrabar da wick sono falsi negativi** — l'overshoot
|
||||
che buca lo stop e rientra è esattamente il movimento che la fade fada. Verificato: indipendente dal
|
||||
loss-guard Hurst, plateau buffer 0.4-1.0, regge fee 2x/lag/slippage, coda ≈ base nei crash veri (FTX:
|
||||
+2.4% vs −39% del no-SL puro). **PORT06: FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS Sharpe 8.82→10.06 DD
|
||||
1.30→1.15.** La famiglia "cavalca il prezzo" (trailing/ride/partial-runner, 15 varianti) è invece tutta
|
||||
SCARTATA: oltre il TP=media non c'è coda (4ª conferma). Collaterali: l'engine intrabar filla gli stop
|
||||
"al livello" anche su gap-through (54% dei casi per stop tight) → bias PRO stop-stretti nelle ricerche
|
||||
future; mai deployare strategie con `sl=0` (il fallback −2% del worker non si applicherebbe). Harness
|
||||
riusabile `scripts/analysis/exit_lab.py` + policy in `exit_policies/`. Implementazione: `fade_base.backtest`
|
||||
+ `StrategyWorker.tick` (param `sl_confirm_atr`, None = comportamento storico; il backtest canonico
|
||||
`build_everything` resta NON filtrato → il live farà meglio del backtest, come per il loss-guard).
|
||||
Diario `docs/diary/2026-06-04-exit-lab.md`.
|
||||
|
||||
**Portafoglio.** Diversificare su sotto-conti indipendenti equipesati (le 4 strategie
|
||||
× BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul
|
||||
paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown.
|
||||
|
||||
**Combinare le due famiglie (fade + honest).** Le fade (reversione intraday 1h) e le
|
||||
honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate**
|
||||
(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il
|
||||
rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve
|
||||
→ DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD /
|
||||
2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in
|
||||
`scripts/analysis/combine_portfolio.py`.
|
||||
|
||||
**ROT02 — riduzione DD (top_k 2→3).** La rotazione dual-momentum honest concentrava
|
||||
il book su 2 soli asset (DD 40%). Diversificare su 3 (`top_k=3`) dimezza quasi il DD
|
||||
(40%→26%) e *alza* pure il ritorno full (+1095%→+1303%, ret/DD da 27 a 50); il
|
||||
vol-target abbassa il DD ma sacrifica ritorno, quindi si tiene top_k=3 senza VT.
|
||||
Applicato a `ROT02_dual_momentum.py` e a `_rot_daily_equity` (usata dai portafogli).
|
||||
|
||||
**Portafogli pronti (artefatti accorpati e migliorati).** Oltre a `PORT01` (solo
|
||||
honest), due script in `scripts/strategies/`:
|
||||
- `PORT02_fade_master.py` — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend),
|
||||
equal-weight daily: DD ~8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
|
||||
- `PORT03_all_master.py` — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti:
|
||||
`equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e `5050` fra le due
|
||||
famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata.
|
||||
Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con
|
||||
`generate_signals`, quindi non nel `strategy_loader`.
|
||||
|
||||
**Esplorazione famiglie alternative (branch `strategy_explore`, 2026-05-29).** Esplorate
|
||||
9 famiglie nuove con agenti paralleli su harness onesto condiviso
|
||||
(`scripts/analysis/explore_lab.py`). 7 sono rumore (rifiutate: stagionalità oraria/mensile,
|
||||
cross-sectional reversal, opening-range breakout, lead-lag BTC→alt, continuation intraday —
|
||||
quest'ultima riconferma la dominanza mean-reversion). Due edge reali:
|
||||
- **PR01 Pairs** (`scripts/strategies/PR01_pairs_reversion.py`): spread reversion
|
||||
market-neutral sul log-ratio z-score, **config UNIVERSALE** `n=50 z_in=2.0 z_exit=0.75
|
||||
max_bars=72` (anti-overfit, niente tuning per-coppia). **5 coppie robuste**: ETH/BTC
|
||||
(Sharpe 4.36), LTC/ETH (3.08), ADA/ETH (2.69), BTC/LTC (2.36, robusta anche 4h), ETH/SOL
|
||||
(1.96, la più debole). Pattern: sempre alt-liquido vs major. Plateau confermato
|
||||
(heatmap 20/20 Sharpe>1) + walk-forward (ETH/BTC 11/12 finestre+). **BNB/ETH scartata**
|
||||
(overfit). Corr col mercato ~0.02-0.08. Fee su **2 gambe**: worker live implementato
|
||||
(`src/live/pairs_worker.py`, sezione `pairs:` in `strategies.yml`). LOGICA validata
|
||||
(`validate_worker_pairs.py`: replay == backtest ESATTO). LIVE (`live_smoke_pairs.py`,
|
||||
smoke reale Cerbero): **tutte e 5 le coppie con feed live fresco**. Naming Deribit:
|
||||
BTC/ETH = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC,
|
||||
storia dal 2022). Trappola: `LTC-PERPETUAL`/`SOL-PERPETUAL` danno vuoto/dati errati →
|
||||
usare sempre `_USDC-PERPETUAL`. Resta da verificare solo liquidità/fill in esecuzione.
|
||||
Verifica edge: `pairs_research.py`.
|
||||
- **TSM01** (`scripts/analysis/tsmom_research.py`): TSMOM multi-orizzonte 3/6/12m + risk-off,
|
||||
**gross 0.30**, distinto da ROT02 (corr 0.62), DD 15-22%, mai un anno negativo. Robusto
|
||||
(36/36 config OOS+) ma diversificatore, non motore di ritorno (rende meno di ROT02).
|
||||
|
||||
Aggiungere i **5 pairs** al MASTER (quasi scorrelati, ~0.02-0.09) è il free-lunch più
|
||||
grande (`scripts/analysis/combine_v2.py`). **Numeri sobri onesti** (l'OOS singolo 2024-25
|
||||
è regime calmo → ottimistico ~50%): worst-DD su 90g rolling **~6%** (non 2.3%), Sharpe
|
||||
atteso **~5** (mediana semestrale), ogni anno positivo dal 2021, regge **leva 2x +
|
||||
slippage doppio** (CAGR 36%, Sharpe 5.1). Config robusta raccomandata: **MASTER-esteso
|
||||
equal-weight, leva 2x, cap pairs ~30-35%** (i pairs sono ~57% del rischio; worker live a
|
||||
2 gambe implementato, validato e con feed live su tutte e 5 le coppie — resta da
|
||||
verificare liquidità/fill in esecuzione reale). La confluenza multi-TF è stata SCARTATA (overfit).
|
||||
|
||||
**Pattern del segnale per FORMA (branch `shape_patterns`, 2026-05-29).** Esplorate 5 famiglie
|
||||
di *shape forecasting* con agenti paralleli su harness onesto (`scripts/analysis/shape_lab.py`:
|
||||
analog kNN causale, no-look-ahead verificato). **4/5 sono RUMORE** (riconfermano la dominanza
|
||||
mean-reversion): analog kNN sulla forma grezza (solo BTC-overfit), encoding candele
|
||||
UP/DOWN/DOJI+body/shadow (hit-rate ~50%), DTW+template geometrici (DTW *peggiora* l'euclidea;
|
||||
template overfit), PIP/pivot/zig-zag (0/48 robuste). Vedi `scripts/analysis/shape_*_research.py`.
|
||||
- **SH01 Shape-ML** (`scripts/strategies/SH01_shape_ml.py`): UNICO edge. Una LogisticRegression
|
||||
legge 17 feature di forma (body/shadow, rendimenti, pendenza/curvatura, pos max/min, RSI,
|
||||
estensione) e predice il segno del rendimento a H barre in **walk-forward** (scaler+modello
|
||||
solo sul passato, no leakage). Config **W24 H12 th0.58**. A differenza dello squeeze
|
||||
**regge fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria**, non nella frequenza.
|
||||
Validazione (`scripts/analysis/shape_ml_validate.py`): BTC robusto OVUNQUE (expanding +219%/
|
||||
OOS +42% Sharpe 2.72 8-9 anni; rolling 2y +166%/+96%; stress 2x+slippage OK), ETH/ADA
|
||||
robusti solo expanding (secondari), LTC/SOL/XRP scartati. Griglia: **5/27 celle robuste su
|
||||
cresta stretta W24/H8-12** → overfit moderato, scelta la config conservativa. **Valore vero:
|
||||
diversificatore** (corr +0.08 col MASTER); aggiungerlo migliora l'OOS del MASTER (Sharpe
|
||||
4.33→5.10, DD 4.7%→4.2%). NON motore standalone. **LIVE (2026-06-01): gira come StrategyWorker
|
||||
reale** (vedi fix wiring sotto in SCOPE LIVE). Diario: `docs/diary/2026-05-29-shape.md`.
|
||||
|
||||
**ARGO / GEX opzioni (analisi 2026-06-01, SCARTATO).** Valutato ARGO (lettura del gamma-exposure dei
|
||||
dealer Deribit) come filtro di regime. Esito **NO-GO**: il net-GEX si calcola live (Deribit mainnet
|
||||
public API, OI reale ~368k contratti, DVOL/funding storici gratis) ma **lo storico per-strike dell'OI
|
||||
non è gratuito → non backtestabile OOS** (stesso muro delle opzioni W18/19/21). Niente evidenza crypto,
|
||||
segno fragile, mercato dominato dai perp. Diario `docs/diary/2026-06-01-argo-gex-feasibility.md`.
|
||||
|
||||
**Storico opzioni REALE da cerbero-bite (2026-06-09, il muro ARGO è caduto per il periodo recente).**
|
||||
Il container **cerbero-bite** (accanto, `/opt/docker/cerbero-bite`) accumula in continuo lo **storico
|
||||
per-strike** della catena opzioni Deribit (BTC+ETH) nella tabella `option_chain_snapshots` del suo
|
||||
SQLite (`bite-data:/app/data/state.sqlite`, root-only): bid/ask/mid/**IV per-strike**/greche/OI/volume,
|
||||
**dal 2026-05-01**, cadenza ~12 min (~110k righe/asset). È esattamente il dato che ARGO/W18-21
|
||||
credevano non-gratuito. `scripts/analysis/options_fetcher.py` lo importa via `docker exec` →
|
||||
`data/options/{eth,btc}_chain.parquet`; `scripts/analysis/options_chain.py` (`OptionChain`) espone
|
||||
loader + `skew_curve()` / `premium_levels()` (aggregati ROBUSTI) + `quote()` causale best-effort.
|
||||
**Caveat granularità:** cerbero-bite snapshotta una **fetta rotante** (~1 scadenza per ciclo) → ottimo
|
||||
per skew/premi **aggregati**, limitato per il pricing **per-trade** preciso (usare la finestra di
|
||||
staleness). **Pannello regime `market_snapshots`** (anch'esso importato → `data/options/market_snapshots.parquet`,
|
||||
loader `options_chain.load_market(asset)`; merge causale su prezzo via `options_chain.attach_market(df, asset)`):
|
||||
feature REALI pre-calcolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross,
|
||||
`dealer_net_gamma` (net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**.
|
||||
È il segnale ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (lo
|
||||
`spot` elimina il proxy ATM). **Copertura reale:** spot/dvol dal 2026-03-26 (sparse/daily fino a fine
|
||||
aprile), **net-GEX denso orario solo da ~2026-05-01** → ~5-6 settimane, **un singolo regime calmo**:
|
||||
analisi esplorativa OK ma NIENTE edge validabile ora (i prior GEX/VRP non si replicano su 9 sett.;
|
||||
`liquidation_risk` costante 'low'). Valore FORWARD: rivalutare quando il pannello attraversa
|
||||
gamma-flip/liquidazioni/crash con held-out. **NB look-ahead:** usare `attach_market` (merge_asof
|
||||
causale, NaN prima della copertura), MAI `astype(int64)` su un timestamp datetime (darebbe ns →
|
||||
match all'ultimo snapshot = leak). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6
|
||||
come sembrava da un singolo snapshot a 1g, illiquido); premio reale put 10% OTM ≈ **1.0%/mese** (0.65%/sett),
|
||||
catastrofe 15%+ OTM ≈ 0.33%/mese. **Vincolo strutturale:** gli strike **10% OTM a 24h NON esistono**
|
||||
(Deribit lista solo near-ATM sui tenor corti) → un overlay **per-trade a 24h è infattibile**; l'unica
|
||||
struttura eseguibile è una **put settimanale/mensile standing** (catastrofe-cap di sleeve), da gateare
|
||||
coi premi reali. Vedi `docs/diary/2026-06-08-mr02eth-replace-search.md`.
|
||||
|
||||
**Frattali del segnale × Regime ARGO (ricerca 100 agenti, 2026-06-02, RECORD).** Cercata una strategia
|
||||
che combini un segnale frattale (Hurst/Higuchi/Williams/analog) con un gate di regime (DVOL/VRP/funding).
|
||||
Infrastruttura riusabile: `scripts/analysis/regime_fetcher.py` (DVOL+funding da Deribit mainnet →
|
||||
**`data/regime/`**, NON `data/raw/` che è solo OHLCV) e `regime_lab.py` (feature regime+frattali causali,
|
||||
cache, harness netto-OOS). Esito: **15 strategie robuste e causali, ma NESSUNA batte/migliora il PORT06**
|
||||
(diversificatori sovrapposti alle fade). **Finding: il prior ARGO "VRP>0=range=fade" è SMENTITO** —
|
||||
l'edge è su **VRP<0 + DVOL bassa**. Il vincitore `FR01_hurst_calm_fade.py` è robusto ma DILUISCE il
|
||||
PORT06 (OOS Sharpe 8.89→8.72) → **non deployato** (in `scripts/strategies/` ma NON in MODULE_MAP/yml).
|
||||
Il sottoprodotto utile è stato il **loss-guard Hurst** (vedi sopra), che invece MIGLIORA il PORT06.
|
||||
Diario `docs/diary/2026-06-02-fractal-argo-search.md`.
|
||||
|
||||
**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze):
|
||||
1. Ingresso eseguibile: direzione e prezzo decisi con dati **fino a `close[i]`**, mai `close[i-1]` con direzione da `i`.
|
||||
2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva.
|
||||
3. Validazione **out-of-sample** (held-out) + robustezza su griglia parametri + sweep fee.
|
||||
4. Crea script in `scripts/strategies/`, aggiungi a `MODULE_MAP` (`strategy_loader.py`) e a `strategies.yml`.
|
||||
|
||||
Strategie scartate storiche in `scripts/waste/` (W01-W28 + la famiglia squeeze).
|
||||
|
||||
**Verso €50/giorno.** Con 4 strategie indipendenti (MR01/MR02/MR03/MR07) × 2 asset
|
||||
(BTC/ETH) su €1000 ciascuna, il PnL medio storico aggregato è ben oltre €50/giorno;
|
||||
ma quei numeri sono backtest a leva 3x su 8 anni e includono anni eccezionali (es.
|
||||
ETH 2024). Stima onesta: il target è *plausibile* su un portafoglio diversificato di
|
||||
queste fade, ma va confermato col paper trader live prima di rischiare capitale reale.
|
||||
|
||||
## Portafogli
|
||||
|
||||
- Un `Portfolio` è un oggetto di prima classe (`src/portfolio/`) con definizione (sleeve + schema pesi) e due facce sulla **STESSA** definizione: `.backtest()` (riusa il builder unico di `sleeves.py` → parità esatta con `report_families`) e live (`PortfolioRunner`: capitale pool condiviso, sizing per peso, ribilancio giornaliero, ledger aggregato in `data/portfolios/{code}/`).
|
||||
- **Schemi peso:** `equal` (default), `cap` (tetto per famiglia, es. pairs 33% — config raccomandata), `inverse_vol`, `cluster_rp` (equal fra cluster naturali poi inverse-vol intra-cluster), `manual`. Definiti in `weighting.py`; la chiave cap è la famiglia (PAIRS/FADE/HONEST/SHAPE/TSM/XSEC).
|
||||
- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33 + shape 0.0588`, **leva 3x** (era 2x fino al 2026-06-12, vedi sotto), ribilancio 1D. Backtest PORT06 canonico (dati al 2026-05-28, pre-cap-shape): FULL Sharpe 6.47 DD 4.10% / OOS Sharpe 8.82 DD 1.30%; **con EXIT-16 close-confirm: FULL 7.84 / 2.60%, OOS 10.06 / 1.15%** (i vecchi 6.07/8.19 erano pre-loss-guard/pre-refresh dati). Col cap SHAPE (2026-06-05): FULL 6.43 / 3.96%, OOS 8.58 / 1.36% — assicurazione sulla coda SH01, vedi sotto. Col BLEND ETH/BTC 15m (2026-06-09, v1.1.16): FULL 7.20 / 3.68%, OOS 9.66 / 1.31% — 18 sleeve. Con XS01 (2026-06-09): OOS Sharpe 10.07, FULL DD 3.46 — 19 sleeve (pool live real-only 15; i 4 book multi-asset TR01/ROT02/TSM01/XS01 girano in statistica). **Con SWAP fade 1h→15m (config live attuale, v1.1.30, 2026-06-12, vedi sotto): FULL Sharpe 8.13 DD 2.47% / OOS Sharpe 10.86 DD 2.09%** (regression-lock `test_backtest_parity_cap`).
|
||||
- **LEVA 3x (v1.1.29, 2026-06-12, scelta utente).** Era 2x ("sobrio") dal primo deploy live. Frontiera ACCEL50 (`scripts/analysis/accel50_research.py`): a Sharpe OOS ~10 il collo di bottiglia è la TAGLIA, non il rischio — lev 2→3 porta gli anni-a-€50/g da 3.3 a 1.9 con FULL DD 3.5→5.2% (modello lineare). PAIRS `position_size_family` 0.20→0.13 per CONSERVARE l'esposizione validata ~0.40 (la leva accelera le famiglie con stop, non i pairs no-stop). Diario `docs/diary/2026-06-12-accel50.md`.
|
||||
- **SWAP fade 1h→15m (v1.1.30, 2026-06-12, scelta utente "swap secco").** Le 6 fade (MR01/02/07 × BTC/ETH) girano a **15m** invece di 1h: stessi `sid` (pesi/alloc/ledger intatti), `tf="15m"` in `_defs.FADE` + `combine_portfolio.FADE_TF` (le due facce backtest/live sulla stessa def). Gate `scripts/analysis/fade15m_port06_gate.py`: parametri 1h NON ri-tunati (anti-overfit), corr daily 15m↔1h media **0.26** (diversificazione vera, non lo stesso edge più veloce), edge ETH regge il flat-entry-skip (non stale-print). SWAP promosso: FULL 7.34→8.13 / OOS 10.07→10.86 (OOS DD 1.48→2.09 accettato). DIP01 resta 1h (non era nel gate). `max_bars=24` ora = 6h; EXIT-16 confirm sulla barra 15m completata. Caveat: MR02_BTC 15m è il più fee-sensitive (fee2x OOS Sh 0.60) → monitorare le fee reali. **TF sweep** (`fade_tf_sweep.py`): 1m/2m chiusi (fee-death + flat 13-26%), 5m no-swap (MR02_BTC muore a fee2x), 10m in watchlist (ADD bocciato: OOS 10.86→10.76). Diari `docs/diary/2026-06-12-fade15m-gate.md` / `-fade-tf-sweep.md`.
|
||||
- **XS01 — Cross-Sectional Reversion (ATTIVO LIVE 2026-06-09; dispersion-gate v1.1.20, 2026-06-10).**
|
||||
Famiglia nuova XSEC, distinta da pairs (pairwise) e fade (single-asset): ogni `hold=12` ore
|
||||
classifica 8 crypto (UNIVERSE8) per rendimento a `lb=48` ore e va long i perdenti relativi /
|
||||
short i vincenti (peso ∝ −(ret − media cross-section)), market-neutral gross 1, fee 0.20%
|
||||
RT/book (turnover 2). Scorrelato ~0 da pairs e fade. FULL Sharpe ~3.3, plateau lb 12-72 ×
|
||||
hold 6-24; cost-sensitive (muore a ~0.35% RT/book). Gate PORT06: OOS Sharpe 9.66→10.07,
|
||||
FULL DD 3.68→3.46. **Dispersion-gate** (`disp_min=0.0313` = p50 TRAIN): entra solo se la
|
||||
std cross-section del momentum ≥ soglia — diagnostica monotona TRAIN+OOS, plateau p30-p70,
|
||||
standalone Sharpe 2.50→3.46 (regge fee 2x), PORT06 OOS 10.07→10.37 a DD pari. Come
|
||||
trend/hurst sulle fade, il gate agisce SOLO sul path live (backtest canonico non filtrato
|
||||
→ il live farà meglio del backtest). 8 gambe → PAPER (niente esecuzione reale a €2k).
|
||||
Worker `CrossSectionalWorker` validato (`validate_xsec_worker`: replay == backtest esatto).
|
||||
**PHASE-TRANCHING (2026-06-11, `tranches=3`)**: la fase del roll non-sovrapposto è arbitraria
|
||||
e da sola muove Sharpe daily FULL 1.52-2.33 / DD 13.8-33% (timing-luck) → live gira con 3
|
||||
sub-book sfasati di hold/3 su capitale comune (PnL/K), ensemble di fase SENZA parametri
|
||||
fittati. Gate `xs01_tranche_gate.py`: plateau K=2 E K=3 promossi (PORT06 OOS Sh 10.07→10.15,
|
||||
DD 1.48→1.38, FULL pari); validatore esteso (K=1 == xsec_sim esatto; K=3 == unione fasi
|
||||
0/4/8 esatto). Solo path live (backtest canonico single-phase). Strategia
|
||||
`scripts/strategies/XS01_cross_sectional.py`, gate `scripts/analysis/xsec_port06_gate.py` /
|
||||
`xs01_dispersion_gate.py`. NB scartata nella stessa tornata: FC01 funding-carry (nessun
|
||||
edge su 6.5 anni). Diari `docs/diary/2026-06-09/10-*`.
|
||||
- **BLEND ETH/BTC 15m flat-skip (ATTIVO LIVE v1.1.16, 2026-06-09).** Sleeve `PR_ETHBTC_15M` accanto al 1h: pairs ETH/BTC a **15m** (n=66, z_in=1.674, z_exit=1.0, max_bars=35) a **mezza size** (`params.position_size=0.10`). **Origine: gioco "Blind Traders"** (100 agenti ciechi su BTC/ETH anonimizzati → riscoprono la mean-reversion, vincitore = spread ETH/BTC 15m; `scripts/games/`). Gate PORT06 sul serio: **non duplicato (corr 1h↔15m = 0.37)**, robusto (16/16 celle Sharpe>1), e l'edge **NON è artefatto delle candele flat ETH 15m** (16% storico; filtrandole resta l'83% dello Sharpe). Engine `pairs_research.pairs_sim_flat` con uscita **LIVE-REALIZABLE** (`flat_skip`: niente entry/exit su barre O=H=L=C, esce alla 1a barra pulita), **regression-lock** a `pairs_sim` con flat_skip=False. **Worker validato** (`validate_worker_pairs` a 15m: replay == backtest, 8452 vs 8453 trade; 1h byte-exact). Runner: fetch sub-orario diretto da Cerbero + `pos_for_spec` override **per-sleeve** (params.position_size > famiglia > globale). Mezza size perché a peso pieno il 15m pesava il 25.8% del rischio PORT06 (→ 11.5%, bilanciato col 1h) — blend-tilt prudente sul **caveat slippage** (a slippage realistico il vantaggio di Sharpe regge, quello di DD si assottiglia: il vero banco di prova è il ledger reale shadow). Diari `docs/diary/2026-06-09-pairs15m-*.md`, gate `scripts/analysis/pairs15m_port06_gate.py` / `pairs15m_gate_final.py` / `pairs15m_flatcheck.py`, smoke `pairs15m_live_smoke.py`.
|
||||
- **SH01 SENZA STOP-LOSS — by design, CONFERMATO da ricerca (2026-06-05).** Dopo il crash ETH (−15.6% su un trade SH01 live), ricerca multi-agente con harness dedicato `scripts/analysis/sh01_exit_lab.py` (cache segnali walk-forward, engine con **fill gap-aware** `worse(livello, open)`, parity esatta con explore_lab, protocollo train≤2023-11-01/OOS): **11 famiglie di stop testate (ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop, disaster-cap close+intrabar, swing, vol-regime), 0 sopravvissute** al gate (ETH migliorato senza degradare BTC, train E oos, plateau). Pattern: ogni stop stretto abbastanza da toccare la coda ETH rompe BTC; ogni stop largo non arriva alla coda; nei crash il fill è al gap, non al livello (lo stop "protettivo" PEGGIORA la coda OOS). Mitigazione adottata: **cap famiglia SHAPE a 0.0588 in PORT06** (≈ dimezzata; costo OOS Sharpe −0.24, FULL DD −0.14pp) — la prossima coda impatta il conto per metà. NON impostare mai `sl`/`sl_confirm_atr` su SH_BTC/SH_ETH. Direzione futura: liquidity-gate sull'entry (skip dopo feed flat). Diario `docs/diary/2026-06-05-sh01-sl-research.md`.
|
||||
- **Data layer Cerbero v2:** `get_historical_v2` unificato + `get_instruments` (naming robusto) + `get_ticker_batch`. Trading su Deribit.
|
||||
- **SCOPE LIVE (fase 2 completata):** il runner esegue TUTTI gli sleeve di PORT06. Worker: single `StrategyWorker` (fade MR01/02/07, DIP01, **e SH01**), `PairsWorker` (PR01 2 gambe), e i multi-asset dedicati `BasketTrendWorker` (TR01 4h), `RotationWorker` (ROT02 1d), `TsmomWorker` (TSM01 1d). Il runner fetcha 1h da Cerbero v2 e **resampla a 4h/1d** (lookback dimensionato sui daily: TSM01 usa 252g). Validazione: runner pool/ribilancio/ledger == backtest (`validate_portfolio_runner.py`, identico); worker multi-asset == reference (`validate_honest_workers.py`: TSM01 esatto, ROT02 +1303% canonico, TR01 +32% vs +42% — residuo di convenzione capitale-unico vs media-equity; il bug `mean(rets)` sui soli asset in posizione, che dava −44%, è FIXATO il 2026-06-11: sovrappesava N/k a paniere parziale).
|
||||
- **SH01 bootstrap full-history (punto-10, 2026-06-07).** La ri-validazione col train-window del
|
||||
regime live (`sh01_trainwindow_validate.py`) ha mostrato che SH01 a train 365g **NON è robusto**
|
||||
(BTC fee-2x FULL −42%, ETH Sharpe −0.02, trade-rate 22-26% vs 10% validato: LogReg over-confident,
|
||||
soglia inerte — la diagnosi sweep era esatta). L'edge è MONOTONO nella memoria: solo l'expanding
|
||||
full-history passa il gate. Fix live: il runner passa agli sleeve `ml` la storia FULL
|
||||
(`_with_history`: parquet locale + feed, gap-guard con WARN) e `ml_wf_entries(last_block_only=True)`
|
||||
fitta SOLO l'ultimo blocco del walk-forward → segnali **identici per costruzione** al WF completo
|
||||
(parity test esatto), 0.6 s/tick su 73k barre. Manutenzione: tenere fresco il parquet
|
||||
(`download_all()`). MAI ri-tunare la soglia nel regime corto (instabile/incoerente fra asset).
|
||||
Diario `docs/diary/2026-06-07-sh01-trainwindow.md`.
|
||||
- **FIX SH01 wiring (2026-06-01).** SH01 gira come **`StrategyWorker` NORMALE** (NON il vecchio `MLWorkerWrapper` di `multi_runner`, che usava il `SignalEngine` **squeeze SCARTATO**: apriva senza metadata ed usciva a `hold_bars=3`, ignorando del tutto SH01_shape_ml). `SH01_shape_ml.generate_signals` fa il walk-forward (retraining) internamente ad ogni tick ed emette `metadata.max_bars=12` → exit a orizzonte via `StrategyWorker.tick`. Serve ≥4000 barre 1h (`_ML_LOOKBACK_DAYS=365`). Vedi `docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md`.
|
||||
- **Altri fix StrategyWorker (2026-06-01).** Exit a orizzonte puro per strategie senza TP/SL (`elif self.max_bars`, SH01 esce a H=12 non hold_bars=3); `is_win = net > 0` (win NETTO fee, non lordo); filtro `min_tp_frac` (salta micro-scalp col TP entro le fee); loss-guard `hurst_max=0.55` sulle fade (vedi sopra).
|
||||
- **Exit intrabar (fase 3, risolto):** lo `StrategyWorker` ora esce sui TP/SL toccati INTRABAR (high/low della barra, al livello, SL prioritario) come il backtest — non più solo sul close. Allinea fade/DIP01 live al backtest intrabar (`tests/portfolio/test_intrabar_exit.py`). Caveat residuo onesto: nel paper trading l'high/low usato è quello della barra in corso al poll; su un fill reale conterebbe il momento del tocco.
|
||||
- **ESECUZIONE REALE — pairs 2 gambe + SH01 (2026-06-08).** Estesa oltre i fade: i **5 pairs PR01**
|
||||
eseguono reale a 2 gambe (`PairsExecutionClient`: open/close long A/short B, leg-risk unwind, MAI
|
||||
close_position; `pairs_enabled: true` acceso a conto flat, v1.1.12); **SH01** (BTC/ETH) esegue
|
||||
single-leg con exit a orizzonte H=12 — niente TP/SL, `_place_real_tp` no-op e `_real_close` chiude
|
||||
tutto market reduce-only, disaster-bracket on-book come unica protezione di coda (v1.1.13). Motivo:
|
||||
SH01 è il diversificatore più decorrelato (senza i 5 sleeve PAPER il DD del portafoglio sale
|
||||
3.96→5.35%). Copertura reale ora 15 sleeve su 19 (fade+DIP+**6 pairs incl. ETH/BTC 15m**+SH01);
|
||||
restano simulati TR01/ROT02/TSM01/XS01 (book multi-asset, bloccati dal capitale: rumore
|
||||
arrotondamento 20-30% a €2k, serve ~€20k). Diari `docs/diary/2026-06-08-pairs-executor.md`.
|
||||
- **ESECUZIONE REALE — shadow (v1.0.3, 2026-06-03).** I **6 fade** (MR01/MR02/MR07 × BTC/ETH) eseguono ordini **REALI su Deribit testnet** accanto al fill simulato (*shadow*: sim + reale in parallelo, il sim resta la verità che guida le decisioni). `src/live/execution.py` `ExecutionClient`: `open` (market) + `close_amount` (market **reduce-only della SOLA quota del worker** — i 3 fade BTC condividono lo strumento e le posizioni si nettano per conto, quindi NON si usa `close_position` che flatterebbe le quote altrui); **verifica l'esecuzione sul TRADE** (order_id in `get_trade_history`, non sulla size netta aggregata); **fee REALI lette dai `trades[]`**. Strumenti = **lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`, amount nel base-coin, step 0.0001/0.001): scelti perché il **payoff lineare == matematica del backtest** (l'inverse `*-PERPETUAL` introdurrebbe una base 1/prezzo) e fee/PnL sono in USDC. Lo `StrategyWorker` tiene un **ledger reale parallelo** (`real_capital`, persistito) e logga `REAL_OPEN`/`REAL_CLOSE` col confronto **slippage** (prezzo sim vs eseguito) e **fee** (assunta 0.10% vs reale). Config: `portfolios.yml` → `overrides.execution {enabled, sleeves:[MR01,MR02,MR07,DIP01], instruments:{BTC:BTC_USDC-PERPETUAL, ETH:ETH_USDC-PERPETUAL}}` (**DIP01 aggiunto il 2026-06-04**: stesso wiring single-leg, TP resting incluso); pairs/rotation/tsmom/shape restano **simulati** (pairs richiede un executor a 2 gambe con gestione leg-risk; shape non ha TP). **Fee reali misurate = 0.05%/lato = 0.10% RT** (== assunto del backtest, su ETH; BTC inverse era ~0.094%). **Alert Telegram:** `REAL_EXEC_LIVE` (primo ordine reale verificato per worker) + `REAL_OPEN_FAIL`. Smoke (testnet, €0): `scripts/analysis/live_exec_smoke.py` (layer: ordine→verifica→fee) e `live_shadow_smoke.py` (catena worker open/close). **Capitale live portato a 2000** (notional fade ~$35) per ridurre il rumore di arrotondamento su BTC (step lineare ~$6.7). NB: ledger reale ≠ ledger sim — i worker già in posizione sim a un restart non hanno quota reale corrispondente; lo shadow reale parte pulito dalla prossima apertura.
|
||||
- **Disaster-bracket on-book + alert outage (v1.1.4, 2026-06-07).** A ogni `REAL_OPEN` dei fade
|
||||
eseguiti il worker piazza uno **STOP_MARKET reduce-only a ~−30%** dall'ingresso (trigger sul mark,
|
||||
`ExecutionClient.place_disaster_sl`, cancellato in `_real_close`): assicurazione per gli outage
|
||||
(poll-loop fermo = exit non valutati), in operatività normale non scatta mai → 0 costo Sharpe.
|
||||
Config `overrides.execution.disaster_sl_pct` (0.30; 0=off). NB: il `set_stop_loss` di cerbero-mcp
|
||||
è un `private/edit` (solo ordini APERTI) → inutilizzabile su market fillati; la cancel di un
|
||||
trigger order risponde `untriggered` (= successo, verificato testnet). Alert Telegram `FEED_OUTAGE`
|
||||
dopo 5 poll falliti consecutivi (con elenco posizioni reali aperte) + notifica di ripresa. I fix
|
||||
di parità della stessa tornata (v1.1.3): TR01 fee×leva + forming-bar TR01/Pairs + WARN
|
||||
`PANEL_SHORT` su TSM01/ROT02; `hourly_report` ora mostra i multi-asset (sezione MULTI-ASSET).
|
||||
Diario `docs/diary/2026-06-07-sweep-fixes.md`.
|
||||
- **VERITÀ CONTABILE su netting di conto (v1.1.24, 2026-06-11, da audit live).** Il modello
|
||||
"quote per-worker con reduce-only" si rompe quando worker dello stesso strumento hanno
|
||||
direzioni OPPOSTE (pairs long ETH vs fade short ETH): Deribit cappa/respinge i reduce-only
|
||||
in silenzio. L'audit ha trovato: close fillato 0.078 ma bookato 0.105 (`Fill.amount` era il
|
||||
RICHIESTO), 3 gambe pairs mai eseguite col PnL sim sommato al ledger reale (gamba orfana sul
|
||||
conto, ETH/SOL di fatto short nudo), e il conto short 0.027 ETH più dei libri (riallineato a
|
||||
mano). Fix: (1) `Fill.filled_amount` (fonte `order.filled_amount`) usato da TUTTI i ledger;
|
||||
(2) `REAL_CLOSE_PARTIAL` (log+alert) quando il close filla meno del residuo; (3) pairs:
|
||||
PnL bookato SOLO per le gambe verificate, gamba respinta → `orphan_legs` persistito +
|
||||
alert `PAIR_LEG_ORPHAN`, `applied` solo con ENTRAMBE le gambe (altrimenti sim_fallback
|
||||
dichiarato); (4) `REAL_DIVERGENCE` anche su jsonl (prima solo Telegram); (5) runner:
|
||||
tick isolato per-worker (un'eccezione non salta gli altri; alert `WORKER_ERROR_STREAK` a 5).
|
||||
**RISOLTO in v1.1.25 col NETTING delle chiusure**: `close_amount` tenta il reduce-only
|
||||
(sicurezza storica: un bug di stato filla 0) e riesegue il RESIDUO cappato/respinto in
|
||||
**market puro** — che muove il conto esattamente del delta del libro, cioè netta contro le
|
||||
quote opposte → niente più gambe orfane/close cappati per costruzione (copre anche i pairs
|
||||
via `close_pair`). Il chiamante riceve UN Fill combinato (prezzo medio pesato, fee sommate,
|
||||
`notes` con 'netting'); evento `NET_CLOSE` (log+Telegram) a ogni fallback. La sicurezza
|
||||
persa sul residuo è coperta dal **reconciler orario** (`reconcile_account.py`, cron host
|
||||
:40, alert `ACCOUNT_DRIFT`): conto vs Σ libri+orfani, tolleranza 1.5×step, anti-race.
|
||||
`orphan_legs`/`REAL_CLOSE_PARTIAL` restano come ultima difesa (se fallisce anche il market
|
||||
puro). Test `tests/portfolio/test_netting_close.py`.
|
||||
- **TP_PHANTOM — il tocco TP va confermato dal book reale (v1.1.23, 2026-06-11).** Il feed
|
||||
testnet stampa wick anomali che (a) generano segnali fade su ETH e (b) "toccano" il TP
|
||||
intrabar della stessa barra: il sim bookava +4% fantasma a bars_held=0 e `_real_close`
|
||||
chiudeva A MERCATO una posizione il cui resting TP non aveva mai fillato (−fee/spread a
|
||||
giro, 14 giri l'11-06, report Telegram 26/0 vs reale 11/15 — fix conteggio in v1.1.22).
|
||||
Gate in `StrategyWorker._tp_phantom` (zero parametri, verita' d'esecuzione, NON un filtro
|
||||
di strategia): tocco sim + **resting LIMIT a zero fill** + prezzo corrente che non ha
|
||||
raggiunto il livello → exit SOPPRESSA (il limit sul book reale e' l'oracolo: se il prezzo
|
||||
avesse scambiato li', avrebbe fillato); SL close-confirm e max_bars restano attivi.
|
||||
Fill (anche parziale) o prezzo oltre il livello o worker non eseguito → comportamento
|
||||
storico. Fail-open su errori di rete. Log `TP_PHANTOM` dedup per barra + alert Telegram.
|
||||
Test `tests/portfolio/test_tp_phantom.py`.
|
||||
- **TP reale = LIMIT reduce-only AL LIVELLO (2026-06-04).** Misurati +235 bps di slippage medio sulle uscite take-profit market-on-poll (sim esce al livello intrabar, il reale chiudeva al poll post-rimbalzo: sim +11.85 vs reale +0.62 USD sui primi 7 close). Fix: a ogni `REAL_OPEN` il worker piazza un **limit reduce-only al TP** (`ExecutionClient.place_tp_limit`, prezzo quantizzato al tick, SOLA quota del worker) → `REAL_TP_RESTING`; a ogni chiusura sim `_real_close` **cancella il resting → riconcilia i fill (anche parziali) via `get_trade_history` per order_id → market reduce-only solo del residuo** → ledger su prezzo combinato. `real_tp_order_id` persistito in `status.json` (resume-safe). Lo **SL resta market-on-poll** (deliberato: i trigger Deribit generano un nuovo order_id al trigger → fill non verificabile per order_id; e sul SL il rimbalzo lavora a favore). Fill da resting = fee **maker ~0%**. Smoke: `live_shadow_smoke.py` (2 scenari, testnet). Diario `docs/diary/2026-06-04-shadow-divergence.md`.
|
||||
- **position_size per-famiglia (2026-06-07).** `portfolios.yml` accetta `position_size_family`
|
||||
(chiave = `weighting.family_of`); plumbing `runner.pos_for_spec`. **PAIRS a 0.20** (esposizione
|
||||
0.40 ≈ il validato 0.45): la famiglia è senza stop e col globale 0.5×lev2 girava a ~2.2x il
|
||||
validato (ETH/BTC DD grezzo 78% a quella taglia). PORT06 OOS DD 3.40→1.26% al costo di OOS
|
||||
Sharpe 9.05→8.43 — assicurazione come il cap SHAPE. Gate `pairspos_port06_impact.py`,
|
||||
diario `docs/diary/2026-06-07-pairspos-gate.md`. NB (2026-06-11): il **disaster-cap sullo
|
||||
z** (exit se |z|≥z_stop, griglia pre-registrata 3.0-5.0 su tutte le coppie) è **BOCCIATO**
|
||||
— coda e Sharpe OOS peggiorano ovunque (lo stop realizza la perdita al massimo overshoot e
|
||||
l'engine rientra nello spread divergente: churn di fee), **5ª conferma** che gli stop su
|
||||
mean-reversion sono falsi negativi. Record `scripts/analysis/pairs_zstop_research.py`,
|
||||
diario `docs/diary/2026-06-11-stability-sweep.md`.
|
||||
- **REAL-TRUTH ledger (2026-06-10, scelta utente).** Con `overrides.execution.real_truth: true`
|
||||
(ATTIVO) il `capital` dei worker eseguiti si aggiorna col **PnL dei FILL REALI** (fee reali
|
||||
incluse) invece del PnL sim: `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)` e
|
||||
`_close_position`/`_close` applicano il reale al ledger; il sim resta SOLO diagnostica nel log
|
||||
CLOSE (`pnl_source`/`sim_pnl`/`real_pnl`). Fallback al sim dichiarato (`pnl_source=sim_fallback`)
|
||||
solo se il trade reale non è mai esistito/fillato (REAL_OPEN_FAIL/leg-fail). Equity → pesi →
|
||||
allocazioni → notional derivano così dai soldi veri sul conto (il notional reale era GIÀ la
|
||||
formula sim `capital·ps·lev`; il gap storico sim/reale era contabile: ledger separati + spike
|
||||
print delle candele testnet che il sim bookava e il reale no). Le DECISIONI (entry/exit) restano
|
||||
guidate dal feed; i multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione. Test:
|
||||
`tests/portfolio/test_real_truth.py`. Diario `docs/diary/2026-06-10-real-truth-ledger.md`.
|
||||
- **RIBILANCIO CONSERVA L'EQUITY (fix v1.1.31, 2026-06-13).** Il ribilancio giornaliero
|
||||
gonfiava l'equity quando un worker era in posizione: i flat si dividevano l'INTERO `total`
|
||||
(che includeva il capitale degli in-position) e gli in-position lo trattenevano in più →
|
||||
doppio conteggio di Σ(capital−alloc) sugli in-pos. Caso reale: MR02_BTC 15m seedato a 181.19
|
||||
(eredità INIT_LINEAGE) e in posizione al ribilancio delle 00:01 → **+4.77 di equity dal nulla**
|
||||
(scoperto perché l'equity saliva senza un trade dietro). Fix: `ledger.allocate(weights, reserved={sid:cap})`
|
||||
— gli in-position TRATTENGONO il loro capitale (deployato, non spostabile), i flat si dividono
|
||||
`total − Σreserved` per peso RINORMALIZZATO → `Σalloc == total` sempre, equity conservata per
|
||||
costruzione. `runner.rebalance_allocations` passa i `reserved` dai worker `in_position`. Parità
|
||||
runner intatta (5.8e-08). Test `test_ledger.py`/`test_runner_rebalance.py`. Diario
|
||||
`docs/diary/2026-06-13-rebalance-conservation.md`.
|
||||
- **INIT_LINEAGE — eredità capitale al cambio timeframe (2026-06-12).** Un worker al primo avvio
|
||||
(niente status.json) eredita `capital`/`real_capital` dal worker più recente di STESSA
|
||||
strategia+asset su altro tf (glob `{strategy}__{asset}__*`), MAI la posizione. Nato dallo swap
|
||||
fade 1h→15m: i worker nuovi partivano dall'allocazione del pool scartando il PnL del gemello
|
||||
(−16.8 di equity, riallineata a mano col seed la prima volta). `StrategyWorker._inherit_lineage_capital`,
|
||||
evento `INIT_LINEAGE`. Test `tests/portfolio/test_capital_lineage.py`.
|
||||
- **STALE_REAL_POSITION — guard anti-orfano nel reconciler (v1.1.31, 2026-06-13).** Lo swap
|
||||
1h→15m aveva ritirato MR02_BTC 1h dal config mentre teneva uno short REALE → posizione nuda non
|
||||
gestita per 13h, e il reconciler NON la vedeva (lo status fermo del worker morto contava ancora
|
||||
come "libro" → conto == libri). `reconcile_account.compute_stale_real_positions(max_age_min=15)`:
|
||||
un worker con `real_in_position` e status.json fermo da >15 min = non gestito → alert
|
||||
`STALE_REAL_POSITION`. Discriminante = STALENESS (un worker vivo riscrive ogni poll): cattura
|
||||
ritirati-da-swap, crashati, rimossi dal config. Orfano chiuso a mano (testnet, +0.85). Resta da
|
||||
fare la PREVENZIONE lato runner (flattare/consegnare la posizione del ritirato al boot). Diario
|
||||
`docs/diary/2026-06-13-orphan-swap-guard.md`.
|
||||
- **Limite noto:** al ribilancio le posizioni APERTE restano sul loro notional (non travasate); fedele al backtest daily-rebalanced entro il turnover infragiornaliero.
|
||||
|
||||
## Multi-Strategy Paper Trader
|
||||
|
||||
Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna con €1000 USDC virtuali indipendenti.
|
||||
|
||||
**Config:** `strategies.yml` — due sezioni: `strategies` (single-leg: fade/honest) e
|
||||
`pairs` (a 2 gambe). Attive: 6 fade (MR01/MR02/MR07 × BTC/ETH) + 5 coppie PR01.
|
||||
**Due worker:** `strategy_worker.py` (single-leg) e `pairs_worker.py` (2 gambe,
|
||||
long A / short B sullo z-score del log-ratio, fee su 2 gambe).
|
||||
**Persistenza:** `data/paper_trades/{worker_id}/` con `trades.jsonl` (append-only) + `status.json` (resume al restart).
|
||||
**Hot-add:** aggiungi riga YAML → `docker compose restart` → storico intatto.
|
||||
**Exit strategia:** se un `Signal` porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), il worker esce su take-profit/stop-loss/time-limit; i pairs escono su |z|≤z_exit o max_bars.
|
||||
**Naming Deribit (feed live):** major = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Vedi INSTRUMENT_MAP in `multi_runner.py`.
|
||||
**Notifiche:** Telegram per ogni trade (richiede `.env` con `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID`).
|
||||
- **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
|
||||
dato viene prima della strategia; certificare sempre.
|
||||
- **Look-ahead squeeze (storico).** L'intera famiglia squeeze-breakout aveva accuratezze 76-82% che
|
||||
erano artefatto: decideva la direzione con la candela di breakout `i` ma entrava a `close[i-1]`.
|
||||
Con ingresso onesto: lancio di moneta. (Dettagli nei diari in `Old/`.)
|
||||
- **Entry sugli estremi di candela.** Strategie che entrano a `close` quando `close` è all'estremo
|
||||
del range (≤0.1% o ≥99.9%) gonfiano i ritorni in modo irrealistico (ETH 2024: +30.848% → +2.725%
|
||||
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
|
||||
mean-reversion (i breakout rientrano) — MA anche quegli edge erano per lo più artefatto del feed:
|
||||
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
|
||||
|
||||
- Strategie in `scripts/strategies/` con codice univoco (MR01, ...).
|
||||
- Script scartati in `scripts/waste/` (W01-W28 + famiglia squeeze).
|
||||
- Diario in `docs/diary/YYYY-MM-DD.md`. Aggiornare dopo ogni esperimento significativo.
|
||||
- Nessun dato sensibile nei commit (token, chiavi API). Usare `.gitignore`.
|
||||
- Verificare sempre assenza di data leakage prima di fidarsi dei risultati. In particolare: `returns[i-w : i]` include `close[i]` che è un candle nel futuro — usare `returns[i-w : i-1]`.
|
||||
- Strategie in `scripts/strategies/` con codice univoco; scartate documentate nel diario.
|
||||
- Diario in `docs/diary/YYYY-MM-DD.md`, aggiornato dopo ogni esperimento significativo.
|
||||
- **Nessun segreto nei commit** (token/chiavi). `.env` e `.env.mainnet` sono gitignored.
|
||||
- Versionamento: `VERSION` (semver) + `scripts/bump_version.py`. `src/version.py` lo legge.
|
||||
|
||||
## 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]`.
|
||||
- **Fee:** Deribit perp reale = taker ~0.05%/lato (**0.10% round-trip**), maker ~0%. Usare 0.10% RT come baseline (lo 0.20% storico era pessimista 2x). Includere SEMPRE nel backtest: sono vincolo di prim'ordine, molte operazioni = morte per fee. Il worker usa `strategy.fee_rt` (MR01 = 0.001).
|
||||
- **Leva:** testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown.
|
||||
- **GBM:** GradientBoostingClassifier di scikit-learn. Ensemble di alberi decisionali sequenziali. Walk-forward per evitare leakage temporale.
|
||||
- **Cerbero `get_historical` (fix 2026-05-28):** `end_date` come data nuda è inclusivo dell'intera giornata fino all'ultima candela chiusa (es. `end=oggi` arriva fino ad ora, non più a mezzanotte); accettati anche timestamp con orario (`...T14:00:00`, naive=UTC); nessun cap a ~5000 righe (paginazione interna). Il client passa già `end=oggi`, ora corretto. Prima del fix il paper trader restava a zero trade perché il feed era fermo a mezzanotte.
|
||||
- **Dati ETH Deribit 15m:** 14-30%/anno di candele *flat* (O=H=L=C, volume 0, run fino a ~54h) per bassa liquidità del perpetuo. Verificato (2026-05-28): escluderle NON cambia i backtest (Δacc ≤0.5pp) → edge robusto. Resta un caveat operativo (slippage/fill in trading reale, irrilevante per paper). BTC pulito eccetto picco ~8% nel 2024.
|
||||
- **FEED TESTNET INAFFIDABILE — `ETH-PERPETUAL` inverse CONGELATO (2026-06-13).** Il feed di
|
||||
DECISIONE che il runner usa per BTC/ETH (inverse perp, `INSTRUMENT_MAP`) può congelarsi sul
|
||||
testnet: il 13-06 `ETH-PERPETUAL` era fermo a **1661.95 per 12h+** (1 solo valore) mentre il
|
||||
reale (`ETH_USDC-PERPETUAL`, lineare, dove si ESEGUE) si muoveva (gap −1.3%); BTC era vivo.
|
||||
Effetto: tutte le decisioni ETH (SH01_ETH, 3 fade ETH 15m, gambe ETH dei pairs) girano su un
|
||||
prezzo morto → **spiega lo 0-trade delle fade ETH 15m** (feed piatto = niente rottura banda).
|
||||
È un GUASTO testnet, non di strategia: su **mainnet** l'arbitraggio tiene inverse ≈ lineare ≈
|
||||
realtà. Fix testnet possibile (reroute decisione ETH a USDC lineare in `INSTRUMENT_MAP`, con
|
||||
caveat bootstrap SH01) NON applicato — il fix vero è andare a mainnet. Nota: la dashboard mostra
|
||||
entry/mark/PnL **REALI** (non il sim-feed dislocato); il sim resta solo nel modal diagnostico.
|
||||
**Update 2026-06-14: ANCORA congelato a 1661.95 (36h+).** Confermato anche un vincolo strutturale
|
||||
del feed: **Cerbero v2 (`get_historical_v2`) serve SOLO 5m/15m/1h** — 30m/10m danno HTTP 400 in
|
||||
ogni formato (l'endpoint legacy `get_historical` è 404, rimosso). La voce `"30m"` in
|
||||
`runner._SUBHOURLY` era **speculativa, mai testata live** → qualsiasi TF sub-orario oltre 5m/15m
|
||||
va DERIVATO resamplando nel runner (come 4h/1d dal 1h), non fetchato diretto.
|
||||
- **TIMING SWEEP pairs & honest (2026-06-14, NESSUN deploy).** Domanda utente: pairs/honest
|
||||
beneficiano di TF più veloci come le fade (swap 1h→15m)? Esito: **no, niente deployato.** Vincolo
|
||||
dati (alt 1h-only) → sweepabili solo **pairs ETH/BTC** e **DIP01 (BTC)**; TR01/ROT02 fuori scope
|
||||
(multi-asset alt + lente). Il gate full+OOS migliorava PORT06 (+10m/30m/5m su tutte le metriche),
|
||||
MA due muri: (a) **30m/10m feed-blocked** (v2 serve solo 5/15m/1h, vedi sopra); (b) **5m** è
|
||||
nativo ma nel regime ATTUALE è il PEGGIORE (−10.5% 30g, DD 19.5%/180g vs 3% del 1h, Sharpe sotto
|
||||
15m/1h) + flat-share ETH 29% (slippage reale) → guadagno backtest modesto (FULL Sh +0.39) non vale
|
||||
il rischio su soldi reali. **Lezione: il gate PORT06 full+OOS è necessario ma NON sufficiente** —
|
||||
incrociare con (1) la fattibilità del FEED live e (2) 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"). Artefatti: `scripts/analysis/timing_sweep_pairs_honest.py`,
|
||||
diario `docs/diary/2026-06-14-timing-sweep-pairs-honest.md`. (Config live invariata: 19 sleeve.)
|
||||
- **MICRO-TEST MAINNET = il gate per scalare il capitale (piano pronto, 2026-06-13).** Testnet
|
||||
valida solo la MECCANICA (feed/fill farlocchi); l'edge sopravvive ai fill veri? si risponde solo
|
||||
con poco denaro reale su mainnet. **Switch già eseguibile via `.env`**: `CerberoClient` legge il
|
||||
token da env (`CERBERO_TOKEN`, default testnet invariato; `is_mainnet()` helper) → puntare a
|
||||
mainnet = solo `.env`, niente codice. Piano completo (fasi smoke→fade-only €1000 2-4 sett→verdetto
|
||||
ledger-vs-backtest→espansione; sizing: fade €1000 = arrotondamento 2.6% BTC, pairs esclusi al 30%):
|
||||
`docs/specs/mainnet-microtest-plan.md`. **Stato 2026-06-14:** token mainnet (`MAINNET_TOKEN` di
|
||||
cerbero-mcp, che ha già le chiavi Deribit LIVE) **wired e VERIFICATO** — `is_mainnet()=True`, auth
|
||||
OK su `get_account_summary` read-only. Tenuto in **`.env.mainnet`** dedicato (chmod 600, gitignored),
|
||||
FUORI dal `.env` condiviso → il runner testnet NON flippa a mainnet a un riavvio accidentale (il
|
||||
micro-test girerà come **servizio separato** con dir dati pulita + portfolios fade-only, per non
|
||||
mescolare i ledger). NB architettura token: il `MAINNET_TOKEN` è la chiave d'accesso *all'MCP* +
|
||||
selettore d'ambiente (non una chiave Deribit) → il nostro client DEVE presentarlo per essere
|
||||
instradato al live. **UNICO BLOCCO: conto Deribit mainnet VUOTO (€0 USDC/BTC/ETH) → serve deposito
|
||||
~€1000 USDC** prima di Fase 0 smoke. Minimo assoluto €500 (rumore BTC ~5%), raccomandato €1000 (2.6%).
|
||||
Tutto il lavoro pre-reset (preservato in git per consultazione storica): strategie
|
||||
(`Old/scripts/strategies`), stack live e portafogli (`Old/src/live`, `Old/src/portfolio`,
|
||||
`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.
|
||||
```
|
||||
|
||||
+3
-10
@@ -1,18 +1,11 @@
|
||||
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 ./
|
||||
|
||||
COPY 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"]
|
||||
# Monitor PAPER del portafoglio attivo (TP01+XS01). Esecuzione REALE disabilitata.
|
||||
CMD ["uv", "run", "python", "-m", "src.live.dashboard", "--port", "8787"]
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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:00–09: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 (66700–66834):
|
||||
|
||||
```
|
||||
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.7–2.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.0–11.35) **con corr bassa**:
|
||||
|
||||
- **BTC** (15m/30m/1h, range o none): corr **0.15–0.27**, molto più ortogonale della
|
||||
griglia ETH bocciata (0.40). OOS di portafoglio 11.1–11.35 (il guadagno OOS più grosso).
|
||||
- **ETH** (15m/30m/1h): corr **0.21–0.57** (15m la peggiore, fino a 0.57; a 1h scende a
|
||||
0.22–0.39). OOS di portafoglio ~10.9–11.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 (4–7 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 **~54–67%** (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.1–6.6%** (+0.6/+4.0pp,
|
||||
**oltre la soglia tail** in quasi tutte le celle). **A HALF size invece rientra a 2.33–2.54%**
|
||||
(~baseline) preservando l'OOS ~11.0–11.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: **12–33%** (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.37–2.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.22–0.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.43–0.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.16–0.22 = la diversificazione migliore), MA la FULL DD standalone ~54% li rende
|
||||
**tail-risky a full size** (FULL DD portafoglio 3.1–6.6%, oltre soglia). **Deployabili SOLO a
|
||||
half-size** (FULL DD portafoglio 2.33–2.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** (2024–25). 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 ~47–57k. È 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.15–0.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** (|close−EMA200|/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 5–7) 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).
|
||||
@@ -14,16 +14,25 @@ testnet era ancora congelato a 1661.95 da 36h+ → conferma definitiva: testnet
|
||||
|
||||
---
|
||||
|
||||
## STATO (2026-06-14)
|
||||
## 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.
|
||||
- 🔴 **BLOCCO: conto Deribit mainnet VUOTO** (equity 0.0 USDC/BTC/ETH). Serve il deposito (~€1000
|
||||
USDC) prima di qualsiasi smoke/trade.
|
||||
- ⏭️ Appena finanziato: avviare il micro-test come **servizio separato** (env_file `.env.mainnet`,
|
||||
dir dati pulita, `portfolios.yml` fade-only) → Fase 0 smoke → Fase 1.
|
||||
- ✅ **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.
|
||||
|
||||
---
|
||||
|
||||
@@ -164,9 +173,9 @@ feed congelato). Criterio (lo stesso del report, soglie già nel verdetto 🟢/
|
||||
|
||||
- [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`
|
||||
- [ ] **Conto Deribit mainnet KYC + finanziato ~€1000 USDC** ← BLOCCO ATTUALE (conto a €0)
|
||||
- [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)
|
||||
- [ ] Fase 0 smoke mainnet verde (ordine→verifica→fee)
|
||||
- [ ] `portfolios.yml` micro-test (fade-only, €1000, exec sleeves MR01/02/07)
|
||||
- [ ] runner mainnet + reconciler + report ledger-vs-backtest schedulati
|
||||
- [ ] dopo ≥10–20 trade: leggere il verdetto → scala / aspetta / stop
|
||||
@@ -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
|
||||
@@ -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,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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user