2 Commits

Author SHA1 Message Date
Adriano bcccfde9a0 feat(strategie): portafogli master (PORT02/PORT03) + waste delle peggiori (MR03, ROT01)
Crea gli artefatti accorpati e migliorati:
- PORT02_fade_master: 3 fade (MR01/MR02/MR07) x BTC/ETH = 6 sleeve, filtro trend,
  equal-weight daily. DD 8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
- PORT03_all_master: portafoglio MASTER fade+honest (9 sleeve), varianti equal
  (max Sharpe: DD 5.2%/4.7% OOS, Sharpe 3.95/4.42) e 50/50 (min DD 5.1%/4.3%).

Sposta in scripts/waste/ le due peggiori:
- MR03 keltner_fade: fade piu' debole (BTC Sharpe 1.22), ridondante con MR01, il
  filtro trend la peggiorava; rimuoverla MIGLIORA il portafoglio fade.
- ROT01 xsect_rotation: strettamente dominata da ROT02 (stesso meccanismo, ROT02
  meglio su tutto), non usata da alcun portafoglio.

Sganciata MR03 da strategy_loader, strategies.yml e dal motore portafogli
(risk_management.STRATS). La funzione keltner_fade resta in strategy_research_v2
come record. CLAUDE.md aggiornato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:21:37 +02:00
Adriano 1af4addbdd feat(analysis): studio combinazione strategie fade + honest (diversificazione)
combine_portfolio.py: costruisce l'equity giornaliera di tutte le sleeve (8 fade +
3 honest) su indice comune 2021-2026, misura la correlazione cross-famiglia e
confronta i portafogli FULL/OOS (ret, CAGR, DD, Sharpe).

Risultato: le due famiglie sono quasi scorrelate (corr ~0.05). Combinarle migliora
il rischio/rendimento: equal-weight 11 sleeve -> DD 6.1% full / 4.6% OOS, Sharpe OOS
4.46 (vs honest-only 12% DD / 2.23 e fade-only 8.6% DD / 4.14), CAGR ~43% mantenuta.
Il 50/50 fra famiglie da' il DD piu' basso (5.5% full / 4.0% OOS). Diario 2026-05-29
e nota CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:14:14 +02:00
10 changed files with 364 additions and 30 deletions
+23 -1
View File
@@ -90,9 +90,13 @@ Tutte le strategie estendono `src.strategies.base.Strategy`
|--------|------|-----------|-----------------------------------|----|------|
| **MR01** | Bollinger Fade | banda std attorno a SMA | BTC +201% / ETH +1238% | 15-72% | Fada la banda, TP alla media, SL ad ATR |
| **MR02** | Donchian Fade | estremi canale H/L | BTC +172% / ETH enorme | 30-42% | Fada la rottura del canale, TP al centro |
| **MR03** | Keltner Fade | canale ATR attorno a EMA | BTC +112% / ETH +886% | 20-66% | Banda indipendente da Bollinger |
| **MR07** | Return Reversal | z dei rendimenti di barra | BTC +105% / ETH +195% | 25-46% | Fada il movimento estremo, exit in ATR; esposizione ~8% |
> **MR03 Keltner Fade** spostata in `scripts/waste/`: era la fade più debole
> (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa
> idea di banda). Rimuoverla dal portafoglio ne ha *migliorato* le metriche.
> La funzione `keltner_fade` resta in `strategy_research_v2.py` come record.
**Lezione confermata:** l'edge è sempre *mean-reversion* (i breakout rientrano).
Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro
(RSI revert, ADX-filtered fade) perdono netti → restano scartati.
@@ -118,6 +122,24 @@ Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano
× BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul
paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown.
**Combinare le due famiglie (fade + honest).** Le fade (reversione intraday 1h) e le
honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate**
(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il
rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve
→ DD 5.2% full / 4.7% OOS e Sharpe 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
`scripts/analysis/combine_portfolio.py`.
**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.
Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con
`generate_signals`, quindi non nel `strategy_loader`.
**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze):
1. Ingresso eseguibile: direzione e prezzo decisi con dati **fino a `close[i]`**, mai `close[i-1]` con direzione da `i`.
2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva.
+49
View File
@@ -0,0 +1,49 @@
# Diario di ricerca — 2026-05-29
## Combinare le strategie migliora i risultati?
**Domanda:** usare insieme le due famiglie di strategie presenti sul repo migliora
il profilo rischio/rendimento rispetto a usarle separatamente?
- **FADE** (mie): reversione intraday 1h, long/short, BTC/ETH — MR01 Bollinger,
MR02 Donchian, MR03 Keltner, MR07 Return-reversal (tutte col filtro trend 3.0 ATR).
- **HONEST** (altra sessione): long-only multi-regime multi-crypto — DIP01 (dip-buy
1h BTC), TR01 (EMA-trend 4h basket), ROT02 (dual-momentum rotation 1d).
**Metodo** (`scripts/analysis/combine_portfolio.py`): per ogni sleeve si costruisce
l'equity **giornaliera** normalizzata su un indice comune (2021-01-01 → 2026-05-26),
si passa ai rendimenti giornalieri, si misura la correlazione cross-famiglia e si
confrontano i portafogli equal-weight (ribilanciati ogni giorno), 50/50 fra famiglie
e inverse-vol. Metriche FULL e OOS (ultimo 30% della finestra comune, da 2024-10-12):
ritorno, CAGR, max DD, Sharpe annualizzato. Le curve honest sono riusate da
`honest_improve2.py`; quelle fade da `risk_management.build_trades`.
**Correlazione:** cross-famiglia **+0.05** (quasi indipendenti). Intra-fade +0.18,
intra-honest +0.05. L'unica coppia un po' correlata è MR01_BTC↔DIP01_BTC (+0.43),
entrambe mean-reversion su BTC. Famiglie scorrelate ⇒ diversificazione quasi ideale.
**Risultati (FULL | OOS):**
| Portafoglio | Ret% | CAGR | DD% | Sharpe | oDD% | oSharpe |
|---|---|---|---|---|---|---|
| FADE only (8) | +549 | 41 | 8.6 | 3.75 | 5.4 | 4.14 |
| HONEST only (3) | +642 | 45 | 12.0 | 1.90 | 6.5 | 2.23 |
| **ALL equal-weight (11)** | +589 | 43 | 6.1 | **3.95** | 4.6 | **4.46** |
| **ALL 50/50 famiglie** | +615 | 44 | **5.5** | 3.18 | **4.0** | 3.87 |
| ALL inverse-vol | +483 | 39 | 5.8 | 3.97 | 4.6 | 4.02 |
**Conclusione: sì, combinare conviene.**
- DD crolla: combinato 5.56.1% full / 4.04.6% OOS, contro 8.6% (fade) e **12%**
(honest) da sole → drawdown ridotto del 3550%.
- Sharpe sale: combinato OOS **4.46** vs honest 2.23 (raddoppia) e batte pure fade (4.14).
- CAGR resta ~4344% (≈ media delle due famiglie) ma con metà del rischio: è il
"free lunch" della diversificazione fra sorgenti di edge scorrelate.
- Best Sharpe = equal-weight degli 11 sleeve; best DD = 50/50 fra le due famiglie.
**Caveat onesti:** la finestra comune è 20212026 (5.4 anni), OOS ~2024-10→oggi
(1.6 anni) — pochi regimi. CAGR e Sharpe sono backtest a leva 3x; il 2024 cripto
favorevole pesa. Il target €50/giorno resta vincolato dal capitale: 43% CAGR su
€1000 non fa €50/giorno a breve, serve compounding pluriennale o più capitale.
Prossimo passo: confermare il portafoglio combinato nel paper trader live.
**File:** `scripts/analysis/combine_portfolio.py` (nuovo).
+156
View File
@@ -0,0 +1,156 @@
"""Studio: combinare TUTTE le strategie (fade + honest) migliora i risultati?
Due famiglie con meccanismi e orizzonti diversi:
FADE (intraday 1h, long/short, BTC/ETH): MR01 boll, MR02 donchian, MR07
return-reversal — tutte col filtro trend 3.0 ATR. (MR03 keltner -> waste.)
HONEST (long-only, multi-regime, multi-crypto): DIP01 (dip-buy 1h BTC),
TR01 (EMA trend 4h basket), ROT02 (dual-momentum rotation 1d).
Metodo: per ogni sleeve si costruisce l'equity GIORNALIERA normalizzata su un
indice comune (2021-01-01 -> 2026-05-26), si passa ai rendimenti giornalieri,
si misura la correlazione cross-famiglia e si confrontano i portafogli
equal-weight (ribilanciati ogni giorno) e inverse-vol. Metriche FULL e OOS
(ultimo 30% della finestra comune): ritorno, CAGR, max DD, Sharpe annualizzato.
Tutto NETTO (fee gia' incluse nelle sleeve), leva 3x, pos 15% per sleeve.
"""
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.risk_management import strats_for, build_trades, INIT
# curve daily honest gia' pronte nell'altra famiglia
from scripts.analysis.honest_improve2 import (
_daily_equity, _norm, dip_market_gated, _tr_basket_daily, _rot_daily_equity,
)
IDX = pd.date_range("2021-01-01", "2026-05-26", freq="1D", tz="UTC")
OOS_FRAC = 0.30
SPLIT = int(len(IDX) * (1 - OOS_FRAC)) # confine OOS sulla finestra comune
OOS_DATE = IDX[SPLIT].date()
ANN = 365.0 # giorni/anno per annualizzare
# ---------------- equity giornaliere ----------------
def fade_daily_equity(asset: str, fn, params) -> pd.Series:
"""Equity giornaliera di uno sleeve fade: trade 1h (filtro trend 3.0) -> equity -> daily."""
df = load_data(asset, "1h")
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
trades = build_trades(fn(df, **params), df, trend_max=3.0)
n = len(df); eq = np.full(n, INIT, dtype=float); cap = INIT
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * 0.15 * ret, 10.0)
eq[j:] = cap
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
return _norm(s)
def build_all_sleeves() -> dict[str, pd.Series]:
sleeves: dict[str, pd.Series] = {}
# --- FADE: 8 sleeve ---
for asset in ["BTC", "ETH"]:
for nm, (fn, params) in strats_for(asset).items():
sleeves[f"{nm}_{asset}"] = fade_daily_equity(asset, fn, params)
# --- HONEST: 3 sleeve (riuso le funzioni dell'altra famiglia) ---
d = dip_market_gated("BTC", market_n=0, return_equity=True)
sleeves["DIP01_BTC"] = _norm(_daily_equity(d["eq_ts"], d["eq_v"], IDX))
sleeves["TR01_basket"] = _norm(_tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX))
sleeves["ROT02_rot"] = _norm(_rot_daily_equity(IDX))
return sleeves
# ---------------- metriche ----------------
def metrics(daily_ret: pd.Series, lo: int = 0, hi: int | None = None) -> dict:
r = daily_ret.iloc[lo:hi]
eq = (1 + r).cumprod()
peak = eq.cummax(); dd = float(((peak - eq) / peak).max() * 100)
yrs = len(r) / ANN
tot = (eq.iloc[-1] - 1) * 100
cagr = ((eq.iloc[-1]) ** (1 / yrs) - 1) * 100 if yrs > 0 else 0.0
sharpe = float(r.mean() / r.std() * np.sqrt(ANN)) if r.std() > 0 else 0.0
return dict(ret=tot, cagr=cagr, dd=dd, sharpe=sharpe)
def yearly_returns(daily_ret: pd.Series) -> dict[int, float]:
"""Rendimento % netto per anno solare dai rendimenti giornalieri composti."""
g = daily_ret.groupby(daily_ret.index.year).apply(lambda x: ((1 + x).prod() - 1) * 100)
return {int(y): float(v) for y, v in g.items()}
def port_returns(members: dict[str, pd.Series], weights: dict[str, float] | None = None) -> pd.Series:
"""Rendimenti giornalieri di un portafoglio ribilanciato ogni giorno ai pesi dati."""
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in members.items()})
if weights is None:
return dr.mean(axis=1)
w = pd.Series(weights); w = w / w.sum()
return (dr * w).sum(axis=1)
def inv_vol_weights(members: dict[str, pd.Series], lo=0, hi=None) -> dict[str, float]:
"""Pesi inversamente proporzionali alla volatilita' (stimata sulla finestra train)."""
vol = {k: v.pct_change().iloc[lo:hi].std() for k, v in members.items()}
inv = {k: (1.0 / s if s and s > 0 else 0.0) for k, s in vol.items()}
tot = sum(inv.values())
return {k: x / tot for k, x in inv.items()}
# ---------------- report ----------------
def row(label, dr):
f = metrics(dr); o = metrics(dr, 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['cagr']:>7.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
def main():
print("Costruzione equity giornaliere (puo' richiedere ~1 min)...")
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")}
# --- correlazione cross-famiglia ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in S.items()})
corr = dr.corr()
fade_k, hon_k = list(fade), list(honest)
cross = corr.loc[fade_k, hon_k]
print("\n" + "=" * 92)
print(f" CORRELAZIONE rendimenti giornalieri — FADE (righe) vs HONEST (colonne) | {IDX[0].date()}->{IDX[-1].date()}")
print("=" * 92)
print(f" {'':<12s}" + "".join(f"{c:>13s}" for c in hon_k))
for f in fade_k:
print(f" {f:<12s}" + "".join(f"{cross.loc[f,c]:>13.2f}" for c in hon_k))
intra_fade = corr.loc[fade_k, fade_k].values[np.triu_indices(len(fade_k), 1)].mean()
intra_hon = corr.loc[hon_k, hon_k].values[np.triu_indices(len(hon_k), 1)].mean()
print(f"\n Corr media intra-FADE {intra_fade:+.2f} | intra-HONEST {intra_hon:+.2f} | "
f"cross-famiglia {cross.values.mean():+.2f} (piu' bassa = piu' diversificazione)")
# --- confronto portafogli ---
print("\n" + "=" * 92)
print(f" PORTAFOGLI equal-weight (ribil. giornaliero) | OOS da {OOS_DATE} | leva3x pos15%/sleeve")
print("=" * 92)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oCAGR':>7s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 88)
row("FADE only (8 sleeve)", port_returns(fade))
row("HONEST only (3 sleeve)", port_returns(honest))
row("ALL equal-weight (11)", port_returns(S))
# 50/50 fra le due famiglie (ogni famiglia equipesata al suo interno)
fr, hr = port_returns(fade), port_returns(honest)
row("ALL 50/50 famiglie", (fr + hr) / 2)
# inverse-vol sul train, applicato a tutti gli 11 sleeve
w = inv_vol_weights(S, lo=0, hi=SPLIT)
row("ALL inverse-vol", port_returns(S, w))
print(" " + "-" * 88)
print(" Sharpe annualizzato sui rendimenti giornalieri. Confronta DD e Sharpe:")
print(" se il combinato ha DD piu' basso e Sharpe piu' alto delle singole famiglie, combinare conviene.")
if __name__ == "__main__":
main()
+5 -4
View File
@@ -29,18 +29,19 @@ sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.strategy_research import bollinger_fade, atr
from scripts.analysis.strategy_research_v2 import donchian_fade, keltner_fade, return_reversal
from scripts.analysis.strategy_research_v2 import donchian_fade, return_reversal
FEE_RT, LEV, POS, INIT, OOS_FRAC = 0.001, 3.0, 0.15, 1000.0, 0.30
# config base di ogni strategia (come strategies.yml); su ETH MR03 usa n=50
# config base di ogni strategia (come strategies.yml).
# NB: MR03 keltner_fade spostata in scripts/waste/ (fade piu' debole, ridondante
# con MR01); la funzione keltner_fade resta in strategy_research_v2 come record.
STRATS = {
"MR01": (bollinger_fade, dict(n=50, k=2.5, sl_atr=2.0, max_bars=24)),
"MR02": (donchian_fade, dict(n=20, sl_atr=2.0, max_bars=24)),
"MR03": (keltner_fade, dict(n=30, k=2.0, sl_atr=2.0, max_bars=24)),
"MR07": (return_reversal,dict(n=50, k=3.5, tp_atr=2.0, sl_atr=1.5, max_bars=24)),
}
STRATS_ETH = dict(STRATS); STRATS_ETH["MR03"] = (keltner_fade, dict(n=50, k=2.0, sl_atr=2.0, max_bars=24))
STRATS_ETH = dict(STRATS)
def strats_for(asset: str) -> dict:
+52
View File
@@ -0,0 +1,52 @@
"""PORT02 — Portafoglio FADE accorpato e migliorato (6 sleeve, equal-weight daily).
Accorpa le 3 strategie fade mean-reversion su BTC e ETH, ciascuna MIGLIORATA con
il filtro trend (salta i fade contro trend estremi: |close-EMA200|/ATR > 3.0):
MR01 Bollinger fade · MR02 Donchian fade · MR07 Return-reversal
x {BTC, ETH} -> 6 sleeve indipendenti, pos 0.15 ciascuna, leva 3x.
(MR03 Keltner spostata in waste: fade piu' debole e ridondante con MR01.)
Le curve sono poco correlate fra loro (corr media intra-fade ~0.18): la
diversificazione abbatte il DD aggregato ben sotto quello del singolo sleeve.
Risultato (netto fee 0.10% RT, equal-weight ribilanciato ogni giorno, finestra
comune 2021-2026, OOS = ultimo 30%):
Ret +666% / CAGR 46% / DD 8.2% FULL · OOS DD 5.9% / Sharpe 3.95 FULL / 4.09 OOS.
Ricostruzione e confronto: scripts/analysis/combine_portfolio.py.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import ( # noqa: E402
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
def run():
sleeves = {k: v for k, v in build_all_sleeves().items() if k.startswith("MR")}
pr = port_returns(sleeves)
full, oos = metrics(pr), metrics(pr, lo=SPLIT)
print("=" * 84)
print(f" PORT02 — FADE master (8 sleeve, equal-weight daily) | {IDX[0].date()} -> {IDX[-1].date()}")
print("=" * 84)
print(f" {'sleeve':<14s}{'Ret%':>9s}{'DD%':>7s}{'Shrp':>7s}")
for name, s in sleeves.items():
m = metrics(s.pct_change().fillna(0.0))
print(f" {name:<14s}{m['ret']:>+9.0f}{m['dd']:>7.1f}{m['sharpe']:>7.2f}")
print(" " + "-" * 80)
print(f" {'PORTAFOGLIO':<14s}{full['ret']:>+9.0f}{full['dd']:>7.1f}{full['sharpe']:>7.2f}"
f" CAGR {full['cagr']:.0f}%")
print(f" {' di cui OOS':<14s}{oos['ret']:>+9.0f}{oos['dd']:>7.1f}{oos['sharpe']:>7.2f}"
f" (da {OOS_DATE})")
pa = yearly_returns(pr)
print(" Per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in pa.items()))
if __name__ == "__main__":
run()
+77
View File
@@ -0,0 +1,77 @@
"""PORT03 — Portafoglio MASTER: accorpa TUTTE le strategie (fade + honest).
Unisce le due famiglie del progetto, quasi scorrelate (correlazione cross-famiglia
~0.05), in un unico portafoglio diversificato:
FADE (reversione intraday 1h, long/short, BTC/ETH, col filtro trend):
MR01 Bollinger · MR02 Donchian · MR07 Return-reversal -> 6 sleeve
HONEST (long-only multi-regime multi-crypto):
DIP01 dip-buy (1h) · TR01 EMA-trend (4h basket) · ROT02 dual-momentum (1d) -> 3 sleeve
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%):
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.
Due varianti operative selezionabili:
weighting="equal" -> equal-weight sui 9 sleeve (massimo Sharpe)
weighting="5050" -> 50% famiglia fade + 50% famiglia honest (minimo DD)
"""
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.combine_portfolio import ( # noqa: E402
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
def master_returns(sleeves: dict, weighting: str = "equal"):
"""Rendimenti giornalieri del portafoglio master.
equal = equal-weight su tutti gli 11 sleeve; 5050 = media fra le due famiglie."""
fade = {k: v for k, v in sleeves.items() if k.startswith("MR")}
honest = {k: v for k, v in sleeves.items() if not k.startswith("MR")}
if weighting == "5050":
return (port_returns(fade) + port_returns(honest)) / 2
return port_returns(sleeves)
def run():
sleeves = build_all_sleeves()
fade = {k: v for k, v in sleeves.items() if k.startswith("MR")}
honest = {k: v for k, v in sleeves.items() if not k.startswith("MR")}
print("=" * 90)
print(f" PORT03 — MASTER (fade + honest) | {IDX[0].date()} -> {IDX[-1].date()} | OOS da {OOS_DATE}")
print("=" * 90)
print(f" {'portafoglio':<22s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 86)
def line(label, pr):
f, o = metrics(pr), metrics(pr, lo=SPLIT)
print(f" {label:<22s}{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}")
line(f"FADE only ({len(fade)})", port_returns(fade))
line(f"HONEST only ({len(honest)})", port_returns(honest))
line(f"MASTER equal ({len(sleeves)})", master_returns(sleeves, "equal"))
line("MASTER 50/50 fam", master_returns(sleeves, "5050"))
print(" " + "-" * 86)
pa = yearly_returns(master_returns(sleeves, "equal"))
print(" MASTER equal per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in pa.items()))
print(" -> combinare le due famiglie scorrelate (~0.05) abbassa il DD e alza lo Sharpe.")
if __name__ == "__main__":
run()
-1
View File
@@ -19,7 +19,6 @@ _REGISTRY: dict[str, type[Strategy]] = {}
MODULE_MAP = {
"MR01_bollinger_fade": ("MR01_bollinger_fade", "BollingerFade"),
"MR02_donchian_fade": ("MR02_donchian_fade", "DonchianFade"),
"MR03_keltner_fade": ("MR03_keltner_fade", "KeltnerFade"),
"MR07_return_reversal": ("MR07_return_reversal", "ReturnReversal"),
}
+2 -24
View File
@@ -60,30 +60,8 @@ strategies:
trend_max: 3.0 # salta fade contro trend estremo (|close-EMA200|/ATR>3): Acc+ DD-
ema_long: 200
# MR03 Keltner fade: fade canale ATR su EMA (banda indipendente da Bollinger).
# Robusto su tutta la griglia n x k. BTC n30 k2.0 +112% OOS DD20%.
# ETH: edge ampio ma DD pieno ~65% (tratto dell'asset, come MR01) -> leva bassa.
- name: MR03_keltner_fade
asset: BTC
tf: 1h
enabled: true
params:
n: 30
k: 2.0
sl_atr: 2.0
max_bars: 24
# NB: su MR03 BTC il filtro trend PEGGIORA Acc e DD (unico sleeve) -> disattivo.
- name: MR03_keltner_fade
asset: ETH
tf: 1h
enabled: true
params:
n: 50
k: 2.0
sl_atr: 2.0
max_bars: 24
trend_max: 3.0 # salta fade contro trend estremo (|close-EMA200|/ATR>3): Acc+ DD-
ema_long: 200
# MR03 Keltner fade -> spostata in scripts/waste/ (fade piu' debole e ridondante
# con MR01; il filtro trend la peggiorava su BTC). Vedi CLAUDE.md.
# MR07 Return reversal: fade movimento di barra estremo (z dei rendimenti).
# Meccanismo distinto (volatilita' rendimenti, non livelli). Esposizione bassa