Source code for numeraire_dataset.sources

"""tidyfinance-backed loaders for standard (non-vintage) sources.

tidyfinance (MIT) is the primary backend: Fama-French, Goyal-Welch, FRED, JKP, q-factors, OSAP.
These loaders return **tidy frames** (the package convention — see :mod:`numeraire_dataset.builders`
for the vintage-aware FRED-MD builder that tidyfinance does not cover) plus a ``data_vintage``
provenance stamp. :func:`to_timeseries_view` optionally turns a frame into a numeraire point-in-time
view; it imports ``numeraire`` lazily, so the frame loaders stay numeraire-independent.

For a *golden reproduction*, pin a fixture instead of a live pull — tidyfinance fetches the current
release, which drifts, and the live predictor sets differ slightly from any one paper's (e.g.
Goyal-Welch has no ``dfr``). Pin the ``tidyfinance`` version so a pull is reproducible.
"""

from __future__ import annotations

from importlib.metadata import version
from typing import TYPE_CHECKING, Any

import pandas as pd
import tidyfinance as tf

if TYPE_CHECKING:
    from numeraire.core.data import TimeSeriesView

_TF_VERSION = version("tidyfinance")


[docs] def data_vintage(dataset: str) -> str: """Provenance stamp for a live tidyfinance pull (``tidyfinance:<dataset>@<version>``).""" return f"tidyfinance:{dataset}@{_TF_VERSION}"
[docs] def load_ff_factors( *, freq: str = "monthly", start_date: str = "1926-07-01", end_date: str = "2023-12-31" ) -> pd.DataFrame: """Fama-French 3-factor tidy frame (``date, mkt_excess, smb, hml, risk_free``), in decimals. ``mkt_excess`` is the excess market return, ``risk_free`` the 1-month T-bill — the canonical academic market/risk-free pair. """ dataset = f"factors_ff_3_{freq}" return tf.download_data( # pyright: ignore[reportAttributeAccessIssue] # no stubs domain="factors_ff", dataset=dataset, start_date=start_date, end_date=end_date )
[docs] def load_goyal_welch( *, freq: str = "monthly", start_date: str = "1926-07-01", end_date: str = "2023-12-31" ) -> pd.DataFrame: """Goyal-Welch equity-premium frame: ``date, rp_div`` (excess market) + macro predictors.""" dataset = f"macro_predictors_{freq}" return tf.download_data( # pyright: ignore[reportAttributeAccessIssue] # no stubs domain="macro_predictors", dataset=dataset, start_date=start_date, end_date=end_date )
[docs] def to_timeseries_view( df: pd.DataFrame, *, ret_col: str, feature_cols: list[str] | None = None, date_col: str = "date", asset: str = "mkt", horizon: int = 1, ) -> TimeSeriesView: """A tidy ``(date, ret, features…)`` frame → single-asset numeraire ``TimeSeriesView``. ``feature_cols=None`` uses every column except ``date_col`` and ``ret_col``. Rows with any NaN in the selected columns are dropped and the index is sorted. Requires the ``numeraire`` extra (imported lazily so the frame loaders above do not depend on numeraire). """ from numeraire.core.data import TimeSeriesView # lazy: keep frame loaders numeraire-free feats = feature_cols if feats is None: feats = [c for c in df.columns if c not in (date_col, ret_col)] clean = df.set_index(date_col)[[ret_col, *feats]].dropna().sort_index() idx = pd.DatetimeIndex(pd.to_datetime(clean.index), name="date") returns = pd.DataFrame({asset: clean[ret_col].to_numpy()}, index=idx) features = pd.DataFrame(clean[feats].to_numpy(), index=idx, columns=feats) return TimeSeriesView(returns, features, horizon=horizon)
[docs] def load_gw_view( *, freq: str = "monthly", start_date: str = "1926-07-01", end_date: str = "2023-12-31", predictors: list[str] | None = None, horizon: int = 1, ) -> tuple[Any, str]: """Goyal-Welch view (excess ``rp_div`` + predictors) + ``data_vintage``, ready for VoC/1-A. Convenience over :func:`load_goyal_welch` + :func:`to_timeseries_view`; needs the ``numeraire`` extra. Return type is the numeraire ``TimeSeriesView`` (typed ``Any`` to avoid a hard import). """ df = load_goyal_welch(freq=freq, start_date=start_date, end_date=end_date) view = to_timeseries_view(df, ret_col="rp_div", feature_cols=predictors, horizon=horizon) return view, data_vintage(f"macro_predictors_{freq}")