research(wave-0822): DEALER-GAMMA scartato — e dealer_net_gamma e' il GEX col segno invertito
This commit is contained in:
@@ -6,7 +6,7 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
|
|||||||
|
|
||||||
| # | filone | verdetto | in una riga |
|
| # | filone | verdetto | in una riga |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 9 | ADAPTIVE-HORIZON | **SCARTATO** | il "vincitore adattivo" ha lookback incollato al bordo il 100% del tempo = TSMOM costante a 20g (corr 1.000, dSh 0.00 in 8/8 anni e 0/12 ancore); isolando le celle davvero adattive -> NEUTRAL, corr→TP01 0.90 = TP01 travestito |
|
| 9 | ADAPTIVE-HORIZON | **SCARTATO** | il "vincitore adattivo" ha lookback incollato al bordo il 100% del tempo = TSMOM costante a 20g (corr 1.000, dSh 0.00 in 8/8 anni e 0/12 ancore); isolando le celle davvero adattive -> NEUTRAL, corr→TP01 0.90 = TP01 travestito || 4 | DEALER-GAMMA | **SCARTATO** | la gamba tradeable e' INCOERENTE (BTC non separa, ETH separa al ROVESCIO); cio' che resta e' quasi tutto DVOL (corr 0.66), gia' refutato il 26/06. DSR 0.440 e la cella migliore sta SOTTO il massimo atteso dal rumore |
|
||||||
|
|
||||||
## Note che sopravvivono ai singoli filoni
|
## Note che sopravvivono ai singoli filoni
|
||||||
|
|
||||||
@@ -22,3 +22,26 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
|
|||||||
una superficie frastagliata. **Nessun cambio a TP01.**
|
una superficie frastagliata. **Nessun cambio a TP01.**
|
||||||
- Riaprirebbe il filone: un rilevatore il cui L si muove DAVVERO che batta il miglior L costante
|
- Riaprirebbe il filone: un rilevatore il cui L si muove DAVVERO che batta il miglior L costante
|
||||||
(non TP01) in >=6/8 anni appaiati con corr→TP01 < 0.6 — es. BOCPD/PELT vero, non provato.
|
(non TP01) in >=6/8 anni appaiati con corr→TP01 < 0.6 — es. BOCPD/PELT vero, non provato.
|
||||||
|
|
||||||
|
### 4 — DEALER-GAMMA (r0822_dealer_gamma.py, 136 statistiche dichiarate)
|
||||||
|
⚠️ **DIFETTO DI DATO, il risultato piu' riusabile del filone: `dealer_net_gamma` e' il GEX col SEGNO
|
||||||
|
INVERTITO** — corr **-0.95 BTC / -0.89 ETH** contro la ricostruzione da manuale sulla catena
|
||||||
|
(962/1116 snapshot, ~280-316 strike), fit `dng ~ -0.7*GEX`. **Chi lo usasse col nome che porta
|
||||||
|
leggerebbe ogni regime al contrario.** Spiega anche la contraddizione interna del file (corr fra
|
||||||
|
`dng>0` e `spot>flip` = -0.28 / -0.92).
|
||||||
|
⚠️ **Il file dichiara piu' di quel che ha:** cadenza 15 min solo dal **2026-05-01**; i 37 giorni
|
||||||
|
prima sono **un punto al giorno** -> **90 giorni, non 4 mesi**. `liquidation_long_risk` e
|
||||||
|
`liquidation_short_risk` sono **COSTANTI** ('low' in 17.229 righe su 17.229 non nulle) = zero
|
||||||
|
informazione. `gamma_flip_level` congelato nel **61%** dei passi su BTC.
|
||||||
|
- Gate: **null de-levering FALLITO** (baseline x0.6 stesso maxDD con Sharpe migliore); **DSR 0.440**
|
||||||
|
su 64 trial, e nel modo piu' netto — la cella migliore (+4.98 lordo) sta **sotto** il massimo
|
||||||
|
atteso dal puro rumore (+5.12). `marginal_vs_tp01` inutilizzabile: su 90 giorni full e hold-out
|
||||||
|
COINCIDONO (`n_hold_days == n_days == 90`).
|
||||||
|
- **Null location-matched** (aggiunto dall'agente): `gamma_flip_level` bocciato — 96% di accordo con
|
||||||
|
`spot > SMA(720h)` su ETH; su BTC la SMA nuda da' uno spread MAGGIORE.
|
||||||
|
- **Ampiezza effettiva**: 73/59 episodi di regime ma 6-8 coprono il 78-86% del tempo -> **n_eff 53
|
||||||
|
(BTC) / 24 (ETH) ore** su 2.130. Il regime coincide col MESE (maggio long, giugno short, luglio long).
|
||||||
|
- ✅ Ricostruibile oggi dalla NOSTRA catena, meglio dell'originale (518 ore dal 01/08, ~209-262
|
||||||
|
strike/ora, quote_status ok ~100%, segno giusto) — **ma la soglia non e' portabile** (`dng=0`
|
||||||
|
corrisponde a `GEX=+5.8e6` su BTC): ricalibrarla sui 90 giorni sarebbe la selezione che il
|
||||||
|
progetto rifiuta. Riapertura non prima di **~30-40 episodi di regime = ~2 anni (meta' 2028)**.
|
||||||
|
|||||||
@@ -655,9 +655,11 @@ def main() -> None:
|
|||||||
print(" 3. `gamma_flip_level` e' una media mobile: 96% di accordo con spot>SMA(720h) su ETH; su")
|
print(" 3. `gamma_flip_level` e' una media mobile: 96% di accordo con spot>SMA(720h) su ETH; su")
|
||||||
print(" BTC (accordo 0.68) la SMA nuda da' uno spread di vol MAGGIORE del livello stesso.")
|
print(" BTC (accordo 0.68) la SMA nuda da' uno spread di vol MAGGIORE del livello stesso.")
|
||||||
print(" Niente contenuto di opzioni isolabile. Trappola 2 del brief.")
|
print(" Niente contenuto di opzioni isolabile. Trappola 2 del brief.")
|
||||||
print(" 4. Ampiezza effettiva: 6-8 episodi di regime, n_eff 24-53 ore su 2.130. Il regime")
|
print(" 4. Ampiezza effettiva: 73 (BTC) / 59 (ETH) episodi di regime, ma SEI e OTTO di essi")
|
||||||
print(" coincide col mese (maggio LONG, giugno SHORT, luglio LONG): lo spread di vol e' UN")
|
print(" coprono il 78-86% del tempo; n_eff 53 e 24 ore su 2.130. Il regime coincide col")
|
||||||
print(" confronto fra tre mesi, non 2.136 osservazioni.")
|
print(" MESE (maggio LONG, giugno SHORT, luglio LONG): lo spread di vol e' un confronto")
|
||||||
|
print(" fra tre mesi, non fra 2.136 osservazioni. Tolto giugno cala del 46% (BTC) e del")
|
||||||
|
print(" 71% (ETH) e resta poggiato su 134 / 366 ore.")
|
||||||
print(" 5. Cio' che resta della gamba (A) e' quasi tutto DVOL (corr 0.66 su BTC), e il DVOL")
|
print(" 5. Cio' che resta della gamba (A) e' quasi tutto DVOL (corr 0.66 su BTC), e il DVOL")
|
||||||
print(" come modulatore di TP01 e' gia' stato refutato il 26/06: era de-levering.")
|
print(" come modulatore di TP01 e' gia' stato refutato il 26/06: era de-levering.")
|
||||||
print(" 6. Il gate misurato FALLISCE il null del de-levering (baseline x0.6 lo domina) su una")
|
print(" 6. Il gate misurato FALLISCE il null del de-levering (baseline x0.6 lo domina) su una")
|
||||||
|
|||||||
@@ -622,11 +622,14 @@ def main() -> None:
|
|||||||
debole. (Ed e' esattamente la parte che rendeva l'angolo NUOVO rispetto al filone funding.)
|
debole. (Ed e' esattamente la parte che rendeva l'angolo NUOVO rispetto al filone funding.)
|
||||||
2. IL FATTO NON REGGE LA PROPRIA SOGLIA DI POTENZA, dichiarata prima di guardare: l'unico
|
2. IL FATTO NON REGGE LA PROPRIA SOGLIA DI POTENZA, dichiarata prima di guardare: l'unico
|
||||||
effetto e' il decile piu' basso di oi_delta (-0.40% a 24h) e la MDE a 24h e' 1.06%.
|
effetto e' il decile piu' basso di oi_delta (-0.40% a 24h) e la MDE a 24h e' 1.06%.
|
||||||
La pendenza lineare e' nulla (|t|<1.3), il |ritorno| futuro e' piatto fra i decili (la
|
La pendenza lineare e' nulla (|t|<1.3 in 4 specificazioni su 4); il |ritorno| futuro e'
|
||||||
'cascata' non c'e'), e la scomposizione per mese mette quasi tutto in giugno.
|
quasi piatto fra i decili (0.59-0.75% a 4h, solo il decile 0 leggermente sopra) -> la
|
||||||
|
'cascata' non c'e'; e il segno e' ASSENTE nel primo dei tre mesi (maggio y24=+0.02%,
|
||||||
|
giugno -0.64%, luglio -0.74%): 2 mesi su 3 non sono un plateau, sono due mesi.
|
||||||
3. IL SEGNALE E' UN MASSIMO DI 18 CELLE SU 114 GIORNI: Sharpe 3.6 con SE(Sharpe)=2.0,
|
3. IL SEGNALE E' UN MASSIMO DI 18 CELLE SU 114 GIORNI: Sharpe 3.6 con SE(Sharpe)=2.0,
|
||||||
deflated-Sharpe 0.10 (18 celle) / 0.004 (118 trial) contro un massimo atteso PER CASO di
|
deflated-Sharpe 0.10 (18 celle) / 0.004 (118 trial) contro un massimo atteso PER CASO di
|
||||||
Sharpe 5.4; implausible_sharpe=True; marginal_vs_tp01 = NEUTRAL; 60% del P&L in 3 giorni.
|
Sharpe 5.4; implausible_sharpe=True; marginal_vs_tp01 = NEUTRAL; e il P&L e' per il 71%
|
||||||
|
di giugno e per il 60% concentrato in 3 giorni su 114.
|
||||||
4. ED E' COMUNQUE MORTO ALLA NASCITA: la colonna su cui poggia non e' ricostruibile con i dati
|
4. ED E' COMUNQUE MORTO ALLA NASCITA: la colonna su cui poggia non e' ricostruibile con i dati
|
||||||
che raccogliamo (l'OI della catena e' quello delle OPZIONI, rango ~0.05 con la colonna) e
|
che raccogliamo (l'OI della catena e' quello delle OPZIONI, rango ~0.05 con la colonna) e
|
||||||
la sorgente e' stata dismessa il 2026-07-30.
|
la sorgente e' stata dismessa il 2026-07-30.
|
||||||
|
|||||||
@@ -630,6 +630,7 @@ def deep_dive(results) -> None:
|
|||||||
B = A.tp01_baseline_daily()
|
B = A.tp01_baseline_daily()
|
||||||
C = r["C"]
|
C = r["C"]
|
||||||
best = None
|
best = None
|
||||||
|
rows_ctl = []
|
||||||
for p in MOM_CONTROL_GRID:
|
for p in MOM_CONTROL_GRID:
|
||||||
fnm = mk_mom(tf="1d", **p)
|
fnm = mk_mom(tf="1d", **p)
|
||||||
Cm = A.candidate_daily(fnm, tf="1d")
|
Cm = A.candidate_daily(fnm, tf="1d")
|
||||||
@@ -637,18 +638,27 @@ def deep_dive(results) -> None:
|
|||||||
JB = pd.concat({"B": B, "M": Cm}, axis=1, join="inner").dropna()
|
JB = pd.concat({"B": B, "M": Cm}, axis=1, join="inner").dropna()
|
||||||
up = A._sh((1 - W_BLEND) * JB["B"] + W_BLEND * JB["M"]) - A._sh(JB["B"])
|
up = A._sh((1 - W_BLEND) * JB["B"] + W_BLEND * JB["M"]) - A._sh(JB["B"])
|
||||||
row = dict(params=p, sharpe=round(A._sh(Cm), 3),
|
row = dict(params=p, sharpe=round(A._sh(Cm), 3),
|
||||||
|
insample=round(A._sh(Cm[Cm.index < A.HOLDOUT]), 3),
|
||||||
hold=round(A._sh(Cm[Cm.index >= A.HOLDOUT]), 3),
|
hold=round(A._sh(Cm[Cm.index >= A.HOLDOUT]), 3),
|
||||||
corr_to_acc=round(float(J["A"].corr(J["M"])), 3),
|
corr_to_acc=round(float(J["A"].corr(J["M"])), 3),
|
||||||
corr_to_tp01=round(float(JB["B"].corr(JB["M"])), 3),
|
corr_to_tp01=round(float(JB["B"].corr(JB["M"])), 3),
|
||||||
uplift_w25_full=round(up, 3))
|
uplift_w25_full=round(up, 3))
|
||||||
print(f" MOM {str(p):<28} Sh {row['sharpe']:+.3f} hold {row['hold']:+.3f} "
|
print(f" MOM {str(p):<28} Sh {row['sharpe']:+.3f} IS {row['insample']:+.3f} "
|
||||||
f"corr->{r['code']} {row['corr_to_acc']:+.3f} corr->TP01 {row['corr_to_tp01']:+.3f} "
|
f"hold {row['hold']:+.3f} corr->{r['code']} {row['corr_to_acc']:+.3f} "
|
||||||
f"uplift {row['uplift_w25_full']:+.3f}")
|
f"corr->TP01 {row['corr_to_tp01']:+.3f} uplift {row['uplift_w25_full']:+.3f}")
|
||||||
|
rows_ctl.append(row)
|
||||||
if best is None or row["corr_to_acc"] > best["corr_to_acc"]:
|
if best is None or row["corr_to_acc"] > best["corr_to_acc"]:
|
||||||
best = row
|
best = row
|
||||||
print(f" livello piu' vicino all'accelerazione: {best['params']} corr {best['corr_to_acc']:+.3f}")
|
print(f" livello piu' vicino all'accelerazione: {best['params']} corr {best['corr_to_acc']:+.3f}")
|
||||||
print(" lettura: corr alta col LIVELLO => l'accelerazione non e' una dimensione nuova; "
|
print(" lettura: corr alta col LIVELLO => l'accelerazione non e' una dimensione nuova; "
|
||||||
"corr bassa => e' distinta ma resta da spiegare perche' non sopravvive al DSR.\n")
|
"corr bassa => e' distinta ma resta da spiegare perche' non sopravvive al DSR.")
|
||||||
|
bh = max(rows_ctl, key=lambda x: x["hold"])
|
||||||
|
bi = max(rows_ctl, key=lambda x: x["insample"])
|
||||||
|
print(f" TRAPPOLA DICHIARATA: la cella di controllo migliore SULL'HOLD-OUT e' {bh['params']} "
|
||||||
|
f"(hold {bh['hold']:+.3f}); quella scelta IN-SAMPLE-ONLY e' {bi['params']} "
|
||||||
|
f"(IS {bi['insample']:+.3f}, hold {bi['hold']:+.3f}). Se coincidono, il controllo e'"
|
||||||
|
" selezionabile; se no, guardare la colonna hold e' selezione-sull'hold-out (trappola 3)"
|
||||||
|
" e NON promuove nulla.\n")
|
||||||
|
|
||||||
|
|
||||||
NOT_TESTED = [
|
NOT_TESTED = [
|
||||||
|
|||||||
@@ -156,28 +156,62 @@ def sezione0(chain: pd.DataFrame) -> None:
|
|||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
# SEZIONE 1 — RICOSTRUZIONE DELLA CURVA + CERTIFICAZIONE
|
# SEZIONE 1 — RICOSTRUZIONE DELLA CURVA + CERTIFICAZIONE
|
||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
def _atm_iv(g: pd.DataFrame) -> float:
|
def _interp_at(gid: np.ndarray, x: np.ndarray, y: np.ndarray, ngrp: int,
|
||||||
"""ATM IV di UNO snapshot+scadenza: interpolazione sul DELTA, mai estrapolazione."""
|
target: float) -> np.ndarray:
|
||||||
vals = []
|
"""Interpolazione lineare di y(x) a `target`, per ogni gruppo, VETTORIALE e senza
|
||||||
for typ, tgt in (("C", 0.5), ("P", -0.5)):
|
estrapolazione (NaN se il gruppo non racchiude il target).
|
||||||
s = g[g.option_type == typ]
|
|
||||||
if len(s) >= 2:
|
`gid` dev'essere ordinato crescente e (gid, x) lessicograficamente ordinati.
|
||||||
d = s["delta"].to_numpy()
|
Equivalente a np.interp gruppo-per-gruppo: qui serve solo perche' i gruppi sono ~30.000
|
||||||
if d.min() <= tgt <= d.max():
|
e un ciclo python li paga a caro prezzo su una VPS a 2 core condivisa.
|
||||||
o = np.argsort(d)
|
"""
|
||||||
vals.append(float(np.interp(tgt, d[o], s["iv"].to_numpy()[o])))
|
n_tot = np.bincount(gid, minlength=ngrp)
|
||||||
return float(np.mean(vals)) if vals else np.nan
|
start = np.concatenate([[0], np.cumsum(n_tot)[:-1]])
|
||||||
|
n_below = np.bincount(gid[x <= target], minlength=ngrp)
|
||||||
|
ok = (n_below > 0) & (n_below < n_tot)
|
||||||
|
out = np.full(ngrp, np.nan)
|
||||||
|
if not ok.any():
|
||||||
|
return out
|
||||||
|
lo = (start + n_below - 1)[ok]
|
||||||
|
hi = lo + 1
|
||||||
|
x0, x1, y0, y1 = x[lo], x[hi], y[lo], y[hi]
|
||||||
|
w = np.where(x1 != x0, (target - x0) / np.where(x1 != x0, x1 - x0, 1.0), 0.0)
|
||||||
|
out[ok] = y0 + w * (y1 - y0)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def build_atm_by_expiry(chain: pd.DataFrame) -> pd.DataFrame:
|
def build_atm_by_expiry(chain: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""ATM IV per (asset, ora, scadenza) = IV interpolata sul DELTA a 0.5 (call) / -0.5 (put).
|
||||||
|
|
||||||
|
Perche' il delta e non "lo strike piu' vicino allo spot": il delta referenzia il FORWARD.
|
||||||
|
A 90 giorni il forward sta lontano dallo spot, quindi lo strike ATM-spot e' un'opzione OTM
|
||||||
|
e la sua IV porta dentro lo SKEW — che e' un altro filone e falserebbe la pendenza in modo
|
||||||
|
sistematico e crescente col tenore. Nessuna estrapolazione: se il delta 0.5 non e' racchiuso
|
||||||
|
dai quotati, quella scadenza a quell'ora non ha ATM e viene scartata.
|
||||||
|
"""
|
||||||
f = CACHE / "atm_by_expiry.parquet"
|
f = CACHE / "atm_by_expiry.parquet"
|
||||||
if f.exists():
|
if f.exists():
|
||||||
return pd.read_parquet(f)
|
return pd.read_parquet(f)
|
||||||
q = chain[chain.quoted & (chain.dte > DTE_MIN)]
|
q = chain[chain.quoted & (chain.dte > DTE_MIN)].copy()
|
||||||
out = (q.groupby(["asset", "hr", "exp"])
|
keys = ["asset", "hr", "exp"]
|
||||||
.apply(lambda g: pd.Series({"iv": _atm_iv(g), "dte": g["dte"].median()}),
|
q["gid"] = q.groupby(keys, sort=True).ngroup()
|
||||||
include_groups=False)
|
ngrp = int(q["gid"].max()) + 1
|
||||||
.reset_index().dropna(subset=["iv"]))
|
ivs = []
|
||||||
|
for typ, tgt in (("C", 0.5), ("P", -0.5)):
|
||||||
|
s = q[q.option_type == typ].sort_values(["gid", "delta"])
|
||||||
|
v = np.full(ngrp, np.nan)
|
||||||
|
if len(s):
|
||||||
|
g2 = s["gid"].to_numpy()
|
||||||
|
uniq, inv = np.unique(g2, return_inverse=True)
|
||||||
|
v[uniq] = _interp_at(inv, s["delta"].to_numpy(), s["iv"].to_numpy(), len(uniq), tgt)
|
||||||
|
ivs.append(v)
|
||||||
|
M = np.vstack(ivs)
|
||||||
|
with np.errstate(invalid="ignore"):
|
||||||
|
cnt = np.sum(np.isfinite(M), axis=0)
|
||||||
|
iv = np.where(cnt > 0, np.nansum(np.where(np.isfinite(M), M, 0.0), axis=0) / np.maximum(cnt, 1), np.nan)
|
||||||
|
out = (q.groupby(keys, sort=True)["dte"].median().reset_index())
|
||||||
|
out["iv"] = iv
|
||||||
|
out = out.dropna(subset=["iv"]).reset_index(drop=True)
|
||||||
CACHE.mkdir(parents=True, exist_ok=True)
|
CACHE.mkdir(parents=True, exist_ok=True)
|
||||||
out.to_parquet(f)
|
out.to_parquet(f)
|
||||||
return out
|
return out
|
||||||
@@ -266,7 +300,7 @@ def px_hourly(a: str) -> pd.Series:
|
|||||||
index=pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True)))
|
index=pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True)))
|
||||||
|
|
||||||
|
|
||||||
def _block_pctl(x: np.ndarray, y: np.ndarray, stat: float, L: int, n: int = 800,
|
def _block_pctl(x: np.ndarray, y: np.ndarray, stat: float, L: int, n: int = 400,
|
||||||
seed: int = 20260822) -> float:
|
seed: int = 20260822) -> float:
|
||||||
"""Percentile della correlazione osservata contro il null a BLOCCHI (y ricampionata da
|
"""Percentile della correlazione osservata contro il null a BLOCCHI (y ricampionata da
|
||||||
blocchi non allineati a x): conserva l'autocorrelazione e rompe solo l'accoppiamento."""
|
blocchi non allineati a x): conserva l'autocorrelazione e rompe solo l'accoppiamento."""
|
||||||
@@ -400,6 +434,51 @@ def build_atm_cost(chain: pd.DataFrame) -> pd.DataFrame:
|
|||||||
return atm
|
return atm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _roll_table(r: pd.DataFrame, H: int) -> pd.DataFrame:
|
||||||
|
"""Roll IMPLICITO vs REALIZZATO su orizzonte H ore, appaiato per (scadenza, ISTANTE D'INGRESSO).
|
||||||
|
|
||||||
|
Appaiato sull'INGRESSO e non sull'uscita: e' l'uscita che le due letture spostano, e un
|
||||||
|
inner-join sulla data d'uscita terrebbe solo i casi in cui non e' successo niente (trappola
|
||||||
|
incontrata 3 volte nel progetto: venue-watch, GTAA, VRP profit-take).
|
||||||
|
impl = iv_curva(T-H) al tempo t - iv(T) al tempo t (se la curva non si muovesse)
|
||||||
|
real = iv(T-H) al tempo t+H - iv(T) al tempo t (cio' che e' successo davvero)
|
||||||
|
"""
|
||||||
|
r = r[["hr", "exp", "iv", "dte"]].dropna()
|
||||||
|
nxt = r[["hr", "exp", "iv"]].copy()
|
||||||
|
nxt["hr"] = nxt["hr"] - pd.Timedelta(hours=H)
|
||||||
|
j = r.merge(nxt.rename(columns={"iv": "iv_fut"}), on=["hr", "exp"], how="inner")
|
||||||
|
j["T2"] = j["dte"] - H / 24.0
|
||||||
|
j = j[j["T2"] > DTE_MIN]
|
||||||
|
if j.empty:
|
||||||
|
return pd.DataFrame(columns=["dte", "impl", "real"])
|
||||||
|
# curva dell'ora t, ordinata: chiave monotona globale gid*1000 + dte -> searchsorted unico
|
||||||
|
c = r.sort_values(["hr", "dte"]).copy()
|
||||||
|
c["gid"] = c.groupby("hr", sort=True).ngroup()
|
||||||
|
key = c["gid"].to_numpy() * 1000.0 + c["dte"].to_numpy()
|
||||||
|
w = (c["iv"].to_numpy() / 100.0) ** 2 * c["dte"].to_numpy() # varianza totale
|
||||||
|
gmap = dict(zip(c["hr"].to_numpy(), c["gid"].to_numpy()))
|
||||||
|
n_tot = np.bincount(c["gid"].to_numpy())
|
||||||
|
start = np.concatenate([[0], np.cumsum(n_tot)[:-1]])
|
||||||
|
gq = np.array([gmap[h] for h in j["hr"].to_numpy()])
|
||||||
|
pos = np.searchsorted(key, gq * 1000.0 + j["T2"].to_numpy(), side="left")
|
||||||
|
lo, hi = pos - 1, pos
|
||||||
|
ok = (lo >= start[gq]) & (hi < start[gq] + n_tot[gq])
|
||||||
|
ivT2 = np.full(len(j), np.nan)
|
||||||
|
if ok.any():
|
||||||
|
l, h2 = lo[ok], hi[ok]
|
||||||
|
x0 = key[l] - gq[ok] * 1000.0
|
||||||
|
x1 = key[h2] - gq[ok] * 1000.0
|
||||||
|
t2 = j["T2"].to_numpy()[ok]
|
||||||
|
ww = np.where(x1 != x0, (t2 - x0) / np.where(x1 != x0, x1 - x0, 1.0), 0.0)
|
||||||
|
var = w[l] + ww * (w[h2] - w[l])
|
||||||
|
ivT2[ok] = np.sqrt(np.maximum(var, 0.0) / t2) * 100.0
|
||||||
|
j = j.assign(ivT2=ivT2).dropna(subset=["ivT2"])
|
||||||
|
return pd.DataFrame({"dte": j["dte"].to_numpy(),
|
||||||
|
"impl": j["ivT2"].to_numpy() - j["iv"].to_numpy(),
|
||||||
|
"real": j["iv_fut"].to_numpy() - j["iv"].to_numpy()})
|
||||||
|
|
||||||
|
|
||||||
def sezione3(chain: pd.DataFrame, atm_exp: pd.DataFrame, curve: pd.DataFrame) -> None:
|
def sezione3(chain: pd.DataFrame, atm_exp: pd.DataFrame, curve: pd.DataFrame) -> None:
|
||||||
hr("SEZIONE 3 — Q2: il CARRY di vol (roll-down) e' incassabile al bid-ask reale?")
|
hr("SEZIONE 3 — Q2: il CARRY di vol (roll-down) e' incassabile al bid-ask reale?")
|
||||||
print(" Definizione senza modello: un'opzione a tenore T, dopo dt, sta a T-dt. Se la curva non")
|
print(" Definizione senza modello: un'opzione a tenore T, dopo dt, sta a T-dt. Se la curva non")
|
||||||
@@ -412,25 +491,7 @@ def sezione3(chain: pd.DataFrame, atm_exp: pd.DataFrame, curve: pd.DataFrame) ->
|
|||||||
roll_day = {}
|
roll_day = {}
|
||||||
for H in (24, 168):
|
for H in (24, 168):
|
||||||
for a in ASSETS:
|
for a in ASSETS:
|
||||||
r = atm_exp[atm_exp.asset == a]
|
d = _roll_table(atm_exp[atm_exp.asset == a], H)
|
||||||
by_hr = {h: g.sort_values("dte") for h, g in r.groupby("hr")}
|
|
||||||
rows = []
|
|
||||||
for _, g in r.set_index(["exp", "hr"]).sort_index().groupby(level=0):
|
|
||||||
g = g.droplevel(0).sort_index()
|
|
||||||
fut = g["iv"].reindex(g.index + pd.Timedelta(hours=H))
|
|
||||||
for t, iv0, d0, iv1 in zip(g.index, g["iv"].to_numpy(),
|
|
||||||
g["dte"].to_numpy(), fut.to_numpy()):
|
|
||||||
T2 = d0 - H / 24.0
|
|
||||||
gg = by_hr.get(t)
|
|
||||||
if not np.isfinite(iv1) or T2 <= DTE_MIN or gg is None or len(gg) < 2:
|
|
||||||
continue
|
|
||||||
td, ivd = gg["dte"].to_numpy(), gg["iv"].to_numpy()
|
|
||||||
if td.min() > T2 or td.max() < T2:
|
|
||||||
continue
|
|
||||||
w = (ivd / 100.0) ** 2 * td
|
|
||||||
ivT2 = np.sqrt(np.interp(T2, td, w) / T2) * 100.0
|
|
||||||
rows.append((d0, ivT2 - iv0, iv1 - iv0))
|
|
||||||
d = pd.DataFrame(rows, columns=["dte", "impl", "real"])
|
|
||||||
d = d[(d.dte >= 4) & (d.dte <= 45)]
|
d = d[(d.dte >= 4) & (d.dte <= 45)]
|
||||||
if len(d) < 50:
|
if len(d) < 50:
|
||||||
print(f" {a} dt={H}h: campione insufficiente ({len(d)})")
|
print(f" {a} dt={H}h: campione insufficiente ({len(d)})")
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ def extract(asset: str, off: int) -> dict:
|
|||||||
ts_close=ltf["timestamp"].values.astype(np.int64) + LTF_MIN * 60_000,
|
ts_close=ltf["timestamp"].values.astype(np.int64) + LTF_MIN * 60_000,
|
||||||
day_ent=idx[idxE].floor("D") if len(idxE) else pd.DatetimeIndex([], tz="UTC"),
|
day_ent=idx[idxE].floor("D") if len(idxE) else pd.DatetimeIndex([], tz="UTC"),
|
||||||
)
|
)
|
||||||
|
out["base_daily"] = equity_daily(out, size_flat(out)) # riusata da VTL/§9: calcolarla 1 volta
|
||||||
R._CACHE.clear() # ent = 18k dict per chiave: non accumulare
|
R._CACHE.clear() # ent = 18k dict per chiave: non accumulare
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -413,8 +414,7 @@ def main() -> None:
|
|||||||
for o in offs:
|
for o in offs:
|
||||||
sz = {}
|
sz = {}
|
||||||
for a in ASSETS:
|
for a in ASSETS:
|
||||||
base_a = equity_daily(EX[o][a], size_flat(EX[o][a]))
|
sz[a] = sizes_from_L(EX[o][a], leverage_series(EX[o][a]["base_daily"], **sp))
|
||||||
sz[a] = sizes_from_L(EX[o][a], leverage_series(base_a, **sp))
|
|
||||||
SLE[nm][o] = leg_daily(EX[o], sz)
|
SLE[nm][o] = leg_daily(EX[o], sz)
|
||||||
BK[nm][o] = book(TP, SLE[nm][o])
|
BK[nm][o] = book(TP, SLE[nm][o])
|
||||||
MTP[nm][o] = 1.0
|
MTP[nm][o] = 1.0
|
||||||
@@ -546,6 +546,13 @@ def main() -> None:
|
|||||||
print(f" {n:<28} IS {is_med[n]:>7.3f} HOLD {ho_med[n]:>7.3f} FULL {fu_med[n]:>7.3f}")
|
print(f" {n:<28} IS {is_med[n]:>7.3f} HOLD {ho_med[n]:>7.3f} FULL {fu_med[n]:>7.3f}")
|
||||||
best = max(CAUSAL, key=lambda x: is_med[x])
|
best = max(CAUSAL, key=lambda x: is_med[x])
|
||||||
print(f"\n cella scelta AL BUIO: {best} (IS {is_med[best]:.3f} vs baseline {base_is:.3f})")
|
print(f"\n cella scelta AL BUIO: {best} (IS {is_med[best]:.3f} vs baseline {base_is:.3f})")
|
||||||
|
iso_is = {n: float(np.median([sh(ins(BK[n][o])) - sh(ins(CTRL[n][o])) for o in offs]))
|
||||||
|
for n in CAUSAL}
|
||||||
|
best_iso = max(CAUSAL, key=lambda x: iso_is[x])
|
||||||
|
print(f" cella scelta AL BUIO **a ISO-PESO**: {best_iso} (dIS iso {iso_is[best_iso]:+.3f})")
|
||||||
|
print(f" la stessa cella a iso-peso: dShFULL {RES[best]['iF']:+.3f} "
|
||||||
|
f"({RES[best]['niF']}/{len(offs)}), dShHOLD {RES[best]['iH']:+.3f} "
|
||||||
|
f"({RES[best]['niH']}/{len(offs)})")
|
||||||
trials = [fu_med[n] for n in CAUSAL] + [med_F]
|
trials = [fu_med[n] for n in CAUSAL] + [med_F]
|
||||||
dsr, sr0 = A.deflated_sharpe(fu_med[best], trials, BK[best][can])
|
dsr, sr0 = A.deflated_sharpe(fu_med[best], trials, BK[best][can])
|
||||||
dsr_b, _ = A.deflated_sharpe(med_F, trials, base_b[can])
|
dsr_b, _ = A.deflated_sharpe(med_F, trials, base_b[can])
|
||||||
@@ -599,9 +606,9 @@ def main() -> None:
|
|||||||
f"attive {imp.get('active_frac', 0)*100:.1f}% perdite/attive "
|
f"attive {imp.get('active_frac', 0)*100:.1f}% perdite/attive "
|
||||||
f"{imp.get('loss_frac', 0)*100:.1f}% Calmar {imp.get('calmar', 0):.1f}")
|
f"{imp.get('loss_frac', 0)*100:.1f}% Calmar {imp.get('calmar', 0):.1f}")
|
||||||
cols = {"TP01": TP, "SKH01": SLE["BASE"][can]}
|
cols = {"TP01": TP, "SKH01": SLE["BASE"][can]}
|
||||||
for nm in [best_sleeve, list(BVT)[0]]:
|
for nm in [best, best_sleeve] + list(BVT)[:2]:
|
||||||
m = vol(SLE[nm][can]) / vol(SLE["BASE"][can])
|
m_s = vol(SLE[nm][can]) / vol(SLE["BASE"][can])
|
||||||
wp = {"TP01": W_TP, "SKH01": W_SKH * m}
|
wp = {"TP01": W_TP * MTP[nm][can], "SKH01": W_SKH * m_s}
|
||||||
g = weights_tilt_null(cols, {"TP01": W_TP, "SKH01": W_SKH}, wp,
|
g = weights_tilt_null(cols, {"TP01": W_TP, "SKH01": W_SKH}, wp,
|
||||||
caps={"SKH01": 0.5}, floor=0.05, n=300, k_seen=len(ALL))
|
caps={"SKH01": 0.5}, floor=0.05, n=300, k_seen=len(ALL))
|
||||||
print(f" tilt-null {nm:<26} peso equiv SKH {wp['SKH01']/sum(wp.values()):.3f} "
|
print(f" tilt-null {nm:<26} peso equiv SKH {wp['SKH01']/sum(wp.values()):.3f} "
|
||||||
@@ -634,6 +641,57 @@ def main() -> None:
|
|||||||
print(f" {label:<28}{tm:>10.3f}{tr:>10.3f}{tm-tr:>9.3f}{no:>13}{ns:>11}{tu:>12.1f}")
|
print(f" {label:<28}{tm:>10.3f}{tr:>10.3f}{tm-tr:>9.3f}{no:>13}{ns:>11}{tu:>12.1f}")
|
||||||
print(" 'ordini eseg' e 'sotto-min' sono sui due asset sommati, su tutta la storia 1h.")
|
print(" 'ordini eseg' e 'sotto-min' sono sui due asset sommati, su tutta la storia 1h.")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §9 per anno + deriva
|
||||||
|
print("\n" + "-" * 112)
|
||||||
|
print(" 9. SCOMPOSIZIONE PER ANNO del dSharpe a ISO-PESO (regola codificata dopo SOL, 22/08:")
|
||||||
|
print(" un contributo positivo si scompone per ANNO prima di crederci) + deriva della size")
|
||||||
|
print("-" * 112)
|
||||||
|
yrs = sorted({int(y) for y in BK["BASE"][can].index.year})
|
||||||
|
top = [best, best_iso] + [max(BVT, key=lambda x: RES[x]["iF"])]
|
||||||
|
top = list(dict.fromkeys(top))
|
||||||
|
print(f" {'variante':<28}" + "".join(f"{y:>8}" for y in yrs))
|
||||||
|
for nm in top:
|
||||||
|
cells = []
|
||||||
|
for y in yrs:
|
||||||
|
v = []
|
||||||
|
for o in offs:
|
||||||
|
a_ = BK[nm][o][BK[nm][o].index.year == y]
|
||||||
|
c_ = CTRL[nm][o][CTRL[nm][o].index.year == y]
|
||||||
|
if len(a_) > 20:
|
||||||
|
v.append(sh(a_) - sh(c_))
|
||||||
|
cells.append(float(np.median(v)) if v else float("nan"))
|
||||||
|
npos = int(np.nansum(np.asarray(cells) > 0))
|
||||||
|
print(f" {nm:<28}" + "".join(f"{c:>+8.2f}" for c in cells) +
|
||||||
|
f" anni positivi {npos}/{len(yrs)}")
|
||||||
|
print()
|
||||||
|
for nm in top:
|
||||||
|
if nm in SZ:
|
||||||
|
szs = np.concatenate([SZ[nm](EX[can][a]) for a in ASSETS])
|
||||||
|
dys = pd.DatetimeIndex(np.concatenate([EX[can][a]["day_ent"].values for a in ASSETS]))
|
||||||
|
elif nm in VTL:
|
||||||
|
szs, dys = [], []
|
||||||
|
for a in ASSETS:
|
||||||
|
szs.append(sizes_from_L(EX[can][a],
|
||||||
|
leverage_series(EX[can][a]["base_daily"], **VTL[nm])))
|
||||||
|
dys.append(EX[can][a]["day_ent"].values)
|
||||||
|
szs = np.concatenate(szs); dys = pd.DatetimeIndex(np.concatenate(dys))
|
||||||
|
else:
|
||||||
|
L = leverage_series(BK["BASE"][can], BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
|
||||||
|
szs, dys = [], []
|
||||||
|
for a in ASSETS:
|
||||||
|
szs.append(sizes_from_L(EX[can][a], L))
|
||||||
|
dys.append(EX[can][a]["day_ent"].values)
|
||||||
|
szs = np.concatenate(szs); dys = pd.DatetimeIndex(np.concatenate(dys))
|
||||||
|
s = pd.Series(szs, index=dys)
|
||||||
|
med = s.groupby(s.index.year).median()
|
||||||
|
rho = float(np.corrcoef(np.arange(len(med)), med.values)[0, 1]) if len(med) > 2 else float("nan")
|
||||||
|
print(f" size mediana per anno — {nm:<22}" +
|
||||||
|
"".join(f"{med.get(y, float('nan')):>8.2f}" for y in yrs) +
|
||||||
|
f" corr col tempo {rho:+.2f}")
|
||||||
|
print(" ⚠️ una size che DERIVA col tempo non e' controllo del rischio: e' un tilt temporale")
|
||||||
|
print(" (la vol crypto e' scesa lungo il campione -> qualunque 1/vol pesa di piu' il")
|
||||||
|
print(" periodo recente, che e' anche l'hold-out). La riga 'corr col tempo' la misura.")
|
||||||
|
|
||||||
print("\n" + "=" * 112)
|
print("\n" + "=" * 112)
|
||||||
print(f" fatto in {time.time()-t0:.0f}s")
|
print(f" fatto in {time.time()-t0:.0f}s")
|
||||||
print("=" * 112)
|
print("=" * 112)
|
||||||
@@ -652,8 +710,7 @@ def netted_target(asset: str, ex: dict, nm, SZ, VTL, BVT, SLE, TP, can, df1h) ->
|
|||||||
elif nm in SZ:
|
elif nm in SZ:
|
||||||
sizes = SZ[nm](ex)
|
sizes = SZ[nm](ex)
|
||||||
elif nm in VTL:
|
elif nm in VTL:
|
||||||
base_a = equity_daily(ex, size_flat(ex))
|
sizes = sizes_from_L(ex, leverage_series(ex["base_daily"], **VTL[nm]))
|
||||||
sizes = sizes_from_L(ex, leverage_series(base_a, **VTL[nm]))
|
|
||||||
else: # BOOKVT
|
else: # BOOKVT
|
||||||
L = leverage_series(book(TP, SLE["BASE"][can]), BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
|
L = leverage_series(book(TP, SLE["BASE"][can]), BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
|
||||||
sizes = sizes_from_L(ex, L)
|
sizes = sizes_from_L(ex, L)
|
||||||
|
|||||||
@@ -325,9 +325,16 @@ def main() -> None:
|
|||||||
for a in ASSETS:
|
for a in ASSETS:
|
||||||
V = CB.dvol_series(a)
|
V = CB.dvol_series(a)
|
||||||
win = V[(V.index >= puts["ts"].min()) & (V.index <= puts["ts"].max())]
|
win = V[(V.index >= puts["ts"].min()) & (V.index <= puts["ts"].max())]
|
||||||
pct = float((V[V.index < win.index[0]].values[:, None] < win.values).mean())
|
hist = V[V.index < win.index[0]].to_numpy(float)
|
||||||
print(f" {a}: DVOL nel campione min {win.min():.1f} / mediana {win.median():.1f} / "
|
print(f" {a}: DVOL nel campione min {win.min().1f} / mediana {win.median().1f} / "
|
||||||
f"max {win.max():.1f} → percentile mediano vs storia 2021+ = {pct:.0%}")
|
f"max {win.max().1f}")
|
||||||
|
print(f" vs storia 2021+ ({len(hist)} giorni) la MEDIANA del campione sta al "
|
||||||
|
f"{float((hist < win.median()).mean()).0%} pctl, il suo MASSIMO al "
|
||||||
|
f"{float((hist < win.max()).mean()).0%} pctl")
|
||||||
|
print("\n \u26a0\ufe0f Due statistiche diverse, da tenere distinte: CLAUDE.md cita «DVOL max al 24\u00b0/28\u00b0\n"
|
||||||
|
" pctl» dal campione del 30/07. Un MASSIMO cresce con la finestra — e' una statistica\n"
|
||||||
|
" di ESTREMO, non di livello. Per dire in che regime siamo la lente e' la MEDIANA, ed\n"
|
||||||
|
" e' bassissima.")
|
||||||
|
|
||||||
# ------------------------------------------------------------ §2 CAMPIONE
|
# ------------------------------------------------------------ §2 CAMPIONE
|
||||||
hr("§2 IL CAMPIONE — quante settimane ci sono DAVVERO")
|
hr("§2 IL CAMPIONE — quante settimane ci sono DAVVERO")
|
||||||
@@ -446,8 +453,16 @@ def main() -> None:
|
|||||||
print(f" {a:6s} {lot:>7.2f} {wid:>10,.0f} {wid * lot - tr['cred'].median() * lot:>11,.0f}$ "
|
print(f" {a:6s} {lot:>7.2f} {wid:>10,.0f} {wid * lot - tr['cred'].median() * lot:>11,.0f}$ "
|
||||||
f"{tr['k_short'].median() * lot:>12,.0f}$ {tr['cred'].median() * lot:>12,.2f}$")
|
f"{tr['k_short'].median() * lot:>12,.0f}$ {tr['cred'].median() * lot:>12,.2f}$")
|
||||||
print(" Sul rischio DEFINITO un lotto minimo inverse sta dentro $600. Sul cash-secured no.\n"
|
print(" Sul rischio DEFINITO un lotto minimo inverse sta dentro $600. Sul cash-secured no.\n"
|
||||||
" Quale dei due valga dipende dal regime di margine del conto, che NON e' leggibile\n"
|
" Quale dei due valga dipende dal regime di margine del conto (standard vs portfolio),\n"
|
||||||
" da dati pubblici: si legge sul conto. E' un limite di questa misura, non un dettaglio.")
|
" che NON e' leggibile da dati pubblici.")
|
||||||
|
print(" ⚠️ E c'e' una domanda PRIMA di quella, che nessun dato pubblico chiude: un saldo in\n"
|
||||||
|
" USDC puo' marginare un'opzione INVERSE (collaterale in BTC/ETH)? Se no, la famiglia\n"
|
||||||
|
" con 3 mesi di storia non e' negoziabile da questo conto a NESSUN capitale.\n"
|
||||||
|
" Precedente esatto: il 26/07 GTAA01 aveva 5 settimane di misure sopra l'assunzione\n"
|
||||||
|
" che i 6 ETF fossero comprabili — il blocco PRIIPs si scopri' con UN ordine di prova.\n"
|
||||||
|
" La regola che ne usci' («la negoziabilita' sul conto REALE si verifica quando lo\n"
|
||||||
|
" sleeve entra in RICERCA, non quando entra nel book») si applica qui identica, e\n"
|
||||||
|
" questo script NON puo' eseguirla: richiede il conto, non i dati.")
|
||||||
|
|
||||||
# ------------------------------------------------------------ §4 BACKTEST
|
# ------------------------------------------------------------ §4 BACKTEST
|
||||||
hr("§4 BACKTEST SU QUOTE REALI — griglia dichiarata")
|
hr("§4 BACKTEST SU QUOTE REALI — griglia dichiarata")
|
||||||
|
|||||||
@@ -535,6 +535,25 @@ def main() -> None:
|
|||||||
f"{dg['med_ticket']:>11.0f}{dg['orders_py']:>10.0f}"
|
f"{dg['med_ticket']:>11.0f}{dg['orders_py']:>10.0f}"
|
||||||
f"{trk:>12.2f}{' ⚠' if trk < 0.8 else ' '}")
|
f"{trk:>12.2f}{' ⚠' if trk < 0.8 else ' '}")
|
||||||
print()
|
print()
|
||||||
|
print(f" {'cella':<13}{'|dw| mediano al ribil.':>24}{'capitale sleeve minimo':>25}{' = book al 15%':>17}")
|
||||||
|
for nm in ("MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"):
|
||||||
|
cell = xs_cells[nm]
|
||||||
|
Weff = xs_effective_weights(cell["W"], cell["scale"])
|
||||||
|
chg = np.abs(np.diff(cell["W"], axis=0)).sum(axis=1) > 1e-12 # giorni di ribilanciamento
|
||||||
|
rows = np.where(chg)[0] + 1
|
||||||
|
d = np.abs(np.diff(Weff, axis=0))[rows - 1]
|
||||||
|
med = float(np.median(d[d > 1e-9])) if np.any(d > 1e-9) else float("nan")
|
||||||
|
capmin = MIN_ORDER_HL / med if med > 0 else float("nan")
|
||||||
|
print(f" {'XS ' + nm.split()[1]:<13}{med:>24.4f}{capmin:>24.0f}$"
|
||||||
|
f"{capmin/0.15:>16.0f}$")
|
||||||
|
print(" ^ ARITMETICA: il ticket di un ribilanciamento e' |dw|*capitale_sleeve. Il min-order")
|
||||||
|
print(" morde sotto il 'capitale sleeve minimo'. Per il CANONICO k=5 quel numero e' ~150$")
|
||||||
|
print(" di sleeve = ~1.000$ di book al 15%, non 20.000$. La soglia dichiarata dal progetto")
|
||||||
|
print(" veniva da una diagnostica di TURNOVER (quota di turnover sopra il min-order), che a")
|
||||||
|
print(" capitale piccolo e' bassa perche' salta la deriva GIORNALIERA del vol-target — la")
|
||||||
|
print(" quale non porta segnale. Stessa lezione gia' imparata su TP01 ('a $600 il min-order")
|
||||||
|
print(" $5 e' GIA' la banda ottimale'), non applicata a XS01.")
|
||||||
|
print()
|
||||||
print(" 'gambe eseg' = quota delle VARIAZIONI di posizione sopra il min-order (la diagnostica")
|
print(" 'gambe eseg' = quota delle VARIAZIONI di posizione sopra il min-order (la diagnostica")
|
||||||
print(" su cui si basa la soglia '$20k'). 'haircut' = quanto ne costa in Sharpe. Sono cose")
|
print(" su cui si basa la soglia '$20k'). 'haircut' = quanto ne costa in Sharpe. Sono cose")
|
||||||
print(" DIVERSE: il turnover saltato e' la deriva del vol-target, non il segnale.")
|
print(" DIVERSE: il turnover saltato e' la deriva del vol-target, non il segnale.")
|
||||||
@@ -710,6 +729,40 @@ def main() -> None:
|
|||||||
print(f" break-even di costo per gamba: XS k=2 {_be(be.get('XS MAJ19 k=2'))}, "
|
print(f" break-even di costo per gamba: XS k=2 {_be(be.get('XS MAJ19 k=2'))}, "
|
||||||
f"XS k=5 {_be(be.get('XS MAJ19 k=5*'))}, XSR k=2 {_be(be.get('XSR ALL50 k=2'))}, "
|
f"XS k=5 {_be(be.get('XS MAJ19 k=5*'))}, XSR k=2 {_be(be.get('XSR ALL50 k=2'))}, "
|
||||||
f"XSR pieno {_be(be.get('XSR ALL50 k=pieno*'))}")
|
f"XSR pieno {_be(be.get('XSR ALL50 k=pieno*'))}")
|
||||||
|
print("-" * 108)
|
||||||
|
print(" 1. CONCENTRARE NON COMPRA RENDIMENTO, COMPRA ESEGUIBILITA'. Il null del de-levering")
|
||||||
|
print(" boccia ogni k tranne i due che sono fortuna d'ancora (XS k=4: fase0 al 90° pctl,")
|
||||||
|
print(" mediana 1.10 < 1.15 del canonico; XSR k=6: +0.03 su 1.65). Nessuna concentrazione")
|
||||||
|
print(" batte il proprio originale a PARI maxDD.")
|
||||||
|
print(" 2. MA IL COSTO E' MOLTO PIU' BASSO DEL PREVISTO, e la mia ipotesi (a) era sbagliata:")
|
||||||
|
print(" mi aspettavo Sh ~ sqrt(ampiezza) e quindi il 40-60% dell'edge a k=2-3. Misurato:")
|
||||||
|
print(" XS-LITE k=2 conserva il 98% del canonico A MEDIANA DI FASE con 4 gambe invece di 10,")
|
||||||
|
print(" e N_hhi scende 6.9 -> 3.0. La radice dell'ampiezza NON descrive questa curva: la")
|
||||||
|
print(" dispersione cross-section e' concentrata negli ESTREMI del ranking, e le gambe 3-5")
|
||||||
|
print(" per lato aggiungono rischio quanto segnale. Il collasso vero e' solo a k=1 (41%).")
|
||||||
|
print(" 3. IL MURO NON E' IL MIN-ORDER, E' IL PESO DELLO SLEEVE. Con $600 DEDICATI, XS01 gira")
|
||||||
|
print(" gia' oggi a k=5 (tracking 1.04, haircut -0.04). E' al 15% del book che muore: a")
|
||||||
|
print(" sleeve $90 il canonico esegue il 4% delle variazioni e il lordo scende al 13% del")
|
||||||
|
print(" target. A quel capitale k=2 e' l'unica cella CON EDGE che traccia (1.01: k=1 traccia")
|
||||||
|
print(" ma non ha edge, Sh 0.13). Il '~$20k' e'")
|
||||||
|
print(" quindi il prodotto di due scelte (peso 15% + k=5), non una proprieta' del segnale.")
|
||||||
|
print(" 4. XSR01 NON SI CONCENTRA: k<=3 perde l'edge IN-SAMPLE (IS 0.96 -> 0.28) e il gate")
|
||||||
|
print(" marginale lo declassa ad ADDS -> NOISE (has_insample_edge=False). Ipotesi (c)")
|
||||||
|
print(" CONFERMATA: li' l'ampiezza E' l'edge. E muore a ~28 bps/gamba di costo totale a")
|
||||||
|
print(" OGNI k, contro >50 bps di XS-LITE: concentrare non ripara il rischio #1 di XSR01.")
|
||||||
|
print(" 5. ⛔ NIENTE DI QUESTO E' DEPLOYABILE OGGI, e non per una ragione misurata qui:")
|
||||||
|
print(" XS01/XSR01 vivono su HYPERLIQUID, e la decisione dell'operatore del 26/07 tiene")
|
||||||
|
print(" il 100% del capitale su Deribit fino a $20k. A $20k il canonico e' eseguibile da")
|
||||||
|
print(" solo. XS-LITE serve SE e QUANDO si apre HL sotto quella soglia — non prima.")
|
||||||
|
print("-" * 108)
|
||||||
|
print(" GATE PRE-REGISTRATO (dichiarato ORA, prima di qualunque dato nuovo):")
|
||||||
|
print(" XS-LITE k=2 su MAJ19 si riapre SOLO se si apre un conto Hyperliquid con >= $300")
|
||||||
|
print(" dedicati allo sleeve, e in quel caso si deploya il k CANONICO (5) se il capitale")
|
||||||
|
print(" dello sleeve e' >= $750, k=2 sotto. Nessun nuovo backtest serve per questa scelta:")
|
||||||
|
print(" e' gia' decisa dalla tabella (5) e non va ri-ottimizzata guardando i dati nuovi.")
|
||||||
|
print(" Come CANDIDATO a sleeve XS-LITE resta bocciato: DSR FAIL a ogni conteggio e null")
|
||||||
|
print(" del de-levering non superato. Non si ri-testa prima del 2027-06 (3.5 anni di")
|
||||||
|
print(" storia HL, di cui >=1.5 fuori dal campione di scoperta di XS01).")
|
||||||
print("=" * 108)
|
print("=" * 108)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user