research(hyro): HyroTrader nello specifico — consistency 40% refutata, DD 6% e' il vincolo

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>
This commit is contained in:
Adriano Dal Pastro
2026-07-25 17:10:26 +00:00
parent ea6743d6b0
commit 6f64845aa9
4 changed files with 333 additions and 0 deletions
+15
View File
@@ -496,6 +496,21 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
rendita, taglia il maxDD 15.3→12.0%, unico split che regge coi costi). ⚠ Il confronto FAVORISCE il rendita, taglia il maxDD 15.3→12.0%, unico split che regge coi costi). ⚠ Il confronto FAVORISCE il
crypto per costruzione (7 anni con 2 bull vs 30 anni di GTAA); tassi e aliquote (26%/33%) sono crypto per costruzione (7 anni con 2 bull vs 30 anni di GTAA); tassi e aliquote (26%/33%) sono
assunzioni dichiarate, da confermare col commercialista (non un parere fiscale). assunzioni dichiarate, da confermare col commercialista (non un parere fiscale).
(7) **HYROTRADER nello specifico** (`r0725_hyro.py`): la firm meglio classificata per questo book
(API reale da funded, nessun limite di tempo, **weekend consentito** — e il weekend porta il 38%
del gross di TP01). **Ipotesi REFUTATA: la regola di consistency 40% NON morde** (costo ≈0pp a
ogni leva/lente) 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% (test cablato). **Il vincolo vero e' il maxDD 6% STATICO**: config A (book
live) 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%, Sharpe 1.13. **Funded consigliato: config B
a 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). EV del biglietto $100k **positivo in entrambe le lenti** (+$6.789
close-only, +$1.166 intraday), ma ~69% di perdere la fee nella lente pessimista. Cap $200k/trader
⇒ ~€15-30/g max → per €50/g servono piu' firm (punto 5). ⚠ Discrepanza col 24/07 (P(vivo) A@0.75x
58% vs 10% mio): mio wick indipendente = piu' severo + finestra 2024+ dove A e' debole; **la
direzione concorda, sul LIVELLO il numero del 24/07 (recon MTM vero) e' piu' affidabile**.
⚠ Bug catturato in sessione: base di prelievo funded aggiornata giornalmente come HWM mobile → ⚠ Bug catturato in sessione: base di prelievo funded aggiornata giornalmente come HWM mobile →
guadagno al checkpoint ≈ 0 → **payout $230/anno invece di ~$7.000**; regole vere = max-loss STATICO guadagno al checkpoint ≈ 0 → **payout $230/anno invece di ~$7.000**; regole vere = max-loss STATICO
dal saldo iniziale + base ripristinata dopo il prelievo. Test di regressione cablato. dal saldo iniziale + base ripristinata dopo il prelievo. Test di regressione cablato.
@@ -289,6 +289,69 @@ vanno confermate col commercialista (**non è un parere fiscale**).
--- ---
## 8) HYROTRADER nello specifico (`r0725_hyro.py`)
La firm che il 24/07 aveva messo prima in classifica per *questo* book: unica con **API reale** da
funded (sub-account Bybit) → il book gira automatico; **nessun limite di tempo** sull'eval (una
strategia lenta non è penalizzata); **weekend consentito** — non banale, l'ondata 2026-07-17 ha
misurato che il weekend porta il **38% del gross di TP01**. Biglietti $25k/$249, $50k/$379,
$100k/$579, cap $200k, split 80%, fee rimborsata al pass.
Due regole mordono proprio noi, e nessuna analisi le aveva modellate: **maxDD 6% STATICO** e
**consistency 40%** (nessun giorno > 40% del profitto totale, in eval).
**Ipotesi refutata: la consistency NON morde.** 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.
Ragione strutturale: il book accumula il 10% in **130-300 giorni**, quindi nessun singolo giorno
si avvicina al 40% del cumulato. ⚠️ Prima di dichiararlo ho dovuto **provare che il controllo
funziona** (un "costo 0" può essere un bug): `test_consistency_MORDE_su_un_book_grumoso` mostra
che su un book con profitto concentrato in 1-2 giorni la regola porta P(pass) da >90% a <5%. Il
meccanismo c'è; è il book a non essere grumoso abbastanza. *(Il primo probe era sbagliato — strappi
ripetuti si diluiscono a vicenda, e sim_eval fa block-bootstrap, non replay: due iterazioni di test
prima di avere un discriminante valido.)*
**Il vincolo vero è il DD 6% statico.** Finestra comune 2024+, de-luck ×0.6:
| | Sharpe | CAGR | vol | maxDD |
|---|---|---|---|---|
| A — book live (TP01/SKH01) | 0.74 | 7.2% | 10.1% | 9.5% |
| B — + XS01 | **1.13** | 10.0% | 8.8% | **6.2%** |
Config A ha maxDD 9.5% contro un limite di 6%: **strutturalmente incompatibile a leva piena**.
**Funded $100k, P(vivo 1 anno) / €-giorno:**
| config | leva | close-only (tetto) | intraday (pavimento) |
|---|---|---|---|
| A | 0.50× | 80.2% / €8.33 | 44.0% / €6.86 |
| A | 0.75× | 51.1% / €12.21 | **10.1%** / — |
| A | 1.00× | 27.1% / €13.89 | **1.4%** / — |
| B | 0.50× | 98.5% / €8.69 | **71.0%** / €7.69 |
| B | 0.75× | 85.4% / €12.78 | 31.6% / €8.66 |
| B | 1.00× | 66.0% / €16.20 | 9.9% / — |
**EV del biglietto** (config B, eval 1.0×, funded a leva ottima, 1 anno): close-only P(pass) 73.0%
→ EV **+$6.789** sul biglietto $100k; intraday P(pass) 31.1% → EV **+$1.166**. **Positivo in
entrambe le lenti** — ma con ~69% di probabilità di perdere la fee nella lente pessimista.
**Raccomandazione operativa:**
1. **Config B (con XS01), non il book live attuale.** A 1.0× intraday config A sopravvive l'1.4%:
mandarci il book a 2 sleeve a leva piena è morte quasi certa.
2. **Funded a 0.50×** (non 0.75× come indicava il 24/07 per la config A): E[payout] scende poco
(€7.69 vs €8.66/g) ma la sopravvivenza raddoppia (71% vs 32%) — e su orizzonti pluriennali la
sopravvivenza **compone**, un conto bustato smette di produrre per sempre.
3. Eval a 0.75-1.0×; biglietto $25-50k come test economico — l'EV scala **linearmente** con la
taglia, quindi la ragione per partire piccoli è varianza e apprendimento, non EV.
4. **Cap $200k/trader ⇒ ~€15-30/g anche negli scenari buoni.** Per €50/g servono più firm → §6.
⚠️ **Discrepanza dichiarata col 24/07:** lì il funded di config A a 0.75× dava P(vivo) 58%, qui
10%. Due cause note: (a) il mio wick è a estrazione indipendente (più severo — §6); (b) finestra
2024+, dove config A è debole (Sharpe 0.74 vs 0.99 sulla finestra piena). **La direzione concorda
(meno leva è meglio), il livello no** — e il numero del 24/07 usa il recon MTM vero, quindi sul
livello è più affidabile del mio.
---
## Bug catturato in sessione ## Bug catturato in sessione
**Modello funded, base di prelievo aggiornata ogni giorno come un HWM mobile** → al checkpoint **Modello funded, base di prelievo aggiornata ogni giorno come un HWM mobile** → al checkpoint
+210
View File
@@ -0,0 +1,210 @@
"""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()
+45
View File
@@ -180,3 +180,48 @@ def test_senza_cassa_nessun_conto_e_nessun_reddito(pl):
assert r["p_zero"] == 1.0 assert r["p_zero"] == 1.0
finally: finally:
pl.START_EUR = old pl.START_EUR = old
# ------------------------------------------- 5) regola di CONSISTENCY HyroTrader
@pytest.fixture(scope="module")
def hy():
return _load("r0725_hyro", "scripts/research/r0725_hyro.py")
def test_consistency_MORDE_su_un_book_grumoso(hy):
"""PROVA CHE IL CONTROLLO FUNZIONA. Un UNICO giorno da +12% porta da solo tutto il profitto
e non arriva mai altro: con la regola 40% quel path non deve passare MAI (0.12 > 0.40*0.12),
senza regola passa subito. Se questo test non discrimina, il 'costo ~0pp' misurato sul book
vero sarebbe un bug, non un risultato.
NB sul probe: sim_eval fa BLOCK BOOTSTRAP (blocchi di 20g), non replay -> uno strappo isolato
finirebbe solo in alcuni path. Qui gli strappi sono spaziati di 20 giorni = esattamente uno per
blocco, cosi' OGNI path ne vede uno ogni 20 giorni in modo deterministico. In 40 giorni:
senza regola si passa al 1o strappo (+12% >= target 10%); con la regola servono 3 strappi
perche' il giorno migliore scenda sotto il 40% del profitto cumulato -> oltre la finestra.
"""
r = np.zeros(200)
r[::20] = 0.12
senza = hy.sim_eval(r, 1.0, consistency=False, intraday=False, max_days=40, n_paths=200)
con = hy.sim_eval(r, 1.0, consistency=True, intraday=False, max_days=40, n_paths=200)
assert senza["p_pass"] > 0.9, "senza regola un +12% secco deve passare il target 10%"
assert con["p_pass"] < 0.05, "la regola 40% deve bloccare un profitto concentrato in 1-2 giorni"
def test_consistency_non_morde_su_un_book_liscio(hy):
"""Controprova: un book che accumula il target in tanti giorni piccoli non e' toccato
dalla regola. E' il motivo per cui sul book vero il costo misurato e' ~0."""
r = np.full(400, 0.0008) # ~+0.08%/g: il 10% arriva in ~120 giorni
senza = hy.sim_eval(r, 1.0, consistency=False, intraday=False, n_paths=200)
con = hy.sim_eval(r, 1.0, consistency=True, intraday=False, n_paths=200)
assert abs(con["p_pass"] - senza["p_pass"]) < 0.02
def test_dd_statico_6pct_e_binding(hy):
"""Il maxDD 6% STATICO di HYRO e' il vincolo vero: un book con DD storico sopra il 6%
non puo' girare a leva piena. Sanity: alzando la leva la sopravvivenza funded scende."""
cfg = hy.configs()
r = list(cfg.values())[0].values.astype(float)
lo = hy.sim_funded(r, 0.5, False, 100_000.0, n_paths=400)["p_alive"]
hi = hy.sim_funded(r, 1.0, False, 100_000.0, n_paths=400)["p_alive"]
assert lo > hi