"""r0823b_split_deposito — §62 SPLIT-DEPOSITO: quanta parte del versamento va ai BIGLIETTI (2026-08-23). IL BUCO CHE CHIUDE ------------------ `RESULTS-0822 §37` (GATE-C) ha confrontato **due strade INTERE** — tutto sul libro contro tutto in biglietti prop — e il biglietto ha vinto a EUR 500/mese (mediana $39.820 contro $22.444, P(>=50/g) 13,7% contro 0,0%). Ma **«tutto o niente» non e' una politica: e' un caso limite.** Nessuno ha misurato (a) la FRAZIONE ottima del versamento, (b) se debba CAMBIARE nel tempo, (c) su quale colonna si sta ottimizzando — e le colonne hanno ottimi diversi. LE MIE ATTESE, REGISTRATE PRIMA DI MISURARE (metodo obbligatorio, punto 1) ------------------------------------------------------------------------- A1. **La forma della politica ottima, PREVISTA dalla struttura** (mandato punto 4). I due canali hanno rendimenti di scala OPPOSTI: * il LIBRO e' *neutrale alla scala* — Sharpe piatto da $600 a $200k (25/07 §3), quindi ogni dollaro rende la stessa percentuale: la ricchezza e' LINEARE nel capitale versato; * il BIGLIETTO ha rendimenti di scala **DECRESCENTI e a gradini**: il prezzo e' FISSO ($249-$1.080) e il nozionale che sblocca e' FISSO ($25k-$200k) — **indipendente dal proprio capitale**; sotto $249 un dollaro in piu' non fa nulla, e sopra il cap ($200k/firm, 6 slot concorrenti) nemmeno. ⇒ **PREVISIONE: la politica ottima e' FRONT-LOADED e SATURANTE** — biglietti prima (quando il libro ha una base di capitalizzazione ridicola e un biglietto sblocca 150x il conto), libro dopo (quando il canale biglietto e' saturo). Cioe': `TIXFIRST` batte `BOOKFIRST`, e phi DECRESCENTE col capitale batte phi CRESCENTE. A2. **Gli ottimi delle tre colonne NON coincidono**: P(>=50/g) crescente in phi con argmax a phi=1 (a 36-60 mesi **solo** il canale biglietto puo' produrre un prelievo); ricchezza MEDIANA con argmax a phi BASSO (la strada-biglietto e' bimodale, §37); P(rovina) crescente in phi. **Se e' cosi', «la frazione ottima» non esiste finche' non si dice quale colonna.** A3. **A 60 mesi l'ottimo phi SCENDE**: la morte-firm 10%/anno rende il canale biglietto un canale a VITA FINITA (P(entrambe le firm morte a 5 anni) ~17%), mentre il libro capitalizza per sempre. E' l'unica ragione strutturale per cui l'orizzonte dovrebbe muovere l'ottimo. A4. **Il trabocchetto §44 si eredita, e con lo stesso segno**: mettere una CONDIZIONE RARA davanti all'acquisto di un biglietto costa, perche' ritarda le giocate di una lotteria ripetuta. Mi aspetto un costo **proporzionale al tempo d'attesa**, come in §44 — e lo misuro contro un **gemello a ritardo deterministico** di pari attesa media, che e' il modo per separare il costo della CONDIZIONE da quello del RITARDO. A5. **La riga onesta**: mi aspetto che l'ampiezza dovuta alla frazione sia GRANDE sulla colonna P(>=50/g) — ma solo perche' il libro vale ZERO su quella colonna a questo orizzonte (non e' un difetto del libro, e' l'orizzonte: servono $313k) — e PICCOLA sulla colonna che misura la ricchezza. Se esce cosi', va detto che **la frazione conta sulla metrica sbagliata**. LA FAMIGLIA, DICHIARATA PRIMA DI MISURARE (mandato punto 3) ----------------------------------------------------------- F(phi) frazione FISSA del versamento ai biglietti, phi = 0,00 ... 1,00 (11 celle). phi=1 == la strada BIGLIETTO di §37; phi=0 == la strada LIBRO di §37. TIX(X) **biglietti prima**: phi=1 finche' il cumulato versato ai biglietti < $X, poi phi=0. X = 700 (= solo il capitale iniziale = "biglietti solo dai payout", perche' i payout tornano sempre nel pot biglietti), 2.000, 5.000, 10.000. BOOK(X) **libro prima**: phi=0 finche' il libro < $X, poi phi=1. X = 2.000, 5.000, 10.000, 20.000. PHI_UP(X) phi CRESCE col capitale: phi = clip(libro / X, 0, 1), X = 10.000. PHI_DN(X) phi DECRESCE col capitale: phi = 1 - clip(libro / X, 0, 1), X = 10.000. CASH(B) phi=1 ma la cassa in eccesso oltre $B va al libro (B = 1.200 = il biglietto piu' caro). E' l'unica politica che puo' DOMINARE phi=1 per costruzione. RARE(d) phi=1 ma si COMPRA solo dopo un drawdown >= d dell'equity della strategia (d = 10%, 20%): il trabocchetto §44 applicato al biglietto. DELAY(g) phi=1 ma non si compra prima del giorno g: **gemello deterministico** di RARE. Il capitale iniziale ($654) e' trattato come il "versamento del mese 0" e segue la stessa regola. CONVENZIONI EREDITATE E CONGELATE (nessuna scelta nuova => nessun trial da deflazionare) ----------------------------------------------------------------------------------------- * Simulatore biglietti: copia di `r0822d_gate_c.ladder` (che e' bit-exact vs `r0725_prop_coupled. simulate`) con l'aggiunta del pot LIBRO. Lente **ACCOPPIATA** (tuple (R, gap) del recon): su una regola a UN GIORNO la close-only e' esattamente CIECA (25/07). * Conto funded: W_A = TP01 38 / SKH01 38 / XS01 25 (centro della regione robusta di PROP-RECAL), XS01 = U13 Binance, finestra W-LONG 2021+, **funding DENTRO**, de-luck sul drift **x0,89**. * Pot LIBRO: `FD.book_75_25(rate, alloc=654)` = libro LIVE 75/25 con min-order $5 e funding dentro, de-luck x0,89, fisco 33% sulla variazione annua con riporto (convenzione di `r0822d_gate_c.book_road` = quella di `r0807_piano_netto`). ⚠ Dichiarato: NON contiene la patrimoniale 0,2%/anno che `PN.accumula` invece include; la differenza e' misurata in §1. * Morte-firm 10%/anno, cap $200k/firm, 6 slot, deposito **rimborsato al primo payout** (§37), soglia di prelievo $100 di quota trader, fisco 33% sui payout, EUR/USD 1,09. * Rendita: `perp` e muro dalla lente **L3 CONGIUNTA** (`PN.muro`), cioe' la stessa che produce i $313k del piano. Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_split_deposito.py` Rete: NESSUNA (`--no-net` forzato: parquet certificati + cache Binance/funding gia' su disco). """ from __future__ import annotations import sys import time from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"): sys.path.insert(0, str(_p)) if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import sys.argv.append("--no-net") import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07) import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti (25/07) import r0807_piano_netto as PN # noqa: E402 muro / accumula (07/08) import r0727_tasse as TX # noqa: E402 patrimoniale import r0822_prop_alloc as PA # noqa: E402 J, bootstrap (22/08) import r0822b_prop_recal as PR # noqa: E402 PROP-RECAL (22/08) import r0822b_xs01_oos as X # noqa: E402 universo U13 + cache Binance import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01 import r0822d_funding as FD # noqa: E402 il funding (22/08) import r0822d_gate_c as GC # noqa: E402 §37: ladder + book_road from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402 FD.USE_NET = False EURUSD = pl.EURUSD TAX_RATE = pl.TAX_RATE SPLIT = pl.SPLIT DELUCK = PA.DELUCK # x0,89 MISURATO (r0726_deluck_factor) FIRMS = pl.FIRMS N_SLOT = pl.MAX_CONCURRENT TARGET = pl.TARGET_EUR_DAY START_EUR = 600.0 # $654 = il conto reale (convenzione di §37) MIN_PAYOUT = GC.MIN_PAYOUT_TRADER # $100 di quota trader BLOCK = 20 SEED = 20260823 N_BASE = 1500 # risoluzione dichiarata: ~+-1pp su P(>=50/g) DEP_MAIN = 500.0 # il piano in corso (config/live.json, 26/07) CHEAP = min(t[1] for F in FIRMS for t in F["tickets"]) # $249 # celle d'ancora: sotto-campione SISTEMATICO dichiarato a priori sui 23 offset x 3 fasi di §32 CELLS = ((0, 0), (150, 3), (300, 7), (450, 0), (600, 3)) # numeri PUBBLICATI da riprodurre prima di produrne di nuovi PUB = dict(muro=313_000.0, p50g_37=0.137, med_tix_37=39_820.0, med_book_37=22_444.0, med_tix_0=132.0, med_book_0=787.0, p50g_0=0.042, pzero_37=0.403, p50g_anchor=0.026) T0 = time.time() def clock() -> str: return f"[{time.time()-T0:5.0f}s]" def hr(t: str = "") -> None: print("\n" + "=" * 108) if t: print(f" {t}") print("=" * 108) # =========================================================================================== # LE POLITICHE — (etichetta, tipo, parametro). Dichiarate sopra, nel docstring. # =========================================================================================== def phi_of(kind: str, par: float, book: np.ndarray, cash: np.ndarray, to_tix: np.ndarray) -> np.ndarray: """Frazione del versamento che va al pot BIGLIETTI. Vettoriale sul blocco di percorsi.""" if kind == "FIX": return np.full(len(book), float(par)) if kind == "TIX": # biglietti prima return (to_tix < par).astype(float) if kind == "BOOK": # libro prima return (book >= par).astype(float) if kind == "PHI_UP": # cresce col capitale return np.clip(book / par, 0.0, 1.0) if kind == "PHI_DN": # decresce col capitale return 1.0 - np.clip(book / par, 0.0, 1.0) if kind in ("CASH", "RARE", "DELAY"): # phi = 1, la differenza e' altrove return np.ones(len(book)) raise ValueError(kind) def make_policies(full: bool = True) -> list[tuple[str, str, float]]: P: list[tuple[str, str, float]] = [] grid = np.round(np.arange(0.0, 1.001, 0.10), 2) if full else np.array([0.0, 0.25, 0.50, 0.75, 1.0]) for f in grid: P.append((f"F {f:.2f}", "FIX", float(f))) if not full: return P for xk in (700.0, 2_000.0, 5_000.0, 10_000.0): P.append((f"TIX ${xk:,.0f}", "TIX", xk)) for xk in (2_000.0, 5_000.0, 10_000.0, 20_000.0): P.append((f"BOOK ${xk:,.0f}", "BOOK", xk)) P.append(("PHI_UP $10k", "PHI_UP", 10_000.0)) P.append(("PHI_DN $10k", "PHI_DN", 10_000.0)) P.append(("CASH-ATT $1.2k", "CASH", 1_200.0)) P.append(("RARE dd 10%", "RARE", 0.10)) P.append(("RARE dd 20%", "RARE", 0.20)) P.append(("DELAY 95g", "DELAY", 95.0)) P.append(("DELAY 713g", "DELAY", 713.0)) return P # =========================================================================================== # IL SIMULATORE COMBINATO — pot BIGLIETTI + pot LIBRO sullo STESSO percorso # =========================================================================================== def sim_split(R: np.ndarray, G: np.ndarray, rb: np.ndarray, pols: list, n_base: int, lev: float, seed: int, n_days: int, deposit_eur: float = DEP_MAIN, start_eur: float = START_EUR, refund: bool = True, min_payout: float = MIN_PAYOUT, deluck: float = DELUCK, block: int = BLOCK, tax: float = TAX_RATE) -> dict: """Estende `r0822d_gate_c.ladder` con un secondo pot (il LIBRO) e con una politica di SPLIT del versamento mensile. Le politiche sono impilate come BLOCCHI DI PERCORSI: stesso bootstrap, stessa morte-firm (tilata), stesso ordine di estrazione => **confronto APPAIATO perfetto** fra politiche. Con `pols=[("x","FIX",1.0)]`, `refund=False`, `deposit=0`, `min_payout=0` dev'essere BIT-EXACT rispetto a `GC.ladder` (controllo R2). Con `pols=[("x","FIX",0.0)]` il pot libro dev'essere BIT-EXACT rispetto a `GC.book_road` (controllo R3). """ n_pol = len(pols) P = n_pol * n_base L = len(R) rng = np.random.default_rng(seed) Rd = R.astype(float) - (1.0 - deluck) * float(R.astype(float).mean()) Gd = G.astype(float) Bd = rb.astype(float) - (1.0 - deluck) * float(rb.astype(float).mean()) n_blocks = int(np.ceil(n_days / block)) starts0 = rng.integers(0, L - block, size=(n_base, n_blocks)) # stesso stream di GC starts = np.tile(starts0, (n_pol, 1)) # ...e TILATO: appaiato deaths0 = rng.random((n_days, n_base, len(FIRMS))) # morte-firm, tilata p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0) pid = np.repeat(np.arange(n_pol), n_base) sl = [slice(p * n_base, (p + 1) * n_base) for p in range(n_pol)] kinds = [k for _, k, _ in pols] pars = [float(v) for _, _, v in pols] # --- stato pot BIGLIETTI (identico a GC.ladder) --------------------------------------- cash = np.zeros(P) phase = np.full((P, N_SLOT), -1, np.int8) eq = np.ones((P, N_SLOT)) firm = np.full((P, N_SLOT), -1, np.int8) notional = np.zeros((P, N_SLOT)) price_paid = np.zeros((P, N_SLOT)) refunded = np.zeros((P, N_SLOT), bool) firm_alive = np.ones((P, len(FIRMS)), bool) used_f = np.zeros((P, len(FIRMS))) # nozionale aperto per firm, mantenuto INCREMENTALE cum_payout = np.zeros(P) spent = np.zeros(P) got_back = np.zeros(P) n_payouts = np.zeros(P, int) n_bought = np.zeros(P, int) to_tix = np.zeros(P) # cumulato VERSATO al pot biglietti (non i payout) to_book = np.zeros(P) # --- stato pot LIBRO (identico a GC.book_road) ---------------------------------------- bookc = np.zeros(P) basis = np.zeros(P) carry = np.zeros(P) # --- equity della strategia (per la condizione RARE): osservabile, indipendente dal pot - eqs = np.ones(P) peak = np.ones(P) def deposita(amount: np.ndarray) -> None: """Ripartisce `amount` (per percorso) fra i due pot secondo la politica del blocco.""" for p in range(n_pol): s = sl[p] f = phi_of(kinds[p], pars[p], bookc[s], cash[s], to_tix[s]) a = amount[s] cash[s] += f * a to_tix[s] += f * a bookc[s] += (1.0 - f) * a basis[s] += (1.0 - f) * a to_book[s] += (1.0 - f) * a deposita(np.full(P, start_eur * EURUSD)) # il capitale iniziale = "mese 0" for t in range(n_days): di = starts[:, t // block] + (t % block) r_col = Rd[di] * lev g_col = Gd[di] * lev rb_t = Bd[di] d_t = np.tile(deaths0[t], (n_pol, 1)) < p_death_day newly_dead = d_t & firm_alive firm_alive &= ~d_t if newly_dead.any(): for fi in range(len(FIRMS)): kill = newly_dead[:, fi] if kill.any(): hit = (firm == fi) & kill[:, None] phase[hit] = -1 notional[hit] = 0.0 used_f[kill, fi] = 0.0 # --- gating dell'acquisto: default aperto, chiuso per RARE/DELAY ------------------ allow = np.ones(P, bool) for p in range(n_pol): if kinds[p] == "RARE": allow[sl[p]] = (eqs[sl[p]] / peak[sl[p]] - 1.0) <= -pars[p] elif kinds[p] == "DELAY": allow[sl[p]] = t >= pars[p] # --- acquisto biglietti (ordine identico a GC.ladder) ---------------------------- for k in range(N_SLOT): free = (phase[:, k] == -1) & allow if not free.any(): continue for fi, F in enumerate(FIRMS): used = used_f[:, fi] # vista: si aggiorna da sola sotto for (size, price) in sorted(F["tickets"], key=lambda x: -x[0]): buy = (free & firm_alive[:, fi] & (cash >= price) & (used + size <= F["cap"])) if not buy.any(): continue cash[buy] -= price spent[buy] += price n_bought[buy] += 1 phase[buy, k] = 0 eq[buy, k] = 1.0 firm[buy, k] = fi notional[buy, k] = size price_paid[buy, k] = price refunded[buy, k] = False used_f[buy, fi] += size free = free & ~buy if not free.any(): break if not free.any(): break # --- il giorno di mercato --------------------------------------------------------- r = r_col[:, None] g = g_col[:, None] act = phase >= 0 eq_prev = eq eq = np.where(act, eq * (1.0 + r), eq) eq_low = np.where(act, eq_prev * (1.0 + r + g), eq) r_low = r + g for fi, F in enumerate(FIRMS): s_ = act & (firm == fi) if not s_.any(): continue ev_t, ev_dd, ev_dl = F["ev"] fu_ml, fu_dl = F["fu"] in_ev = s_ & (phase == 0) in_fu = s_ & (phase == 1) bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl))) | (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl)))) if bust.any(): used_f[:, fi] -= np.where(bust, notional, 0.0).sum(axis=1) phase[bust] = -1 notional[bust] = 0.0 passed = in_ev & (eq >= 1.0 + ev_t) & ~bust phase[passed] = 1 eq[passed] = 1.0 # --- il pot LIBRO + l'equity osservabile della strategia -------------------------- bookc = np.maximum(bookc * (1.0 + rb_t), 0.0) eqs = eqs * (1.0 + rb_t) peak = np.maximum(peak, eqs) # --- fine mese: payout, rimborso, cassa attiva, versamento ------------------------ if (t + 1) % 30 == 0: fu = phase == 1 pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout) gain = np.where(pay, (eq - 1.0) * notional, 0.0) net = gain.sum(axis=1) * SPLIT * (1.0 - tax) cum_payout += net cash += net n_payouts += (pay.sum(axis=1) > 0).astype(int) eq = np.where(pay, 1.0, eq) if refund: back = pay & ~refunded amt = np.where(back, price_paid, 0.0).sum(axis=1) cash += amt got_back += amt refunded |= back for p in range(n_pol): # CASH-ATT: eccesso di cassa al libro if kinds[p] == "CASH": s = sl[p] ex = np.maximum(cash[s] - pars[p], 0.0) cash[s] -= ex bookc[s] += ex basis[s] += ex if deposit_eur: deposita(np.full(P, deposit_eur * EURUSD)) # --- fine anno: fisco sul pot LIBRO (convenzione GC.book_road) -------------------- if (t + 1) % 365 == 0: gg = (bookc - basis) + carry due = np.where(gg > 0, tax * gg, 0.0) carry = np.where(gg > 0, 0.0, gg) bookc = bookc - due basis = bookc.copy() years = n_days / 365.0 flow = cum_payout / EURUSD / years / 365.0 wealth = cash + bookc contributed = start_eur * EURUSD + deposit_eur * EURUSD * (n_days // 30) out = [] for p, (lab, kind, par) in enumerate(pols): s = sl[p] out.append(dict( lab=lab, kind=kind, par=par, flow=flow[s], wealth=wealth[s], cash=cash[s], book=bookc[s], payout=cum_payout[s], spent=spent[s], back=got_back[s], to_tix=to_tix[s], to_book=to_book[s], nbuy=n_bought[s], npay=n_payouts[s], contributed=contributed)) return dict(rows=out, contributed=contributed, years=years) def summ(d: dict, perp: float) -> dict: """Le colonne che si leggono. `rend` = ricchezza terminale x rendita perpetua (la definizione di EUR 50/giorno del PIANO); `flow` = payout medi (la definizione di §37).""" w, f = d["wealth"], d["flow"] rend = w * perp / 365.0 / EURUSD return dict( lab=d["lab"], p50_flow=float((f >= TARGET).mean()), p50_rend=float((rend >= TARGET).mean()), med_w=float(np.median(w)), mean_w=float(w.mean()), p10_w=float(np.percentile(w, 10)), p90_w=float(np.percentile(w, 90)), med_flow=float(np.median(f)), med_rend=float(np.median(rend)), p_loss=float((w < d["contributed"]).mean()), p_ruin=float((w < CHEAP).mean()), p_nopay=float((d["payout"] <= 0).mean()), med_tix=float(np.median(d["to_tix"])), nbuy=float(np.mean(d["nbuy"]))) # =========================================================================================== def main() -> None: try: sys.stdout.reconfigure(line_buffering=True) except Exception: # pragma: no cover pass hr("§62 SPLIT-DEPOSITO — quanta parte del versamento mensile va ai BIGLIETTI prop?") print(" §37 ha confrontato due CASI LIMITE (tutto libro / tutto biglietti). Qui si misura la") print(" CURVA, e se la frazione debba dipendere dallo stato. Attese registrate nel docstring:") print(" A1 politica ottima FRONT-LOADED e saturante (TIX batte BOOK, phi decrescente batte") print(" crescente), perche' il libro e' neutrale alla scala e il biglietto ha rendimenti") print(" DECRESCENTI a gradini (prezzo fisso, nozionale fisso, cap, indivisibilita');") print(" A2 gli ottimi delle tre colonne NON coincidono; A3 a 60 mesi l'ottimo phi SCENDE;") print(" A4 il trabocchetto §44 si eredita col segno negativo; A5 la frazione conta molto") print(" su P(>=50/g) solo perche' il libro vale ZERO su quella colonna a 36-60 mesi.") # ================================================================= dati hr(f"§0. IL PANNELLO — le stesse serie di §37 (U13 Binance, W-LONG 2021+) {clock()}") Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE) p13 = Cb[X.U13].dropna() o13, h13, l13 = (Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13], Lb.reindex(p13.index)[X.U13]) phases = sorted({ph for _, ph in CELLS}) XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in phases} xs0 = XSP[0] base = pc.crypto_daily_tuples(1.0, 0.0).index.intersection(xs0.index) first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]] I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))] print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | de-luck x{DELUCK}") rate = GC.funding_rate() if rate is None: print(" !! cache funding assente: il filone NON gira senza funding (lente L3). STOP.") return xs_ann = GC.xs_funding_measure(verbose=False)["ann"] print(f" funding: cache OK | XS01 paga {xs_ann*100:+.2f}%/anno (riproduzione di §37)") rb_full = FD.book_75_25(rate, alloc=654.0) if rb_full.index.tz is None: rb_full.index = rb_full.index.tz_localize("UTC") rb = np.nan_to_num(rb_full.reindex(I).values.astype(float)) print(f" pot LIBRO (75/25 a $654, funding dentro): drift {rb.mean()*365*100:.2f}%/anno, " f"vol {rb.std()*np.sqrt(365)*100:.1f}%, Sharpe {rb.mean()*365/(rb.std()*np.sqrt(365)):.2f}") W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25} ev_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8220) fu_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8221) RG: dict = {} for off, ph in CELLS: f = GC.fvec_for(W_A, I, rate, off, xs_ann) R0, G0 = AP.rg_off(W_A, I, XSP[ph], off) Rx = R0 - f j, lv, _ = GC.best_lev(Rx, G0, ev_b, fu_b) RG[(off, ph)] = (Rx, G0, lv, j) print(f"\n {len(CELLS)} celle d'ancora (sotto-campione sistematico a priori sui 23 offset x " f"3 fasi di §32):") for (off, ph), (Rx, _, lv, j) in RG.items(): print(f" offset {off:>3} min, fase {ph} -> J {j:.3f} @ leva {lv:.3f}x " f"drift {Rx.mean()*365*100:+6.2f}%/anno") # ================================================================= §1 repliche hr(f"§1. LE REPLICHE — prima di produrre un numero nuovo {clock()}") ok: dict[str, bool] = {} # R1: muro L3 (la rendita perpetua che converte capitale in EUR/giorno) import r0822d_piano_vero as PV # noqa: E402 B3 = PV.costruisci_serie() r_l3 = PV.deluck(B3["fund"].values.astype(float)) netto = TARGET * 365 * EURUSD perp, muro = PN.muro(r_l3, netto, TAX_RATE, TX.PATRIMONIALE) ok["R1"] = abs(muro - PUB["muro"]) < 3_000.0 print(f" R1 muro L3 CONGIUNTA: perpetua {perp:.2%} · ${muro:,.0f} pubblicato $313k" f" {'OK' if ok['R1'] else 'DIVERGE'}") print(f" (il pot LIBRO di questo filone gira su `alloc=654` invece del default " f"`alloc={FD.ALLOC:.0f}` di L3: drift " f"{rb.mean()*365*100:.2f}% contro {PV.deluck(B3['fund'].values.astype(float)).mean()*365*100:.2f}% " f"-> differenza dichiarata, non nascosta)") # R2: il mio simulatore == GC.ladder (che e' bit-exact vs pc.simulate) cell0 = CELLS[0] R0, G0, LV0, _ = RG[cell0] ref = GC.ladder(R0, G0, N_BASE, LV0, SEED, refund=False, min_payout=0.0, deposit_eur=0.0) mine = sim_split(R0, G0, rb, [("ctl", "FIX", 1.0)], N_BASE, LV0, SEED, 1080, deposit_eur=0.0, refund=False, min_payout=0.0) a = np.sort(ref["eur_day"]) b = np.sort(mine["rows"][0]["flow"]) d2 = float(np.abs(ref["eur_day"] - mine["rows"][0]["flow"]).max()) ok["R2"] = d2 < 1e-12 print(f" R2 sim_split(phi=1) vs GC.ladder, percorso per percorso: max|diff| = {d2:.3e}" f" {'BIT-EXACT' if ok['R2'] else 'DIVERGE'} (ordinati {np.abs(a-b).max():.3e})") # R3: il pot libro == GC.book_road, sugli stessi sorteggi n_blocks = int(np.ceil(1080 / BLOCK)) rng_c = np.random.default_rng(SEED) st_c = rng_c.integers(0, len(I) - BLOCK, size=(N_BASE, n_blocks)) BOOT_C = (st_c[:, :, None] + np.arange(BLOCK)[None, None, :]).reshape(N_BASE, -1)[:, :1080] brd = GC.book_road(rb, BOOT_C, START_EUR, DEP_MAIN) mineb = sim_split(R0, G0, rb, [("ctl", "FIX", 0.0)], N_BASE, LV0, SEED, 1080, deposit_eur=DEP_MAIN) d3 = float(np.abs(np.sort(brd["cash"]) - np.sort(mineb["rows"][0]["wealth"])).max()) ok["R3"] = d3 < 1e-8 print(f" R3 sim_split(phi=0) vs GC.book_road (stessi blocchi): max|diff| = ${d3:.3e}" f" {'IDENTICO' if ok['R3'] else 'DIVERGE'}") # R4: gli estremi di §37, alla banda d'ancora (la cella mediana di §37 non e' pubblicata) print(f"\n R4 GLI ESTREMI DI §37 — le due strade intere, sulle {len(CELLS)} celle d'ancora") print(f" (§37 gira sulla SUA cella mediana, non pubblicata: qui si verifica che i suoi") print(f" numeri cadano DENTRO la banda, non che coincidano)") print(f"\n {'versamento':>11} {'strada':>10} {'mediana (banda celle)':>34} " f"{'media':>12} {'P(>=50/g)':>22}") r37, _r4 = {}, [] for dep in (0.0, DEP_MAIN): for lab, phi in (("BIGLIETTO", 1.0), ("LIBRO", 0.0)): med, mea, p50 = [], [], [] for c in CELLS: Rc, Gc, lvc, _ = RG[c] s = sim_split(Rc, Gc, rb, [(lab, "FIX", phi)], N_BASE, lvc, SEED, 1080, deposit_eur=dep) q = summ(s["rows"][0], perp) med.append(q["med_w"]); mea.append(q["mean_w"]); p50.append(q["p50_flow"]) r37[(dep, lab)] = (np.median(med), np.median(mea), np.median(p50)) pubm = PUB["med_tix_37"] if (dep and lab == "BIGLIETTO") else ( PUB["med_book_37"] if dep else (PUB["med_tix_0"] if lab == "BIGLIETTO" else PUB["med_book_0"])) inside = min(med) <= pubm <= max(med) _r4.append(bool(inside)) print(f" EUR{dep:>7,.0f}/m {lab:>10} ${np.median(med):>10,.0f} " f"[${min(med):>8,.0f},${max(med):>9,.0f}] ${np.median(mea):>11,.0f} " f"{np.median(p50):>9.1%} [{min(p50):>5.1%},{max(p50):>5.1%}] " f"{'PUB dentro' if inside else 'PUB fuori'}") ok["R4"] = all(_r4) print(f" §37 pubblica: BIGLIETTO EUR500 ${PUB['med_tix_37']:,.0f} / P {PUB['p50g_37']:.1%} · " f"LIBRO ${PUB['med_book_37']:,.0f} · BIGLIETTO EUR0 ${PUB['med_tix_0']:,.0f} · " f"LIBRO EUR0 ${PUB['med_book_0']:,.0f}") print(f"\n REPLICHE: {sum(ok.values())}/{len(ok)} " f"{'tutte OK' if all(ok.values()) else 'ATTENZIONE: ' + str([k for k,v in ok.items() if not v])}") if not (ok["R2"] and ok["R3"]): print(" Senza R2/R3 i numeri sotto sarebbero di un'altra macchina. STOP.") return # ================================================================= §2 la curva hr(f"§2. LA CURVA DELLA FRAZIONE — 36 mesi, EUR {DEP_MAIN:.0f}/mese, {N_BASE} percorsi {clock()}") print(" Tutte le politiche girano nello STESSO Monte Carlo (blocchi impilati): stesso") print(" bootstrap, stessa morte-firm => ogni delta e' APPAIATO percorso per percorso.") POLS = make_policies(full=True) RES: dict = {} for c in CELLS: Rc, Gc, lvc, _ = RG[c] s = sim_split(Rc, Gc, rb, POLS, N_BASE, lvc, SEED, 1080, deposit_eur=DEP_MAIN) RES[c] = {r["lab"]: r for r in s["rows"]} print(f" cella {c} fatta {clock()}") CONTRIB = s["contributed"] fix_labs = [p[0] for p in POLS if p[1] == "FIX"] print(f"\n Versato in 36 mesi: ${CONTRIB:,.0f} (capitale iniziale ${START_EUR*EURUSD:,.0f} " f"+ 36 x EUR{DEP_MAIN:.0f})") print(f"\n {'phi':>6} {'P(>=50/g) FLUSSO':>26} {'P(>=50/g) RENDITA':>18} " f"{'ricch. MEDIANA':>26} {'media':>11} {'P(12} {'P(<$249)':>10} " f"{'EUR/g med':>10}") print(f" {'':>6} {'(la metrica di §37)':>26} {'(la metrica del PIANO)':>18}") curva = [] for lab in fix_labs: q = {c: summ(RES[c][lab], perp) for c in CELLS} g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731 lohi = lambda k: (min(q[c][k] for c in CELLS), max(q[c][k] for c in CELLS)) # noqa: E731 p50, pl_, ph_ = g("p50_flow"), *lohi("p50_flow") mw, mlo, mhi = g("med_w"), *lohi("med_w") rendm = g("med_flow") + g("med_rend") curva.append(dict(lab=lab, phi=float(lab.split()[1]), p50=p50, med=mw, mean=g("mean_w"), ploss=g("p_loss"), pruin=g("p_ruin"), prend=g("p50_rend"), eurg=rendm)) print(f" {lab.split()[1]:>6} {p50:>13.1%} [{pl_:>5.1%},{ph_:>5.1%}] " f"{g('p50_rend'):>17.1%} ${mw:>10,.0f} [${mlo:>7,.0f},${mhi:>8,.0f}] " f"${g('mean_w'):>10,.0f} {g('p_loss'):>12.1%} {g('p_ruin'):>10.1%} {rendm:>10.2f}") CV = pd.DataFrame(curva) a_p50 = CV.loc[CV["p50"].idxmax()] a_med = CV.loc[CV["med"].idxmax()] a_mean = CV.loc[CV["mean"].idxmax()] a_saf = CV.loc[CV["ploss"].idxmin()] print(f"\n ARGMAX PER COLONNA — la risposta al mandato punto 2:") print(f" P(>=50/g) flusso : phi = {a_p50['phi']:.2f} ({a_p50['p50']:.1%})") print(f" ricchezza MEDIANA: phi = {a_med['phi']:.2f} (${a_med['med']:,.0f})") print(f" ricchezza MEDIA : phi = {a_mean['phi']:.2f} (${a_mean['mean']:,.0f})") print(f" P(=50/g) RENDITA: max {CV['prend'].max():.1%} su TUTTE le celle " f"-> {'nessuno arriva' if CV['prend'].max() < 0.005 else 'qualcuno arriva'}") print(f" >>> ATTESA A2 ('gli ottimi non coincidono'): " f"{'CONFERMATA' if len({round(a_p50['phi'],2), round(a_med['phi'],2)}) > 1 else 'SMENTITA'}") # ---- risoluzione MC, misurata (mandato: riportarla accanto a ogni delta) -------------- print(f"\n §2.1 RISOLUZIONE MONTE CARLO — misurata su 3 semi (non assunta) {clock()}") probe = [("F 0.00", "FIX", 0.0), ("F 0.50", "FIX", 0.5), ("F 1.00", "FIX", 1.0)] Rc, Gc, lvc, _ = RG[CELLS[0]] seeds = (SEED, SEED + 101, SEED + 202) tabS = {} for sd in seeds: s = sim_split(Rc, Gc, rb, probe, N_BASE, lvc, sd, 1080, deposit_eur=DEP_MAIN) tabS[sd] = {r["lab"]: summ(r, perp) for r in s["rows"]} sp_lvl = max(max(tabS[sd][l]["p50_flow"] for sd in seeds) - min(tabS[sd][l]["p50_flow"] for sd in seeds) for l in ("F 0.50", "F 1.00")) d_pair = [tabS[sd]["F 1.00"]["p50_flow"] - tabS[sd]["F 0.50"]["p50_flow"] for sd in seeds] sp_pair = max(d_pair) - min(d_pair) print(f" {'seme':>10} {'P50 phi=0.5':>13} {'P50 phi=1.0':>13} {'delta appaiato':>16} " f"{'med. ricch. phi=0':>18}") for sd in seeds: print(f" {sd:>10} {tabS[sd]['F 0.50']['p50_flow']:>13.2%} " f"{tabS[sd]['F 1.00']['p50_flow']:>13.2%} " f"{tabS[sd]['F 1.00']['p50_flow']-tabS[sd]['F 0.50']['p50_flow']:>+16.2%} " f"${tabS[sd]['F 0.00']['med_w']:>17,.0f}") print(f" ampiezza fra semi: LIVELLI {sp_lvl*100:.2f} pp · DIFFERENZE APPAIATE " f"{sp_pair*100:.2f} pp -> ogni delta sotto ~{max(sp_pair,0.0)*100:.1f} pp e' RUMORE") # ================================================================= §3 politiche di stato hr(f"§3. LE POLITICHE DIPENDENTI DALLO STATO — famiglia dichiarata prima {clock()}") print(f" {'politica':>16} {'P(>=50/g)':>22} {'ricch. mediana':>15} {'media':>11} " f"{'P(12} {'P(<$249)':>10} {'$ ai biglietti':>15} {'biglietti':>10}") fam = [p[0] for p in POLS if p[1] in ("TIX", "BOOK", "PHI_UP", "PHI_DN", "CASH")] ref_lab = "F 1.00" qref = {c: summ(RES[c][ref_lab], perp) for c in CELLS} for lab in [ref_lab, "F 0.00"] + fam: q = {c: summ(RES[c][lab], perp) for c in CELLS} g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731 dp = float(np.median([q[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS])) npos = sum(1 for c in CELLS if q[c]["p50_flow"] - qref[c]["p50_flow"] > 0) mark = "" if lab == ref_lab else f" d {dp*100:+5.2f}pp ({npos}/{len(CELLS)})" print(f" {lab:>16} {g('p50_flow'):>9.1%}{mark:>13} ${g('med_w'):>14,.0f} " f"${g('mean_w'):>10,.0f} {g('p_loss'):>12.1%} {g('p_ruin'):>10.1%} " f"${g('med_tix'):>14,.0f} {g('nbuy'):>10.1f}") tixb = float(np.median([summ(RES[c]["TIX $5,000"], perp)["p50_flow"] for c in CELLS])) bookb = float(np.median([summ(RES[c]["BOOK $5,000"], perp)["p50_flow"] for c in CELLS])) up = float(np.median([summ(RES[c]["PHI_UP $10k"], perp)["p50_flow"] for c in CELLS])) dn = float(np.median([summ(RES[c]["PHI_DN $10k"], perp)["p50_flow"] for c in CELLS])) print(f"\n >>> ATTESA A1 (front-loaded): TIX $5k {tixb:.1%} contro BOOK $5k {bookb:.1%} " f"-> {'CONFERMATA' if tixb > bookb else 'SMENTITA'}; " f"phi DECRESCENTE {dn:.1%} contro CRESCENTE {up:.1%} " f"-> {'CONFERMATA' if dn > up else 'SMENTITA'}") cashp = {c: summ(RES[c]["CASH-ATT $1.2k"], perp) for c in CELLS} d_cash = float(np.median([cashp[c]["med_w"] - qref[c]["med_w"] for c in CELLS])) d_cash50 = float(np.median([cashp[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS])) print(f" CASH-ATT (l'unica che puo' dominare phi=1): ricchezza mediana " f"${d_cash:+,.0f}, P(>=50/g) {d_cash50*100:+.2f}pp " f"({sum(1 for c in CELLS if cashp[c]['med_w'] > qref[c]['med_w'])}/{len(CELLS)} celle)") # ================================================================= §4 il trabocchetto §44 hr(f"§4. IL TRABOCCHETTO DI §44 — la CONDIZIONE davanti al bonifico {clock()}") print(" §44: «comprare meglio e comprare tardi sono la stessa mossa». Qui la condizione non") print(" ritarda un BONIFICO ma un ACQUISTO di biglietto. Si separa il costo della CONDIZIONE") print(" da quello del RITARDO con un GEMELLO DETERMINISTICO di pari attesa media.") print(f"\n {'politica':>14} {'attesa media (g)':>18} {'P(>=50/g)':>11} {'d appaiato':>12} " f"{'ricch. mediana':>15} {'d':>11}") # attesa media della condizione, misurata sulla stessa serie for lab in ("F 1.00", "RARE dd 10%", "RARE dd 20%", "DELAY 95g", "DELAY 713g"): q = {c: summ(RES[c][lab], perp) for c in CELLS} g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731 dp = float(np.median([q[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS])) dm = float(np.median([q[c]["med_w"] - qref[c]["med_w"] for c in CELLS])) wait = {"F 1.00": 0.0, "DELAY 95g": 95.0, "DELAY 713g": 713.0}.get(lab, float("nan")) if np.isnan(wait): d_ = float(lab.split()[-1].rstrip("%")) / 100.0 eqp = np.cumprod(1.0 + PV.deluck(rb)) dd = eqp / np.maximum.accumulate(eqp) - 1.0 hit = np.flatnonzero(dd <= -d_) wait = float(hit[0]) if len(hit) else float("inf") print(f" {lab:>14} {wait:>18.0f} {g('p50_flow'):>11.1%} {dp*100:>+11.2f}pp " f"${g('med_w'):>14,.0f} ${dm:>+10,.0f}") print(" (l'attesa di RARE e' il primo giorno in cui la condizione si verifica sulla serie") print(" storica de-luckata: nel bootstrap e' una variabile aleatoria, qui e' un'ancora)") # ================================================================= §5 la scala hr(f"§5. I RENDIMENTI DI SCALA — la previsione A1, misurata direttamente {clock()}") print(" Il canale BIGLIETTO assorbe soldi solo a gradini (prezzo fisso, 6 slot, cap firm).") print(" Quanto ne assorbe davvero, e quando satura?") Rc, Gc, lvc, _ = RG[CELLS[0]] print(f"\n {'versamento':>12} {'$ versati ai bigl.':>19} {'$ SPESI in biglietti':>21} " f"{'assorbimento':>13} {'cassa finale med':>17} {'biglietti comprati':>19}") for dep in (0.0, 250.0, 500.0, 1_000.0, 2_000.0): s = sim_split(Rc, Gc, rb, [("F 1.00", "FIX", 1.0)], N_BASE, lvc, SEED, 1080, deposit_eur=dep) r = s["rows"][0] got = float(np.mean(r["to_tix"])) sp = float(np.mean(r["spent"])) print(f" EUR{dep:>8,.0f}/m ${got:>18,.0f} ${sp:>20,.0f} {sp/max(got,1e-9):>12.1%} " f"${np.median(r['cash']):>16,.0f} {np.mean(r['nbuy']):>19.1f}") print(" ('assorbimento' = quanta parte dei soldi mandati al pot biglietti finisce davvero") print(" in un biglietto; il resto resta CASSA FERMA — e' il costo dell'indivisibilita'.)") print(f"\n §5.1 LA FRAZIONE CONTRO L'IMPORTO — la domanda che decide {clock()}") print(f" {'versamento':>12} {'phi=0 P(>=50/g)':>16} {'phi=1 P(>=50/g)':>16} " f"{'ampiezza phi':>13} {'phi=0 ricch.med':>16} {'phi=1 ricch.med':>16}") prev = None imp_rows = [] for dep in (0.0, 250.0, 500.0, 1_000.0): s = sim_split(Rc, Gc, rb, [("F 0.00", "FIX", 0.0), ("F 1.00", "FIX", 1.0)], N_BASE, lvc, SEED, 1080, deposit_eur=dep) q0, q1 = summ(s["rows"][0], perp), summ(s["rows"][1], perp) imp_rows.append((dep, q0, q1)) print(f" EUR{dep:>8,.0f}/m {q0['p50_flow']:>16.1%} {q1['p50_flow']:>16.1%} " f"{(q1['p50_flow']-q0['p50_flow'])*100:>+12.1f}pp ${q0['med_w']:>15,.0f} " f"${q1['med_w']:>15,.0f}") prev = (q0, q1) d_phi = imp_rows[2][2]["p50_flow"] - imp_rows[2][1]["p50_flow"] d_dep_tix = imp_rows[2][2]["p50_flow"] - imp_rows[1][2]["p50_flow"] d_dep_book = imp_rows[2][1]["med_w"] - imp_rows[1][1]["med_w"] print(f"\n A EUR500/m: passare phi 0->1 vale {d_phi*100:+.1f}pp di P(>=50/g); passare il") print(f" VERSAMENTO da EUR250 a EUR500 (a phi=1) vale {d_dep_tix*100:+.1f}pp.") print(f" Sulla RICCHEZZA (phi=0): EUR250 -> EUR500 vale ${d_dep_book:+,.0f} di mediana.") # ================================================================= §6 60 mesi hr(f"§6. ORIZZONTE 60 MESI — l'unica ragione strutturale per cui l'ottimo dovrebbe muoversi {clock()}") p5 = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** 5 print(f" A 5 anni ogni firm muore con p = {p5:.0%}; entrambe con p = {p5**2:.0%}. Il canale") print(f" biglietto ha VITA FINITA, il libro no. Attesa A3: l'ottimo phi SCENDE.") POLS60 = ([("F 0.00", "FIX", 0.0), ("F 0.20", "FIX", 0.20), ("F 0.50", "FIX", 0.50), ("F 0.80", "FIX", 0.80), ("F 1.00", "FIX", 1.0), ("TIX $5,000", "TIX", 5_000.0), ("BOOK $5,000", "BOOK", 5_000.0), ("PHI_DN $10k", "PHI_DN", 10_000.0), ("CASH-ATT $1.2k", "CASH", 1_200.0)]) R60: dict = {} for c in CELLS[:3]: Rc2, Gc2, lvc2, _ = RG[c] s = sim_split(Rc2, Gc2, rb, POLS60, N_BASE, lvc2, SEED, 1800, deposit_eur=DEP_MAIN) R60[c] = {r["lab"]: r for r in s["rows"]} print(f"\n {'politica':>16} {'P(>=50/g) 36m':>14} {'P(>=50/g) 60m':>14} {'ricch.med 60m':>15} " f"{'media 60m':>12} {'P(12} {'P(>=50/g) RENDITA 60m':>22}") for lab, _, _ in POLS60: q36 = float(np.median([summ(RES[c][lab], perp)["p50_flow"] for c in CELLS[:3]])) q = {c: summ(R60[c][lab], perp) for c in CELLS[:3]} g = lambda k: float(np.median([q[c][k] for c in CELLS[:3]])) # noqa: E731 print(f" {lab:>16} {q36:>14.1%} {g('p50_flow'):>14.1%} ${g('med_w'):>14,.0f} " f"${g('mean_w'):>11,.0f} {g('p_loss'):>12.1%} {g('p50_rend'):>22.1%}") c60 = pd.DataFrame([dict(phi=float(l.split()[1]), p50=float(np.median([summ(R60[c][l], perp)["p50_flow"] for c in CELLS[:3]])), med=float(np.median([summ(R60[c][l], perp)["med_w"] for c in CELLS[:3]]))) for l, k, _ in POLS60 if k == "FIX"]) print(f"\n argmax P(>=50/g): 36 mesi phi={a_p50['phi']:.2f} · 60 mesi " f"phi={c60.loc[c60['p50'].idxmax(),'phi']:.2f}") print(f" argmax ricchezza mediana: 36 mesi phi={a_med['phi']:.2f} · 60 mesi " f"phi={c60.loc[c60['med'].idxmax(),'phi']:.2f}") print(f" >>> ATTESA A3 ('a 60 mesi l'ottimo phi scende'): " f"{'CONFERMATA' if c60.loc[c60['p50'].idxmax(),'phi'] < a_p50['phi'] else 'SMENTITA'}") # ================================================================= §7 verdetto hr(f"§7. VERDETTO {clock()}") sp = max(sp_pair, 0.0) * 100 rng_p50 = (CV["p50"].max() - CV["p50"].min()) * 100 rng_med = CV["med"].max() - CV["med"].min() print(f" Ampiezza della CURVA fra phi=0 e phi=1 (36 mesi, EUR{DEP_MAIN:.0f}/m):") print(f" P(>=50/g) flusso : {rng_p50:.1f} pp (risoluzione MC {sp:.1f} pp) -> " f"{'SEGNALE' if rng_p50 > 3*sp else 'DENTRO IL RUMORE'}") print(f" ricchezza mediana: ${rng_med:,.0f}") print(f" P(>=50/g) rendita: {(CV['prend'].max()-CV['prend'].min())*100:.1f} pp") print(f" P( nessun deflated-Sharpe da calcolare; e la grandezza") print(f" misurata e' una P, non uno Sharpe.") print(f"\n {clock()} fine.") if __name__ == "__main__": main()