cblib causale: spot +1h e DVOL +1 giorno (debito §5.18); §11 rimisurato 0,714 → 0,712, verdetti invariati
- cblib.causale(s, cadenza): spot_series e dvol_series escono causali (asof = ultima chiusura NOTA); il DVOL giornaliero era una seconda serie con lo stesso difetto (fino a 24h avanti), verificato contro l'API pubblica a 1h. Regolamento ST alle 08:00 invece delle 09:00. - §11 riprodotto al millesimo su worktree HEAD (0,714 [0,690-0,779], n=19, flag --al) e rimisurato: 0,712 [0,664-0,732]; solo spot 0,721, solo DVOL 0,693, DVOL orario di controllo 0,717. Mediana onesta BTC 1,45 → 2,12, fee 1,51x/1,87x. r0730: 0,73 → 0,71. §75: ETH pre 0,76, BTC durante 0,89 (orario 0,83). skew panel ≤0,005. vrp_f_watch: f canonico 0,732 → 0,706, differenza +0,097 [+0,042, +0,140]. - r0909 senza doppio shift, contesto sul DVOL del giorno; r0822 regime su date di calendario; r0901 join riallineato (bit-exact). Aperti con costo: DVOL orario (−0,04/+0,06 nel crollo), spot 5m (≤25 min). - test: +3 causalita' in test_cb_chain_vrp, anti-doppio-shift in test_r0909; suite 1011/1011. - revisione fable: 16 segnalazioni, tutte applicate (fra cui un «−1,8% in un'ora» che era +3,9%). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjj5vPEBoAJrB23P6RjKzs
This commit is contained in:
@@ -23,7 +23,8 @@ sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "analysis"))
|
||||
|
||||
from cblib import bs_put, f_factors, pick_legs # noqa: E402
|
||||
import cblib # noqa: E402
|
||||
from cblib import bs_put, causale, f_factors, pick_legs # noqa: E402
|
||||
from certify_cb_chain import ( # noqa: E402
|
||||
crossed_rate, hollow_rate, monotonicity_violations, worst_day_hollow,
|
||||
)
|
||||
@@ -196,3 +197,61 @@ def test_nel_sleeve_f_moltiplica_il_credito_NETTO():
|
||||
assert SL.VRP_CFG["f"] == 1.0, (
|
||||
"il sleeve gira ancora a f=1.0: se cambia, aggiornare il diario 2026-07-30 e CLAUDE.md"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================================
|
||||
# C. CAUSALITA' delle serie di contesto (debito §5.18, 09/09): `asof(ts)` non guarda avanti
|
||||
# ===========================================================================================
|
||||
|
||||
def test_causale_rietichetta_alla_chiusura_e_non_tocca_i_valori():
|
||||
"""Il feed e' etichettato all'APERTURA: la chiusura della barra T e' nota a T+cadenza.
|
||||
DEVE FALLIRE SE: qualcuno "semplifica" togliendo lo spostamento, o sposta i VALORI."""
|
||||
idx = pd.date_range("2026-06-01", periods=5, freq="h", tz="UTC")
|
||||
S = pd.Series([10.0, 11.0, 12.0, 13.0, 14.0], index=idx)
|
||||
Sc = causale(S, "1h")
|
||||
assert list(Sc.index) == list(idx + pd.Timedelta(hours=1))
|
||||
assert Sc.asof(idx[2]) == 11.0, "a 02:00 e' nota la chiusura della barra 01:00"
|
||||
assert S.asof(idx[2]) == 12.0, "il difetto che la funzione ripara (controllo positivo)"
|
||||
assert np.isnan(Sc.asof(idx[0])), "prima della prima chiusura nota non c'e' spot"
|
||||
assert list(Sc.values) == list(S.values), "i VALORI non si toccano, solo le etichette"
|
||||
|
||||
|
||||
def test_spot_series_e_causale_sul_feed_1h(monkeypatch):
|
||||
"""`cblib.spot_series(a).asof(ts)` = ultima chiusura ORARIA nota a ts. Controllo positivo:
|
||||
la serie grezza del feed (etichettata all'apertura) a ts da' la chiusura di ts+1h."""
|
||||
import scripts.analysis.research_lab as rl
|
||||
idx = pd.date_range("2026-06-01", periods=6, freq="h", tz="UTC")
|
||||
epoch = pd.Timestamp("1970-01-01", tz="UTC") # NON `asi8 // 10**6`: in pandas 3 e' in `us` (D6)
|
||||
px = pd.DataFrame({"timestamp": ((idx - epoch) // pd.Timedelta(milliseconds=1)).astype("int64"),
|
||||
"close": [100.0, 101.0, 102.0, 103.0, 104.0, 105.0]})
|
||||
monkeypatch.setattr(rl, "load_tf", lambda asset, tf: px)
|
||||
cblib.spot_series.cache_clear()
|
||||
try:
|
||||
S = cblib.spot_series("ZZZ")
|
||||
ts = idx[3] + pd.Timedelta(minutes=17) # 03:17: la barra 03:00 e' ancora aperta
|
||||
assert S.asof(ts) == 102.0, "a 03:17 l'ultima chiusura nota e' quella della barra 02:00"
|
||||
assert S.asof(idx[3]) == 102.0, "a 03:00 esatte la barra 02:00 e' appena chiusa"
|
||||
assert S.asof(idx[5] + pd.Timedelta(hours=1)) == 105.0, "l'ultima chiusura arriva un'ora dopo"
|
||||
grezza = pd.Series(px["close"].values, index=idx)
|
||||
assert grezza.asof(ts) == 103.0, "il difetto riparato: la serie del feed a 03:17 da' la chiusura delle 04:00"
|
||||
finally:
|
||||
cblib.spot_series.cache_clear()
|
||||
|
||||
|
||||
def test_dvol_series_e_causale_sul_feed_giornaliero(monkeypatch, tmp_path):
|
||||
"""La riga DVOL del giorno D e' la chiusura delle 23:00 di D: dentro D non e' ancora nota.
|
||||
`cblib.dvol_series(a).asof(ts in D)` deve dare la chiusura di D−1."""
|
||||
days = pd.date_range("2026-06-01", periods=4, freq="D", tz="UTC")
|
||||
epoch = pd.Timestamp("1970-01-01", tz="UTC")
|
||||
pd.DataFrame({"timestamp": ((days - epoch) // pd.Timedelta(milliseconds=1)).astype("int64"),
|
||||
"close": [36.0, 37.0, 43.0, 47.0]}).to_parquet(tmp_path / "dvol_zzz.parquet")
|
||||
monkeypatch.setattr(cblib, "RAW", tmp_path)
|
||||
cblib.dvol_series.cache_clear()
|
||||
try:
|
||||
V = cblib.dvol_series("ZZZ")
|
||||
ts = days[2] + pd.Timedelta(hours=10) # 03/06 10:00
|
||||
assert V.asof(ts) == 37.0, "dentro il 03/06 l'ultimo DVOL noto e' la chiusura del 02/06"
|
||||
assert V.asof(days[3]) == 43.0, "alle 00:00 del 04/06 e' nota la chiusura del 03/06"
|
||||
assert np.isnan(V.asof(days[0] + pd.Timedelta(hours=5))), "nel primo giorno non c'e' storia"
|
||||
finally:
|
||||
cblib.dvol_series.cache_clear()
|
||||
|
||||
Reference in New Issue
Block a user