mirror of
https://github.com/robertmartin8/PyPortfolioOpt.git
synced 2022-11-27 18:02:41 +03:00
Promote Solver & Verbose to Constructor Options; internals are now private
This commit is contained in:
@@ -132,8 +132,7 @@ raw_weights = ef.max_sharpe()
|
||||
cleaned_weights = ef.clean_weights()
|
||||
ef.save_weights_to_file("weights.csv") # saves to file
|
||||
print(cleaned_weights)
|
||||
ef.verbose = True
|
||||
ef.portfolio_performance()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
```
|
||||
|
||||
This outputs the following weights:
|
||||
|
||||
@@ -930,8 +930,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1256,8 +1255,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1419,8 +1417,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1494,8 +1491,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -556,8 +556,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -686,8 +685,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ef.verbose = True\n",
|
||||
"ef.portfolio_performance();"
|
||||
"ef.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
"returns = risk_models.returns_from_prices(df)\n",
|
||||
"hrp = HRPOpt(returns)\n",
|
||||
"weights = hrp.optimize()\n",
|
||||
"hrp.verbose = True\n",
|
||||
"hrp.portfolio_performance();"
|
||||
"hrp.portfolio_performance(verbose=True);"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -121,14 +121,14 @@ Basic Usage
|
||||
from pypfopt import base_optimizer
|
||||
|
||||
base_optimizer.portfolio_performance(
|
||||
weights, expected_returns, cov_matrix, risk_free_rate=0.02
|
||||
weights, expected_returns, cov_matrix, verbose=True, risk_free_rate=0.02
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
PyPortfolioOpt defers to cvxpy's default choice of solver. If you would like to explicitly
|
||||
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>`_.
|
||||
choose the solver, simply pass the optional ``solver = "ECOS"`` kwarg to the constructor.
|
||||
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,8 +178,7 @@ 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.verbose = True
|
||||
ef.portfolio_performance()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
|
||||
@@ -131,8 +131,7 @@ that's fine too::
|
||||
# Optimise for maximal Sharpe ratio
|
||||
ef = EfficientFrontier(mu, S)
|
||||
weights = ef.max_sharpe()
|
||||
ef.verbose = True
|
||||
ef.portfolio_performance()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
|
||||
This outputs the following:
|
||||
|
||||
|
||||
12
examples.py
12
examples.py
@@ -28,8 +28,7 @@ def deviation_risk_parity(w, cov_matrix):
|
||||
|
||||
ef = EfficientFrontier(mu, S)
|
||||
weights = ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)
|
||||
ef.verbose = True
|
||||
ef.portfolio_performance()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
|
||||
"""
|
||||
Expected annual return: 22.9%
|
||||
@@ -83,8 +82,7 @@ rets = bl.bl_returns()
|
||||
ef = EfficientFrontier(rets, S)
|
||||
ef.max_sharpe()
|
||||
print(ef.clean_weights())
|
||||
ef.verbose = True
|
||||
ef.portfolio_performance()
|
||||
ef.portfolio_performance(verbose=True)
|
||||
|
||||
"""
|
||||
{'GOOG': 0.2015,
|
||||
@@ -117,8 +115,7 @@ Sharpe Ratio: 0.46
|
||||
# Hierarchical risk parity
|
||||
hrp = HRPOpt(returns)
|
||||
weights = hrp.optimize()
|
||||
hrp.verbose = True
|
||||
hrp.portfolio_performance()
|
||||
hrp.portfolio_performance(verbose=True)
|
||||
print(weights)
|
||||
plotting.plot_dendrogram(hrp) # to plot dendrogram
|
||||
|
||||
@@ -153,8 +150,7 @@ Sharpe Ratio: 0.66
|
||||
# Crticial Line Algorithm
|
||||
cla = CLA(mu, S)
|
||||
print(cla.max_sharpe())
|
||||
cla.verbose = True
|
||||
cla.portfolio_performance()
|
||||
cla.portfolio_performance(verbose=True)
|
||||
plotting.plot_efficient_frontier(cla) # to plot
|
||||
|
||||
"""
|
||||
|
||||
@@ -47,7 +47,6 @@ class BaseOptimizer:
|
||||
self.tickers = tickers
|
||||
# Outputs
|
||||
self.weights = None
|
||||
self.verbose = False
|
||||
|
||||
def _make_output_weights(self, weights=None):
|
||||
"""
|
||||
@@ -142,12 +141,16 @@ class BaseConvexOptimizer(BaseOptimizer):
|
||||
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
|
||||
"""
|
||||
|
||||
def __init__(self, n_assets, tickers=None, weight_bounds=(0, 1)):
|
||||
def __init__(self, n_assets, tickers=None, weight_bounds=(0, 1), solver=None, verbose=False):
|
||||
"""
|
||||
:param weight_bounds: minimum and maximum weight of each asset OR single min/max pair
|
||||
if all identical, defaults to (0, 1). Must be changed to (-1, 1)
|
||||
for portfolios with shorting.
|
||||
:type weight_bounds: tuple OR tuple list, optional
|
||||
:param solver: name of solver. list available solvers with: `cvxpy.installed_solvers()`
|
||||
:type solver: str, optional (see cvxpy.Problem#_solve for default. spoiler: it's ECOS)
|
||||
:param verbose: whether performance and debugging info should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
"""
|
||||
super().__init__(n_assets, tickers)
|
||||
|
||||
@@ -160,7 +163,8 @@ class BaseConvexOptimizer(BaseOptimizer):
|
||||
self._upper_bounds = None
|
||||
self._map_bounds_to_constraints(weight_bounds)
|
||||
|
||||
self.solver = None
|
||||
self._solver = solver
|
||||
self._verbose = verbose
|
||||
|
||||
def _map_bounds_to_constraints(self, test_bounds):
|
||||
"""
|
||||
@@ -212,10 +216,10 @@ class BaseConvexOptimizer(BaseOptimizer):
|
||||
try:
|
||||
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
|
||||
|
||||
if self.solver is not None:
|
||||
opt.solve(solver=self.solver, verbose=self.verbose)
|
||||
if self._solver is not None:
|
||||
opt.solve(solver=self._solver, verbose=self._verbose)
|
||||
else:
|
||||
opt.solve(verbose=self.verbose)
|
||||
opt.solve(verbose=self._verbose)
|
||||
except (TypeError, cp.DCPError):
|
||||
raise exceptions.OptimizationError
|
||||
|
||||
|
||||
@@ -451,7 +451,7 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
|
||||
"""
|
||||
return self.bl_weights(risk_aversion)
|
||||
|
||||
def portfolio_performance(self, risk_free_rate=0.02):
|
||||
def portfolio_performance(self, risk_free_rate=0.02, verbose=False):
|
||||
"""
|
||||
After optimising, calculate (and optionally print) the performance of the optimal
|
||||
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
|
||||
@@ -461,6 +461,8 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
|
||||
The period of the risk-free rate should correspond to the
|
||||
frequency of expected returns.
|
||||
:type risk_free_rate: float, optional
|
||||
:param verbose: whether performance should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
:raises ValueError: if weights have not been calcualted yet
|
||||
:return: expected return, volatility, Sharpe ratio.
|
||||
:rtype: (float, float, float)
|
||||
@@ -471,6 +473,6 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
|
||||
self.weights,
|
||||
self.posterior_rets,
|
||||
self.posterior_cov,
|
||||
self.verbose,
|
||||
verbose,
|
||||
risk_free_rate,
|
||||
)
|
||||
|
||||
@@ -445,13 +445,15 @@ 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, risk_free_rate=0.02):
|
||||
def portfolio_performance(self, risk_free_rate=0.02, verbose=False):
|
||||
"""
|
||||
After optimising, calculate (and optionally print) the performance of the optimal
|
||||
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
|
||||
|
||||
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02
|
||||
:type risk_free_rate: float, optional
|
||||
:param verbose: whether performance should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
:raises ValueError: if weights have not been calculated yet
|
||||
:return: expected return, volatility, Sharpe ratio.
|
||||
:rtype: (float, float, float)
|
||||
@@ -460,6 +462,6 @@ class CLA(base_optimizer.BaseOptimizer):
|
||||
self.weights,
|
||||
self.expected_returns,
|
||||
self.cov_matrix,
|
||||
self.verbose,
|
||||
verbose,
|
||||
risk_free_rate,
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
|
||||
"""
|
||||
|
||||
def __init__(self, expected_returns, cov_matrix, weight_bounds=(0, 1), gamma=0):
|
||||
def __init__(self, expected_returns, cov_matrix, weight_bounds=(0, 1), gamma=0, solver=None, verbose=False):
|
||||
"""
|
||||
:param expected_returns: expected returns for each asset. Can be None if
|
||||
optimising for volatility only (but not recommended).
|
||||
@@ -68,6 +68,10 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
:param gamma: L2 regularisation parameter, defaults to 0. Increase if you want more
|
||||
non-negligible weights
|
||||
:type gamma: float, optional
|
||||
:param solver: name of solver. list available solvers with: `cvxpy.installed_solvers()`
|
||||
:type solver: str
|
||||
:param verbose: whether performance and debugging info should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
:raises TypeError: if ``expected_returns`` is not a series, list or array
|
||||
:raises TypeError: if ``cov_matrix`` is not a dataframe or array
|
||||
"""
|
||||
@@ -89,7 +93,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
if cov_matrix.shape != (len(expected_returns), len(expected_returns)):
|
||||
raise ValueError("Covariance matrix does not match expected returns")
|
||||
|
||||
super().__init__(len(tickers), tickers, weight_bounds)
|
||||
super().__init__(len(tickers), tickers, weight_bounds, solver=solver, verbose=verbose)
|
||||
|
||||
@staticmethod
|
||||
def _validate_expected_returns(expected_returns):
|
||||
@@ -333,7 +337,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
|
||||
return self._solve_cvxpy_opt_problem()
|
||||
|
||||
def portfolio_performance(self, risk_free_rate=0.02):
|
||||
def portfolio_performance(self, risk_free_rate=0.02, verbose=False):
|
||||
"""
|
||||
After optimising, calculate (and optionally print) the performance of the optimal
|
||||
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
|
||||
@@ -342,6 +346,8 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
The period of the risk-free rate should correspond to the
|
||||
frequency of expected returns.
|
||||
:type risk_free_rate: float, optional
|
||||
:param verbose: whether performance should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
:raises ValueError: if weights have not been calcualted yet
|
||||
:return: expected return, volatility, Sharpe ratio.
|
||||
:rtype: (float, float, float)
|
||||
@@ -350,6 +356,6 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
self.weights,
|
||||
self.expected_returns,
|
||||
self.cov_matrix,
|
||||
self.verbose,
|
||||
verbose,
|
||||
risk_free_rate,
|
||||
)
|
||||
|
||||
@@ -167,7 +167,7 @@ class HRPOpt(base_optimizer.BaseOptimizer):
|
||||
self.set_weights(weights)
|
||||
return weights
|
||||
|
||||
def portfolio_performance(self, risk_free_rate=0.02, frequency=252):
|
||||
def portfolio_performance(self, risk_free_rate=0.02, frequency=252, verbose=False):
|
||||
"""
|
||||
After optimising, calculate (and optionally print) the performance of the optimal
|
||||
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio
|
||||
@@ -180,7 +180,9 @@ class HRPOpt(base_optimizer.BaseOptimizer):
|
||||
:param frequency: number of time periods in a year, defaults to 252 (the number
|
||||
of trading days in a year)
|
||||
:type frequency: int, optional
|
||||
:raises ValueError: if weights have not been calcualted yet
|
||||
:param verbose: whether performance should be printed, defaults to False
|
||||
:type verbose: bool, optional
|
||||
:raises ValueError: if weights have not been calculated yet
|
||||
:return: expected return, volatility, Sharpe ratio.
|
||||
:rtype: (float, float, float)
|
||||
"""
|
||||
@@ -192,5 +194,5 @@ class HRPOpt(base_optimizer.BaseOptimizer):
|
||||
mu = self.returns.mean() * frequency
|
||||
|
||||
return base_optimizer.portfolio_performance(
|
||||
self.weights, mu, cov, self.verbose, risk_free_rate
|
||||
self.weights, mu, cov, verbose, risk_free_rate
|
||||
)
|
||||
|
||||
@@ -208,13 +208,11 @@ def test_save_weights_to_file():
|
||||
os.remove("tests/test.json")
|
||||
|
||||
def assert_verbose_option(optimize_for_method, *args, solver=None):
|
||||
ef = setup_efficient_frontier()
|
||||
ef.solver = solver
|
||||
|
||||
# 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
|
||||
|
||||
ef = setup_efficient_frontier(solver=solver, verbose=verbose)
|
||||
|
||||
with patch("cvxpy.Problem.solve") as mock:
|
||||
with pytest.raises(exceptions.OptimizationError):
|
||||
|
||||
@@ -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(), 0.017070218149194846)
|
||||
np.testing.assert_almost_equal(da._allocation_rmse_error(verbose=False), 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()
|
||||
rmse1 = da1._allocation_rmse_error(verbose=False)
|
||||
da2 = DiscreteAllocation(w, latest_prices, total_portfolio_value=100000)
|
||||
da2.greedy_portfolio()
|
||||
rmse2 = da2._allocation_rmse_error()
|
||||
rmse2 = da2._allocation_rmse_error(verbose=False)
|
||||
assert rmse2 < rmse1
|
||||
|
||||
da3 = DiscreteAllocation(w, latest_prices, total_portfolio_value=10000)
|
||||
da3.lp_portfolio()
|
||||
rmse3 = da3._allocation_rmse_error()
|
||||
rmse3 = da3._allocation_rmse_error(verbose=False)
|
||||
da4 = DiscreteAllocation(w, latest_prices, total_portfolio_value=30000)
|
||||
da4.lp_portfolio()
|
||||
rmse4 = da4._allocation_rmse_error()
|
||||
rmse4 = da4._allocation_rmse_error(verbose=False)
|
||||
assert rmse4 < rmse3
|
||||
|
||||
|
||||
|
||||
@@ -66,8 +66,7 @@ def test_min_volatility():
|
||||
|
||||
|
||||
def test_min_volatility_different_solver():
|
||||
ef = setup_efficient_frontier()
|
||||
ef.solver = "ECOS"
|
||||
ef = setup_efficient_frontier(solver="ECOS")
|
||||
w = ef.min_volatility()
|
||||
assert isinstance(w, dict)
|
||||
assert set(w.keys()) == set(ef.tickers)
|
||||
@@ -76,13 +75,11 @@ def test_min_volatility_different_solver():
|
||||
test_performance = (0.179312, 0.159151, 1.001015)
|
||||
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-5)
|
||||
|
||||
ef = setup_efficient_frontier()
|
||||
ef.solver = "OSQP"
|
||||
ef = setup_efficient_frontier(solver="OSQP")
|
||||
w = ef.min_volatility()
|
||||
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-5)
|
||||
|
||||
ef = setup_efficient_frontier()
|
||||
ef.solver = "SCS"
|
||||
ef = setup_efficient_frontier(solver="SCS")
|
||||
w = ef.min_volatility()
|
||||
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-3)
|
||||
|
||||
|
||||
@@ -45,13 +45,13 @@ def get_market_caps():
|
||||
return mcaps
|
||||
|
||||
|
||||
def setup_efficient_frontier(data_only=False):
|
||||
def setup_efficient_frontier(data_only=False, solver=None, verbose=False):
|
||||
df = get_data()
|
||||
mean_return = expected_returns.mean_historical_return(df)
|
||||
sample_cov_matrix = risk_models.sample_cov(df)
|
||||
if data_only:
|
||||
return mean_return, sample_cov_matrix
|
||||
return EfficientFrontier(mean_return, sample_cov_matrix)
|
||||
return EfficientFrontier(mean_return, sample_cov_matrix, solver=solver, verbose=verbose)
|
||||
|
||||
|
||||
def setup_cla(data_only=False):
|
||||
|
||||
Reference in New Issue
Block a user