Exemple #1
0
def generate_portfolio_allocations(stocks, portfolio_opt, start_date, end_date,
                                   *other_settings):
    """
        Description: Generates risk stats for a given set of stocks

        Parameters:
            stocks: List of tickers compatible with the yfinance module
            portfolio_opt: List of portfolio
            start_date: start date in YYYY-MM-DD formatted date
            end_date: end date in YYYY-MM-DD formatted date
            *other_settings: unimportant settings in https://mlfinlab.readthedocs.io/en/latest/portfolio_optimisation/mean_variance.html

        Returns: an list of dictionaries
    """
    asset_prices = get_prices(stocks, start_date, end_date)
    # Calculate empirical covariance of assets
    portfolio_data = []
    for sol_settings in portfolio_opt:
        mvo = MeanVarianceOptimisation()
        solution = sol_settings['name']
        description = sol_settings['description']
        mvo.allocate(asset_names=asset_prices.columns,
                     asset_prices=asset_prices,
                     solution=solution,
                     *other_settings)
        ivp_weights = mvo.weights.sort_values(by=0, ascending=False, axis=1)
        weights_html = ivp_weights.to_html(classes='table-alternating')
        portfolio_data.append(
            dict(name=solution, weights=weights_html, description=description))

    # TODO return 2 parameters, one is the pathname to the plot, the rest is a
    # list of portfolio allocations
    return portfolio_data
Exemple #2
0
    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(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)
Exemple #3
0
    def test_custom_objective_with_asset_names(self):
        """
        Test custom portfolio objective and constraints while providing a list of asset names.
        """

        mvo = MeanVarianceOptimisation()
        custom_obj = 'cp.Minimize(kappa)'
        constraints = ['cp.sum(weights) == 1', 'weights >= 0', 'weights <= 1']
        non_cvxpy_variables = {
            'num_assets': self.data.shape[1],
            'covariance': self.data.cov(),
            'asset_names': self.data.columns
        }
        cvxpy_variables = [
            'kappa = cp.quad_form(weights, covariance)',
        ]
        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 list(mvo.asset_names) == list(self.data.columns)
        assert mvo.portfolio_return is None
        assert mvo.portfolio_risk is None
        np.testing.assert_almost_equal(np.sum(weights), 1)
Exemple #4
0
    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)
Exemple #5
0
    def test_all_inputs_none(self):
        """
        Test allocation when all inputs are None.
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_names=self.data.columns)
    def test_value_error_for_unknown_solution(self):
        """
        Test ValueError on passing unknown solution string
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data, solution='ivp')
    def test_value_error_for_non_dataframe_input(self):
        # pylint: disable=invalid-name
        """
        Test ValueError on passing non-dataframe input
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data.values, solution='inverse_variance', asset_names=self.data.columns)
    def test_value_error_for_unknown_solution(self):
        # pylint: disable=invalid-name
        """
        Test ValueError on passing unknown solution string
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data, solution='ivp', asset_names=self.data.columns)
    def test_unknown_returns_calculation(self):
        # pylint: disable=invalid-name
        """
        Test ValueError on passing unknown returns calculation string
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation(calculate_expected_returns='unknown_returns')
            mvo.allocate(asset_prices=self.data, asset_names=self.data.columns)
    def test_value_error_for_non_date_index(self):
        """
        Test ValueError on passing dataframe not indexed by date
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            data = self.data.reset_index()
            mvo.allocate(asset_prices=data, solution='inverse_variance', asset_names=self.data.columns)
    def test_resampling_asset_prices(self):
        """
        Test resampling of asset prices
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data, solution='inverse_variance', resample_by='B', 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)
    def test_max_sharpe_solution(self):
        """
        Test the calculation of inverse-variance portfolio weights
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data, solution='max_sharpe', 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)
Exemple #13
0
    def test_no_asset_names(self):
        """
        Test MVO when not supplying a list of asset names.
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data)
        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)
Exemple #14
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):
            mvo = MeanVarianceOptimisation()
            expected_returns = ReturnsEstimation().calculate_mean_historical_returns(asset_prices=self.data,
                                                                                     resample_by='W')
            covariance = ReturnsEstimation().calculate_returns(asset_prices=self.data, resample_by='W').cov()
            mvo.allocate(expected_asset_returns=expected_returns, covariance_matrix=covariance.values)
    def test_mvo_with_exponential_returns(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of inverse-variance portfolio weights
        """

        mvo = MeanVarianceOptimisation(calculate_expected_returns='exponential')
        mvo.allocate(asset_prices=self.data, resample_by='B', solution='inverse_variance', 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)
Exemple #16
0
    def test_max_decorrelation(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of maximum decorrelation portfolio weights.
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data, solution='max_decorrelation', 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)
    def test_value_error_for_no_efficient_risk_optimal_weights(self):
        # pylint: disable=invalid-name
        """
        Test ValueError when no optimal weights are found for efficient risk solution
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data,
                         solution='efficient_risk',
                         weight_bounds=(0.9, 1),
                         asset_names=self.data.columns)
Exemple #18
0
    def test_value_error_for_no_quadratic_utlity_optimal_weights(self):
        # pylint: disable=invalid-name
        """
        Test ValueError when no optimal weights are found for max return-minimum volatility solution.
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data,
                         solution='max_return_min_volatility',
                         weight_bounds=(0.9, 1),
                         asset_names=self.data.columns)
Exemple #19
0
    def test_value_error_for_no_max_decorrelation_optimal_weights(self):
        # pylint: disable=invalid-name
        """
        Test ValueError when no optimal weights are found for max decorrelation solution.
        """

        with self.assertRaises(ValueError):
            mvo = MeanVarianceOptimisation()
            mvo.allocate(asset_prices=self.data,
                         solution='max_decorrelation',
                         weight_bounds=(0.9, 1),
                         asset_names=self.data.columns)
    def test_min_volatility_with_target_return(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of inverse-variance portfolio weights
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data, solution='efficient_risk', asset_names=self.data.columns, resample_by='W')
        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)
Exemple #21
0
    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 = {
                'objective': 'cp.Minimize(risk)',
                'constraints': ['cp.sum(weights) == 1', 'weights >= 0', 'weights <= 1', 'weights[4] <= -1']
            }
            mvo.allocate_custom_objective(custom_objective=custom_obj, asset_prices=self.data)
Exemple #22
0
    def test_min_volatility_for_target_return(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of minimum volatility-target return portfolio weights.
        """

        mvo = MeanVarianceOptimisation()
        prices = self.data.resample('W').last()
        mvo.allocate(asset_prices=prices, solution='efficient_risk', 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)
Exemple #23
0
    def test_max_return_min_volatility_solution(self):
        """
        Test the calculation of maximum expected return and minimum volatility portfolio weights.
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data,
                     risk_aversion=50,
                     solution='max_return_min_volatility',
                     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)
Exemple #24
0
    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 = ReturnsEstimation().calculate_mean_historical_returns(asset_prices=self.data,
                                                                                 resample_by='W')
        covariance = ReturnsEstimation().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
Exemple #25
0
    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 = ReturnsEstimation().calculate_exponential_historical_returns(asset_prices=self.data,
                                                                                        resample_by='W')
        covariance = ReturnsEstimation().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)
Exemple #26
0
    def test_plotting_efficient_frontier(self):
        # pylint: disable=invalid-name, protected-access
        """
        Test the plotting of the efficient frontier.
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimation().calculate_mean_historical_returns(asset_prices=self.data,
                                                                                 resample_by='W')
        covariance = ReturnsEstimation().calculate_returns(asset_prices=self.data, resample_by='W').cov()
        plot = mvo.plot_efficient_frontier(covariance=covariance,
                                           expected_asset_returns=expected_returns)
        assert plot.axes.xaxis.label._text == 'Volatility'
        assert plot.axes.yaxis.label._text == 'Return'
        assert len(plot._A) == 100
Exemple #27
0
    def test_custom_objective_function(self):
        """
        Test custom portfolio objective and allocation constraints.
        """

        mvo = MeanVarianceOptimisation()
        custom_obj = {
            'objective': 'cp.Minimize(risk)',
            'constraints': ['cp.sum(weights) == 1', 'weights >= 0', 'weights <= 1']
        }
        mvo.allocate_custom_objective(custom_objective=custom_obj, asset_prices=self.data)
        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)
Exemple #28
0
    def test_max_return_min_volatility_with_specific_weight_bounds(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of weights when specific bounds are supplied.
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data,
                     solution='max_return_min_volatility',
                     weight_bounds=['weights[0] <= 0.3'],
                     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)
    def test_mvo_with_input_as_returns_and_covariance(self):
        # pylint: disable=invalid-name, bad-continuation
        """
        Test MVO when we pass expected returns and covariance matrix as input
        """

        mvo = MeanVarianceOptimisation()
        expected_returns = ReturnsEstimation().calculate_mean_historical_returns(asset_prices=self.data, resample_by='W')
        covariance = ReturnsEstimation().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)
    def test_efficient_risk_with_specific_weight_bounds(self):
        # pylint: disable=invalid-name
        """
        Test the calculation of weights when specific bounds are supplied
        """

        mvo = MeanVarianceOptimisation()
        mvo.allocate(asset_prices=self.data,
                     solution='efficient_risk',
                     target_return=0.01,
                     weight_bounds={0: (0.3, 1)},
                     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)