Files
PythagorasGoal/Old/scripts/analysis/projection_3y.py
T
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
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>
2026-06-19 15:20:59 +00:00

149 lines
5.9 KiB
Python

"""Proiezione a 3 anni del portafoglio live (PORT06) ESCLUDENDO il 2024.
Risponde a: "partendo da 1000 EUR, con capitale che compone e puntata che cresce
di conseguenza, quale guadagno giornaliero atteso e quale traiettoria a 3 anni?"
Punti chiave (perche' i numeri differiscono da report_families):
- report_families/Portfolio.backtest usano le curve sleeve NATIVE a leva 3x.
- Il container LIVE (src.portfolio.runner via portfolios.yml) gira a pos 0.15 x 2x.
- Il PnL giornaliero scala ESATTAMENTE con la leva: pnl = cap * pos * lev * (ret - fee),
stesso segnale -> ratio 2/3 per gli sleeve leverati.
- ROT02 e TSM01 NON si riscalano: usano `gross` (0.45 / 0.30), indipendente dalla leva
e identico fra backtest e worker live.
- Il 2024 e' escluso perche' anno eccezionale (crypto +; gonfia ogni stima).
CAVEAT (le proiezioni sono OTTIMISTICHE):
- 2021-2025 e' quasi tutto bull/recovery; poco orso/flat prolungato nel campione.
- Dati alt = testnet (volume sottile, fill/slippage NON modellati).
- OOS singolo (2024-25) = regime calmo -> ~50% ottimistico (vedi report_families (D)).
Lo scenario SOBRIO (haircut ~50%) e' il numero prudente su cui pianificare.
Run: uv run python scripts/analysis/projection_3y.py
"""
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 scripts.portfolios._defs import PORTFOLIOS
from src.portfolio.sleeves import all_sleeve_equities, sleeve_returns_df
from scripts.analysis.combine_portfolio import port_returns
# sleeve la cui esposizione NON dipende dalla leva (gross-based) -> non si riscalano
NOSCALE = {"ROT02_rot", "TSM01"}
NATIVE_LEV = 3.0 # leva delle curve sleeve in combine_portfolio
EXCLUDE_YEAR = 2024
START_CAPITAL = 1000.0
def live_portfolio_returns(live_leverage: float = 2.0) -> pd.Series:
"""Rendimenti giornalieri di PORT06 al sizing LIVE (pos 0.15 x `live_leverage`).
Riscala gli sleeve leverati di live_leverage/NATIVE_LEV; ROT/TSM invariati."""
p = PORTFOLIOS["PORT06"]
p.leverage = live_leverage
p.weighting = "cap"
p.caps = {"PAIRS": 0.33}
eq = all_sleeve_equities()
dr = sleeve_returns_df(p.sleeve_ids)
w = p.weight_vector(dr)
scale = live_leverage / NATIVE_LEV
eq_live = {}
for sid in p.sleeve_ids:
r = eq[sid].pct_change().fillna(0.0)
r = r if sid in NOSCALE else r * scale
eq_live[sid] = (1 + r).cumprod()
pr = port_returns(eq_live, w)
pr.index = pd.to_datetime(pr.index)
return pr
def stats_ex_year(pr: pd.Series, exclude: int = EXCLUDE_YEAR) -> dict:
ex = pr[pr.index.year != exclude]
n = len(ex)
years = n / 365.0
tot = (1 + ex).prod() - 1
cagr = (1 + tot) ** (1 / years) - 1
yvals = [(1 + g).prod() - 1 for _, g in ex.groupby(ex.index.year)]
return {
"days": n, "years": years, "total": tot, "cagr": cagr,
"year_median": float(np.median(yvals)), "year_mean": float(np.mean(yvals)),
"daily_mean_eur": float(ex.mean() * START_CAPITAL),
"daily_median_eur": float(ex.median() * START_CAPITAL),
"daily_std_eur": float(ex.std() * START_CAPITAL),
"pos_days": float((ex > 0).mean()),
"per_year": {int(y): float((1 + g).prod() - 1) for y, g in pr.groupby(pr.index.year)},
}
def project(annual: float, years: int = 3, start: float = START_CAPITAL) -> list[dict]:
"""Capitale che compone; la puntata cresce col capitale -> EUR/giorno cresce."""
rows, cap = [], start
for yr in range(1, years + 1):
s = cap
cap = cap * (1 + annual)
rows.append({"year": yr, "start": s, "end": cap,
"gain": cap - s, "eur_per_day": (cap - s) / 365.0})
return rows
def years_to_target(daily_target: float, annual: float, start: float = START_CAPITAL) -> float:
"""Anni per raggiungere un certo EUR/giorno componendo (capitale = target*365/cagr)."""
cap_needed = daily_target * 365.0 / annual
if cap_needed <= start:
return 0.0
return float(np.log(cap_needed / start) / np.log(1 + annual))
def main():
pr = live_portfolio_returns(live_leverage=2.0)
s = stats_ex_year(pr)
print("=" * 72)
print(" PORT06 LIVE (pos 0.15 x 2x) — proiezione 3 anni, ESCLUSO 2024")
print("=" * 72)
print(" Rendimento live per anno:")
for y, v in s["per_year"].items():
flag = " <-- ESCLUSO" if y == EXCLUDE_YEAR else ""
print(f" {y}: {v * 100:+6.1f}%{flag}")
print()
print(f" CAGR (escl 2024): {s['cagr'] * 100:5.1f}% "
f"[{s['years']:.2f} anni di dati]")
print(f" anno mediano: {s['year_median'] * 100:5.1f}%")
print(f" anno medio: {s['year_mean'] * 100:5.1f}%")
print(f" EUR/giorno su 1000: media {s['daily_mean_eur']:.2f} | "
f"mediana {s['daily_median_eur']:.2f} | std {s['daily_std_eur']:.2f}")
print(f" giorni positivi: {s['pos_days'] * 100:.1f}%")
print()
scenarios = [
("CAGR backtest escl-2024", s["cagr"]),
("anno mediano", s["year_median"]),
("SOBRIO (haircut ~50%)", s["cagr"] * 0.5),
]
for name, g in scenarios:
print(f" -- 3 anni @ {g * 100:.0f}%/anno ({name}) --")
for r in project(g):
print(f" anno {r['year']}: {r['start']:7.0f} -> {r['end']:7.0f} EUR "
f"(+{r['gain']:5.0f}, ~{r['eur_per_day']:4.2f} EUR/g medi)")
print()
print(" -- Target 50 EUR/giorno (reality check) --")
for name, g in scenarios[:1] + scenarios[2:]:
cap_needed = 50.0 * 365.0 / g
t = years_to_target(50.0, g)
print(f" @ {g * 100:.0f}%/anno: servono ~{cap_needed:,.0f} EUR schierati "
f"-> da 1000 EUR, ~{t:.0f} anni componendo")
print(" => il collo di bottiglia e' il CAPITALE iniziale, non la strategia.")
if __name__ == "__main__":
main()