Coverage for src/cvx/simulator/_analytics.py: 100%
22 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-31 13:14 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-31 13:14 +0000
1# Copyright 2023 Stanford University Convex Optimization Group
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""jquantstats-backed performance analytics for the Portfolio class.
16This module houses :class:`PortfolioAnalytics`, a mixin that supplies the
17statistics, plotting, and reporting surface layered on top of a portfolio's
18net asset value. Keeping it separate from the core
19:class:`~cvx.simulator.portfolio.Portfolio` data model isolates the dependency
20on :mod:`jquantstats` and keeps each module focused on a single concern.
21"""
23from __future__ import annotations
25from typing import TYPE_CHECKING, Any
27from jquantstats.data import Data
29if TYPE_CHECKING:
30 import pandas as pd
33class PortfolioAnalytics:
34 """Mixin providing performance statistics, plots, and reports for a portfolio.
36 The mixin expects the host class to expose a ``nav`` :class:`pandas.Series`
37 property. From that NAV it lazily builds and caches a
38 :class:`jquantstats.data.Data` object (in :meth:`_build_data`) and exposes
39 the ``stats``/``plots``/``reports`` surface plus the :meth:`sharpe` and
40 :meth:`snapshot` convenience helpers on top of it.
41 """
43 if TYPE_CHECKING:
44 # Supplied by the host dataclass (Portfolio); declared here purely so the
45 # type checker can resolve the attributes the mixin relies on.
46 _data: Data
48 @property
49 def nav(self) -> pd.Series:
50 """Net asset value of the portfolio over time (provided by the host)."""
51 ...
53 def _build_data(self) -> None:
54 """Build and cache the derived quantstats Data from the portfolio NAV.
56 This constructs the returns-based :class:`~jquantstats.data.Data` object
57 used by the reporting and statistics helpers and stores it on the frozen
58 instance's ``_data`` field. It is invoked from ``__post_init__`` after
59 the input validation has passed.
60 """
61 frame = self.nav.pct_change().to_frame().reset_index()
62 frame.columns = ["Date", frame.columns[1]]
63 d = Data.from_returns(returns=frame)
65 object.__setattr__(self, "_data", d)
67 @property
68 def stats(self) -> Any:
69 """Get statistical analysis data for the portfolio.
71 This property provides access to various statistical metrics calculated
72 for the portfolio, such as Sharpe ratio, volatility, drawdowns, etc.
74 Returns:
75 -------
76 object
77 An object containing various statistical metrics for the portfolio
79 Notes:
80 -----
81 The statistics are calculated by the underlying jquantstats library
82 and are based on the portfolio's NAV time series.
84 """
85 return self._data.stats
87 @property
88 def plots(self) -> Any:
89 """Get visualization tools for the portfolio.
91 This property provides access to various plotting functions for visualizing
92 the portfolio's performance, returns, drawdowns, etc.
94 Returns:
95 -------
96 object
97 An object containing various plotting methods for the portfolio
99 Notes:
100 -----
101 The plotting functions are provided by the underlying jquantstats library
102 and operate on the portfolio's NAV time series.
104 """
105 return self._data.plots
107 @property
108 def reports(self) -> Any:
109 """Get reporting tools for the portfolio.
111 This property provides access to various reporting functions for generating
112 performance reports, risk metrics, and other analytics for the portfolio.
114 Returns:
115 -------
116 object
117 An object containing various reporting methods for the portfolio
119 Notes:
120 -----
121 The reporting functions are provided by the underlying jquantstats library
122 and operate on the portfolio's NAV time series.
124 """
125 return self._data.reports
127 def sharpe(self, periods: int | None = None) -> float:
128 """Calculate the Sharpe ratio for the portfolio.
130 The Sharpe ratio is a measure of risk-adjusted return, calculated as
131 the portfolio's excess return divided by its volatility.
133 Parameters
134 ----------
135 periods : int, optional
136 The number of periods per year for annualization.
137 For daily data, use 252; for weekly data, use 52; for monthly data, use 12.
138 If None, no annualization is performed.
140 Returns:
141 -------
142 float
143 The Sharpe ratio of the portfolio
145 Notes:
146 -----
147 The Sharpe ratio is calculated using the portfolio's NAV time series.
148 A higher Sharpe ratio indicates better risk-adjusted performance.
150 """
151 return float(self.stats.sharpe(periods=periods)["NAV"])
153 def snapshot(self, title: str = "Portfolio Summary", log_scale: bool = True) -> Any:
154 """Generate and display a snapshot of the portfolio summary.
156 This method creates a visual representation of the portfolio summary
157 using the associated plot functionalities. The snapshot can be
158 configured with a title and whether to use a logarithmic scale.
160 Args:
161 title: A string specifying the title of the snapshot.
162 Default is "Portfolio Summary".
163 log_scale: A boolean indicating whether to display the plot
164 using a logarithmic scale. Default is True.
166 Returns:
167 The generated plot object representing the portfolio snapshot.
169 """
170 return self.plots.snapshot(title=title, log_scale=log_scale)