Coverage for src/jquantstats/_protocol.py: 100%
4 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-23 06:13 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-23 06:13 +0000
1"""Shared protocol definitions used across jquantstats subpackages.
3Design rationale
4----------------
5The analytics subpackages (``_stats``, ``_plots``, ``_reports``, ``_utils``)
6must not import the concrete `Data` / `Portfolio` classes at runtime — that
7would create circular imports, since those classes compose the subpackages.
8Instead, each consumer annotates against a structural Protocol:
10- `DataLike` and `StatsLike` (this module) are shared by every subpackage —
11 there is exactly one definition of each.
12- ``PortfolioLike`` is deliberately *not* shared: each subpackage declares its
13 own (``_plots/_protocol.py``, ``_reports/_protocol.py``,
14 ``_utils/_protocol.py``) listing only the members it actually consumes
15 (interface segregation). Keep it that way — a merged PortfolioLike would
16 re-couple the subpackages to the full Portfolio surface.
17"""
19from __future__ import annotations
21from collections.abc import Iterator
22from typing import Protocol, runtime_checkable
24import polars as pl
27class StatsLike(Protocol): # pragma: no cover
28 """Structural interface for the statistics facade used by reports."""
30 def summary(self) -> pl.DataFrame:
31 """Full summary DataFrame (one row per metric, one column per asset)."""
32 ...
35@runtime_checkable
36class DataLike(Protocol): # pragma: no cover
37 """Authoritative structural interface for Data consumers.
39 Union of the members required by the stats mixins, plots, reports, and
40 utils — annotating against the superset is harmless for consumers that
41 use only part of it, and keeps a single definition.
42 """
44 @property
45 def returns(self) -> pl.DataFrame:
46 """Return DataFrame (asset columns only, no benchmark or date)."""
47 ...
49 @property
50 def index(self) -> pl.DataFrame:
51 """Date / time index DataFrame."""
52 ...
54 @property
55 def benchmark(self) -> pl.DataFrame | None:
56 """Benchmark DataFrame, or None when no benchmark was provided."""
57 ...
59 @property
60 def all(self) -> pl.DataFrame:
61 """Combined DataFrame of date index, return, and benchmark columns."""
62 ...
64 @property
65 def assets(self) -> list[str]:
66 """Names of the asset return columns."""
67 ...
69 @property
70 def date_col(self) -> list[str]:
71 """Column names used as the date/time index."""
72 ...
74 @property
75 def stats(self) -> StatsLike:
76 """Statistics facade used by reports."""
77 ...
79 @property
80 def _periods_per_year(self) -> float:
81 """Estimated number of return periods per calendar year."""
82 ...
84 def items(self) -> Iterator[tuple[str, pl.Series]]:
85 """Iterate over (asset_name, returns_series) pairs."""
86 ...