Cleaned up Pat's PR

This commit is contained in:
robertmartin8
2020-08-31 20:09:28 +08:00
parent 49506ebeb4
commit 3ace0da7ee
8 changed files with 29 additions and 77 deletions

View File

@@ -141,14 +141,16 @@ class BaseConvexOptimizer(BaseOptimizer):
- ``save_weights_to_file()`` saves the weights to csv, json, or txt. - ``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 :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) if all identical, defaults to (0, 1). Must be changed to (-1, 1)
for portfolios with shorting. for portfolios with shorting.
:type weight_bounds: tuple OR tuple list, optional :type weight_bounds: tuple OR tuple list, optional
:param solver: name of solver. list available solvers with: `cvxpy.installed_solvers()` :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) :type solver: str, optional. Defaults to "ECOS"
:param verbose: whether performance and debugging info should be printed, defaults to False :param verbose: whether performance and debugging info should be printed, defaults to False
:type verbose: bool, optional :type verbose: bool, optional
""" """

View File

@@ -451,18 +451,18 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
""" """
return self.bl_weights(risk_aversion) 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 After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio. portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.
This method uses the BL posterior returns and covariance matrix. 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. :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 The period of the risk-free rate should correspond to the
frequency of expected returns. frequency of expected returns.
:type risk_free_rate: float, optional :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 :raises ValueError: if weights have not been calcualted yet
:return: expected return, volatility, Sharpe ratio. :return: expected return, volatility, Sharpe ratio.
:rtype: (float, float, float) :rtype: (float, float, float)

View File

@@ -445,15 +445,15 @@ class CLA(base_optimizer.BaseOptimizer):
# Overrides parent method since set_weights does nothing. # Overrides parent method since set_weights does nothing.
raise NotImplementedError("set_weights does nothing for CLA") 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 After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio. 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 :param verbose: whether performance should be printed, defaults to False
:type verbose: bool, optional :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 :raises ValueError: if weights have not been calculated yet
:return: expected return, volatility, Sharpe ratio. :return: expected return, volatility, Sharpe ratio.
:rtype: (float, float, float) :rtype: (float, float, float)

View File

@@ -53,7 +53,15 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
- ``save_weights_to_file()`` saves the weights to csv, json, or txt. - ``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 :param expected_returns: expected returns for each asset. Can be None if
optimising for volatility only (but not recommended). 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)): if cov_matrix.shape != (len(expected_returns), len(expected_returns)):
raise ValueError("Covariance matrix does not match 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 @staticmethod
def _validate_expected_returns(expected_returns): def _validate_expected_returns(expected_returns):
@@ -346,17 +356,17 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
return self._solve_cvxpy_opt_problem() 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 After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio. 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. :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 The period of the risk-free rate should correspond to the
frequency of expected returns. frequency of expected returns.
:type risk_free_rate: float, optional :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 :raises ValueError: if weights have not been calcualted yet
:return: expected return, volatility, Sharpe ratio. :return: expected return, volatility, Sharpe ratio.
:rtype: (float, float, float) :rtype: (float, float, float)

View File

@@ -172,12 +172,14 @@ class HRPOpt(base_optimizer.BaseOptimizer):
self.set_weights(weights) self.set_weights(weights)
return 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 After optimising, calculate (and optionally print) the performance of the optimal
portfolio. Currently calculates expected return, volatility, and the Sharpe ratio portfolio. Currently calculates expected return, volatility, and the Sharpe ratio
assuming returns are daily 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. :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 The period of the risk-free rate should correspond to the
frequency of expected returns. 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 :param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year) of trading days in a year)
:type frequency: int, optional :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 :raises ValueError: if weights have not been calculated yet
:return: expected return, volatility, Sharpe ratio. :return: expected return, volatility, Sharpe ratio.
:rtype: (float, float, float) :rtype: (float, float, float)

View File

@@ -1,10 +1,7 @@
import json import json
import os import os
import cvxpy
import numpy as np import numpy as np
import pytest import pytest
from random import random
from mock import patch, Mock
from pypfopt import EfficientFrontier from pypfopt import EfficientFrontier
from pypfopt import exceptions from pypfopt import exceptions
from tests.utilities_for_tests import get_data, setup_efficient_frontier 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.max_sharpe()
assert ef.min_volatility() assert ef.min_volatility()
# TODO: fix
# assert ef.efficient_return(0.05)
# assert ef.efficient_risk(0.20)
def test_none_bounds(): def test_none_bounds():
ef = EfficientFrontier( ef = EfficientFrontier(
@@ -206,54 +199,3 @@ def test_save_weights_to_file():
os.remove("tests/test.txt") os.remove("tests/test.txt")
os.remove("tests/test.json") 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)

View File

@@ -1,4 +1,3 @@
import warnings
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import pytest import pytest

View File

@@ -1,4 +1,3 @@
import warnings
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import pytest import pytest