diff --git a/docs/diary/2026-07-24-goal-50eur-roadmap.md b/docs/diary/2026-07-24-goal-50eur-roadmap.md index 7d16c85..9b30292 100644 --- a/docs/diary/2026-07-24-goal-50eur-roadmap.md +++ b/docs/diary/2026-07-24-goal-50eur-roadmap.md @@ -141,6 +141,9 @@ tassi USD-EUR — hedged in EUR il "4.5%" reale è ~2.5%.)_ dei ritorni reali del book): de-luck ×0.6 a leva 1.0-1.5x → **P(pass) 55-68%, mediana 68-137 giorni**; funded $100k HYRO de-luck → P(vivo 1a) 69%, payout mediano €31/g SE vivo. La MC dell'agente (Sharpe 0.5-1.0, code t(4)) converge: P(pass) 40-72%. + ⚠️ **SUPERATO da C-bis (MC intraday, sera stessa)**: questi numeri sono close-only e + OTTIMISTI — coi wick intraday P(pass) de-luck ~44% a 1.0x, funded a 1.0x P(vivo) crolla + a 20.5% (leva ottima funded = 0.75x). Vedi sezione C-bis sotto. - **Attriti fuori dal MC** (che tagliano l'atteso): carry/swap, recycling dei bust (~40-55%/anno di P(bust) al limite 6%), consistency in eval, **controparte non regolata** (il settore ha perso il 13% delle firm nel solo 2024, $50M+ di payout stranded; profitti @@ -183,6 +186,56 @@ tassi USD-EUR — hedged in EUR il "4.5%" reale è ~2.5%.)_ mediana €5-15/g. In "qualche mese": ~€0. Il vincolo non è la strategia, è il **trust-capital** — e si accumula sullo stesso orologio del capitale. +### C-bis — MC INTRADAY wick-aware (addendum serale): chiuso il caveat n.1 — i wick +### tagliano 6-37pp di P(pass) e RIBALTANO la config consigliata + +Script `scripts/research/r0724_goal50_intraday_mc.py`. Il MC di parte C applicava le +regole alle CHIUSURE daily; le regole vere scattano sull'equity intraday. Qui l'equity +del book è ricostruita MTM a 1h sul feed certificato: TP01 = pesi daily (fee di +ribilanciamento incluse), SKH01 = trade 230m con exit AL LIVELLO SL/TP nell'ora del +trigger (SL prioritario, stessa convenzione di `backtest_signals`), wick per-barra +firmati col segno del peso. + +**Validazione della ricostruzione** (obbligatoria prima di credere ai numeri): +leg TP01 corr daily 0.9997 e log-total +0.833 vs sleeve×0.75 +0.827 (esatto); leg SKH +log-total +0.548 vs sleeve×0.25 +0.513 (Δ≈0.5%/anno, bordi orari); book Sharpe MTM 1.62 +vs 1.77 dello sleeve — il delta è l'ATTRIBUZIONE: lo sleeve SKH è equity-a-scalino +per-trade (P&L intero sulla barra d'ingresso), mentre un conto prop è marcato MTM → +la lens MTM è quella giusta per questo uso. ⚠️ Bug catturato dalla sanity al primo run: +`reindex` con indice tz-naive contro tz-aware → NaN→0 SILENZIOSI, TP01 sparito dal recon +(corr 0.43) — ennesima conferma della famiglia di lezioni pandas-tz del progetto. + +**Geometria dei wick del book**: gap (min intraday − chiusura) p50 −0.17pp, p10 −1.03pp, +p1 −2.7pp, worst −6pp; 2.6% dei giorni con wick oltre −2pp. Piccolo in mediana, ma le +regole prop sono sensibili proprio alla coda. + +**Eval (de-luck ×0.6, P(pass) close→intraday):** a leva 1.0x HYRO 58.8%→**44.7%**, +BREAKOUT 57.1%→**26.6%**, FTMO Swing 65.7%→**59.9%**. Tre conseguenze: +1. **La leva 1.5x consigliata dal MC close-only è REFUTATA**: intraday fa PEGGIO di 1.0x + (HYRO 36.0% vs 44.7%) — i wick amplificati bruciano il daily-loss prima che il drift + paghi. Config onesta: **eval a leva 1.0x**, mediana pass ~99g (HYRO) / ~125g (FTMO). +2. **Breakout Classic declassata**: il daily-loss 3% coi wick del book è una tagliola + (P(pass) 26.6%) — fuori dalla shortlist. +3. **FTMO Swing riabilitata dal lato regole**: il DD 10% assorbe i wick (haircut solo + −5.8pp) → miglior P(pass); resta il caveat swap CFD ~10-25%/anno NON nel MC, che + colpisce esattamente i multi-week hold del book. + +**Fase funded — sweep di leva (novità decisiva): la leva ottima è 0.75x, non 1x.** +A 1.0x col max-loss 6% HYRO l'account de-luck sopravvive 1 anno solo il **20.5%** delle +volte (il close-only diceva 69% — era il numero più gonfiato di tutta la parte C). +A **0.75x**: HYRO P(vivo) 58.1%, E[payout] $5.8k/anno (~€14.5/g ATTESO, mediana €20.6/g +se vivo); FTMO 0.75x P(vivo) 82.2%, E[payout] $7.4k lordo swap. Equivalente operativo: +girare il funded a target_vol ~15% invece del 20% canonico. + +**EV del biglietto aggiornato (eval 1.0x, funded 0.75x, de-luck):** P(pass) ~44%, +**P(perdere la fee) ~56%** (peggio del 35-45% stimato close-only), EV a 1 anno ++$491/$1.048/$2.197 per i conti 25/50/100k → il fronte prop resta EV-positivo ma più +sottile; il costo atteso per arrivare funded è ~$570-1.325 a seconda della taglia. + +Limiti dichiarati: wick a risoluzione 1h (i 5m sarebbero più severi → le P(pass) restano +un TETTO); leva applicata linearmente al giorno; bust funded = payout 0 (i prelievi +settimanali reali migliorano l'atteso). Test suite invariata (172 pass). + ## Roadmap — sintesi e decision tree ### Verdetto finale (nessuna modalità esclusa, tutte quantificate) @@ -208,12 +261,17 @@ proprio cresciuto con depositi (a €20k il book fa ~€5-8/g). Top-decile ≈ | 3 | Trigger regime CC01 (funding 30g, WARN 10%/ALERT 15%) | `scripts/live/cc01_regime_watch.py` | | 4 | Fix test latente paper-advance (pre-esistente su main) | `tests/test_trend_portfolio.py` | | 5 | Questo diario | `docs/diary/2026-07-24-goal-50eur-roadmap.md` | +| 6 | MC prop INTRADAY wick-aware (recon MTM 1h del book) | `scripts/research/r0724_goal50_intraday_mc.py` | ### Decisioni che spettano all'operatore (non automatizzabili) -1. **Fronte prop** (l'unico che cambia la scala in mesi): sì/no. Se sì: HyroTrader - $25-50k come test (€230-350), poi $100k; leva eval 1.0-1.5x; prelievi settimanali; - budget mentale = perdere l'intera fee con P~35-45%. Il book si esegue a mano (5 min/g) +1. **Fronte prop** (l'unico che cambia la scala in mesi): sì/no. Se sì (numeri C-bis, + MC intraday): HyroTrader $25-50k come test (€230-350), poi $100k; **eval a leva 1.0x + (NON 1.5x — refutata dai wick), funded a 0.75x** (≈ target_vol 15%); prelievi + settimanali; budget mentale = perdere l'intera fee con **P~56%** de-luck; atteso + funded ~€14.5/g a $100k. Alternativa con P(pass) migliore: FTMO Swing (DD 10% + assorbe i wick) MA swap CFD 10-25%/anno sui multi-week hold. Breakout Classic NO + (daily-loss 3% + wick = P(pass) 27%). Il book si esegue a mano (5 min/g) dal segnale di `paper_portfolio` finché non c'è l'API funded. 2. **Track record pubblico** (canale 18-36 mesi): aprire Darwinex Zero (~€40-50/mese, l'unico venue dove lo Sharpe difensivo compete sul merito) e/o candidarsi lead trader diff --git a/scripts/research/r0724_goal50_intraday_mc.py b/scripts/research/r0724_goal50_intraday_mc.py new file mode 100644 index 0000000..ca3b289 --- /dev/null +++ b/scripts/research/r0724_goal50_intraday_mc.py @@ -0,0 +1,358 @@ +"""r0724_goal50_intraday_mc — MC prop-firm con regole sui WICK INTRADAY (2026-07-24). + +CHIUDE il caveat n.1 di r0724_goal50_math.py parte C: quel MC applicava daily-loss e +max-drawdown alle CHIUSURE giornaliere del book, ma le regole vere delle firm scattano +sull'equity INTRADAY mark-to-market (il wick conta). Qui: + + 1) ricostruzione MTM a 1h dell'equity del book Deribit (TP01 75% pesi daily + SKH01 25% + posizioni 230m con exit AL LIVELLO SL/TP nell'ora del trigger, SL prioritario — stessa + convenzione di backtest_signals), wick per-barra inclusi (low/high firmati col segno + del peso), fee incluse (TP01 fee_side*|Dheld|, SKH 0.10% RT a fine trade); + 2) per ogni giorno UTC: R_d (chiusura MTM) e m_d (minimo intraday cumulato). Il MC gira + su TUPLE (R_d, m_d) della STESSA ricostruzione -> chiusure e wick coerenti tra loro. + NB: il daily MTM e' la lens GIUSTA per un conto prop (l'equity del conto e' marcata + intraday); lo sleeve SKH ufficiale ha attribuzione a scalino per-trade (P&L intero + sulla barra d'ingresso, caveat noto "equity daily-step") -> la corr daily recon-vs- + sleeve su SKH e' strutturalmente bassa; la sanity giusta su SKH e' il TOTALE. + 3) block-bootstrap sulle tuple: stesse regole/leve/de-luck del MC daily-close; breach + valutato su m. Confronto diretto close-only vs intraday = "wick haircut" in pp; + 4) fase funded intraday-aware + EV del biglietto per taglia ($25k/$50k/$100k HYRO). + +APPROSSIMAZIONI DICHIARATE: (a) wick a risoluzione 1h (i 5m sarebbero piu' profondi -> +le P(pass) restano un TETTO, ma molto piu' oneste del close-only); (b) confini barra +230m non allineati all'ora -> piccoli edge sub-orari; (c) leva applicata linearmente al +giorno (ok per lev <= 2); (d) trigger SL/TP ricercato sulle H/L orarie dentro la barra +230m d'uscita, fallback = cap all'ultima ora della barra. + +Uso: `uv run python scripts/research/r0724_goal50_intraday_mc.py` (~1-2 min: SKH 5m full) +""" +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)) + +from src.data.downloader import load_data # noqa: E402 +from src.portfolio.portfolio import StrategyPortfolio, metrics # noqa: E402 +from src.portfolio.sleeves import deribit_book_sleeves # noqa: E402 +from src.strategies.skyhook import SKH01_V2_DD, build_frames, skyhook_entries # noqa: E402 +from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio, resample_1d # noqa: E402 + +EURUSD = 1.09 +RNG = np.random.default_rng(724) +ASSETS = ("BTC", "ETH") +W_TP, W_SKH = 0.75, 0.25 # pesi del book live Deribit (deribit_book_sleeves) +SKH_FEE_RT = 0.001 + + +# ------------------------------------------------------------------ esposizioni + +def tp01_daily_frame(asset: str) -> pd.DataFrame: + """Per giorno UTC: peso TP01 tenuto (deciso a close[d-1]) e fee del ribilanciamento, + gia' scalati al leg (0.5 del book TP; il peso di book W_TP si applica fuori).""" + tp = TrendPortfolio(**CANONICAL) + df = resample_1d(load_data(asset, "1h")) + tgt = tp.target_series(df) + held = np.zeros(len(tgt)) + held[1:] = tgt[:-1] + fee = tp.fee_side * np.abs(np.diff(held, prepend=0.0)) + # tz-aware UTC come l'indice 1h del chiamante: un mismatch naive/aware nel reindex + # darebbe NaN->0 silenziosi (TP01 sparirebbe dal recon — visto al primo run, corr 0.43) + days = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True).dt.floor("D")) + return pd.DataFrame({"w": held * 0.5, "fee": fee * 0.5}, index=days) + + +def skh_hourly_contrib(asset: str, idx1h: pd.DatetimeIndex, close: np.ndarray, + low: np.ndarray, high: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """Contributo del leg SKH01 (peso W_SKH*0.5) a ritorno e wick di ogni barra 1h. + + Replica trade-per-trade la logica di sleeves._skyhook_positions/backtest_signals: + entry a close 230m [i], exit alla prima barra 230m che tocca SL (prioritario) o TP + — AL LIVELLO — oppure a close dopo max_bars. Qui il trade e' marcato a mercato ora + per ora; nell'ora del trigger il ritorno e' cappato al livello e la posizione muore. + Fee RT sottratta nell'ora d'uscita.""" + ltf, htf = build_frames(load_data(asset, "5m")) + ent = skyhook_entries(ltf, htf, SKH01_V2_DD) + H = ltf["high"].values + L = ltf["low"].values + times = pd.DatetimeIndex(pd.to_datetime(ltf["datetime"], utc=True)) + bar_td = times[1] - times[0] + n = len(ltf) + w = W_SKH * 0.5 + ret = np.zeros(len(idx1h)) + wick = np.zeros(len(idx1h)) + + i = 0 + while i < n: + e = ent[i] + if e is None: + i += 1 + continue + d, sl, tp_, mb = e["dir"], e["sl"], e["tp"], e["max_bars"] + exit_idx, exit_mode = None, None + for s in range(1, mb + 1): + j = i + s + if j >= n: + break + hit_sl = (L[j] <= sl) if d == 1 else (H[j] >= sl) + hit_tp = (H[j] >= tp_) if d == 1 else (L[j] <= tp_) + if hit_sl or hit_tp or s == mb: + exit_idx = j + exit_mode = "sl" if hit_sl else ("tp" if hit_tp else "time") + break + if exit_idx is None: + exit_idx, exit_mode = n - 1, "open" # trade ancora aperto a fine dati + + t0 = times[i] + bar_td # entry a close della barra i + t1 = times[exit_idx] + bar_td # fine della barra d'uscita + h0 = int(idx1h.searchsorted(t0, side="left")) + h1 = int(idx1h.searchsorted(t1, side="left")) + trig_from = int(idx1h.searchsorted(times[exit_idx], side="left")) + done = False + last_h = None + for h in range(max(h0, 1), h1): + prev_c = close[h - 1] + r_h = close[h] / prev_c - 1.0 + wk_h = (low[h] / prev_c - 1.0) if d == 1 else (high[h] / prev_c - 1.0) + if exit_mode in ("sl", "tp") and h >= trig_from: + hit_sl_h = (low[h] <= sl) if d == 1 else (high[h] >= sl) + hit_tp_h = (high[h] >= tp_) if d == 1 else (low[h] <= tp_) + if hit_sl_h: # SL prioritario, come backtest_signals + lvl = sl + wk_h = sl / prev_c - 1.0 # il modello esce al livello: wick cappato + elif hit_tp_h: + lvl = tp_ # wick avverso dell'ora resta (adverse-first) + else: + lvl = None + if lvl is not None: + ret[h] += w * d * (lvl / prev_c - 1.0) - w * SKH_FEE_RT + wick[h] += w * d * wk_h + done = True + break + ret[h] += w * d * r_h + wick[h] += w * d * wk_h + last_h = h + if exit_mode in ("sl", "tp") and not done and last_h is not None: + # trigger non trovato sulle H/L orarie (bordi sub-orari): forza il cap + # all'ultima ora -> P&L coerente col prezzo d'uscita modellato + prev_c = close[last_h - 1] + lvl = sl if exit_mode == "sl" else tp_ + ret[last_h] += w * d * (lvl / prev_c - 1.0) - w * d * (close[last_h] / prev_c - 1.0) \ + - w * SKH_FEE_RT + elif exit_mode == "time" and last_h is not None: + ret[last_h] -= w * SKH_FEE_RT + if exit_mode == "open": + break + i = exit_idx + 1 + return ret, wick + + +def book_intraday_days() -> tuple[pd.DataFrame, dict]: + """Per giorno UTC: R_d (chiusura MTM netta del book ricostruito) e m_d (min intraday). + Ritorna anche le serie daily per-leg per la sanity.""" + book_r = None + book_wk = None + legs: dict[str, pd.Series] = {} + for a in ASSETS: + df = load_data(a, "1h") + idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)) + close = df["close"].values.astype(float) + low = df["low"].values.astype(float) + high = df["high"].values.astype(float) + prev = np.concatenate(([np.nan], close[:-1])) + r_close = close / prev - 1.0 + # --- TP01: peso costante nel giorno, fee alla prima ora del giorno + tpf = tp01_daily_frame(a) + day_of = idx.floor("D") + w_tp = tpf["w"].reindex(day_of).values * W_TP + w_tp = np.nan_to_num(w_tp) + fee_tp = tpf["fee"].reindex(day_of).values * W_TP + fee_tp = np.nan_to_num(fee_tp) + first_of_day = np.concatenate(([True], day_of[1:] != day_of[:-1])) + r_wick_tp = np.where(w_tp >= 0, low / prev - 1.0, high / prev - 1.0) + tp_ret = w_tp * r_close - np.where(first_of_day, fee_tp, 0.0) + tp_wick = w_tp * r_wick_tp - np.where(first_of_day, fee_tp, 0.0) + # --- SKH01: MTM per-trade con exit al livello + skh_ret, skh_wick = skh_hourly_contrib(a, idx, close, low, high) + tot_r = pd.Series(tp_ret + skh_ret, index=idx) + tot_wk = pd.Series(tp_wick + skh_wick, index=idx) + legs[f"tp_{a}"] = pd.Series(tp_ret, index=idx) + legs[f"skh_{a}"] = pd.Series(skh_ret, index=idx) + book_r = tot_r if book_r is None else book_r.add(tot_r, fill_value=np.nan) + book_wk = tot_wk if book_wk is None else book_wk.add(tot_wk, fill_value=np.nan) + book_r = book_r.dropna() + book_wk = book_wk.reindex(book_r.index) + + rows = [] + for day, g in book_r.groupby(book_r.index.floor("D")): + r = g.values + wk = book_wk.loc[g.index].values + cum = np.cumprod(1 + r) + cum_prev = np.concatenate(([1.0], cum[:-1])) + m = min(cum.min(), (cum_prev * (1 + wk)).min()) - 1.0 + rows.append((day, cum[-1] - 1.0, min(m, cum[-1] - 1.0))) + out = pd.DataFrame(rows, columns=["day", "R", "m"]).set_index("day") + return out, legs + + +# ------------------------------------------------------------------ Monte Carlo + +def _paths(R: np.ndarray, gap: np.ndarray, n_days: int, n_paths: int, block: int = 20, + drift_scale: float = 1.0) -> tuple[np.ndarray, np.ndarray]: + """Bootstrap a blocchi sulle TUPLE (R, gap) — il wick resta accoppiato al suo giorno.""" + mu = R.mean() + R_adj = (R - mu) + mu * drift_scale + n = len(R_adj) + n_blocks = int(np.ceil(n_days / block)) + starts = RNG.integers(0, n - block, size=(n_paths, n_blocks)) + idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days] + return R_adj[idx], gap[idx] + + +def _sim_eval(R: np.ndarray, m: np.ndarray, target: float, max_dd: float, + daily_loss: float, max_days: int, intraday: bool) -> dict: + """Eval: pass se la chiusura tocca 1+target prima di un breach. Breach su minimo + intraday (m) se intraday=True, altrimenti su chiusura (R) come nel MC vecchio. + Pareggio stesso giorno -> vince il breach (il minimo precede quasi sempre la chiusura).""" + worst = m if intraday else R + eq = np.cumprod(1 + R, axis=1) + eq_start = eq / (1 + R) + dd_breach = eq_start * (1 + worst) < (1 - max_dd) + dl_breach = worst < -daily_loss + fail = dd_breach | dl_breach + passed = eq >= 1 + target + first_fail = np.where(fail.any(axis=1), fail.argmax(axis=1), max_days + 1) + first_pass = np.where(passed.any(axis=1), passed.argmax(axis=1), max_days + 1) + ok = first_pass < first_fail + return {"p_pass": ok.mean(), + "median_days": float(np.median(first_pass[ok])) if ok.any() else np.nan} + + +RULES = [ + ("HYRO 1-step: tgt 10% / DD 6% st / daily 4%", 0.10, 0.06, 0.04), + ("BREAKOUT Classic: tgt 10% / DD 6% st / dl 3%", 0.10, 0.06, 0.03), + ("FTMO Swing: tgt 10% / DD 10% st / daily 5%", 0.10, 0.10, 0.05), +] +FUNDED_RULES = [("HYRO (max loss 6% st, daily 4%)", 0.06, 0.04), + ("FTMO (max loss 10% st, daily 5%)", 0.10, 0.05)] +# biglietti HyroTrader 1-step (ricerca R2): fee rimborsata al primo payout se funded +TICKETS = [(25_000, 249.0), (50_000, 379.0), (100_000, 579.0)] + + +def main() -> None: + print("costruzione MTM intraday del book (TP01 daily + SKH01 230m exit-al-livello, 1h)...") + intr, legs = book_intraday_days() + intr = intr[intr.index >= "2019-03-01"] + R = intr["R"].to_numpy() + m = intr["m"].to_numpy() + gap = m - R + + # ---- sanity vs sleeve ufficiali + port = StrategyPortfolio(deribit_book_sleeves()) + book = port.combined_daily().dropna() + book.index = pd.to_datetime(book.index, utc=True).floor("D") + book = book[book.index >= "2019-03-01"] + both = intr.index.intersection(book.index) + corr = float(np.corrcoef(R[intr.index.isin(both)], book.loc[both].to_numpy())[0, 1]) + mm_rec = metrics(intr.loc[both, "R"]) + mm_book = metrics(book.loc[both]) + tp_daily = ((legs["tp_BTC"] + legs["tp_ETH"]).groupby( + (legs["tp_BTC"] + legs["tp_ETH"]).index.floor("D")).apply(lambda g: float(np.prod(1 + g) - 1))) + skh_daily = ((legs["skh_BTC"] + legs["skh_ETH"]).groupby( + (legs["skh_BTC"] + legs["skh_ETH"]).index.floor("D")).apply(lambda g: float(np.prod(1 + g) - 1))) + print("=" * 100) + print(" RICOSTRUZIONE MTM — sanity e geometria dei wick") + print("=" * 100) + print(f" giorni: {len(both)} ({both[0].date()} -> {both[-1].date()})") + print(f" corr daily recon vs book certificato: {corr:.3f} " + "(SKH sleeve = equity a scalino per-trade -> <1 atteso)") + print(f" Sharpe/maxDD recon MTM {mm_rec['sharpe']:.2f}/{mm_rec['maxdd']:.1%} " + f"vs book certificato {mm_book['sharpe']:.2f}/{mm_book['maxdd']:.1%}") + sl_tot = {} + for s in port.sleeves: + sd = s.daily() + sd.index = pd.to_datetime(sd.index, utc=True).floor("D") + sl_tot[s.name] = float(np.log1p(sd[sd.index >= "2019-03-01"]).sum()) + print(f" log-total leg TP: recon {np.log1p(tp_daily).sum():+.3f} vs sleeve x{W_TP} " + f"{sl_tot['TP01_trend_1d'] * W_TP:+.3f} | leg SKH: recon {np.log1p(skh_daily).sum():+.3f} " + f"vs sleeve x{W_SKH} {sl_tot['SKH01_skyhook'] * W_SKH:+.3f}") + q = np.percentile(gap, [50, 10, 1]) + print(f" gap wick (min intraday - chiusura): p50 {q[0]*100:.2f}pp p10 {q[1]*100:.2f}pp " + f"p1 {q[2]*100:.2f}pp worst {gap.min()*100:.2f}pp") + print(f" giorni con wick oltre -2pp sotto la chiusura: {(gap < -0.02).mean():.1%}") + if corr < 0.80 or abs(mm_rec["sharpe"] - mm_book["sharpe"]) > 0.5: + print(" !! recon troppo lontana dal book certificato: NON credere ai numeri sotto") + + n_paths, max_days = 20_000, 365 + print("\n" + "=" * 100) + print(" EVAL MC — regole sui WICK INTRADAY vs sole chiusure (stesse path, stesso seed)") + print("=" * 100) + for drift_scale, lab in ((1.0, "book modellato"), (0.6, "de-luck x0.6 (onesto)")): + Rp, Gp = _paths(R, gap, max_days, n_paths, drift_scale=drift_scale) + for lev in (1.0, 1.5, 2.0): + R_l, m_l = Rp * lev, (Rp + Gp) * lev + print(f"\n --- {lab}, leva x{lev:.1f} ---") + for name, tgt, dd, dl in RULES: + a = _sim_eval(R_l, m_l, tgt, dd, dl, max_days, intraday=False) + b = _sim_eval(R_l, m_l, tgt, dd, dl, max_days, intraday=True) + d = f"{b['median_days']:.0f}g" if np.isfinite(b["median_days"]) else "n/a" + print(f" {name:<44} close {a['p_pass']:>5.1%} -> intraday {b['p_pass']:>5.1%} " + f"(wick {100*(b['p_pass']-a['p_pass']):+.1f}pp) mediana {d}") + + print("\n" + "=" * 100) + print(" FASE FUNDED intraday-aware — $100k, split 80%, 1 anno, SWEEP DI LEVA") + print(" (bust=0 payout: conservativo; con DD 6% la domanda giusta e' se girare SOTTO 1x)") + print("=" * 100) + ev_inputs = {} + for drift_scale, lab in ((1.0, "book modellato"), (0.6, "de-luck x0.6")): + Rp, Gp = _paths(R, gap, 365, n_paths, drift_scale=drift_scale) + for fname, dd_lim, dl_lim in FUNDED_RULES: + best = (None, -1.0) + for lev in (0.5, 0.75, 1.0, 1.25): + Rl, ml = Rp * lev, (Rp + Gp) * lev + eq = np.cumprod(1 + Rl, axis=1) + eq_start = eq / (1 + Rl) + blown = (eq_start * (1 + ml) < 1 - dd_lim) | (ml < -dl_lim) + alive = ~blown.any(axis=1) + pnl = (eq[:, -1] - 1) * 100_000 * 0.80 + qq = np.percentile(pnl[alive], [10, 50, 90]) if alive.any() else [np.nan] * 3 + e_payout = float(np.where(alive, np.maximum(pnl, 0.0), 0.0).mean()) + if e_payout > best[1]: + best = (lev, e_payout) + print(f" {lab:<16} {fname:<32} lev {lev:.2f} P(vivo 1a) {alive.mean():>5.1%} " + f"p10/p50/p90 EUR {qq[0]/EURUSD/365:>5.1f} / {qq[1]/EURUSD/365:>5.1f} / " + f"{qq[2]/EURUSD/365:>5.1f} /g E[payout] ${e_payout:,.0f}") + print(f" {'':<16} {fname:<32} -> lev ottima {best[0]:.2f} (E[payout] ${best[1]:,.0f})") + if fname.startswith("HYRO"): + ev_inputs[lab] = best[1] + + print("\n" + "=" * 100) + print(" EV DEL BIGLIETTO (HYRO: eval a leva x1.0, funded a leva ottima; regole intraday;") + print(" fee rimborsata al pass)") + print("=" * 100) + for drift_scale, lab in ((1.0, "book modellato"), (0.6, "de-luck x0.6")): + Rp, Gp = _paths(R, gap, max_days, n_paths, drift_scale=drift_scale) + R_l, m_l = Rp * 1.0, (Rp + Gp) * 1.0 + res = _sim_eval(R_l, m_l, 0.10, 0.06, 0.04, max_days, intraday=True) + p = res["p_pass"] + e100 = ev_inputs[lab] + print(f"\n {lab}: P(pass) {p:.1%}") + for size, fee in TICKETS: + e_pay = e100 * size / 100_000 + ev = p * (e_pay + fee) - fee + cost_funded = fee / p if p > 0 else np.nan + print(f" ${size//1000}k (fee ${fee:.0f}): EV 1a = ${ev:>8,.0f} " + f"costo atteso per arrivare funded ${cost_funded:,.0f} " + f"P(perdere la fee) {1-p:.0%}") + print("\n NB: EV lordo di tasse (33%) e attriti fuori-MC (consistency, recycling bust,") + print(" controparte non regolata). Bust funded = payout 0 (prelievi settimanali reali") + print(" lo migliorano). I wick sono a risoluzione 1h: i 5m sarebbero ~piu' severi.") + + +if __name__ == "__main__": + main()