Files
Adriano Dal Pastro ea6743d6b0 research(rendita): curva capitale->book eseguibile, muro della rendita, asimmetria funded
Ondata "rendita passiva" (2° filone del 2026-07-25). Book live, pesi, config e cron
INVARIATI: questa sessione non tocca nulla del path di esecuzione.

1) DIFETTO DI ESEGUIBILITA' su GTAA01 (5° sleeve del book attivo). gtaa.py modella 2bps
   proporzionali e lo sleeve e' documentato "switch mensile/basso turnover": entrambe false.
   Il vol-target ribilancia OGNI GIORNO su 6 gambe e IB ha un pavimento FISSO per ordine
   (min(max($0.35, $0.0035/az), 1% valore)) = ~$530/anno indipendenti dal capitale.
   A $600: CAGR -3.5% / Sharpe -0.55 (vs +5.5%/0.77 modellato); negativo fino a ~$3k.
   Con banda $50 + cadenza settimanale (plateau, non argmax) torna a Sh 0.52 / CAGR 3.0%.
   -> Sharpe onesto di GTAA01 ~0.55-0.64, non 0.77.
   REGOLA NUOVA: il costo di un venue va modellato nella sua FORMA (fisso vs proporzionale),
   non solo nel livello. Controprova su Deribit (proporzionale): TP01 realistico = modellato
   da ~$500 in su.

2) LA RENDITA NON E' capitale x CAGR. Il muro del 24/07 (EUR 122k) ignorava il rischio di
   sequenza. Prelievo perpetuo (P(cap a 20a >= cap iniziale) >= 90%) sul path live
   de-luckato: 5.98% -> $497k; banda onesta $232k-$497k (il de-luck x0.6 sopra il path live
   rischia di contare due volte la degradazione SKH01). Invariante di lente: la rendita
   perpetua vale ~45-55% del CAGR -> ogni muro calcolato come target/CAGR sbaglia di ~2x.

3) DIVERSIFICARE NON CREA REDDITO a pari nozionale (5.98% -> 6.35%); libera BUDGET DI
   RISCHIO: a iso-rischio (vol 15%) 7.51%/$395k -> 9.25%/$321k. L'ipotesi "piu' capitale ->
   piu' sleeve -> CAGR super-lineare" e' REFUTATA nella forma forte (curva Sharpe piatta da
   $600 a $200k).

4) ASIMMETRIA CAPITALE-PROPRIO vs FUNDED. Su conto funded il capitale e' $100k -> gli sleeve
   STAT-MODE per taglia (XS01) diventano eseguibili, e le regole prop passano sul DRAWDOWN,
   non sul CAGR. Aggiungendo XS01: P(pass) HYRO 44.4->54.7%, funded P(vivo 1a) 18.9->57.6%.

5) LA VIA: i 600 euro come BIGLIETTO. Scala di conti funded, 36 mesi. Problema mai studiato:
   N conti sullo STESSO book bustano INSIEME -> serve sleeve diversi su conti diversi.
   Vince il MISTO, non gli estremi; il book live attuale e' la politica PEGGIORE.
   Verdetto in banda: P(arrivare a 50 EUR/g in 3 anni) 2-21%, P(bruciare i 600) 33-78%
   -> scommessa a coda destra, non una rendita.

6) ADDENDUM "10k su IB": allocazione fra venue. 94% su IB piu' che dimezza il reddito
   (EUR 0.93/g vs 2.16/g). La leva che convertirebbe lo Sharpe in reddito e' chiusa dai
   costi (Reg-T 2x + margine 5.5%) -> vince tutto-Deribit a 1.33x. GTAA01 sui 10k rende
   EUR 0.50/g sopra la liquidita' ferma, pagati con un maxDD del 10%.

Bug catturato in sessione: base di prelievo funded aggiornata giornalmente come HWM mobile
-> payout $230/anno invece di ~$7.000; regole vere = max-loss STATICO dal saldo iniziale.
Test di regressione cablato.

Test: 214 pass (200 preesistenti + 14 nuovi in tests/test_capcurve.py).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:05:16 +00:00

202 lines
9.3 KiB
Python

"""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()