mirror of
https://github.com/robertmartin8/PyPortfolioOpt.git
synced 2022-11-27 18:02:41 +03:00
Allow Verbose as an Option to cvxpy.Problem#solve
This commit is contained in:
@@ -201,22 +201,25 @@ 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):
|
||||
def _solve_cvxpy_opt_problem(self, verbose=False):
|
||||
"""
|
||||
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=True)
|
||||
opt.solve(solver=self.solver, verbose=verbose)
|
||||
else:
|
||||
opt.solve()
|
||||
opt.solve(verbose=verbose)
|
||||
except (TypeError, cp.DCPError):
|
||||
raise exceptions.OptimizationError
|
||||
|
||||
if opt.status != "optimal":
|
||||
raise exceptions.OptimizationError
|
||||
self.weights = self._w.value.round(16) + 0.0 # +0.0 removes signed zero
|
||||
@@ -296,7 +299,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, **kwargs):
|
||||
def convex_objective(self, custom_objective, weights_sum_to_one=True, verbose=False, **kwargs):
|
||||
"""
|
||||
Optimise a custom convex objective function. Constraints should be added with
|
||||
``ef.add_constraint()``. Optimiser arguments must be passed as keyword-args. Example::
|
||||
@@ -313,6 +316,8 @@ 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
|
||||
@@ -326,7 +331,7 @@ class BaseConvexOptimizer(BaseOptimizer):
|
||||
if weights_sum_to_one:
|
||||
self._constraints.append(cp.sum(self._w) == 1)
|
||||
|
||||
return self._solve_cvxpy_opt_problem()
|
||||
return self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
|
||||
def nonconvex_objective(
|
||||
self,
|
||||
|
||||
@@ -134,7 +134,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
del self._constraints[0]
|
||||
del self._constraints[0]
|
||||
|
||||
def min_volatility(self):
|
||||
def min_volatility(self, verbose=False):
|
||||
"""
|
||||
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()
|
||||
return self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
|
||||
def max_sharpe(self, risk_free_rate=0.02):
|
||||
def max_sharpe(self, risk_free_rate=0.02, verbose=False):
|
||||
"""
|
||||
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()
|
||||
self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
# 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):
|
||||
def max_quadratic_utility(self, risk_aversion=1, market_neutral=False, verbose=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()
|
||||
return self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
|
||||
def efficient_risk(self, target_volatility, market_neutral=False):
|
||||
def efficient_risk(self, target_volatility, market_neutral=False, verbose=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()
|
||||
return self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
|
||||
def efficient_return(self, target_return, market_neutral=False):
|
||||
def efficient_return(self, target_return, market_neutral=False, verbose=False):
|
||||
"""
|
||||
Calculate the 'Markowitz portfolio', minimising volatility for a given target return.
|
||||
|
||||
@@ -331,7 +331,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
|
||||
else:
|
||||
self._constraints.append(cp.sum(self._w) == 1)
|
||||
|
||||
return self._solve_cvxpy_opt_problem()
|
||||
return self._solve_cvxpy_opt_problem(verbose=verbose)
|
||||
|
||||
def portfolio_performance(self, verbose=False, risk_free_rate=0.02):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import json
|
||||
import os
|
||||
import cvxpy
|
||||
import numpy as np
|
||||
import pytest
|
||||
from random import random
|
||||
from mock import patch, Mock
|
||||
from pypfopt import EfficientFrontier
|
||||
from pypfopt import exceptions
|
||||
from tests.utilities_for_tests import get_data, setup_efficient_frontier
|
||||
@@ -203,3 +206,55 @@ def test_save_weights_to_file():
|
||||
|
||||
os.remove("tests/test.txt")
|
||||
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()
|
||||
|
||||
with patch("cvxpy.Problem.solve") as mock:
|
||||
with pytest.raises(exceptions.OptimizationError):
|
||||
# we're not testing the behavior of ef.min_volatility, just that it
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
# whether the value of verbose is passed on to Problem#solve
|
||||
_name, _args, kwargs = mock.mock_calls[0]
|
||||
assert kwargs['verbose'] == verbose
|
||||
|
||||
def test_min_volatility_verbose_option():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility)
|
||||
|
||||
def test_min_volatility_verbose_option_with_solver():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility, solver=cvxpy.settings.ECOS)
|
||||
|
||||
def test_max_sharpe_verbose_option():
|
||||
assert_verbose_option(EfficientFrontier.max_sharpe)
|
||||
|
||||
def test_max_sharpe_verbose_option_with_solver():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility, solver=cvxpy.settings.ECOS)
|
||||
|
||||
def test_max_quadratic_utility_verbose_option():
|
||||
assert_verbose_option(EfficientFrontier.max_quadratic_utility)
|
||||
|
||||
def test_max_quadratic_utility_verbose_option_with_solver():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility, solver=cvxpy.settings.ECOS)
|
||||
|
||||
def test_efficient_risk_verbose_option():
|
||||
assert_verbose_option(EfficientFrontier.efficient_risk, 0.1)
|
||||
|
||||
def test_efficient_risk_verbose_option_with_solver():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility, solver=cvxpy.settings.ECOS)
|
||||
|
||||
def test_efficient_return_verbose_option():
|
||||
assert_verbose_option(EfficientFrontier.efficient_return, 0.01)
|
||||
|
||||
def test_efficient_return_verbose_option_with_solver():
|
||||
assert_verbose_option(EfficientFrontier.min_volatility, solver=cvxpy.settings.ECOS)
|
||||
|
||||
Reference in New Issue
Block a user