Coverage for src/jquantstats/_plots/_protocol.py: 100%
6 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"""Protocols describing the minimal interfaces required by the _plots subpackage."""
3from __future__ import annotations
5from typing import Protocol, runtime_checkable
7import polars as pl
9from jquantstats._cost_model import CostModel
10from jquantstats._protocol import DataLike
12__all__ = ["DataLike", "PortfolioLike", "PortfolioStatsLike"]
15class PortfolioStatsLike(Protocol): # pragma: no cover
16 """Structural interface for the stats facade used by `PortfolioPlots`."""
18 def sharpe(self) -> dict[str, float]:
19 """Annualised Sharpe ratio per asset column."""
20 ...
22 def rolling_sharpe(
23 self,
24 rolling_period: int = 126,
25 periods_per_year: int | float | None = None,
26 ) -> pl.DataFrame:
27 """Rolling Sharpe ratio series."""
28 ...
30 def rolling_volatility(
31 self,
32 rolling_period: int = 126,
33 periods_per_year: int | float | None = None,
34 annualize: bool = True,
35 ) -> pl.DataFrame:
36 """Rolling volatility series."""
37 ...
39 def annual_breakdown(self) -> pl.DataFrame:
40 """Per-year metric breakdown."""
41 ...
44@runtime_checkable
45class PortfolioLike(Protocol): # pragma: no cover
46 """Structural interface required by the `PortfolioPlots` class.
48 Any object satisfying this protocol can be passed as ``portfolio`` without a
49 concrete dependency on `Portfolio`.
50 """
52 @property
53 def prices(self) -> pl.DataFrame:
54 """Price (holding) DataFrame."""
55 ...
57 @property
58 def aum(self) -> float:
59 """Assets under management."""
60 ...
62 @property
63 def cost_model(self) -> CostModel:
64 """Trading cost model."""
65 ...
67 @property
68 def nav_accumulated(self) -> pl.DataFrame:
69 """Accumulated NAV series."""
70 ...
72 @property
73 def tilt(self) -> PortfolioLike:
74 """Tilt component portfolio."""
75 ...
77 @property
78 def timing(self) -> PortfolioLike:
79 """Timing component portfolio."""
80 ...
82 @property
83 def net_cost_nav(self) -> pl.DataFrame:
84 """Net-of-cost accumulated NAV series."""
85 ...
87 @property
88 def drawdown(self) -> pl.DataFrame:
89 """Drawdown series."""
90 ...
92 @property
93 def assets(self) -> list[str]:
94 """Asset names."""
95 ...
97 @property
98 def monthly(self) -> pl.DataFrame:
99 """Monthly returns grouped by year and month."""
100 ...
102 @property
103 def profits(self) -> pl.DataFrame:
104 """Per-period profit series."""
105 ...
107 @property
108 def stats(self) -> PortfolioStatsLike:
109 """Statistics facade (rolling_sharpe, rolling_volatility, annual_breakdown, sharpe)."""
110 ...
112 def lag(self, n: int) -> PortfolioLike:
113 """Return a lagged copy of this portfolio."""
114 ...
116 def smoothed_holding(self, n: int) -> PortfolioLike:
117 """Return a smoothed-holdings copy of this portfolio."""
118 ...
120 def trading_cost_impact(self, max_bps: int = 20) -> pl.DataFrame:
121 """Return a DataFrame of Sharpe vs. one-way trading costs."""
122 ...
124 def correlation(self, frame: pl.DataFrame, name: str = "portfolio") -> pl.DataFrame:
125 """Return the correlation matrix including the portfolio profit series."""
126 ...