"""r0725_hyro — HYROTRADER nello specifico: config, leva e la regola di CONSISTENCY (2026-07-25). La ricerca del 24/07 ha messo HyroTrader prima in classifica per QUESTO book: * unica con **API reale** da funded (sub-account Bybit) -> il book gira automatico, non a mano; * **nessun limite di tempo** sull'eval -> una strategia lenta non e' penalizzata; * **weekend consentito** -> non banale: l'ondata 2026-07-17 ha misurato che il weekend porta il **38% del gross di TP01** nel 31% del tempo (de-esporlo e' attivamente dannoso); * funding = mercato (stessa economia del book Deribit), fee rimborsata al pass, payout ~12h. Biglietti 1-step: $25k/$249, $50k/$379, $100k/$579. Cap $200k per trader. Ma ha due regole che mordono ESATTAMENTE questo book, e nessuna analisi le aveva modellate: 1. **max drawdown 6% STATICO** (dal saldo iniziale) — il book de-luckato ha maxDD 15.4% a 1.0x; 2. **CONSISTENCY 40% in eval**: nessun singolo giorno puo' valere piu' del 40% del profitto totale. E' una tagliola per un book LENTO e GRUMOSO: SKH01 ha P&L per-trade a scalino (caveat noto "equity daily-step") e TP01 fa mesi piatti seguiti da strappi di trend. Se il target del 10% arriva con 2-3 giornatone, l'eval **non passa**: si deve continuare a tradare finche' il profitto totale si diluisce — esponendosi altro tempo al DD 6%. Questo script misura: P(pass) con e senza consistency, la leva ottima, e l'EV di ogni biglietto. ONESTA': doppia lente. CLOSE-ONLY = tetto ottimista; INTRADAY = pavimento (gap lognormale calibrato sul recon MTM 1h del 24/07, ma estratto indipendente dal rendimento del giorno -> breach spuri: vedi il caveat in r0725_prop_ladder.py). La verita' sta in mezzo. Finestra comune 2024+ (dove esiste XS01) = anche la finestra di scoperta di XS01 -> la config B e' favorita. Uso: `uv run python scripts/research/r0725_hyro.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 from r0725_prop_ladder import WICK_MU, WICK_SIGMA, WICK_REF_VOL # noqa: E402 # --- regole HyroTrader 1-step (ricerca 24/07, fonti primarie) EV_TARGET, EV_DD, EV_DL = 0.10, 0.06, 0.04 FU_ML, FU_DL = 0.06, 0.04 CONSISTENCY = 0.40 # nessun giorno > 40% del profitto totale (solo in eval) SPLIT = 0.80 TICKETS = [(25_000, 249.0), (50_000, 379.0), (100_000, 579.0)] CAP = 200_000 DELUCK = 0.6 RNG = np.random.default_rng(20260725) 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 configs() -> dict: skh = _d(skl.skh_book(0, "hourly")) tp = _d(tp01_realistic(100_000 * 0.75)) xs = _d(_xsec_returns()).resample("1D").apply(lambda x: (1 + x).prod() - 1) lo = max(tp.index.min(), skh.index.min(), xs.index.min()) out = {} for name, cols, w in ( ("A book live (TP01/SKH01)", {"TP01": tp, "SKH01": skh}, {"TP01": .75, "SKH01": .25}), ("B + XS01 (19 alt perp)", {"TP01": tp, "SKH01": skh, "XS01": xs}, {"TP01": .55, "SKH01": .20, "XS01": .25}), ): s = combine_outer(cols, w, lo=lo) s = s - (1.0 - DELUCK) * float(s.mean()) out[name] = s return out def _paths(r: np.ndarray, n_days: int, n_paths: int, block: int = 20) -> np.ndarray: 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] return r[idx] def _wick(shape, vol: float) -> np.ndarray: scale = float(np.clip(vol / WICK_REF_VOL, 0.0, 3.0)) return -np.exp(RNG.normal(WICK_MU, WICK_SIGMA, size=shape)) * scale def sim_eval(r: np.ndarray, k: float, consistency: bool, intraday: bool, max_days: int = 540, n_paths: int = 8000) -> dict: """Eval HYRO. Il pass richiede equity >= 1+target E (se consistency) che il MIGLIOR giorno non superi il 40% del profitto cumulato. Breach su equity intraday.""" R = _paths(r, max_days, n_paths) * k G = _wick(R.shape, float(r.std() * np.sqrt(365)) * k) if intraday else np.zeros_like(R) eq = np.ones(n_paths) best_day = np.zeros(n_paths) # miglior guadagno giornaliero in USD-equity alive = np.ones(n_paths, bool) passed = np.zeros(n_paths, bool) day_pass = np.full(n_paths, max_days + 1, int) for t in range(max_days): prev = eq eq = np.where(alive & ~passed, eq * (1 + R[:, t]), eq) low = np.where(alive & ~passed, prev * (1 + R[:, t] + G[:, t]), eq) gain = np.maximum(eq - prev, 0.0) best_day = np.where(alive & ~passed, np.maximum(best_day, gain), best_day) bust = alive & ~passed & ((low < 1.0 - EV_DD) | ((R[:, t] + G[:, t]) < -EV_DL)) alive &= ~bust profit = eq - 1.0 ok = alive & ~passed & (profit >= EV_TARGET) if consistency: ok &= best_day <= CONSISTENCY * np.maximum(profit, 1e-9) day_pass[ok] = t passed |= ok return dict(p_pass=float(passed.mean()), median_days=float(np.median(day_pass[passed])) if passed.any() else float("nan")) def sim_funded(r: np.ndarray, k: float, intraday: bool, notional: float, years: int = 1, n_paths: int = 8000) -> dict: n_days = years * 365 R = _paths(r, n_days, n_paths) * k G = _wick(R.shape, float(r.std() * np.sqrt(365)) * k) if intraday else np.zeros_like(R) eq = np.ones(n_paths) alive = np.ones(n_paths, bool) payout = np.zeros(n_paths) for t in range(n_days): prev = eq eq = np.where(alive, eq * (1 + R[:, t]), eq) low = np.where(alive, prev * (1 + R[:, t] + G[:, t]), eq) bust = alive & ((low < 1.0 - FU_ML) | ((R[:, t] + G[:, t]) < -FU_DL)) alive &= ~bust if (t + 1) % 30 == 0: g = np.where(alive & (eq > 1.0), eq - 1.0, 0.0) payout += g * SPLIT eq = np.where(alive & (eq > 1.0), 1.0, eq) usd = payout * notional return dict(p_alive=float(alive.mean()), e_payout=float(usd.mean()), eur_day=float(usd.mean() * (1 - TAX_RATE) / EURUSD / 365.0)) def main() -> None: cfg = configs() print("=" * 100) print(" HYROTRADER — config, leva e la regola di CONSISTENCY 40%") print("=" * 100) print(f" regole: target {EV_TARGET:.0%} | maxDD {EV_DD:.0%} STATICO | daily {EV_DL:.0%} | " f"consistency {CONSISTENCY:.0%} (eval) | split {SPLIT:.0%} | cap ${CAP:,}") for n, s in cfg.items(): m = metrics(s) print(f" {n:>28}: Sharpe {m['sharpe']:>5.2f} CAGR {m['cagr']:>6.1%} " f"vol {s.std()*np.sqrt(365):>5.1%} maxDD {m['maxdd']:>5.1%}") for intraday in (False, True): lens = "INTRADAY (pavimento)" if intraday else "CLOSE-ONLY (tetto)" print("\n" + "-" * 100) print(f" EVAL — {lens}: quanto costa la regola di CONSISTENCY") print("-" * 100) print(f" {'config':>28} {'leva':>5} {'P(pass) senza':>14} {'P(pass) CON':>12} " f"{'costo regola':>13} {'giorni med':>11}") for n, s in cfg.items(): r = s.values.astype(float) for k in (0.5, 0.75, 1.0): a = sim_eval(r, k, consistency=False, intraday=intraday, n_paths=4000) b = sim_eval(r, k, consistency=True, intraday=intraday, n_paths=4000) print(f" {n:>28} {k:>5.2f} {a['p_pass']:>14.1%} {b['p_pass']:>12.1%} " f"{b['p_pass']-a['p_pass']:>+13.1f}pp {b['median_days']:>11.0f}") print(f"\n FUNDED $100k — {lens}") print(f" {'config':>28} {'leva':>5} {'P(vivo 1a)':>11} {'E[payout]':>11} {'EUR/g':>7}") for n, s in cfg.items(): r = s.values.astype(float) for k in (0.5, 0.75, 1.0): f = sim_funded(r, k, intraday, 100_000.0, n_paths=4000) print(f" {n:>28} {k:>5.2f} {f['p_alive']:>11.1%} ${f['e_payout']:>10,.0f} " f"{f['eur_day']:>7.2f}") # --- EV del biglietto (lente intraday = quella su cui si decide, piu' close-only come tetto) print("\n" + "-" * 100) print(" EV DEL BIGLIETTO — config B, eval a leva 1.0x, funded a leva ottima, 1 anno") print(" (la fee e' rimborsata al pass: il costo atteso e' fee x P(fallire l'eval))") print("-" * 100) r = cfg["B + XS01 (19 alt perp)"].values.astype(float) for intraday in (False, True): lens = "intraday" if intraday else "close-only" pe = sim_eval(r, 1.0, consistency=True, intraday=intraday, n_paths=4000)["p_pass"] best = None for k in (0.5, 0.75, 1.0): f = sim_funded(r, k, intraday, 100_000.0, n_paths=4000) if best is None or f["e_payout"] > best[1]["e_payout"]: best = (k, f) print(f"\n [{lens}] P(pass eval) {pe:.1%} | leva funded ottima {best[0]:.2f}x " f"(P(vivo) {best[1]['p_alive']:.1%})") print(f" {'biglietto':>12} {'prezzo':>8} {'E[payout] 1a':>14} {'costo atteso':>13} " f"{'EV':>10} {'EUR/g atteso':>13}") for size, price in TICKETS: scale = size / 100_000.0 ev_payout = pe * best[1]["e_payout"] * scale cost = price * (1.0 - pe) # la fee e' rimborsata al pass -> si perde solo se si fallisce ev = ev_payout - cost print(f" ${size:>11,} ${price:>7,.0f} ${ev_payout:>13,.0f} " f"${cost:>12,.0f} ${ev:>9,.0f} {ev*(1-TAX_RATE)/EURUSD/365:>13.2f}") if __name__ == "__main__": main()