"""r0725_prop_config — QUALE BOOK mandare su un conto funded (2026-07-25). Il 24/07 (`r0724_goal50_math.py` parte C + `r0724_goal50_intraday_mc.py`) ha stabilito che il conto funded e' l'unico canale che moltiplica il NOZIONALE senza capitale proprio, e ha quantificato P(pass) e payout — ma sempre e solo per il **book a 2 sleeve** (TP01+SKH01), perche' e' quello eseguibile a $600 sul conto Deribit. Il punto che quell'analisi non ha visto: **su un conto funded il capitale non e' piu' $600, e' $100k** — quindi gli sleeve che a casa nostra sono STAT-MODE per taglia (XS01 a ~$20k) DIVENTANO eseguibili. E le regole prop premiano esattamente cio' che la diversificazione produce: passano sul DRAWDOWN e sulla perdita giornaliera, NON sul CAGR. Un book a Sharpe piu' alto e vol piu' bassa dovrebbe passare molto piu' spesso, a parita' di tutto il resto. Confronta tre configurazioni CRYPTO-ONLY (cio' che un sub-account di prop firm puo' davvero eseguire — niente GTAA01 che e' equity su IB, niente VRP01 che e' opzioni + regola permanente): A) TP01 75 / SKH01 25 — il book live attuale (quello valutato il 24/07) B) A + XS01 — cross-sectional 19 alt perp: eseguibile a $100k, NON a $600 C) B + XSR01 — contro-fattuale (gate pre-registrato 2026-10-23, non deployato) ONESTA' (limiti dichiarati, ereditati e nuovi): 1. **Close-only**: il MC gira sulle CHIUSURE giornaliere. C-bis del 24/07 ha dimostrato che i wick intraday tagliano 6-37pp di P(pass) -> **i livelli ASSOLUTI qui sono un TETTO**. Il ricostruttore MTM intraday esiste solo per TP01+SKH01 (feed 5m); XS01/XSR01 sono 1d nativi su Hyperliquid e il loro intraday NON e' ricostruibile dai nostri dati -> non lo invento. Cio' che questo script misura onestamente e' il **DELTA fra configurazioni**, e il delta e' CONSERVATIVO: i book B/C hanno vol piu' bassa, quindi wick piu' piccoli, quindi subirebbero un haircut intraday MINORE di A. 2. **Finestra comune**: XS01 parte dal 2024 -> il confronto principale gira sui giorni in cui TUTTI gli sleeve esistono (2024+, ~2.5 anni). Riportata anche la finestra piena (outer-join) dove pre-2024 i book B/C degradano su A. 3. De-luck x0.6 sul drift (convenzione del progetto) come caso di decisione. 4. SKH01 sul PATH LIVE (fill orario), non sul backtest. Uso: `uv run python scripts/research/r0725_prop_config.py` """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "research")) from src.portfolio.portfolio import combine_outer, metrics # noqa: E402 from src.portfolio.sleeves import _xsec_returns # noqa: E402 import r0724_skh_live_weight as skl # noqa: E402 from r0725_capcurve import EURUSD, TAX_RATE, tp01_realistic # noqa: E402 RNG = np.random.default_rng(20260725) PROP_NOTIONAL = 100_000.0 SPLIT = 0.80 # profit split tipico (80% al trader) RULES = [ ("HYRO 1-step tgt10% DD6% dl4%", 0.10, 0.06, 0.04), ("FTMO Swing tgt10% DD10% dl5%", 0.10, 0.10, 0.05), ] FUNDED_RULES = [("HYRO max-loss 6%", 0.06, 0.04), ("FTMO max-loss 10%", 0.10, 0.05)] # ---------------------------------------------------------------- serie book def _d(s: pd.Series) -> pd.Series: s = s.dropna().sort_index() if s.index.tz is None: s.index = s.index.tz_localize("UTC") return s def build_configs(deluck: float = 0.6) -> dict: """Le tre configurazioni, su griglia giornaliera, de-luckate sul drift.""" skh = _d(skl.skh_book(0, "hourly")) tp = _d(tp01_realistic(PROP_NOTIONAL * 0.75)) xs = _d(_xsec_returns()).resample("1D").apply(lambda x: (1 + x).prod() - 1) cfgs = { "A TP01/SKH01 (live oggi)": ({"TP01": tp, "SKH01": skh}, {"TP01": 0.75, "SKH01": 0.25}), "B + XS01 (19 alt perp)": ({"TP01": tp, "SKH01": skh, "XS01": xs}, {"TP01": 0.55, "SKH01": 0.20, "XS01": 0.25}), } out = {} for name, (cols, w) in cfgs.items(): s = combine_outer(cols, w, lo=pd.Timestamp("2019-03-14", tz="UTC")) s = s - (1.0 - deluck) * float(s.mean()) out[name] = dict(full=s, cols=cols, w=w) return out def common_window(cfgs: dict) -> pd.Timestamp: """Primo giorno in cui TUTTI gli sleeve di TUTTE le config hanno dato.""" starts = [] for v in cfgs.values(): for s in v["cols"].values(): starts.append(s.index.min()) return max(starts) # ------------------------------------------------------------------ MC eval def _paths(r: np.ndarray, n_days: int, n_paths: int, block: int = 20) -> np.ndarray: n_blocks = int(np.ceil(n_days / block)) starts = RNG.integers(0, len(r) - block, size=(n_paths, n_blocks)) idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days] return r[idx] def sim_eval(r: np.ndarray, target: float, max_dd: float, daily_loss: float, k: float, max_days: int = 365, n_paths: int = 8000) -> dict: R = _paths(r, max_days, n_paths) * k eq = np.cumprod(1 + R, axis=1) eq_start = eq / (1 + R) fail = (eq_start * (1 + R) < (1 - max_dd)) | (R < -daily_loss) passed = eq >= 1 + target ff = np.where(fail.any(axis=1), fail.argmax(axis=1), max_days + 1) fp = np.where(passed.any(axis=1), passed.argmax(axis=1), max_days + 1) ok = fp < ff return dict(p_pass=float(ok.mean()), median_days=float(np.median(fp[ok])) if ok.any() else float("nan")) def sim_funded(r: np.ndarray, max_loss: float, daily_loss: float, k: float, years: int = 1, n_paths: int = 8000) -> dict: """Fase funded con regole REALI: max-loss **STATICO** (misurato dal saldo iniziale del conto, non da un picco mobile) + daily-loss; prelievo mensile dell'80% del profitto, dopo il quale il saldo torna alla base (e con esso il buffer di perdita). NB: la base di prelievo si aggiorna SOLO al prelievo. Aggiornarla giorno per giorno (come un HWM) azzera per costruzione il guadagno misurato al checkpoint -> payout ~0. E' l'errore che questo modello ha commesso al primo giro ed e' stato corretto. """ n_days = years * 365 R = _paths(r, n_days, n_paths) * k eq = np.ones(n_paths) # equity normalizzata alla BASE del conto alive = np.ones(n_paths, bool) payout = np.zeros(n_paths) for t in range(n_days): eq = np.where(alive, eq * (1 + R[:, t]), eq) bust = alive & ((eq < 1.0 - max_loss) | (R[:, t] < -daily_loss)) alive &= ~bust if (t + 1) % 30 == 0: gain = np.where(alive & (eq > 1.0), eq - 1.0, 0.0) payout += gain * SPLIT eq = np.where(alive & (eq > 1.0), 1.0, eq) # profitto prelevato, base ripristinata usd = payout * PROP_NOTIONAL return dict(p_alive=float(alive.mean()), e_payout=float(usd.mean()), med_payout=float(np.median(usd)), eur_day_mean=float(usd.mean() * (1 - TAX_RATE) / EURUSD / 365.0), eur_day_med=float(np.median(usd) * (1 - TAX_RATE) / EURUSD / 365.0)) def main() -> None: print("=" * 104) print(" QUALE BOOK su un conto FUNDED — il 24/07 ha valutato solo il book a 2 sleeve") print("=" * 104) cfgs = build_configs() lo = common_window(cfgs) print(f"\n finestra comune (tutti gli sleeve esistono): dal {lo.date()}") print(" NB: livelli ASSOLUTI = TETTO (close-only; C-bis 24/07: i wick tagliano 6-37pp).") print(" Il DELTA fra config e' la misura onesta, ed e' CONSERVATIVO (B ha vol minore).\n") for win, wlabel in ((lo, "FINESTRA COMUNE (2024+)"), (None, "FINESTRA PIENA (outer-join)")): print("-" * 104) print(f" {wlabel}") print("-" * 104) stats = {} for name, v in cfgs.items(): s = v["full"] if win is None else v["full"][v["full"].index >= win] m = metrics(s) vol = float(s.std() * np.sqrt(365)) stats[name] = (s.values.astype(float), m, vol) print(f" {name:>28} Sharpe {m['sharpe']:>5.2f} CAGR {m['cagr']:>6.1%} " f"vol {vol:>5.1%} maxDD {m['maxdd']:>5.1%}") for rname, tgt, dd, dl in RULES: print(f"\n EVAL — {rname}") print(f" {'config':>28} {'leva':>5} {'P(pass)':>8} {'giorni med':>11}") for name, (r, m, vol) in stats.items(): for k in (0.75, 1.0, 1.5): e = sim_eval(r, tgt, dd, dl, k) print(f" {name:>28} {k:>5.2f} {e['p_pass']:>8.1%} " f"{e['median_days']:>11.0f}") print(f"\n FUNDED ${PROP_NOTIONAL:,.0f} — 1 anno, split {SPLIT:.0%}, prelievi mensili") print(f" {'config':>28} {'regola':>20} {'leva':>5} {'P(vivo)':>8} " f"{'E[payout]':>11} {'EUR/g atteso':>13}") for name, (r, m, vol) in stats.items(): for fname, ml, dl in FUNDED_RULES: for k in (0.5, 0.75, 1.0): f = sim_funded(r, ml, dl, k) print(f" {name:>28} {fname:>20} {k:>5.2f} {f['p_alive']:>8.1%} " f"${f['e_payout']:>10,.0f} {f['eur_day_mean']:>13.2f}") print() if __name__ == "__main__": main()