Files

1104 lines
60 KiB
Python

"""r0822d_gate_c — GATE PROP-01, gamba (c): il BIGLIETTO riprezzato come OPZIONE (2026-08-22).
IL BUCO CHE CHIUDE
------------------
`GATE PROP-01` (RESULTS-0822 §29) ha tre gambe. Due sono state chiuse stanotte:
(a) LISTINO -> PASS 13/13, misurato sul venue (Bybit `instruments-info`, 833 strumenti);
(b) ANCORA SKH01 -> PASS, delta appaiato +0,116 sui 23 offset, 230/230 celle congiunte
positive — ma il numero OPERATIVO e' sceso a P(>=50 EUR/g) = 4,4% [2,5-8,2%], P(zero) 35,3%.
La gamba (c) e' rimasta una CONSTATAZIONE, non un criterio:
"un'eval HYRO $100k costa $579 su un conto di $635 = il 91%"
e per giunta poggia su un'economia sbagliata. La pagina della firm dichiara la quota come
**"Refundable Challenge Deposit, returned in full alongside your first funded payout"**:
non e' una fee affondata, e' un **deposito rimborsabile al PRIMO PAYOUT**. Immobilizza il 91%
del conto; non lo brucia. Va riprezzato come **struttura a opzione a DUE STADI**
(passare l'eval **E POI** produrre abbastanza profitto da chiedere un prelievo), non come spesa.
⚠ E qui c'e' un fatto che nessuno ha notato: **il progetto contiene GIA' DUE modelli che si
contraddicono fra loro.**
* `r0725_hyro.py:200` -> `cost = price * (1.0 - pe)` = quota **rimborsata al PASS**;
* `r0725_prop_ladder.simulate` / `r0725_prop_coupled.simulate` -> `cash[buy] -= price`
e la quota **non torna MAI** = spesa affondata.
Il primo e' OTTIMISTA (rimborsa a un evento che accade prima e piu' spesso di quello vero),
il secondo e' PESSIMISTA (non rimborsa affatto). **Il vero sta in mezzo, e il numero operativo
del progetto — P(>=50 EUR/g) — viene dal secondo.**
LA PREVISIONE, REGISTRATA PRIMA DI MISURARE
-------------------------------------------
1. Il rimborso al primo payout vale **poco** sull'EV del biglietto e **piu'** sulla scala di
conti (dove la cassa che torna ricompra un biglietto e composta). Mi aspetto: EV +5-15%,
P(>=50/g) +1-3 punti percentuali.
2. `P(primo payout | pass)` mi aspetto **alta** (>80%): la soglia di prelievo e' $100 di quota
trader = 0,125% di nozionale su $100k, cioe' un'inezia rispetto al 10% appena prodotto
nell'eval. Quindi la differenza fra le due convenzioni di rimborso e' **piccola**.
3. Il **funding** su un conto funded fa male in proporzione alla leva: a 0,75x mi aspetto
~-1,6%/anno di equity del conto (cioe' 0,75 x 2,16%), e su un conto a barriera un costo di
drift si paga sulla P(pass) piu' che sul maxDD.
4. **XS01: mi aspetto che INCASSI funding**, perche' e' market-neutral e i perp con funding piu'
caro sono tipicamente quelli in forte trend, che il momentum cross-sezionale compra... ma
comprarli vuol dire PAGARE il loro funding. Le due forze tirano in verso opposto: **non lo so,
e per questo si misura.** Se il segno esce negativo (costo) lo dico.
5. **La domanda che decide**: con P(>=50 EUR/g) 4,4% e P(zero) 35,3%, il biglietto ha EV
positivo? E batte l'alternativa ovvia (tenere i $635 sul libro e versare)? Mi aspetto:
**EV positivo sulla MEDIA e negativo sulla MEDIANA** — cioe' una lotteria con valore atteso
positivo, che e' esattamente la forma che un conto da $635 non puo' permettersi molte volte.
CONVENZIONI DICHIARATE
----------------------
* Lente **ACCOPPIATA** ovunque (`r0725_prop_coupled`): su una regola a UN GIORNO la close-only
e' esattamente CIECA. La close-only compare solo come controllo.
* Configurazione del conto funded: **W_A = TP01 38 / SKH01 38 / XS01 25** (centro della regione
robusta di PROP-RECAL), XS01 = **U13 Binance** (la versione piu' debole e l'unica testabile
fuori campione), finestra **W-LONG 2021+**, de-luck sul drift **x0,89**.
* Il conto PROPRIO gira invece il **libro LIVE 75/25 a $654 con min-order $5** (XS01 non e'
eseguibile a quel capitale): l'asimmetria e' REALE ed e' il punto del canale funded.
* Banda d'ancora: **23 offset x 3 fasi (0,3,7)**, lo stesso sottocampione dichiarato da
`r0822c_skh_anchor_prop`. La stima onesta e' la **MEDIANA**, non l'ancora canonica.
* **Firm death 10%/anno** e **cap $200k/trader**: assunzioni gia' in uso nel progetto (24/07),
dichiarate e non nascoste.
* Soglia di prelievo **$100 di quota trader** (fonte primaria-dichiarativa della firm, non
misurata) -> profitto lordo >= $125 su $100k.
* Monte Carlo: la risoluzione si dichiara. 1500 percorsi sulla scala di conti (+-1pp su
P(>=50/g)), 6000 sul biglietto a conto singolo, 2500 su J.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0822d_gate_c.py`
Rete: NESSUNA (tutto da disco: parquet certificati, cache Binance di r0822b, cache funding
di r0822d_funding). Se la cache del funding manca, la sezione 1 lo dichiara e si ferma li'.
"""
from __future__ import annotations
import contextlib
import io
import re
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 r0724_goal50_intraday_mc as mc # noqa: E402 recon orario per-sleeve (24/07)
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 r0822_prop_alloc as PA # noqa: E402 J, bootstrap, simulatore (22/08)
import r0822b_prop_recal as PR # noqa: E402 PROP-RECAL (22/08)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + cache Binance (22/08)
import r0822c_skh_anchor_prop as AP # noqa: E402 gamba (b), offset di SKH01 (22/08)
import r0822d_funding as FD # noqa: E402 il funding (22/08)
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
RAW = ROOT / "data" / "raw"
HYRO = "HYRO"
ASSETS = ("BTC", "ETH")
EURUSD = pl.EURUSD
TAX_RATE = pl.TAX_RATE
SPLIT = pl.SPLIT
DELUCK = PA.DELUCK # x0,89, fattore d'ancora MISURATO (26/07)
LEVS = PR.LEVS
OFFSETS = AP.OFFSETS # 23 offset a priori di SKH01
PHASES_SUB = AP.PHASES_SUB # (0, 3, 7) — dichiarato da r0822c
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
W_LIVE = {"TP01": 0.75, "SKH01": 0.25}
N_PATHS_J = PA.N_PATHS # 2500
N_PATHS_LAD = 1500
N_PATHS_TIX = 6000
EV_H = FU_H = 365
SEED_EV, SEED_FU = 8220, 8221
SEED_LAD = 20260822
MIN_PAYOUT_TRADER = 100.0 # quota trader minima per un prelievo (firm)
HORIZON_DAYS = pl.HORIZON_MONTHS * 30 # 1080 = 36 mesi
# numeri PUBBLICATI da riprodurre prima di produrne di nuovi
PUB = dict(
hyro_ev_close=6789.0, hyro_ev_intra=1166.0, # r0725_hyro (25/07)
recal_j=0.453, recal_live=0.281, recal_d=0.175, recal_p50g=0.078, # §29
anchor_p50g=0.044, anchor_p10=0.025, anchor_p90=0.082,
anchor_pzero=0.353, # §32
fund_book=-2.16, fund_tp=-2.10, fund_skh=-2.32, # §35 (%/anno)
)
TRIALS = 0
T0 = time.time()
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "-" * 104)
if t:
print(f" {t}")
print("-" * 104)
def tick(lab: str) -> None:
print(f" [{time.time()-T0:6.0f}s] {lab}")
# ============================================================================ 1. IL FUNDING
def funding_rate() -> dict | None:
"""Serie oraria del funding per asset (LINEARE dove esiste, INVERSE prima come proxy
dichiarato). Legge SOLO la cache di `r0822d_funding` — nessuna rete."""
try:
F = {}
for instr, (start, _a, _k) in FD.INSTR.items():
d = FD.fetch_funding(instr, start).copy()
d["dt"] = pd.to_datetime(d["ts"], unit="ms", utc=True)
F[instr] = d.drop_duplicates(subset="ts").sort_values("dt").set_index("dt")
return FD.build_rate(F)
except Exception as exc: # pragma: no cover
print(f" !! cache funding assente/illeggibile ({exc}) — il funding NON viene messo dentro")
return None
_FTP: dict[int, pd.Series] = {}
def f_tp01_sleeve(rate: dict) -> pd.Series:
"""Costo di funding GIORNALIERO dello sleeve TP01 a peso 1.0, sulla STESSA lente delle gambe
orarie del recon (`mc.tp01_daily_frame` = TP01 canonico, senza min-order: su un conto funded
da $100k il min-order $5 non esiste, e questa e' la lente giusta li').
Convenzione temporale identica a `FD.tp01_canonical`: la posizione tenuta nel giorno d e'
decisa a close[d-1], e paga il funding sulle 24 ore (d, d+24h]."""
if 0 in _FTP:
return _FTP[0]
out = None
for a in ASSETS:
tpf = mc.tp01_daily_frame(a)
days = pd.DatetimeIndex(tpf.index)
s = pd.Series(tpf["w"].values * FD.daily_funding(rate[a], days), index=days)
out = s if out is None else out.add(s, fill_value=0.0)
_FTP[0] = out
return out
_FSKH: dict[int, pd.Series] = {}
def f_skh_sleeve(rate: dict, off: int = 0) -> pd.Series:
"""Costo di funding giornaliero dello sleeve SKH01 (50/50) all'offset `off`, come
DIFFERENZA fra il path live senza e con funding (`FD.skh_book`). E' esatto al primo ordine
(l'equity compone: il secondo ordine e' ~1e-6/giorno) e riproduce il -2,32%/anno di §35."""
if off in _FSKH:
return _FSKH[off]
a0 = FD.skh_book(off, "hourly", None)
a1 = FD.skh_book(off, "hourly", rate)
if a0.index.tz is None:
a0.index = a0.index.tz_localize("UTC")
if a1.index.tz is None:
a1.index = a1.index.tz_localize("UTC")
s = (a0 - a1).dropna()
_FSKH[off] = s
return s
def xs_funding_measure(verbose: bool = True) -> dict:
"""IL SEGNO DEL FUNDING SU XS01, MISURATO (non assunto).
XS01 e' long 5 / short 5 a pesi uguali -> `sum(W) = 0` ESATTAMENTE. Quindi
`sum(W_i f_i) = sum(W_i (f_i - fbar))`: **il LIVELLO del funding non entra, entra solo la
sua DISPERSIONE CROSS-SEZIONALE**. Cio' significa due cose:
* non e' vero ne' che 'incassa' ne' che 'paga' per costruzione: dipende da come il
funding e' distribuito FRA gli asset che il momentum compra e vende;
* il fatto noto che il funding Hyperliquid sia ~2,1x quello del venue vero (§35) si
trasferisce alla dispersione, quindi si riporta una BANDA, non un punto.
Dato: `data/raw/hlfund_<sym>_1h.parquet` (19 major, 2023-05 -> 2026-06)."""
P = X.hl_panel()
det: dict = {}
X.xsec_engine(P, phase=0, detail=det)
W, scale, idx = det["W"], det["scale"], det["index"]
cols = list(P.columns)
gross_sum = float(np.abs(W.sum(axis=1)).max())
fun = {}
for s in cols:
f = RAW / f"hlfund_{s.lower()}_1h.parquet"
if not f.exists():
continue
d = pd.read_parquet(f)["funding"]
d.index = pd.DatetimeIndex(d.index).tz_convert("UTC")
fun[s] = d.groupby(d.index.floor("D")).sum()
have = [s for s in cols if s in fun]
Fm = pd.DataFrame({s: fun[s] for s in have}).reindex(idx)
cover = Fm.notna().mean(axis=1)
Fv = Fm.fillna(0.0).values
sel = [cols.index(s) for s in have]
Wp = np.vstack([np.zeros((1, len(cols))), W[:-1]]) # posizioni TENUTE nel giorno i
cost = pd.Series((Wp[:, sel] * Fv).sum(axis=1) * scale, index=idx)
gross = pd.Series(np.abs(Wp[:, sel]).sum(axis=1) * scale, index=idx)
ok = cover >= 0.95
c = cost[ok]
if verbose:
print(f" pannello HL {P.shape[1]} gambe, {len(idx)} giorni; file di funding per "
f"{len(have)}/{len(cols)} gambe")
print(f" CONTROLLO di dollar-neutralita': max|sum(W)| = {gross_sum:.2e} "
f"-> {'ESATTO' if gross_sum < 1e-12 else 'NON neutrale'}")
print(f" giorni con copertura >=95% delle gambe: {int(ok.sum())} "
f"({c.index.min().date()} -> {c.index.max().date()})")
print(f"\n {'anno':>6} {'costo funding %/anno':>22} {'gross nozionale':>17} {'giorni':>7}")
for y, g in c.groupby(c.index.year):
gg = gross.reindex(g.index)
print(f" {y:>6} {float(g.mean()*365)*100:>21.3f}% {float(gg.mean()):>16.3f}x "
f"{len(g):>7}")
ann = float(c.mean() * 365)
se = float(c.std(ddof=1) / np.sqrt(len(c)) * 365)
print(f" {'TUTTO':>6} {ann*100:>21.3f}% {float(gross.reindex(c.index).mean()):>16.3f}x "
f"{len(c):>7} IC95 [{(ann-1.96*se)*100:+.3f}%, {(ann+1.96*se)*100:+.3f}%]")
ann = float(c.mean() * 365)
return dict(ann=ann, series=c, gross=float(gross.reindex(c.index).mean()),
se=float(c.std(ddof=1) / np.sqrt(len(c)) * 365), n=len(c))
# ================================================================ 2. LIBRO CON FUNDING
def fvec_for(w: dict, idx: pd.DatetimeIndex, rate: dict | None, off: int,
xs_ann: float) -> np.ndarray | None:
"""Vettore giornaliero del costo di funding del LIBRO ai pesi `w`.
TP01 e SKH01: serie misurate giorno per giorno. XS01: **tasso annuo costante** misurato su
Hyperliquid 2024-2026 e applicato all'intera finestra — dichiarato, perche' `hlfund` parte
nel 2023-05 e il pannello di XS01 usato qui e' Binance 2021+ (nessun funding su disco)."""
if rate is None:
return None
f = np.zeros(len(idx))
wtp, wsk, wxs = w.get("TP01", 0.0), w.get("SKH01", 0.0), w.get("XS01", 0.0)
if wtp:
f += wtp * np.nan_to_num(f_tp01_sleeve(rate).reindex(idx).values)
if wsk:
f += wsk * np.nan_to_num(f_skh_sleeve(rate, off).reindex(idx).values)
if wxs:
f += wxs * (xs_ann / 365.0)
return f
def best_lev(R, G, ev, fu) -> tuple[float, float, np.ndarray]:
jb, lb, vb = -1.0, None, None
for lv in LEVS:
o = PR.obj_rg(R, G, lv, ev, fu, factor=DELUCK)
if o["J"] > jb:
jb, lb, vb = o["J"], lv, o["jvec"]
bump(len(LEVS))
return jb, lb, vb
# ============================================ 3. SCALA DI CONTI COL DEPOSITO RIMBORSABILE
def ladder(R: np.ndarray, G: np.ndarray, n_paths: int, lev: float, seed: int,
refund: bool, deposit_eur: float = 0.0, start_eur: float = pl.START_EUR,
min_payout: float = 0.0, boot: np.ndarray | None = None,
block: int = 20, deluck: float = DELUCK) -> dict:
"""Copia di `r0725_prop_coupled.simulate` (un solo vettore di peso, `MAX_CONCURRENT` slot)
con TRE aggiunte e nessun'altra differenza:
1. `refund` — la quota torna in cassa **al PRIMO PAYOUT** di quel conto (regola vera
della firm). Se il conto busta prima di un payout, la quota e' persa.
2. `deposit_eur` — versamento mensile, per confrontare la strada-biglietto con la
strada-versare a parita' di soldi che entrano.
3. `min_payout` — quota trader minima per poter prelevare ($100 secondo la firm).
Con `refund=False, deposit_eur=0, min_payout=0, boot=None` deve essere **BIT-EXACT**
rispetto a `pc.simulate`: e' il controllo di §3.1.
Il funding, se c'e', e' gia' dentro `R` -> essendo `Rs = R*lev`, il costo di funding
**scala con la leva**, che e' la sua vera economia (e' proporzionale all'esposizione)."""
rng = np.random.default_rng(seed)
n_days = HORIZON_DAYS
L = len(R)
Rm = R.astype(float).reshape(-1, 1).copy()
Gm = G.astype(float).reshape(-1, 1).copy()
Rm = Rm - (1.0 - deluck) * Rm.mean(axis=0, keepdims=True)
if boot is None:
n_blocks = int(np.ceil(n_days / block))
starts = rng.integers(0, L - block, size=(n_paths, n_blocks))
idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
else:
idx = boot
n_slot = pl.MAX_CONCURRENT
sel = np.zeros(n_slot, dtype=int)
Rs = Rm[idx][:, :, sel] * lev
G_ = Gm[idx][:, :, sel] * lev
FIRMS = pl.FIRMS
cash = np.full(n_paths, start_eur * EURUSD)
phase = np.full((n_paths, n_slot), -1, np.int8)
eq = np.ones((n_paths, n_slot))
firm = np.full((n_paths, n_slot), -1, np.int8)
notional = np.zeros((n_paths, n_slot))
price_paid = np.zeros((n_paths, n_slot))
refunded = np.zeros((n_paths, n_slot), bool)
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
cum_payout = np.zeros(n_paths)
spent = np.zeros(n_paths)
got_back = np.zeros(n_paths)
deposited = np.zeros(n_paths)
n_payouts = np.zeros(n_paths, int)
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
for t in range(n_days):
died = rng.random((n_paths, len(FIRMS))) < p_death_day
newly_dead = died & firm_alive
firm_alive &= ~died
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
for k in range(n_slot):
free = phase[:, k] == -1
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = np.where(firm == fi, notional, 0.0).sum(axis=1)
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
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
free = free & ~buy
if not free.any():
break
if not free.any():
break
r = Rs[:, t, :]
g = G_[:, t, :]
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))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
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_RATE)
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
if deposit_eur:
cash += deposit_eur * EURUSD
deposited += deposit_eur * EURUSD
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
return dict(eur_day=eur_day, med=float(np.median(eur_day)),
p10=float(np.percentile(eur_day, 10)), p90=float(np.percentile(eur_day, 90)),
p_ge10=float((eur_day >= 10.0).mean()),
p_target=float((eur_day >= pl.TARGET_EUR_DAY).mean()),
p_zero=float((cum_payout <= 0).mean()),
p_payout=float((n_payouts > 0).mean()),
cash=cash, med_cash=float(np.median(cash)), mean_cash=float(cash.mean()),
p10_cash=float(np.percentile(cash, 10)), p90_cash=float(np.percentile(cash, 90)),
med_spent=float(np.median(spent)), mean_back=float(got_back.mean()),
contributed=float(start_eur * EURUSD + deposited.mean()))
def book_road(rb: np.ndarray, boot: np.ndarray, start_eur: float, deposit_eur: float,
deluck: float = DELUCK, tax: float = TAX_RATE) -> dict:
"""La strada ALTERNATIVA: gli stessi soldi restano sul LIBRO LIVE (proprio capitale, nessuna
barriera, nessuna firm) e si versa. Stessi sorteggi di bootstrap della strada-biglietto
(confronto APPAIATO), stesso de-luck, stesso orizzonte.
Fisco: 33% sulla variazione ANNUA di valore, con riporto delle minusvalenze (convenzione di
`r0807_piano_netto`; il libro realizza quasi tutto entro l'anno -> limite superiore stretto)."""
R = rb.astype(float).copy()
R = R - (1.0 - deluck) * R.mean()
r = R[boot]
n_paths, n_days = r.shape
cap = np.full(n_paths, start_eur * EURUSD)
basis = cap.copy()
carry = np.zeros(n_paths)
deposited = 0.0
for t in range(n_days):
cap = np.maximum(cap * (1.0 + r[:, t]), 0.0)
if (t + 1) % 30 == 0 and deposit_eur:
cap += deposit_eur * EURUSD
basis += deposit_eur * EURUSD
deposited += deposit_eur * EURUSD
if (t + 1) % 365 == 0:
g = (cap - basis) + carry
due = np.where(g > 0, tax * g, 0.0)
carry = np.where(g > 0, 0.0, g)
cap = cap - due
basis = cap.copy()
return dict(cash=cap, med_cash=float(np.median(cap)), mean_cash=float(cap.mean()),
p10_cash=float(np.percentile(cap, 10)), p90_cash=float(np.percentile(cap, 90)),
contributed=float(start_eur * EURUSD + deposited))
# ==================================================== 4. IL BIGLIETTO A CONTO SINGOLO, 2 STADI
def ticket_mc(R: np.ndarray, G: np.ndarray, lev_ev: float, lev_fu: float, notional: float,
n_paths: int = N_PATHS_TIX, seed: int = 82241, max_eval_days: int = 540,
funded_days: int = 365, min_payout: float = MIN_PAYOUT_TRADER,
consistency: bool = True, firm_death: bool = True, block: int = 20,
deluck: float = DELUCK, firm: str = HYRO) -> dict:
"""UN conto HYRO, i due stadi **CHAINED sullo stesso percorso** (eval -> funded), che e' la
differenza rispetto a `PA.eval_sim`/`PA.funded_sim`, che girano su bootstrap INDIPENDENTI e
moltiplicano le probabilita'.
Ritorna cio' che serve a prezzare il deposito come opzione:
p_pass = P(passare l'eval entro `max_eval_days`)
p_payout = P(arrivare ad ALMENO un payout) <- l'evento che rimborsa la quota
p_pay_given = P(payout | pass)
e_payout = E[payout netti], incondizionato, sui `funded_days` successivi al pass
"""
tgt, dd, dl = pl.FIRMS[[f["name"] for f in pl.FIRMS].index(firm)]["ev"]
ml, dlf = pl.FIRMS[[f["name"] for f in pl.FIRMS].index(firm)]["fu"]
rng = np.random.default_rng(seed)
Rd = R.astype(float) - (1.0 - deluck) * float(np.mean(R))
total = max_eval_days + funded_days
L = len(Rd)
nb = int(np.ceil(total / block))
st = rng.integers(0, L - block, size=(n_paths, nb))
bi = (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :total]
rr = Rd[bi]
gg = G[bi]
eq = np.ones(n_paths)
phase = np.zeros(n_paths, np.int8) # 0 eval, 1 funded, -1 morto, 2 finito
best_day = np.zeros(n_paths)
fu_day = np.zeros(n_paths, int)
payout = np.zeros(n_paths)
ever = np.zeros(n_paths, bool)
passed = np.zeros(n_paths, bool)
t_pass = np.full(n_paths, -1, int)
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0) if firm_death else 0.0
for t in range(total):
if p_death_day:
phase[(rng.random(n_paths) < p_death_day) & (phase >= 0)] = -1
alive = phase >= 0
if not alive.any():
break
in_ev = alive & (phase == 0)
in_fu = alive & (phase == 1)
lev = np.where(in_fu, lev_fu, lev_ev)
rt = rr[:, t] * lev
gt = gg[:, t] * lev
eqp = eq
eq = np.where(alive, eq * (1.0 + rt), eq)
eq_low = np.where(alive, eqp * (1.0 + rt + gt), eq)
r_low = rt + gt
bust = ((in_ev & ((eq_low < 1.0 - dd) | (r_low < -dl)))
| (in_fu & ((eq_low < 1.0 - ml) | (r_low < -dlf))))
phase[bust] = -1
gain = np.maximum(eq - eqp, 0.0)
best_day = np.where(in_ev & ~bust, np.maximum(best_day, gain), best_day)
profit = eq - 1.0
ok = in_ev & ~bust & (profit >= tgt)
if consistency:
ok &= best_day <= pl.FIRMS[0].get("consistency", 0.40) * np.maximum(profit, 1e-9)
phase[ok] = 1
eq[ok] = 1.0
fu_day[ok] = 0
passed |= ok
t_pass[ok] = t
still_ev = (phase == 0) & (t >= max_eval_days - 1)
phase[still_ev] = -1 # eval scaduta
now_fu = phase == 1
fu_day[now_fu & ~ok] += 1
chk = now_fu & (fu_day > 0) & (fu_day % 30 == 0)
pay = chk & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
payout += np.where(pay, (eq - 1.0) * notional * SPLIT * (1.0 - TAX_RATE), 0.0)
ever |= pay
eq = np.where(pay, 1.0, eq)
phase[now_fu & (fu_day >= funded_days)] = 2
return dict(p_pass=float(passed.mean()), p_payout=float(ever.mean()),
p_pay_given=float(ever.sum() / max(passed.sum(), 1)),
e_payout=float(payout.mean()),
e_payout_given=float(payout[ever].mean()) if ever.any() else 0.0,
t_pass=float(np.median(t_pass[passed])) if passed.any() else float("nan"))
def ev_three_ways(res: dict, price: float) -> dict:
"""Le tre convenzioni sul deposito, sullo STESSO Monte Carlo.
(i) SPESA PERSA — la quota non torna mai (`pc.simulate`, il numero operativo)
(ii) RIMBORSO AL PASS — la quota torna se passi (`r0725_hyro:200`)
(iii) RIMBORSO AL PAYOUT — la quota torna col primo prelievo (la regola VERA della firm)
"""
e, pp, pa = res["e_payout"], res["p_pass"], res["p_payout"]
return dict(sunk=e - price,
at_pass=e - price * (1.0 - pp),
at_payout=e - price * (1.0 - pa),
p_break_even=float(np.clip((price - e) / price, 0.0, 1.0)) if price else 0.0)
# ==================================================================================== main
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" GATE PROP-01 gamba (c) — il BIGLIETTO come OPZIONE, il FUNDING dentro, e il")
print(" confronto con l'alternativa ovvia: tenere i $635 sul libro e versare")
print("=" * 104)
print(" PREVISIONE REGISTRATA PRIMA: (1) il rimborso vale +5-15% di EV e +1-3pp su")
print(" P(>=50/g); (2) P(primo payout | pass) > 80%; (3) il funding costa ~0,75 x 2,16%")
print(" su un funded a 0,75x; (4) sul segno del funding di XS01 NON ho una previsione,")
print(" le due forze tirano in verso opposto; (5) EV positivo sulla MEDIA e negativo")
print(" sulla MEDIANA — una lotteria, non un investimento.")
# ============================================================== §0.1 riproduzione r0725_hyro
hr("§0.1 RIPRODUZIONE — `r0725_hyro` (25/07): l'EV del biglietto come fu pubblicato")
buf = io.StringIO()
t = time.time()
import r0725_hyro as HY # noqa: E402
with contextlib.redirect_stdout(buf):
HY.main()
txt = buf.getvalue()
lens_now, ev_now = None, {}
for line in txt.splitlines():
if "[close-only]" in line:
lens_now = "close"
elif "[intraday]" in line:
lens_now = "intra"
m = re.match(r"\s*\$\s*100,000\s+\$\s*([\d,]+)\s+\$\s*([\d,]+)\s+\$\s*([\d,]+)\s+"
r"\$\s*([\-\d,]+)", line)
if m and lens_now:
ev_now[lens_now] = float(m.group(4).replace(",", ""))
print(f" rigirato oggi ({time.time()-t:.0f}s), stessa macchina, stessi semi, biglietto $100k:")
print(f" {'lente':>14} {'EV oggi':>12} {'EV PUBBLICATO 25/07':>22} {'delta':>10}")
for k, lab, p in (("close", "close-only", PUB["hyro_ev_close"]),
("intra", "intraday", PUB["hyro_ev_intra"])):
v = ev_now.get(k, float("nan"))
print(f" {lab:>14} ${v:>11,.0f} ${p:>21,.0f} {v-p:>+10,.0f}")
print(" Lo scarto NON e' un errore di riproduzione: `r0725_hyro` ri-deriva le proprie serie")
print(" dal feed CORRENTE, e dal 25/07 a oggi il feed ha ~4 settimane di barre in piu'.")
print(" Ordine di grandezza e conclusione identici -> la macchina e' quella.")
print("\n ⚠ E il codice riprodotto contiene gia' la contraddizione: `cost = price*(1-pe)`")
print(" = quota RIMBORSATA AL PASS, mentre `pc.simulate` (da cui esce il numero")
print(" operativo P(>=50/g)) fa `cash -= price` e non la restituisce MAI.")
bump(1)
tick("§0.1")
# ============================================================== dati comuni
hr("§0.2 IL PANNELLO — le stesse serie di PROP-RECAL (U13 Binance, W-LONG 2021+)")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13 = Ob.reindex(p13.index)[X.U13]
h13 = Hb.reindex(p13.index)[X.U13]
l13 = Lb.reindex(p13.index)[X.U13]
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES_SUB}
bump(len(PHASES_SUB))
xs0 = XSP[PHASES_SUB[0]]
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base = cry_idx.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
S_LONG = max(first13, pd.Timestamp("2021-01-01", tz="UTC"))
I = base[base >= S_LONG]
ev_b = PA.boot_idx(len(I), EV_H, N_PATHS_J, seed=SEED_EV)
fu_b = PA.boot_idx(len(I), FU_H, N_PATHS_J, seed=SEED_FU)
print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | de-luck x{DELUCK} | "
f"{N_PATHS_J} percorsi per J")
pc.DELUCK = DELUCK
# J all'ancora canonica, mediana sulle 3 fasi dichiarate
js, jlv = [], []
for ph in PHASES_SUB:
Rc, Gc = AP.rg_off(W_A, I, XSP[ph], 0)
j, lv, _ = best_lev(Rc, Gc, ev_b, fu_b)
js.append(j); jlv.append(lv)
RL0, GL0 = AP.rg_off(W_LIVE, I, xs0, 0)
jL, lvL, vL0 = best_lev(RL0, GL0, ev_b, fu_b)
dpair = []
for ph in PHASES_SUB:
Rc, Gc = AP.rg_off(W_A, I, XSP[ph], 0)
_, _, vc = best_lev(Rc, Gc, ev_b, fu_b)
dpair.append(float((vc - vL0).mean()))
print(f" J(W_A 38/38/25) ancora canonica, fasi {PHASES_SUB}: mediana {np.median(js):.3f} "
f"(PUB {PUB['recal_j']:.3f} su 10 fasi) | leve {jlv}")
print(f" J(LIVE 75/25) : {jL:.3f} @{lvL:.2f}x "
f"(PUB {PUB['recal_live']:.3f})")
print(f" delta appaiato mediano : {np.median(dpair):+.3f} "
f"(PUB {PUB['recal_d']:+.3f})")
tick("§0.2")
# ============================================================== §1 il funding
hr("§1. IL FUNDING, MESSO DENTRO — e il segno su XS01, MISURATO")
rate = funding_rate()
if rate is None:
print(" Senza cache non si mette dentro nulla: il resto gira SENZA funding e lo dichiara.")
xs_ann = 0.0
else:
fb0 = FD.book_75_25(None, alloc=654.0)
fb1 = FD.book_75_25(rate, alloc=654.0)
d_book = float((fb0 - fb1).dropna().mean() * 365) * 100
ftp = f_tp01_sleeve(rate)
fsk = f_skh_sleeve(rate, 0)
c0, _ = FD.tp01_canonical(None)
c1, _ = FD.tp01_canonical(rate)
d_tp = float((c0 - c1).dropna().mean() * 365) * 100
print(f" RIPRODUZIONE dei costi di §35 (%/anno, segno = costo):")
print(f" {'serie':>36} {'oggi':>9} {'PUB §35':>9}")
print(f" {'LIBRO 75/25 path live':>36} {-d_book:>8.2f}% {PUB['fund_book']:>8.2f}%")
print(f" {'TP01 canonico':>36} {-d_tp:>8.2f}% {PUB['fund_tp']:>8.2f}%")
print(f" {'SKH01 50/50 path live (off 0)':>36} "
f"{-float(fsk.mean()*365)*100:>8.2f}% {PUB['fund_skh']:>8.2f}%")
print(f" {'TP01 gambe orarie (lente prop)':>36} "
f"{-float(ftp.reindex(I).mean()*365)*100:>8.2f}% {'—':>9}")
print("\n ⚠ La riga 'gambe orarie' non e' la stessa di 'TP01 canonico': il recon orario")
print(" usa `mc.tp01_daily_frame` (senza min-order, che e' la lente giusta su un")
print(" conto funded da $100k). Le due lenti differiscono di poco e si dichiarano.")
print("\n IL SEGNO DEL FUNDING SU XS01 — misurato, non assunto:")
xsf = xs_funding_measure()
xs_ann = xsf["ann"]
print(f"\n >>> XS01 **PAGA** {xs_ann*100:+.2f}%/anno di funding (segno positivo = costo).")
print(" La previsione registrata ('non lo so, le due forze tirano in verso opposto')")
print(" si risolve cosi': il momentum cross-sezionale COMPRA i perp col funding piu'")
print(" caro e VENDE quelli col funding piu' basso -> paga la dispersione.")
print(" Essendo `sum(W)=0` il LIVELLO non entra: entra solo la DISPERSIONE.")
print(f" Se il funding Hyperliquid e' ~2,1x quello del venue eseguibile (§35),")
print(f" la banda onesta e' [{xs_ann/2.1*100:+.2f}%, {xs_ann*100:+.2f}%]/anno.")
print("\n LA LEVA — perche' il funding non e' un costo fisso:")
print(" il funding e' proporzionale all'ESPOSIZIONE, e l'esposizione di un conto funded")
print(" e' `lev x` quella del libro -> nel simulatore `Rs = R*lev` porta dentro anche il")
print(" costo, che quindi scala. Verifica numerica (libro W_A, costo %/anno di equity):")
fA = fvec_for(W_A, I, rate, 0, xs_ann)
for lv in (0.50, 0.75, 1.00, 1.25):
print(f" leva {lv:>4.2f}x -> {-float(np.mean(fA))*365*100*lv:>6.2f}%/anno "
f"(= {lv:.2f} x {-float(np.mean(fA))*365*100:.2f}%)")
tick("§1")
# ============================================================== §2 banda d'ancora
hr("§2. LA BANDA D'ANCORA — 23 offset x 3 fasi, con e SENZA funding (riproduzione + delta)")
print(f" Sottocampione dichiarato, identico a `r0822c`: {len(OFFSETS)} offset x "
f"{len(PHASES_SUB)} fasi {PHASES_SUB} = {len(OFFSETS)*len(PHASES_SUB)} celle, "
f"x2 (funding on/off).")
print(f" Ladder: {N_PATHS_LAD} percorsi (risoluzione ~+-1pp su P(>=50/g)), lente ACCOPPIATA,")
print(f" {pl.MAX_CONCURRENT} conti max, morte-firm {pl.FIRM_DEATH_PER_YEAR:.0%}/anno, "
f"deposito NON rimborsato (= convenzione di `pc.simulate`, per riprodurre).")
cells: list[dict] = []
t = time.time()
for off in OFFSETS:
fA = fvec_for(W_A, I, rate, off, xs_ann)
for ph in PHASES_SUB:
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
for tag, Rx in (("nofund", R0), ("fund", R0 - fA if fA is not None else None)):
if Rx is None:
continue
j, lv, _ = best_lev(Rx, G0, ev_b, fu_b)
lad = ladder(Rx, G0, N_PATHS_LAD, lv, SEED_LAD, refund=False)
cells.append(dict(off=off, ph=ph, tag=tag, J=j, lev=lv,
p50=lad["p_target"], pz=lad["p_zero"], med=lad["med"],
ppay=lad["p_payout"]))
bump(2)
C = pd.DataFrame(cells)
print(f" ({time.time()-t:.0f}s)")
print(f"\n {'variante':>12} {'J mediana':>10} {'P(>=50/g)':>10} {'banda p10-p90':>18} "
f"{'P(zero)':>9} {'EUR/g med':>10}")
for tag, lab in (("nofund", "SENZA funding"), ("fund", "CON funding")):
s = C[C["tag"] == tag]
if not len(s):
continue
print(f" {lab:>12} {s['J'].median():>10.3f} {s['p50'].median():>10.1%} "
f"[{np.percentile(s['p50'],10):>7.1%},{np.percentile(s['p50'],90):>7.1%}] "
f"{s['pz'].median():>9.1%} {s['med'].median():>10.2f}")
s_nf = C[C["tag"] == "nofund"]
print(f"\n RIPRODUZIONE §32: P(>=50/g) mediana {s_nf['p50'].median():.1%} "
f"(PUB {PUB['anchor_p50g']:.1%}) | banda "
f"[{np.percentile(s_nf['p50'],10):.1%}, {np.percentile(s_nf['p50'],90):.1%}] "
f"(PUB [{PUB['anchor_p10']:.1%}, {PUB['anchor_p90']:.1%}]) | "
f"P(zero) {s_nf['pz'].median():.1%} (PUB {PUB['anchor_pzero']:.1%})")
if rate is not None:
s_f = C[C["tag"] == "fund"]
m = C.pivot_table(index=["off", "ph"], columns="tag", values="p50")
dpp = (m["fund"] - m["nofund"])
mj = C.pivot_table(index=["off", "ph"], columns="tag", values="J")
dj = (mj["fund"] - mj["nofund"])
print(f"\n IL FUNDING, appaiato cella per cella (mediana delle DIFFERENZE, non")
print(f" differenza delle mediane): dJ {dj.median():+.4f} "
f"({int((dj<0).sum())}/{len(dj)} celle negative) | "
f"dP(>=50/g) {dpp.median()*100:+.2f}pp "
f"({int((dpp<0).sum())}/{len(dpp)} negative)")
print(f" Il numero operativo col funding dentro: **P(>=50 EUR/g) "
f"{s_f['p50'].median():.1%}** [{np.percentile(s_f['p50'],10):.1%}, "
f"{np.percentile(s_f['p50'],90):.1%}], P(zero) {s_f['pz'].median():.1%}")
# cella d'ancora MEDIANA su cui si prezza il biglietto
ref_tag = "fund" if rate is not None else "nofund"
S = C[C["tag"] == ref_tag].reset_index(drop=True)
k_med = int((S["p50"] - S["p50"].median()).abs().idxmin())
OFF_M, PH_M, LEV_M = int(S.loc[k_med, "off"]), int(S.loc[k_med, "ph"]), float(S.loc[k_med, "lev"])
print(f"\n CELLA D'ANCORA MEDIANA scelta per prezzare il biglietto: offset {OFF_M} min, "
f"fase {PH_M}, leva {LEV_M:.2f}x")
print(" (non l'ancora canonica: quella e' al 91° pctl dei 23 offset — §32)")
fM = fvec_for(W_A, I, rate, OFF_M, xs_ann)
R_M0, G_M = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)
R_M = R_M0 - fM if fM is not None else R_M0
tick("§2")
# ============================================================== §3 il biglietto a 2 stadi
hr("§3. IL BIGLIETTO COME OPZIONE — i DUE stadi, sullo stesso percorso")
print(" Stadio 1: passare l'eval (target 10%, maxDD 6% STATICO, daily 4%, consistency 40%).")
print(" Stadio 2: sul funded, arrivare ad ALMENO un payout — che e' l'evento che")
print(" rimborsa il deposito. Soglia: quota trader >= $100 (fonte dichiarativa della firm).")
print(f" {N_PATHS_TIX} percorsi, blocchi di 20 giorni, morte-firm dentro, lente accoppiata.")
rows = []
for lev_ev in (0.50, 0.75, 1.00):
for lev_fu in (0.50, 0.75, 1.00):
r = ticket_mc(R_M, G_M, lev_ev=lev_ev, lev_fu=lev_fu, notional=100_000.0)
rows.append(dict(lev_ev=lev_ev, lev_fu=lev_fu,
**{k: r[k] for k in
("p_pass", "p_payout", "p_pay_given", "e_payout",
"e_payout_given", "t_pass")}))
bump(1)
T = pd.DataFrame(rows)
print(f"\n {'leva eval':>10} {'leva fund':>10} {'P(pass)':>9} {'P(payout)':>10} "
f"{'P(pay|pass)':>12} {'E[payout]':>11} {'E[pay|pay]':>12} {'gg al pass':>11}")
for _, x in T.iterrows():
print(f" {x['lev_ev']:>10.2f} {x['lev_fu']:>10.2f} {x['p_pass']:>9.1%} "
f"{x['p_payout']:>10.1%} {x['p_pay_given']:>12.1%} ${x['e_payout']:>10,.0f} "
f"${x['e_payout_given']:>11,.0f} {x['t_pass']:>11.0f}")
best = T.loc[T["e_payout"].idxmax()]
LEV_EV, LEV_FU = float(best["lev_ev"]), float(best["lev_fu"])
print(f"\n Leve che massimizzano E[payout]: eval {LEV_EV:.2f}x / funded {LEV_FU:.2f}x")
print(f" (per confronto, la leva che massimizza J alla stessa cella e' {LEV_M:.2f}x: J e'")
print(" P(pass) x P(vivo), che NON e' E[payout] — l'ottimo non deve coincidere)")
pg = T["p_pay_given"]
print(f" >>> PREVISIONE (2) 'P(primo payout | pass) > 80%': vale in "
f"{int((pg > 0.80).sum())}/{len(pg)} celle (banda {pg.min():.1%}-{pg.max():.1%}) -> "
f"{'CONFERMATA salvo a leva funded 1.00x' if (pg > 0.80).sum() >= 6 else 'SMENTITA'}")
print(" Il verso e' meccanico e vale la pena dirlo: piu' leva sul funded alza")
print(" E[payout] e ABBASSA P(payout), perche' fa bustare prima del primo prelievo.")
print(" Sono due obiettivi diversi: chi vuole il RIMBORSO non vuole la leva massima.")
hr("§3.1 LE TRE CONVENZIONI SUL DEPOSITO — stesso Monte Carlo, tre economie")
print(" (i) SPESA PERSA `pc.simulate` -> EV = E[payout] - prezzo")
print(" (ii) RIMBORSO AL PASS `r0725_hyro` -> EV = E[payout] - prezzo x (1 - P(pass))")
print(" (iii)RIMBORSO AL PAYOUT la firm -> EV = E[payout] - prezzo x (1 - P(payout))")
print(f"\n {'biglietto':>12} {'prezzo':>8} {'E[payout]':>11} {'(i) persa':>11} "
f"{'(ii) al pass':>13} {'(iii) al payout':>16} {'EUR/g atteso':>13}")
tix_rows = []
for size, price in pl.FIRMS[0]["tickets"]:
r = ticket_mc(R_M, G_M, lev_ev=LEV_EV, lev_fu=LEV_FU, notional=float(size))
e3 = ev_three_ways(r, price)
tix_rows.append(dict(size=size, price=price, **r, **e3))
print(f" ${size:>11,} ${price:>7,.0f} ${r['e_payout']:>10,.0f} ${e3['sunk']:>10,.0f} "
f"${e3['at_pass']:>12,.0f} ${e3['at_payout']:>15,.0f} "
f"{e3['at_payout']/EURUSD/365:>13.2f}")
bump(1)
TX = pd.DataFrame(tix_rows)
big = TX.iloc[-1]
rJ = ticket_mc(R_M, G_M, lev_ev=LEV_M, lev_fu=LEV_M, notional=100_000.0)
eJ = ev_three_ways(rJ, 579.0)
bump(1)
print(f"\n Lo STESSO biglietto $100k alla leva che massimizza J ({LEV_M:.2f}x su entrambi")
print(" gli stadi, cioe' la leva con cui e' costruito tutto il resto di questo filone):")
print(f" P(pass) {rJ['p_pass']:.1%} | P(payout) {rJ['p_payout']:.1%} | "
f"E[payout] ${rJ['e_payout']:,.0f} | EV persa ${eJ['sunk']:+,.0f} | "
f"EV al payout ${eJ['at_payout']:+,.0f}")
print(" -> l'EV e' positivo a ENTRAMBE le leve: la conclusione non dipende da quale")
print(" obiettivo (J o E[payout]) si sceglie per tararla.")
d_conv = big["at_payout"] - big["sunk"]
print(f"\n Quanto vale la CORREZIONE (da 'spesa persa' a 'rimborso al payout'), $100k: "
f"${d_conv:+,.0f} = {d_conv/max(abs(big['sunk']),1e-9)*100:+.1f}% dell'EV.")
print(f" Quanto separa le due convenzioni SBAGLIATE fra loro (al pass vs al payout): "
f"${big['at_pass']-big['at_payout']:+,.0f} — cioe' prezzo x (P(pass)-P(payout)) = "
f"${big['price']*(big['p_pass']-big['p_payout']):+,.0f}.")
print(f" >>> PREVISIONE (1) 'il rimborso vale +5-15% di EV': "
f"{'CONFERMATA' if 0.05 <= d_conv/max(abs(big['sunk']),1e-9) <= 0.15 else 'SMENTITA'}")
print(f"\n SENSIBILITA': con E[payout] = ${big['e_payout']:,.0f} e prezzo ${big['price']:,.0f},")
print(f" l'EV al payout resta positivo finche' P(payout) > "
f"{max(0.0,(big['price']-big['e_payout'])/big['price']):.1%} — misurata "
f"{big['p_payout']:.1%}.")
print(" ⚠ E' un EV in DOLLARI ATTESI. Su un conto da $654 il biglietto si compra poche")
print(" volte: la media non e' la grandezza che decide. Si guarda la distribuzione (§5).")
print(" ⚠ RICONCILIAZIONE (tre P(payout) diverse compaiono in questo file, e NON sono in")
print(" contraddizione): qui e' UN biglietto su UN anno di funded; la scala di conti")
print(" (§4-§6) lascia correre 36 mesi e ricompra -> la sua P e' piu' alta. Confrontare")
print(" le due senza dire la finestra e' l'errore che le farebbe sembrare incoerenti.")
tick("§3")
# ============================================================== §4 ladder col rimborso
hr("§4. LA SCALA DI CONTI COL DEPOSITO RIMBORSABILE")
print(" §4.1 CONTROLLO — con `refund=False, deposito=0, min_payout=0` il mio simulatore")
print(" dev'essere BIT-EXACT rispetto a `pc.simulate` (stesso seme, stesso stream).")
cfg = tuple(W_A.get(n, 0.0) for n in PA.CRYPTO_PROP)
key = (cfg[0], cfg[1], cfg[2], "shared")
saved = pc._TUP_CACHE.pop(key, None)
pc._TUP_CACHE[key] = (R_M, G_M, I)
ref = pc.simulate([cfg], [0] * pl.MAX_CONCURRENT, n_paths=N_PATHS_LAD, lev=LEV_M,
seed=SEED_LAD, lens="coupled", start=str(I[0].date()))
pc._TUP_CACHE.pop(key, None)
if saved is not None:
pc._TUP_CACHE[key] = saved
mine = ladder(R_M, G_M, N_PATHS_LAD, LEV_M, SEED_LAD, refund=False)
dmax = float(np.abs(np.sort(ref["eur_day"]) - np.sort(mine["eur_day"])).max())
dmax2 = float(np.abs(ref["eur_day"] - mine["eur_day"]).max())
print(f" max|diff| percorso per percorso = {dmax2:.3e} (ordinati: {dmax:.3e}) -> "
f"{'IDENTICO' if dmax2 < 1e-12 else 'DIVERGE — STOP'}")
if dmax2 >= 1e-12:
print(" Il simulatore non replica: i numeri sotto sarebbero di un'altra macchina.")
return
bump(1)
print("\n §4.2 QUANTO VALE IL RIMBORSO (cella d'ancora mediana, stessi semi)")
print(f" {'convenzione':>34} {'EUR/g med':>10} {'P(>=10/g)':>10} {'P(>=50/g)':>10} "
f"{'P(zero)':>9} {'cassa fin. med':>15} {'media':>10}")
lad_rows = {}
for tag, rf, mp in (("spesa persa (pubblicato)", False, 0.0),
("spesa persa + soglia $100", False, MIN_PAYOUT_TRADER),
("RIMBORSO al primo payout", True, MIN_PAYOUT_TRADER)):
r = ladder(R_M, G_M, N_PATHS_LAD, LEV_M, SEED_LAD, refund=rf, min_payout=mp)
lad_rows[tag] = r
print(f" {tag:>34} {r['med']:>10.2f} {r['p_ge10']:>10.1%} {r['p_target']:>10.1%} "
f"{r['p_zero']:>9.1%} ${r['med_cash']:>14,.0f} ${r['mean_cash']:>9,.0f}")
bump(1)
a = lad_rows["spesa persa + soglia $100"]
b = lad_rows["RIMBORSO al primo payout"]
print(f"\n Delta del rimborso: P(>=50/g) {(b['p_target']-a['p_target'])*100:+.2f}pp | "
f"EUR/g mediana {b['med']-a['med']:+.2f} | cassa finale mediana "
f"${b['med_cash']-a['med_cash']:+,.0f} | quota restituita media ${b['mean_back']:,.0f}")
print(f" >>> PREVISIONE (1) 'il rimborso vale +1-3pp su P(>=50/g)': "
f"{'CONFERMATA' if 0.01 <= (b['p_target']-a['p_target']) <= 0.03 else 'SMENTITA'} "
f"({(b['p_target']-a['p_target'])*100:+.2f}pp)")
tick("§4")
# ============================================================== §5 biglietto vs versare
hr("§5. IL BIGLIETTO CONTRO IL VERSARE — stessa metrica, stesso orizzonte, stessi sorteggi")
print(" Strada A (BIGLIETTO): $654 comprano il biglietto piu' grande possibile; la scala di")
print(" conti gira il libro W_A 38/38/25 sul conto della firm (XS01 LI' E' ESEGUIBILE),")
print(" deposito rimborsabile, morte-firm, cap $200k/firm; la cassa resta FERMA.")
print(" Strada B (LIBRO): gli stessi $654 restano sul libro LIVE 75/25 a min-order $5")
print(" (XS01 NON e' eseguibile a $654), nessuna barriera, fisco 33% sull'anno.")
print(" ⚠ L'asimmetria fra le due strade e' REALE ed e' il punto del canale funded: il")
print(" conto della firm da' accesso a sleeve che il capitale proprio non puo' eseguire.")
rb_full = FD.book_75_25(rate, alloc=654.0) if rate is not None else FD.book_75_25(None, 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"\n libro LIVE a $654 (funding {'DENTRO' if rate is not None else 'FUORI'}): "
f"drift {rb.mean()*365*100:.2f}%/anno, vol {rb.std()*np.sqrt(365)*100:.1f}%, "
f"Sharpe {rb.mean()*365/(rb.std()*np.sqrt(365)):.2f} su {len(rb)} giorni")
rngb = np.random.default_rng(51224)
nb = int(np.ceil(HORIZON_DAYS / 20))
st = rngb.integers(0, len(I) - 20, size=(N_PATHS_LAD, nb))
BOOT = (st[:, :, None] + np.arange(20)[None, None, :]).reshape(N_PATHS_LAD, -1)[:, :HORIZON_DAYS]
print(f" bootstrap CONDIVISO fra le due strade: {N_PATHS_LAD} percorsi x {HORIZON_DAYS} "
f"giorni, blocchi 20 -> confronto APPAIATO")
print(f"\n {'versamento':>12} {'strada':>10} {'mediana':>11} {'p10':>10} {'p90':>10} "
f"{'media':>11} {'P(sotto il versato)':>20} {'P(>=50/g)':>10}")
cmp_rows = []
for dep in (0.0, 250.0, 500.0):
A = ladder(R_M, G_M, N_PATHS_LAD, LEV_M, SEED_LAD, refund=True,
deposit_eur=dep, min_payout=MIN_PAYOUT_TRADER, boot=BOOT)
B = book_road(rb, BOOT, pl.START_EUR, dep)
bump(2)
for lab, r in (("BIGLIETTO", A), ("LIBRO", B)):
p_loss = float((r["cash"] < r["contributed"]).mean())
p50g = r.get("p_target", 0.0)
print(f" {f'EUR {dep:,.0f}/mese':>12} {lab:>10} ${r['med_cash']:>10,.0f} "
f"${r['p10_cash']:>9,.0f} ${r['p90_cash']:>9,.0f} ${r['mean_cash']:>10,.0f} "
f"{p_loss:>20.1%} {p50g:>10.1%}")
cmp_rows.append(dict(dep=dep, road=lab, med=r["med_cash"], mean=r["mean_cash"],
p10=r["p10_cash"], p90=r["p90_cash"], ploss=p_loss))
print(f" {'':>12} {'delta':>10} ${A['med_cash']-B['med_cash']:>+10,.0f} "
f"{'':>10} {'':>10} ${A['mean_cash']-B['mean_cash']:>+10,.0f}")
CMP = pd.DataFrame(cmp_rows)
print("\n ('P(>=50/g)' e' definita solo per la strada BIGLIETTO: la strada LIBRO non preleva")
print(" durante l'accumulo. La colonna che si confronta e' la RICCHEZZA TERMINALE.)")
tick("§5")
# ============================================================== §6 la curva del capitale
hr("§6. LA CURVA DEL CAPITALE — a che conto il biglietto smette di essere la strada migliore")
print(" Stesso confronto appaiato, capitale iniziale variabile, nessun versamento (cosi' la")
print(" sola variabile e' il capitale). La strada LIBRO resta il 75/25 a ogni taglia:")
print(" e' una scelta CONSERVATIVA CONTRO la strada libro (a $5k/$20k il libro potrebbe")
print(" aggiungere XSR01/XS01) -> il capitale d'incrocio misurato e' un LIMITE SUPERIORE.")
cheap = min(t[1] for F in pl.FIRMS for t in F["tickets"])
print(f"\n {'capitale':>10} {'A mediana':>12} {'B mediana':>12} {'A media':>12} "
f"{'B media':>12} {'A P(0 pay)':>11} {'A P(<${:.0f})'.format(cheap):>12} "
f"{'A med|>0':>10} {'vince med.':>11}")
caps = (600.0, 1_000.0, 2_000.0, 3_500.0, 6_000.0, 10_000.0, 20_000.0, 40_000.0)
cross_med = cross_mean = None
prev = None
for ce in caps:
A = ladder(R_M, G_M, N_PATHS_LAD, LEV_M, SEED_LAD, refund=True, start_eur=ce,
min_payout=MIN_PAYOUT_TRADER, boot=BOOT)
B = book_road(rb, BOOT, ce, 0.0)
bump(2)
wm = "BIGLIETTO" if A["med_cash"] > B["med_cash"] else "LIBRO"
nz = A["cash"][A["cash"] >= cheap]
p_stuck = float((A["cash"] < cheap).mean())
print(f" EUR{ce:>7,.0f} ${A['med_cash']:>11,.0f} ${B['med_cash']:>11,.0f} "
f"${A['mean_cash']:>11,.0f} ${B['mean_cash']:>11,.0f} {A['p_zero']:>11.1%} "
f"{p_stuck:>12.1%} ${np.median(nz) if len(nz) else 0:>9,.0f} {wm:>11}")
cur = (A["med_cash"] > B["med_cash"], A["mean_cash"] > B["mean_cash"])
if prev is not None:
if prev[0] != cur[0] and cross_med is None:
cross_med = (prev[2], ce)
if prev[1] != cur[1] and cross_mean is None:
cross_mean = (prev[2], ce)
prev = (cur[0], cur[1], ce)
print(f"\n Incrocio sulla MEDIANA: {cross_med if cross_med else 'nessuno nella griglia'}")
print(f" Incrocio sulla MEDIA : {cross_mean if cross_mean else 'nessuno nella griglia'}")
print("\n ⚠ IL 'GRADINO' FRA EUR 1.000 E EUR 2.000 NON E' UN MECCANISMO: e' la MEDIANA")
print(" che cambia MODO. La distribuzione della strada-biglietto e' BIMODALE — o si")
print(" finisce SENZA nemmeno i soldi per ricomprare il biglietto piu' economico, o si")
print(f" finisce con migliaia. La colonna `P(<${cheap:.0f})` e' la massa del modo basso e")
print(" si muove LISCIA; la mediana complessiva salta di un ordine di grandezza appena")
print(" quella massa attraversa il 50%. **La mediana non e' una statistica robusta qui**:")
print(" quelle robuste sono `P(<$249)`, `P(0 payout)` e la media, che SATURANO.")
print(" -> il 'capitale d'incrocio' e' il punto in cui una massa di probabilita' passa")
print(" il 50%, NON una soglia con un meccanismo dietro. Va detto, perche' un numero")
print(" cosi' netto e' esattamente quello che si finirebbe per citare come soglia.")
print("\n §6.1 LA SECONDA FIRM — l'unico ingrediente STRUTTURALE che cambia con la cassa")
print(" (FTMO: max loss 10% contro il 6% STATICO di HYRO — il vincolo che il")
print(" progetto misura come binding su questo libro fin dal 25/07).")
print(" Si legge su MEDIA e P(0 payout), non sulla mediana (vedi sopra).")
all_firms = pl.FIRMS
print(f"\n {'capitale':>10} {'firm ammesse':>16} {'mediana':>11} {'media':>11} "
f"{'P(0 payout)':>11} {'cassa med':>11}")
for ce in (1_000.0, 2_000.0, 3_500.0):
for lab, fl in (("solo HYRO", [all_firms[0]]), ("HYRO+FTMO", all_firms)):
pl.FIRMS = fl
try:
A = ladder(R_M, G_M, N_PATHS_LAD, LEV_M, SEED_LAD, refund=True, start_eur=ce,
min_payout=MIN_PAYOUT_TRADER, boot=BOOT)
finally:
pl.FIRMS = all_firms
bump(1)
print(f" EUR{ce:>7,.0f} {lab:>16} ${A['med_cash']:>10,.0f} "
f"${A['mean_cash']:>10,.0f} {A['p_zero']:>11.1%} ${np.median(A['cash']):>10,.0f}")
print("\n Lettura: la seconda firm ALZA la media (piu' nozionale, e se una firm muore la")
print(" cassa si ricicla sull'altra) e puo' ALZARE anche P(0 payout) al capitale piccolo,")
print(" perche' converte in biglietti la cassa che altrimenti sarebbe rimasta ferma.")
print(" Non e' un pasto gratis: e' piu' esposizione allo stesso rischio binario.")
tick("§6")
# ============================================================== §7 verdetto
hr("§7. LA GAMBA (c), RIFORMULATA — e la risposta alla domanda che decide")
ev_pay = float(big["at_payout"])
ev_sunk = float(big["sunk"])
A0 = [r for r in cmp_rows if r["dep"] == 0.0 and r["road"] == "BIGLIETTO"][0]
B0 = [r for r in cmp_rows if r["dep"] == 0.0 and r["road"] == "LIBRO"][0]
print(f" EV del biglietto $100k alla cella d'ancora MEDIANA, col funding dentro:")
print(f" convenzione VERA (rimborso al primo payout) ${ev_pay:>+10,.0f}")
print(f" convenzione PESSIMISTA (spesa persa) ${ev_sunk:>+10,.0f}")
print(f" -> EV positivo in {'ENTRAMBE' if min(ev_pay, ev_sunk) > 0 else 'UNA SOLA' if max(ev_pay,ev_sunk)>0 else 'NESSUNA'} "
f"le convenzioni.")
print(f"\n A 36 mesi, senza versamenti, $654 iniziali (confronto appaiato):")
print(f" BIGLIETTO mediana ${A0['med']:,.0f} media ${A0['mean']:,.0f} "
f"p10 ${A0['p10']:,.0f} P(sotto il versato) {A0['ploss']:.1%}")
print(f" LIBRO mediana ${B0['med']:,.0f} media ${B0['mean']:,.0f} "
f"p10 ${B0['p10']:,.0f}")
med_win = "BIGLIETTO" if A0["med"] > B0["med"] else "LIBRO"
mean_win = "BIGLIETTO" if A0["mean"] > B0["mean"] else "LIBRO"
print(f" -> sulla MEDIANA vince {med_win}; sulla MEDIA vince {mean_win}.")
print(" (e la mediana qui e' una statistica BIMODALE: dice da che parte del 50% sta")
print(" P(0 payout), non 'quanto' si guadagna — vedi §6)")
print(f" >>> PREVISIONE (5) 'EV positivo sulla MEDIA e negativo sulla MEDIANA': "
f"{'CONFERMATA' if (mean_win=='BIGLIETTO' and med_win=='LIBRO') else 'SMENTITA'}")
print("\n ONESTA' OBBLIGATORIE, portate avanti e non nascoste:")
print(f" * morte-firm {pl.FIRM_DEATH_PER_YEAR:.0%}/anno e' un'ASSUNZIONE del progetto")
print(" (24/07), non una misura: sopra c'e' dentro, ma nessuno l'ha stimata.")
print(" * la RIMBORSABILITA' e' DICHIARATIVA: viene dalla pagina della firm + fonti")
print(" secondarie concordi. Nessuno l'ha vista accadere. Il progetto ha gia' pagato")
print(" per un'assunzione mai verificata sul conto reale (GTAA01/PRIIPs).")
print(f" * il cap ${pl.FIRMS[0]['cap']:,}/trader limita STRUTTURALMENTE il canale: il")
print(" nozionale massimo su HYRO+FTMO e' $600k, cioe' un tetto al reddito, non al rischio.")
print(" * XS01 sul conto funded e' misurato su U13 fuori campione, ma le 6 gambe che")
print(" fuori campione non esistono restano non testabili (§29).")
print(" * l'ancora GIORNALIERA di TP01 (24 ore) NON e' in questa banda: terza dimensione,")
print(" dichiarata anche da §32, e la sua fortuna e' ancora dentro questi numeri.")
hr("§7.1 IL TESTO PROPOSTO DELLA GAMBA (c)")
price = float(big["price"])
p_nopay = 1.0 - float(big["p_payout"])
print(f" Quantita' misurate oggi, a costo ZERO (una corsa di questo script):")
print(f" prezzo del biglietto $100k ${price:>10,.0f}")
print(f" quota del conto ($654) immobilizzata {price/(pl.START_EUR*EURUSD):>10.1%}")
print(f" P(nessun payout) -> quota persa {p_nopay:>10.1%}")
print(f" PERDITA ATTESA = prezzo x P(nessun payout) ${price*p_nopay:>10,.0f}")
print(f" EV con deposito NON rimborsato (pessimista) ${ev_sunk:>+10,.0f}")
print(f" EV con deposito rimborsato al payout (vero) ${ev_pay:>+10,.0f}")
print(f"\n Mesi di piano che il biglietto mette a rischio (quota / versamento mensile):")
for dep in (250.0, 500.0, 800.0):
print(f" a EUR {dep:>4,.0f}/mese: {price/(dep*EURUSD):>4.1f} mesi di versamenti "
f"(perdita attesa = {price*p_nopay/(dep*EURUSD):.1f} mesi)")
print("\n IL PUNTO DI STRUTTURA, che decide la FORMA del gate:")
print(" * misurare l'EV costa UNA CORSA DI QUESTO SCRIPT — zero dollari, minuti;")
print(" * misurare la RIMBORSABILITA' costa $579 e si puo' fare SOLO comprando.")
print(" Un gate che aspettasse la seconda misura sarebbe un gate che si puo' superare solo")
print(" dopo averlo violato. Percio' la gamba (c) va scritta in modo che la")
print(" rimborsabilita' NON decida: si richiede EV positivo sotto la convenzione")
print(" PESSIMISTA (quota mai restituita). Il rimborso diventa allora un regalo, non")
print(" un'ipotesi su cui si e' scommesso.")
print("\n >>> TESTO PROPOSTO — `GATE PROP-01 (c) CAPITALE`, due condizioni CONGIUNTE:")
print(" (c1) ECONOMICA, misura a costo zero, da rifare a ogni cambio del libro o")
print(" delle regole della firm:")
print(" EV del biglietto > 0 con la quota trattata come SPESA PERSA, calcolato")
print(" alla cella d'ancora MEDIANA (non canonica), col FUNDING dentro e sotto")
print(" la lente ACCOPPIATA, con `r0822d_gate_c.py`.")
print(f" Lettura di oggi: ${ev_sunk:+,.0f} -> "
f"{'PASS' if ev_sunk > 0 else 'FAIL'}")
print(" (c2) DI SOPRAVVIVENZA, verificabile dal saldo e dal piano di versamento:")
print(" prezzo del biglietto <= 2 versamenti mensili del piano in corso.")
print(" Motivo: il piano di accumulo e' la leva che il progetto ha misurato come")
print(" dominante (26/07: da 'mai' a 16 anni), e zero e' ASSORBENTE. Un biglietto")
print(" che costa piu' di due mensilita' non e' una scommessa sul canale funded:")
print(" e' un ritardo del piano che lo finanzia.")
print(f" Lettura di oggi (piano dichiarato EUR 500/mese, 26/07): "
f"${price:,.0f} vs 2 x ${500*EURUSD:,.0f} = ${2*500*EURUSD:,.0f} -> "
f"{'PASS' if price <= 2*500*EURUSD else 'FAIL'}")
print(" SCADENZA: nessuna. (c1) e (c2) sono verificabili OGGI; un gate d'attesa qui")
print(" non avrebbe un dato da aspettare, e stanotte due date sono cadute per questo.")
print(" Se si compra, si registra PRIMA: il rimborso deve comparire col primo payout;")
print(" se non compare, la convenzione vera e' 'spesa persa' — che (c1) ha gia'")
print(" assunto, quindi la decisione non va rifatta, va solo annotata.")
print(f"\n TRIAL/CELLE DICHIARATI (al rialzo): {TRIALS}")
print(f" fatto in {time.time()-T0:.0f}s")
print("=" * 104)
if __name__ == "__main__":
main()