17b0f2cb47
Misura nuova: a $6.050 lo split CON GTAA01 e' impossibile (servirebbe il 50% del conto per i $3.000 di gamba minima), ma quello in LIQUIDITA' non ha soglie. Due correzioni prima dei numeri: - la configurazione vera e' 5.000 + 500/mese (dal commento in config/live.json del 26/07), non i 250/mese di tutte le tabelle. Nessuna azione di config: il cap e' gia' armato e vale min($3.000, equity_osservata x 0.5) = leva lorda <=1x. - a $6.050 non si sblocca nulla (GTAA01/XS01/XSR01 tutti fuori portata o sotto gate): il book resta TP01+SKH01. Split in liquidita' (p=1%, 20a): fuori 10% -> P(perso tutto) 18.4% -> 3.5% (0.0% con cassa in banca), salvati $6.818; 25% -> salvati $17.045, al prezzo di 0.9pp di P(arrivare) e circa un anno di ritardo mediano. P(perso tutto) SATURA a qualunque quota > 0: la protezione binaria si compra col fatto di avere un secondo conto. Cio' che distingue le quote e' il salvataggio. ERRORE CORRETTO IN SESSIONE: la colonna del salvataggio riportava prima il capitale a 20 anni condizionato al fallimento ($77k al 10% = 11x il vero), gonfiato dalla convenzione ereditata dal 26/07 per cui i versamenti si dirottano ai superstiti -> attribuiva allo split il valore di continuare a versare, che si ottiene comunque aprendo un altro conto. Ora misura il salvataggio istantaneo, congelato in un test. simulate() accetta un hazard PER VENUE (una cassa in banca non fallisce come un exchange); la replica esatta dei numeri del 26/07 e' preservata e testata. Test: 4 nuovi (508 totali), tutti verdi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
351 lines
20 KiB
Python
351 lines
20 KiB
Python
#!/usr/bin/env python
|
||
"""r0727_lumpsum_split.py — il capitale che e' GIA' fermo, e cosa fa alla decisione di venue.
|
||
|
||
LA DOMANDA. Sul conto che gira ci sono **$600**. Fermi altrove ce ne sono ~**€10.000** (Revolut:
|
||
€6.043 in XEON, che rende ~0% reale netto dopo inflazione e tasse, piu' VWCE/SPYM/PPFB) e €329 su
|
||
Degiro. Tutte le traiettorie pubblicate (25/07 e 26/07) partono da `START = 600.0` **cablato**: il
|
||
progetto ha misurato con cura il CALENDARIO dei versamenti (fattore 6) e non ha mai misurato un
|
||
**versamento iniziale**.
|
||
|
||
PERCHE' LE DUE DOMANDE SI MISURANO INSIEME. Un lump-sum e' il front-loading estremo, e il 26/07
|
||
aveva gia' misurato che il front-loading *mette piu' capitale sull'exchange prima* — cioe' proprio
|
||
dove vive il rischio di venue. E la decisione del 26/07 ("100% Deribit fino a $20k") e' stata
|
||
presa con **$600 sul conto, dove lo split e' impossibile per costruzione**. CLAUDE.md registra
|
||
l'impegno esplicito: *"Se cambia il piano (orizzonte, importo dei versamenti) si riapre PRIMA."*
|
||
Un lump-sum da €5-10k e' esattamente quel cambiamento. Misurare il lump-sum senza il rischio di
|
||
venue risponderebbe alla meta' comoda della domanda.
|
||
|
||
COSA NON DECIDE QUESTO SCRIPT. Quanto di quei €6.043 sia un vero fondo d'emergenza e quanto
|
||
capitale in attesa: e' una decisione dell'operatore, e un fondo d'emergenza NON e' capitale
|
||
disponibile. Qui si misura **cosa compra ogni euro che entra**, per ogni taglia, cosi' che la
|
||
decisione si prenda sul prezzo giusto.
|
||
|
||
ASSUNZIONI DICHIARATE
|
||
* Il costo-opportunita' del lump-sum e' ~0% reale (XEON: ~2% lordo, ~0 netto dopo inflazione e
|
||
26%). Tutti i numeri sono NOMINALI in USD, come ovunque nel progetto: il confronto e' quindi
|
||
fra "fermo a ~0 reale" e "dentro il book", non fra due investimenti rischiosi.
|
||
* Book = quello LIVE (TP01+SKH01 su Deribit), de-luckato ×0.89 (fattore MISURATO,
|
||
r0726_deluck_factor). E' lo stesso book di ogni traiettoria pubblicata -> i numeri sono
|
||
confrontabili con la tabella dei versamenti del 26/07.
|
||
* Bersaglio = $272.061, il muro del 26/07 (rendita perpetua a €50/g netti). Identico per CONC e
|
||
SPLIT: e' CONSERVATIVO contro lo split, che avrebbe un muro leggermente diverso.
|
||
* `p` (probabilita' annua di fallimento di un venue) NON e' stimata: e' una sensibilita'.
|
||
La sceglie l'operatore.
|
||
|
||
uv run python scripts/research/r0727_lumpsum_split.py
|
||
"""
|
||
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_venue_risk as VR # noqa: E402
|
||
from src.portfolio.gtaa import GTAA_MIN_CAPITAL # noqa: E402
|
||
|
||
DELUCK = 0.89
|
||
START = 600.0 # il conto Deribit di oggi
|
||
YEARS = 20
|
||
N_PATHS = 4000
|
||
BLOCK = 20
|
||
SEED = 726 # lo stesso di r0726_venue_risk: consente la replica esatta
|
||
TARGET = VR.TARGET # $272.061
|
||
LUMPS_EUR = (0.0, 2_000.0, 5_000.0, 10_000.0)
|
||
P_GRID = (0.0, 0.005, 0.01, 0.02, 0.05)
|
||
# La configurazione realmente pianificata dall'operatore: il cap di config/live.json e' stato
|
||
# alzato il 26/07 "in previsione del versamento (EUR 5.000 + 500/mese -> equity ~$6.050)".
|
||
LUMP_FOCUS = 5_000.0
|
||
DEP_FOCUS = 500.0
|
||
|
||
# Split: la quota su IB parte dal 25% raccomandato da r0725_ib10k (oltre, la rendita cala:
|
||
# 0% -> €2.16/g, 50% -> €1.54/g su $11.5k). Ma la quota MINIMA e' imposta dall'eseguibilita'
|
||
# della gamba equity: sotto GTAA_MIN_CAPITAL non c'e' uno sleeve, c'e' cash su un secondo conto.
|
||
# Si accetta di salire fino a IB_SHARE_MAX pur di rendere lo split possibile; oltre, no.
|
||
CONC = {"Deribit": 1.0}
|
||
IB_SHARE = 0.25
|
||
IB_SHARE_MAX = 0.35
|
||
|
||
# Haircut sulla gamba IB nella variante realistica: costi a taglia piccola (CAGR 3.8% -> 3.0% a
|
||
# $3k, misurato il 25/07) + drag del veicolo UCITS (+0.10%/anno, misurato il 26/07).
|
||
IB_HAIRCUT_YR = 0.008 + 0.001
|
||
|
||
|
||
def simulate(cfg: dict, series: dict, p_annual: float | dict, lump_usd: float = 0.0,
|
||
dep_eur: float = 250.0, n_paths: int = N_PATHS, years: int = YEARS,
|
||
seed: int = SEED) -> dict:
|
||
"""Accumulo con capitale iniziale `START + lump_usd` e jump di venue.
|
||
|
||
Generalizza `r0726_venue_risk.simulate` sul capitale iniziale, sul deposito e sul rischio
|
||
PER VENUE: la struttura (ordine di consumo dell'RNG compreso) e' identica, cosi' che con
|
||
lump=0, dep=250 e `p` scalare i numeri debbano coincidere ESATTAMENTE con quelli del 26/07.
|
||
|
||
`p_annual` puo' essere un dict {venue: p}: un conto bancario/liquidita' NON fallisce come un
|
||
exchange (tutela dei depositi fino a €100k), e attribuirgli lo stesso hazard e' una scelta
|
||
CONSERVATIVA contro lo split — che va poi misurata, non assunta.
|
||
"""
|
||
rng = np.random.default_rng(seed)
|
||
n_days = years * 365
|
||
names = list(cfg)
|
||
J = pd.concat({k: series[k] for k in names}, axis=1, join="inner").fillna(0.0)
|
||
R = J.values.astype(float)
|
||
idx = CC._boot_paths(np.arange(len(R), dtype=float), n_paths, n_days, BLOCK, rng).astype(int)
|
||
dep = dep_eur * CC.EURUSD
|
||
|
||
w0 = np.array([cfg[k] for k in names], float)
|
||
cap = np.outer(np.full(n_paths, START + lump_usd), w0)
|
||
alive = np.ones((n_paths, len(names)), bool)
|
||
p_vec = np.array([p_annual.get(k, 0.0) if isinstance(p_annual, dict) else p_annual
|
||
for k in names], float)
|
||
haz = 1.0 - (1.0 - p_vec) ** (1.0 / 365.0) # (V,), broadcast sulle estrazioni
|
||
hit = np.zeros(n_paths, bool)
|
||
hit_day = np.full(n_paths, n_days + 1, int)
|
||
main_i = int(np.argmax(w0)) # il venue che porta il book
|
||
salvage = np.full(n_paths, np.nan) # cio' che resta NEL MOMENTO in cui il main cade
|
||
for t in range(n_days):
|
||
cap *= (1.0 + R[idx[:, t], :])
|
||
if (haz > 0).any():
|
||
dead = (rng.random((n_paths, len(names))) < haz) & alive
|
||
cap = np.where(dead, 0.0, cap)
|
||
alive &= ~dead
|
||
jm = dead[:, main_i]
|
||
if jm.any():
|
||
salvage[jm] = cap[jm].sum(axis=1)
|
||
if t % 30 == 0 and t > 0 and dep > 0:
|
||
wa = alive * w0
|
||
tot = wa.sum(axis=1, keepdims=True)
|
||
share = np.divide(wa, tot, out=np.zeros_like(wa), where=tot > 0)
|
||
cap += dep * share
|
||
tot_cap = cap.sum(axis=1)
|
||
newly = (~hit) & (tot_cap >= TARGET)
|
||
hit_day[newly] = t
|
||
hit |= newly
|
||
yrs = np.where(hit, hit_day / 365.0, np.nan)
|
||
fin = yrs[~np.isnan(yrs)]
|
||
# Cio' che distingue una quota dall'altra NON e' P(perso tutto) — che satura a qualunque
|
||
# quota > 0 — ma quanto si SALVA nel momento del fallimento. ⚠️ Volutamente NON si misura il
|
||
# capitale a 20 anni condizionato al fallimento: dopo la morte di un venue questo modello
|
||
# dirotta i versamenti ai superstiti (e li scarta se non ne restano), quindi quel numero
|
||
# attribuirebbe allo split il valore di continuare a versare — che si puo' fare comunque
|
||
# aprendo un altro conto. Il salvataggio istantaneo e' l'unica parte che lo split compra.
|
||
sv = salvage[~np.isnan(salvage)]
|
||
return dict(p_reach=float(hit.mean()),
|
||
median_salvage=float(np.median(sv)) if len(sv) > 20 else float("nan"),
|
||
median_yrs=float(np.median(fin)) if len(fin) > 50 else float("nan"),
|
||
p_all_dead=float((~alive.any(axis=1)).mean()),
|
||
median_end=float(np.median(cap.sum(axis=1))),
|
||
paid=START + lump_usd + dep * (n_days // 30))
|
||
|
||
|
||
def haircut(s: pd.Series, per_year: float) -> pd.Series:
|
||
"""Abbassa il drift di una serie giornaliera di `per_year` all'anno (costi non modellati)."""
|
||
return s - per_year / 365.0
|
||
|
||
|
||
def split_config(total_usd: float) -> dict | None:
|
||
"""Configurazione di split possibile a quel capitale, o None.
|
||
|
||
La quota IB non e' libera: dal basso la vincola l'ESEGUIBILITA' (sotto GTAA_MIN_CAPITAL la
|
||
gamba equity non esiste, e lo 'split' sarebbe cash fermo su un secondo conto), dall'alto la
|
||
vincola la RENDITA (r0725_ib10k: oltre il 25% si paga, oltre il 50% si dimezza). Fra i due
|
||
vincoli si prende la quota minima che rende la gamba eseguibile — mai meno del 25%
|
||
raccomandato, mai piu' di IB_SHARE_MAX.
|
||
"""
|
||
need = GTAA_MIN_CAPITAL / total_usd if total_usd > 0 else float("inf")
|
||
share = max(IB_SHARE, need)
|
||
if share > IB_SHARE_MAX:
|
||
return None
|
||
return {"Deribit": 1.0 - share, "IB": share}
|
||
|
||
|
||
def main() -> None:
|
||
print("=" * 104)
|
||
print(" r0727 — IL CAPITALE GIA' FERMO: cosa compra un versamento iniziale, e cosa fa al venue")
|
||
print("=" * 104)
|
||
|
||
series = VR.venue_series()
|
||
r_deribit = series["Deribit"].values.astype(float)
|
||
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
|
||
_, perp, wall = WR.perp_and_wall(r_deribit, 1.0, gross)
|
||
print(f" book live de-luckato ×{DELUCK} | rendita perpetua {perp:.2%} | bersaglio ${TARGET:,.0f}")
|
||
print(f" {N_PATHS} path, blocchi {BLOCK}g, orizzonte {YEARS}a, conto di partenza ${START:.0f}, "
|
||
f"EURUSD {CC.EURUSD}")
|
||
|
||
# ---------------------------------------------------------------- validazione
|
||
print("\n" + "-" * 104)
|
||
print(" VALIDAZIONE — con lump 0 e €250/mese si devono riprodurre i numeri del 26/07")
|
||
print("-" * 104)
|
||
for p in (0.0, 0.01):
|
||
mine = simulate(CONC, series, p, lump_usd=0.0, dep_eur=250.0)
|
||
theirs = VR.simulate({"Deribit": 1.0}, series, p, n_paths=N_PATHS, years=YEARS, seed=SEED)
|
||
ok = (abs(mine["p_reach"] - theirs["p_reach"]) < 1e-12
|
||
and abs(mine["p_all_dead"] - theirs["p_all_dead"]) < 1e-12)
|
||
print(f" p={p:.1%} P(arrivare) {mine['p_reach']:.4f} vs {theirs['p_reach']:.4f} · "
|
||
f"P(perso tutto) {mine['p_all_dead']:.4f} vs {theirs['p_all_dead']:.4f} "
|
||
f"-> {'IDENTICO' if ok else 'DIVERGE (la generalizzazione ha cambiato qualcosa)'}")
|
||
|
||
# ---------------------------------------------------------------- (1) cosa compra il lump
|
||
print("\n" + "=" * 104)
|
||
print(" (1) COSA COMPRA UN VERSAMENTO INIZIALE — senza rischio di venue (p=0), 100% Deribit")
|
||
print("=" * 104)
|
||
print(f"\n {'lump':>8}{'dep/mese':>10}{'tot versato':>14}{'anni al muro':>14}"
|
||
f"{'P(entro 20a)':>14}{'cap. mediano':>15}{'rendita a 20a':>16}")
|
||
grid = {}
|
||
for dep in (0.0, 250.0):
|
||
for L in LUMPS_EUR:
|
||
r = simulate(CONC, series, 0.0, lump_usd=L * CC.EURUSD, dep_eur=dep)
|
||
grid[(L, dep)] = r
|
||
rent = r["median_end"] * perp * (1 - CC.TAX_RATE) / 365.0 / CC.EURUSD
|
||
yrs = f"{r['median_yrs']:.1f}a" if not np.isnan(r["median_yrs"]) else "mai"
|
||
print(f" €{L:>7,.0f}{dep:>10,.0f}{r['paid']:>13,.0f}${yrs:>14}"
|
||
f"{r['p_reach']:>13.1%}{r['median_end']:>14,.0f}${rent:>13.2f} €/g")
|
||
|
||
# equivalenza: a quale versamento mensile equivale il lump, a parita' di traguardo mediano.
|
||
# NB: NON si confronta il lump col totale dei versamenti risparmiati — sarebbe la domanda
|
||
# sbagliata (il valore e' arrivare prima, non versare meno), e darebbe un rapporto <1 che
|
||
# invita alla conclusione opposta.
|
||
print("\n EQUIVALENZA — a quanti €/mese IN PIU', per sempre, equivale il lump-sum?")
|
||
base = grid[(0.0, 250.0)]["median_yrs"]
|
||
for L in LUMPS_EUR[1:]:
|
||
got = grid[(L, 250.0)]["median_yrs"]
|
||
lo, hi = 250.0, 3_000.0
|
||
for _ in range(12): # bisezione sul deposito a pari traguardo
|
||
mid = 0.5 * (lo + hi)
|
||
y = simulate(CONC, series, 0.0, lump_usd=0.0, dep_eur=mid)["median_yrs"]
|
||
if np.isnan(y) or y > got:
|
||
lo = mid
|
||
else:
|
||
hi = mid
|
||
eq = 0.5 * (lo + hi)
|
||
print(f" €{L:>6,.0f} oggi -> traguardo {got:.1f}a (invece di {base:.1f}a) "
|
||
f"= come versare €{eq:,.0f}/mese invece di €250, cioe' €{eq - 250:,.0f}/mese in piu' "
|
||
f"per {got:.0f} anni (€{(eq - 250) * 12 * got:,.0f})")
|
||
|
||
# ---------------------------------------------------------------- (2) venue
|
||
print("\n" + "=" * 104)
|
||
print(" (2) LO STESSO LUMP, COL RISCHIO DI VENUE DENTRO — concentrato vs split")
|
||
print(f" SPLIT = Deribit / IB, quota IB scelta dal CAPITALE (minimo eseguibile "
|
||
f"${GTAA_MIN_CAPITAL:,.0f}, tetto {IB_SHARE_MAX:.0%})")
|
||
print("=" * 104)
|
||
ib_real = {**series, "IB": haircut(series["IB"], IB_HAIRCUT_YR)}
|
||
for L in LUMPS_EUR:
|
||
tot = START + L * CC.EURUSD
|
||
cfg = split_config(tot)
|
||
if cfg:
|
||
why = (f"POSSIBILE a quota IB {cfg['IB']:.0%} (${tot * cfg['IB']:,.0f} sulla gamba equity; "
|
||
f"il minimo eseguibile e' ${GTAA_MIN_CAPITAL:,.0f})")
|
||
else:
|
||
why = (f"IMPOSSIBILE: servirebbe una quota IB del {GTAA_MIN_CAPITAL / tot:.0%} "
|
||
f"per fare ${GTAA_MIN_CAPITAL:,.0f}, oltre il tetto {IB_SHARE_MAX:.0%}")
|
||
print(f"\n lump €{L:,.0f} -> capitale iniziale ${tot:,.0f} · split {why}")
|
||
print(f" {'p annua':>9}{'P(arrivare) CONC':>19}{'SPLIT':>9}{'SPLIT+costi':>13}"
|
||
f"{'P(perso tutto) CONC':>22}{'SPLIT':>9}")
|
||
for p in P_GRID:
|
||
c = simulate(CONC, series, p, lump_usd=L * CC.EURUSD)
|
||
if cfg:
|
||
s = simulate(cfg, series, p, lump_usd=L * CC.EURUSD)
|
||
sr = simulate(cfg, ib_real, p, lump_usd=L * CC.EURUSD)
|
||
print(f" {p:>8.1%}{c['p_reach']:>18.1%}{s['p_reach']:>9.1%}{sr['p_reach']:>13.1%}"
|
||
f"{c['p_all_dead']:>21.1%}{s['p_all_dead']:>9.1%}")
|
||
else:
|
||
print(f" {p:>8.1%}{c['p_reach']:>18.1%}{'-':>9}{'-':>13}"
|
||
f"{c['p_all_dead']:>21.1%}{'-':>9}")
|
||
# --- lo split che si puo' fare OGGI: la gamba equity e' bloccata dal PRIIPs -------------
|
||
print("\n" + "-" * 104)
|
||
print(" (2-bis) E SE LA SECONDA GAMBA FOSSE SOLO LIQUIDITA'?")
|
||
print(" GTAA01 oggi NON e' deployabile (blocco PRIIPs sugli ETF USA; la via UCITS su Degiro")
|
||
print(" e' verificata ma in preparazione). Lo split disponibile SUBITO e' Deribit + un conto")
|
||
print(" fermo (XEON o simile, ~0% reale). Quanta protezione viene dalla DIVERSIFICAZIONE DI")
|
||
print(" VENUE e quanta dai ritorni della gamba equity?")
|
||
print("-" * 104)
|
||
L = 10_000.0
|
||
tot = START + L * CC.EURUSD
|
||
cfg = split_config(tot)
|
||
zero = {**series, "IB": series["IB"] * 0.0} # stessa griglia temporale, drift nullo
|
||
print(f"\n lump €{L:,.0f} (${tot:,.0f}), quota seconda gamba {cfg['IB']:.0%}")
|
||
print(f" {'p annua':>9}{'P(arrivare) CONC':>19}{'SPLIT-GTAA':>13}{'SPLIT-CASSA':>13}"
|
||
f"{'P(perso tutto) CONC':>22}{'SPLIT':>9}")
|
||
for p in P_GRID:
|
||
c = simulate(CONC, series, p, lump_usd=L * CC.EURUSD)
|
||
g = simulate(cfg, series, p, lump_usd=L * CC.EURUSD)
|
||
z = simulate(cfg, zero, p, lump_usd=L * CC.EURUSD)
|
||
print(f" {p:>8.1%}{c['p_reach']:>18.1%}{g['p_reach']:>13.1%}{z['p_reach']:>13.1%}"
|
||
f"{c['p_all_dead']:>21.1%}{g['p_all_dead']:>9.1%}")
|
||
print("\n (P(perso tutto) e' identica fra SPLIT-GTAA e SPLIT-CASSA: dipende da QUANTI conti")
|
||
print(" falliscono, non da cosa ci sta sopra. La differenza fra le due colonne di")
|
||
print(" P(arrivare) e' il prezzo di tenere ferma la seconda gamba invece di investirla.)")
|
||
|
||
print("\n ⚠️ La colonna P(perso tutto) sotto CONC non dipende dal lump: con UN conto solo,")
|
||
print(" 'almeno un fallimento' COINCIDE con 'perso tutto', e quella probabilita' e' una")
|
||
print(" proprieta' del tempo di esposizione, non del capitale. Il lump non la peggiora —")
|
||
print(" ma moltiplica cio' che quella probabilita' porta via.")
|
||
|
||
# ---------------------------------------------------------------- (2-ter) dove mettere 5k
|
||
print("\n" + "=" * 104)
|
||
print(f" (2-ter) DOVE METTERE €{LUMP_FOCUS:,.0f} — la configurazione realmente pianificata")
|
||
print(f" (€{LUMP_FOCUS:,.0f} + €{DEP_FOCUS:,.0f}/mese; il cap di `live.json` e' gia'")
|
||
print(" armato per questo caso dal 26/07)")
|
||
print("=" * 104)
|
||
tot5 = START + LUMP_FOCUS * CC.EURUSD
|
||
print(f"\n Capitale iniziale ${tot5:,.0f}. Cosa si sblocca a questa taglia: NIENTE.")
|
||
print(f" GTAA01 richiede ${GTAA_MIN_CAPITAL:,.0f} SULLA GAMBA "
|
||
f"(= {GTAA_MIN_CAPITAL / tot5:.0%} del conto: oltre ogni quota sensata) e non e'")
|
||
print(" deployabile comunque (PRIIPs); XS01 ~$20.000; XSR01 e' sotto gate fino al 23/10.")
|
||
print(" -> il book resta TP01+SKH01, e la sola domanda e' QUANTA parte NON sta sull'exchange.")
|
||
print("\n Seconda gamba = LIQUIDITA' ferma (nessuna soglia di eseguibilita', ~0% reale).")
|
||
print(" Due letture del rischio della liquidita', perche' non e' un exchange:")
|
||
print(" [A] cassa rischiosa come Deribit (conservativa contro lo split)")
|
||
print(" [B] cassa che non fallisce (conto bancario con tutela dei depositi)")
|
||
for p in (0.005, 0.01, 0.02, 0.05):
|
||
print(f"\n p(exchange) = {p:.1%}")
|
||
print(f" {'fuori exchange':>15}{'P(arrivare)':>13}{'anni':>8}"
|
||
f"{'P(perso tutto) [A]':>21}{'[B]':>8}{'salvato se cade':>17}")
|
||
for share in (0.0, 0.10, 0.25, 0.40):
|
||
cfg5 = {"Deribit": 1.0 - share, "IB": share} if share > 0 else {"Deribit": 1.0}
|
||
ser5 = {**series, "IB": series["IB"] * 0.0}
|
||
a = simulate(cfg5, ser5, p, lump_usd=LUMP_FOCUS * CC.EURUSD, dep_eur=DEP_FOCUS)
|
||
b = simulate(cfg5, ser5, {"Deribit": p, "IB": 0.0},
|
||
lump_usd=LUMP_FOCUS * CC.EURUSD, dep_eur=DEP_FOCUS)
|
||
yrs = f"{a['median_yrs']:.1f}a" if not np.isnan(a["median_yrs"]) else "mai"
|
||
resta = b["median_salvage"]
|
||
print(f" {share:>14.0%}{a['p_reach']:>13.1%}{yrs:>8}"
|
||
f"{a['p_all_dead']:>20.1%}{b['p_all_dead']:>8.1%}{resta:>16,.0f}$")
|
||
print("\n ⚠️ La colonna [B] e' quella realistica per un conto bancario in UE (tutela dei")
|
||
print(" depositi fino a €100k): tenere fuori una quota qualsiasi porta P(perso tutto)")
|
||
print(" a ZERO, perche' 'perso tutto' richiede che falliscano ENTRAMBI i conti.")
|
||
print(" La [A] e' il caso in cui anche la cassa e' a rischio: e' li' che si vede il")
|
||
print(" prezzo vero della protezione, e resta grande.")
|
||
|
||
# ---------------------------------------------------------------- (3) la soglia
|
||
print("\n" + "=" * 104)
|
||
print(" (3) DA QUALE CAPITALE LO SPLIT E' POSSIBILE SENZA DISTORCERE I PESI")
|
||
print("=" * 104)
|
||
ideale = GTAA_MIN_CAPITAL / IB_SHARE
|
||
minimo = GTAA_MIN_CAPITAL / IB_SHARE_MAX
|
||
tot10 = START + 10_000 * CC.EURUSD
|
||
print(f"\n soglia di eseguibilita' della gamba equity (GTAA_MIN_CAPITAL): ${GTAA_MIN_CAPITAL:,.0f}")
|
||
print(f" quota IB raccomandata da r0725_ib10k (oltre, cala la rendita): {IB_SHARE:.0%}")
|
||
print(f" -> split a quota RACCOMANDATA possibile da : ${ideale:,.0f}")
|
||
print(f" -> split possibile forzando la quota fino al {IB_SHARE_MAX:.0%} da : ${minimo:,.0f}")
|
||
print(f"\n Con un lump da €10.000 il conto sarebbe ${tot10:,.0f}: "
|
||
f"{'SOPRA' if tot10 >= ideale else 'SOTTO'} la soglia a quota")
|
||
print(f" raccomandata (${ideale:,.0f}) e {'sopra' if tot10 >= minimo else 'sotto'} "
|
||
f"quella a quota forzata (${minimo:,.0f}) -> lo split e' possibile, ma a")
|
||
print(f" quota IB {max(IB_SHARE, GTAA_MIN_CAPITAL / tot10):.0%}, non {IB_SHARE:.0%}: "
|
||
"e' il capitale a scegliere la quota, non la preferenza.")
|
||
print(f"\n La riapertura della decisione di venue e' fissata a ${20_000:,.0f}. Un lump da")
|
||
print(f" €10k porta il conto a ${tot10:,.0f} — sotto quella data ma sopra la fattibilita'")
|
||
print(" tecnica, ed e' un cambiamento del piano: CLAUDE.md dice di riaprire PRIMA in")
|
||
print(" questo caso, e questa tabella e' il materiale per farlo.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|