Source code for numeraire.core.protocols

"""Core contracts (spine). Protocols, not base classes — methods conform by duck typing.

Spine protocols (``DataView``, ``Estimator``, ``Splitter``, ``Evaluator``, result schema)
are committed. The capability layer (``to_weights`` / ``to_pricing`` / ...) is v0 and is
expected to crystallize from the first three real adapters, so ``Model`` only
mandates ``capabilities()`` — concrete extractors are optional and dispatched by capability.
"""

from __future__ import annotations

from collections.abc import Iterator
from typing import ClassVar, Protocol, runtime_checkable

import pandas as pd


[docs] @runtime_checkable class DataView(Protocol): """A point-in-time aligned view of the data. A returns panel is one realization."""
[docs] def window(self, end: object) -> DataView: """Return the view restricted to information available up to ``end`` (no look-ahead).""" ...
@property def calendar(self) -> pd.DatetimeIndex: """Rebalancing / observation timestamps.""" ...
[docs] @runtime_checkable class Model(Protocol): """A fitted model. Exposes whatever capabilities it has — capabilities, not mandatory methods. Optional, dispatched by capability (do NOT make these mandatory): ``to_weights(view) -> pd.DataFrame | pd.Series``, ``expected_returns(view) -> pd.DataFrame``, ``to_density(view)``, ``to_surface(view)``, ... Capability → method-name mapping (the three crystallized surfaces follow *different* naming patterns, so an extension author must map deliberately, not by rote): - ``capabilities.TO_WEIGHTS`` → ``to_weights()`` - ``capabilities.TO_FORECAST`` → ``forecast()`` (**not** ``to_forecast``) - ``capabilities.TO_PRICING`` → ``expected_returns()`` (**not** ``to_pricing``) A model declaring a capability must expose its mapped method; the conformance suite (``numeraire.testing.check_capabilities``) enforces this. """
[docs] def capabilities(self) -> set[str]: """The set of capability names this model supports (see ``numeraire.core.capabilities``).""" ...
[docs] @runtime_checkable class SupportsWeights(Protocol): """Capability protocol (v0): a model that emits portfolio/timing weights (``to_weights``). Optional and dispatched by capability — a model advertises it via ``capabilities() >= {capabilities.TO_WEIGHTS}``. Kept deliberately thin; the capability layer crystallizes once the third real adapter lands. """
[docs] def to_weights(self, view: DataView) -> pd.DataFrame | pd.Series: """Return per-date weights for each date in ``view.calendar``. A fixed-universe time-series method returns a wide ``(date x asset)`` ``pd.DataFrame``; a cross-sectional (ragged / entering-exiting universe) method returns a long ``pd.Series`` on a ``(date, asset)`` MultiIndex — the panel engine (``backtest_panel``) accepts that form (or an equivalent one-column frame). Either way the engine aligns the returned labels to realized returns before scoring, so column / row order need not be canonical. """ ...
[docs] @runtime_checkable class SupportsForecast(Protocol): """Capability protocol (v0): a model that emits a next-horizon return forecast. The forecast-origin engine fits on a window ending at ``t`` and calls ``forecast`` on that same window; the returned value is the prediction of the return over ``(t, t+h]``, where ``t`` is the window's last date. Advertised via ``capabilities() >= {TO_FORECAST}``. """
[docs] def forecast(self, view: DataView) -> pd.Series: """Per-asset forecast of the return over ``(t, t+h]`` (``t`` is the view's last date).""" ...
[docs] @runtime_checkable class SupportsPricing(Protocol): """Capability protocol: a model that prices a cross-section of test assets (``to_pricing``). The single shared operation across the pricing/SDF family (factor models, SDFs, three-pass risk-premium estimators): the cross-section of **expected returns** on a set of assets. A conditional model varies its estimate by date (e.g. a characteristics-driven loading times a factor premium); an unconditional model returns the same row every date (broadcast). Advertised via ``capabilities() >= {TO_PRICING}``. Kept deliberately to this one method — the bespoke per-method accessors (loadings, latent factors, per-candidate premia) stay method-local; only the pricing surface the framework's evaluators and comparison harness consume is standardized here. """
[docs] def expected_returns(self, view: DataView) -> pd.DataFrame: """Return ``(date x asset)`` expected returns for each date in ``view.calendar``.""" ...
[docs] @runtime_checkable class Estimator(Protocol): """scikit-learn-compatible. The method-specific body lives in methods/ or a lab repo."""
[docs] def fit(self, view: DataView) -> Model: """Fit on a (point-in-time) view and return a fitted ``Model``.""" ...
[docs] @runtime_checkable class Splitter(Protocol): """Yields (train, test) views — purge/embargo/PIT aware. May wrap sklearn splitters."""
[docs] def split(self, view: DataView) -> Iterator[tuple[DataView, DataView]]: """Yield (train, test) view pairs; the test view is strictly future of train.""" ...
[docs] @runtime_checkable class Evaluator(Protocol): """Scores OOS output, emitting rows of the standard tidy result schema.""" requires: ClassVar[set[str]] """Capabilities an OOS output must expose for this evaluator to run."""
[docs] def evaluate(self, oos_output: object) -> pd.DataFrame: """Return rows in the standard result schema (see ``numeraire.core.schema``).""" ...