Files
momentum-system/tests/unit/research/test_premarket_eval_enrichment.py
T
momentum devandClaude Fable 5 f6999c7f6a dilution(016): unbreak nightly EDGAR refresh + pre-register filing-veto experiment
premarket_eval's shadow enrichment call site queried session_date = current_date
(wrong on UTC-server Postgres at night, so it never matched any symbols) and
shared one try-block for float_cache + dilution filings (a float_cache failure
silently skipped the dilution refresh too). Fix: compute the ET session date
in Python, split into two independent try-blocks, lift into a testable
_refresh_shadow_enrichment(con, session_date). float_cache now applies its own
DDL unconditionally on use instead of only when an API key is present.

Adds iv_dilution_gate (additive, mirrors iv_regime_gate's keep-or-zero shape
keyed on symbol instead of session_date, fail-open on unfetched symbols) plus
its once-per-run preload, and registers the pre-registered dilution_veto_v1
experiment via the existing experiments/*.json self-registering cohort. Live
veto stays OFF; this only wires the shadow measurement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:54:49 -05:00

73 lines
2.8 KiB
Python

"""research/premarket_eval.py::_refresh_shadow_enrichment (plan 016) — the
nightly float/dilution enrichment call site was broken two ways: a
``session_date = current_date`` query (wrong on UTC-server Postgres at night)
and a shared try-block where a float_cache failure silently skipped the
dilution refresh too. This test runs against an ISOLATED throwaway Postgres
schema (EventStore ":memory:"), never the production DB (mirrors
tests/unit/experiments/conftest.py), and never makes a live HTTP call — both
enrichment functions are monkeypatched."""
from __future__ import annotations
from datetime import date, datetime
import pytest
from research.premarket_eval import _refresh_shadow_enrichment
from storage.event_store import EventStore
SESS = date(2026, 6, 11)
@pytest.fixture
def store():
s = EventStore(":memory:")
yield s
s.close()
def _seed_minute_bar(store, symbol, session_date, volume):
store.con.execute(
"INSERT INTO minute_bars (symbol, timestamp, session_date, is_premarket, "
"is_regular_hours, open, high, low, close, volume, vwap, quality_score) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
[symbol, datetime(session_date.year, session_date.month, session_date.day, 13, 35),
session_date, False, True, 10.0, 10.1, 9.9, 10.0, volume, 10.0, 1.0])
def test_dilution_still_called_when_float_raises(store, monkeypatch):
_seed_minute_bar(store, "AAA", SESS, 1_000_000)
_seed_minute_bar(store, "BBB", SESS, 500_000)
# a different session_date's bars must not leak into the query
_seed_minute_bar(store, "ZZZ", date(2026, 6, 10), 9_999_999)
def _raise_float(con, symbols):
raise RuntimeError("relation \"float_cache\" does not exist")
calls = []
def _record_dilution(con, symbols):
calls.append((con, list(symbols)))
return {"symbols": len(symbols), "filings": 0}
monkeypatch.setattr("research.ingestion.float_cache.refresh_float_cache", _raise_float)
monkeypatch.setattr("research.ingestion.sec_dilution.refresh_dilution_events", _record_dilution)
_refresh_shadow_enrichment(store.con, SESS)
assert len(calls) == 1
seen_con, seen_symbols = calls[0]
assert seen_con is store.con
# highest-volume first, only the seeded session's symbols
assert seen_symbols == ["AAA", "BBB"]
def test_no_symbols_is_a_noop(store, monkeypatch):
called = {"float": False, "dilution": False}
monkeypatch.setattr("research.ingestion.float_cache.refresh_float_cache",
lambda con, syms: called.__setitem__("float", True))
monkeypatch.setattr("research.ingestion.sec_dilution.refresh_dilution_events",
lambda con, syms: called.__setitem__("dilution", True))
_refresh_shadow_enrichment(store.con, SESS) # no minute_bars seeded
assert called == {"float": False, "dilution": False}