Add system alerts log with nav badge and Admin Alerts tab.
Persist job and ingestion warnings/errors for 7 days, surface a dismissible top-nav badge, treat stale OHLCV as a warning (e.g. ticker renames), and show market bar age on the ticker freshness chip.
This commit is contained in:
@@ -59,6 +59,19 @@ async def _get_ohlcv_bar_count(db: AsyncSession, ticker_id: int) -> int:
|
||||
return int(result.scalar() or 0)
|
||||
|
||||
|
||||
async def _get_latest_ohlcv_date(db: AsyncSession, ticker_id: int) -> date | None:
|
||||
result = await db.execute(
|
||||
select(func.max(OHLCVRecord.date)).where(OHLCVRecord.ticker_id == ticker_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
# If the provider returns no bars but our last stored session is older than this,
|
||||
# treat the run as stale (not "success / up to date"). Common causes: ticker
|
||||
# rename, delisting, or multi-day halt — SATS→ECHO is the canonical example.
|
||||
_STALE_OHLCV_GAP_DAYS = 5
|
||||
|
||||
|
||||
async def _update_progress(
|
||||
db: AsyncSession, ticker_id: int, last_date: date
|
||||
) -> None:
|
||||
@@ -145,8 +158,9 @@ async def fetch_and_ingest(
|
||||
|
||||
# Provider returned nothing. With no history at all this almost always means
|
||||
# the provider doesn't cover this symbol (Alpaca = US listings only) — surface
|
||||
# that instead of a misleading "success". With existing bars it just means
|
||||
# there were no new bars in the requested window.
|
||||
# that instead of a misleading "success". With recent history, an empty window
|
||||
# usually means weekends/holidays. With a multi-day gap, the symbol is likely
|
||||
# halted, delisted, or *renamed* (e.g. SATS → ECHO) and we must not claim success.
|
||||
if not records:
|
||||
existing = await _get_ohlcv_bar_count(db, ticker.id)
|
||||
if existing == 0:
|
||||
@@ -160,10 +174,24 @@ async def fetch_and_ingest(
|
||||
"(Alpaca serves US-listed securities only)."
|
||||
),
|
||||
)
|
||||
latest = await _get_latest_ohlcv_date(db, ticker.id)
|
||||
gap_days = (end_date - latest).days if latest is not None else None
|
||||
if gap_days is not None and gap_days > _STALE_OHLCV_GAP_DAYS:
|
||||
return IngestionResult(
|
||||
symbol=ticker.symbol,
|
||||
records_ingested=0,
|
||||
last_date=latest,
|
||||
status="stale",
|
||||
message=(
|
||||
f"No new bars since {latest.isoformat()} ({gap_days}d gap). "
|
||||
"The symbol may be halted, delisted, or renamed under a new ticker — "
|
||||
"check the listing and add/fetch the current symbol if it changed."
|
||||
),
|
||||
)
|
||||
return IngestionResult(
|
||||
symbol=ticker.symbol,
|
||||
records_ingested=0,
|
||||
last_date=None,
|
||||
last_date=latest,
|
||||
status="complete",
|
||||
message="Already up to date — no new bars.",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user