14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
5.5 KiB
Python
137 lines
5.5 KiB
Python
"""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()
|