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>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user