diff --git a/pypfopt/base_optimizer.py b/pypfopt/base_optimizer.py index 0dc2157..74f7e9b 100644 --- a/pypfopt/base_optimizer.py +++ b/pypfopt/base_optimizer.py @@ -141,14 +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), solver=None, verbose=False): + 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 solver: name of solver. list available solvers with: ``cvxpy.installed_solvers()`` + :type solver: str, optional. Defaults to "ECOS" :param verbose: whether performance and debugging info should be printed, defaults to False :type verbose: bool, optional """ diff --git a/pypfopt/black_litterman.py b/pypfopt/black_litterman.py index 99e43f2..7e9299a 100644 --- a/pypfopt/black_litterman.py +++ b/pypfopt/black_litterman.py @@ -451,18 +451,18 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer): """ return self.bl_weights(risk_aversion) - def portfolio_performance(self, risk_free_rate=0.02, verbose=False): + def portfolio_performance(self, verbose=False, 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. :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) diff --git a/pypfopt/cla.py b/pypfopt/cla.py index 356e378..0b0decf 100644 --- a/pypfopt/cla.py +++ b/pypfopt/cla.py @@ -445,15 +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, verbose=False): + def portfolio_performance(self, verbose=False, 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 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 + :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 :return: expected return, volatility, Sharpe ratio. :rtype: (float, float, float) diff --git a/pypfopt/efficient_frontier.py b/pypfopt/efficient_frontier.py index a919579..f7e4d41 100644 --- a/pypfopt/efficient_frontier.py +++ b/pypfopt/efficient_frontier.py @@ -53,7 +53,15 @@ 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, solver=None, verbose=False): + 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). @@ -93,7 +101,9 @@ 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, solver=solver, verbose=verbose) + super().__init__( + len(tickers), tickers, weight_bounds, solver=solver, verbose=verbose + ) @staticmethod def _validate_expected_returns(expected_returns): @@ -346,17 +356,17 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer): return self._solve_cvxpy_opt_problem() - def portfolio_performance(self, risk_free_rate=0.02, verbose=False): + def portfolio_performance(self, verbose=False, 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. :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) diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py index d0a2df6..5070816 100644 --- a/pypfopt/hierarchical_portfolio.py +++ b/pypfopt/hierarchical_portfolio.py @@ -172,12 +172,14 @@ class HRPOpt(base_optimizer.BaseOptimizer): self.set_weights(weights) return weights - def portfolio_performance(self, risk_free_rate=0.02, frequency=252, verbose=False): + def portfolio_performance(self, verbose=False, 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. @@ -185,8 +187,6 @@ 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 - :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) diff --git a/tests/test_base_optimizer.py b/tests/test_base_optimizer.py index 6453142..1bd69f7 100644 --- a/tests/test_base_optimizer.py +++ b/tests/test_base_optimizer.py @@ -1,10 +1,7 @@ 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 @@ -46,10 +43,6 @@ def test_weight_bounds_minus_one_to_one(): assert ef.max_sharpe() assert ef.min_volatility() - # TODO: fix - # assert ef.efficient_return(0.05) - # assert ef.efficient_risk(0.20) - def test_none_bounds(): ef = EfficientFrontier( @@ -206,54 +199,3 @@ 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): - # 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 = setup_efficient_frontier(solver=solver, verbose=verbose) - - 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) - - # 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) diff --git a/tests/test_expected_returns.py b/tests/test_expected_returns.py index 21f3ac8..763d1a8 100644 --- a/tests/test_expected_returns.py +++ b/tests/test_expected_returns.py @@ -1,4 +1,3 @@ -import warnings import pandas as pd import numpy as np import pytest diff --git a/tests/test_risk_models.py b/tests/test_risk_models.py index 13412d7..157443d 100644 --- a/tests/test_risk_models.py +++ b/tests/test_risk_models.py @@ -1,4 +1,3 @@ -import warnings import pandas as pd import numpy as np import pytest