fix(tickers): honor the effective date instead of retiring on the mark
Deploy / lint (push) Successful in 11s
Deploy / test (push) Successful in 1m22s
Deploy / deploy (push) Successful in 38s

active_only tested delisted_on IS NULL, so a symbol dropped out of signals the
moment a Form 25 was detected — ten days before Rule 12d2-2 makes the removal
effective, while it was demonstrably still trading. A manual future-dated mark
behaved the same way. It now compares against the database's own date, so a
pending delisting stays live until the day it takes effect.

That exposes a second problem the fix would otherwise create. Trading typically
stops before the ten-day delay expires, so across that window the symbol is
correctly active yet produces no bars — and confirm_delisting returned None for
an already-marked row, which would have fired the staleness warning daily for
ten days, the exact noise this flow exists to remove. It now reports the known
effective date on every path where the delisting is established, so the caller
warns only about gaps that are still unexplained.

CURRENT_DATE renders identically on postgres and sqlite, and the OR is
parenthesized when callers chain further where clauses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:58:51 +02:00
co-authored by Claude Opus 5
parent 1d4ed39fd2
commit 247a7889b9
2 changed files with 84 additions and 16 deletions
+50 -1
View File
@@ -236,9 +236,11 @@ async def test_a_confirmed_row_is_never_reprobed(session: AsyncSession, monkeypa
raise AssertionError("a SEC-confirmed row must not cost another request")
monkeypatch.setattr(ticker_service, "_sec_client_factory", _explode, raising=False)
# Costs no request, and still reports the date so the caller knows this gap
# is explained and must not warn about it again.
assert await ticker_service.confirm_delisting(
session, "EA", last_bar=date(2026, 8, 4), today=date(2026, 9, 1)
) is None
) == date(2026, 8, 4)
async def test_ohlcv_priority_ordering_skips_delisted(session: AsyncSession):
@@ -365,3 +367,50 @@ async def test_prune_keeps_delisted_rows(session: AsyncSession, monkeypatch):
assert remaining == ["AAPL", "EA"] # GONE pruned, EA protected
assert summary["deleted"] == 1
assert summary["kept_delisted"] == ["EA"]
async def test_a_future_effective_date_keeps_the_symbol_live(session: AsyncSession):
"""Form 25 is known ten days before removal takes effect. The symbol is still
trading in that window and must keep being scanned and ingested."""
session.add_all([Ticker(symbol="AAPL"), Ticker(symbol="EA")])
await session.commit()
await ticker_service.mark_delisted(session, "EA", delisted_on=date(2026, 8, 14))
def active(as_of: date) -> list[str]:
return ticker_service.active_only(select(Ticker.symbol), as_of=as_of)
before = (await session.execute(active(date(2026, 8, 11)))).scalars().all()
on_the_day = (await session.execute(active(date(2026, 8, 14)))).scalars().all()
after = (await session.execute(active(date(2026, 8, 15)))).scalars().all()
assert sorted(before) == ["AAPL", "EA"] # still trading
assert sorted(on_the_day) == ["AAPL"] # removal effective
assert sorted(after) == ["AAPL"]
async def test_the_pending_window_does_not_re_warn(session: AsyncSession, monkeypatch):
"""Between filing and effect the symbol is active but produces no bars. That
must not resurrect the daily staleness warning this flow exists to end."""
session.add(Ticker(symbol="EA", cik="0000712515"))
await session.commit()
monkeypatch.setattr(
ticker_service,
"_sec_client_factory",
lambda: _sec_client(_submissions(["25-NSE"], ["2026-08-04"])),
raising=False,
)
first = await ticker_service.confirm_delisting(
session, "EA", last_bar=date(2026, 8, 4), today=date(2026, 8, 11)
)
assert first == date(2026, 8, 14)
def _explode():
raise AssertionError("must not re-probe a confirmed row")
monkeypatch.setattr(ticker_service, "_sec_client_factory", _explode, raising=False)
# Every later run inside the window still reports the delisting, so the
# caller keeps emitting "delisted" rather than "no new bars".
for day in (date(2026, 8, 12), date(2026, 8, 13)):
assert await ticker_service.confirm_delisting(
session, "EA", last_bar=date(2026, 8, 4), today=day
) == date(2026, 8, 14)