Files
Adriano Dal Pastro 842420ce7b research: follow-up SKH01 chiuso — il blocco non esisteva, il de-luck x0.6 era troppo severo
Il follow-up "book sul path live" era fermo da tre sessioni su questa premessa:
"il simulatore compone per-trade a nozionale unitario, LO SLEEVE E' VOL-TARGETED".

LA PREMESSA ERA FALSA. Verificato in tre modi: _skyhook_returns chiama backtest_signals
con leverage=1.0/position_size=1.0; nessun target_vol/vol_target/realized_vol nel
sorgente; sim_equity(canonical) riproduce backtest_signals a max|diff| = 0.0. Il 20.7%
di vol realizzata dello sleeve e' un PRODOTTO della strategia (uscite % asimmetriche +
poco tempo a mercato), non un parametro: SKH01 e' l'unica delle 5 a NON essere
vol-targeted, il contrario di quanto si credeva. Test permanente cablato.

MISURA (ingressi live vs backtest, de-luckata su 8 offset a priori; sanity 120/120 bin
con ingresso ricostruiti):
- il numero del 26/07 era ~4x troppo grande: like-with-like (mediana PER-ASSET) +0.38 su
  3 offset -> +0.097 su 8, e "6/6 non negativi" -> 13/16. Sleeve 50/50: +0.112, 8/8.
- a livello di BOOK lo Sharpe e' una monetina (FULL +0.048, HOLD +0.051) ma il DRIFT e'
  +0.73pp positivo nel 100% delle estrazioni: l'ingresso intra-bin prende un prezzo
  migliore, i falsi ingressi aggiungono churn, vol e ritorno salgono insieme.

IL FATTORE x0.6 DECOMPOSTO E MISURATO:
  (a) fortuna d'ancora sul DRIFT: x0.874 (5-sleeve) / x0.890 (book live). La vol e'
      invariata fra le ancore (7.80->7.76%): la fortuna sta tutta nel drift.
  (b) path live: NON-NEGATIVO ovunque (uscite +0.081 Sh, ingressi +0.73pp, TP01 ~0).
Il x0.6 implicava un residuo x0.687 attribuito al live oltre l'ancora, che nessuna misura
sostiene -> FATTORE ONESTO x0.87-0.91, troppo severo del 31-34%. Il sospetto registrato
il 25/07 ("conta due volte la degradazione SKH01") e' confermato quantitativamente.

MURI DI CAPITALE ricalcolati con la stessa macchineria del 25/07: perpetua 6.00% ->
10.91%, muro per 50 EUR/g $494.758 -> $272.061 (-45%) a leva 1.0. La conclusione
STRUTTURALE non cambia: $272k restano ~453x il conto di oggi.

IPOTESI MIA REFUTATA nella stessa sessione: che la grid timing-luck di SKH01 fosse un
artefatto della lente a chiusura-di-bin. Dispersione LIVE/CANONICO = 1.59x -> il live e'
PIU' disperso. L'audit del 02/07 resta valido com'e'. (Nata su 2 offset, chiusa a 8.)

Trovato per strada: simulate() in r0726_skh_partial_entry.py non era mai chiamata da
main() — i numeri headline di quel diario venivano da una corsa mai committata.

Book, pesi, cron, config INVARIATI.

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

80 lines
3.0 KiB
Python

#!/usr/bin/env python
"""r0726_skh_sigcache.py — costruisce e mette in CACHE le tabelle di segnale intra-bin di SKH01.
E' il pezzo caro del follow-up "book sul path live" (`r0726_skh_live_book.py`): per ogni
(asset, offset) ricostruisce il segnale che il cron ORARIO vedrebbe a OGNI osservazione dentro
ogni bin 230m — ~82.000 righe, ~9 minuti. Isolato qui perche' e' (a) puro, (b) riusabile, e
(c) ripartibile: la cache e' su disco, il job si puo' interrompere e riprendere.
Sottocampione di offset dichiarato A PRIORI: 8 degli 23 della griglia SKH01, uno ogni 90 minuti
su [0, 690). NON e' una selezione — e' un sottocampione uniforme scelto per costo (2 core, ~9
min per run, cron live da non affamare). Va dichiarato perche' la banda d'ancora che ne esce ha
8 punti invece di 23.
uv run python scripts/research/r0726_skh_sigcache.py [--assets BTC ETH] [--offsets ...]
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
import r0726_skh_partial_entry as SP # noqa: E402
# 8 offset uniformi su [0,690), uno ogni 90m. Dichiarati a priori, offset 0 = canonico.
OFFSETS_SUB = (0, 90, 180, 270, 360, 450, 540, 630)
ASSETS = ("BTC", "ETH")
CACHE_DIR = ROOT / "data" / "_cache" / "skh_sigtab"
def cache_path(asset: str, off: int) -> Path:
return CACHE_DIR / f"sigtab_{asset.lower()}_off{off:03d}.parquet"
def get_signal_table(asset: str, off: int, verbose: bool = True) -> pd.DataFrame:
"""Tabella di segnale intra-bin, da cache se presente."""
p = cache_path(asset, off)
if p.exists():
return pd.read_parquet(p)
t0 = time.time()
tab = SP.signal_table(asset, off)
p.parent.mkdir(parents=True, exist_ok=True)
tab.to_parquet(p, index=False)
if verbose:
print(f" {asset} off{off:>3} {len(tab):>7} righe "
f"segnali {int((tab['dir'] != 0).sum()):>5} in {time.time()-t0:5.0f}s", flush=True)
return tab
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--assets", nargs="+", default=list(ASSETS))
ap.add_argument("--offsets", nargs="+", type=int, default=list(OFFSETS_SUB))
args = ap.parse_args()
print(f"[SIGCACHE] {len(args.assets)} asset x {len(args.offsets)} offset "
f"-> {CACHE_DIR}", flush=True)
t0 = time.time()
done = 0
for off in args.offsets: # offset esterno: cosi' ogni offset e'
for a in args.assets: # completo su entrambi gli asset appena finisce
p = cache_path(a, off)
if p.exists():
print(f" {a} off{off:>3} gia' in cache", flush=True)
continue
get_signal_table(a, off)
done += 1
print(f"[SIGCACHE] fatto: {done} nuove tabelle in {time.time()-t0:.0f}s", flush=True)
if __name__ == "__main__":
main()