mirror of
https://github.com/robertmartin8/PyPortfolioOpt.git
synced 2022-11-27 18:02:41 +03:00
Hidden API for changing solver
This commit is contained in:
@@ -70,8 +70,6 @@ Basic Usage
|
|||||||
.. automodule:: pypfopt.efficient_frontier
|
.. automodule:: pypfopt.efficient_frontier
|
||||||
|
|
||||||
.. autoclass:: EfficientFrontier
|
.. autoclass:: EfficientFrontier
|
||||||
:members:
|
|
||||||
:exclude-members: custom_objective
|
|
||||||
|
|
||||||
.. automethod:: __init__
|
.. automethod:: __init__
|
||||||
|
|
||||||
@@ -85,6 +83,7 @@ Basic Usage
|
|||||||
If you want to generate short-only portfolios, there is a quick hack. Multiply
|
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.
|
your expected returns by -1, then optimise a long-only portfolio.
|
||||||
|
|
||||||
|
.. automethod:: min_volatility
|
||||||
|
|
||||||
.. automethod:: max_sharpe
|
.. automethod:: max_sharpe
|
||||||
|
|
||||||
@@ -110,6 +109,8 @@ Basic Usage
|
|||||||
:py:meth:`efficient_return`, the optimiser will fail silently and return
|
:py:meth:`efficient_return`, the optimiser will fail silently and return
|
||||||
weird weights. *Caveat emptor* applies!
|
weird weights. *Caveat emptor* applies!
|
||||||
|
|
||||||
|
.. automethod:: efficient_return
|
||||||
|
|
||||||
.. automethod:: portfolio_performance
|
.. automethod:: portfolio_performance
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
@@ -123,6 +124,12 @@ Basic Usage
|
|||||||
weights, expected_returns, cov_matrix, verbose=True, 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 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
|
Adding objectives and constraints
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class BaseConvexOptimizer(BaseOptimizer):
|
|||||||
- ``n_assets`` - int
|
- ``n_assets`` - int
|
||||||
- ``tickers`` - str list
|
- ``tickers`` - str list
|
||||||
- ``weights`` - np.ndarray
|
- ``weights`` - np.ndarray
|
||||||
|
- ``solver`` - str
|
||||||
|
|
||||||
Public methods:
|
Public methods:
|
||||||
|
|
||||||
@@ -144,6 +145,8 @@ class BaseConvexOptimizer(BaseOptimizer):
|
|||||||
self._upper_bounds = None
|
self._upper_bounds = None
|
||||||
self._map_bounds_to_constraints(weight_bounds)
|
self._map_bounds_to_constraints(weight_bounds)
|
||||||
|
|
||||||
|
self.solver = None
|
||||||
|
|
||||||
def _map_bounds_to_constraints(self, test_bounds):
|
def _map_bounds_to_constraints(self, test_bounds):
|
||||||
"""
|
"""
|
||||||
Process input bounds into a form acceptable by cvxpy and add to the constraints list.
|
Process input bounds into a form acceptable by cvxpy and add to the constraints list.
|
||||||
@@ -193,7 +196,11 @@ class BaseConvexOptimizer(BaseOptimizer):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
|
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
|
||||||
opt.solve()
|
|
||||||
|
if self.solver is not None:
|
||||||
|
opt.solve(solver=self.solver, verbose=True)
|
||||||
|
else:
|
||||||
|
opt.solve()
|
||||||
except (TypeError, cp.DCPError):
|
except (TypeError, cp.DCPError):
|
||||||
raise exceptions.OptimizationError
|
raise exceptions.OptimizationError
|
||||||
if opt.status != "optimal":
|
if opt.status != "optimal":
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
|||||||
- ``bounds`` - float tuple OR (float tuple) list
|
- ``bounds`` - float tuple OR (float tuple) list
|
||||||
- ``cov_matrix`` - np.ndarray
|
- ``cov_matrix`` - np.ndarray
|
||||||
- ``expected_returns`` - np.ndarray
|
- ``expected_returns`` - np.ndarray
|
||||||
|
- ``solver`` - str
|
||||||
|
|
||||||
- Output: ``weights`` - np.ndarray
|
- Output: ``weights`` - np.ndarray
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
|||||||
:return: asset weights for the efficient risk portfolio
|
:return: asset weights for the efficient risk portfolio
|
||||||
:rtype: dict
|
: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")
|
raise ValueError("target_volatility should be a positive float")
|
||||||
|
|
||||||
self._objective = objective_functions.portfolio_return(
|
self._objective = objective_functions.portfolio_return(
|
||||||
|
|||||||
@@ -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():
|
def test_min_volatility_no_rets():
|
||||||
# Should work with no rets, see issue #82
|
# Should work with no rets, see issue #82
|
||||||
df = get_data()
|
df = get_data()
|
||||||
|
|||||||
Reference in New Issue
Block a user