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:
- Scheduler layer β
background_looperskips the market-data profiles (extended,full,market,screener,darkpool_late) when a slot fires on a non-trading day (weekend or full NYSE holiday).overnightkeeps running: its sources (news, SEC filings, the AI-capital-cycle web collectors) are calendar-agnostic and persist to their own tables.hot_loopergates on the holiday-aware session check. - Capture layer β the signal-capture stage is a no-op for any non-session trade date, no matter which cycle reached it: no daily or intraday signal rows, no transitions, no alerts. This is the invariant; the scheduler skips are the optimization.
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:
overnightβ the markets are closed but news, Fed liquidity, and the low-cadence collectors keep moving. A light pull every ~2h keeps the news feed warm, catches overnight Fed-balance-sheet operations, and gives the once-a-day filing/web collectors their slot without touching the market-data budget.extendedβ pre-market (08:30, 09:00, 09:15) and post-market (16:30, 17:00, 18:00) windows. Pull the macro skeleton plus weekly positioning (cot,aaii) so the open and close both ship with fresh data.fullβ the four anchor cycles. Open (09:31), midday (12:00), pre-close (15:45), and close (16:01). Every source fires, including the slower breadth batch.marketβ the 15-minute intraday cadence during RTH. Pulls onlynews, the one genuinely intraday-fast source.credit,liquidity, andfredwere dropped from this tick (2026-06): they publish on daily/weekly cadence, so refetching them every 15 min was wasted work β they stay covered byextended(6 slots) andfull(4 slots) through the trading day. Note: the 10:30 slot is occupied by the screener; the flanking 10:15 and 10:45 market slots maintain the news cadence.hotβ the 5-minute βfast pulseβ during the live session. Three sources only (market,vol_structure,energy) so the dashboardβs health score and intraday sparklines stay near-live without overloading the brokerage data-session budget.screenerβ the single-name screener. Runs twice daily by operator decision (2026-06-05): once at 10:30 ET (RTH morning snapshot β catches post-earnings gaps and opening momentum ~1 h into the session) and once at 17:30 ET (post-close settled-bar run). The 10:30 board is a mid-session snapshot β todayβs Close is the current market price, not the EOD print; the API response carriesis_intraday_snapshot=trueto signal this. Request-spreading (batch size + inter-batch delay, configurable viaSCREENER_FETCH_BATCH_SIZE/SCREENER_FETCH_BATCH_DELAY_Senv vars, default 50 tickers / 2 s) keeps the ~903-name market-data pull from hitting the rate budget harder than the existing breadth batch.darkpool_lateβ a dark-pool-only pass at 21:45 ET. The vendor posts the same-day print in the ~20:00β22:00 ET window, after the lastfullcycle; this slot lands it before midnight so the dayβs row carries its own print instead of the prior dayβs.
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
- Data sources β what each source pulls and from where.
- Lifecycle β what happens inside one cycle, end-to-end.
- Source health β how cycle outcomes get tracked.
- Code:
app/config.py:SCHEDULE/PROFILES,app/main.py:background_looper/hot_looper.