def test_value_error_for_no_min_volatility_optimal_weights(self): # pylint: disable=invalid-name """ Test ValueError when no optimal weights are found for minimum volatility solution. """ with self.assertRaises(ValueError): mvo = MeanVarianceOptimisation() mvo.allocate(asset_prices=self.data, solution='min_volatility', weight_bounds=(0.9, 1), asset_names=self.data.columns)
def test_max_sharpe_solution(self): """ Test the calculation of maximum sharpe 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)
def test_resampling_asset_prices(self): """ Test resampling of asset prices. """ mvo = MeanVarianceOptimisation() mvo.allocate(asset_prices=self.data, 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)
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)
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)
def test_max_diversification(self): # pylint: disable=invalid-name """ Test the calculation of maximum diversification portfolio weights. """ mvo = MeanVarianceOptimisation() mvo.allocate(asset_prices=self.data, solution='max_diversification', 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_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)
def test_portfolio_metrics(self): """ Test the printing of portfolio metrics to stdout. """ mvo = MeanVarianceOptimisation() mvo.allocate(asset_prices=self.data) with patch('sys.stdout', new=StringIO()) as fake_out: mvo.get_portfolio_metrics() output = fake_out.getvalue().strip() self.assertTrue( 'Portfolio Return = 0.017362404155484328' in output) self.assertTrue('Portfolio Risk = 9.385801639141577e-06' in output) self.assertTrue( 'Portfolio Sharpe Ratio = -4.125045816381286' in output)
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, 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)
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 _calculate_efficient_frontier(mean, covariance, asset_names, num_portfolios): """ Generate portfolios along the efficient frontier. :param mean: (Numpy array) Mean returns. :param covariance: (Numpy matrix) Covariance of returns. :param asset_names: (Python list) List of asset names in the portfolio. :param num_portfolios: (int) Number of portfolios to generate along the efficient frontier. :return: (list, list, list) Portfolios on the efficient frontier. """ # Calculate minimum risk portfolio mvo = MeanVarianceOptimisation() mvo.allocate(expected_asset_returns=mean, covariance_matrix=covariance, asset_names=asset_names, solution='min_volatility') min_volatility_weights = mvo.weights.values min_volatility_return = mvo.portfolio_return # Maximum return maximum_return = np.max(mean) # Get the target returns along the frontier steps = (maximum_return - min_volatility_return) / (num_portfolios - 1) target_returns = np.arange(min_volatility_return, maximum_return, steps) # Start calculating the portfolios along the frontier portfolio_weights, portfolio_volatility, portfolio_return = min_volatility_weights, mvo.portfolio_risk, mvo.portfolio_return bayesian_portfolios = [portfolio_weights] bayesian_portfolio_volatilities = [portfolio_volatility] bayesian_portfolio_returns = [portfolio_return] for target_return in target_returns: mvo.allocate(expected_asset_returns=mean, covariance_matrix=covariance, target_return=target_return, asset_names=asset_names, solution='efficient_risk') bayesian_portfolios.append(mvo.weights.values) bayesian_portfolio_volatilities.append(mvo.portfolio_risk) bayesian_portfolio_returns.append(mvo.portfolio_return) return bayesian_portfolios, bayesian_portfolio_volatilities, bayesian_portfolio_returns
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_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)
def _calculate_bayesian_frontier(self, asset_names): """ Generate portfolios along the bayesian efficient frontier. :param asset_names: (Numpy array/Python list) List of asset names in the portfolio. :return: (Python list, Python list, Python list) Portfolios along the bayesian efficient frontier. """ # Calculate minimum risk portfolio mvo = MeanVarianceOptimisation() mvo.allocate(expected_asset_returns=self.posterior_mean, covariance_matrix=self.posterior_covariance, asset_names=asset_names, solution='min_volatility') min_volatility_weights = mvo.weights.values min_volatility_return = mvo.portfolio_return # Maximum return maximum_return = np.max(self.posterior_mean) # Get the target returns along the frontier step_size = (maximum_return - min_volatility_return) / (self.discretisations - 1) target_returns = np.arange(min_volatility_return, maximum_return + step_size, step_size) # Start calculating the portfolios along the frontier bayesian_portfolios = [min_volatility_weights] bayesian_portfolio_volatilities = [mvo.portfolio_risk] bayesian_portfolio_returns = [mvo.portfolio_return] for target_return in target_returns: mvo.allocate(expected_asset_returns=self.posterior_mean, covariance_matrix=self.posterior_covariance, target_return=target_return, asset_names=asset_names, solution='efficient_risk') bayesian_portfolios.append(mvo.weights.values) bayesian_portfolio_volatilities.append(mvo.portfolio_risk) bayesian_portfolio_returns.append(mvo.portfolio_return) return bayesian_portfolios, bayesian_portfolio_volatilities, bayesian_portfolio_returns
def test_min_volatility_solution(self): """ Test the calculation of minimum volatility portfolio weights. """ mvo = MeanVarianceOptimisation() mvo.allocate(asset_prices=self.data, solution='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) # Check that the volatility is the minimum among all other portfolios for solution_string in { "inverse_variance", "max_sharpe", "max_return_min_volatility", "max_diversification", "max_decorrelation" }: mvo_ = MeanVarianceOptimisation() mvo_.allocate(asset_prices=self.data, solution=solution_string, asset_names=self.data.columns) assert mvo.portfolio_risk <= mvo_.portfolio_risk