Skip to content

KB/operations

Collection Schedule

Last verified

Data collection runs on seven named profiles. Six of them (overnight, extended, full, market, screener, darkpool_late) are driven by a static 42-slot list in app/config.py:SCHEDULE. The seventh (hot) runs on its own 5-minute timer, only while the market session is actually open.

The seven profiles

Profile When Cadence Sources
overnight 00:00, 02:00, 04:00, 06:00, 08:00, 20:00, 22:00 ET ~2h off-hours news, liquidity, ebp, plus the filing/web collectors (edgar_xbrl, edgar_useful_life, edgar_form4, openrouter_tokens, gpu_rental, frontier_pricing)
extended 08:30, 09:00, 09:15, 16:30, 17:00, 18:00 ET Pre / post-market market, news, vol_structure, liquidity, credit, fred, energy, macro_indicators, fred_macro, cot, aaii, ebp + siblings
full 09:31, 12:00, 15:45, 16:01 ET 4Γ— per trading day All 23 sources (the overnight-only collectors excepted)
market 09:45 – 10:15, 10:45 – 15:30 ET, every 15 min 15 min during RTH news
hot While the session is open, every 5 min hot_looper (independent) market, vol_structure, energy
screener 10:30 ET (RTH snapshot) + 17:30 ET (post-close) 2Γ— per trading day screener_pipeline
darkpool_late 21:45 ET 1Γ— evening darkpool β€” lands the same-day dark-pool vendor print (posted ~20:00–22:00 ET) before midnight

The static SCHEDULE carries 42 time slots across the six background-looper profiles. The hot profile is not in SCHEDULE β€” it runs on its own interval and only during the live session.

Weekend & holiday behavior

Market data does not move when the market is closed, and a closed-day cycle used to persist carry-forward signal rows β€” including genuinely drifted weekend vendor quotes β€” that poisoned every trading-day computation downstream. Since 2026-07 the platform guards closed days at two layers:

Early-close half-days are trading days. The 13:00 ET close still carries real bars, so every slot fires and rows persist normally on days like the Friday after Thanksgiving. (The AI-brief scheduler throttles separately on those days β€” premium AI calls are a cost decision, not a data decision.)

Why seven profiles

Each profile is a tradeoff between freshness and load. The shapes:

Cycle locking

generate_report_logic() is wrapped by a module-level asyncio.Lock (_report_lock). The startup invocation and the two loopers all queue on the same lock β€” a full cycle that runs long won’t get clobbered by the next market tick; the market cycle just waits its turn. Cycle-shared globals (_source_cache, _prev_context) are mutated inside the lock; any code that reads or writes them from outside the cycle must take the lock first. C5 (2026-06): the slow post-capture re-render now runs outside _report_lock (its report.md write is serialized by a dedicated _render_lock), so a long full cycle no longer delays a queued hot cycle by its re-render duration.

Release-aware scheduling

The signal registry’s release_calendar slot (declared in signal_definitions.json) carries {publishes, weekday, approximate_time_et, lag_days} for every metric. The release-aware scheduler (app/release_scheduler.py, live in production since 2026-06-04) reads these for its scheduled-source set (cot, aaii) β€” firing each on its upstream publish window and letting the static cycle reuse the cached payload when it is fresh relative to the last release.

See also