Hidden API for changing solver

This commit is contained in:
robertmartin8
2020-06-07 13:22:16 +08:00
parent 481b43b546
commit 8d991da378
4 changed files with 41 additions and 5 deletions

View File

@@ -70,8 +70,6 @@ Basic Usage
.. automodule:: pypfopt.efficient_frontier
.. autoclass:: EfficientFrontier
:members:
:exclude-members: custom_objective
.. automethod:: __init__
@@ -85,6 +83,7 @@ Basic Usage
If you want to generate short-only portfolios, there is a quick hack. Multiply
your expected returns by -1, then optimise a long-only portfolio.
.. automethod:: min_volatility
.. automethod:: max_sharpe
@@ -110,6 +109,8 @@ Basic Usage
:py:meth:`efficient_return`, the optimiser will fail silently and return
weird weights. *Caveat emptor* applies!
.. automethod:: efficient_return
.. automethod:: portfolio_performance
.. tip::
@@ -123,6 +124,12 @@ Basic Usage
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 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>`_.
Adding objectives and constraints
=================================

View File

@@ -113,6 +113,7 @@ class BaseConvexOptimizer(BaseOptimizer):
- ``n_assets`` - int
- ``tickers`` - str list
- ``weights`` - np.ndarray
- ``solver`` - str
Public methods:
@@ -144,6 +145,8 @@ class BaseConvexOptimizer(BaseOptimizer):
self._upper_bounds = None
self._map_bounds_to_constraints(weight_bounds)
self.solver = None
def _map_bounds_to_constraints(self, test_bounds):
"""
Process input bounds into a form acceptable by cvxpy and add to the constraints list.
@@ -193,6 +196,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=True)
else:
opt.solve()
except (TypeError, cp.DCPError):
raise exceptions.OptimizationError

View File

@@ -28,7 +28,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
- ``bounds`` - float tuple OR (float tuple) list
- ``cov_matrix`` - np.ndarray
- ``expected_returns`` - np.ndarray
- ``solver`` - str
- Output: ``weights`` - np.ndarray
@@ -265,7 +265,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
:return: asset weights for the efficient risk portfolio
:rtype: dict
"""
if not isinstance(target_volatility, float) or target_volatility < 0:
if not isinstance(target_volatility, (float, int)) or target_volatility < 0:
raise ValueError("target_volatility should be a positive float")
self._objective = objective_functions.portfolio_return(

View File

@@ -65,6 +65,28 @@ def test_min_volatility():
)
def test_min_volatility_different_solver():
ef = setup_efficient_frontier()
ef.solver = "ECOS"
w = ef.min_volatility()
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
assert all([i >= 0 for i in w.values()])
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"
w = ef.min_volatility()
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-5)
ef = setup_efficient_frontier()
ef.solver = "SCS"
w = ef.min_volatility()
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-3)
def test_min_volatility_no_rets():
# Should work with no rets, see issue #82
df = get_data()