Files
Adriano Dal Pastro dc4135a781 fix: le soglie della tabella anni contraddicevano la decisione venue
L'operatore ha chiesto "GTAA01 e XS01 sono in deribit?". No, e la domanda scopre
un'incoerenza in cio' che avevo presentato.

MAPPA REALE DEI VENUE:
  TP01, SKH01  -> Deribit          (gli unici LIVE)
  VRP01        -> Deribit opzioni  (paper: regola "niente short-vol da modello")
  XS01         -> Hyperliquid      (stat-mode, serve ~$20k sulla gamba)
  GTAA01       -> Interactive Brokers, 6 ETF azionari (paper)

L'INCOERENZA: la tabella anno-per-anno annotava "anno 1: GTAA01 entra nel book" e
"anno 8: XS01 eseguibile", ma attivare GTAA01 E' lo split di venue, e l'operatore ha
deciso il 2026-07-26 di restare 100% Deribit fino a $20k. Le due cose non stanno insieme:
sotto i $20k quelle attivazioni non avvengono.

COSA NON ERA SBAGLIATO: i NUMERI. `book_series(with_gtaa=0)` usa il solo book Deribit a 2
sleeve, quindi tutte le traiettorie, i muri e le rendite pubblicate NON assumono nessuna
delle due attivazioni — sono Deribit-only e coerenti con la decisione. Sbagliata era solo
la colonna di annotazione, che descriveva cio' che sarebbe POSSIBILE come se fosse
pianificato.

Etichette corrette: le soglie ora dicono "sarebbe eseguibile ... ma la decisione venue dice
$20k", e i $20k sono marcati come il punto in cui GTAA01/XS01 diventano una SCELTA.

REGOLA: un'annotazione che descrive una possibilita' accanto a numeri che descrivono un
piano viene letta come parte del piano. Se una decisione registrata la esclude, va detto
nella riga stessa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 21:57:59 +00:00

130 lines
5.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
"""r0726_tabella_anni.py — la tabella anno per anno del piano dichiarato (€5.000 + €500/mese).
Non aggiunge misure: mette in forma leggibile quelle di `r0726_piano_5k500.py`, anno per anno e
con gli anni di CALENDARIO, piu' tre colonne che nelle tabelle precedenti mancavano e che servono
a leggerla onestamente:
* QUANTO HAI VERSATO fino a quel punto — senza, il capitale sembra un rendimento;
* la BANDA p10-p90, non solo la mediana — meta' dei percorsi sta fuori dalla colonna centrale;
* la SOGLIA attraversata, quando ce n'e' una (GTAA01, decisione venue, traguardo).
Ipotesi dichiarate (le stesse di tutto il filone): fattore d'ancora ×0.89 MISURATO, book live
TP01+SKH01 sul path reale, fisco 33%, EURUSD 1.09, NESSUN rischio di venue e NESSUN decadimento
dell'edge — i due scenari peggiori sono misurati altrove (`r0726_venue_risk`, `r0726_decadimento`)
e vanno letti insieme a questa.
uv run python scripts/research/r0726_tabella_anni.py # €500/mese (il piano)
uv run python scripts/research/r0726_tabella_anni.py 1000 # altro importo mensile
uv run python scripts/research/r0726_tabella_anni.py 1000 10000 # mensile + versamento iniziale
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import r0725_capcurve as CC # noqa: E402
import r0726_capwall_refresh as WR # noqa: E402
import r0726_deposits as DP # noqa: E402
OGGI = pd.Timestamp("2026-07-26")
CONTO = 596.92
# Parametrizzabili da riga di comando: chiedere "e con X al mese?" non deve richiedere di
# modificare lo script (e quindi di avere due versioni dei numeri in giro).
MENSILE_EUR = float(sys.argv[1]) if len(sys.argv) > 1 else 500.0
LUMP_EUR = float(sys.argv[2]) if len(sys.argv) > 2 else 5_000.0
N_PATHS, BLOCK, YEARS, SEED = 6000, 20, 20, 20260726
# ⚠️ NESSUNA di queste soglie e' un'attivazione automatica, e due di esse richiedono un SECONDO
# VENUE: GTAA01 sta su Interactive Brokers (6 ETF azionari), XS01 su Hyperliquid. Solo TP01 e
# SKH01 sono su Deribit. Con la decisione dell'operatore del 2026-07-26 (100% Deribit fino a
# $20k), le soglie sotto i $20k NON attivano niente: si limitano a dire quando la cosa
# diventerebbe possibile. La simulazione usa comunque il solo book Deribit a 2 sleeve
# (`book_series` con with_gtaa=0), quindi i numeri NON assumono nessuna di queste attivazioni.
SOGLIE = [(13_000, "GTAA01 sarebbe eseguibile (IB) — ma la decisione venue dice $20k"),
(20_000, "⭐ si riapre la decisione venue: qui GTAA01/XS01 diventano una scelta"),
(117_000, "XS01 sarebbe eseguibile (Hyperliquid), sempre 2° venue")]
def main() -> None:
r = DP.deluck_returns()
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
_, perp, wall = WR.perp_and_wall(r, 1.0, gross)
start = CONTO + LUMP_EUR * CC.EURUSD
dep = MENSILE_EUR * CC.EURUSD
rng = np.random.default_rng(SEED)
n_days = 365 * YEARS
paths = CC._boot_paths(r, N_PATHS, n_days, BLOCK, rng)
cap = np.full(N_PATHS, start)
paid = start
snaps, paid_at = {}, {}
for t in range(n_days):
cap = cap * (1.0 + paths[:, t])
if t % 30 == 0 and t > 0:
cap += dep
paid += dep
if (t + 1) % 365 == 0:
y = (t + 1) // 365
snaps[y], paid_at[y] = cap.copy(), paid
print("=" * 104)
print(f" PIANO €{LUMP_EUR:,.0f} SUBITO + €{MENSILE_EUR:,.0f}/MESE — anno per anno")
print("=" * 104)
print(f" partenza ${start:,.0f} · bersaglio €{CC.TARGET_EUR_DAY:.0f}/g = ${wall:,.0f} "
f"· rendita perpetua {perp:.2%} · fattore d'ancora ×{DP.DELUCK}")
print(f" {N_PATHS:,} percorsi bootstrap · SENZA rischio di venue e SENZA decadimento "
f"dell'edge (misurati a parte)")
print(f"\n {'anno':>5}{'':>7}{'versato':>11}{'p10':>12}{'MEDIANO':>13}{'p90':>13}"
f"{'rendita med.':>15}{'P(traguardo)':>14} soglia")
print(" " + "-" * 100)
viste = set()
for y in range(1, YEARS + 1):
s = snaps[y]
med = float(np.median(s))
rent = med * perp * (1 - CC.TAX_RATE) / 365.0 / CC.EURUSD
p_wall = float((s >= wall).mean())
nota = ""
for lv, txt in SOGLIE:
if lv not in viste and med >= lv:
viste.add(lv)
nota = f"<- {txt}"
if wall not in viste and med >= wall:
viste.add(wall)
nota = "<- ⭐ TRAGUARDO (capitale mediano)"
anno_cal = (OGGI + pd.DateOffset(years=y)).year
print(f" {y:>5}{anno_cal:>7}{paid_at[y]:>10,.0f}${np.percentile(s,10):>11,.0f}"
f"${med:>12,.0f}${np.percentile(s,90):>12,.0f}"
f"{rent:>12.2f} €/g{p_wall:>13.1%} {nota}")
# da dove viene il capitale
print("\n" + "=" * 104)
print(" DA DOVE VIENE IL CAPITALE (mediano)")
print("=" * 104)
print(f"\n {'anno':>5}{'versato':>12}{'rendimento':>14}{'quota da rendimento':>22}")
for y in (1, 3, 5, 10, 15, 20):
med = float(np.median(snaps[y]))
ret = med - paid_at[y]
print(f" {y:>5}{paid_at[y]:>11,.0f}${ret:>13,.0f}${ret/med:>21.0%}")
print("\n All'inizio il capitale E' quello che hai versato; il rendimento diventa la parte")
print(" principale solo dopo ~8-10 anni. E' la ragione per cui i primi anni si giudicano")
print(" sui VERSAMENTI e non sui risultati.")
print("\n ⚠️ La colonna MEDIANA non e' una previsione: meta' dei percorsi sta sotto. La banda")
print(" p10-p90 e' larga per costruzione (vol ~8%/anno composta per 20 anni), e i due")
print(" scenari che la tabella NON contiene — fallimento del venue e morte dell'edge —")
print(" sono misurati in r0726_venue_risk.py e r0726_decadimento.py.")
if __name__ == "__main__":
main()