2 Commits

Author SHA1 Message Date
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
7 changed files with 183 additions and 19 deletions
+10 -4
View File
@@ -126,17 +126,23 @@ 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**
(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 OOS 4.42 (vs honest-only 12% DD / 2.23 Sharpe e
fade-only 8.2% DD / 4.09 Sharpe), CAGR ~46% mantenuta. Studio in
→ 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 3.95/4.42) e `5050` fra le due
famiglie (minimo DD: 5.1% full / 4.3% OOS). È la configurazione consigliata.
`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`.
+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.
**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`.
+1 -1
View File
@@ -170,7 +170,7 @@ def _rot_daily_equity(idx):
for i in range(101, T - 1):
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]
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)
for j in chosen:
nw[j] = 0.45 / len(chosen)
+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()
+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
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%
TR01_basket +591% DD 27% CAGR 43%
ROT02_dualmom +771% DD 40% CAGR 49%
PORTAFOGLIO +642% DD 12% CAGR 45% <-- DD piu' basso di ogni sleeve
Per-anno: 2021 +203 · 2022 -1 · 2023 +47 · 2024 +50 · 2025 +14 · 2026 -2
ROT02_dualmom +984% DD 26% CAGR 56% (top_k=3: DD 40->26, PnL su)
PORTAFOGLIO +676% DD 13% CAGR 46% <-- DD piu' basso di ogni sleeve
Per-anno: 2021 +224 · 2022 +1 · 2023 +48 · 2024 +48 · 2025 +10 · 2026 -2
Logica e ricostruzione: scripts/analysis/honest_improve2.py.
"""
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
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)
HONEST only (3) DD 12.0% Sharpe 1.90 (OOS DD 6.5 / Shrp 2.23)
MASTER eq (9) DD 5.2% Sharpe 3.95 (OOS DD 4.7 / Shrp 4.42) <- miglior Sharpe
MASTER 50/50 DD 5.1% Sharpe 3.31 (OOS DD 4.3) <- miglior DD
CAGR ~46% mantenuta in entrambe le varianti combinate.
HONEST only (3) DD 12.6% Sharpe 2.20 (OOS DD 7.8 / Shrp 2.02)
MASTER eq (9) DD 5.2% Sharpe 4.23 (OOS DD 4.7 / Shrp 4.33) <- miglior Sharpe
MASTER 50/50 DD 5.0% Sharpe 3.69 (OOS DD 4.5) <- miglior DD
CAGR ~47% mantenuta in entrambe le varianti combinate.
Due varianti operative selezionabili:
weighting="equal" -> equal-weight sui 9 sleeve (massimo Sharpe)
+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
rispetto a ROT01.
ROT01 base : FULL +679% / OOS +44% / DD 53%
ROT02 SMA100 : FULL +1095% / OOS +98% / DD 40% <-- PnL su, DD giu'
Param-insensitive sulla finestra di regime (SMA100-150). Dettagli in
scripts/analysis/honest_improve.py (rot_improved).
ROT01 base : FULL +679% / OOS +44% / DD 53%
ROT02 k2 SMA100 : FULL +1095% / OOS +98% / DD 40% (versione iniziale)
ROT02 k3 SMA100 : FULL +1303% / OOS +68% / DD 26% <-- DD quasi dimezzato, PnL su
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
@@ -22,7 +28,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
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():