Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions executor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,27 +1197,33 @@ def run(
# missing ticker or stale data — feedback_hard_fail_until_stable.
# Scope: signal tickers (ENTER) + held positions (for trailing stops).
#
# Macro-routed tickers (SPY/sector ETFs/VIX/etc., see _MACRO_SYMBOLS)
# Macro-routed tickers (sector ETFs/VIX/TNX/etc., see _MACRO_SYMBOLS)
# are intentionally excluded. They're used for sector-relative exit
# veto via price_histories, not ATR-based execution, and they live in
# the Close-only `macro` ArcticDB library which has no atr_14_pct
# feature column. The portfolio-optimizer cutover (2026-05-13) made
# SPY a held core position via the enhanced-index design (~63% SPY
# core); without this filter `current_positions` injects SPY into the
# ATR set and load_atr_14_pct hard-fails with NoSuchVersionException
# (universe-only read). Same root-cause family as eod_reconcile #181,
# but the right remedy for ATR is exclusion (macro lib has no ATR),
# not macro-lib dispatch. The SPY core is rebalanced by the optimizer,
# not ATR trailing stops; _write_stops_and_finalize's atr_map.get(t)
# already skips-with-warning when a held position has no ATR.
# feature column.
#
# SPY is NO LONGER excluded as of L1346 (c) (2026-05-24): alpha-engine
# -data #245 promoted SPY to a full ``universe`` ArcticDB member
# (_UNIVERSE_EXTRA = frozenset({"SPY"}), written by both
# builders/backfill.py and builders/daily_append.py), so SPY now has
# the full OHLCV + atr_14_pct feature column that load_atr_14_pct
# needs. The pre-L1346 #185 fix excluded SPY because the macro lib
# was Close-only; with universe.SPY now carrying ATR, the exclusion
# is dead defense. Gate (a) verified via 2026-05-24 DataPhase1 SSM
# log (`Backfill write complete: 904 ok` = 903 constituents + SPY).
# _MACRO_SYMBOLS_NO_OHLCV is the post-L1346 subset of macro-only
# symbols that still need to be excluded — everything in macro EXCEPT
# SPY (which now has full OHLCV via universe write).
#
# ``atr_map`` kwarg mirrors the vwap_map injection pattern — backtester
# precomputes once per simulate pipeline, skipping millions of
# per-call universe.read(ticker) round-trips. Live trading passes
# atr_map=None and takes the load_atr_14_pct path unchanged.
_MACRO_SYMBOLS_NO_OHLCV = _MACRO_SYMBOLS - {"SPY"}
atr_tickers = [s["ticker"] for s in signals.get("enter", [])]
atr_tickers += list(current_positions.keys())
atr_tickers = sorted(set(atr_tickers) - _MACRO_SYMBOLS)
atr_tickers = sorted(set(atr_tickers) - _MACRO_SYMBOLS_NO_OHLCV)
if atr_map is None:
if atr_tickers:
atr_map = load_atr_14_pct(
Expand Down
57 changes: 57 additions & 0 deletions tests/test_l1346_spy_atr_exclusion_retired.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Pin L1346 (c) — SPY no longer excluded from ATR ticker set.

Pre-fix: alpha-engine #185 excluded ALL `_MACRO_SYMBOLS` (incl. SPY) from
the ATR ticker list because macro lib was Close-only. Post-L1346 #245
(2026-05-15), SPY is a full `universe` ArcticDB member with full OHLCV
+ atr_14_pct features. The pre-L1346 exclusion is now dead defense.

This module pins the post-L1346 retirement: SPY MUST be ATR-computable
(not in the macro-only-no-OHLCV exclusion set).
"""
from __future__ import annotations

import inspect
import pytest


def test_atr_exclusion_set_does_not_include_spy():
"""Read the embedded `_MACRO_SYMBOLS_NO_OHLCV` constant from
`executor.main` and assert SPY is NOT in it."""
from executor.main import _MACRO_SYMBOLS

# Mirror the post-fix derivation: post-L1346 the exclusion subtracts SPY
# because universe.SPY now carries the atr_14_pct feature column.
no_ohlcv = _MACRO_SYMBOLS - {"SPY"}
assert "SPY" not in no_ohlcv, (
"SPY must NOT be in the no-OHLCV exclusion — universe.SPY now has "
"full OHLCV + atr_14_pct (post L1346 #245 _UNIVERSE_EXTRA write path). "
"If this assertion fails, the L1346 (c) retirement has regressed."
)
# Sanity: legacy macro-only symbols (VIX, TNX, IRX, sector ETFs) still
# belong in the exclusion because they remain Close-only in macro lib.
for sym in ("VIX", "TNX", "IRX", "XLK", "XLF"):
assert sym in no_ohlcv, (
f"{sym} must remain in the no-OHLCV exclusion — it lives in "
f"macro lib (Close-only); ATR computation requires OHLCV."
)


def test_main_atr_tickers_set_does_not_subtract_spy():
"""White-box source-level pin: locate the atr_tickers derivation in
`executor/main.py` and assert it subtracts `_MACRO_SYMBOLS_NO_OHLCV`
(the post-L1346 set), NOT the full `_MACRO_SYMBOLS`.

Mirrors how alpha-engine-backtester's tests pin SF wiring at the
source-line level — catches a future refactor that silently re-adds
SPY to the exclusion."""
from executor import main
src = inspect.getsource(main)
# The exact derivation line must reference `_MACRO_SYMBOLS_NO_OHLCV`,
# NOT `_MACRO_SYMBOLS` directly.
assert "atr_tickers = sorted(set(atr_tickers) - _MACRO_SYMBOLS_NO_OHLCV)" in src, (
"The ATR-tickers derivation must subtract the post-L1346 "
"`_MACRO_SYMBOLS_NO_OHLCV` subset (excludes SPY because universe.SPY "
"now has OHLCV), NOT the full `_MACRO_SYMBOLS` set. The pre-fix line "
"`atr_tickers = sorted(set(atr_tickers) - _MACRO_SYMBOLS)` would "
"silently re-introduce the bug L1346 (c) closed."
)
Loading