quantlab.universe.UniverseCatalog#
- class quantlab.universe.UniverseCatalog(config)[source]#
Bases:
objectPoint-in-time reference table of US equity universes.
Builds one table with the columns
symbol,category,start_date,end_dateandend_date_is_inferredfrom every roster fetcher inROSTER_FETCHERSand every index membership fetcher inMEMBERSHIP_FETCHERS. A category names one universe, such as"us_all"or"sp500_constituent". The table is saved as parquet throughPlBackendand answers point-in-time queries. Dates are ISOYYYY-MM-DDstrings throughout and are compared as text, which gives the right order for that format.A walk-forward backtest, which steps through time and rebalances as it goes, must call
get_symbols_as_ofon each rebalance date rather than once at setup. Otherwise the symbol list reflects later dates, and the backtest uses information it could not have had (look-ahead bias).- Parameters:
- configUniverseConfig
Where the table is saved (
output_path) and where fetchers cache change logs (cache_dir).
Examples
Build the table from the live sources and persist it:
config = UniverseConfig( output_path="data/reference/universe.parquet", cache_dir="data/reference/_cache", ) UniverseCatalog(config).build().save() # downloads from Tiingo/Wikipedia
Load a table already on disk and query it (here a three-row table written by hand):
>>> pl.DataFrame({ ... "symbol": ["AAPL", "MSFT", "OLD1"], ... "category": ["us_all"] * 3, ... "start_date": ["1980-12-12", "1986-03-13", "1980-01-01"], ... "end_date": [None, None, "1997-06-30"], ... "end_date_is_inferred": [False] * 3, ... }).write_parquet(config.output_path) >>> catalog = UniverseCatalog.load(config) >>> catalog.get_symbols_as_of("us_all", "2020-01-01") ['AAPL', 'MSFT']
- CATALOG_COLUMNS = ('symbol', 'category', 'start_date', 'end_date', 'end_date_is_inferred')#
Column order of the saved table.
end_date_is_inferredmarks an end date thatreconstruct_intervalsinferred rather than read from a source.
- MEMBERSHIP_FETCHERS = (<class 'quantlab.universe.SP500MembershipFetcher'>, <class 'quantlab.universe.Nasdaq100MembershipFetcher'>)#
The index membership fetchers this catalog uses. Listing a fetcher here gives its category a coverage start date:
buildtakes the category fromcls.CATEGORYand the queries take the start date fromcls.PIT_COVERAGE_START. Roster fetchers do not belong here; they have listing dates but no coverage start.
- ROSTER_FETCHERS = (<class 'quantlab.universe.NasdaqUniverseFetcher'>, <class 'quantlab.universe.USEquityUniverseFetcher'>)#
The exchange roster fetchers this catalog uses, looped over in
buildlikeMEMBERSHIP_FETCHERS. Kept separate because a roster has no coverage start, so a query before a stock listed is answered from the roster’s own dates instead of raising. Each fetcher downloads the ticker directory itself, which costs a couple of seconds per build.
- build(allow_stale=False)[source]#
Fetch every category and hold the combined table in memory.
Rosters come first, then index memberships. Each frame is reduced to
CATALOG_COLUMNS, in that order, before the frames are joined. Nothing is written to disk untilsave.- Parameters:
- allow_stalebool, default False
Whether to accept a membership fetcher that fell back to its cached change-log snapshot. Off by default, because a stale table saved by
savelooks exactly like a fresh one and would freeze the universe at the cache date without anyone noticing.
- Returns:
- Self
self, sobuildandsavechain.
- Raises:
- ValueError
If a membership fetcher is stale and
allow_staleis false.
Examples
>>> catalog = UniverseCatalog(config).build() # downloads every source >>> catalog.save()
- get_symbols_as_of(category, as_of_date)[source]#
Return every symbol whose interval contains
as_of_date.This is point-in-time membership on one day, the query a walk-forward backtest should make at each rebalance. To download a whole window of history, use
get_symbols_in_range. The result has no duplicates and is sorted ascending, for the same reason as there.- Parameters:
- categorystr
A token from
known_categories.- as_of_datestr
ISO date. For an index category it must not be before that index’s coverage start.
- Returns:
- list[str]
Sorted symbols without duplicates.
- Raises:
- ValueError
For an unknown category, a non-ISO date, or a date before the category’s coverage start.
Examples
>>> catalog.get_symbols_as_of("us_all", "1990-01-01") ['AAPL', 'MSFT', 'OLD1'] >>> catalog.get_symbols_as_of("sp500_constituent", "1970-01-01") Traceback (most recent call last): ... ValueError: Cannot answer sp500_constituent membership before 1976-07-01 ...
- get_symbols_in_range(category, start_date, end_date)[source]#
Return every symbol whose interval overlaps
[start_date, end_date].A row overlaps when
start_date <= endandend_dateis null or>= start. A symbol that delisted inside the window is therefore kept; dropping it would be exactly the survivorship bias this table exists to remove. Use this query to download a whole window of history; useget_symbols_as_ofat each rebalance of a walk-forward backtest.The result has no duplicates and is sorted ascending. Callers rely on the order, for example to take the first N symbols with
--limit, so it must depend only on which symbols match and not on the row order of the parquet file.- Parameters:
- categorystr
A token from
known_categories.- start_datestr
ISO date, inclusive. For an index category it must not be before that index’s coverage start. An earlier date is refused, not moved forward, because moving it would return a shorter roster without saying so.
- end_datestr
ISO date, inclusive.
- Returns:
- list[str]
Sorted symbols without duplicates.
- Raises:
- ValueError
For an unknown category, a non-ISO date, or a
start_datebefore the category’s coverage start.
Examples
>>> catalog.get_symbols_in_range("us_all", "1995-01-01", "2000-12-31") ['AAPL', 'MSFT', 'OLD1']
- known_categories()[source]#
Return every category this catalog can answer for.
This is the union of both fetcher lists, so a category cannot exist without a fetcher, and every listed fetcher’s category is known.
Examples
>>> sorted(catalog.known_categories()) ['nasdaq100_constituent', 'nasdaq_all', 'sp500_constituent', 'us_all']
- classmethod load(config)[source]#
Return a catalog whose table is read from
config.output_path.- Parameters:
- configUniverseConfig
The same config the table was built with; only
output_pathis read here.
Examples
>>> catalog = UniverseCatalog.load(config) >>> sorted(catalog.known_categories()) ['nasdaq100_constituent', 'nasdaq_all', 'sp500_constituent', 'us_all']