6f64845aa9
Addendum all'ondata rendita, su richiesta dell'operatore ("e hyrotrader").
CONSISTENCY 40%: ipotesi REFUTATA. Avevo previsto una tagliola (SKH01 ha P&L a scalino,
TP01 fa strappi di trend). Misurato: costo ~0pp a ogni leva e in entrambe le lenti,
perche' il book accumula il 10% in 130-300 giorni -> nessun giorno si avvicina al 40%
del cumulato. Prima di dichiararlo ho dovuto PROVARE che il controllo funziona (un
"costo 0" puo' essere un bug): su un book con profitto concentrato in 1-2 giorni la
regola porta P(pass) da >90% a <5%. Due iterazioni di test prima di avere un
discriminante valido (strappi ripetuti si diluiscono; sim_eval fa block-bootstrap,
non replay).
IL VINCOLO VERO E' IL maxDD 6% STATICO. Config A (book live TP01/SKH01) ha maxDD 9.5%
> 6% = strutturalmente incompatibile a leva piena: intraday a 1.0x sopravvive l'1.4%.
Config B (+XS01) ha maxDD 6.2% e Sharpe 1.13.
RACCOMANDAZIONE: config B a leva funded 0.50x (intraday P(vivo) 71% vs 32% a 0.75x;
E[payout] €7.69 vs €8.66/g — la sopravvivenza COMPONE su piu' anni, un conto bustato
smette di produrre per sempre). EV del biglietto $100k positivo in entrambe le lenti
(+$6.789 close-only, +$1.166 intraday), con ~69% di perdere la fee nella lente
pessimista. Cap $200k/trader => ~€15-30/g max: per €50/g servono piu' firm.
DISCREPANZA DICHIARATA col 24/07 (P(vivo) A@0.75x: 58% loro, 10% mio): il mio wick e' a
estrazione indipendente (piu' severo) e la finestra 2024+ e' quella dove A e' debole.
La direzione concorda (meno leva e' meglio); sul LIVELLO il numero del 24/07 (recon MTM
vero) e' piu' affidabile del mio.
Piu' l'addendum "10k su IB" al diario (analisi gia' committata in ea6743d).
Test: 217 pass (+3: consistency morde/non-morde, DD statico binding).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
211 lines
9.8 KiB
Python
211 lines
9.8 KiB
Python
"""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()
|