ea6743d6b0
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>
202 lines
9.3 KiB
Python
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()
|