Universe Construction API
This chapter is generated from the canonical platform doc, which lives in docs/ (single source of truth).
Universe Construction API
Full spec for UniverseService. Referenced from CLAUDE.md §16.
UniverseService (src/sbuniverse/services/universe_service.py) provides three point-in-time universe methods. All filter gold.dim_instrument lifecycle columns (ipo_date, delisting_date) so only instruments active on as_of_date are returned.
Methods
| Method | Description | Reliable From |
|---|---|---|
get_index_tickers(index_code, as_of_date) |
Exact historical index membership via gold.fact_index_constituent |
SP500: Oct 2008; NASDAQ100: Jan 1995; DJIA: Jan 1994 |
get_broad_universe_tickers(as_of_date) |
All fmp_promotion_allowlist-eligible instruments alive on date |
~Jan 1995 (~1,546 symbols); grows to ~6,885 by 2024 |
get_filtered_tickers(exchanges, sectors, industries, countries, as_of_date) |
Dimension-filtered universe via three-tier fallback | Same as broad; market cap filter uses current snapshot only |
Three-Tier Fallback (get_filtered_tickers)
- Tier 1 —
silver.fmp_market_screener(preferred; not yet populated) - Tier 2 —
silver.fmp_company_profile_bulk(available; has exchange, sector, industry, country, market_cap snapshot) - Tier 3 —
gold.dim_instrument JOIN silver.fmp_promotion_allowlist(always available; no dimension filters, no market cap)
Package Boundary
sbuniverse.infra.universe_repo.UniverseRepo— canonical implementation of all query methodssbuniverse.infra.universe_repo.UniverseRepo— equivalent standalone implementation for use within sbfoundation (avoids circular import viasbfoundation/__init__.py)sbuniverse.services.universe_service.UniverseService— adds run-context utilities (run_id,now,today,from_date,last_quarter_end,next_market_day) on top of the repo
Research Universe (factor-research scope)
The factor-research universe is separate from the strategy/ingestion universes above. It is the cross-section every factor-research SQL surface (sbic, sbalphalens, sbpermtest, sbdiag, sbfactorcontrib) measures factors over, injected as a shared membership CTE rather than a hardcoded allowlist join.
Production universe (F-148 / RUS-1)
sbuniverse.research_universe_membership_cte(config) emits a CTE (instrument_sk, date_sk) for the single production research universe, driven by config/universes/research.yaml via sbcontracts.research_universe_config.ResearchUniverseConfigService:
mode: allowlist_only(default, current production) — allowlist ∩ lifecycle ∩ US-market-day.mode: base— additionally a point-in-time price floor + trailing dollar-ADV floor (the tradeable Base universe).
Variants (F-168 / RUS-2)
sbuniverse.research_universe_variant_cte(base_config, variant) emits the membership CTE chain for a universe variant — Base ∩ axis-filter — for the deferred calibration sweep (RUS-3). The 9 variants are declared in config/universes/research-variants.yaml and loaded by sbcontracts.research_universe_variant.ResearchUniverseVariantService:
| Variant(s) | Axis | Filter (∩ Base) |
|---|---|---|
base |
base | none (Base only) |
cap_small / cap_mid / cap_large |
cap | market-cap tier vs per-date percentile cutoffs |
liq_top20 / liq_top50 |
liquidity | top fraction by per-date dollar-ADV percentile rank |
vol_high / vol_low |
volatility | above/below per-date median trailing-vol |
sector_neutral |
sector_neutral | Base membership; scoring transform (see below) |
Notes:
- Base inheritance — every membership variant forces
mode: base(dataclasses.replace), so the price/ADV floors always apply regardless of the productionresearch.yamlmode. Base floors are inherited fromresearch.yaml, not duplicated. - Cap breakpoints — config-selectable
breakpoint_method(nysedefault |cross_sectional): NYSE-listed (gold.dim_exchange.exchange_code) per-date percentiles applied to the full cross-section, over a config-selectablemarket_cap_source(computed_pitdefault =close × ASOF carry-forwardgold.fact_quarter.weighted_average_shs_out_diluted;profile_staticalso implemented;annual_market_cap_f` deferred). - Sector-neutral is not a membership filter — it z-scores the factor within
(date_sk, sector_sk)before the cross-sectional IC, carried as theResearchUniverseVariant.sector_neutralizeflag and applied bysbic.services._ic_sql.filtered_factor_cte/ threaded throughICService.factor_ic_ir(..., variant=...).
RUS-2 ships definitions + the scoring flag only — the sweep runner, ranking, sidecar, and SPA are RUS-3. See docs/backlog/feature-research-universe-variants-design-brief.md.
Known Gaps
- Historical market cap —
market_capin Tier 1/2 reflects today’s snapshot, not theas_of_datevalue. Point-in-time market cap requiresfact_eodbackfill (seeuniverse_screener_features_exec_plan.md). - Liquidity filters — ADV, ADTV unavailable until
fact_eodis backfilled. - SP500 pre-2008 — constituent change log is incomplete before Oct 2008.
- NULL
ipo_date— ~162 allowlist symbols have noipo_date; treated as always-eligible (conservative).