Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 666c906907 | |||
| 9ebdfc7a7a |
@@ -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**
|
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`.
|
||||||
|
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -8,9 +8,15 @@ 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():
|
||||||
|
|||||||
Reference in New Issue
Block a user