8c0b5f97ee
I dodici filoni della sesta ondata erano rimasti `_in corso_`: la sessione si e' chiusa dopo che gli agenti avevano scritto gli script e prima che i verdetti fossero consolidati, e i loro messaggi finali sono persi. I numeri qui NON vengono da quei messaggi: vengono dalla riesecuzione dei dodici script (07:38-08:06 UTC, sequenziale, log in logs/r0823b/ che e' gitignored). E' il motivo per cui l'ondata non e' andata persa: ogni script calcola il proprio verdetto a runtime. Cosa tocca decisioni gia' prese: - §67 il gate pre-registrato XSR01 del 23/10 legge un monitor tarato sul pavimento del venue sbagliato (C* $15-20k, non ~$3k) - §64 la raccomandazione di §51 (raccogliere la catena USDC) non e' giustificata dalla ragione che porta: le due superfici sono la stessa - §60 la politica MISTO scelta il 25/07 non e' piu' l'ottimo (oggi MISTO-A) - §63 domanda fiscale NUOVA, diversa da quella aperta il 07/08 Il risultato piu' grande e' di §58: l'obiettivo del progetto ha DUE definizioni operative in uso che danno 33,9% contro 0,33% sulla stessa domanda, e non era mai stato detto quale si stesse ottimizzando. r0823b_quasi_passati.py (§68) terminava con IndexError: Griglia.combo() indicizzava con self.idx (2720 giorni) un sottoinsieme di 958 righe. Corretto col parametro idx esplicito piu' un controllo di lunghezza; la rinormalizzazione e' riga per riga, quindi i valori sono quelli dell'intento dell'autore. La correzione e' del coordinatore, non dell'autore, ed e' dichiarata nel registro. Libro, pesi, cron, config INVARIATI. Nessun ordine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
785 lines
43 KiB
Python
785 lines
43 KiB
Python
"""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(<versato)':>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(<versato) min : phi = {a_saf['phi']:.2f} ({a_saf['ploss']:.1%})")
|
|
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(<versato)':>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(<versato)':>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(<versato) : {(CV['ploss'].max()-CV['ploss'].min())*100:.1f} pp")
|
|
print(f"\n In EUR/giorno (mediana, flusso + rendita terminale — le due NON si sommano")
|
|
print(f" senza doppio conteggio, si leggono separate):")
|
|
for lab in ("F 0.00", "F 0.50", "F 1.00"):
|
|
row = CV[CV["lab"] == lab].iloc[0]
|
|
print(f" {lab}: EUR {row['eurg']:.2f}/giorno mediani a 36 mesi")
|
|
print(f"\n Il target e' EUR {TARGET:.0f}/giorno.")
|
|
print(f" TRIALS: nessuna cella SELEZIONATA (la famiglia e' dichiarata nel docstring e tutte")
|
|
print(f" le celle sono riportate) -> 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()
|