diff --git a/scripts/analysis/report_families.py b/scripts/analysis/report_families.py new file mode 100644 index 0000000..1f4e9a7 --- /dev/null +++ b/scripts/analysis/report_families.py @@ -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()