842420ce7b
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>
80 lines
3.0 KiB
Python
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()
|