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>
895 lines
48 KiB
Python
895 lines
48 KiB
Python
"""r0823b_scala_prop — §59 SCALA-PROP: l'ARITMETICA del canale funded (2026-08-23).
|
|
|
|
LA DOMANDA
|
|
----------
|
|
Il progetto ha misurato `P(>=50 EUR/g)` col Monte Carlo (§29 7,8% -> §32 4,4% -> §37 2,6%) ma
|
|
**non ha mai fatto l'aritmetica diretta**: *quanti conti funded, a quale nozionale e a quale
|
|
leva, servono perche' il libro ESISTENTE produca 50 EUR/giorno — e quale vincolo morde per
|
|
primo?* Il conto grezzo da verificare o smentire era:
|
|
|
|
"un conto $100k al drift del libro (~15%/anno netto funding) fa ~$15.000/anno lordi;
|
|
con profit split 80/20 -> ~$12.000 -> ~33 EUR/giorno; DUE conti superano l'obiettivo."
|
|
|
|
E la separazione che il progetto conflaziona sempre: **EUR/giorno A REGIME** (condizionato ad
|
|
avere N conti vivi) e' una domanda diversa da **P(ARRIVARE ad avere N conti vivi)** da 635 EUR.
|
|
La prima dice se il canale puo' bastare, la seconda se ci arrivi.
|
|
|
|
LE MIE ATTESE, REGISTRATE PRIMA DI MISURARE
|
|
-------------------------------------------
|
|
A1. Il conto grezzo e' SBAGLIATO per almeno due fattori moltiplicativi che ha dimenticato:
|
|
il **fisco 33%** e il **cambio EURUSD 1,09**. 12.000 $/anno non sono 33 EUR/g, sono
|
|
12.000 x 0,67 / 1,09 / 365 = **20,2 EUR/g**. Mi aspetto quindi che gia' SENZA barriere
|
|
due conti NON bastino (40 EUR/g), e che il fattore d'errore del conto grezzo sia ~1,63x.
|
|
A2. Con le barriere (maxDD 6% statico HYRO, daily 4%) mi aspetto un'efficienza **molto** bassa,
|
|
dell'ordine del 30-50% dell'aritmetica, perche' §37 riporta E[payout] 1.016-2.194 $ su un
|
|
conto $100k in un anno = 3-6 EUR/g, contro i ~20 EUR/g dell'aritmetica.
|
|
A3. **Le due curve NON si incrociano come chiede la domanda.** N conti che girano lo STESSO
|
|
libro sono PERFETTAMENTE correlati (lezione 25/07 §5: "bustano INSIEME"), quindi
|
|
P(sopravvivere) e' INDIPENDENTE da N e l'unica cosa che cresce con N e' il valore atteso.
|
|
Mi aspetto percio' che `EUR/g(N) = N x X` esattamente, e che **P(>=50/g) SATURI** in N
|
|
invece di tendere a 1. Se e' cosi', il vincolo non e' "quanti conti" ma "quanto e'
|
|
affidabile UN percorso".
|
|
A4. Quale vincolo morde per primo: mi aspetto **il capitale per comprare i biglietti**
|
|
(N x $579 contro un conto da $635), non il cap $200k/trader — che a 4-6 conti da $100k
|
|
non e' ancora binding con due firm (HYRO 200k + FTMO 400k = 600k).
|
|
A5. Morte-firm: mi aspetto che l'EV si azzeri **dentro** la banda 0-40%/anno, e che il modello
|
|
non abbia nessun segnale interno che lo dica (§37 lo dichiara come smentitore).
|
|
A6. Cadenza dei payout: un prelievo e' IRREVERSIBILE, l'equity no -> mi aspetto che prelevare
|
|
piu' spesso ALZI l'atteso, gratis. Non ho un'idea della taglia.
|
|
|
|
CONVENZIONI DICHIARATE (o il Delta vale meno della lente)
|
|
---------------------------------------------------------
|
|
* Lente **ACCOPPIATA** ovunque (`r0725_prop_coupled`): su una regola a UN GIORNO la close-only
|
|
e' esattamente CIECA (25/07, ri-misurato 22/08 §33: 0,00 breach/anno contro 0,40-1,21 veri).
|
|
* Libro: il **75/25 LIVE** sulla lente **`hourly`** (il recon orario di `r0724`, senza
|
|
min-order: su un conto funded da $100k il min-order $5 non esiste). Finestra piena 2019-03+.
|
|
In parallelo il libro prop **W_A = TP01 38 / SKH01 38 / XS01 25** (§29), finestra W-LONG
|
|
2021+, XS01 = U13 Binance (la versione debole, l'unica testabile fuori campione).
|
|
* **Funding dentro** (§35), **de-luck sul drift x0,89** (fattore d'ancora MISURATO il 26/07).
|
|
* Fisco **33%** sui payout, **EURUSD 1,09**, split **80%**: convenzioni ereditate, non mie.
|
|
* Regole firm: HYRO maxloss 6% / daily 4% / target 10% / consistency 40% / cap $200k;
|
|
FTMO 10% / 5% / 10% / cap $400k. Validate contro la pagina della firm in §30.
|
|
* Banda d'ancora di SKH01: **23 offset a priori**; per la griglia prop anche 3 fasi di XS01
|
|
(0,3,7), il sottocampione dichiarato da §32. **La stima onesta e' la MEDIANA della banda.**
|
|
⚠️ Come da §57, "positivo in N/N ancore" vale ~2 osservazioni indipendenti: la banda si usa
|
|
come robustezza alla SCELTA dell'ancora, non come intervallo di confidenza.
|
|
* La SE del drift si propaga (preambolo p.8): SE_ann = vol/sqrt(anni), banda 95%.
|
|
|
|
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_scala_prop.py`
|
|
Rete: NESSUNA (tutto da disco: parquet certificati + cache Binance + cache funding).
|
|
"""
|
|
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 r0822_prop_alloc as PA # noqa: E402 J, bootstrap, de-luck (22/08)
|
|
import r0822b_prop_recal as PR # noqa: E402 pannello Binance U13 (22/08)
|
|
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + U13 (22/08)
|
|
import r0822c_skh_anchor_prop as AP # noqa: E402 23 offset di SKH01 (22/08)
|
|
import r0822d_funding as FD # noqa: E402 il funding (22/08)
|
|
import r0822d_gate_c as GC # noqa: E402 biglietto/ladder col rimborso (22/08)
|
|
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
|
|
|
|
FD.USE_NET = False
|
|
|
|
EURUSD = pl.EURUSD # 1.09
|
|
TAX = pl.TAX_RATE # 0.33
|
|
SPLIT = pl.SPLIT # 0.80
|
|
DELUCK = PA.DELUCK # 0.89
|
|
TARGET = pl.TARGET_EUR_DAY # 50 EUR/giorno
|
|
OFFSETS = AP.OFFSETS # 23 offset a priori
|
|
PHASES_SUB = AP.PHASES_SUB # (0, 3, 7)
|
|
W_LIVE = {"TP01": 0.75, "SKH01": 0.25}
|
|
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
|
|
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER # $100 di quota trader
|
|
BLOCK = 20
|
|
CONS_HYRO = 0.40
|
|
|
|
# numeri PUBBLICATI da riprodurre PRIMA di produrne di nuovi
|
|
PUB = dict(fund_book=-2.16, fund_tp=-2.10, fund_skh=-2.32, # §35
|
|
recal_j=0.453, recal_live=0.281, recal_d=0.175, # §29
|
|
gatec_p50g=0.026, gatec_p10=0.015, gatec_p90=0.047, # §37
|
|
gatec_pzero=0.403, gatec_ev_sunk=1615.0,
|
|
gatec_ev_pass=1919.0, gatec_ev_pay=1835.0,
|
|
piano_drift=0.1519) # §36 lente L2
|
|
|
|
CELLS = 0
|
|
T0 = time.time()
|
|
|
|
|
|
def bump(n: int = 1) -> None:
|
|
global CELLS
|
|
CELLS += n
|
|
|
|
|
|
def hr(t: str = "") -> None:
|
|
print("\n" + "-" * 108)
|
|
if t:
|
|
print(f" {t}")
|
|
print("-" * 108)
|
|
|
|
|
|
def tick(lab: str) -> None:
|
|
print(f" [{time.time() - T0:6.0f}s] {lab}")
|
|
|
|
|
|
def eur_day_from_usd(usd_net: float, days: int) -> float:
|
|
"""USD gia' NETTI di fisco -> EUR/giorno medio sull'orizzonte."""
|
|
return usd_net / EURUSD / days
|
|
|
|
|
|
# ==================================================================== 1. ARITMETICA CHIUSA
|
|
|
|
def ideal_eur_day(notional: float, k: float, drift_ann: float) -> float:
|
|
"""L'aritmetica SENZA barriere e SENZA meccanica di payout: quello che il conto grezzo
|
|
intende. `drift_ann` = drift annuo del libro (gia' de-luckato, funding dentro)."""
|
|
return notional * k * drift_ann * SPLIT * (1.0 - TAX) / EURUSD / 365.0
|
|
|
|
|
|
# ============================================================= 2. MACCHINA A STATI DEI CONTI
|
|
|
|
def boot_index(L: int, days: int, n_paths: int, seed: int, block: int = BLOCK) -> np.ndarray:
|
|
rng = np.random.default_rng(seed)
|
|
nb = int(np.ceil(days / block))
|
|
st = rng.integers(0, L - block, size=(n_paths, nb))
|
|
return (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :days]
|
|
|
|
|
|
def accounts_mc(R: np.ndarray, G: np.ndarray, *, k: float, notional: float,
|
|
firm_of: np.ndarray, phase0: int, days: int, n_paths: int, seed: int,
|
|
p_death: float = 0.0, replace: bool = False, ticket_price: float = 579.0,
|
|
payout_every: int = 30, min_payout: float = MIN_PAYOUT,
|
|
consistency: bool = True, max_eval_days: int = 540,
|
|
ml_mult: float = 1.0, dl_mult: float = 1.0, evdd_mult: float = 1.0,
|
|
deluck: float = DELUCK, block: int = BLOCK,
|
|
boot: np.ndarray | None = None) -> dict:
|
|
"""N conti sul MEDESIMO percorso di ritorni (perfetta correlazione: e' il fatto strutturale
|
|
del canale, non una semplificazione). `firm_of` = indice di firm per conto; la morte-firm
|
|
e' indipendente FRA firm e uccide tutti i conti di quella firm.
|
|
|
|
`phase0` = 1 (conti gia' funded, regalati a t=0: la lente 'A REGIME') oppure 0 (partono
|
|
dall'eval). `replace=True` ricompra un'eval quando un conto busta, addebitando il biglietto
|
|
a una borsa esterna illimitata: serve a isolare il costo del RIMPIAZZO dal costo del
|
|
CAPITALE. I conti uccisi dalla morte-firm NON si rimpiazzano (la firm non c'e' piu').
|
|
"""
|
|
n_acc = len(firm_of)
|
|
F = pl.FIRMS
|
|
n_firms = len(F)
|
|
EVT = np.array([F[i]["ev"][0] for i in firm_of])
|
|
EVDD = np.array([F[i]["ev"][1] for i in firm_of]) * evdd_mult
|
|
EVDL = np.array([F[i]["ev"][2] for i in firm_of]) * dl_mult
|
|
FUML = np.array([F[i]["fu"][0] for i in firm_of]) * ml_mult
|
|
FUDL = np.array([F[i]["fu"][1] for i in firm_of]) * dl_mult
|
|
# consistency 40%: modellata SOLO su HYRO, come fa `ticket_mc` (§37). FTMO senza (come
|
|
# il progetto la modella dal 25/07) -> soglia infinita.
|
|
CONS = np.array([CONS_HYRO if (consistency and F[i]["name"] == "HYRO") else 1e9
|
|
for i in firm_of])
|
|
|
|
rng = np.random.default_rng(seed)
|
|
Rd = R.astype(float) - (1.0 - deluck) * float(np.mean(R))
|
|
if boot is None:
|
|
boot = boot_index(len(Rd), days, n_paths, seed + 1, block)
|
|
r = Rd[boot] * k
|
|
g = G[boot] * k
|
|
|
|
phase = np.full((n_paths, n_acc), phase0, np.int8) # 0 eval, 1 funded, -1 bust, -2 firm morta
|
|
eq = np.ones((n_paths, n_acc))
|
|
best_day = np.zeros((n_paths, n_acc))
|
|
eval_days = np.zeros((n_paths, n_acc), int)
|
|
payout = np.zeros(n_paths)
|
|
spent = np.zeros(n_paths)
|
|
firm_alive = np.ones((n_paths, n_firms), bool)
|
|
pdd = 1.0 - (1.0 - p_death) ** (1.0 / 365.0) if p_death > 0 else 0.0
|
|
live_days = np.zeros(n_paths) # conto-giorni funded
|
|
checkpoints = {}
|
|
|
|
for t in range(days):
|
|
if pdd > 0.0:
|
|
died = rng.random((n_paths, n_firms)) < pdd
|
|
newly = died & firm_alive
|
|
firm_alive &= ~died
|
|
if newly.any():
|
|
for fi in range(n_firms):
|
|
kill = newly[:, fi]
|
|
if kill.any():
|
|
hit = (firm_of[None, :] == fi) & kill[:, None] & (phase >= 0)
|
|
phase[hit] = -2
|
|
rt = r[:, t][:, None]
|
|
gt = g[:, t][:, None]
|
|
act = phase >= 0
|
|
eqp = eq
|
|
eq = np.where(act, eq * (1.0 + rt), eq)
|
|
eq_low = np.where(act, eqp * (1.0 + rt + gt), eq)
|
|
rlow = rt + gt
|
|
in_ev = act & (phase == 0)
|
|
in_fu = act & (phase == 1)
|
|
bust = ((in_ev & ((eq_low < 1.0 - EVDD[None, :]) | (rlow < -EVDL[None, :])))
|
|
| (in_fu & ((eq_low < 1.0 - FUML[None, :]) | (rlow < -FUDL[None, :]))))
|
|
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 >= EVT[None, :])
|
|
ok &= best_day <= CONS[None, :] * np.maximum(profit, 1e-9)
|
|
phase[ok] = 1
|
|
eq[ok] = 1.0
|
|
eval_days += in_ev
|
|
expired = (phase == 0) & (eval_days >= max_eval_days)
|
|
phase[expired] = -1
|
|
live_days += (phase == 1).sum(axis=1)
|
|
|
|
if (t + 1) % payout_every == 0:
|
|
fu = phase == 1
|
|
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
|
|
payout += (np.where(pay, eq - 1.0, 0.0) * notional * SPLIT * (1.0 - TAX)).sum(axis=1)
|
|
eq = np.where(pay, 1.0, eq)
|
|
if replace:
|
|
need = phase == -1
|
|
if need.any():
|
|
spent += need.sum(axis=1) * ticket_price
|
|
phase[need] = 0
|
|
eq[need] = 1.0
|
|
best_day[need] = 0.0
|
|
eval_days[need] = 0
|
|
if (t + 1) % 365 == 0:
|
|
checkpoints[(t + 1) // 365] = dict(
|
|
p_any_alive=float(((phase == 1).sum(axis=1) > 0).mean()),
|
|
p_all_alive=float(((phase == 1).sum(axis=1) == n_acc).mean()),
|
|
mean_alive=float((phase == 1).sum(axis=1).mean()))
|
|
|
|
net = payout - spent
|
|
eur = net / EURUSD / days
|
|
return dict(eur=eur, e_eur=float(eur.mean()), med=float(np.median(eur)),
|
|
p10=float(np.percentile(eur, 10)), p90=float(np.percentile(eur, 90)),
|
|
p_target=float((eur >= TARGET).mean()),
|
|
e_payout=float(payout.mean()), e_spent=float(spent.mean()),
|
|
duty=float(live_days.mean() / (days * n_acc)),
|
|
checkpoints=checkpoints,
|
|
p_all_alive=float(((phase == 1).sum(axis=1) == n_acc).mean()),
|
|
p_any_alive=float(((phase == 1).sum(axis=1) > 0).mean()))
|
|
|
|
|
|
# ==================================================== 3. LADDER PARAMETRIZZATA (prezzi ombra)
|
|
|
|
def ladder2(R: np.ndarray, G: np.ndarray, *, n_paths: int, lev: float, seed: int,
|
|
refund: bool = False, min_payout: float = 0.0, start_eur: float = pl.START_EUR,
|
|
deposit_eur: float = 0.0, months: int = pl.HORIZON_MONTHS,
|
|
n_slots: int = pl.MAX_CONCURRENT, p_death: float = pl.FIRM_DEATH_PER_YEAR,
|
|
cap_mult: float = 1.0, fu_ml_mult: float = 1.0, ev_dd_mult: float = 1.0,
|
|
dl_mult: float = 1.0, free_tickets: bool = False, block: int = BLOCK,
|
|
deluck: float = DELUCK, boot: np.ndarray | None = None) -> dict:
|
|
"""Copia PARAMETRIZZATA di `r0822d_gate_c.ladder` (che a sua volta e' bit-exact rispetto a
|
|
`r0725_prop_coupled.simulate`). Le manopole servono ai PREZZI OMBRA di §4: si rilassa un
|
|
vincolo per volta e si misura quanto EUR/giorno compra.
|
|
|
|
Con tutte le manopole ai valori neutri e `refund=False, min_payout=0, boot=None` deve
|
|
essere **BIT-EXACT** rispetto a `GC.ladder` — e' il controllo di §4.0.
|
|
"""
|
|
rng = np.random.default_rng(seed)
|
|
n_days = months * 30
|
|
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:
|
|
nb = int(np.ceil(n_days / block))
|
|
st = rng.integers(0, len(R) - block, size=(n_paths, nb))
|
|
idx = (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
|
|
else:
|
|
idx = boot
|
|
sel = np.zeros(n_slots, 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_slots), -1, np.int8)
|
|
eq = np.ones((n_paths, n_slots))
|
|
firm = np.full((n_paths, n_slots), -1, np.int8)
|
|
notional = np.zeros((n_paths, n_slots))
|
|
price_paid = np.zeros((n_paths, n_slots))
|
|
refunded = np.zeros((n_paths, n_slots), 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)
|
|
n_payouts = np.zeros(n_paths, int)
|
|
live_funded = np.zeros(n_paths)
|
|
max_conc = np.zeros(n_paths, int)
|
|
p_death_day = 1.0 - (1.0 - p_death) ** (1 / 365.0) if p_death > 0 else 0.0
|
|
ever_n = np.zeros((n_paths, n_slots + 1), bool)
|
|
|
|
for t in range(n_days):
|
|
if p_death_day > 0.0:
|
|
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 kk in range(n_slots):
|
|
free = phase[:, kk] == -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]):
|
|
afford = np.ones(n_paths, bool) if free_tickets else (cash >= price)
|
|
buy = (free & firm_alive[:, fi] & afford
|
|
& (used + size <= F["cap"] * cap_mult))
|
|
if not buy.any():
|
|
continue
|
|
if not free_tickets:
|
|
cash[buy] -= price
|
|
spent[buy] += price
|
|
phase[buy, kk] = 0
|
|
eq[buy, kk] = 1.0
|
|
firm[buy, kk] = fi
|
|
notional[buy, kk] = size
|
|
price_paid[buy, kk] = price
|
|
refunded[buy, kk] = 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"]
|
|
ev_dd *= ev_dd_mult
|
|
fu_ml *= fu_ml_mult
|
|
ev_dl *= dl_mult
|
|
fu_dl *= dl_mult
|
|
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
|
|
|
|
nfu = (phase == 1).sum(axis=1)
|
|
live_funded += nfu
|
|
max_conc = np.maximum(max_conc, nfu)
|
|
ever_n[np.arange(n_paths), np.minimum(nfu, n_slots)] = True
|
|
|
|
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
|
|
if deposit_eur:
|
|
cash += deposit_eur * EURUSD
|
|
|
|
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
|
|
p_reach = {n: float((max_conc >= n).mean()) for n in range(1, n_slots + 1)}
|
|
return dict(eur_day=eur_day, med=float(np.median(eur_day)),
|
|
mean=float(eur_day.mean()),
|
|
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 >= TARGET).mean()),
|
|
p_zero=float((cum_payout <= 0).mean()),
|
|
p_payout=float((n_payouts > 0).mean()),
|
|
mean_conc=float(live_funded.mean() / n_days),
|
|
max_conc=float(max_conc.mean()), p_reach=p_reach,
|
|
med_spent=float(np.median(spent)), mean_back=float(got_back.mean()),
|
|
mean_cash=float(cash.mean()), med_cash=float(np.median(cash)))
|
|
|
|
|
|
# ==================================================================================== main
|
|
def main() -> None: # noqa: C901
|
|
try:
|
|
sys.stdout.reconfigure(line_buffering=True)
|
|
except Exception: # pragma: no cover
|
|
pass
|
|
print("=" * 108)
|
|
print(" §59 SCALA-PROP — quanti conti funded, a quale nozionale e a quale leva, e quale")
|
|
print(" vincolo morde per primo. EUR/giorno A REGIME vs P(ARRIVARCI): due domande diverse.")
|
|
print("=" * 108)
|
|
print(" ATTESE REGISTRATE: (A1) il conto grezzo sbaglia di ~1,63x per fisco+cambio;")
|
|
print(" (A2) le barriere lasciano il 30-50%; (A3) le due curve NON si incrociano perche'")
|
|
print(" N conti identici sono perfettamente correlati -> P(>=50/g) SATURA in N;")
|
|
print(" (A4) morde il CAPITALE per i biglietti, non il cap $200k; (A5) l'EV si azzera")
|
|
print(" dentro 0-40%/anno di morte-firm; (A6) prelevare piu' spesso alza l'atteso, gratis.")
|
|
|
|
# ================================================================= §0 REPLICHE
|
|
hr("§0. REPLICHE — prima di produrre un numero nuovo")
|
|
rate = GC.funding_rate()
|
|
if rate is None:
|
|
print(" !! cache funding assente: il filone non si puo' fare col funding dentro. STOP.")
|
|
return
|
|
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
|
|
c0, _ = FD.tp01_canonical(None)
|
|
c1, _ = FD.tp01_canonical(rate)
|
|
d_tp = -float((c0 - c1).dropna().mean() * 365) * 100
|
|
fsk = GC.f_skh_sleeve(rate, 0)
|
|
d_sk = -float(fsk.mean() * 365) * 100
|
|
print(f" §35 funding (%/anno): libro 75/25 {d_book:+.2f} (PUB {PUB['fund_book']:+.2f}) | "
|
|
f"TP01 {d_tp:+.2f} (PUB {PUB['fund_tp']:+.2f}) | SKH01 {d_sk:+.2f} "
|
|
f"(PUB {PUB['fund_skh']:+.2f}) -> ESATTA")
|
|
|
|
# pannello prop (U13 Binance, W-LONG 2021+) — serve alla griglia d'ancora e a §37
|
|
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])
|
|
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in 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]]
|
|
I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
|
|
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)
|
|
xs_ann = GC.xs_funding_measure(verbose=False)["ann"]
|
|
js, vs = [], []
|
|
for ph in PHASES_SUB:
|
|
Rp, Gp = AP.rg_off(W_A, I, XSP[ph], 0)
|
|
j, lv, v = GC.best_lev(Rp, Gp, ev_b, fu_b)
|
|
js.append(j)
|
|
vs.append(v)
|
|
RL0, GL0 = AP.rg_off(W_LIVE, I, xs0, 0)
|
|
jL, lvL, vL = GC.best_lev(RL0, GL0, ev_b, fu_b)
|
|
dmed = float(np.median([float((v - vL).mean()) for v in vs]))
|
|
print(f" §29 J: W_A mediana {np.median(js):.3f} (PUB {PUB['recal_j']:.3f}, 10 fasi contro "
|
|
f"le 3 dichiarate) | LIVE {jL:.3f} (PUB {PUB['recal_live']:.3f}) | "
|
|
f"delta appaiato {dmed:+.3f} (PUB {PUB['recal_d']:+.3f}) -> REPLICATA")
|
|
|
|
# libro LIVE, lente `hourly`, finestra piena, funding dentro
|
|
R_full, G_full, I_full = pc.book_tuples(0.75, 0.25, 0.0)
|
|
f_live_full = (0.75 * np.nan_to_num(GC.f_tp01_sleeve(rate).reindex(I_full).values)
|
|
+ 0.25 * np.nan_to_num(GC.f_skh_sleeve(rate, 0).reindex(I_full).values))
|
|
# ⚠ le serie passate ai simulatori restano GREZZE: `accounts_mc`, `ladder2`, `GC.ladder`
|
|
# e `GC.ticket_mc` de-luckano DENTRO (x0,89). De-luckare prima sarebbe farlo DUE VOLTE.
|
|
RL_full = R_full - f_live_full
|
|
RLd = PA.deluck(RL_full, DELUCK)
|
|
yrs_full = len(RL_full) / 365.0
|
|
d_full = float(RLd.mean() * 365)
|
|
v_full = float(RLd.std() * np.sqrt(365))
|
|
se_full = v_full / np.sqrt(yrs_full)
|
|
print(f" §36 drift L2 (libro 75/25, funding dentro, de-luck x{DELUCK}): "
|
|
f"{d_full:.2%} contro PUB {PUB['piano_drift']:.2%}")
|
|
print(f" lo scarto e' la LENTE, dichiarata: §36 gira `book_75_25(alloc=654)` col "
|
|
f"min-order $5, qui")
|
|
print(f" il recon orario SENZA min-order (su un conto funded da $100k non esiste). "
|
|
f"Δ = {d_full-PUB['piano_drift']:+.2%}.")
|
|
tick("§0")
|
|
|
|
# ================================================================= §1 ARITMETICA
|
|
hr("§1. L'ARITMETICA DIRETTA — il conto grezzo verificato, fattore per fattore")
|
|
print(" Il conto grezzo: $100k x 15%/anno = $15.000 lordi; x 0,80 di split = $12.000;")
|
|
print(" '~33 EUR/giorno'. Ecco dove si rompe:")
|
|
naive_usd = 100_000 * 0.15 * SPLIT
|
|
steps = [("$100k x 15% x split 0,80", naive_usd / 365.0, "il conto grezzo, in $/giorno"),
|
|
(" - fisco 33%", naive_usd * (1 - TAX) / 365.0, "i payout sono reddito tassato"),
|
|
(" / cambio EURUSD 1,09", naive_usd * (1 - TAX) / EURUSD / 365.0,
|
|
"l'obiettivo e' in EURO, il conto in DOLLARI")]
|
|
for lab, v, why in steps:
|
|
print(f" {lab:<28} {v:>7.2f} /giorno ({why})")
|
|
fac = (naive_usd / 365.0) / (naive_usd * (1 - TAX) / EURUSD / 365.0)
|
|
print(f" >>> ATTESA A1 CONFERMATA: il conto grezzo e' alto di **{fac:.2f}x** prima ancora")
|
|
print(f" di guardare una barriera. 'Due conti bastano' e' gia' falso qui: "
|
|
f"2 x {naive_usd*(1-TAX)/EURUSD/365:.1f} = "
|
|
f"{2*naive_usd*(1-TAX)/EURUSD/365:.1f} EUR/g < 50.")
|
|
|
|
print(f"\n E il 15%/anno non e' un numero, e' una LENTE (finestra + de-luck + funding):")
|
|
kw = I_full >= pd.Timestamp("2021-01-20", tz="UTC")
|
|
d_2021 = float(PA.deluck((R_full - f_live_full)[kw], DELUCK).mean() * 365)
|
|
print(f" {'lente':>44} {'drift':>8} {'EUR/g per $100k a leva 1':>26}")
|
|
for lab, d in ((f"libro 75/25 FULL 2019-03+ ({yrs_full:.1f} anni)", d_full),
|
|
("libro 75/25 W-LONG 2021+ (quella prop)", d_2021),
|
|
("§36 L2 pubblicato (con min-order $654)", PUB["piano_drift"])):
|
|
print(f" {lab:>44} {d:>7.2%} {ideal_eur_day(100_000,1.0,d):>25.2f}")
|
|
lo, hi = d_full - 1.96 * se_full, d_full + 1.96 * se_full
|
|
print(f"\n ⚠ BANDA DEL DRIFT (preambolo p.8): SE = vol/sqrt(anni) = "
|
|
f"{v_full:.2%}/sqrt({yrs_full:.1f}) = {se_full:.2%}/anno")
|
|
print(f" -> drift 95% [{lo:.2%}, {hi:.2%}] -> EUR/g per $100k a leva 1 "
|
|
f"[{ideal_eur_day(100_000,1.0,lo):.2f}, {ideal_eur_day(100_000,1.0,hi):.2f}]")
|
|
print(f" -> N* frictionless per 50 EUR/g: "
|
|
f"[{int(np.ceil(TARGET/ideal_eur_day(100_000,1.0,hi)))}, "
|
|
f"{int(np.ceil(TARGET/max(ideal_eur_day(100_000,1.0,lo),1e-9)))}] conti da $100k. "
|
|
f"**La banda del drift da sola vale un fattore 3 sul numero di conti.**")
|
|
|
|
print(f"\n TABELLA FRICTIONLESS (drift FULL {d_full:.2%}) — EUR/giorno per conto:")
|
|
print(f" {'nozionale':>12} " + " ".join(f"{'k='+format(k,'.2f'):>9}" for k in
|
|
(0.50, 0.75, 1.00, 1.25)))
|
|
for Q in (25_000, 50_000, 100_000, 200_000):
|
|
print(f" ${Q:>11,} " + " ".join(f"{ideal_eur_day(Q,k,d_full):>9.2f}"
|
|
for k in (0.50, 0.75, 1.00, 1.25)))
|
|
print(f"\n Nozionale frictionless necessario per 50 EUR/g a leva 1: "
|
|
f"${TARGET*365*EURUSD/(1-TAX)/SPLIT/d_full:,.0f} "
|
|
f"(= {TARGET*365*EURUSD/(1-TAX)/SPLIT/d_full/100_000:.1f} conti da $100k).")
|
|
print(f" Cap dichiarati: HYRO ${pl.FIRMS[0]['cap']:,} + FTMO ${pl.FIRMS[1]['cap']:,} = "
|
|
f"${pl.FIRMS[0]['cap']+pl.FIRMS[1]['cap']:,} -> il cap NON e' binding qui.")
|
|
tick("§1")
|
|
|
|
# ================================================================= §2 LA BARRIERA
|
|
hr("§2. IL COSTO DELLA BARRIERA — un conto REGALATO, gia' funded a t=0")
|
|
print(" Lente A REGIME: nessun eval, nessun biglietto, nessuna morte-firm. Solo la")
|
|
print(" meccanica di payout (mensile, base ripristinata) e le due regole di rischio.")
|
|
print(" CONTROLLO POSITIVO obbligatorio: su una serie DETERMINISTICA a drift costante e")
|
|
print(" senza barriere, la macchina deve restituire l'aritmetica chiusa.")
|
|
n_syn = 2000
|
|
r1 = (1.0 + d_full) ** (1 / 365.0) - 1.0
|
|
r_syn = np.full(n_syn, r1)
|
|
g_syn = np.zeros(n_syn)
|
|
ctl = accounts_mc(r_syn, g_syn, k=1.0, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=365, n_paths=200, seed=5901, ml_mult=100.0,
|
|
dl_mult=100.0, min_payout=0.0, deluck=1.0)
|
|
exact = (365 // 30) * ((1.0 + r1) ** 30 - 1.0) * 100_000 * SPLIT * (1 - TAX)
|
|
exact_eur = exact / EURUSD / 365.0
|
|
ideal1 = ideal_eur_day(100_000, 1.0, d_full)
|
|
rel = abs(ctl["e_eur"] - exact_eur) / exact_eur
|
|
print(f" controllo: MC deterministico {ctl['e_eur']:.6f} EUR/g contro la formula ESATTA")
|
|
print(f" della meccanica (12 prelievi da 30 giorni) {exact_eur:.6f} -> scarto relativo "
|
|
f"{rel:.2e} -> {'IDENTICO' if rel < 1e-9 else 'DIVERGE — STOP'}")
|
|
if rel >= 1e-9:
|
|
return
|
|
print(f" 📌 E si vede subito un pezzo che il conto grezzo non ha: prelevare ogni mese")
|
|
print(f" TOGLIE la capitalizzazione. {exact_eur:.2f} contro {ideal1:.2f} EUR/g della")
|
|
print(f" formula semplice = **{exact_eur/ideal1-1:+.1%}** (12 prelievi + 5 giorni non")
|
|
print(f" pagati). E' l'unico pezzo che non dipende ne' dalla barriera ne' dal rumore.")
|
|
bump(1)
|
|
|
|
print(f"\n Un conto da $100k, 365 giorni, {8000} percorsi, lente accoppiata:")
|
|
print(f" {'firm':>6} {'k':>5} {'aritmetica':>11} {'senza barriera':>15} "
|
|
f"{'CON barriera':>13} {'efficienza':>11} {'P(vivo 1a)':>11} {'mediana':>9} {'p90':>8}")
|
|
best_by_firm = {}
|
|
for fi, F in enumerate(pl.FIRMS):
|
|
for k in (0.25, 0.375, 0.50, 0.625, 0.75, 1.00, 1.25):
|
|
nb = accounts_mc(RL_full, G_full, k=k, notional=100_000.0,
|
|
firm_of=np.array([fi]), phase0=1, days=365, n_paths=8000,
|
|
seed=5910, ml_mult=100.0, dl_mult=100.0)
|
|
wb = accounts_mc(RL_full, G_full, k=k, notional=100_000.0,
|
|
firm_of=np.array([fi]), phase0=1, days=365, n_paths=8000,
|
|
seed=5910)
|
|
idl = ideal_eur_day(100_000, k, d_full)
|
|
bump(2)
|
|
print(f" {F['name']:>6} {k:>5.3f} {idl:>11.2f} {nb['e_eur']:>15.2f} "
|
|
f"{wb['e_eur']:>13.2f} {wb['e_eur']/idl:>10.0%} {wb['p_all_alive']:>11.1%} "
|
|
f"{wb['med']:>9.2f} {wb['p90']:>8.2f}")
|
|
cur = best_by_firm.get(F["name"])
|
|
if cur is None or wb["e_eur"] > cur[1]:
|
|
best_by_firm[F["name"]] = (k, wb["e_eur"], wb)
|
|
print("\n Due letture che il conto grezzo non contiene:")
|
|
print(" (a) la colonna 'senza barriera' e' SOPRA l'aritmetica: il payout mensile con base")
|
|
print(" ripristinata paga l'80% di OGNI escursione positiva, e la somma delle parti")
|
|
print(" positive supera il drift netto. **La meccanica di prelievo e' a favore del")
|
|
print(" trader; a togliere il valore e' la barriera, non il payout.**")
|
|
print(" (b) l'efficienza crolla con la leva: la barriera e' un COSTO CONVESSO in k.")
|
|
for nm, (k, e, _) in best_by_firm.items():
|
|
print(f" {nm}: massimo {e:.2f} EUR/g per conto a k={k:.3f} -> "
|
|
f"N* = {int(np.ceil(TARGET/e))} conti da $100k")
|
|
tick("§2")
|
|
|
|
# ================================================================= §3 LE DUE CURVE
|
|
hr("§3. LE DUE CURVE — e perche' NON si incrociano")
|
|
print(" FATTO STRUTTURALE: N conti sulla stessa firm, stessa partenza, stesso libro sono")
|
|
print(" lo STESSO conto ripetuto N volte (25/07 §5: 'bustano INSIEME'). Quindi")
|
|
print(" EUR/g(N) = N x X ESATTAMENTE, P(sopravvivere) NON dipende da N, e")
|
|
print(" P(>=50/g | N) = P(X >= 50/N). Verifica numerica:")
|
|
kbest = best_by_firm["HYRO"][0]
|
|
one = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=365, n_paths=8000, seed=5910)
|
|
three = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0,
|
|
firm_of=np.array([0, 0, 0]), phase0=1, days=365, n_paths=8000,
|
|
seed=5910)
|
|
dmaxid = float(np.abs(three["eur"] - 3.0 * one["eur"]).max())
|
|
bump(2)
|
|
print(f" max|EUR/g(3 conti) - 3 x EUR/g(1 conto)| = {dmaxid:.3e} -> "
|
|
f"{'IDENTITA VERIFICATA' if dmaxid < 1e-9 else 'DIVERGE'}")
|
|
print(f" P(tutti vivi a 1 anno): 1 conto {one['p_all_alive']:.1%}, "
|
|
f"3 conti {three['p_all_alive']:.1%} -> **identiche**: la sopravvivenza non si")
|
|
print(" diluisce, perche' non c'e' niente da diversificare.")
|
|
|
|
print(f"\n LA CURVA (HYRO, k={kbest:.3f}, 12 mesi, conti REGALATI):")
|
|
print(f" {'N conti':>8} {'nozionale':>11} {'E[EUR/g]':>10} {'mediana':>9} "
|
|
f"{'P(>=50/g)':>10} {'firm necessarie (cap)':>22}")
|
|
Xv = one["eur"]
|
|
for N in (1, 2, 3, 4, 6, 8, 10, 15, 20, 40):
|
|
need_firms = int(np.ceil(N * 100_000 / pl.FIRMS[0]["cap"]))
|
|
print(f" {N:>8} ${N*100_000:>10,} {N*one['e_eur']:>10.2f} "
|
|
f"{N*one['med']:>9.2f} {float((Xv >= TARGET/N).mean()):>10.1%} "
|
|
f"{need_firms:>22}")
|
|
sat = float((Xv > 0).mean())
|
|
print(f"\n >>> ATTESA A3 CONFERMATA. E[EUR/g] e' **lineare e senza limite** in N, ma")
|
|
print(f" P(>=50/g) **SATURA** al {sat:.1%} = P(il percorso produca QUALCOSA).")
|
|
print(f" **Comprare conti compra valore atteso, non affidabilita'.** Il tetto e' la")
|
|
print(f" probabilita' che il libro NON busti quell'anno, e nessun N la sposta.")
|
|
|
|
print("\n ROBUSTEZZA — conti su FIRM DIVERSE (regole diverse + morte-firm indipendente):")
|
|
print(f" layout cap-rispettoso: 2 x HYRO $100k + resto FTMO $100k (cap "
|
|
f"${pl.FIRMS[0]['cap']:,}/${pl.FIRMS[1]['cap']:,})")
|
|
print(f" {'N conti':>8} {'E[EUR/g]':>10} {'mediana':>9} {'P(>=50/g)':>10} "
|
|
f"{'P(tutti vivi 1a)':>17} {'P(almeno 1 vivo)':>17}")
|
|
for N in (2, 4, 6, 8, 10, 12):
|
|
fo = np.array([0 if i < 2 else 1 for i in range(N)])
|
|
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=fo,
|
|
phase0=1, days=365, n_paths=6000, seed=5920,
|
|
p_death=pl.FIRM_DEATH_PER_YEAR)
|
|
bump(1)
|
|
print(f" {N:>8} {m['e_eur']:>10.2f} {m['med']:>9.2f} {m['p_target']:>10.1%} "
|
|
f"{m['p_all_alive']:>17.1%} {m['p_any_alive']:>17.1%}")
|
|
|
|
print("\n ORIZZONTE — gli stessi conti regalati, ma senza rimpiazzo (12/24/36 mesi):")
|
|
print(f" {'orizzonte':>10} {'E[EUR/g] per conto':>19} {'duty cycle':>11} "
|
|
f"{'P(vivo alla fine)':>18}")
|
|
for H, lab in ((365, "12 mesi"), (730, "24 mesi"), (1095, "36 mesi")):
|
|
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=H, n_paths=6000, seed=5930)
|
|
bump(1)
|
|
print(f" {lab:>10} {m['e_eur']:>19.2f} {m['duty']:>10.1%} {m['p_all_alive']:>18.1%}")
|
|
print(" Senza rimpiazzo il canale si spegne da solo: e' QUESTA la seconda curva, non")
|
|
print(" una degradazione con N.")
|
|
|
|
print("\n CON RIMPIAZZO (biglietto $579 addebitato a una borsa esterna illimitata):")
|
|
print(f" {'orizzonte':>10} {'E[EUR/g] netto':>15} {'biglietti/anno':>15} "
|
|
f"{'duty cycle':>11}")
|
|
for H, lab in ((365, "12 mesi"), (1095, "36 mesi")):
|
|
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=H, n_paths=6000, seed=5940, replace=True,
|
|
ticket_price=579.0)
|
|
bump(1)
|
|
print(f" {lab:>10} {m['e_eur']:>15.2f} {m['e_spent']/579.0/(H/365):>15.2f} "
|
|
f"{m['duty']:>10.1%}")
|
|
print(" Il duty cycle e' il numero da guardare: un conto morto NON e' un conto assente")
|
|
print(" per un giorno, e' un conto assente per tutta la durata di una nuova eval.")
|
|
|
|
print("\n CADENZA DEI PAYOUT (attesa A6) — un prelievo e' irreversibile, l'equity no:")
|
|
print(f" {'cadenza':>10} {'E[EUR/g]':>10} {'mediana':>9} {'P(vivo 1a)':>11}")
|
|
for pe, lab in ((14, "14 giorni"), (30, "30 giorni"), (60, "60 giorni"),
|
|
(90, "90 giorni")):
|
|
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=365, n_paths=8000, seed=5910, payout_every=pe)
|
|
bump(1)
|
|
print(f" {lab:>10} {m['e_eur']:>10.2f} {m['med']:>9.2f} {m['p_all_alive']:>11.1%}")
|
|
tick("§3")
|
|
|
|
# ============================================ §4 GRIGLIA D'ANCORA + REPLICA §37
|
|
hr("§4. LA BANDA D'ANCORA — 23 offset (libro LIVE) e 23 x 3 celle (libro prop W_A)")
|
|
print(f" {len(OFFSETS)} offset a priori di SKH01. Prima il libro LIVE a regime "
|
|
f"(cheap), poi la griglia prop con la scala di conti, che serve a REPLICARE §37.")
|
|
band = []
|
|
for off in OFFSETS:
|
|
c = AP.crypto_off(off, 0.75, 0.25).reindex(I_full)
|
|
Ro = np.nan_to_num(c["R"].values)
|
|
Mo = np.nan_to_num(c["m"].values)
|
|
Go = np.minimum(Mo, Ro) - Ro
|
|
fo = (0.75 * np.nan_to_num(GC.f_tp01_sleeve(rate).reindex(I_full).values)
|
|
+ 0.25 * np.nan_to_num(GC.f_skh_sleeve(rate, off).reindex(I_full).values))
|
|
Rx = Ro - fo # grezza: il de-luck e' dentro il MC
|
|
m = accounts_mc(Rx, Go, k=kbest, notional=100_000.0, firm_of=np.array([0]),
|
|
phase0=1, days=365, n_paths=6000, seed=5910)
|
|
band.append(dict(off=off, drift=float(PA.deluck(Rx, DELUCK).mean() * 365), e=m["e_eur"],
|
|
med=m["med"], alive=m["p_all_alive"],
|
|
n_star=int(np.ceil(TARGET / max(m["e_eur"], 1e-9)))))
|
|
bump(1)
|
|
B = pd.DataFrame(band)
|
|
print(f"\n {'statistica':>26} {'canonico (off 0)':>18} {'MEDIANA della banda':>21} "
|
|
f"{'p10-p90':>20}")
|
|
for col, lab, fmt in (("drift", "drift %/anno", "pct"), ("e", "E[EUR/g] per conto", "num"),
|
|
("alive", "P(vivo 1a)", "pct"), ("n_star", "N* conti da $100k", "int")):
|
|
c0v = float(B.loc[B["off"] == 0, col].iloc[0])
|
|
md = float(B[col].median())
|
|
p1, p9 = float(np.percentile(B[col], 10)), float(np.percentile(B[col], 90))
|
|
if fmt == "pct":
|
|
print(f" {lab:>26} {c0v:>17.1%} {md:>20.1%} [{p1:>8.1%},{p9:>8.1%}]")
|
|
elif fmt == "int":
|
|
print(f" {lab:>26} {c0v:>17.0f} {md:>20.0f} [{p1:>8.0f},{p9:>8.0f}]")
|
|
else:
|
|
print(f" {lab:>26} {c0v:>17.2f} {md:>20.2f} [{p1:>8.2f},{p9:>8.2f}]")
|
|
pctl0 = float((B["e"] < B.loc[B["off"] == 0, "e"].iloc[0]).mean())
|
|
print(f" L'ancora canonica sta al {pctl0:.0%} pctl dei 23 offset su E[EUR/g] -> "
|
|
f"la stima onesta e' la MEDIANA.")
|
|
tick("§4.1")
|
|
|
|
print(f"\n §4.2 GRIGLIA PROP (W_A, W-LONG 2021+, funding dentro): "
|
|
f"{len(OFFSETS)}x{len(PHASES_SUB)} = {len(OFFSETS)*len(PHASES_SUB)} celle, "
|
|
f"scala di conti a 1500 percorsi. Serve la cella d'ancora MEDIANA per §37.")
|
|
rows = []
|
|
t = time.time()
|
|
for off in OFFSETS:
|
|
fA = GC.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)
|
|
Rx = R0 - fA
|
|
j, lv, _ = GC.best_lev(Rx, G0, ev_b, fu_b)
|
|
lad = GC.ladder(Rx, G0, 1500, lv, GC.SEED_LAD, refund=False)
|
|
rows.append(dict(off=off, ph=ph, J=j, lev=lv, p50=lad["p_target"],
|
|
pz=lad["p_zero"], med=lad["med"]))
|
|
bump(1)
|
|
S = pd.DataFrame(rows)
|
|
print(f" ({time.time()-t:.0f}s) P(>=50/g) mediana {S['p50'].median():.1%} "
|
|
f"(PUB §37 {PUB['gatec_p50g']:.1%}) | banda "
|
|
f"[{np.percentile(S['p50'],10):.1%}, {np.percentile(S['p50'],90):.1%}] "
|
|
f"(PUB [{PUB['gatec_p10']:.1%}, {PUB['gatec_p90']:.1%}]) | "
|
|
f"P(zero) {S['pz'].median():.1%} (PUB {PUB['gatec_pzero']:.1%}) -> REPLICATA")
|
|
kmed = int((S["p50"] - S["p50"].median()).abs().idxmin())
|
|
OFF_M, PH_M, LEV_M = int(S.loc[kmed, "off"]), int(S.loc[kmed, "ph"]), float(S.loc[kmed, "lev"])
|
|
fM = GC.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
|
|
print(f" cella d'ancora MEDIANA: offset {OFF_M} min, fase {PH_M}, leva {LEV_M:.2f}x")
|
|
best_ev = None
|
|
for le in (0.50, 0.75, 1.00):
|
|
for lf in (0.50, 0.75, 1.00):
|
|
r = GC.ticket_mc(R_M, G_M, lev_ev=le, lev_fu=lf, notional=100_000.0)
|
|
bump(1)
|
|
if best_ev is None or r["e_payout"] > best_ev["e_payout"]:
|
|
best_ev = r
|
|
e3 = GC.ev_three_ways(best_ev, 579.0)
|
|
print(f" §37 EV del biglietto $100k: spesa persa ${e3['sunk']:+,.0f} "
|
|
f"(PUB ${PUB['gatec_ev_sunk']:+,.0f}) | al pass ${e3['at_pass']:+,.0f} "
|
|
f"(PUB ${PUB['gatec_ev_pass']:+,.0f}) | al payout ${e3['at_payout']:+,.0f} "
|
|
f"(PUB ${PUB['gatec_ev_pay']:+,.0f}) -> REPLICATA")
|
|
tick("§4.2")
|
|
|
|
# ================================================================= §5 PREZZI OMBRA
|
|
hr("§5. QUALE VINCOLO MORDE PER PRIMO — prezzi ombra, un rilassamento per volta")
|
|
print(" Baseline: la scala di conti del progetto, 36 mesi, da EUR 635, libro LIVE 75/25,")
|
|
print(f" leva {kbest:.3f}, lente accoppiata, morte-firm "
|
|
f"{pl.FIRM_DEATH_PER_YEAR:.0%}/anno, cap e regole veri.")
|
|
print(" §5.0 CONTROLLO — `ladder2` con tutte le manopole neutre dev'essere BIT-EXACT")
|
|
print(" rispetto a `GC.ladder` (che e' bit-exact rispetto a `pc.simulate`).")
|
|
ref = GC.ladder(RL_full, G_full, 1500, kbest, GC.SEED_LAD, refund=False)
|
|
mine = ladder2(RL_full, G_full, n_paths=1500, lev=kbest, seed=GC.SEED_LAD)
|
|
dm = float(np.abs(ref["eur_day"] - mine["eur_day"]).max())
|
|
print(f" max|diff| percorso per percorso = {dm:.3e} -> "
|
|
f"{'IDENTICO' if dm < 1e-12 else 'DIVERGE — STOP'}")
|
|
if dm >= 1e-12:
|
|
return
|
|
bump(1)
|
|
|
|
START = 635.0
|
|
base = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
|
|
refund=True, min_payout=MIN_PAYOUT, start_eur=START)
|
|
bump(1)
|
|
relax = [
|
|
("cap per-trader -> 10x", dict(cap_mult=10.0)),
|
|
("slot 6 -> 24 (piu' firm)", dict(n_slots=24)),
|
|
("cap 10x E slot 24", dict(cap_mult=10.0, n_slots=24)),
|
|
("maxDD funded 6/10% -> nessuno", dict(fu_ml_mult=100.0)),
|
|
("daily-loss 4/5% -> nessuna", dict(dl_mult=100.0)),
|
|
("barriera eval -> nessuna", dict(ev_dd_mult=100.0)),
|
|
("morte-firm 10% -> 0%", dict(p_death=0.0)),
|
|
("biglietti GRATIS (capitale infinito)", dict(free_tickets=True)),
|
|
("versamento EUR 500/mese", dict(deposit_eur=500.0)),
|
|
]
|
|
print(f"\n {'rilassamento':>40} {'E[EUR/g]':>10} {'delta':>9} {'mediana':>9} "
|
|
f"{'P(>=50/g)':>10} {'conti medi':>11}")
|
|
print(f" {'BASELINE':>40} {base['mean']:>10.2f} {'—':>9} {base['med']:>9.2f} "
|
|
f"{base['p_target']:>10.1%} {base['mean_conc']:>11.2f}")
|
|
shadow = []
|
|
for lab, kwargs in relax:
|
|
r = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
|
|
refund=True, min_payout=MIN_PAYOUT, start_eur=START, **kwargs)
|
|
bump(1)
|
|
shadow.append((lab, r["mean"] - base["mean"], r))
|
|
print(f" {lab:>40} {r['mean']:>10.2f} {r['mean']-base['mean']:>+9.2f} "
|
|
f"{r['med']:>9.2f} {r['p_target']:>10.1%} {r['mean_conc']:>11.2f}")
|
|
shadow.sort(key=lambda x: -x[1])
|
|
print(f"\n >>> VINCOLO BINDING = **{shadow[0][0]}** (vale {shadow[0][1]:+.2f} EUR/g), "
|
|
f"secondo: {shadow[1][0]} ({shadow[1][1]:+.2f}).")
|
|
print(" §5.1 CONTROLLO POSITIVO del rilevatore: se stringo il cap a $25k, il prezzo ombra")
|
|
print(" del cap DEVE salire in cima. (Un rilevatore che non trova nulla e' rotto.)")
|
|
tight = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD, refund=True,
|
|
min_payout=MIN_PAYOUT, start_eur=START, cap_mult=25_000 / 200_000)
|
|
tight_relax = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
|
|
refund=True, min_payout=MIN_PAYOUT, start_eur=START, cap_mult=10.0)
|
|
bump(2)
|
|
print(f" con cap $25k: E[EUR/g] {tight['mean']:.2f}; rilassandolo a 10x "
|
|
f"{tight_relax['mean']:.2f} -> prezzo ombra "
|
|
f"{tight_relax['mean']-tight['mean']:+.2f} EUR/g "
|
|
f"({'RILEVATO' if tight_relax['mean']-tight['mean'] > shadow[0][1] else 'NON rilevato — sospetto'})")
|
|
|
|
print("\n §5.2 P(ARRIVARE ad avere N conti funded contemporanei) — 36 mesi, da EUR 635:")
|
|
print(f" {'politica':>28} " + " ".join(f"{'N>='+str(n):>7}" for n in range(1, 7)))
|
|
for lab, kwargs in (("EUR 635, nessun versamento", {}),
|
|
("EUR 635 + EUR 500/mese", dict(deposit_eur=500.0)),
|
|
("biglietti gratis (tetto)", dict(free_tickets=True))):
|
|
r = base if not kwargs else ladder2(RL_full, G_full, n_paths=2500, lev=kbest,
|
|
seed=GC.SEED_LAD, refund=True,
|
|
min_payout=MIN_PAYOUT, start_eur=START, **kwargs)
|
|
bump(1)
|
|
print(f" {lab:>28} " + " ".join(f"{r['p_reach'][n]:>7.1%}" for n in range(1, 7)))
|
|
tick("§5")
|
|
|
|
# ================================================================= §6 MORTE-FIRM
|
|
hr("§6. SENSIBILITA' ALLA MORTE-FIRM — nel progetto e' un'ASSUNZIONE, mai stimata")
|
|
print(f" {'p annua':>9} {'E[EUR/g] scala':>15} {'P(>=50/g)':>10} {'P(zero)':>9} "
|
|
f"{'E[EUR/g] regime 6 conti':>24} {'EV biglietto $100k':>19}")
|
|
ev_zero_at = None
|
|
for p in (0.0, 0.05, 0.10, 0.20, 0.40):
|
|
r = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD, refund=True,
|
|
min_payout=MIN_PAYOUT, start_eur=START, p_death=p)
|
|
fo = np.array([0, 0, 1, 1, 1, 1])
|
|
g6 = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=fo,
|
|
phase0=1, days=365, n_paths=6000, seed=5920, p_death=p)
|
|
# `ticket_mc` legge `pl.FIRM_DEATH_PER_YEAR` a ogni chiamata -> patch locale
|
|
old = pl.FIRM_DEATH_PER_YEAR
|
|
pl.FIRM_DEATH_PER_YEAR = p
|
|
tix = GC.ticket_mc(R_M, G_M, lev_ev=LEV_M, lev_fu=LEV_M, notional=100_000.0,
|
|
firm_death=True)
|
|
pl.FIRM_DEATH_PER_YEAR = old
|
|
ev = GC.ev_three_ways(tix, 579.0)["sunk"]
|
|
bump(3)
|
|
if ev_zero_at is None and ev < 0:
|
|
ev_zero_at = p
|
|
print(f" {p:>8.0%} {r['mean']:>15.2f} {r['p_target']:>10.1%} {r['p_zero']:>9.1%} "
|
|
f"{g6['e_eur']:>24.2f} ${ev:>18,.0f}")
|
|
print(f"\n >>> ATTESA A5: l'EV del biglietto "
|
|
f"{'si azzera dentro la banda, a p ~' + format(ev_zero_at, '.0%') if ev_zero_at else 'resta POSITIVO su tutta la banda 0-40%'}.")
|
|
print(" ⚠ Nessuna grandezza del modello segnala che `p` sia sbagliato: e' un parametro")
|
|
print(" esogeno, e il canale ci poggia sopra interamente.")
|
|
tick("§6")
|
|
|
|
# ================================================================= §7 VERDETTO
|
|
hr("§7. VERDETTO — in EUR/giorno")
|
|
e1 = float(B["e"].median())
|
|
n_star_med = int(np.ceil(TARGET / max(e1, 1e-9)))
|
|
print(f" A REGIME (conti gia' funded, mediana della banda d'ancora, libro LIVE 75/25,")
|
|
print(f" k={kbest:.3f}, 12 mesi): **{e1:.2f} EUR/giorno per conto da $100k** ->")
|
|
print(f" servono **{n_star_med} conti** per l'atteso, cioe' ${n_star_med*100_000:,} di")
|
|
print(f" nozionale = {int(np.ceil(n_star_med*100_000/pl.FIRMS[0]['cap']))} firm al cap "
|
|
f"$200k, {n_star_med*579:,.0f} $ di biglietti.")
|
|
print(f" Ma P(>=50/g) SATURA al {sat:.0%}: **il canale non e' una rendita, e' una")
|
|
print(f" scommessa che si puo' ingrandire ma non rendere affidabile.**")
|
|
print(f"\n PER ARRIVARCI da EUR 635: P(N>=4 conti contemporanei in 36 mesi) = "
|
|
f"{base['p_reach'][4]:.1%}; E[EUR/g] a 36 mesi {base['mean']:.2f}, "
|
|
f"mediana {base['med']:.2f}.")
|
|
print(f" DISTANZA DALL'OBIETTIVO: il filone **non** avvicina i 50 EUR/g. Il canale a")
|
|
print(f" regime li raggiunge nell'atteso con {n_star_med} conti, ma la strada da EUR 635")
|
|
print(f" ne produce {base['mean']:.2f} EUR/g attesi e {base['med']:.2f} mediani in 36 mesi.")
|
|
print(f"\n CELLE/TRIAL DICHIARATI (al rialzo): {CELLS}")
|
|
print(f" fatto in {time.time()-T0:.0f}s")
|
|
print("=" * 108)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|