diff --git a/README.md b/README.md
index c31e381..3278e52 100755
--- a/README.md
+++ b/README.md
@@ -132,7 +132,8 @@ raw_weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
ef.save_weights_to_file("weights.csv") # saves to file
print(cleaned_weights)
-ef.portfolio_performance(verbose=True)
+ef.verbose = True
+ef.portfolio_performance()
```
This outputs the following weights:
diff --git a/cookbook/2-Mean-Variance-Optimisation.ipynb b/cookbook/2-Mean-Variance-Optimisation.ipynb
index 9d5e4db..c7d386c 100644
--- a/cookbook/2-Mean-Variance-Optimisation.ipynb
+++ b/cookbook/2-Mean-Variance-Optimisation.ipynb
@@ -930,7 +930,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -1255,7 +1256,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -1417,7 +1419,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -1491,7 +1494,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -1562,4 +1566,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb b/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb
index 361f57c..5d2e5b9 100644
--- a/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb
+++ b/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb
@@ -556,7 +556,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -685,7 +686,8 @@
}
],
"source": [
- "ef.portfolio_performance(verbose=True);"
+ "ef.verbose = True\n",
+ "ef.portfolio_performance();"
]
},
{
@@ -1165,4 +1167,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/cookbook/HierarchicalRiskParity.ipynb b/cookbook/HierarchicalRiskParity.ipynb
index 9115cd1..ad554eb 100644
--- a/cookbook/HierarchicalRiskParity.ipynb
+++ b/cookbook/HierarchicalRiskParity.ipynb
@@ -32,7 +32,8 @@
"returns = risk_models.returns_from_prices(df)\n",
"hrp = HRPOpt(returns)\n",
"weights = hrp.optimize()\n",
- "hrp.portfolio_performance(verbose=True);"
+ "hrp.verbose = True\n",
+ "hrp.portfolio_performance();"
]
},
{
@@ -85,4 +86,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
-}
+}
\ No newline at end of file
diff --git a/docs/EfficientFrontier.rst b/docs/EfficientFrontier.rst
index 8b01eeb..b941cb2 100644
--- a/docs/EfficientFrontier.rst
+++ b/docs/EfficientFrontier.rst
@@ -26,7 +26,7 @@ the main difficulty is inputting our specific problem into a solver.
PyPortfolioOpt aims to do the hard work for you, allowing for one-liners like ``ef.min_volatility()``
to generate a portfolio that minimises the volatility, while at the same time allowing for more
-complex problems to be built up from modular units. This is all possible thanks to
+complex problems to be built up from modular units. This is all possible thanks to
`cvxpy `_, the *fantastic* python-embedded modelling
language for convex optimisation upon which PyPortfolioOpt's efficient frontier functionality lies.
@@ -47,7 +47,7 @@ the optimisation objective, and the optimisation constraints. For example, the c
optimisation problem is to **minimise risk** subject to a **return constraint** (i.e the portfolio
must return more than a certain amount). From an implementation perspective, however, there is
not much difference between an objective and a constraint. Consider a similar problem, which is to
-**maximize return** subject to a **risk constraint** -- now, the role of risk and return have swapped.
+**maximize return** subject to a **risk constraint** -- now, the role of risk and return have swapped.
To that end, PyPortfolioOpt defines an :py:mod:`objective_functions` module that contains objective functions
(which can also act as constraints, as we have just seen). The actual optimisation occurs in the :py:class:`efficient_frontier.EfficientFrontier` class.
@@ -61,7 +61,7 @@ For example, adding a regularisation objective (explained below) to a minimum vo
ef.min_volatility() # find the portfolio that minimises volatility and L2_reg
.. tip::
-
+
If you would like to plot the efficient frontier, take a look at the :ref:`plotting` module.
Basic Usage
@@ -90,7 +90,7 @@ Basic Usage
.. caution::
Because ``max_sharpe()`` makes a variable substitution, additional objectives may
- not work as intended.
+ not work as intended.
.. automethod:: max_quadratic_utility
@@ -116,19 +116,19 @@ Basic Usage
.. tip::
If you would like to use the ``portfolio_performance`` function independently of any
- optimiser (e.g for debugging purposes), you can use::
+ optimiser (e.g for debugging purposes), you can use::
from pypfopt import base_optimizer
base_optimizer.portfolio_performance(
- weights, expected_returns, cov_matrix, verbose=True, risk_free_rate=0.02
+ weights, expected_returns, cov_matrix, risk_free_rate=0.02
)
-.. note::
+.. note::
PyPortfolioOpt defers to cvxpy's default choice of solver. If you would like to explicitly
- choose the solver and see verbose output, simply assign ``ef.solver = "ECOS"`` prior to calling
- the actual optimisation method. You can choose from any of the `supported solvers `_.
+ choose the solver, simply assign ``ef.solver = "ECOS"`` prior to calling the actual optimisation
+ method. You can choose from any of the `supported solvers `_.
Adding objectives and constraints
=================================
@@ -213,7 +213,7 @@ different API. For examples, check out this `cookbook recipe `_.
@@ -131,7 +131,8 @@ that's fine too::
# Optimise for maximal Sharpe ratio
ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()
- ef.portfolio_performance(verbose=True)
+ ef.verbose = True
+ ef.portfolio_performance()
This outputs the following:
@@ -159,7 +160,7 @@ Contents
.. toctree::
:caption: Other information
-
+
Roadmap
Contributing
About
diff --git a/examples.py b/examples.py
index e8cfb93..da22fd6 100644
--- a/examples.py
+++ b/examples.py
@@ -28,7 +28,8 @@ def deviation_risk_parity(w, cov_matrix):
ef = EfficientFrontier(mu, S)
weights = ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)
-ef.portfolio_performance(verbose=True)
+ef.verbose = True
+ef.portfolio_performance()
"""
Expected annual return: 22.9%
@@ -82,7 +83,8 @@ rets = bl.bl_returns()
ef = EfficientFrontier(rets, S)
ef.max_sharpe()
print(ef.clean_weights())
-ef.portfolio_performance(verbose=True)
+ef.verbose = True
+ef.portfolio_performance()
"""
{'GOOG': 0.2015,
@@ -115,7 +117,8 @@ Sharpe Ratio: 0.46
# Hierarchical risk parity
hrp = HRPOpt(returns)
weights = hrp.optimize()
-hrp.portfolio_performance(verbose=True)
+hrp.verbose = True
+hrp.portfolio_performance()
print(weights)
plotting.plot_dendrogram(hrp) # to plot dendrogram
@@ -150,7 +153,8 @@ Sharpe Ratio: 0.66
# Crticial Line Algorithm
cla = CLA(mu, S)
print(cla.max_sharpe())
-cla.portfolio_performance(verbose=True)
+cla.verbose = True
+cla.portfolio_performance()
plotting.plot_efficient_frontier(cla) # to plot
"""
diff --git a/pypfopt/base_optimizer.py b/pypfopt/base_optimizer.py
index 1ea69b3..dafffbc 100644
--- a/pypfopt/base_optimizer.py
+++ b/pypfopt/base_optimizer.py
@@ -47,6 +47,7 @@ class BaseOptimizer:
self.tickers = tickers
# Outputs
self.weights = None
+ self.verbose = False
def _make_output_weights(self, weights=None):
"""
@@ -201,22 +202,20 @@ class BaseConvexOptimizer(BaseOptimizer):
self._constraints.append(self._w >= self._lower_bounds)
self._constraints.append(self._w <= self._upper_bounds)
- def _solve_cvxpy_opt_problem(self, verbose=False):
+ def _solve_cvxpy_opt_problem(self):
"""
Helper method to solve the cvxpy problem and check output,
once objectives and constraints have been defined
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:raises exceptions.OptimizationError: if problem is not solvable by cvxpy
"""
try:
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
if self.solver is not None:
- opt.solve(solver=self.solver, verbose=verbose)
+ opt.solve(solver=self.solver, verbose=self.verbose)
else:
- opt.solve(verbose=verbose)
+ opt.solve(verbose=self.verbose)
except (TypeError, cp.DCPError):
raise exceptions.OptimizationError
@@ -299,7 +298,7 @@ class BaseConvexOptimizer(BaseOptimizer):
is_sector = [sector_mapper[t] == sector for t in self.tickers]
self._constraints.append(cp.sum(self._w[is_sector]) >= sector_lower[sector])
- def convex_objective(self, custom_objective, weights_sum_to_one=True, verbose=False, **kwargs):
+ def convex_objective(self, custom_objective, weights_sum_to_one=True, **kwargs):
"""
Optimise a custom convex objective function. Constraints should be added with
``ef.add_constraint()``. Optimiser arguments must be passed as keyword-args. Example::
@@ -316,8 +315,6 @@ class BaseConvexOptimizer(BaseOptimizer):
:type custom_objective: function with signature (cp.Variable, `**kwargs`) -> cp.Expression
:param weights_sum_to_one: whether to add the default objective, defaults to True
:type weights_sum_to_one: bool, optional
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:raises OptimizationError: if the objective is nonconvex or constraints nonlinear.
:return: asset weights for the efficient risk portfolio
:rtype: OrderedDict
@@ -331,7 +328,7 @@ class BaseConvexOptimizer(BaseOptimizer):
if weights_sum_to_one:
self._constraints.append(cp.sum(self._w) == 1)
- return self._solve_cvxpy_opt_problem(verbose=verbose)
+ return self._solve_cvxpy_opt_problem()
def nonconvex_objective(
self,
diff --git a/pypfopt/black_litterman.py b/pypfopt/black_litterman.py
index 7e9299a..9bb0594 100644
--- a/pypfopt/black_litterman.py
+++ b/pypfopt/black_litterman.py
@@ -451,14 +451,12 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
"""
return self.bl_weights(risk_aversion)
- def portfolio_performance(self, verbose=False, risk_free_rate=0.02):
+ def portfolio_performance(self, risk_free_rate=0.02):
"""
After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
This method uses the BL posterior returns and covariance matrix.
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02.
The period of the risk-free rate should correspond to the
frequency of expected returns.
@@ -473,6 +471,6 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
self.weights,
self.posterior_rets,
self.posterior_cov,
- verbose,
+ self.verbose,
risk_free_rate,
)
diff --git a/pypfopt/cla.py b/pypfopt/cla.py
index 58710ae..952d604 100644
--- a/pypfopt/cla.py
+++ b/pypfopt/cla.py
@@ -445,13 +445,11 @@ class CLA(base_optimizer.BaseOptimizer):
# Overrides parent method since set_weights does nothing.
raise NotImplementedError("set_weights does nothing for CLA")
- def portfolio_performance(self, verbose=False, risk_free_rate=0.02):
+ def portfolio_performance(self, risk_free_rate=0.02):
"""
After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02
:type risk_free_rate: float, optional
:raises ValueError: if weights have not been calculated yet
@@ -462,6 +460,6 @@ class CLA(base_optimizer.BaseOptimizer):
self.weights,
self.expected_returns,
self.cov_matrix,
- verbose,
+ self.verbose,
risk_free_rate,
)
diff --git a/pypfopt/efficient_frontier.py b/pypfopt/efficient_frontier.py
index 97f2f68..d8f5068 100644
--- a/pypfopt/efficient_frontier.py
+++ b/pypfopt/efficient_frontier.py
@@ -134,7 +134,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
del self._constraints[0]
del self._constraints[0]
- def min_volatility(self, verbose=False):
+ def min_volatility(self):
"""
Minimise volatility.
@@ -149,9 +149,9 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
self._constraints.append(cp.sum(self._w) == 1)
- return self._solve_cvxpy_opt_problem(verbose=verbose)
+ return self._solve_cvxpy_opt_problem()
- def max_sharpe(self, risk_free_rate=0.02, verbose=False):
+ def max_sharpe(self, risk_free_rate=0.02):
"""
Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio,
as it is the portfolio for which the capital market line is tangent to the efficient frontier.
@@ -209,12 +209,12 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
k >= 0,
] + new_constraints
- self._solve_cvxpy_opt_problem(verbose=verbose)
+ self._solve_cvxpy_opt_problem()
# Inverse-transform
self.weights = (self._w.value / k.value).round(16) + 0.0
return self._make_output_weights()
- def max_quadratic_utility(self, risk_aversion=1, market_neutral=False, verbose=False):
+ def max_quadratic_utility(self, risk_aversion=1, market_neutral=False):
r"""
Maximise the given quadratic utility, i.e:
@@ -246,9 +246,9 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
else:
self._constraints.append(cp.sum(self._w) == 1)
- return self._solve_cvxpy_opt_problem(verbose=verbose)
+ return self._solve_cvxpy_opt_problem()
- def efficient_risk(self, target_volatility, market_neutral=False, verbose=False):
+ def efficient_risk(self, target_volatility, market_neutral=False):
"""
Maximise return for a target risk. The resulting portfolio will have a volatility
less than the target (but not guaranteed to be equal).
@@ -285,9 +285,9 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
else:
self._constraints.append(cp.sum(self._w) == 1)
- return self._solve_cvxpy_opt_problem(verbose=verbose)
+ return self._solve_cvxpy_opt_problem()
- def efficient_return(self, target_return, market_neutral=False, verbose=False):
+ def efficient_return(self, target_return, market_neutral=False):
"""
Calculate the 'Markowitz portfolio', minimising volatility for a given target return.
@@ -331,15 +331,13 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
else:
self._constraints.append(cp.sum(self._w) == 1)
- return self._solve_cvxpy_opt_problem(verbose=verbose)
+ return self._solve_cvxpy_opt_problem()
- def portfolio_performance(self, verbose=False, risk_free_rate=0.02):
+ def portfolio_performance(self, risk_free_rate=0.02):
"""
After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02.
The period of the risk-free rate should correspond to the
frequency of expected returns.
@@ -352,6 +350,6 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
self.weights,
self.expected_returns,
self.cov_matrix,
- verbose,
+ self.verbose,
risk_free_rate,
)
diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py
index f6611c2..0031da1 100644
--- a/pypfopt/hierarchical_portfolio.py
+++ b/pypfopt/hierarchical_portfolio.py
@@ -167,14 +167,12 @@ class HRPOpt(base_optimizer.BaseOptimizer):
self.set_weights(weights)
return weights
- def portfolio_performance(self, verbose=False, risk_free_rate=0.02, frequency=252):
+ def portfolio_performance(self, risk_free_rate=0.02, frequency=252):
"""
After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio
assuming returns are daily
- :param verbose: whether performance should be printed, defaults to False
- :type verbose: bool, optional
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02.
The period of the risk-free rate should correspond to the
frequency of expected returns.
@@ -194,5 +192,5 @@ class HRPOpt(base_optimizer.BaseOptimizer):
mu = self.returns.mean() * frequency
return base_optimizer.portfolio_performance(
- self.weights, mu, cov, verbose, risk_free_rate
+ self.weights, mu, cov, self.verbose, risk_free_rate
)
diff --git a/tests/test_base_optimizer.py b/tests/test_base_optimizer.py
index 085daf6..eee8fa6 100644
--- a/tests/test_base_optimizer.py
+++ b/tests/test_base_optimizer.py
@@ -214,6 +214,7 @@ def assert_verbose_option(optimize_for_method, *args, solver=None):
# using a random number for `verbose` simply to test that what is received
# by the method is passed on to Problem#solve
verbose=random()
+ ef.verbose = verbose
with patch("cvxpy.Problem.solve") as mock:
with pytest.raises(exceptions.OptimizationError):
@@ -221,7 +222,7 @@ def assert_verbose_option(optimize_for_method, *args, solver=None):
# passes the verbose kwarg on to Problem#solve.
# mocking Problem#solve causes EfficientFrontier#min_volatility to
# raise an error, but it is safe to ignore it
- optimize_for_method(ef, *args, verbose=verbose)
+ optimize_for_method(ef, *args)
# mock.assert_called_with(verbose=verbose) doesn't work here because
# sometimes the mock is called with more kwargs. all we want to know is
diff --git a/tests/test_discrete_allocation.py b/tests/test_discrete_allocation.py
index 60214b6..155717e 100644
--- a/tests/test_discrete_allocation.py
+++ b/tests/test_discrete_allocation.py
@@ -208,7 +208,7 @@ def test_lp_allocation_rmse_error():
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices)
da.lp_portfolio()
- np.testing.assert_almost_equal(da._allocation_rmse_error(verbose=False), 0.017070218149194846)
+ np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.017070218149194846)
def test_lp_portfolio_allocation_short():
@@ -310,18 +310,18 @@ def test_rmse_decreases_with_value():
da1 = DiscreteAllocation(w, latest_prices, total_portfolio_value=10000)
da1.greedy_portfolio()
- rmse1 = da1._allocation_rmse_error(verbose=False)
+ rmse1 = da1._allocation_rmse_error()
da2 = DiscreteAllocation(w, latest_prices, total_portfolio_value=100000)
da2.greedy_portfolio()
- rmse2 = da2._allocation_rmse_error(verbose=False)
+ rmse2 = da2._allocation_rmse_error()
assert rmse2 < rmse1
da3 = DiscreteAllocation(w, latest_prices, total_portfolio_value=10000)
da3.lp_portfolio()
- rmse3 = da3._allocation_rmse_error(verbose=False)
+ rmse3 = da3._allocation_rmse_error()
da4 = DiscreteAllocation(w, latest_prices, total_portfolio_value=30000)
da4.lp_portfolio()
- rmse4 = da4._allocation_rmse_error(verbose=False)
+ rmse4 = da4._allocation_rmse_error()
assert rmse4 < rmse3