Coverage for src / jquantstats / _reports / _protocol.py: 100%
4 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-07 14:28 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-07 14:28 +0000
1"""Protocols describing the minimal interfaces required by the _reports subpackage."""
3from __future__ import annotations
5from typing import Protocol, runtime_checkable
7import plotly.graph_objects as go
8import polars as pl
11@runtime_checkable
12class StatsLike(Protocol): # pragma: no cover
13 """Structural interface for the statistics methods used by :class:`~jquantstats._reports._data.Reports`."""
15 def sharpe(self, periods: int | float | None = None) -> dict[str, float]:
16 """Annualised Sharpe ratio per asset."""
17 ...
19 def smart_sharpe(self, periods: int | float | None = None) -> dict[str, float]:
20 """Smart Sharpe ratio per asset."""
21 ...
23 def sortino(self, periods: int | float | None = None) -> dict[str, float]:
24 """Annualised Sortino ratio per asset."""
25 ...
27 def adjusted_sortino(self, periods: int | float | None = None) -> dict[str, float]:
28 """Adjusted Sortino (Sortino / √2) per asset."""
29 ...
31 def smart_sortino(self, periods: int | float | None = None) -> dict[str, float]:
32 """Smart Sortino ratio per asset."""
33 ...
35 def omega(self, periods: int | float | None = None) -> dict[str, float]:
36 """Omega ratio per asset."""
37 ...
39 def probabilistic_sharpe_ratio(self) -> dict[str, float]:
40 """Probabilistic Sharpe ratio per asset."""
41 ...
43 def cagr(self, periods: int | float | None = None) -> dict[str, float]:
44 """Compound annual growth rate per asset."""
45 ...
47 def comp(self) -> dict[str, float]:
48 """Total compounded return per asset."""
49 ...
51 def exposure(self) -> dict[str, float]:
52 """Market exposure (time in market) per asset."""
53 ...
55 def max_drawdown(self) -> dict[str, float]:
56 """Maximum drawdown per asset."""
57 ...
59 def avg_drawdown(self) -> dict[str, float]:
60 """Average drawdown per asset."""
61 ...
63 def max_drawdown_duration(self) -> dict[str, float]:
64 """Maximum drawdown duration per asset."""
65 ...
67 def recovery_factor(self) -> dict[str, float]:
68 """Recovery factor per asset."""
69 ...
71 def ulcer_index(self) -> dict[str, float]:
72 """Ulcer index per asset."""
73 ...
75 def serenity_index(self) -> dict[str, float]:
76 """Serenity index per asset."""
77 ...
79 def ulcer_performance_index(self) -> dict[str, float]:
80 """Ulcer performance index per asset."""
81 ...
83 def calmar(self, periods: int | float | None = None) -> dict[str, float]:
84 """Calmar ratio per asset."""
85 ...
87 def rar(self, periods: int | float = 252) -> dict[str, float]:
88 """Risk-adjusted return per asset."""
89 ...
91 def risk_return_ratio(self) -> dict[str, float]:
92 """Risk-return ratio per asset."""
93 ...
95 def gain_to_pain_ratio(self, aggregate: str | None = None) -> dict[str, float]:
96 """Gain-to-pain ratio per asset."""
97 ...
99 def payoff_ratio(self) -> dict[str, float]:
100 """Payoff ratio per asset."""
101 ...
103 def profit_factor(self) -> dict[str, float]:
104 """Profit factor per asset."""
105 ...
107 def profit_ratio(self) -> dict[str, float]:
108 """Profit ratio per asset."""
109 ...
111 def common_sense_ratio(self) -> dict[str, float]:
112 """Common sense ratio per asset."""
113 ...
115 def cpc_index(self) -> dict[str, float]:
116 """CPC index per asset."""
117 ...
119 def tail_ratio(self) -> dict[str, float]:
120 """Tail ratio per asset."""
121 ...
123 def outlier_win_ratio(self) -> dict[str, float]:
124 """Outlier win ratio per asset."""
125 ...
127 def outlier_loss_ratio(self) -> dict[str, float]:
128 """Outlier loss ratio per asset."""
129 ...
131 def volatility(self, periods: int | float | None = None) -> dict[str, float]:
132 """Annualised volatility per asset."""
133 ...
135 def value_at_risk(self, alpha: float = 0.05) -> dict[str, float]:
136 """Value at Risk per asset."""
137 ...
139 def conditional_value_at_risk(self, sigma: float = 1.0, alpha: float = 0.05, **kwargs: float) -> dict[str, float]:
140 """Conditional Value at Risk per asset."""
141 ...
143 def win_loss_ratio(self) -> dict[str, float]:
144 """Win/loss ratio per asset."""
145 ...
147 def win_rate(self) -> dict[str, float]:
148 """Win rate per asset."""
149 ...
151 def monthly_win_rate(self) -> dict[str, float]:
152 """Monthly win rate per asset."""
153 ...
155 def avg_return(self) -> dict[str, float]:
156 """Average return per asset."""
157 ...
159 def avg_win(self) -> dict[str, float]:
160 """Average win per asset."""
161 ...
163 def avg_loss(self) -> dict[str, float]:
164 """Average loss per asset."""
165 ...
167 def best(self) -> dict[str, float]:
168 """Best period return per asset."""
169 ...
171 def worst(self) -> dict[str, float]:
172 """Worst period return per asset."""
173 ...
175 def skew(self) -> dict[str, float]:
176 """Skewness per asset."""
177 ...
179 def kurtosis(self) -> dict[str, float]:
180 """Kurtosis per asset."""
181 ...
183 def consecutive_wins(self) -> dict[str, float]:
184 """Maximum consecutive wins per asset."""
185 ...
187 def consecutive_losses(self) -> dict[str, float]:
188 """Maximum consecutive losses per asset."""
189 ...
191 def kelly_criterion(self) -> dict[str, float]:
192 """Kelly criterion per asset."""
193 ...
195 def risk_of_ruin(self) -> dict[str, float]:
196 """Risk of ruin per asset."""
197 ...
199 def expected_return(self, aggregate: str | None = None) -> dict[str, float]:
200 """Expected return per asset."""
201 ...
203 def greeks(self) -> dict[str, dict[str, float]]:
204 """Alpha and beta per asset."""
205 ...
207 def r2(self) -> dict[str, float]:
208 """R-squared per asset versus benchmark."""
209 ...
211 def treynor_ratio(self, periods: int | float | None = None) -> dict[str, float]:
212 """Treynor ratio per asset."""
213 ...
215 def drawdown_details(self) -> dict[str, pl.DataFrame]:
216 """Drawdown period details per asset."""
217 ...
219 def summary(self) -> pl.DataFrame:
220 """Full summary DataFrame (one row per metric, one column per asset)."""
221 ...
224@runtime_checkable
225class DataLike(Protocol): # pragma: no cover
226 """Structural interface required by the :class:`~jquantstats._reports._data.Reports` class.
228 Any object satisfying this protocol can be passed as ``data`` without a
229 concrete dependency on :class:`~jquantstats._data.Data`.
230 """
232 @property
233 def stats(self) -> StatsLike:
234 """Statistics facade."""
235 ...
237 @property
238 def all(self) -> pl.DataFrame:
239 """Combined DataFrame of date index and all return columns."""
240 ...
243@runtime_checkable
244class PlotsLike(Protocol): # pragma: no cover
245 """Structural interface for the portfolio plots facade used by :class:`~jquantstats._reports._portfolio.Report`."""
247 def snapshot(self) -> go.Figure:
248 """NAV + drawdown snapshot figure."""
249 ...
251 def rolling_sharpe_plot(self) -> go.Figure:
252 """Rolling Sharpe figure."""
253 ...
255 def rolling_volatility_plot(self) -> go.Figure:
256 """Rolling volatility figure."""
257 ...
259 def annual_sharpe_plot(self) -> go.Figure:
260 """Annual Sharpe figure."""
261 ...
263 def monthly_returns_heatmap(self) -> go.Figure:
264 """Monthly returns heatmap figure."""
265 ...
267 def correlation_heatmap(self) -> go.Figure:
268 """Correlation heatmap figure."""
269 ...
271 def lead_lag_ir_plot(self) -> go.Figure:
272 """Lead/lag IR figure."""
273 ...
275 def trading_cost_impact_plot(self) -> go.Figure:
276 """Trading cost impact figure."""
277 ...
280@runtime_checkable
281class PortfolioLike(Protocol): # pragma: no cover
282 """Structural interface required by the :class:`~jquantstats._reports._portfolio.Report` class.
284 Any object satisfying this protocol can be passed as ``portfolio`` without a
285 concrete dependency on :class:`~jquantstats.portfolio.Portfolio`.
286 """
288 prices: pl.DataFrame
289 aum: float
291 @property
292 def assets(self) -> list[str]:
293 """Asset names."""
294 ...
296 @property
297 def plots(self) -> PlotsLike:
298 """Portfolio plots facade."""
299 ...
301 @property
302 def stats(self) -> StatsLike:
303 """Statistics facade."""
304 ...
306 def turnover_summary(self) -> pl.DataFrame:
307 """Turnover summary DataFrame."""
308 ...