Files
PythagorasGoal/tests/test_skh_live_book.py
T
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

120 lines
4.6 KiB
Python

"""Test per il follow-up "book sul path live di SKH01" (r0726_skh_live_book / _deluck_factor).
Il test piu' importante e' `test_lente_sleeve_uguale_a_quella_del_simulatore`: e' l'invariante su
cui poggia TUTTA la misura, ed e' esattamente la premessa che per tre sessioni si era creduta
FALSA ("lo sleeve e' vol-targeted, il simulatore no"). Se qualcuno domani aggiunge un vol-target
a `_skyhook_returns`, quella premessa diventa vera e i numeri di questo filone smettono di valere:
il test deve fallire, non passare in silenzio.
"""
from __future__ import annotations
import inspect
import sys
from pathlib import Path
import numpy as np
import pandas as pd
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
# ===========================================================================
# L'INVARIANTE DI LENTE — la premessa del follow-up
# ===========================================================================
def test_sleeve_skyhook_non_e_vol_targeted():
"""`_skyhook_returns` compone per-trade a nozionale unitario: niente vol-target.
E' il fatto che sblocca il follow-up; se cambia, va rifatta tutta la misura."""
from src.portfolio.sleeves import _skyhook_returns
src = inspect.getsource(_skyhook_returns)
assert "leverage=1.0" in src
for kw in ("target_vol", "vol_target", "realized_vol"):
assert kw not in src, f"lo sleeve ora usa {kw}: la lente e' cambiata, rifare r0726"
def test_lente_sleeve_uguale_a_quella_del_simulatore():
"""sim_equity(mode='canonical') deve riprodurre backtest_signals ESATTAMENTE.
E' cio' che rende confrontabili path live e sleeve senza conversioni."""
import r0702_anchor_skh01 as r02
_, m, ltf, ent = r02.run_asset("BTC", 0)
eq = r02.sim_equity(ltf, ent, "canonical")
assert float(np.max(np.abs(eq - m.equity))) == 0.0
# ===========================================================================
# Convenzioni di combinazione
# ===========================================================================
def _s(vals, start="2020-01-01"):
return pd.Series(np.asarray(vals, float),
index=pd.date_range(start, periods=len(vals), freq="1D", tz="UTC"))
def test_skh_book_e_5050_su_inner_join():
import r0726_skh_live_book as LB
a = _s([0.01, 0.02, 0.03])
b = _s([0.03, 0.02, 0.01])
out = LB.skh_book({"BTC": a, "ETH": b})
assert list(np.round(out.values, 10)) == [0.02, 0.02, 0.02]
def test_skh_book_inner_join_taglia_le_date_non_comuni():
"""Convenzione dello sleeve: inner join. Se un asset ha meno storia, il book parte dopo."""
import r0726_skh_live_book as LB
a = _s([0.01] * 5, start="2020-01-01")
b = _s([0.03] * 3, start="2020-01-03")
out = LB.skh_book({"BTC": a, "ETH": b})
assert len(out) == 3
assert out.index[0] == pd.Timestamp("2020-01-03", tz="UTC")
def test_book_metrics_drift_e_annualizzato_a_365():
import r0726_skh_live_book as LB
r = 0.001
cols = {"X": _s([r] * 400)}
_, _, drift = LB.book_metrics(cols, {"X": 1.0})
assert drift == pytest.approx(r * 365.25, rel=1e-9)
def test_drift_and_cagr_coerenti():
import r0726_deluck_factor as DF
r = 0.0005
cols = {"X": _s([r] * 800)}
drift, cagr, vol = DF.drift_and_cagr(cols, {"X": 1.0})
assert drift == pytest.approx(r * 365.25, rel=1e-9)
assert cagr == pytest.approx((1 + r) ** 365.25 - 1, rel=1e-6) # serie costante -> CAGR esatto
assert vol == pytest.approx(0.0, abs=1e-12) # nessuna varianza
# ===========================================================================
# Il sottocampione di offset: dichiarato a priori, non selezionato
# ===========================================================================
def test_offsets_sub_e_un_sottoinsieme_uniforme_della_griglia_piena():
import r0702_anchor_skh01 as r02
import r0726_skh_sigcache as SC
full = set(r02.OFFSETS)
assert set(SC.OFFSETS_SUB) <= full, "il sottocampione contiene offset fuori griglia"
assert SC.OFFSETS_SUB[0] == 0, "l'offset canonico deve essere incluso"
passi = np.diff(np.array(SC.OFFSETS_SUB))
assert len(set(passi.tolist())) == 1, f"sottocampione non uniforme: passi {passi}"
def test_cache_path_e_deterministico_e_separa_asset_e_offset():
import r0726_skh_sigcache as SC
p1 = SC.cache_path("BTC", 0)
p2 = SC.cache_path("BTC", 90)
p3 = SC.cache_path("ETH", 0)
assert p1 != p2 and p1 != p3
assert p1 == SC.cache_path("BTC", 0)
assert p1.suffix == ".parquet"