1dd26342ca
Delle sei misure prodotte oggi solo UNA apriva una decisione: il peso 75/25 fu confermato il 24/07 con la lente HOURLY, e il 26/07 quella lente e' risultata pessimistica su ENTRAMBI i lati di SKH01 (uscite +0.081, ingressi +0.048 di Sharpe di book). Se SKH01 vale piu' di come e' stato pesato, 0.25 poteva non essere piu' l'ottimo. Contro tirava il LOO de-luckato dello stesso giorno (SKH01 = il meno affidabile dei cinque). Due correzioni in versi opposti: si misura, non si deduce. MISURA (path live intra_entry=True, 8 offset, mediana delle differenze APPAIATE): argmax a w=0.35, e 0.30-0.40 batte 0.25 nell'88% degli offset -> il segnale c'e' ed e' nel verso previsto dalla correzione di lente. Ma il plateau entro 0.05 di Sharpe e' [0.25 ... 0.50] e il peso live e' DENTRO, e `weights_tilt_null` FALLISCE (delta_insample -0.0026, gate_pass False). IL MOTIVO VERO sta in cio' che la mediana nasconde: il guadagno e' tutto nella coda ALTA. p10 per peso 1.463 / 1.465 / 1.455 / 1.435 / 1.374 mentre il p90 sale monotono 1.915 -> 2.064. Alzare SKH01 non compra Sharpe, compra dipendenza da quale ancora ti e' capitata (banda da 0.45 a 0.69). Coerente con altre due misure indipendenti dello stesso giorno: SKH01 ha la frazione d'ancora piu' grande da restituire (LOO) ed e' 4x piu' fee-sensibile. Tre misure indipendenti dicono che SKH01 e' la gamba fragile del book e che 0.25 sta all'estremo prudente della regione robusta. LA RIVALUTAZIONE CHE CONTA NON E' SULLA STRATEGIA. Ordini di grandezza a confronto: ottimizzare il peso = +0.030 Sharpe (gate fallito); versare EUR 250/mese invece di EUR 0 = da MAI a 16.2 anni (P entro 20a = 92%); perdere il conto Deribit a p=1% = -18% di probabilita' di arrivare, ripartendo da zero. Il book e' dentro il suo plateau su ogni asse misurato: la ricerca ha smesso di essere il vincolo binding. I vincoli binding oggi sono capitale che entra e conto che non sparisce. Book, pesi, cron, config INVARIATI. Gate pre-registrati alle loro date (anticiparli sarebbe selezione sull'hold-out). 365 test verdi (+6). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 lines
6.4 KiB
Python
138 lines
6.4 KiB
Python
#!/usr/bin/env python
|
|
"""r0726_reeval_live_weight.py — il peso 75/25 regge dopo le correzioni di oggi?
|
|
|
|
PERCHE' RIAPRIRLO. Il peso del book live (TP01 0.75 / SKH01 0.25) fu confermato il **24/07**
|
|
(`r0724_skh_live_weight.py`) misurando il path live con la lente **hourly**. Il 26/07 quella lente
|
|
e' risultata **sistematicamente pessimistica su ENTRAMBI i lati** di SKH01:
|
|
* USCITE: `resample_5m` non scarta il bin in corso e `_skyhook_positions` ci itera dentro -> il
|
|
live rileva SL/TP intra-barra; la lente hourly sottostima **+0.081 Sharpe FULL di book**;
|
|
* INGRESSI: il cron valuta a ogni confine orario dentro il bin 230m, non a chiusura di bin ->
|
|
mediana appaiata **+0.112** a livello di sleeve, **+0.048** di Sharpe FULL di book.
|
|
Se SKH01 vale piu' di come e' stato pesato, **l'ottimo potrebbe non essere piu' 0.25**. E in senso
|
|
opposto tira il leave-one-out de-luckato dello stesso giorno, che lo da' come il **meno affidabile**
|
|
dei cinque sleeve (+0.142 FULL, positivo nel 91% delle estrazioni, contro +0.390/100% di TP01).
|
|
|
|
Due correzioni misurate che puntano in versi OPPOSTI: la domanda non e' retorica, e la risposta non
|
|
si puo' dedurre. Qui si misura.
|
|
|
|
METODO
|
|
* path live vero: `intra_entry=True` (ingressi intra-bin) sui segnali gia' in cache;
|
|
* griglia di peso 0.00-0.50, su 8 offset (sottocampione a priori `OFFSETS_SUB`);
|
|
* statistica = **mediana delle differenze APPAIATE** per offset (mai differenza di mediane);
|
|
* qualunque cambio proposto passa poi da `weights_tilt_null`, che e' il gate del progetto.
|
|
|
|
uv run python scripts/research/r0726_reeval_live_weight.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"))
|
|
|
|
import r0726_skh_live_book as LB # noqa: E402
|
|
import r0726_skh_sigcache as SC # noqa: E402
|
|
from src.portfolio.portfolio import (combine_outer, metrics, # noqa: E402
|
|
to_daily, weights_tilt_null)
|
|
from src.portfolio.sleeves import tp01_sleeve # noqa: E402
|
|
|
|
W_GRID = (0.00, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.50)
|
|
W_LIVE = 0.25
|
|
HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC")
|
|
|
|
|
|
def _sh(s: pd.Series) -> float:
|
|
v = s.dropna()
|
|
return float(v.mean() / v.std() * np.sqrt(365)) if len(v) > 2 and v.std() > 0 else 0.0
|
|
|
|
|
|
def main() -> None:
|
|
print("=" * 100)
|
|
print(" r0726 — RIVALUTAZIONE del peso live TP01/SKH01 dopo le correzioni del 26/07")
|
|
print("=" * 100)
|
|
print(" Il 24/07 confermo' 0.25 con la lente HOURLY. Oggi quella lente e' misurata")
|
|
print(" pessimistica su entrambi i lati di SKH01 (uscite +0.081, ingressi +0.048 di book).")
|
|
print(" Contro: il LOO de-luckato da' SKH01 come il MENO affidabile dei cinque.")
|
|
|
|
have = [o for o in SC.OFFSETS_SUB
|
|
if (SC.CACHE_DIR / f"sigtab_btc_off{o:03d}.parquet").exists()
|
|
and (SC.CACHE_DIR / f"sigtab_eth_off{o:03d}.parquet").exists()]
|
|
print(f"\n offset disponibili in cache: {have} ({len(have)}/23 della griglia piena)")
|
|
if len(have) < 4:
|
|
print(" ✋ troppo pochi offset: rilanciare r0726_skh_sigcache.py")
|
|
return
|
|
|
|
tp = tp01_sleeve(weight=1.0).daily()
|
|
|
|
# ---- per ogni offset, il path LIVE di SKH01 e le curve di Sharpe al variare del peso
|
|
rows_full, rows_hold = {w: [] for w in W_GRID}, {w: [] for w in W_GRID}
|
|
for off in have:
|
|
dailies = {}
|
|
for a in ("BTC", "ETH"):
|
|
dailies[a] = LB.paths_for(a, off)["live"]
|
|
skh = LB.skh_book(dailies)
|
|
for w in W_GRID:
|
|
bk = combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": 1.0 - w, "SKH01": w})
|
|
rows_full[w].append(_sh(bk))
|
|
rows_hold[w].append(_sh(bk[bk.index >= HOLDOUT]))
|
|
|
|
print(f"\n {'peso SKH':>9}{'Sh FULL med':>13}{'banda p10-p90':>22}"
|
|
f"{'Sh HOLD med':>13}{'d vs 0.25 (appaiata)':>23}")
|
|
base_f = np.array(rows_full[W_LIVE])
|
|
base_h = np.array(rows_hold[W_LIVE])
|
|
best_f, best_w = -9e9, None
|
|
for w in W_GRID:
|
|
f = np.array(rows_full[w])
|
|
h = np.array(rows_hold[w])
|
|
dpair = np.median(f - base_f) # mediana delle DIFFERENZE, non differenza di mediane
|
|
mf = float(np.median(f))
|
|
if mf > best_f:
|
|
best_f, best_w = mf, w
|
|
tag = " <- LIVE" if w == W_LIVE else ""
|
|
print(f" {w:>9.2f}{mf:>13.3f}"
|
|
f" [{np.percentile(f,10):>6.3f},{np.percentile(f,90):>6.3f}]"
|
|
f"{float(np.median(h)):>13.3f}{dpair:>+15.3f}"
|
|
f" ({(f>base_f).mean()*100:>3.0f}%){tag}")
|
|
|
|
print(f"\n argmax della mediana FULL: w = {best_w:.2f} (live = {W_LIVE:.2f})")
|
|
|
|
# ---- plateau: un argmax senza plateau e' rumore
|
|
near = [w for w in W_GRID if np.median(rows_full[w]) >= best_f - 0.05]
|
|
print(f" plateau entro 0.05 di Sharpe: {near}")
|
|
if W_LIVE in near:
|
|
print(f" -> il peso live E' dentro il plateau: la differenza col massimo non e'")
|
|
print(f" distinguibile, e cambiare peso su questa base sarebbe rumore.")
|
|
else:
|
|
print(f" -> il peso live e' FUORI dal plateau: il cambio va valutato dal gate.")
|
|
|
|
# ---- gate del progetto su un eventuale cambio
|
|
if best_w != W_LIVE:
|
|
print(f"\n GATE `weights_tilt_null` sul cambio {W_LIVE:.2f} -> {best_w:.2f}:")
|
|
off0 = have[0]
|
|
skh0 = LB.skh_book({a: LB.paths_for(a, off0)["live"] for a in ("BTC", "ETH")})
|
|
res = weights_tilt_null({"TP01": to_daily(tp), "SKH01": to_daily(skh0)},
|
|
{"TP01": 1 - W_LIVE, "SKH01": W_LIVE},
|
|
{"TP01": 1 - best_w, "SKH01": best_w},
|
|
k_seen=len(W_GRID))
|
|
for k in ("delta_insample", "pctl_hold", "gate_pass"):
|
|
if k in res:
|
|
print(f" {k:<16} {res[k]}")
|
|
print(f"\n ⚠️ Anche a gate passato servirebbero finestre OOS disgiunte e un controllo di")
|
|
print(f" eseguibilita' al peso aumentato: il gate e' NECESSARIO, non sufficiente.")
|
|
else:
|
|
print(f"\n Nessun cambio da valutare: l'argmax coincide col peso live.")
|
|
|
|
# ---- sensibilita' alla fee, che il 1 agosto puo' cambiare
|
|
print(f"\n PROMEMORIA fee (r0726_fee_sensitivity): SKH01 e' ~4x piu' sensibile di TP01")
|
|
print(f" (-0.69% vs -0.09% di CAGR per bps/lato). La regola gia' decisa resta:")
|
|
print(f" taker <=5bps -> non si tocca nulla; >10bps -> rivedere PRIMA il peso di SKH01.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|