Skip to content

core_data_updater.jobs.events

jobs/events.py — EventsFlow: splits · dividends · symbol_changes.

Design record: EVENTS_FLOW.md (pre-stamped from the records; §7 decisions taken per recommendation, 2026-08-16). Shape: UPDATE events_update 3 whole-market calendar sweeps (3 HTTP calls, no symbols) — sensor-fired (events_ready), manual=True BUILD events_build full survivorship-free history: dividends/splits per-symbol over the STORED universe, symbol_changes global — manual, max_concurrent=1, replace scoped BACKFILL events_backfill wide re-sweep (‘30d’) + targeted repull of the RECENT NAMED GAPS — nightly 09:30 UTC, born STOPPED

Laws carried here: configs read LIVE per step (per-step rows, §7-D1; enabled comes back VISIBLE — a disabled row skips, named); every landing through CoreDataRepo.land (the EVENTS policy: dividends ride KEY_UPGRADE so a period-named row upgrades its ‘unknown’ placeholder in place, period None → ‘unknown’ is a policy fill); state-as-params (the build roster is READ from raw.equities ∪ raw.etfs in fetch_symbols and passed; an unreadable roster RAISES — metadata outage blocks loudly); the run ledger opens idempotently at the first landing (sweep jobs have no fetch step); fulfill-or-name; zero SQL/wire-parsing/conversion in this file.

Sensor (§5.3): events_ready probes every 15m inside probe_window_utc — the target is the latest session whose close+45m has passed, READ from raw.market_sessions via CoreDataStateRepo.sessions (never weekday math; empty calendar = named skip, fail closed); ONE dividends-calendar hit is the vendor-up canary; run_key dedupes one run per session. It reads through CoreDataStateRepo.sessions and gates on metadata’s market_sessions product through requires: the sensor does not run before the calendar has ever materialized, then reads its current rows. This is bootstrap/existence, not an asset-age promise. No cron fallback (§7-D4 rec) — the sensor is primary, and its conditions live INSIDE it; manual=True keeps the UI lever.

Classes

EventsFlow

Bases: CoreDataFlow

The events flow — EVENTS_FLOW.md §5, method for method.

backfill_dividends

backfill_dividends(cfg) -> None

backfill_splits

backfill_splits(cfg) -> None

backfill_symbol_changes

backfill_symbol_changes(cfg) -> None

Re-sweep ONLY — the rename stream is GLOBAL (one vendor call for the whole market; no per-symbol verb exists and none should be added), so a wide window IS the targeted repair here. And ‘wide’ for THIS stream is the FULL ARCHIVE (window NULL → get_symbol_changes, the whole 1969→ history in ONE call, which is exactly what the nightly update already takes): a ‘30d’ window would do strictly LESS than the sweep it’s meant to backstop.

build_dividends

build_dividends(cfg, fetch_symbols: dict) -> None

build_splits

build_splits(cfg, fetch_symbols: dict) -> None

build_symbol_changes

build_symbol_changes(cfg) -> None

Global stream — no symbols, ever (FMP full archive to 1969 in ONE call + EODHD fill; §7-D2 rec: upsert, inserted-count IS the diff).

events_ready

events_ready(ctx, upstream_done)

Probe-then-fire (§5.3): events publish IN ADVANCE, so the fire signal is a SESSION CLOCK — target = the latest session whose close+45m has passed, read from raw.market_sessions (never weekday math; empty calendar = named skip, fail closed) — with ONE dividends- calendar hit as the vendor-up canary. run_key dedupes per session.

The requires gate confirms market_sessions has been bootstrapped; the sensor then reads the current table to compute its target.

fetch_symbols

fetch_symbols(cfg) -> dict

The stored survivorship-free universe (raw.equities ∪ raw.etfs), read ONCE and passed (state-as-params) — {“all”: active ∪ delisted, “active”: active only}; the build steps pick per their row’s include_delisted and symbols override. A FAILED roster read RAISES — a metadata outage must block the build loudly, never masquerade as an empty universe. Opens the run ledger (prices fetch_universe precedent).

update_dividends

update_dividends(cfg) -> None

The policy op is KEY_UPGRADE: a period-named row matching a stored ‘unknown’ row’s (symbol, ex_date, amount) upgrades it in place — never a phantom second payout (the census rule, §7-D2).

update_splits

update_splits(cfg) -> None

update_symbol_changes

update_symbol_changes(cfg) -> None

FULL REPULL by default (§7-D2 rec): the archive is 5.4k rows in ONE call and upsert on the PK makes the repull the diff — the row’s window stays NULL; seeding one flips this to a windowed sweep.