mirror of
https://github.com/robertmartin8/PyPortfolioOpt.git
synced 2022-11-27 18:02:41 +03:00
Cleaned up Pat's PR
This commit is contained in:
@@ -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
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user