Skip to content

Commit 8295f5d

Browse files
Copilottschm
andcommitted
Add numpy-style docstrings to efficient_cvar, efficient_semivariance, efficient_cdar modules
Co-authored-by: tschm <[email protected]>
1 parent 25def59 commit 8295f5d

File tree

3 files changed

+615
-267
lines changed

3 files changed

+615
-267
lines changed

pypfopt/efficient_frontier/efficient_cdar.py

Lines changed: 208 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""
2-
The ``efficient_cdar`` submodule houses the EfficientCDaR class, which
3-
generates portfolios along the mean-CDaR (conditional drawdown-at-risk) frontier.
2+
The ``efficient_cdar`` submodule houses the EfficientCDaR class.
3+
4+
This module generates portfolios along the mean-CDaR
5+
(conditional drawdown-at-risk) frontier.
46
"""
57

68
import warnings
@@ -14,35 +16,37 @@
1416

1517
class EfficientCDaR(EfficientFrontier):
1618
"""
17-
The EfficientCDaR class allows for optimisation along the mean-CDaR frontier, using the
18-
formulation of Chekhlov, Ursayev and Zabarankin (2005).
19-
20-
Instance variables:
21-
22-
- Inputs:
23-
24-
- ``n_assets`` - int
25-
- ``tickers`` - str list
26-
- ``bounds`` - float tuple OR (float tuple) list
27-
- ``returns`` - pd.DataFrame
28-
- ``expected_returns`` - np.ndarray
29-
- ``solver`` - str
30-
- ``solver_options`` - {str: str} dict
31-
32-
- Output: ``weights`` - np.ndarray
33-
34-
Public methods:
35-
36-
- ``min_cdar()`` minimises the CDaR
37-
- ``efficient_risk()`` maximises return for a given CDaR
38-
- ``efficient_return()`` minimises CDaR for a given target return
39-
- ``add_objective()`` adds a (convex) objective to the optimisation problem
40-
- ``add_constraint()`` adds a (linear) constraint to the optimisation problem
41-
42-
- ``portfolio_performance()`` calculates the expected return and CDaR of the portfolio
43-
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict
44-
- ``clean_weights()`` rounds the weights and clips near-zeros.
45-
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
19+
Optimization along the mean-CDaR (Conditional Drawdown at Risk) frontier.
20+
21+
The EfficientCDaR class allows for optimisation along the mean-CDaR
22+
frontier, using the formulation of Chekhlov, Ursayev and Zabarankin (2005).
23+
24+
Attributes
25+
----------
26+
n_assets : int
27+
Number of assets.
28+
tickers : list
29+
List of asset tickers.
30+
bounds : tuple or list
31+
Weight bounds for each asset.
32+
returns : pd.DataFrame
33+
Historical returns data.
34+
expected_returns : np.ndarray
35+
Expected returns for each asset.
36+
solver : str
37+
CVXPY solver name.
38+
solver_options : dict
39+
Solver parameters.
40+
weights : np.ndarray
41+
Optimized portfolio weights.
42+
43+
Examples
44+
--------
45+
>>> from pypfopt import EfficientCDaR, expected_returns
46+
>>> # mu = expected_returns.mean_historical_return(prices)
47+
>>> # returns = expected_returns.returns_from_prices(prices)
48+
>>> # ef = EfficientCDaR(mu, returns)
49+
>>> # weights = ef.min_cdar()
4650
"""
4751

4852
def __init__(
@@ -56,24 +60,35 @@ def __init__(
5660
solver_options=None,
5761
):
5862
"""
59-
:param expected_returns: expected returns for each asset. Can be None if
60-
optimising for CDaR only.
61-
:type expected_returns: pd.Series, list, np.ndarray
62-
:param returns: (historic) returns for all your assets (no NaNs).
63-
See ``expected_returns.returns_from_prices``.
64-
:type returns: pd.DataFrame or np.array
65-
:param beta: confidence level, defaults to 0.95 (i.e expected drawdown on the worst (1-beta) days).
66-
:param weight_bounds: minimum and maximum weight of each asset OR single min/max pair
67-
if all identical, defaults to (0, 1). Must be changed to (-1, 1)
68-
for portfolios with shorting.
69-
:type weight_bounds: tuple OR tuple list, optional
70-
:param solver: name of solver. list available solvers with: `cvxpy.installed_solvers()`
71-
:type solver: str
72-
:param verbose: whether performance and debugging info should be printed, defaults to False
73-
:type verbose: bool, optional
74-
:param solver_options: parameters for the given solver
75-
:type solver_options: dict, optional
76-
:raises TypeError: if ``expected_returns`` is not a series, list or array
63+
Initialize the EfficientCDaR object.
64+
65+
Parameters
66+
----------
67+
expected_returns : pd.Series, list, or np.ndarray
68+
Expected returns for each asset. Can be None if
69+
optimising for CDaR only.
70+
returns : pd.DataFrame or np.ndarray
71+
(Historic) returns for all your assets (no NaNs).
72+
See ``expected_returns.returns_from_prices``.
73+
beta : float, optional
74+
Confidence level, defaults to 0.95 (i.e expected drawdown
75+
on the worst (1-beta) days).
76+
weight_bounds : tuple or list, optional
77+
Minimum and maximum weight of each asset OR single min/max pair
78+
if all identical, defaults to (0, 1). Must be changed to (-1, 1)
79+
for portfolios with shorting.
80+
solver : str, optional
81+
Name of solver. List available solvers with ``cvxpy.installed_solvers()``.
82+
verbose : bool, optional
83+
Whether performance and debugging info should be printed,
84+
defaults to False.
85+
solver_options : dict, optional
86+
Parameters for the given solver.
87+
88+
Raises
89+
------
90+
TypeError
91+
If ``expected_returns`` is not a series, list or array.
7792
"""
7893
super().__init__(
7994
expected_returns=expected_returns,
@@ -91,10 +106,36 @@ def __init__(
91106
self._z = cp.Variable(len(self.returns))
92107

93108
def set_weights(self, input_weights):
109+
"""
110+
Override parent method.
111+
112+
Raises
113+
------
114+
NotImplementedError
115+
Always, as set_weights is not available in EfficientCDaR.
116+
"""
94117
raise NotImplementedError("Method not available in EfficientCDaR.")
95118

96119
@staticmethod
97120
def _validate_beta(beta):
121+
"""
122+
Validate the beta (confidence level) parameter.
123+
124+
Parameters
125+
----------
126+
beta : float
127+
Confidence level.
128+
129+
Returns
130+
-------
131+
float
132+
Validated beta value.
133+
134+
Raises
135+
------
136+
ValueError
137+
If beta is not between 0 and 1.
138+
"""
98139
if not (0 <= beta < 1):
99140
raise ValueError("beta must be between 0 and 1")
100141
if beta <= 0.2:
@@ -105,23 +146,59 @@ def _validate_beta(beta):
105146
return beta
106147

107148
def min_volatility(self):
149+
"""
150+
Override parent method.
151+
152+
Raises
153+
------
154+
NotImplementedError
155+
Always, as min_volatility is not available in EfficientCDaR.
156+
Use min_cdar instead.
157+
"""
108158
raise NotImplementedError("Please use min_cdar instead.")
109159

110160
def max_sharpe(self, risk_free_rate=0.0):
161+
"""
162+
Override parent method.
163+
164+
Raises
165+
------
166+
NotImplementedError
167+
Always, as max_sharpe is not available in EfficientCDaR.
168+
"""
111169
raise NotImplementedError("Method not available in EfficientCDaR.")
112170

113171
def max_quadratic_utility(self, risk_aversion=1, market_neutral=False):
172+
"""
173+
Override parent method.
174+
175+
Raises
176+
------
177+
NotImplementedError
178+
Always, as max_quadratic_utility is not available in EfficientCDaR.
179+
"""
114180
raise NotImplementedError("Method not available in EfficientCDaR.")
115181

116182
def min_cdar(self, market_neutral=False):
117183
"""
118-
Minimise portfolio CDaR (see docs for further explanation).
119-
120-
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
121-
defaults to False. Requires negative lower weight bound.
122-
:param market_neutral: bool, optional
123-
:return: asset weights for the volatility-minimising portfolio
124-
:rtype: OrderedDict
184+
Minimise portfolio CDaR (Conditional Drawdown at Risk).
185+
186+
Parameters
187+
----------
188+
market_neutral : bool, optional
189+
Whether the portfolio should be market neutral (weights sum to zero),
190+
defaults to False. Requires negative lower weight bound.
191+
192+
Returns
193+
-------
194+
OrderedDict
195+
Asset weights for the CDaR-minimising portfolio.
196+
197+
Examples
198+
--------
199+
>>> from pypfopt import EfficientCDaR
200+
>>> # ef = EfficientCDaR(mu, returns)
201+
>>> # weights = ef.min_cdar()
125202
"""
126203
self._objective = self._alpha + 1.0 / (
127204
len(self.returns) * (1 - self._beta)
@@ -138,15 +215,30 @@ def efficient_return(self, target_return, market_neutral=False):
138215
"""
139216
Minimise CDaR for a given target return.
140217
141-
:param target_return: the desired return of the resulting portfolio.
142-
:type target_return: float
143-
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
144-
defaults to False. Requires negative lower weight bound.
145-
:type market_neutral: bool, optional
146-
:raises ValueError: if ``target_return`` is not a positive float
147-
:raises ValueError: if no portfolio can be found with return equal to ``target_return``
148-
:return: asset weights for the optimal portfolio
149-
:rtype: OrderedDict
218+
Parameters
219+
----------
220+
target_return : float
221+
The desired return of the resulting portfolio.
222+
market_neutral : bool, optional
223+
Whether the portfolio should be market neutral (weights sum to zero),
224+
defaults to False. Requires negative lower weight bound.
225+
226+
Returns
227+
-------
228+
OrderedDict
229+
Asset weights for the optimal portfolio.
230+
231+
Raises
232+
------
233+
ValueError
234+
If ``target_return`` is not a positive float.
235+
If no portfolio can be found with return equal to ``target_return``.
236+
237+
Examples
238+
--------
239+
>>> from pypfopt import EfficientCDaR
240+
>>> # ef = EfficientCDaR(mu, returns)
241+
>>> # weights = ef.efficient_return(target_return=0.15)
150242
"""
151243

152244
update_existing_parameter = self.is_parameter_defined("target_return")
@@ -165,16 +257,28 @@ def efficient_return(self, target_return, market_neutral=False):
165257
def efficient_risk(self, target_cdar, market_neutral=False):
166258
"""
167259
Maximise return for a target CDaR.
260+
168261
The resulting portfolio will have a CDaR less than the target
169262
(but not guaranteed to be equal).
170263
171-
:param target_cdar: the desired maximum CDaR of the resulting portfolio.
172-
:type target_cdar: float
173-
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
174-
defaults to False. Requires negative lower weight bound.
175-
:param market_neutral: bool, optional
176-
:return: asset weights for the efficient risk portfolio
177-
:rtype: OrderedDict
264+
Parameters
265+
----------
266+
target_cdar : float
267+
The desired maximum CDaR of the resulting portfolio.
268+
market_neutral : bool, optional
269+
Whether the portfolio should be market neutral (weights sum to zero),
270+
defaults to False. Requires negative lower weight bound.
271+
272+
Returns
273+
-------
274+
OrderedDict
275+
Asset weights for the efficient risk portfolio.
276+
277+
Examples
278+
--------
279+
>>> from pypfopt import EfficientCDaR
280+
>>> # ef = EfficientCDaR(mu, returns)
281+
>>> # weights = ef.efficient_risk(target_cdar=0.10)
178282
"""
179283

180284
update_existing_parameter = self.is_parameter_defined("target_cdar")
@@ -202,6 +306,12 @@ def efficient_risk(self, target_cdar, market_neutral=False):
202306
return self._solve_cvxpy_opt_problem()
203307

204308
def _add_cdar_constraints(self) -> None:
309+
"""
310+
Add the CDaR-specific constraints to the optimization problem.
311+
312+
This method adds the constraints required for CDaR optimization
313+
to the CVXPY problem.
314+
"""
205315
self.add_constraint(lambda _: self._z >= self._u[1:] - self._alpha)
206316
self.add_constraint(
207317
lambda w: self._u[1:] >= self._u[:-1] - self.returns.values @ w
@@ -212,14 +322,32 @@ def _add_cdar_constraints(self) -> None:
212322

213323
def portfolio_performance(self, verbose=False):
214324
"""
215-
After optimising, calculate (and optionally print) the performance of the optimal
216-
portfolio, specifically: expected return, CDaR
217-
218-
:param verbose: whether performance should be printed, defaults to False
219-
:type verbose: bool, optional
220-
:raises ValueError: if weights have not been calculated yet
221-
:return: expected return, CDaR.
222-
:rtype: (float, float)
325+
Calculate the performance of the optimal portfolio.
326+
327+
After optimising, calculate (and optionally print) the performance
328+
of the optimal portfolio, specifically: expected return, CDaR.
329+
330+
Parameters
331+
----------
332+
verbose : bool, optional
333+
Whether performance should be printed, defaults to False.
334+
335+
Returns
336+
-------
337+
tuple
338+
A tuple of (expected return, CDaR).
339+
340+
Raises
341+
------
342+
ValueError
343+
If weights have not been calculated yet.
344+
345+
Examples
346+
--------
347+
>>> from pypfopt import EfficientCDaR
348+
>>> # ef = EfficientCDaR(mu, returns)
349+
>>> # ef.min_cdar()
350+
>>> # mu, cdar = ef.portfolio_performance(verbose=True)
223351
"""
224352
mu = objective_functions.portfolio_return(
225353
self.weights, self.expected_returns, negative=False

0 commit comments

Comments
 (0)