From 2a97294d672a362a516658ce378f2b1846f00896 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Sun, 7 Jun 2026 17:32:00 +0000 Subject: [PATCH] docs: statistiche per anno (trd/PnL%/maxDD) per strategia e mercato nel doc HTML Ogni card ora include la tabella anno x mercato: - fade MR01/02/07 (BTC+ETH) e DIP01: calcolate col PATH LIVE (EXIT-16 + trend 3.0), trades/PnL per anno di entry, DD per anno su equity compounding pos 0.15 + DD totale - PR01: matrice anno x 5 coppie, cella = PnL% (n trade, DD anno) + riga TOT (pairs_sim espone ora anche yearly_n, modifica non-breaking) - TR01/ROT02/TSM01: ret%/DD% per anno dall'equity canonica daily 2021+ - SH01: per anno dal walk-forward EXPANDING (regime validato e ora live) Nota di convenzione su ogni tabella (leva 3x test vs 2x live, fee incluse) + caveat: finestra canonica dal 2021, anni 2018-2020 mostrati per onesta' storica. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/report/strategie_attive.html | 20 +-- scripts/analysis/make_strategy_doc.py | 195 ++++++++++++++++++++++++-- scripts/analysis/pairs_research.py | 6 +- 3 files changed, 200 insertions(+), 21 deletions(-) diff --git a/docs/report/strategie_attive.html b/docs/report/strategie_attive.html index 8acaaa9..3c04c1a 100644 --- a/docs/report/strategie_attive.html +++ b/docs/report/strategie_attive.html @@ -21,7 +21,7 @@ font-size:13px;margin-top:10px}

PythagorasGoal — Strategie attive

Portafoglio live PORT06 (17 sleeve, capitale pool €2.000, leva 2x) · -v1.1.9 · generato 2026-06-07 16:55 UTC · backtest canonico: FULL Sharpe 6.61 / DD 3.58% — OOS Sharpe 8.77 / DD 1.34%

+v1.1.9 · generato 2026-06-07 17:31 UTC · backtest canonico: FULL Sharpe 6.61 / DD 3.58% — OOS Sharpe 8.77 / DD 1.34%

Tre famiglie principali quasi scorrelate fra loro (fade↔honest ~0.05, pairs ~0.02-0.09, shape ~0.08): la diversificazione è la leva anti-drawdown. Pesi equal con tetti per famiglia, @@ -39,15 +39,15 @@ non si fada un crollo/parabolica), EXIT-16 (stop solo sul close confermat

MR01 — Bollinger Fade (BTC, ETH)

FADEESECUZIONE REALE (testnet)

Quando il close chiude fuori dalla banda ±2.5σ attorno alla SMA50, entra CONTRO il movimento (short sopra, long sotto). TP alla media (il prezzo "torna a casa"), SL a 2·ATR, -time-limit 24 barre. Edge OOS validato: BTC +201% / ETH +1238% (fee incluse).

+time-limit 24 barre. Edge OOS validato: BTC +201% / ETH +1238% (fee incluse).

annoBTCETH
trdPnL%DD%trdPnL%DD%
201848-781042-8514
201957-841246+217
202068-291455-359
202182+326860+23812
2022108+567576+7496
2023122+318473+2482
2024113+41311126+8887
202590+368286+3618
202629+69424-97
TOT717+187032588+237623

PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale diverso, spesso negativi) sono mostrati per onestà storica.

MR02 — Donchian Fade (BTC, ETH)

FADEESECUZIONE REALE (testnet)

Fada la rottura degli estremi del canale Donchian a 20 barre (max/min recenti): short sulla rottura del massimo, long sulla rottura del minimo. TP al centro del canale. -Stessa tesi di MR01 con trigger diverso → si combinano bene.

+Stessa tesi di MR01 con trigger diverso → si combinano bene.

annoBTCETH
trdPnL%DD%trdPnL%DD%
2018127+698127+1013
2019118+1816114+1208
2020112+10014135-9423
2021136+33218127+16023
2022106+728591+65012
202396+2353146+5246
2024149+8685180+19524
2025136+3864151+9316
202637+88441+258
TOT1017+2823191112+427831

PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale diverso, spesso negativi) sono mostrati per onestà storica.

MR07 — Return Reversal (BTC, ETH)

FADEESECUZIONE REALE (testnet)

Guarda il rendimento della singola barra: se lo z-score supera ±3.5 (movimento estremo in un'ora), fada il movimento. Exit in multipli di ATR. È la fade più selettiva -(esposizione ~8% del tempo).

+(esposizione ~8% del tempo).

annoBTCETH
trdPnL%DD%trdPnL%DD%
201838-12725+1514
201952+55735-4015
202053-111243+395
202145+245724+725
202282+446558+4727
202367+168458+3371
202475+377587+6456
202560+190255+22617
202617+58217+404
TOT489+151712402+180623

PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale diverso, spesso negativi) sono mostrati per onestà storica.

EXIT-16 — perché lo stop scatta solo sul CLOSE

MECCANISMO COMUNE

Scoperta chiave della ricerca exit (34 agenti, 23 famiglie): gli stop intrabar da wick sono falsi negativi — l'overshoot che buca lo stop e rientra è esattamente il movimento che @@ -58,28 +58,28 @@ portafoglio: OOS Sharpe 8.82→10.06. Esteso oggi anche a DIP01 (grid 36/36).

DIP01 — Dip Buy (BTC)

HONESTESECUZIONE REALE (testnet)

Compra il dip: quando lo z-score del prezzo incrocia sotto −2.5 (sell-off rapido), entra long. TP alla SMA50, EXIT-16 sul SL, max 24 barre. È l'unico sleeve BTC con round-trip -reali su Deribit testnet (TP limit resting + disaster-stop a −30% sul book).

+reali su Deribit testnet (TP limit resting + disaster-stop a −30% sul book).

annoBTC
trdPnL%DD%
201879-7828
201962-10417
202056+6714
202169+31511
202283+40711
202383+2285
202481+3325
202585+3033
202629+76
TOT627+147637

PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale diverso, spesso negativi) sono mostrati per onestà storica.

TR01 — Basket Trend (4h)

HONESTSIMULATO

Trend-following difensivo: long quando EMA20>EMA100 sulle 4 ore, flat altrimenti, su un paniere equal-weight di 5 asset (BNB, BTC, DOGE, SOL, XRP). Cattura i trend lunghi che -le fade per costruzione non prendono. Valuta solo barre 4h COMPLETE.

+le fade per costruzione non prendono. Valuta solo barre 4h COMPLETE.

annoTR01 (paniere 5)
trdPnL%DD%
2021+36
2022-912
2023+455
2024+3819
2025-1519
2026-1011
TOT+5130

PnL = ritorno % dell'anno dall'equity giornaliera canonica (2021-2026, leva 3x convenzione test); DD = max drawdown nell'anno; trd non applicabile (ribilancio giornaliero del book).

ROT02 — Dual Momentum (1d)

HONESTSIMULATO

Rotazione: ogni giorno ordina 8 crypto per momentum a 60 giorni e tiene le top-3 (solo se positive), gross 0.45. Gate di regime: tutto cash se BTC<SMA100. Diversificare su 3 -asset invece di 2 ha quasi dimezzato il DD (40%→26%) alzando il ritorno.

+asset invece di 2 ha quasi dimezzato il DD (40%→26%) alzando il ritorno.

annoROT02 (universo 8)
trdPnL%DD%
2021+00
2022+00
2023+8110
2024+437
2025+1312
2026-89
TOT+13019

PnL = ritorno % dell'anno dall'equity giornaliera canonica (2021-2026, leva 3x convenzione test); DD = max drawdown nell'anno; trd non applicabile (ribilancio giornaliero del book).

PAIRS — spread reversion market-neutral (5 coppie × 5.88%, famiglia ≤33%)

PR01 — Pairs Reversion (ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL)

PAIRSSIMULATO

Market-neutral: quando il rapporto fra due asset si allontana troppo dalla sua media (|z| del log-ratio ≥ 2), compra la gamba debole e shorta la forte; chiude quando il rapporto rientra (|z| ≤ 0.75) o dopo 72 barre. Config universale per tutte le coppie (niente tuning per-coppia = anti-overfit). Correlazione col mercato ~0.05: rende anche quando il mercato è fermo. -Fee su 2 gambe. Senza stop per design → position size ridotto a 0.20 (esposizione ≈ validato).

+Fee su 2 gambe. Senza stop per design → position size ridotto a 0.20 (esposizione ≈ validato).

annoETH/BTCLTC/ETHADA/ETHBTC/LTCETH/SOL
2018-343 (177, dd 45)
2019+233 (198, dd 8)
2020+323 (211, dd 25)
2021+549 (202, dd 12)
2022+1344 (206, dd 3)+292 (45, dd 14)+899 (157, dd 5)+328 (58, dd 8)+1243 (147, dd 9)
2023+464 (213, dd 4)+56 (94, dd 5)+341 (173, dd 10)+61 (106, dd 14)+125 (168, dd 24)
2024+1661 (253, dd 6)+1024 (195, dd 9)+1078 (225, dd 16)+638 (186, dd 10)+1302 (199, dd 12)
2025+1173 (225, dd 16)+962 (196, dd 10)+1046 (187, dd 19)+686 (178, dd 7)+1264 (202, dd 11)
2026+61 (71, dd 2)+279 (76, dd 3)+140 (71, dd 3)+265 (71, dd 3)+270 (88, dd 4)
TOT+5464 (1756, dd 48)+2614 (606, dd 14)+3504 (813, dd 19)+1978 (599, dd 21)+4204 (804, dd 24)

cella: PnL% anno (n trade, max DD% anno) — Σ rendimenti netti per trade, fee 0.20% RT/coppia (2 gambe), leva 3x convenzione test. NB: la finestra canonica del portafoglio parte dal 2021; gli anni precedenti (spesso negativi) sono mostrati per onestà storica.

TSM — trend-following multi-orizzonte (1 sleeve × 6.47%)

TSM01 — TSMOM (1d)

TSMSIMULATO

Long sugli asset con consenso pieno di momentum su 3 orizzonti (3/6/12 mesi), gross 0.30, cash totale se BTC<SMA100. Mai un anno negativo nel backtest. Non è un motore di ritorno: è il diversificatore che lavora nei regimi in cui le fade soffrono. -Attualmente flat by-design (risk-off).

+Attualmente flat by-design (risk-off).

annoTSM01 (universo 8)
trdPnL%DD%
2021+00
2022+00
2023+00
2024-22
2025+76
2026+00
TOT+56

PnL = ritorno % dell'anno dall'equity giornaliera canonica (2021-2026, leva 3x convenzione test); DD = max drawdown nell'anno; trd non applicabile (ribilancio giornaliero del book).

SHAPE — ML morfologico (2 sleeve × 2.94%, famiglia ≤5.88%)

SH01 — Shape-ML (BTC, ETH)

SHAPESIMULATO

Una LogisticRegression legge 17 feature della forma delle ultime 24 barre e @@ -88,7 +88,7 @@ orizzonte. Training walk-forward causale (mai dati futuri). Win-rate ~50% nell'asimmetria, non nella frequenza. Senza stop-loss by design (ogni stop testato rompe l'edge): la coda si gestisce dimezzando il peso della famiglia (cap 5.88%).

Fix di oggi (punto-10): il training live usa la storia COMPLETA dal parquet -locale (il regime corto a 365g non era robusto: trade-rate 22% vs 10% validato).

+locale (il regime corto a 365g non era robusto: trade-rate 22% vs 10% validato).

annoBTCETH
trdPnL%DD%trdPnL%DD%
2018237-6623239+7415
2019318+8815365-1921
2020240+19410219-29353
2021224+30113146+6718
2022142+641191+7922
2023118+17517+215
2024144+110847+1086
202585+775108+5407
202623+59225-307
TOT1531+845231257+54761

PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale diverso, spesso negativi) sono mostrati per onestà storica.

Metodologia

    diff --git a/scripts/analysis/make_strategy_doc.py b/scripts/analysis/make_strategy_doc.py index 1690bd3..ccc6129 100644 --- a/scripts/analysis/make_strategy_doc.py +++ b/scripts/analysis/make_strategy_doc.py @@ -398,6 +398,172 @@ def chart_weights(): return b64(fig) +# ------------------------------------------------------- statistiche per anno +POS_T, LEV_T, FEE_T = 0.15, 3.0, 0.001 # convenzione TEST (canonica) + + +def yearly_stats(trades, ts): + """trades [(i, j, ret_netto_leveraged)] -> ({anno: n/pnl/dd}, dd_totale). + n e PnL per anno di ENTRY (Σ rendimenti netti per trade ×100); DD per anno + sul path di equity compounding (pos 0.15), peak resettato a inizio anno.""" + years: dict[int, dict] = {} + cap, peak, tot_peak, tot_dd = 1000.0, 1000.0, 1000.0, 0.0 + cur = None + for i, j, ret in sorted(trades, key=lambda t: t[1]): + ey, xy = ts.iloc[i].year, ts.iloc[j].year + d = years.setdefault(ey, {"n": 0, "pnl": 0.0, "dd": 0.0}) + d["n"] += 1 + d["pnl"] += ret * 100 + cap = max(cap + cap * POS_T * ret, 10.0) + if cur != xy: + cur, peak = xy, cap + peak = max(peak, cap) + tot_peak = max(tot_peak, cap) + tot_dd = max(tot_dd, (tot_peak - cap) / tot_peak * 100) + dx = years.setdefault(xy, {"n": 0, "pnl": 0.0, "dd": 0.0}) + dx["dd"] = max(dx["dd"], (peak - cap) / peak * 100) + return years, tot_dd + + +def equity_yearly(eq: pd.Series): + """Equity giornaliera -> {anno: pnl(ret% anno)/dd}, dd_totale (per i multi-asset).""" + out = {} + for y, g in eq.groupby(eq.index.year): + pk = g.cummax() + out[int(y)] = {"n": None, "pnl": (g.iloc[-1] / g.iloc[0] - 1) * 100, + "dd": float(((pk - g) / pk).max() * 100)} + pk = eq.cummax() + return out, float(((pk - eq) / pk).max() * 100) + + +def yearly_table(per_market: dict, note="") -> str: + """{mercato: (years_dict, tot_dd)} -> tabella HTML anno × (trd, PnL%, DD%).""" + yrs = sorted({y for ym, _ in per_market.values() for y in ym}) + head = "".join(f'{m}' for m in per_market) + sub = "".join("trdPnL%DD%" for _ in per_market) + rows = [] + for y in yrs: + cells = "" + for ym, _ in per_market.values(): + d = ym.get(y) + if d: + n = "—" if d["n"] is None else d["n"] + cells += f"{n}{d['pnl']:+.0f}{d['dd']:.0f}" + else: + cells += "———" + rows.append(f"{y}{cells}") + cells = "" + for ym, tot_dd in per_market.values(): + ns = [d["n"] for d in ym.values() if d["n"] is not None] + n = sum(ns) if ns else "—" + pnl = sum(d["pnl"] for d in ym.values()) + cells += f"{n}{pnl:+.0f}{tot_dd:.0f}" + rows.append(f'TOT{cells}') + base = ("PnL = Σ rendimenti netti per trade (%, leva 3x, fee 0.10% RT, convenzione test; " + "il live gira a leva 2x); DD = max drawdown nell'anno (equity compounding pos 0.15); " + "riga TOT: DD dell'intero periodo. NB: la finestra canonica di valutazione del " + "portafoglio parte dal 2021 — gli anni 2018-2020 (regime microstrutturale " + "diverso, spesso negativi) sono mostrati per onestà storica.") + return (f'{head}{sub}' + + "".join(rows) + f'
    anno

    {note or base}

    ') + + +def stats_fades(): + """Per-anno delle 3 fade × BTC/ETH col PATH LIVE (EXIT-16 + trend 3.0).""" + from scripts.analysis.risk_management import strats_for + from scripts.analysis.trendmax_port06_impact import build_trades_variant + out = {} + for asset in ("BTC", "ETH"): + df = load_data(asset, "1h") + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + for nm, (fn, params) in strats_for(asset).items(): + tr = build_trades_variant(fn(df, **params), df, mode="exit16", trend_max=3.0) + out.setdefault(nm, {})[asset] = yearly_stats(tr, ts) + return out + + +def stats_dip(): + from scripts.analysis.dip01_exit16_impact import dip_entries, dip_trades + df = load_data("BTC", "1h") + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + tr = dip_trades(dip_entries(df), df, "exit16") + return {"BTC": yearly_stats(tr, ts)} + + +def stats_pairs(): + """Tabella compatta: per anno 'PnL% (n)' per coppia + righe TOT/DD.""" + from scripts.analysis.pairs_research import pairs_sim + from scripts.strategies.PR01_pairs_reversion import PAIRS as PAIRS_CFG + cols, data = [], {} + for a, b, p in PAIRS_CFG: + r = pairs_sim(a, b, **p) + tag = f"{a}/{b}" + cols.append(tag) + s = pd.Series(r["eq_v"], index=pd.to_datetime(r["eq_ts"], utc=True)) + ydd = {int(y): float(((g.cummax() - g) / g.cummax()).max() * 100) + for y, g in s.groupby(s.index.year)} + data[tag] = dict(yearly=r["yearly"], n=r["yearly_n"], dd=r["dd"], + trades=r["trades"], ydd=ydd) + yrs = sorted({y for d in data.values() for y in d["yearly"]}) + rows = [] + for y in yrs: + cells = "".join( + (f"{data[c]['yearly'][y]:+.0f} ({data[c]['n'].get(y,0)}" + f", dd {data[c]['ydd'].get(y,0):.0f})") + if y in data[c]["yearly"] else "—" for c in cols) + rows.append(f"{y}{cells}") + tot = "".join(f"{sum(data[c]['yearly'].values()):+.0f} " + f"({data[c]['trades']}, dd {data[c]['dd']:.0f})" + for c in cols) + rows.append(f'TOT{tot}') + head = "".join(f"{c}" for c in cols) + return (f'{head}' + "".join(rows) + "
    anno
    " + '

    cella: PnL% anno (n trade, max DD% anno) — Σ rendimenti netti ' + "per trade, fee 0.20% RT/coppia (2 gambe), leva 3x convenzione test. NB: la " + "finestra canonica del portafoglio parte dal 2021; gli anni precedenti " + "(spesso negativi) sono mostrati per onestà storica.

    ") + + +def stats_multi(): + """TR01/ROT02/TSM01: per-anno ret/DD dall'equity giornaliera canonica (2021+).""" + from scripts.analysis.combine_portfolio import IDX + from scripts.analysis.honest_improve2 import _tr_basket_daily, _rot_daily_equity + from scripts.analysis.tsmom_research import tsmom_sim + from scripts.analysis.report_families import daily_from + tr = _tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX) + rot = _rot_daily_equity(IDX) + t = tsmom_sim() + tsm = daily_from(t["eq_ts"], t["eq_v"]) + note = ("PnL = ritorno % dell'anno dall'equity giornaliera canonica (2021-2026, leva 3x " + "convenzione test); DD = max drawdown nell'anno; trd non applicabile (ribilancio " + "giornaliero del book).") + return (yearly_table({"TR01 (paniere 5)": equity_yearly(tr)}, note), + yearly_table({"ROT02 (universo 8)": equity_yearly(rot)}, note), + yearly_table({"TSM01 (universo 8)": equity_yearly(tsm)}, note)) + + +def stats_sh01(): + """SH01 per-anno dal walk-forward EXPANDING (il regime validato e ora live).""" + from scripts.analysis.shape_ml_research import ml_wf_entries + out = {} + for asset in ("BTC", "ETH"): + df = load_data(asset, "1h") + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + c = df["close"].values + ents = ml_wf_entries(df, W=24, H=12, model="logit", thresh=0.58) + tr, last = [], -1 + for e in ents: + i, d, mb = e["i"], e["d"], e["max_bars"] + j = min(i + mb, len(c) - 1) + if i <= last or j <= i: + continue + ret = (c[j] - c[i]) / c[i] * d * LEV_T - FEE_T * LEV_T + tr.append((i, j, ret)) + last = j + out[asset] = yearly_stats(tr, ts) + return out + + # ----------------------------------------------------------------- HTML CSS = """ body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#f4f5f7;color:#222} @@ -433,6 +599,14 @@ def main(): print("genero grafici (episodi reali)...") panel = _daily_panel() + print("calcolo statistiche per anno (engine canonici/live-path)...") + st_fade = stats_fades() + st_dip = stats_dip() + t_pairs = stats_pairs() + t_tr, t_rot, t_tsm = stats_multi() + print(" SH01 walk-forward expanding (il piu' lento)...") + st_sh = stats_sh01() + g_w = chart_weights() g_mr01 = chart_fade("scripts.strategies.MR01_bollinger_fade", "BTC", dict(trend_max=3.0, ema_long=200), mr01_bands, @@ -460,17 +634,20 @@ def main(): c_mr01 = card("MR01 — Bollinger Fade (BTC, ETH)", B("fade", "FADE") + real, """

    Quando il close chiude fuori dalla banda ±2.5σ attorno alla SMA50, entra CONTRO il movimento (short sopra, long sotto). TP alla media (il prezzo "torna a casa"), SL a 2·ATR, -time-limit 24 barre. Edge OOS validato: BTC +201% / ETH +1238% (fee incluse).

    """, g_mr01) +time-limit 24 barre. Edge OOS validato: BTC +201% / ETH +1238% (fee incluse).

    """, g_mr01, + yearly_table({"BTC": st_fade["MR01"]["BTC"], "ETH": st_fade["MR01"]["ETH"]})) c_mr02 = card("MR02 — Donchian Fade (BTC, ETH)", B("fade", "FADE") + real, """

    Fada la rottura degli estremi del canale Donchian a 20 barre (max/min recenti): short sulla rottura del massimo, long sulla rottura del minimo. TP al centro del canale. -Stessa tesi di MR01 con trigger diverso → si combinano bene.

    """, g_mr02) +Stessa tesi di MR01 con trigger diverso → si combinano bene.

    """, g_mr02, + yearly_table({"BTC": st_fade["MR02"]["BTC"], "ETH": st_fade["MR02"]["ETH"]})) c_mr07 = card("MR07 — Return Reversal (BTC, ETH)", B("fade", "FADE") + real, """

    Guarda il rendimento della singola barra: se lo z-score supera ±3.5 (movimento estremo in un'ora), fada il movimento. Exit in multipli di ATR. È la fade più selettiva -(esposizione ~8% del tempo).

    """, g_mr07) +(esposizione ~8% del tempo).

    """, g_mr07, + yearly_table({"BTC": st_fade["MR07"]["BTC"], "ETH": st_fade["MR07"]["ETH"]})) c_e16 = card("EXIT-16 — perché lo stop scatta solo sul CLOSE", B("fade", "MECCANISMO COMUNE"), """

    Scoperta chiave della ricerca exit (34 agenti, 23 famiglie): gli stop intrabar da wick @@ -482,17 +659,17 @@ portafoglio: OOS Sharpe 8.82→10.06. Esteso oggi anche a DIP01 (grid 36/36).

    Compra il dip: quando lo z-score del prezzo incrocia sotto −2.5 (sell-off rapido), entra long. TP alla SMA50, EXIT-16 sul SL, max 24 barre. È l'unico sleeve BTC con round-trip -reali su Deribit testnet (TP limit resting + disaster-stop a −30% sul book).

    """, g_dip) +reali su Deribit testnet (TP limit resting + disaster-stop a −30% sul book).

    """, g_dip, yearly_table(st_dip)) c_tr = card("TR01 — Basket Trend (4h)", B("honest", "HONEST") + sim, """

    Trend-following difensivo: long quando EMA20>EMA100 sulle 4 ore, flat altrimenti, su un paniere equal-weight di 5 asset (BNB, BTC, DOGE, SOL, XRP). Cattura i trend lunghi che -le fade per costruzione non prendono. Valuta solo barre 4h COMPLETE.

    """, g_tr) +le fade per costruzione non prendono. Valuta solo barre 4h COMPLETE.

    """, g_tr, t_tr) c_rot = card("ROT02 — Dual Momentum (1d)", B("honest", "HONEST") + sim, """

    Rotazione: ogni giorno ordina 8 crypto per momentum a 60 giorni e tiene le top-3 (solo se positive), gross 0.45. Gate di regime: tutto cash se BTC<SMA100. Diversificare su 3 -asset invece di 2 ha quasi dimezzato il DD (40%→26%) alzando il ritorno.

    """, g_rot) +asset invece di 2 ha quasi dimezzato il DD (40%→26%) alzando il ritorno.

    """, g_rot, t_rot) c_pr = card("PR01 — Pairs Reversion (ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL)", B("pairs", "PAIRS") + sim, """ @@ -500,13 +677,13 @@ asset invece di 2 ha quasi dimezzato il DD (40%→26%) alzando il ritorno.

    "" (|z| del log-ratio ≥ 2), compra la gamba debole e shorta la forte; chiude quando il rapporto rientra (|z| ≤ 0.75) o dopo 72 barre. Config universale per tutte le coppie (niente tuning per-coppia = anti-overfit). Correlazione col mercato ~0.05: rende anche quando il mercato è fermo. -Fee su 2 gambe. Senza stop per design → position size ridotto a 0.20 (esposizione ≈ validato).

    """, g_pr) +Fee su 2 gambe. Senza stop per design → position size ridotto a 0.20 (esposizione ≈ validato).

    """, g_pr, t_pairs) c_tsm = card("TSM01 — TSMOM (1d)", B("tsm", "TSM") + sim, """

    Long sugli asset con consenso pieno di momentum su 3 orizzonti (3/6/12 mesi), gross 0.30, cash totale se BTC<SMA100. Mai un anno negativo nel backtest. Non è un motore di ritorno: è il diversificatore che lavora nei regimi in cui le fade soffrono. -Attualmente flat by-design (risk-off).

    """, g_tsm) +Attualmente flat by-design (risk-off).

    """, g_tsm, t_tsm) c_sh = card("SH01 — Shape-ML (BTC, ETH)", B("shape", "SHAPE") + sim, """

    Una LogisticRegression legge 17 feature della forma delle ultime 24 barre e @@ -515,7 +692,7 @@ orizzonte. Training walk-forward causale (mai dati futuri). Win-rate ~50% nell'asimmetria, non nella frequenza. Senza stop-loss by design (ogni stop testato rompe l'edge): la coda si gestisce dimezzando il peso della famiglia (cap 5.88%).

    Fix di oggi (punto-10): il training live usa la storia COMPLETA dal parquet -locale (il regime corto a 365g non era robusto: trade-rate 22% vs 10% validato).

    """, g_sh) +locale (il regime corto a 365g non era robusto: trade-rate 22% vs 10% validato).

    """, g_sh, yearly_table(st_sh)) html = f""" PythagorasGoal — Strategie attive PORT06 diff --git a/scripts/analysis/pairs_research.py b/scripts/analysis/pairs_research.py index cf0af7b..883c8ed 100644 --- a/scripts/analysis/pairs_research.py +++ b/scripts/analysis/pairs_research.py @@ -51,7 +51,7 @@ def pairs_sim(a, b, tf="1h", n=50, z_in=2.0, z_exit=0.5, max_bars=72, 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 = {} + trades = wins = 0; rets = []; yearly = {}; yearly_n = {} 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: @@ -81,6 +81,7 @@ def pairs_sim(a, b, tf="1h", n=50, z_in=2.0, z_exit=0.5, max_bars=72, 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 + yearly_n[ts.iloc[i].year] = yearly_n.get(ts.iloc[i].year, 0) + 1 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 @@ -90,7 +91,8 @@ def pairs_sim(a, b, tf="1h", n=50, z_in=2.0, z_exit=0.5, max_bars=72, 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) + dd=dd * 100, sharpe=sharpe, yearly=yearly, yearly_n=yearly_n, + eq_ts=eq_ts, eq_v=eq_v) def check_no_lookahead():