示例#1
0
 def __init__(self):
     self.weights = list()
     self.seriated_distances = None
     self.seriated_correlations = None
     self.ordered_indices = None
     self.clusters = None
     self.returns_estimator = ReturnsEstimators()
     self.risk_metrics = RiskMetrics()
     self.risk_estimator = RiskEstimators()
示例#2
0
    def setUp(self):
        """
        Initialize and get the test data
        """

        # Stock prices data to test the Covariance functions
        project_path = os.path.dirname(__file__)
        data_path = project_path + '/test_data/stock_prices.csv'
        self.data = pd.read_csv(data_path, parse_dates=True, index_col="Date")

        # And series of returns
        ret_est = ReturnsEstimators()
        self.returns = ret_est.calculate_returns(self.data)
示例#3
0
    def test_hrp_with_input_as_covariance_matrix(self):
        """
        Test HRP when passing a covariance matrix as input.
        """

        hrp = HierarchicalRiskParity()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        hrp.allocate(asset_names=self.data.columns,
                     covariance_matrix=returns.cov())
        weights = hrp.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#4
0
    def test_valuerror_with_no_asset_names(self):
        """
        Test ValueError when not supplying a list of asset names and no other input.
        """

        with self.assertRaises(ValueError):
            cla = CriticalLineAlgorithm()
            expected_returns = ReturnsEstimators(
            ).calculate_mean_historical_returns(asset_prices=self.data,
                                                resample_by='W')
            covariance = ReturnsEstimators().calculate_returns(
                asset_prices=self.data, resample_by='W').cov()
            cla.allocate(expected_asset_returns=expected_returns,
                         covariance_matrix=covariance)
示例#5
0
    def test_hrp_with_nan_inputs(self):
        """
        Test HRP with NaN inputs
        """

        hrp = HierarchicalRiskParity()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        covariance = returns.cov()
        covariance *= np.nan
        hrp.allocate(covariance_matrix=covariance)
        weights = hrp.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
    def test_valuerror_with_no_asset_names(self):
        """
        Test ValueError when not supplying a list of asset names and no other input
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            expected_returns = ReturnsEstimators(
            ).calculate_mean_historical_returns(asset_prices=self.data,
                                                resample_by='W')
            covariance = ReturnsEstimators().calculate_returns(
                asset_prices=self.data, resample_by='W').cov()
            mvo.allocate(expected_asset_returns=expected_returns,
                         covariance_matrix=covariance.values)
示例#7
0
    def test_herc_with_asset_returns_as_none(self):
        """
        Test HERC when asset returns are not required for calculating the weights.
        """

        herc = HierarchicalEqualRiskContribution()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        herc.allocate(asset_names=self.data.columns,
                      covariance_matrix=returns.cov(),
                      optimal_num_clusters=5,
                      risk_measure='equal_weighting')
        weights = herc.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#8
0
    def test_herc_with_input_as_covariance_matrix(self):
        """
        Test HERC when passing a covariance matrix as input.
        """

        herc = HierarchicalEqualRiskContribution()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        herc.allocate(asset_names=self.data.columns,
                      covariance_matrix=returns.cov(),
                      optimal_num_clusters=6,
                      asset_returns=returns)
        weights = herc.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
    def test_entropy_pooling_for_all_constraints(self):
        """
        Test the Entropy Pooling algorithm for both equality and inequality constraints.
        """

        returns = ReturnsEstimators().calculate_returns(self.data.iloc[:, :5])
        num_time_stamps = returns.shape[0]
        p_initial = np.array([1 / num_time_stamps] * num_time_stamps)
        equality_matrix = returns[['EEM', 'EWG']].values
        equality_vector = [2.3e-02, 3.3e-02]
        inequality_matrix = returns[['TIP', 'EWJ', 'EFA']].values
        inequality_vector = [1.1e-3, 2.2e-4, 3.8e-6]
        ep_solver = EntropyPooling()
        ep_solver.calculate_posterior_probabilities(
            prior_probabilities=p_initial,
            equality_matrix=equality_matrix,
            equality_vector=equality_vector,
            inequality_matrix=inequality_matrix,
            inequality_vector=inequality_vector)
        assert len(ep_solver.posterior_probabilities) == len(p_initial)
        np.testing.assert_almost_equal(
            equality_matrix.T.dot(
                ep_solver.posterior_probabilities)[0][0].round(3),
            equality_vector[0])
        np.testing.assert_almost_equal(
            equality_matrix.T.dot(
                ep_solver.posterior_probabilities)[1][0].round(3),
            equality_vector[1])
        assert ep_solver.posterior_probabilities.T.dot(
            inequality_matrix)[0][0] >= 1.1e-3
        assert inequality_matrix.T.dot(
            ep_solver.posterior_probabilities)[1][0] >= 2.2e-4
        assert inequality_matrix.T.dot(
            ep_solver.posterior_probabilities)[2][0] >= 3.8e-6
    def test_entropy_pooling_for_only_equality_constraints(self):
        # pylint: disable=invalid-name
        """
        Test Entropy Pooling when passing only equality constraints.
        """

        returns = ReturnsEstimators().calculate_returns(self.data.iloc[:, :5])
        num_time_stamps = returns.shape[0]
        p_initial = np.array([1 / num_time_stamps] * num_time_stamps)
        equality_matrix = returns[['EEM', 'EWG']].values
        equality_vector = [2.3e-02, 3.3e-02]
        ep_solver = EntropyPooling()
        ep_solver.calculate_posterior_probabilities(
            prior_probabilities=p_initial,
            equality_matrix=equality_matrix,
            equality_vector=equality_vector)
        assert len(ep_solver.posterior_probabilities) == len(p_initial)
        np.testing.assert_almost_equal(
            equality_matrix.T.dot(
                ep_solver.posterior_probabilities)[0][0].round(3),
            equality_vector[0])
        np.testing.assert_almost_equal(
            equality_matrix.T.dot(
                ep_solver.posterior_probabilities)[1][0].round(3),
            equality_vector[1])
    def test_custom_objective_function(self):
        """
        Test custom portfolio objective and allocation constraints.
        """

        mvo = MeanVarianceOptimisation()
        custom_obj = 'cp.Minimize(risk)'
        constraints = ['cp.sum(weights) == 1', 'weights >= 0', 'weights <= 1']
        non_cvxpy_variables = {
            'num_assets':
            self.data.shape[1],
            'covariance':
            self.data.cov(),
            'expected_returns':
            ReturnsEstimators().calculate_mean_historical_returns(
                asset_prices=self.data, resample_by='W')
        }
        cvxpy_variables = [
            'risk = cp.quad_form(weights, covariance)',
            'portfolio_return = cp.matmul(weights, expected_returns)'
        ]
        mvo.allocate_custom_objective(non_cvxpy_variables=non_cvxpy_variables,
                                      cvxpy_variables=cvxpy_variables,
                                      objective_function=custom_obj,
                                      constraints=constraints)
        weights = mvo.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        assert mvo.asset_names == list(map(str, range(mvo.num_assets)))
        assert mvo.portfolio_return == 0.012854555899642236
        assert mvo.portfolio_risk == 3.0340907720046832
        np.testing.assert_almost_equal(np.sum(weights), 1)
    def test_value_error_for_custom_obj_optimal_weights(self):
        # pylint: disable=invalid-name
        """
        Test ValueError when no optimal weights are found for custom objective solution.
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            custom_obj = 'cp.Minimize(risk - kappa)'
            constraints = [
                'cp.sum(weights) == 1', 'weights >= 0', 'weights <= 1'
            ]
            non_cvxpy_variables = {
                'num_assets':
                self.data.shape[1],
                'covariance':
                self.data.cov(),
                'expected_returns':
                ReturnsEstimators().calculate_mean_historical_returns(
                    asset_prices=self.data, resample_by='W')
            }
            cvxpy_variables = [
                'risk = cp.quad_form(weights, covariance)',
                'portfolio_return = cp.matmul(weights, expected_returns)',
                'kappa = cp.Variable(1)'
            ]
            mvo.allocate_custom_objective(
                non_cvxpy_variables=non_cvxpy_variables,
                cvxpy_variables=cvxpy_variables,
                objective_function=custom_obj,
                constraints=constraints)
示例#13
0
    def __init__(self, confidence_level=0.05):
        """
        Initialise.

        :param confidence_level: (float) The confidence level (alpha) used for calculating expected shortfall and conditional
                                         drawdown at risk.
        """

        self.weights = list()
        self.clusters = None
        self.ordered_indices = None
        self.cluster_children = None
        self.optimal_num_clusters = None
        self.returns_estimator = ReturnsEstimators()
        self.risk_estimator = RiskEstimators()
        self.risk_metrics = RiskMetrics()
        self.confidence_level = confidence_level
    def test_no_asset_names_by_passing_cov(self):
        """
        Test MVO when not supplying a list of asset names but passing covariance matrix as input
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimators(
        ).calculate_exponential_historical_returns(asset_prices=self.data,
                                                   resample_by='W')
        covariance = ReturnsEstimators().calculate_returns(
            asset_prices=self.data, resample_by='W').cov()
        mvo.allocate(expected_asset_returns=expected_returns,
                     covariance_matrix=covariance)
        weights = mvo.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
    def test_exception_in_plotting_efficient_frontier(self):
        # pylint: disable=invalid-name, protected-access
        """
        Test raising of exception when plotting the efficient frontier.
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimators(
        ).calculate_mean_historical_returns(asset_prices=self.data,
                                            resample_by='W')
        covariance = ReturnsEstimators().calculate_returns(
            asset_prices=self.data, resample_by='W').cov()
        plot = mvo.plot_efficient_frontier(
            covariance=covariance,
            max_return=1.0,
            expected_asset_returns=expected_returns)
        assert len(plot._A) == 41
    def test_plotting_efficient_frontier(self):
        # pylint: disable=invalid-name, protected-access
        """
        Test the plotting of the efficient frontier.
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimators(
        ).calculate_mean_historical_returns(asset_prices=self.data,
                                            resample_by='W')
        covariance = ReturnsEstimators().calculate_returns(
            asset_prices=self.data, resample_by='W').cov()
        plot = mvo.plot_efficient_frontier(
            covariance=covariance,
            max_return=1.0,
            expected_asset_returns=expected_returns)
        assert plot.axes.xaxis.label._text == 'Volatility'
        assert plot.axes.yaxis.label._text == 'Return'
        assert len(plot._A) == 41
    def test_mvo_with_input_as_returns_and_covariance(self):
        # pylint: disable=invalid-name
        """
        Test MVO when we pass expected returns and covariance matrix as input.
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimators(
        ).calculate_mean_historical_returns(asset_prices=self.data,
                                            resample_by='W')
        covariance = ReturnsEstimators().calculate_returns(
            asset_prices=self.data, resample_by='W').cov()
        mvo.allocate(covariance_matrix=covariance,
                     expected_asset_returns=expected_returns,
                     asset_names=self.data.columns)
        weights = mvo.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#18
0
    def test_valuerror_with_no_asset_names(self):
        """
        Test ValueError when not supplying a list of asset names and no other input.
        """

        with self.assertRaises(ValueError):
            hrp = HierarchicalRiskParity()
            returns = ReturnsEstimators().calculate_returns(
                asset_prices=self.data)
            hrp.allocate(asset_returns=returns.values)
示例#19
0
    def test_value_error_with_no_asset_names(self):
        """
        Test ValueError when not supplying a list of asset names and no other input
        """

        with self.assertRaises(ValueError):
            herc = HierarchicalEqualRiskContribution()
            returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
            herc.allocate(asset_returns=returns.values,
                          optimal_num_clusters=6)
示例#20
0
    def __init__(self, calculate_expected_returns='mean', risk_free_rate=0.03):
        """
        Constructor.

        :param calculate_expected_returns: (str) The method to use for calculation of expected returns.
                                                 Currently supports: ``mean``, ``exponential``.
        """

        self.weights = list()
        self.asset_names = None
        self.num_assets = None
        self.portfolio_risk = None
        self.portfolio_return = None
        self.portfolio_sharpe_ratio = None
        self.calculate_expected_returns = calculate_expected_returns
        self.returns_estimator = ReturnsEstimators()
        self.risk_estimators = RiskEstimators()
        self.weight_bounds = (0, 1)
        self.risk_free_rate = risk_free_rate
示例#21
0
    def test_cla_with_input_as_returns_and_covariance(self):
        # pylint: disable=invalid-name
        """
        Test CLA when we pass expected returns and covariance matrix as input.
        """

        cla = CriticalLineAlgorithm()
        expected_returns = ReturnsEstimators(
        ).calculate_mean_historical_returns(asset_prices=self.data)
        covariance = ReturnsEstimators().calculate_returns(
            asset_prices=self.data).cov()
        cla.allocate(covariance_matrix=covariance,
                     expected_asset_returns=expected_returns,
                     asset_names=self.data.columns)
        weights = cla.weights.values
        weights[weights <= 1e-15] = 0  # Convert very very small numbers to 0
        for turning_point in weights:
            assert (turning_point >= 0).all()
            assert len(turning_point) == self.data.shape[1]
            np.testing.assert_almost_equal(np.sum(turning_point), 1)
示例#22
0
    def test_hrp_with_input_as_distance_matrix(self):
        """
        Test HRP when passing a distance matrix as input.
        """

        hrp = HierarchicalRiskParity()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        covariance = returns.cov()
        corr = RiskEstimators.cov_to_corr(covariance)
        corr = pd.DataFrame(corr,
                            index=covariance.columns,
                            columns=covariance.columns)
        distance_matrix = np.sqrt((1 - corr).round(5) / 2)
        hrp.allocate(asset_names=self.data.columns,
                     covariance_matrix=covariance,
                     distance_matrix=distance_matrix)
        weights = hrp.weights.values[0]
        self.assertTrue((weights >= 0).all())
        self.assertTrue(len(weights) == self.data.shape[1])
        self.assertAlmostEqual(np.sum(weights), 1)
示例#23
0
    def setUp(self):
        """
        Initialize and load data
        """

        project_path = os.path.dirname(__file__)

        # Loading the price series of ETFs
        price_data_path = project_path + '/test_data/stock_prices.csv'
        self.price_data = pd.read_csv(price_data_path,
                                      parse_dates=True,
                                      index_col="Date")

        # Transforming series of prices to series of returns
        ret_est = ReturnsEstimators()
        self.returns_data = ret_est.calculate_returns(self.price_data)

        # Loading the classification tree of ETFs
        classification_tree_path = project_path + '/test_data/classification_tree.csv'
        self.classification_tree = pd.read_csv(classification_tree_path)
示例#24
0
    def test_no_asset_names_with_asset_returns(self):
        """
        Test HRP when not supplying a list of asset names and when the user passes asset_returns.
        """

        hrp = HierarchicalRiskParity()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        hrp.allocate(asset_returns=returns)
        weights = hrp.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#25
0
    def test_herc_with_input_as_returns(self):
        """
        Test HERC when passing asset returns dataframe as input.
        """

        herc = HierarchicalEqualRiskContribution()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        herc.allocate(asset_returns=returns, asset_names=self.data.columns)
        weights = herc.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#26
0
    def test_no_asset_names_with_asset_returns(self):
        """
        Test HERC when not supplying a list of asset names and when the user passes asset_returns.
        """

        herc = HierarchicalEqualRiskContribution()
        returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
        herc.allocate(asset_returns=returns,
                      optimal_num_clusters=6)
        weights = herc.weights.values[0]
        assert (weights >= 0).all()
        assert len(weights) == self.data.shape[1]
        np.testing.assert_almost_equal(np.sum(weights), 1)
示例#27
0
    def __init__(self,
                 weight_bounds=(0, 1),
                 calculate_expected_returns="mean"):
        """
        Initialise the storage arrays and some preprocessing.

        :param weight_bounds: (tuple) A tuple specifying the lower and upper bound ranges for the portfolio weights.
        :param calculate_expected_returns: (str) The method to use for calculation of expected returns.
                                                 Currently supports ``mean`` and ``exponential``
        """

        self.weight_bounds = weight_bounds
        self.calculate_expected_returns = calculate_expected_returns
        self.weights = list()
        self.lambdas = list()
        self.gammas = list()
        self.free_weights = list()
        self.max_sharpe = None
        self.min_var = None
        self.efficient_frontier_means = None
        self.efficient_frontier_sigma = None
        self.returns_estimator = ReturnsEstimators()
        self.risk_estimators = RiskEstimators()
    def test_value_error_for_no_inequality_vector(self):
        """
        Test ValueError when no inequality vector is specified.
        """

        with self.assertRaises(ValueError):
            returns = ReturnsEstimators().calculate_returns(
                self.data.iloc[:, :5])
            num_time_stamps = returns.shape[0]
            p_initial = np.array([1 / num_time_stamps] * num_time_stamps)
            inequality_matrix = returns[['TIP', 'EWJ', 'EFA']].values
            ep_solver = EntropyPooling()
            ep_solver.calculate_posterior_probabilities(
                prior_probabilities=p_initial,
                inequality_matrix=inequality_matrix)
    def test_value_error_all_inputs_null(self):
        """
        Test ValueError when no equality and inequality matricies have been specified.
        """

        with self.assertRaises(ValueError):
            returns = ReturnsEstimators().calculate_returns(
                self.data.iloc[:, :5])
            num_time_stamps = returns.shape[0]
            p_initial = np.array([1 / num_time_stamps] * num_time_stamps)
            equality_vector = [2.3e-02, 3.3e-02]
            inequality_vector = [1.1e-3, 2.2e-4, 3.8e-6]
            ep_solver = EntropyPooling()
            ep_solver.calculate_posterior_probabilities(
                prior_probabilities=p_initial,
                equality_vector=equality_vector,
                inequality_vector=inequality_vector)
    def test_value_error_for_diff_lengths_of_inequality_constraints(self):
        # pylint: disable=invalid-name
        """
        Test ValueError when the lengths of inequality matrix and inequality vector are different.
        """

        with self.assertRaises(ValueError):
            returns = ReturnsEstimators().calculate_returns(
                self.data.iloc[:, :5])
            num_time_stamps = returns.shape[0]
            p_initial = np.array([1 / num_time_stamps] * num_time_stamps)
            inequality_matrix = returns[['EEM', 'EWG']].values
            inequality_vector = [2.3e-02]
            ep_solver = EntropyPooling()
            ep_solver.calculate_posterior_probabilities(
                prior_probabilities=p_initial,
                inequality_matrix=inequality_matrix,
                inequality_vector=inequality_vector)