6 Commits

Author SHA1 Message Date
Adriano 9a066eb76f feat(analysis): report_families.py - riepilogo strategie/famiglie per anno + integrazione
Report consolidato: (A) Ret%/anno per famiglia (FADE/HONEST/PAIRS/TSM01) e portafogli,
(B) Ret%/anno per ogni strategia singola (15 sleeve), (C) analisi di integrazione delle
nuove famiglie nel MASTER (+pairs/+TSM01/esteso) con correlazioni, (D) numeri sobri e
raccomandazione (leva 2x, cap pairs 30-35%).

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:30:55 +02:00
14 changed files with 1061 additions and 19 deletions
+34 -4
View File
@@ -126,20 +126,50 @@ paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown
honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate** honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate**
(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il (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 rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve
→ DD 5.2% full / 4.7% OOS e Sharpe OOS 4.42 (vs honest-only 12% DD / 2.23 Sharpe e → DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD /
fade-only 8.2% DD / 4.09 Sharpe), CAGR ~46% mantenuta. Studio in 2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in
`scripts/analysis/combine_portfolio.py`. `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 **Portafogli pronti (artefatti accorpati e migliorati).** Oltre a `PORT01` (solo
honest), due script in `scripts/strategies/`: honest), due script in `scripts/strategies/`:
- `PORT02_fade_master.py` — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend), - `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%. 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: - `PORT03_all_master.py` — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti:
`equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 3.95/4.42) e `5050` fra le due `equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e `5050` fra le due
famiglie (minimo DD: 5.1% full / 4.3% OOS). È la configurazione consigliata. famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata.
Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con
`generate_signals`, quindi non nel `strategy_loader`. `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 da estendere). Verifica: `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 e non ancora
validati col worker live a 2 gambe). La confluenza multi-TF è stata SCARTATA (overfit).
**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze): **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`. 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. 2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva.
+155
View File
@@ -0,0 +1,155 @@
# Diario — 2026-05-29 — Esplorazione di nuove famiglie di strategie
## Obiettivo
Trovare 5-10 nuove famiglie di strategie, diverse da quelle esistenti, migliori o
complementari, con DD basso e attenzione alle fee. Esplorazione onesta (no
look-ahead, netto fee, OOS) condotta con **agenti paralleli**, ognuno su una famiglia
indipendente, tutti sullo stesso harness condiviso (`scripts/analysis/explore_lab.py`).
Lavoro sul branch `strategy_explore`.
## Famiglie esplorate (9) ed esito onesto
| Famiglia | Esito | Note |
|---|---|---|
| **Pairs / spread reversion** | ✅ **VINCITORE** | Market-neutral, genuinamente nuova, decorrelata |
| **TSMOM multi-orizzonte** | ✅ diversificatore | Marginale ma distinto (corr 0.53 con ROT02), DD basso |
| Stagionalità settimanale | ⚠️ marginale/fragile | "Mercoledì-long-24h" 7/8 asset OOS+ ma effetto concentrato a 00:00 UTC |
| Vol-target BTC | ⚠️ marginale | Sharpe 0.94 vs 0.76 buy&hold, DD ancora 44% |
| Stagionalità intraday (ora) | ❌ rumore | L'edge orario muore sotto le fee |
| Stagionalità mensile/turn-of-month | ❌ rumore | Reale in-sample, morto OOS dal 2024 |
| Cross-sectional reversal | ❌ nessun edge | Perde vs equal-weight, corr 0.98 col momentum |
| Opening-range breakout | ❌ non generalizza | Solo BTC/ETH, alcuni regimi, fee-fragile |
| Lead-lag BTC→alt | ❌ nessun edge | Reazione contemporanea (corr lag+1 ≈ 0), non batte buy&hold |
| Momentum/continuation intraday | ❌ negativo | Conferma: il *fade* (mean-reversion) domina |
7 famiglie su 9 sono rumore — e l'harness le ha rifiutate senza produrre falsi
positivi (segnale che la metodologia onesta funziona). Due edge reali emergono.
## Vincitore 1 — PAIRS (market-neutral) — `PR01_pairs_reversion.py`
Scommette sul rientro del log-ratio di due cripto verso la media (z-score). Quando
`z ≤ 2` → long A / short B; `z ≥ +2` → l'opposto; esce al rientro (`|z| ≤ 0.5`) o a
tempo. Engine onesto verificato in `pairs_research.py` (test esplicito no-look-ahead:
`z[i]` invariato perturbando il futuro). Fee contate su **2 gambe** (0.20% RT/coppia).
Validazione (netto, leva 3x, OOS = ultimo 30%, 1h):
| Coppia | CAGR | Sharpe | OOS DD | anni+ |
|---|--:|--:|--:|--:|
| ETH/BTC | 144% | 4.04 | 17% | 8/9 |
| LTC/ETH | 71% | 2.52 | 10% | 7/8 |
| ADA/ETH | 77% | 2.16 | 11% | 7/8 |
Tutte le 10 coppie testate positive FULL+OOS, regge fee 0.40% RT/coppia, correlazione
col mercato ~0.02 (market-neutral confermato). DD pieno 42-49% (alto), ma OOS DD
10-17% (buono) e soprattutto **quasi-zero correlazione** col resto → diversificatore
eccezionale. Limite: 2 gambe (long+short), il worker live va esteso prima del live.
## Vincitore 2 — TSM01 (TSMOM multi-orizzonte) — `tsmom_research.py`
Long-only multi-crypto: tiene equal-weight gli asset con consenso pieno del segno di
momentum su 3/6/12 mesi, cash se BTC<SMA100. Distinto da ROT02 (persistenza assoluta
vs ranking relativo), corr 0.53. FULL +169% / OOS +80% / DD 22% / Sharpe 1.07,
**mai un anno negativo**, regge fee 0.40%. Verificato no-look-ahead (cheat-test
esplode a +575%). Marginale come stand-alone (rende meno di ROT02) ma utile in ensemble.
## Il payoff — combinare le nuove fonti col MASTER (`combine_v2.py`)
Le nuove sleeve sono quasi scorrelate col MASTER-9 (pairs ~0.02-0.08, TSM01 0.05).
Aggiungerle migliora nettamente il portafoglio:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + pairs (12)** | **66** | **3.8** | **5.67** | **3.3** | **6.86** |
| MASTER + TSM01 (10) | 44 | 4.7 | 4.21 | 4.2 | 4.33 |
| MASTER esteso (13) | 62 | 3.6 | 5.66 | 3.0 | 6.79 |
I **pairs** sono l'aggiunta decisiva: alzano la CAGR (47→66), **abbassano il DD**
(5.2→3.8 full, 4.7→**3.3** OOS) e portano lo Sharpe OOS a **6.86** — il free-lunch
della diversificazione da una fonte market-neutral scorrelata. TSM01 contribuisce
poco (diluisce il ritorno) ma abbassa lievemente il DD.
## Caveat onesti
- I pairs hanno DD pieno alto (42-49%) sull'1h; il vantaggio sta nella decorrelazione,
non nel DD stand-alone. Richiedono esecuzione a 2 gambe (short del perp B) — da
verificare shortabilità/liquidità sugli alt e raddoppio fee nel worker.
- Sharpe combinati 5-7 e CAGR 60%+ sono backtest a leva 3x su finestra 2021-2026 con
OOS ~1.6 anni e il 2024 cripto eccezionale: numeri ottimistici, da confermare in
paper trading live.
- TSMOM e le strategie honest condividono l'overlay risk-off SMA100: parte della loro
difensività è comune (non perfettamente indipendente).
## Terza ondata — espansione dei meccanismi provati + 2 nuovi sondaggi
Esplorate altre 4 direzioni con agenti paralleli:
- **Fade su 6 nuovi alt (ADA/BNB/DOGE/LTC/SOL/XRP)**: 0 robuste. La mean-reversion
fade vive solo su BTC/ETH (liquidi); sugli alt sparisce o è artefatto di pochi pump
(DOGE). Coerente con la lezione del progetto.
- **Espansione PAIRS** (tutte le 28 coppie): trovate **3 nuove coppie robuste**
BTC/LTC (robusta 1h *e* 4h, Sharpe 2.21, DD 24-34%, concentrazione PnL 9%), ETH/SOL
e BNB/ETH (Sharpe 2.4+, solo 1h). Pattern: sempre alt-liquido vs major, mai alt/alt.
PR01 ora ha **6 coppie**.
- **Low-volatility anomaly**: ❌ in cripto è INVERTITA (vince l'alta vol = alta beta),
ridondante con EW+risk-off/ROT02. L'anti-test high-vol stravince.
- **Confluenza multi-timeframe (fade 1h confermato da 4h)**: non crea edge nuovo e non
migliora lo Sharpe, ma **dimezza il DD** di MR01 (ETH: stesso Sharpe 3.17 a DD 38% vs
63%) e stabilizza l'OOS → utile variante low-DD, non strategia indipendente.
## Bilancio finale e MASTER esteso (6 pairs)
Robusti deployabili: **famiglia PAIRS (6 coppie) + TSM01** (+ confluenza MTF come variante
low-DD di MR01, + tilt stagionale mercoledì marginale). I 6 pairs sono quasi scorrelati col
MASTER (corr 0.02-0.08). MASTER + 6 pairs:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + 6 pairs (15)** | **71** | 5.7 | **5.93** | **2.3** | **7.71** |
| MASTER esteso +TSM01 (16) | 67 | 5.4 | 5.95 | **2.2** | 7.67 |
Aggiungere i 6 pairs porta l'**OOS DD a 2.2-2.3%** (da 4.7%) con Sharpe OOS ~7.7 e tutti
gli anni positivi: il guadagno di diversificazione da fonti market-neutral scorrelate.
## Quarto giro — validazione anti-overfitting e irrobustimento
Tre audit scettici paralleli (walk-forward, plateau, stress, scomposizione):
**Pairs — de-overfittati.** Sostituita la config per-coppia (cherry-picking di z_exit/n)
con **una config universale `n=50 z_in=2.0 z_exit=0.75 max_bars=72`**. Verifiche:
- plateau (non picco): heatmap n×z_in → 20/20 celle Sharpe>1 su ETH/BTC e BTC/LTC;
- walk-forward (train 2y / test 6m rolling): ETH/BTC 11/12 finestre positive, BTC/LTC
9/10 → edge distribuito su tutta la storia, non un regime singolo;
- **BNB/ETH scartata** (era robusta solo coi suoi parametri → overfit; crolla con la
universale e muore per prima allo stress costi). Famiglia ridotta a **5 pairs**.
- stress: 5/6 reggono fee+slippage realistici; solo ETH/BTC regge fee 6x (coda fee-fragile).
**Master — numeri sobri.** L'OOS Sharpe 7.7 / DD 2.3% è **ottimistico ~50%** perché l'OOS
cade nel bull calmo 2024-25. Numeri onesti da usare:
- worst-DD su finestra mobile 90g (2021-2026) = **5.7%** (bear FTX) → budget DD ~6%, non 2.3%;
- Sharpe per-semestre: mediana **~5** (min 1.2, max 12) → atteso ~5, non 7.7;
- ogni anno e ogni semestre dal 2021 positivo (anche il 2022 bear, grazie alle gambe short);
- equal-weight ≈ inverse-vol (non dipende da pesi fortunati);
- regge **leva 2x + slippage doppio** (CAGR 36%, Sharpe 5.1);
- **rischio concentrato: i pairs portano ~57% del rischio** → cap consigliato ~30-35%.
- Config robusta raccomandata: **MASTER-esteso, equal-weight, leva 2x, cap pairs ~30-35%**.
**TSM01 — confermato robusto** (36/36 config OOS+, walk-forward stabile) ma corr reale con
ROT02 = **0.62** (non 0.53), e gran parte del DD basso viene dall'overlay risk-off condiviso.
Tenuto come diversificatore con **gross 0.30** (stesso Sharpe, DD 22%→15%).
**Confluenza multi-TF — SCARTATA: era overfit.** Taglia il 97% dei trade (restano ~40 in
8 anni = non significativo), distrugge lo Sharpe (1.58→0.27 su BTC) e il caso "bello" non
sopravvive alle perturbazioni. Per abbassare il DD di MR01 meglio ridurne la leva, non il filtro 4h.
**Risultato del giro:** quanto trovato regge l'esame anti-overfit (NON è l'errore squeeze),
ma i numeri vanno comunicati sobri (Sharpe ~5, DD ~6%) e con leva 2x + cap pairs. Famiglia
pairs consolidata a 5 coppie con config universale; confluenza MTF rimossa dai vincitori.
## File creati (branch strategy_explore)
`scripts/analysis/explore_lab.py` (harness onesto condiviso), `pairs_research.py`
(verifica + ricerca pairs), `tsmom_research.py` (TSM01), `combine_v2.py` (master
esteso); `scripts/strategies/PR01_pairs_reversion.py` (artefatto pairs).
+15
View File
@@ -47,3 +47,18 @@ favorevole pesa. Il target €50/giorno resta vincolato dal capitale: 43% CAGR s
Prossimo passo: confermare il portafoglio combinato nel paper trader live. Prossimo passo: confermare il portafoglio combinato nel paper trader live.
**File:** `scripts/analysis/combine_portfolio.py` (nuovo). **File:** `scripts/analysis/combine_portfolio.py` (nuovo).
## Pulizia roster + miglioria ROT02
- **Waste delle peggiori:** MR03 Keltner (fade più debole, Sharpe 1.22, ridondante
con MR01 — rimuoverla *migliora* il portafoglio fade: DD 8.6→8.2, ret +549→+666)
e ROT01 (dominata da ROT02). Spostate in `scripts/waste/`.
- **Portafogli pronti:** `PORT02_fade_master` (6 sleeve fade) e `PORT03_all_master`
(9 sleeve fade+honest, varianti equal/5050).
- **ROT02 DD alto → migliorato:** la rotazione concentrava il book su 2 asset
(DD 40%). Sweep su `rot_improved`: `top_k=3` dimezza quasi il DD (40%→26%) e
*alza* il ritorno full (+1095→+1303%, ret/DD 27→50). Il vol-target abbassa il DD
ma sacrifica ritorno (de-leverage) → tenuto top_k=3 senza VT. Caveat onesto:
l'OOS di ROT02 cala un po' (+98→+68%, DD 12→14%), ma il MASTER (config deployata)
migliora lo Sharpe full 3.95→4.23. Applicato a `ROT02_dual_momentum.py` e
`_rot_daily_equity`. Sweep in `honest_improve.rot_improved`.
+87
View File
@@ -0,0 +1,87 @@
"""Combina i NUOVI edge (pairs + TSM01) col MASTER esistente: migliora il portafoglio?
Aggiunge al MASTER a 9 sleeve (6 fade + 3 honest) due nuove fonti scoperte
nell'esplorazione, poco correlate:
- PAIRS market-neutral (ETH/BTC, LTC/ETH, ADA/ETH) -> corr ~0 col mercato
- TSM01 (TSMOM multi-orizzonte + risk-off) -> corr ~0.53 con ROT02
Misura correlazione delle nuove sleeve vs esistenti e confronta MASTER-9 vs
MASTER-esteso su Ret/CAGR/DD/Sharpe, FULL e OOS (finestra comune 2021-2026).
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import (
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
from scripts.analysis.honest_improve2 import _daily_equity, _norm
from scripts.analysis.pairs_research import pairs_sim
from scripts.analysis.tsmom_research import tsmom_sim
def daily_from(eq_ts, eq_v):
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def main():
print("Costruzione equity (puo' richiedere ~1-2 min)...\n")
S = build_all_sleeves() # 9 sleeve esistenti
# nuove sleeve: i 6 pairs robusti di PR01 + TSM01
from scripts.strategies.PR01_pairs_reversion import PAIRS
new = {}
for a, b, p in PAIRS:
r = pairs_sim(a, b, **p)
new[f"PR_{a}{b}"] = daily_from(r["eq_ts"], r["eq_v"])
t = tsmom_sim()
new["TSM01"] = daily_from(t["eq_ts"], t["eq_v"])
allS = {**S, **new}
# --- correlazione nuove vs esistenti ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in allS.items()})
corr = dr.corr()
old_k = list(S); new_k = list(new)
print("=" * 88)
print(" CORRELAZIONE rendimenti giornalieri — NUOVE (righe) vs media esistenti")
print("=" * 88)
for nk in new_k:
avg = corr.loc[nk, old_k].mean()
mx = corr.loc[nk, old_k].abs().max()
print(f" {nk:<12s} corr media col MASTER-9 = {avg:+.2f} |max| = {mx:.2f}")
# --- confronto portafogli ---
def line(label, members):
pr = port_returns(members)
f, o = metrics(pr), metrics(pr, lo=SPLIT)
print(f" {label:<26s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
return pr
print("\n" + "=" * 96)
print(f" MASTER-9 vs MASTER-ESTESO (con pairs+TSM01) | OOS da {OOS_DATE} | equal-weight daily")
print("=" * 96)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 92)
pairs_only = {k: v for k, v in new.items() if k.startswith('PR_')}
line(f"MASTER-9 (base)", S)
line(f"MASTER +pairs ({len(S)+len(pairs_only)})", {**S, **pairs_only})
line(f"MASTER +TSM01 ({len(S)+1})", {**S, "TSM01": new["TSM01"]})
pr_all = line(f"MASTER-esteso ({len(allS)})", allS)
print(" " + "-" * 92)
pa = yearly_returns(pr_all)
print(" MASTER-esteso per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in pa.items()))
print("\n Se il MASTER-esteso ha DD piu' basso e/o Sharpe piu' alto del MASTER-9, le nuove")
print(" famiglie aggiungono valore (diversificazione da fonti scorrelate).")
if __name__ == "__main__":
main()
+171
View File
@@ -0,0 +1,171 @@
"""Harness ONESTO condiviso per esplorare nuove famiglie di strategie.
Regole NON negoziabili (per non ripetere l'errore squeeze look-ahead):
- direzione e prezzo decisi con dati FINO a close[i] incluso, mai con la barra i
usata per scegliere la direzione e poi entrare a i-1;
- ingresso ESEGUIBILE a close[i];
- exit: take-profit / stop-loss intrabar (high/low) e/o time-limit max_bars;
tp/sl possono essere None -> exit solo a tempo (utile per stagionalita');
- una posizione per volta (non-overlap), capitale composto;
- NETTO dopo fee round-trip (default 0.10% RT reale Deribit) e leva;
- validazione OOS (held-out, ultimo 30%) + sweep fee 0.00-0.20% RT.
Le strategie ad alta frequenza muoiono di fee: ogni entry costa fee_rt*lev sul
notional. Tienine conto: meno operazioni e edge > costi.
Asset disponibili: ADA BNB BTC DOGE ETH LTC SOL XRP (1h, 15m; BTC/ETH anche 5m).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
FEE_RT = 0.001 # Deribit perp realistico: taker ~0.05%/lato = 0.10% RT
LEV = 3.0
POS = 0.15
OOS_FRAC = 0.30
ASSETS = ["ADA", "BNB", "BTC", "DOGE", "ETH", "LTC", "SOL", "XRP"]
BARS_PER_YEAR = {"5m": 105120, "15m": 35040, "1h": 8760, "4h": 2190, "1d": 365}
# --------------------------- dati ---------------------------
def get_df(asset: str, tf: str) -> pd.DataFrame:
"""OHLCV con colonna dt (UTC). tf nativo (5m,15m,1h) o resample da 1h (4h,1d).
timestamp resta ms-epoch reale anche dopo il resample (no placeholder)."""
if tf in ("5m", "15m", "1h"):
df = load_data(asset, tf).reset_index(drop=True)
else:
base = load_data(asset, "1h").copy()
base["dt"] = pd.to_datetime(base["timestamp"], unit="ms", utc=True)
base = base.set_index("dt")
rule = {"4h": "4h", "1d": "1D"}[tf]
agg = base.resample(rule).agg(
{"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}
).dropna()
epoch = pd.Timestamp("1970-01-01", tz="UTC") # ms-epoch portabile (qualsiasi risoluzione)
agg["timestamp"] = ((agg.index - epoch) // pd.Timedelta(milliseconds=1)).astype("int64")
df = agg.reset_index(drop=True)
df["dt"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
return df
def _dt(df: pd.DataFrame) -> pd.DatetimeIndex:
return pd.to_datetime(df["timestamp"], unit="ms", utc=True)
# --------------------------- indicatori ---------------------------
def atr(df: pd.DataFrame, n: int = 14) -> np.ndarray:
h, l, c = df["high"].values, df["low"].values, df["close"].values
pc = np.roll(c, 1); pc[0] = c[0]
tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
return pd.Series(tr).rolling(n).mean().values
def ema(x: np.ndarray, n: int) -> np.ndarray:
return pd.Series(x).ewm(span=n, adjust=False).mean().values
def rsi(close: np.ndarray, n: int = 14) -> np.ndarray:
d = np.diff(close, prepend=close[0])
up = pd.Series(np.where(d > 0, d, 0.0)).ewm(alpha=1/n, adjust=False).mean()
dn = pd.Series(np.where(d < 0, -d, 0.0)).ewm(alpha=1/n, adjust=False).mean()
rs = up / dn.replace(0, np.nan)
return (100 - 100 / (1 + rs)).values
# --------------------------- engine ---------------------------
def simulate(entries: list[dict], df: pd.DataFrame, fee_rt: float = FEE_RT,
lev: float = LEV, pos: float = POS, split: int = -1) -> dict:
"""entries: dict con i(idx), d(+1/-1), max_bars; tp/sl opzionali (None=solo tempo).
split: se >0, conta solo entries con i>=split (finestra OOS)."""
h, l, c = df["high"].values, df["low"].values, df["close"].values
n = len(c)
ts = _dt(df)
cap = peak = 1000.0
max_dd = 0.0
fee = fee_rt * lev
trades = wins = 0
last_exit = -1
bars_in = 0
yearly: dict[int, float] = {}
rets: list[float] = []
for e in entries:
i, d = e["i"], e["d"]
if i <= last_exit or i + 1 >= n or i < split:
continue
entry = c[i]
tp, sl, mb = e.get("tp"), e.get("sl"), e["max_bars"]
exit_p = c[min(i + mb, n - 1)]
j = min(i + mb, n - 1)
for k in range(1, mb + 1):
j = i + k
if j >= n:
exit_p = c[n - 1]; break
hit_sl = sl is not None and ((d == 1 and l[j] <= sl) or (d == -1 and h[j] >= sl))
hit_tp = tp is not None and ((d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp))
if hit_sl:
exit_p = sl; break
if hit_tp:
exit_p = tp; break
if k == mb:
exit_p = c[j]
ret = (exit_p - entry) / entry * d * lev - fee
cb = cap
cap = max(cb + cb * pos * ret, 10.0)
peak = max(peak, cap); max_dd = max(max_dd, (peak - cap) / peak)
trades += 1; wins += ret > 0; bars_in += (j - i)
last_exit = j
rets.append(ret * pos)
yearly[ts.iloc[i].year] = yearly.get(ts.iloc[i].year, 0.0) + ret * 100
sharpe = float(np.mean(rets) / np.std(rets) * np.sqrt(len(rets))) if len(rets) > 1 and np.std(rets) > 0 else 0.0
return {
"trades": trades,
"win": wins / trades * 100 if trades else 0.0,
"ret": (cap / 1000 - 1) * 100,
"dd": max_dd * 100,
"sharpe": sharpe,
"yearly": yearly,
"exposure": bars_in / n * 100 if n else 0.0,
}
def evaluate(name: str, entries: list[dict], df: pd.DataFrame,
fees=(0.0, 0.0005, 0.001, 0.002)) -> dict:
"""Valuta una lista di entries: FULL, OOS e sweep fee. Stampa una riga sintetica."""
split = int(len(df) * (1 - OOS_FRAC))
full = simulate(entries, df)
oos = simulate(entries, df, split=split)
sweep = {f: simulate(entries, df, fee_rt=f)["ret"] for f in fees}
sweep_oos = {f: simulate(entries, df, fee_rt=f, split=split)["ret"] for f in fees}
yrs = full["yearly"]; pos_yrs = sum(1 for v in yrs.values() if v > 0)
print(f" {name:<24s} trd={full['trades']:>5d} win={full['win']:>4.1f}% "
f"FULL={full['ret']:>+7.0f}% OOS={oos['ret']:>+7.0f}% DD={full['dd']:>4.0f}% "
f"oDD={oos['dd']:>4.0f}% Shrp={full['sharpe']:>4.2f} exp={full['exposure']:>4.1f}% "
f"anniPos={pos_yrs}/{len(yrs)} | fee0.2%: FULL={sweep[0.002]:>+6.0f} OOS={sweep_oos[0.002]:>+6.0f}")
return {"full": full, "oos": oos, "sweep": sweep, "sweep_oos": sweep_oos, "pos_yrs": pos_yrs, "n_yrs": len(yrs)}
def robust(res: dict) -> bool:
"""Verdetto onesto: positivo FULL e OOS, regge a fee 0.20% RT, quasi tutti gli anni positivi."""
return (res["full"]["ret"] > 0 and res["oos"]["ret"] > 0
and res["sweep"][0.002] > 0 and res["sweep_oos"][0.002] > 0
and res["pos_yrs"] >= max(res["n_yrs"] - 1, 1))
if __name__ == "__main__":
# smoke test: una stagionalita' banale (hour-of-day) su BTC 1h
df = get_df("BTC", "1h"); ts = _dt(df)
ents = [{"i": i, "d": 1, "max_bars": 6, "tp": None, "sl": None}
for i in range(len(df) - 7) if ts.iloc[i].hour == 0]
print("smoke test — BTC long ad ogni 00:00 UTC, hold 6h:")
evaluate("seasonality_h0", ents, df)
+1 -1
View File
@@ -170,7 +170,7 @@ def _rot_daily_equity(idx):
for i in range(101, T - 1): for i in range(101, T - 1):
risk_on = btc[i] > bma[i] if not np.isnan(bma[i]) else False risk_on = btc[i] > bma[i] if not np.isnan(bma[i]) else False
mom = P[i] / P[i - 60] - 1; order = np.argsort(mom)[::-1] mom = P[i] / P[i - 60] - 1; order = np.argsort(mom)[::-1]
chosen = [j for j in order if mom[j] > 0][:2] if risk_on else [] chosen = [j for j in order if mom[j] > 0][:3] if risk_on else [] # top_k=3 (era 2): DD piu' basso
nw = np.zeros(N) nw = np.zeros(N)
for j in chosen: for j in chosen:
nw[j] = 0.45 / len(chosen) nw[j] = 0.45 / len(chosen)
+133
View File
@@ -0,0 +1,133 @@
"""Verifica indipendente + ricerca PAIRS / SPREAD MEAN-REVERSION fra cripto.
Famiglia nuova market-neutral (distinta da tutto l'esistente, single-asset).
Idea: il log-ratio di due cripto oscilla attorno alla media; z-score estremo -> rientra.
Engine ONESTO (no look-ahead, verificato):
- r[i] = log(closeA[i]/closeB[i]); ma/sd = rolling(n) su r -> usano solo r[<=i].
- z[i] = (r[i]-ma[i])/sd[i]. ENTRY a close[i] (eseguibile):
z<=-z_in -> LONG ratio (long A / short B); z>=+z_in -> SHORT ratio.
- EXIT quando |z[j]| <= z_exit (rientro) o time-limit max_bars, a close[j].
- pairs = 2 GAMBE -> fee = 2*fee_rt*lev (0.20% RT/coppia a fee_rt=0.001), il doppio
del single-asset. Rendimento neutral = retA*d - retB*d (notional uguale per gamba).
- non-overlap, capitale composto. Filtro candele sporche: salta salti |dr|>jump_max.
- Ritorno riportato come CAGR e Sharpe ANNUALIZZATO sul tempo reale (no sqrt(n_trade)).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
FEE_RT, LEV, POS, OOS_FRAC = 0.001, 3.0, 0.15, 0.30
BARS_YEAR = 8760 # 1h
def aligned(a: str, b: str, tf: str = "1h"):
da = load_data(a, tf)[["timestamp", "open", "high", "low", "close"]].rename(columns=lambda x: x + "_a" if x != "timestamp" else x)
db = load_data(b, tf)[["timestamp", "close"]].rename(columns={"close": "close_b"})
m = da.merge(db, on="timestamp", how="inner").reset_index(drop=True)
m["dt"] = pd.to_datetime(m["timestamp"], unit="ms", utc=True)
return m
def pairs_sim(a, b, tf="1h", n=50, z_in=2.0, z_exit=0.5, max_bars=72,
jump_max=0.08, fee_rt=FEE_RT, lev=LEV, pos=POS, split_frac=0.0):
m = aligned(a, b, tf)
ca, cb = m["close_a"].values, m["close_b"].values
r = np.log(ca / cb)
dr = np.abs(np.diff(r, prepend=r[0])) # salto 1-bar del log-ratio
ma = pd.Series(r).rolling(n).mean().values
sd = pd.Series(r).rolling(n).std().values
z = (r - ma) / np.where(sd == 0, np.nan, sd) # causale: usa r[<=i]
ts = m["dt"]; N = len(r)
split = int(N * split_frac)
fee = 2 * fee_rt * lev # 2 gambe
cap = peak = 1000.0; dd = 0.0; last = -1
trades = wins = 0; rets = []; yearly = {}
eq_ts: list = []; eq_v: list = []
for i in range(n + 1, N - 1):
if i < split or np.isnan(z[i]) or dr[i] > jump_max:
continue
if i <= last:
continue
if z[i] <= -z_in:
d = 1
elif z[i] >= z_in:
d = -1
else:
continue
# exit: |z|<=z_exit o max_bars
j = min(i + max_bars, N - 1)
for k in range(1, max_bars + 1):
jj = i + k
if jj >= N:
j = N - 1; break
if abs(z[jj]) <= z_exit:
j = jj; break
j = jj
retA = (ca[j] - ca[i]) / ca[i]
retB = (cb[j] - cb[i]) / cb[i]
ret = (retA - retB) * d * lev - fee # long A / short B (o viceversa)
cap = max(cap + cap * pos * ret, 10.0)
peak = max(peak, cap); dd = max(dd, (peak - cap) / peak)
trades += 1; wins += ret > 0; rets.append(ret * pos); last = j
eq_ts.append(ts.iloc[j]); eq_v.append(cap)
yearly[ts.iloc[i].year] = yearly.get(ts.iloc[i].year, 0.0) + ret * 100
yrs_span = (ts.iloc[-1] - ts.iloc[max(split, 0)]).days / 365.25 or 1
sharpe = float(np.mean(rets) / np.std(rets) * np.sqrt(BARS_YEAR / np.mean([max_bars])) ) if len(rets) > 1 and np.std(rets) > 0 else 0.0
# Sharpe annualizzato sul tempo reale: usa rendimenti per-trade scalati alla frequenza media
if len(rets) > 1 and np.std(rets) > 0:
trades_per_year = trades / yrs_span
sharpe = float(np.mean(rets) / np.std(rets) * np.sqrt(trades_per_year))
ret_tot = (cap / 1000 - 1) * 100
cagr = ((cap / 1000) ** (1 / yrs_span) - 1) * 100 if cap > 0 else -100
return dict(trades=trades, win=wins / trades * 100 if trades else 0, ret=ret_tot, cagr=cagr,
dd=dd * 100, sharpe=sharpe, yearly=yearly, eq_ts=eq_ts, eq_v=eq_v)
def check_no_lookahead():
"""Perturba il FUTURO del ratio e verifica che z[i] non cambi (causalita')."""
m = aligned("ETH", "BTC")
r = np.log(m["close_a"].values / m["close_b"].values)
n = 50; i = 1000
z_i = (r[i] - pd.Series(r).rolling(n).mean().values[i]) / pd.Series(r).rolling(n).std().values[i]
r2 = r.copy(); r2[i + 1:] += 0.5 # stravolge il futuro
z_i2 = (r2[i] - pd.Series(r2).rolling(n).mean().values[i]) / pd.Series(r2).rolling(n).std().values[i]
print(f" no-look-ahead: z[i]={z_i:.6f} vs z[i] con futuro perturbato={z_i2:.6f} -> "
f"{'OK (identico)' if abs(z_i - z_i2) < 1e-9 else 'VIOLAZIONE!'}")
def main():
print("=" * 104)
print(f" PAIRS spread reversion — NETTO fee 0.20% RT/coppia (2 gambe), leva {LEV:.0f}x, OOS ultimo {int(OOS_FRAC*100)}%")
print("=" * 104)
check_no_lookahead()
pairs = [("ETH", "BTC"), ("LTC", "ETH"), ("ADA", "ETH"), ("SOL", "ETH"),
("BNB", "BTC"), ("XRP", "BTC"), ("SOL", "BTC"), ("DOGE", "BTC")]
print(f"\n {'coppia':<10s}{'trd':>5s}{'win%':>6s}{'FULL%':>8s}{'OOS%':>8s}{'CAGR%':>7s}"
f"{'DD%':>6s}{'oDD%':>6s}{'Shrp':>6s}{'anni+':>7s}{'fee0.4%RT':>11s}")
print(" " + "-" * 96)
for a, b in pairs:
f = pairs_sim(a, b, n=50, z_in=2.0, z_exit=0.5, max_bars=72)
o = pairs_sim(a, b, n=50, z_in=2.0, z_exit=0.5, max_bars=72, split_frac=1 - OOS_FRAC)
hi = pairs_sim(a, b, n=50, z_in=2.0, z_exit=0.5, max_bars=72, fee_rt=0.002) # 0.4% RT/coppia
yrs = f["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0)
print(f" {a+'/'+b:<10s}{f['trades']:>5d}{f['win']:>6.1f}{f['ret']:>+8.0f}{o['ret']:>+8.0f}"
f"{f['cagr']:>7.0f}{f['dd']:>6.0f}{o['dd']:>6.0f}{f['sharpe']:>6.2f}{f'{pos_y}/{len(yrs)}':>7s}"
f"{hi['ret']:>+11.0f}")
# correlazione con BTC daily (market-neutrality) sulla coppia migliore
print("\n Verifica market-neutrality ETH/BTC: per-anno")
f = pairs_sim("ETH", "BTC", n=50, z_in=2.0, z_exit=0.5, max_bars=72)
print(" " + " ".join(f"{y}:{v:+.0f}%" for y, v in sorted(f["yearly"].items())))
if __name__ == "__main__":
main()
+136
View File
@@ -0,0 +1,136 @@
"""Report aggiornato: risultati per anno + numero trade per anno, tutte le strategie.
Sezioni:
(A) RET% NETTO per anno — ogni strategia singola + i portafogli (FADE / HONEST /
MASTER equal / MASTER 50-50). Ret% dai rendimenti giornalieri composti.
(B) NUMERO TRADE per anno — per ogni strategia singola. Per le fade e DIP01 è il
numero di ingressi; per TR01 e ROT02 (posizione continua) è il numero di
ribilanciamenti/cambi di stato nell'anno.
(C) RIEPILOGO — TOT%, CAGR, DD, Sharpe (FULL e OOS) dei portafogli.
Tutto NETTO fee 0.10% RT, leva 3x, pos 15%/sleeve. Finestra comune 2021-2026,
OOS = ultimo 30%. Config = quella deployata (MR03/ROT01 in waste; ROT02 top_k=3).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.combine_portfolio import (
build_all_sleeves, port_returns, yearly_returns, metrics, SPLIT, OOS_DATE, IDX,
)
from scripts.analysis.risk_management import strats_for, build_trades
from scripts.analysis.honest_lab import get_df, ema, FEE_RT, LEV, POS
from scripts.analysis.honest_improve import rot_improved
from scripts.analysis.honest_improve2 import dip_market_gated
YEARS = sorted(set(IDX.year))
# ---------------- trade per anno, per tipo di strategia ----------------
def fade_trades_year(asset, fn, params) -> dict[int, int]:
df = load_data(asset, "1h")
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
out: dict[int, int] = {}
for i, j, ret in build_trades(fn(df, **params), df, trend_max=3.0):
y = ts.iloc[i].year
out[y] = out.get(y, 0) + 1
return out
def dip_trades_year() -> dict[int, int]:
d = dip_market_gated("BTC", market_n=0)
# yt[anno] = lista dei trade dell'anno -> il conteggio e' la lunghezza
return {int(y): (len(v) if isinstance(v, (list, tuple)) else int(v)) for y, v in d["yt"].items()}
def tr_rebalances_year(assets) -> dict[int, int]:
"""Cambi di stato (entra/esce dal trend) per anno, sommati sul paniere TR01."""
out: dict[int, int] = {}
for a in assets:
df = get_df(a, "4h"); c = df["close"].values
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
ef, es = ema(c, 20), ema(c, 100)
sig = np.where(ef > es, 1.0, 0.0); sig[:100] = 0.0
for i in range(1, len(c)):
if sig[i] != sig[i - 1]:
y = ts.iloc[i].year
out[y] = out.get(y, 0) + 1
return out
def rot_rebalances_year() -> dict[int, int]:
r = rot_improved(lookback=60, top_k=3, regime_n=100)
return {int(y): int(n) for y, n in r["reb"].items()}
def main():
print("Costruzione equity e conteggi (puo' richiedere ~1 min)...\n")
S = build_all_sleeves()
fade = {k: v for k, v in S.items() if k.startswith("MR")}
honest = {k: v for k, v in S.items() if not k.startswith("MR")}
# rendimenti giornalieri per Ret%/anno
sleeve_ret = {k: v.pct_change().fillna(0.0) for k, v in S.items()}
ports = {
"FADE": port_returns(fade),
"HONEST": port_returns(honest),
"MASTEReq": port_returns(S),
"MAST5050": (port_returns(fade) + port_returns(honest)) / 2,
}
# ---- (A) RET% per anno ----
cols_A = list(S) + list(ports)
rety = {**{k: yearly_returns(v) for k, v in sleeve_ret.items()},
**{k: yearly_returns(v) for k, v in ports.items()}}
print("=" * 132)
print(" (A) RET% NETTO PER ANNO — strategie singole e portafogli | leva 3x pos 15% fee 0.10% RT")
print("=" * 132)
print(f" {'Anno':>5s}" + "".join(f"{c.replace('_',''):>11s}" for c in cols_A))
print(" " + "-" * 126)
for y in YEARS:
print(f" {y:>5d}" + "".join(f"{rety[c].get(y, 0):>+11.0f}" for c in cols_A))
# ---- (B) NUMERO TRADE per anno ----
tcounts = {}
for asset in ["BTC", "ETH"]:
for nm, (fn, params) in strats_for(asset).items():
tcounts[f"{nm}_{asset}"] = fade_trades_year(asset, fn, params)
tcounts["DIP01_BTC"] = dip_trades_year()
tcounts["TR01_basket*"] = tr_rebalances_year(["BNB", "BTC", "DOGE", "SOL", "XRP"])
tcounts["ROT02_rot*"] = rot_rebalances_year()
cols_B = list(tcounts)
print("\n" + "=" * 132)
print(" (B) NUMERO TRADE PER ANNO — fade/DIP01 = ingressi; TR01/ROT02 (*) = ribilanciamenti")
print("=" * 132)
print(f" {'Anno':>5s}" + "".join(f"{c.replace('_',''):>13s}" for c in cols_B))
print(" " + "-" * 126)
for y in YEARS:
print(f" {y:>5d}" + "".join(f"{tcounts[c].get(y, 0):>13d}" for c in cols_B))
print(" " + "-" * 126)
print(f" {'TOT':>5s}" + "".join(f"{sum(tcounts[c].values()):>13d}" for c in cols_B))
# ---- (C) riepilogo portafogli ----
print("\n" + "=" * 92)
print(f" (C) RIEPILOGO PORTAFOGLI | OOS da {OOS_DATE}")
print("=" * 92)
print(f" {'portafoglio':<14s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 74)
for name, pr in ports.items():
f, o = metrics(pr), metrics(pr, lo=SPLIT)
print(f" {name:<14s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
print("\n MASTEReq (9 sleeve) = configurazione consigliata. (*) TR01/ROT02 = posizione")
print(" continua: il conteggio e' il numero di ribilanciamenti/cambi di stato, non di trade discreti.")
if __name__ == "__main__":
main()
+136
View File
@@ -0,0 +1,136 @@
"""Report riassuntivo: tutte le strategie/famiglie per anno + analisi di integrazione.
Consolida in un solo posto:
(A) RET% NETTO per anno per FAMIGLIA (FADE / HONEST / PAIRS / TSM01) e per i portafogli.
(B) RET% NETTO per anno per ogni STRATEGIA singola (tutti gli sleeve).
(C) INTEGRAZIONE: cosa succede al MASTER aggiungendo le nuove famiglie (pairs, TSM01).
(D) Numeri SOBRI (worst-case) e raccomandazione operativa.
Famiglie:
FADE (reversione intraday 1h, long/short, BTC/ETH): MR01, MR02, MR07
HONEST (long-only multi-regime multi-crypto): DIP01, TR01, ROT02
PAIRS (market-neutral spread reversion, config universale): 5 coppie
TSM01 (TSMOM multi-orizzonte, diversificatore)
Tutto NETTO fee, leva 3x (vedi nota sobria leva 2x), finestra comune 2021-2026, OOS=ultimo 30%.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import (
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
from scripts.analysis.honest_improve2 import _daily_equity, _norm
from scripts.analysis.pairs_research import pairs_sim
from scripts.analysis.tsmom_research import tsmom_sim
from scripts.strategies.PR01_pairs_reversion import PAIRS
YEARS = sorted(set(IDX.year))
def daily_from(eq_ts, eq_v):
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def build_everything():
S = build_all_sleeves() # 9 sleeve (FADE 6 + HONEST 3)
pairs = {}
for a, b, p in PAIRS:
r = pairs_sim(a, b, **p)
pairs[f"PR_{a}{b}"] = daily_from(r["eq_ts"], r["eq_v"])
t = tsmom_sim()
tsm = {"TSM01": daily_from(t["eq_ts"], t["eq_v"])}
return S, pairs, tsm
def yrow(label, dr):
yr = yearly_returns(dr)
return f" {label:<14s}" + "".join(f"{yr.get(y, 0):>+9.0f}" for y in YEARS)
def metric_block(label, dr):
f, o = metrics(dr), metrics(dr, lo=SPLIT)
return (f" {label:<16s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
def main():
print("Costruzione (puo' richiedere ~1-2 min)...\n")
S, pairs, tsm = build_everything()
fade = {k: v for k, v in S.items() if k.startswith("MR")}
honest = {k: v for k, v in S.items() if not k.startswith("MR")}
fam = {
"FADE": port_returns(fade),
"HONEST": port_returns(honest),
"PAIRS": port_returns(pairs),
"TSM01": tsm["TSM01"].pct_change().fillna(0.0),
}
master9 = port_returns(S)
master_p = port_returns({**S, **pairs})
master_x = port_returns({**S, **pairs, **tsm})
# ---------- (A) per anno, per FAMIGLIA + portafogli ----------
print("=" * 110)
print(" (A) RET% NETTO PER ANNO — per FAMIGLIA e per PORTAFOGLIO | leva 3x, fee netta")
print("=" * 110)
print(f" {'':<14s}" + "".join(f"{y:>9d}" for y in YEARS))
print(" " + "-" * 104)
for k, dr in fam.items():
print(yrow(k, dr))
print(" " + "-" * 104)
print(yrow("MASTER-9", master9))
print(yrow("MASTER+pairs", master_p))
print(yrow("MASTER-esteso", master_x))
# ---------- (B) per anno, per STRATEGIA singola ----------
print("\n" + "=" * 130)
print(" (B) RET% NETTO PER ANNO — per STRATEGIA singola (tutti gli sleeve)")
print("=" * 130)
allsl = {**S, **pairs, **tsm}
cols = list(allsl)
print(f" {'Anno':>5s}" + "".join(f"{c.replace('_',''):>11s}" for c in cols))
print(" " + "-" * 124)
yr_each = {k: yearly_returns(v.pct_change().fillna(0.0)) for k, v in allsl.items()}
for y in YEARS:
print(f" {y:>5d}" + "".join(f"{yr_each[c].get(y, 0):>+11.0f}" for c in cols))
# ---------- (C) integrazione ----------
print("\n" + "=" * 96)
print(f" (C) INTEGRAZIONE delle nuove famiglie nel MASTER | OOS da {OOS_DATE} | equal-weight daily")
print("=" * 96)
print(f" {'portafoglio':<16s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 80)
print(metric_block("MASTER-9", master9))
print(metric_block("+pairs", master_p))
print(metric_block("+TSM01", port_returns({**S, **tsm})))
print(metric_block("MASTER-esteso", master_x))
# correlazione media nuove vs master-9
dr_all = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in {**S, **pairs, **tsm}.items()})
corr = dr_all.corr(); old = list(S)
print(" " + "-" * 80)
for k in list(pairs) + list(tsm):
print(f" corr {k:<11s} vs MASTER-9 = {corr.loc[k, old].mean():+.2f}")
# ---------- (D) numeri sobri ----------
print("\n" + "=" * 96)
print(" (D) NUMERI SOBRI / RACCOMANDAZIONE (anti-overfit)")
print("=" * 96)
print(" - L'OOS singolo (2024-25) e' regime calmo -> Sharpe/DD OOS ottimistici ~50%.")
print(" - Numeri onesti del MASTER-esteso: worst-DD 90g ~6%, Sharpe atteso ~5, ogni anno positivo dal 2021.")
print(" - Regge leva 2x + slippage doppio (CAGR ~36%, Sharpe ~5).")
print(" - Rischio concentrato sui PAIRS (~57%) -> cap allocazione pairs ~30-35%.")
print(" - I pairs sono a 2 gambe (long/short): il worker live va esteso prima del trading reale.")
print(" - CONFIG RACCOMANDATA: MASTER-esteso, equal-weight, leva 2x, cap pairs 30-35%.")
if __name__ == "__main__":
main()
+94
View File
@@ -0,0 +1,94 @@
"""Verifica indipendente + ricerca TSM01 — Time-Series Momentum multi-orizzonte.
Long-only, multi-crypto, bassa frequenza. Per ogni asset il segnale è il CONSENSO
dei segni del momentum su più orizzonti lunghi (3/6/12 mesi); si tengono equal-weight
gli asset con consenso pieno positivo. Overlay risk-off: cash se BTC < SMA100.
Distinta da ROT02 (cross-sectional ranking): qui conta la PERSISTENZA assoluta lenta
di ogni asset, non la classifica relativa. Correlazione con ROT02 ~0.62 -> fattore
parzialmente indipendente, utile come diversificatore (NON come motore di ritorno:
rende meno di ROT02 a parita' di OOS). DD basso.
Anti-overfit: edge su ALTOPIANO (36/36 config orizzonti x thr x regime_n restano OOS+),
walk-forward stabile (4 anni up, 2 piatti per risk-off, mai un anno negativo), regge
fee 0.40% RT. Gran parte del DD basso viene dall'overlay risk-off SMA100 (condiviso),
la struttura multi-orizzonte aggiunge ~+38pp OOS e alza lo Sharpe 0.58->1.07.
Default gross=0.30 (era 0.45): stesso Sharpe ma DD 22%->15% (scelta robusta, non la piu' redditizia).
Engine onesto: pesi a close[i] da soli rendimenti passati, realizzo i->i+1, fee
one-way fee_rt/2 sul turnover. NETTO, leva implicita gross. OOS = ultimo 30%.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.honest_lab import available_assets, FEE_RT
from scripts.analysis.honest_rotation import build_panel
GROSS, OOS_FRAC = 0.30, 0.30 # gross 0.30 (anti-overfit): stesso Sharpe di 0.45, DD piu' basso
def tsmom_sim(horizons=(63, 126, 252), thr=1.0, regime_n=100, gross=GROSS,
fee_rt=FEE_RT, oos_frac=0.0, cheat=False):
"""horizons in giorni. thr=1.0 -> consenso pieno (tutti i segni positivi)."""
panel = build_panel(available_assets(), "1d")
cols = list(panel.columns); P = panel.values; T, N = P.shape
rets = np.zeros_like(P); rets[1:] = P[1:] / P[:-1] - 1
years = panel.index.year.values
btc = P[:, cols.index("BTC")]
bma = pd.Series(btc).rolling(regime_n).mean().values
start = max(max(horizons) + 1, regime_n + 1, int(T * (1 - oos_frac)) if oos_frac else 0)
cap = 1000.0; w = np.zeros(N); eq = [cap]; yearly = {}
eq_ts: list = []; eq_v: list = []
for i in range(start, T - 1):
risk_on = btc[i] > bma[i] if not np.isnan(bma[i]) else False
wi = i + 1 if cheat else i # cheat: usa il futuro (test no-look-ahead)
score = np.zeros(N)
for h in horizons:
score += np.sign(P[wi] / P[wi - h] - 1)
score /= len(horizons)
chosen = [j for j in range(N) if score[j] >= thr] if risk_on else []
nw = np.zeros(N)
for j in chosen:
nw[j] = gross / len(chosen)
cap -= cap * np.abs(nw - w).sum() * (fee_rt / 2); w = nw
cap = max(cap * (1 + float(np.dot(w, rets[i + 1]))), 10.0)
eq.append(cap)
eq_ts.append(panel.index[i + 1]); eq_v.append(cap)
y = int(years[i]); yearly[y] = yearly.get(y, 0.0) + float(np.dot(w, rets[i + 1])) * 100
eq = np.array(eq); peak = np.maximum.accumulate(eq)
dd = float(np.max((peak - eq) / peak) * 100)
yrs = (panel.index[-1] - panel.index[start]).days / 365.25 or 1
rets_d = np.diff(eq) / eq[:-1]
sharpe = float(np.mean(rets_d) / np.std(rets_d) * np.sqrt(365)) if np.std(rets_d) > 0 else 0.0
return dict(ret=(cap / 1000 - 1) * 100, cagr=((cap / 1000) ** (1 / yrs) - 1) * 100,
dd=dd, sharpe=sharpe, yearly=yearly, eq_ts=eq_ts, eq_v=eq_v,
pos_years=sum(1 for v in yearly.values() if v > 0), n_years=len(yearly))
def main():
print("=" * 90)
print(" TSM01 — TSMOM multi-orizzonte (3/6/12m consenso pieno) + risk-off SMA100")
print("=" * 90)
# no-look-ahead: cheat deve esplodere
base = tsmom_sim()
ch = tsmom_sim(cheat=True)
print(f" no-look-ahead: onesto FULL={base['ret']:+.0f}% vs cheat(futuro)={ch['ret']:+.0f}% -> "
f"{'OK (il cheat esplode -> niente leak)' if ch['ret'] > base['ret'] * 2 else 'CONTROLLARE'}")
o = tsmom_sim(oos_frac=1 - OOS_FRAC)
hi = tsmom_sim(fee_rt=0.002)
print(f"\n FULL {base['ret']:+.0f}% CAGR {base['cagr']:.0f}% DD {base['dd']:.0f}% "
f"Sharpe {base['sharpe']:.2f} anni+ {base['pos_years']}/{base['n_years']}")
print(f" OOS {o['ret']:+.0f}% DD {o['dd']:.0f}% | fee 0.40% RT: FULL {hi['ret']:+.0f}%")
print(" Per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in sorted(base["yearly"].items())))
if __name__ == "__main__":
main()
+4 -4
View File
@@ -9,12 +9,12 @@ La diversificazione e' il vero motore di risk-reduction: il DD del portafoglio
scende SOTTO quello della sleeve meno rischiosa, mantenendo una CAGR alta e scende SOTTO quello della sleeve meno rischiosa, mantenendo una CAGR alta e
azzerando quasi gli anni negativi (il 2022 bear passa da -30% di ROT a -1%). azzerando quasi gli anni negativi (il 2022 bear passa da -30% di ROT a -1%).
Risultato (netto, 2021-2026, leva 3x pos 15% per sleeve): Risultato (netto, 2021-2026, leva 3x pos 15% per sleeve; ROT02 ora top_k=3):
DIP01_BTC +322% DD 15% CAGR 31% DIP01_BTC +322% DD 15% CAGR 31%
TR01_basket +591% DD 27% CAGR 43% TR01_basket +591% DD 27% CAGR 43%
ROT02_dualmom +771% DD 40% CAGR 49% ROT02_dualmom +984% DD 26% CAGR 56% (top_k=3: DD 40->26, PnL su)
PORTAFOGLIO +642% DD 12% CAGR 45% <-- DD piu' basso di ogni sleeve PORTAFOGLIO +676% DD 13% CAGR 46% <-- DD piu' basso di ogni sleeve
Per-anno: 2021 +203 · 2022 -1 · 2023 +47 · 2024 +50 · 2025 +14 · 2026 -2 Per-anno: 2021 +224 · 2022 +1 · 2023 +48 · 2024 +48 · 2025 +10 · 2026 -2
Logica e ricostruzione: scripts/analysis/honest_improve2.py. Logica e ricostruzione: scripts/analysis/honest_improve2.py.
""" """
from __future__ import annotations from __future__ import annotations
+6 -5
View File
@@ -11,12 +11,13 @@ Combinare le due famiglie migliora il rischio/rendimento rispetto a ciascuna da
sola: il DD scende e lo Sharpe sale (la honest, da sola piu' lumpy, viene sola: il DD scende e lo Sharpe sale (la honest, da sola piu' lumpy, viene
stabilizzata dalle fade ad alta frequenza). Vedi scripts/analysis/combine_portfolio.py. stabilizzata dalle fade ad alta frequenza). Vedi scripts/analysis/combine_portfolio.py.
Risultato (netto, equal-weight daily, finestra comune 2021-2026, OOS = ultimo 30%): Risultato (netto, equal-weight daily, finestra comune 2021-2026, OOS = ultimo 30%;
ROT02 ora top_k=3 -> DD piu' basso):
FADE only (6) DD 8.2% Sharpe 3.95 (OOS DD 5.9 / Shrp 4.09) FADE only (6) DD 8.2% Sharpe 3.95 (OOS DD 5.9 / Shrp 4.09)
HONEST only (3) DD 12.0% Sharpe 1.90 (OOS DD 6.5 / Shrp 2.23) HONEST only (3) DD 12.6% Sharpe 2.20 (OOS DD 7.8 / Shrp 2.02)
MASTER eq (9) DD 5.2% Sharpe 3.95 (OOS DD 4.7 / Shrp 4.42) <- miglior Sharpe MASTER eq (9) DD 5.2% Sharpe 4.23 (OOS DD 4.7 / Shrp 4.33) <- miglior Sharpe
MASTER 50/50 DD 5.1% Sharpe 3.31 (OOS DD 4.3) <- miglior DD MASTER 50/50 DD 5.0% Sharpe 3.69 (OOS DD 4.5) <- miglior DD
CAGR ~46% mantenuta in entrambe le varianti combinate. CAGR ~47% mantenuta in entrambe le varianti combinate.
Due varianti operative selezionabili: Due varianti operative selezionabili:
weighting="equal" -> equal-weight sui 9 sleeve (massimo Sharpe) weighting="equal" -> equal-weight sui 9 sleeve (massimo Sharpe)
@@ -0,0 +1,78 @@
"""PR01 — Pairs / Spread Mean-Reversion fra cripto (market-neutral). FAMIGLIA NUOVA.
Distinta da tutto l'esistente (single-asset direzionale): scommette sul RIENTRO del
log-ratio di due cripto verso la sua media. Market-neutral (long A / short B) ->
correlazione ~0.02 col mercato -> diversificatore prezioso.
Logica (engine onesto verificato in scripts/analysis/pairs_research.py):
r[i] = log(closeA[i]/closeB[i]); z[i] = (r[i]-SMA_n(r)[i]) / STD_n(r)[i] (causale)
z <= -z_in -> LONG ratio (long A / short B)
z >= +z_in -> SHORT ratio (short A / long B)
EXIT: |z| <= z_exit (rientro) o time-limit max_bars. Ingresso/uscita a close.
Fee su 2 GAMBE = 2*fee_rt*lev (0.20% RT/coppia). Filtro candele sporche (salto>8%).
Validazione anti-overfit (netto, fee 0.20% RT/coppia a 2 gambe, leva 3x, OOS = ultimo
30%, CONFIG UNIVERSALE n=50 z_in=2.0 z_exit=0.75 max_bars=72, 1h):
ETH/BTC : CAGR 158% / Sharpe 4.36 / 8/9 anni+ (regge fee 6x)
LTC/ETH : CAGR 92% / Sharpe 3.08 / 8/8 anni+
ADA/ETH : CAGR 91% / Sharpe 2.69 / 7/8 anni+
BTC/LTC : CAGR 60% / Sharpe 2.36 / 7/8 anni+ (robusta anche a 4h)
ETH/SOL : CAGR 74% / Sharpe 1.96 / 5/7 anni+ (la piu' debole, DD ~63%)
- No look-ahead verificato (z[i] invariato perturbando il futuro).
- PLATEAU non picco: heatmap n x z_in -> 20/20 celle Sharpe>1 (ETH/BTC, BTC/LTC).
- WALK-FORWARD (rolling train 2y / test 6m): ETH/BTC 11/12 finestre positive,
BTC/LTC 9/10 -> edge distribuito su tutta la storia, non un regime singolo.
- Stress costi: 5/6 reggono fee+slippage realistici; solo ETH/BTC regge fee 6x.
- Correlazione con BTC daily ~0.02-0.08 -> market-neutral.
- SCARTATA BNB/ETH: robusta solo coi suoi parametri (overfit), crolla con la universale.
LIMITE OPERATIVO: e' una strategia a 2 gambe (long un perp + short l'altro), il worker
attuale e' single-leg. Per tradarla serve: (a) eseguibilita' short del perp B su
Deribit/Bybit, (b) gestione 2 ordini + fee doppie. Finche' il worker non supporta
2 gambe, PR01 resta validata in backtest ma non wired nel paper trader.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.pairs_research import pairs_sim, OOS_FRAC # noqa: E402
# CONFIG UNIVERSALE (anti-overfit): un'unica terna per TUTTE le coppie, niente
# cherry-picking per-coppia. Validata come ALTOPIANO (heatmap n x z_in: 20/20 celle
# Sharpe>1 su ETH/BTC e BTC/LTC) e walk-forward (ETH/BTC 11/12 finestre+, BTC/LTC 9/10).
UNIV = dict(n=50, z_in=2.0, z_exit=0.75, max_bars=72)
# Coppie robuste con la config universale. Sempre alt-liquido vs major (mai alt/alt).
# BNB/ETH SCARTATA: era robusta solo coi suoi parametri (n=30, z_exit=1.0) -> overfit;
# con la config universale crolla (Sharpe 1.5, DD 71%) ed e' la prima a morire allo stress costi.
PAIRS = [
("ETH", "BTC", UNIV), # la migliore (Sharpe 4.4 univ), regge fee 6x
("LTC", "ETH", UNIV),
("ADA", "ETH", UNIV),
("BTC", "LTC", UNIV), # robusta anche a 4h
("ETH", "SOL", UNIV), # piu' debole (DD ~63%, storia SOL corta) -> peso ridotto
]
def run():
print("=" * 92)
print(" PR01 — PAIRS spread reversion (market-neutral) | netto fee 0.20% RT/coppia, leva 3x")
print("=" * 92)
print(f" {'coppia':<10s}{'trd':>5s}{'win%':>6s}{'CAGR%':>7s}{'OOS DD%':>8s}{'DD%':>6s}{'Shrp':>6s}{'anni+':>7s}")
for a, b, p in PAIRS:
f = pairs_sim(a, b, **p)
o = pairs_sim(a, b, **p, split_frac=1 - OOS_FRAC)
yrs = f["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0)
print(f" {a+'/'+b:<10s}{f['trades']:>5d}{f['win']:>6.1f}{f['cagr']:>7.0f}{o['dd']:>8.0f}"
f"{f['dd']:>6.0f}{f['sharpe']:>6.2f}{f'{pos_y}/{len(yrs)}':>7s}")
print("\n Market-neutral (corr ~0.02-0.08 col mercato) -> ottimo diversificatore di portafoglio.")
print(" Pattern: solo alt-liquido vs major (BTC/ETH); alt-vs-alt e' rumore.")
print(" NB: 2 gambe (long A / short B), fee doppie. Worker live da estendere prima del live.")
if __name__ == "__main__":
run()
+11 -5
View File
@@ -7,10 +7,16 @@ sistema (2022, 2026 YTD) che erano gli unici anni rossi di ROT01.
Risultato (netto, fee 0.10% RT, gross 0.45, OOS = ultimo 30%): MIGLIORA TUTTO Risultato (netto, fee 0.10% RT, gross 0.45, OOS = ultimo 30%): MIGLIORA TUTTO
rispetto a ROT01. rispetto a ROT01.
ROT01 base : FULL +679% / OOS +44% / DD 53% ROT01 base : FULL +679% / OOS +44% / DD 53%
ROT02 SMA100 : FULL +1095% / OOS +98% / DD 40% <-- PnL su, DD giu' ROT02 k2 SMA100 : FULL +1095% / OOS +98% / DD 40% (versione iniziale)
Param-insensitive sulla finestra di regime (SMA100-150). Dettagli in ROT02 k3 SMA100 : FULL +1303% / OOS +68% / DD 26% <-- DD quasi dimezzato, PnL su
scripts/analysis/honest_improve.py (rot_improved).
MIGLIORIA DD (top_k 2 -> 3): la versione iniziale concentrava il book su 2 soli
asset; diversificare su 3 dimezza quasi il drawdown (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 (solo de-leverage), quindi si tiene top_k=3 senza vol-target.
Param-insensitive: top_k 3-4 e regime SMA100-150 danno risultati simili.
Dettagli e sweep in scripts/analysis/honest_improve.py (rot_improved).
""" """
from __future__ import annotations from __future__ import annotations
@@ -22,7 +28,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.honest_improve import rot_improved # noqa: E402 from scripts.analysis.honest_improve import rot_improved # noqa: E402
LOOKBACK, TOP_K, REGIME_N = 60, 2, 100 LOOKBACK, TOP_K, REGIME_N = 60, 3, 100 # top_k=3 (era 2): dimezza il DD
def run(): def run():