"""EQLIB — harness di ricerca EQUITY/ETF (branch research/equities-ib). Legge la CACHE su disco (data/raw/eq_*.parquet, ADJUSTED_LAST, scritta una volta da fetch_ib_equities.py) — MAI da IB. `lru_cache` -> ogni parquet si legge una sola volta per processo. "Memorizza i dati per non rileggerli ogni volta": la persistenza e' il parquet su disco + questa cache. Espone: universi (SECTORS/BROAD), load_eq(sym), panel(universe) allineato, e riusa lo scorer indurito di altlib (_sh, _dd_ret, _to_daily, marginal_vs_tp01) per giudicare i candidati con la stessa disciplina del lato crypto. """ from functools import lru_cache from pathlib import Path import numpy as np, pandas as pd ROOT = Path(__file__).resolve().parents[2] RAW = ROOT / "data" / "raw" # 11 SPDR settoriali. I 9 "classici" (sotto SECTORS_CLASSIC) partono 1998; XLRE 2015, XLC 2018. SECTORS = ["XLK", "XLF", "XLE", "XLV", "XLI", "XLP", "XLY", "XLU", "XLB", "XLRE", "XLC"] SECTORS_CLASSIC = ["XLK", "XLF", "XLE", "XLV", "XLI", "XLP", "XLY", "XLU", "XLB"] # storia lunga (1998+) BROAD = ["SPY", "QQQ", "IWM", "TLT", "GLD", "HYG"] @lru_cache(maxsize=64) def load_eq(sym: str) -> pd.DataFrame: """OHLCV aggiustato (dividendi+split) per `sym`, indicizzato datetime UTC. Cache su disco -> RAM.""" p = RAW / f"eq_{sym.lower()}_1d.parquet" if not p.exists(): 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) return d[["open", "high", "low", "close", "volume"]] @lru_cache(maxsize=16) def _close_panel(universe: tuple) -> pd.DataFrame: cols = {s: load_eq(s)["close"].astype(float) for s in universe} return pd.concat(cols, axis=1).sort_index() def panel(universe=tuple(SECTORS), how: str = "inner") -> pd.DataFrame: """Prezzi close aggiustati [date x asset]. how='inner' = date comuni a TUTTI (start = ETF piu' giovane); 'outer' = unione (NaN dove un ETF non esiste ancora).""" P = _close_panel(tuple(universe)) return P.dropna(how="any") if how == "inner" else P def describe(universe=None): universe = universe or (SECTORS + BROAD) print(f" {'sym':6} {'barre':>6} {'da':>11} {'a':>11} {'anni':>5}") for s in universe: try: d = load_eq(s) print(f" {s:6} {len(d):>6} {str(d.index[0].date()):>11} {str(d.index[-1].date()):>11} " f"{(d.index[-1]-d.index[0]).days/365.25:>5.1f}") except FileNotFoundError: print(f" {s:6} (assente)") Pc = panel(SECTORS_CLASSIC); Pa = panel(SECTORS) print(f"\n panel 9 settori CLASSICI: {Pc.shape[1]}x{len(Pc)} start comune {Pc.index[0].date()}") print(f" panel 11 settori : {Pa.shape[1]}x{len(Pa)} start comune {Pa.index[0].date()}") if __name__ == "__main__": print("=" * 70); print(" EQLIB — cache equity su disco (nessun IB)"); print("=" * 70) describe()