fix(data): split non aggiustati nel feed equity — difetto sul libro live, riparato alla fonte
IWM ed EFA avevano uno split NON aggiustato il 2005-06-09 (IWM 2:1 = -49.5%, EFA 3:1 = -66.5%): IB ADJUSTED_LAST non li aveva aggiustati. La certificazione non li vedeva per un punto cieco STRUTTURALE: l'unica guardia sui salti era `maxret > 50% -> SPIKE?` e uno split 2:1 fa esattamente -50%, cioe' cade sul filo della soglia (IWM passava a 49.5% con status OK). IWM e' una delle 6 gambe di GTAA01, sleeve in PRODUZIONE. Impatto misurato: GTAA6 FULL Sharpe 0.61 -> 0.64, IS (<2015) 0.49 -> 0.54; OOS 2015+ e maxDD INVARIATI (l'artefatto e' nel 2005, fuori hold-out) -> il difetto SOTTOSTIMAVA lo sleeve: nessuna decisione presa va rivista. Discriminante split-vs-crollo: NON il rapporto (SLV 2026-01-30 ha rapporto 1.3994, a 4bps da 1.4, ma e' un crollo vero: GLD -10.3% lo stesso giorno) ma il RANGE INTRADAY — lo split apre gia' al nuovo livello con range normale (IWM: open 47.00, range 1.7%), il crollo si muove DENTRO la barra (SLV: range 33%). - src/data/eq_splits.py: detect_unadjusted_splits() a 3 condizioni congiunte (|ret|>20% AND rapporto ~ fattore comune AND range intraday <5%) + repair_splits() con split multipli componibili; - riparazione in LETTURA in src/portfolio/gtaa.py::_close (produzione) e scripts/research/eqlib.py::load_eq (ricerca); - fetch_ib_equities.certify(): nuovo status SPLIT-NON-AGG + elenco split rilevati; - tests/test_eq_splits.py: 8 casi, inclusi il falso positivo SLV e un crollo -50% esatto con range grande. Regola nuova: ogni soglia di certificazione tarata su un valore tondo va controllata contro il difetto che genera esattamente quel valore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,11 +8,14 @@ Espone: universi (SECTORS/BROAD), load_eq(sym), panel(universe) allineato, e riu
|
||||
indurito di altlib (_sh, _dd_ret, _to_daily, marginal_vs_tp01) per giudicare i candidati con la
|
||||
stessa disciplina del lato crypto.
|
||||
"""
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
import numpy as np, pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from src.data.eq_splits import repair_splits # noqa: E402
|
||||
RAW = ROOT / "data" / "raw"
|
||||
|
||||
# 11 SPDR settoriali. I 9 "classici" (sotto SECTORS_CLASSIC) partono 1998; XLRE 2015, XLC 2018.
|
||||
@@ -29,6 +32,9 @@ def load_eq(sym: str) -> pd.DataFrame:
|
||||
raise FileNotFoundError(f"{p} assente — gira: uv run --with ib_async python scripts/research/fetch_ib_equities.py")
|
||||
d = pd.read_parquet(p).copy()
|
||||
d.index = pd.to_datetime(d["timestamp"], unit="ms", utc=True)
|
||||
d = d.sort_index()
|
||||
# split NON aggiustati da IB (IWM/EFA 2005-06-09) — vedi src/data/eq_splits.py
|
||||
d, _ = repair_splits(d)
|
||||
return d[["open", "high", "low", "close", "volume"]]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user