Coverage for src/jquantstats/_plots/_data/_cumulative.py: 100%
16 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 04:11 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 04:11 +0000
1"""Cumulative-return and equity-curve line charts."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Literal, overload
7from .._render import render
8from .._specs import compare_spec, cumulative_returns_spec, earnings_spec, log_returns_spec
10if TYPE_CHECKING:
11 from matplotlib.figure import Figure as MplFigure
12 from plotly.graph_objects import Figure as PlotlyFigure
14 from jquantstats._protocol import DataLike
16 from .._backend import Backend
17 from .._render import Figure
20class _CumulativePlotsMixin:
21 """Cumulative-return and equity-curve plots for :class:`DataPlots`.
23 Every method here accepts a ``backend`` keyword selecting the renderer, and
24 is overloaded on it: omitting it (or naming ``"plotly"``) is typed as
25 returning a `plotly.graph_objects.Figure`, so callers written before the
26 matplotlib backend existed keep exactly the type they had.
28 One caveat that no type system can express: after
29 `jquantstats.set_plot_backend` changes the process-wide default, a call
30 that passes no ``backend`` returns the new default's figure type while
31 still being *typed* as Plotly's. Pass ``backend=`` explicitly if you change
32 the global default and care about static types.
33 """
35 __slots__ = ()
37 _data: DataLike
39 @overload
40 def returns(
41 self, title: str = ..., log_scale: bool = ..., *, backend: Literal["plotly"] | None = ...
42 ) -> PlotlyFigure: ...
44 @overload
45 def returns(self, title: str = ..., log_scale: bool = ..., *, backend: Literal["matplotlib"]) -> MplFigure: ...
47 def returns(
48 self,
49 title: str = "Cumulative Returns",
50 log_scale: bool = False,
51 *,
52 backend: Backend | None = None,
53 ) -> Figure:
54 """Cumulative compounded returns over time.
56 Plots ``(1 + r).cumprod()`` for every column in the dataset (including
57 benchmark when present).
59 Args:
60 title: Chart title. Defaults to ``"Cumulative Returns"``.
61 log_scale: Use a logarithmic y-axis. Defaults to False.
62 backend: Renderer to use. Defaults to the ambient selection.
64 Returns:
65 Figure: A line chart.
67 """
68 return render(cumulative_returns_spec(self._data, title=title, log_scale=log_scale), backend)
70 @overload
71 def compare(
72 self,
73 title: str = ...,
74 figsize: tuple[int, int] | None = ...,
75 *,
76 backend: Literal["plotly"] | None = ...,
77 ) -> PlotlyFigure: ...
79 @overload
80 def compare(
81 self, title: str = ..., figsize: tuple[int, int] | None = ..., *, backend: Literal["matplotlib"]
82 ) -> MplFigure: ...
84 def compare(
85 self,
86 title: str = "Comparison vs Benchmark",
87 figsize: tuple[int, int] | None = None,
88 *,
89 backend: Backend | None = None,
90 ) -> Figure:
91 """Compare cumulative returns of each asset against the benchmark.
93 Args:
94 title: Chart title. Defaults to ``"Comparison vs Benchmark"``.
95 figsize: Optional ``(width, height)`` in pixels.
96 backend: Renderer to use. Defaults to the ambient selection.
98 Returns:
99 Figure: A line chart.
101 Raises:
102 AttributeError: If no benchmark data is available.
104 """
105 return render(compare_spec(self._data, title=title, figsize=figsize), backend)
107 @overload
108 def log_returns(
109 self,
110 title: str = ...,
111 figsize: tuple[int, int] | None = ...,
112 *,
113 backend: Literal["plotly"] | None = ...,
114 ) -> PlotlyFigure: ...
116 @overload
117 def log_returns(
118 self, title: str = ..., figsize: tuple[int, int] | None = ..., *, backend: Literal["matplotlib"]
119 ) -> MplFigure: ...
121 def log_returns(
122 self,
123 title: str = "Log Returns",
124 figsize: tuple[int, int] | None = None,
125 *,
126 backend: Backend | None = None,
127 ) -> Figure:
128 """Cumulative log returns over time.
130 Plots ``log((1 + r).cumprod())`` — the natural log of the compounded
131 growth factor — which linearises exponential growth and makes
132 multi-asset comparisons on a common scale.
134 Args:
135 title: Chart title. Defaults to ``"Log Returns"``.
136 figsize: Optional ``(width, height)`` in pixels.
137 backend: Renderer to use. Defaults to the ambient selection.
139 Returns:
140 Figure: A line chart.
142 """
143 return render(log_returns_spec(self._data, title=title, figsize=figsize), backend)
145 @overload
146 def earnings(
147 self,
148 start_balance: float = ...,
149 title: str = ...,
150 compounded: bool = ...,
151 *,
152 backend: Literal["plotly"] | None = ...,
153 ) -> PlotlyFigure: ...
155 @overload
156 def earnings(
157 self,
158 start_balance: float = ...,
159 title: str = ...,
160 compounded: bool = ...,
161 *,
162 backend: Literal["matplotlib"],
163 ) -> MplFigure: ...
165 def earnings(
166 self,
167 start_balance: float = 1e5,
168 title: str = "Portfolio Earnings",
169 compounded: bool = True,
170 *,
171 backend: Backend | None = None,
172 ) -> Figure:
173 """Dollar equity curve showing portfolio value over time.
175 Scales cumulative returns by *start_balance* so the y-axis reflects
176 an absolute portfolio value rather than a dimensionless growth factor.
178 Args:
179 start_balance: Starting portfolio value in currency units.
180 Defaults to 100 000.
181 title: Chart title. Defaults to ``"Portfolio Earnings"``.
182 compounded: Use compounded returns (``cumprod``). When False uses
183 cumulative sum. Defaults to True.
184 backend: Renderer to use. Defaults to the ambient selection.
186 Returns:
187 Figure: A line chart.
189 """
190 spec = earnings_spec(self._data, start_balance=start_balance, title=title, compounded=compounded)
191 return render(spec, backend)