mirror of
https://github.com/robertmartin8/PyPortfolioOpt.git
synced 2022-11-27 18:02:41 +03:00
Make Verbose Option a Hidden API
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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();"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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();"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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();"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -121,14 +121,14 @@ Basic Usage
|
||||
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::
|
||||
|
||||
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 <https://www.cvxpy.org/tutorial/advanced/index.html#choosing-a-solver>`_.
|
||||
choose the solver, simply assign ``ef.solver = "ECOS"`` prior to calling the actual optimisation
|
||||
method. You can choose from any of the `supported solvers <https://www.cvxpy.org/tutorial/advanced/index.html#choosing-a-solver>`_.
|
||||
|
||||
Adding objectives and constraints
|
||||
=================================
|
||||
|
||||
@@ -178,7 +178,8 @@ This prints::
|
||||
If we want to know the expected performance of the portfolio with optimal
|
||||
weights ``w``, we can use the :py:meth:`portfolio_performance` method::
|
||||
|
||||
ef.portfolio_performance(verbose=True)
|
||||
ef.verbose = True
|
||||
ef.portfolio_performance()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
12
examples.py
12
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
|
||||
|
||||
"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user