825bf081f4
Il fattore agisce DUE volte e i due effetti pesano quasi uguale: alza il drift (si
accumula prima) E abbassa il bersaglio (il muro scende da $495k a $272k). Le colonne
li separano invece di sommarli alla cieca.
Mediana degli anni da $600 al capitale-rendita (block bootstrap, 3000 path, 25 anni):
dep./mese x0.60 muro $495k x0.89 muro $495k x0.89 muro $272k
(25/07) (solo drift) (26/07)
0 mai mai mai
250 22.4a (8% <20a) 19.6a (53%) 16.2a (91%)
500 19.6a (48%) 15.7a (94%) 12.4a (100%)
1000 14.8a (95%) 12.0a (100%) 9.0a (100%)
2000 10.0a (100%) 8.5a (100%) 6.0a (100%)
VALIDAZIONE: la colonna x0.60 riproduce ESATTAMENTE i numeri pubblicati il 25/07
(500/m -> 19.6a, 1000/m -> 14.8a) -> la replica e' fedele e le altre due colonne sono
confrontabili con quelle.
A 500 EUR/mese: dei ~7 anni guadagnati, ~4 vengono dal drift e ~3 dal bersaglio.
CIO' CHE NON CAMBIA: senza depositi il capitale-rendita non si raggiunge MAI (0% dei
path a 20 anni, a ogni fattore). L'accumulo viene dai versamenti, non dal rendimento:
correggere il x0.6 accorcia i tempi, non crea una via che non c'era.
Book, pesi, cron, config INVARIATI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
186 lines
8.1 KiB
Python
186 lines
8.1 KiB
Python
#!/usr/bin/env python
|
||
"""r0726_capwall_refresh.py — i MURI DI CAPITALE ricalcolati col fattore de-luck corretto.
|
||
|
||
Il 25/07 (`r0725_capcurve.py`) ha calcolato rendita perpetua, muro di capitale per 50 EUR/g e
|
||
traiettoria da $600 applicando un de-luck **×0.6 scelto a occhio** sul drift del book.
|
||
|
||
Il 26/07 quel fattore e' stato MISURATO e decomposto (`r0726_deluck_factor.py` +
|
||
`r0726_skh_live_book.py`):
|
||
* fortuna d'ancora sul drift, book live Deribit (TP01/SKH01 75/25): **×0.890**
|
||
(24 x 23 = 552 configurazioni, estrazioni congiunte);
|
||
* degradazione del path live: **non-negativa su tutto cio' che e' misurato**
|
||
(SKH01 uscite +0.081 Sharpe di book su 23/23 offset; SKH01 ingressi +0.73pp di drift,
|
||
positivo nel 100% delle estrazioni; TP01 barra parziale trascurabile su 24 ancore).
|
||
Il ×0.6 implicava quindi un residuo ×0.687 attribuito al live OLTRE l'ancora, che nessuna misura
|
||
sostiene: era il doppio conteggio gia' sospettato in CLAUDE.md, ora quantificato.
|
||
|
||
Questo script rifa' i numeri della DECISIONE ai tre fattori, sulla stessa macchineria del 25/07
|
||
(nessuna re-implementazione: `book_series`, `survival`, `_boot_paths` importati da li').
|
||
|
||
uv run python scripts/research/r0726_capwall_refresh.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT))
|
||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||
|
||
import r0725_capcurve as CC # noqa: E402
|
||
|
||
# fattori a confronto. 0.890 = fortuna d'ancora MISURATA sul book live Deribit; il vero fattore
|
||
# onesto e' >= 0.890 perche' la componente live e' non-negativa (non la conto: e' conservativo).
|
||
FACTORS = (
|
||
(0.60, "×0.60 (25/07, a occhio)"),
|
||
(0.89, "×0.89 (26/07, ancora MISURATA)"),
|
||
(1.00, "×1.00 (nessun haircut)"),
|
||
)
|
||
LEVERAGES = (1.0, 1.5)
|
||
N_PATHS = 2000
|
||
|
||
|
||
def perp_and_wall(r: np.ndarray, k: float, gross_needed: float) -> tuple[float, float, float]:
|
||
"""(SWR-20a, tasso di rendita PERPETUA, capitale necessario) — stessa ricerca binaria
|
||
del 25/07: perpetua = prelievo con P(cap a 20a >= cap iniziale) >= 90%."""
|
||
lo, hi = 0.0, 0.40
|
||
for _ in range(13):
|
||
mid = (lo + hi) / 2
|
||
s = CC.survival(r, 1e6, 1e6 * mid, 20, k, n_paths=N_PATHS)
|
||
if s["p10_end"] >= 1e6:
|
||
lo = mid
|
||
else:
|
||
hi = mid
|
||
perp = lo
|
||
lo2, hi2 = 0.0, 0.40
|
||
for _ in range(13):
|
||
mid = (lo2 + hi2) / 2
|
||
if CC.survival(r, 1e6, 1e6 * mid, 20, k, n_paths=N_PATHS)["p_survive"] >= 0.95:
|
||
lo2 = mid
|
||
else:
|
||
hi2 = mid
|
||
cap = gross_needed / perp if perp > 0.002 else float("inf")
|
||
return lo2, perp, cap
|
||
|
||
|
||
def trajectory(r: np.ndarray, cap_needed: float, n_paths: int = 3000,
|
||
years: int = 25, seed: int = 725) -> dict:
|
||
"""Da $600 al capitale-rendita, stocastico (block bootstrap): stessa logica di
|
||
r0725_capcurve.part_d, parametrizzata sul fattore e sul bersaglio."""
|
||
rng = np.random.default_rng(seed)
|
||
n_days = 365 * years
|
||
paths = CC._boot_paths(r, n_paths, n_days, 20, rng)
|
||
out = {}
|
||
for dep_eur in (0, 250, 500, 1_000, 2_000):
|
||
dep = dep_eur * CC.EURUSD
|
||
cap = np.full(n_paths, 600.0)
|
||
hit = np.full(n_paths, -1, int)
|
||
for t in range(n_days):
|
||
cap = cap * (1.0 + paths[:, t])
|
||
if t % 30 == 0 and t > 0:
|
||
cap += dep
|
||
newly = (hit < 0) & (cap >= cap_needed)
|
||
hit[newly] = t
|
||
yrs = np.where(hit >= 0, hit / 365.0, np.nan)
|
||
fin = yrs[~np.isnan(yrs)]
|
||
out[dep_eur] = dict(
|
||
p50=float(np.percentile(fin, 50)) if len(fin) > 100 else float("nan"),
|
||
p10=float(np.percentile(fin, 10)) if len(fin) > 100 else float("nan"),
|
||
p20y=float((yrs <= 20).sum() / len(yrs)))
|
||
return out
|
||
|
||
|
||
def main() -> None:
|
||
print("=" * 100)
|
||
print(" r0726 — MURI DI CAPITALE col fattore de-luck MISURATO (era ×0.6 a occhio)")
|
||
print("=" * 100)
|
||
|
||
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
|
||
print(f" bersaglio: {CC.TARGET_EUR_DAY:.0f} EUR/g netti = EUR "
|
||
f"{CC.TARGET_EUR_DAY*365:,.0f}/anno netti = ${gross:,.0f}/anno LORDI "
|
||
f"(fisco {CC.TAX_RATE:.0%}, EURUSD {CC.EURUSD})")
|
||
print(" book: TP01+SKH01 75/25 su path live (lente 'hourly'), costi reali Deribit\n")
|
||
|
||
base = CC.book_series("hourly")
|
||
mu = float(base.values.mean())
|
||
|
||
print(f" {'fattore':<32}{'leva':>6}{'SWR-20a':>10}{'PERPETUA':>10}{'capitale':>16}")
|
||
print(" " + "-" * 74)
|
||
res = {}
|
||
for f, lab in FACTORS:
|
||
r = (base - (1.0 - f) * mu).values.astype(float)
|
||
for k in LEVERAGES:
|
||
swr, perp, cap = perp_and_wall(r, k, gross)
|
||
res[(f, k)] = (swr, perp, cap)
|
||
capf = f"${cap:,.0f}" if np.isfinite(cap) else "IRRAGGIUNGIBILE"
|
||
print(f" {lab:<32}{k:>6.2f}{swr:>9.2%}{perp:>10.2%}{capf:>16}")
|
||
print()
|
||
|
||
print("=" * 100)
|
||
print(" COSA CAMBIA NELLA DECISIONE")
|
||
print("=" * 100)
|
||
for k in LEVERAGES:
|
||
c_old = res[(0.60, k)][2]
|
||
c_new = res[(0.89, k)][2]
|
||
if np.isfinite(c_old) and np.isfinite(c_new):
|
||
print(f" leva {k:.2f}: muro ${c_old:,.0f} -> ${c_new:,.0f} "
|
||
f"({(c_new/c_old - 1)*100:+.0f}%)")
|
||
c_new = res[(0.89, 1.0)][2]
|
||
print(f"\n Il muro scende di ~45%, ma resta ${c_new:,.0f} = ~{c_new/600:,.0f}x il conto")
|
||
print(" di oggi ($600). La conclusione STRUTTURALE del 25/07 non cambia: i 600 euro")
|
||
print(" come CAPITALE restano refutati, come BIGLIETTO (prop) no. Cambia la taglia")
|
||
print(" dell'errore — e cambia che il numero ora e' misurato invece che scelto.")
|
||
print("\n ⚠️ Il ×0.89 e' la sola fortuna d'ancora: e' un LIMITE INFERIORE del fattore")
|
||
print(" onesto, perche' ogni componente misurata del path live e' non-negativa.")
|
||
print(" Il muro vero sta quindi fra la riga ×0.89 e la riga ×1.00.")
|
||
|
||
# rendita a $600 — il numero che il 25/07 dava a ~0.11 EUR/g
|
||
print("\n" + "=" * 100)
|
||
print(" RENDITA A $600 (lo stesso conto di oggi)")
|
||
print("=" * 100)
|
||
for f, lab in FACTORS:
|
||
r = (base - (1.0 - f) * mu).values.astype(float)
|
||
cagr = float((1 + r.mean()) ** 365.25 - 1)
|
||
eur = 600.0 * cagr * (1 - CC.TAX_RATE) / CC.EURUSD / 365.0
|
||
print(f" {lab:<32} CAGR {cagr*100:6.2f}% rendita EUR {eur:.2f}/giorno")
|
||
print("\n (a questa taglia la differenza fra ×0.6 e ×0.89 sono centesimi al giorno:")
|
||
print(" il fattore conta per il MURO e per le soglie prop, non per il conto attuale.)")
|
||
|
||
# ---------------------------------------------------------------- traiettoria
|
||
print("\n" + "=" * 100)
|
||
print(" TRAIETTORIA da $600 al capitale-rendita (block bootstrap, 3000 path, 25 anni)")
|
||
print("=" * 100)
|
||
print(" Il fattore agisce DUE VOLTE: alza il drift (si accumula piu' in fretta) E abbassa")
|
||
print(" il bersaglio (serve meno capitale). Le due colonne centrali separano gli effetti.")
|
||
cap_old = res[(0.60, 1.0)][2]
|
||
cap_new = res[(0.89, 1.0)][2]
|
||
scen = [
|
||
(0.60, cap_old, f"×0.60, muro ${cap_old:,.0f} (25/07)"),
|
||
(0.89, cap_old, f"×0.89, muro ${cap_old:,.0f} (solo drift)"),
|
||
(0.89, cap_new, f"×0.89, muro ${cap_new:,.0f} (26/07)"),
|
||
]
|
||
T = {}
|
||
for f, capn, lab in scen:
|
||
r = (base - (1.0 - f) * mu).values.astype(float)
|
||
T[lab] = trajectory(r, capn)
|
||
|
||
for dep in (0, 250, 500, 1_000, 2_000):
|
||
print(f"\n deposito {dep:,}/mese")
|
||
print(f" {'scenario':<34}{'p10':>8}{'MEDIANA':>10}{'P(entro 20a)':>14}")
|
||
for _f, _c, lab in scen:
|
||
d = T[lab][dep]
|
||
fm = (lambda v: f"{v:.1f}a" if np.isfinite(v) else " —")
|
||
print(f" {lab:<34}{fm(d['p10']):>8}{fm(d['p50']):>10}{d['p20y']:>13.0%}")
|
||
|
||
print("\n ⚠️ Restano vere le due cose che non dipendono dal fattore: senza depositi il")
|
||
print(" capitale-rendita non si raggiunge, e la mediana e' una MEDIANA (meta' dei")
|
||
print(" path arriva dopo, e una quota non arriva affatto).")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|