From 5d02b7200ab01073eccfe26bded06216d83a0a5a Mon Sep 17 00:00:00 2001 From: Brian McMahon Date: Sun, 24 May 2026 17:04:29 -0700 Subject: [PATCH] =?UTF-8?q?feat(executor):=20L1346=20(c)=20second=20half?= =?UTF-8?q?=20=E2=80=94=20price=5Fcache=20+=20eod=5Freconcile=20prefer=20u?= =?UTF-8?q?niverse.SPY=20with=20macro=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the L1346 (c) retirement of SPY-special-cases across the executor. Pre-fix sites used `if ticker in _MACRO_SYMBOLS` to route SPY reads to macro library (Close-only); post-L1346 #245 universe.SPY now has full OHLCV via `_UNIVERSE_EXTRA`. Implementation (mirrors alpha-engine-predictor #196 defensive shape): - `executor/price_cache.py::load_price_histories` — gates on `_MACRO_SYMBOLS_NO_OHLCV = _MACRO_SYMBOLS - {"SPY"}`. SPY routes to universe first; on universe-read failure, defensive fallback to macro.SPY (clearly logged via dual-exception-class read_errors line). - `executor/eod_reconcile.py` — same routing flip + defensive fallback. Non-SPY macro symbols (VIX/TNX/IRX/sector ETFs) remain macro-routed (still Close-only in macro library; no universe entry). Defensive fallback can be removed once L1346 (b)+(c) soak clean for ≥1 Saturday cycle (operator-promoted soak; ~1-2 Saturdays out). Test plan: - Full executor suite 962 pass. - Pre-existing alpha-engine #185 ATR exclusion site already updated to `_MACRO_SYMBOLS_NO_OHLCV` (PR #207). Closes ROADMAP L1346 (c) second half. Combined with #207 (first half) + #196 (predictor (b) gate), the entire L1346 retirement arc is now shipped except the operator soak-watch. Co-Authored-By: Claude Opus 4.7 (1M context) --- executor/eod_reconcile.py | 25 ++++++++++++++++++++++--- executor/price_cache.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/executor/eod_reconcile.py b/executor/eod_reconcile.py index fae1ff8..10f0892 100644 --- a/executor/eod_reconcile.py +++ b/executor/eod_reconcile.py @@ -651,8 +651,13 @@ def run(run_date: str | None = None) -> None: target_ts = pd.Timestamp(run_date).normalize() closing_prices: dict[str, float] = {} missing: list[str] = [] + # L1346 (c) second-half routing post-#245: SPY removed from macro-routed + # set since universe.SPY now carries full OHLCV. Defensive macro fallback + # for SPY preserves backwards compat — mirrors price_cache.py + predictor + # #196 pattern. + _MACRO_SYMBOLS_NO_OHLCV = _MACRO_SYMBOLS - {"SPY"} for ticker in positions.keys(): - if ticker in _MACRO_SYMBOLS: + if ticker in _MACRO_SYMBOLS_NO_OHLCV: if macro_lib is None: macro_lib = _open_macro_library(trades_bucket) lib = macro_lib @@ -661,8 +666,22 @@ def run(run_date: str | None = None) -> None: try: df = lib.read(ticker).data except Exception as e: - missing.append(f"{ticker} ({e.__class__.__name__})") - continue + # SPY-specific defensive fallback to macro.SPY if universe.SPY + # unreadable. Removed once L1346 (b)+(c) soak clean ≥1 cycle. + if ticker == "SPY" and lib is universe_lib: + if macro_lib is None: + macro_lib = _open_macro_library(trades_bucket) + try: + df = macro_lib.read(ticker).data + except Exception as e2: + missing.append( + f"{ticker} (universe={e.__class__.__name__}, " + f"macro={e2.__class__.__name__})" + ) + continue + else: + missing.append(f"{ticker} ({e.__class__.__name__})") + continue if df.empty or "Close" not in df.columns: missing.append(f"{ticker} (no Close column)") continue diff --git a/executor/price_cache.py b/executor/price_cache.py index f0aacbe..9371b0b 100644 --- a/executor/price_cache.py +++ b/executor/price_cache.py @@ -131,8 +131,17 @@ def load_price_histories( read_errors: list[str] = [] empty: list[str] = [] + # L1346 (c) second-half routing post-#245: + # SPY is no longer in `_MACRO_SYMBOLS_NO_OHLCV` because universe.SPY + # now carries full OHLCV (alpha-engine-data #245 _UNIVERSE_EXTRA + # write path; gate (a) verified via 5/24 DataPhase1 SSM log). + # Defensive fallback to macro.SPY preserves backwards compat during + # the cross-repo retirement — mirrors alpha-engine-predictor #196's + # universe-preferred + macro-fallback shape. Non-SPY macro symbols + # (VIX/TNX/IRX/sector ETFs) remain macro-routed (still Close-only). + _MACRO_SYMBOLS_NO_OHLCV = _MACRO_SYMBOLS - {"SPY"} for ticker in tickers: - if ticker in _MACRO_SYMBOLS: + if ticker in _MACRO_SYMBOLS_NO_OHLCV: if macro is None: macro = _open_macro_library(signals_bucket) lib = macro @@ -141,8 +150,24 @@ def load_price_histories( try: df = lib.read(ticker).data except Exception as e: - read_errors.append(f"{ticker} ({e.__class__.__name__})") - continue + # L1346 (c) SPY-specific fallback: if SPY isn't in universe + # (e.g. universe.SPY backfill hadn't run yet), fall through + # to macro.SPY. Removed once L1346 (b) + (c) soak clean for + # ≥1 Saturday cycle on production. + if ticker == "SPY" and lib is universe: + if macro is None: + macro = _open_macro_library(signals_bucket) + try: + df = macro.read(ticker).data + except Exception as e2: + read_errors.append( + f"{ticker} (universe={e.__class__.__name__}, " + f"macro={e2.__class__.__name__})" + ) + continue + else: + read_errors.append(f"{ticker} ({e.__class__.__name__})") + continue if df.empty: empty.append(ticker) continue