def test_efficient_semivariance_vs_heuristic_weekly(): benchmark = 0 _, historic_returns = setup_efficient_semivariance(data_only=True) weekly_returns = historic_returns.resample("W").sum() mean_weekly_returns = weekly_returns.mean(axis=0) es = EfficientSemivariance(mean_weekly_returns, weekly_returns, frequency=52) es.efficient_return(0.20 / 52) mu_es, semi_deviation, _ = es.portfolio_performance() pairwise_semivariance = risk_models.semicovariance(weekly_returns, returns_data=True, benchmark=0, frequency=1) ef = EfficientFrontier(mean_weekly_returns, pairwise_semivariance) ef.efficient_return(0.20 / 52) mu_ef, _, _ = ef.portfolio_performance() portfolio_returns = historic_returns @ ef.weights drops = np.fmin(portfolio_returns - benchmark, 0) T = weekly_returns.shape[0] semivariance = np.sum(np.square(drops)) / T * 52 semi_deviation_ef = np.sqrt(semivariance) assert semi_deviation < semi_deviation_ef assert mu_es / semi_deviation > mu_ef / semi_deviation_ef
def test_efficient_semivariance_vs_heuristic(): benchmark = 0 es = setup_efficient_semivariance() es.efficient_return(0.20) mu_es, semi_deviation, _ = es.portfolio_performance() np.testing.assert_almost_equal(mu_es, 0.2) mean_return, historic_returns = setup_efficient_semivariance( data_only=True) pairwise_semivariance = risk_models.semicovariance(historic_returns, returns_data=True, benchmark=0, frequency=1) ef = EfficientFrontier(mean_return, pairwise_semivariance) ef.efficient_return(0.20) mu_ef, _, _ = ef.portfolio_performance() # mu_ef *= 252 portfolio_returns = historic_returns @ ef.weights drops = np.fmin(portfolio_returns - benchmark, 0) T = historic_returns.shape[0] semivariance = np.sum(np.square(drops)) / T * 252 semi_deviation_ef = np.sqrt(semivariance) assert semi_deviation < semi_deviation_ef assert mu_es / semi_deviation > mu_ef / semi_deviation_ef
def test_ef_example_weekly(): df = get_data() prices_weekly = df.resample("W").first() mu = expected_returns.mean_historical_return(prices_weekly, frequency=52) S = risk_models.sample_cov(prices_weekly, frequency=52) ef = EfficientFrontier(mu, S) ef.efficient_return(0.2) np.testing.assert_almost_equal(ef.portfolio_performance()[0], 0.2)
def test_ef_example(): df = get_data() mu = expected_returns.mean_historical_return(df) S = risk_models.sample_cov(df) ef = EfficientFrontier(mu, S) ef.efficient_return(0.2) np.testing.assert_almost_equal(ef.portfolio_performance()[0], 0.2)
def test_efficient_return_short(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)) target_return = 0.25 weights_sum = 1.0 # Not market neutral, weights must sum to 1. w = ef.efficient_return(target_return) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), weights_sum) w_expected = simple_ef_weights(ef.expected_returns, ef.cov_matrix, target_return, weights_sum) np.testing.assert_almost_equal(ef.weights, w_expected) vol_expected = np.sqrt( objective_functions.portfolio_variance(w_expected, ef.cov_matrix)) sharpe_expected = objective_functions.sharpe_ratio(w_expected, ef.expected_returns, ef.cov_matrix, negative=False) np.testing.assert_allclose(ef.portfolio_performance(), (target_return, vol_expected, sharpe_expected)) sharpe = ef.portfolio_performance()[2] ef_long_only = setup_efficient_frontier() ef_long_only.efficient_return(target_return) long_only_sharpe = ef_long_only.portfolio_performance()[2] assert sharpe > long_only_sharpe
def portfolio_opt(input_return_df: pd.DataFrame, input_freq: str, solution: str, weight_bounds = (0,1), risk_aversion=1, market_neutral=False, risk_free_rate=0.0, target_volatility=0.01, target_return=0.11, returns_data=True, compounding=False): """ pyportfolioopt Portfolios Args ---------- input_return_df: pd.DataFrame historical prices only, or historical + forecasts solution: str 'max_sharpe','min_volatility','max_quadratic_utility', 'efficient_risk','efficient_return','custom' input_freq: str daily/monthly Returns ---------- weights_df: 1 x n pd.DataFrame """ if not isinstance(input_return_df, pd.DataFrame): raise ValueError("Not a valid input_price_df. Type should be a pd.DataFrame.") if not isinstance(input_freq, str): raise ValueError("Not a valid input_freq, please enter daily/monthly.") if not isinstance(solution, str): raise ValueError("Not a valid solution.") # annualized mean returns: returns.mean() * frequency mu = calculate_annualized_expected_returns(input_price_df = input_return_df, input_freq = input_freq, returns_data = returns_data, compounding = compounding) # annulized return covariance matrix: returns.cov() * frequency S = calculate_annualized_return_covariance(input_price_df = input_return_df, input_freq = input_freq, returns_data = returns_data, compounding = compounding) # Optimise for maximal Sharpe ratio ef = EfficientFrontier(mu, S, weight_bounds = weight_bounds, gamma = 0) if solution == 'max_sharpe': raw_weights = ef.max_sharpe(risk_free_rate = risk_free_rate) elif solution == 'min_volatility': raw_weights = ef.min_volatility() elif solution == 'max_quadratic_utility': raw_weights = ef.max_quadratic_utility(risk_aversion = risk_aversion, market_neutral = market_neutral) elif solution == 'efficient_risk': raw_weights = ef.efficient_risk(target_volatility = target_volatility, market_neutral = market_neutral) elif solution == 'efficient_return': raw_weights = ef.efficient_return(target_return = target_return, market_neutral = market_neutral) elif solution == 'custom': print('Outside Implement Required.') return None date = input_return_df.index[-1] # the date on which weights are calculated weights_df = pd.DataFrame(raw_weights, columns = raw_weights.keys(), index=[date]) return weights_df
def test_efficient_return_longshort_target(): mu = pd.Series([-0.15, -0.12, -0.1, -0.05, -0.01, 0.02, 0.03, 0.04, 0.05]) cov = pd.DataFrame(np.diag([0.2, 0.2, 0.4, 0.3, 0.1, 0.5, 0.2, 0.3, 0.1])) ef = EfficientFrontier(mu, cov, weight_bounds=(-1, 1)) w = ef.efficient_return(target_return=0.08, market_neutral=True) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), 0) np.testing.assert_allclose( ef.portfolio_performance(), (0.08, 0.16649041068958137, 0.3603811159542937), atol=1e-6, )
def test_efficient_return_short(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)) w = ef.efficient_return(0.25) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_allclose(ef.portfolio_performance(), (0.25, 0.16826225873038014, 1.3669137793315087)) sharpe = ef.portfolio_performance()[2] ef_long_only = setup_efficient_frontier() ef_long_only.efficient_return(0.25) long_only_sharpe = ef_long_only.portfolio_performance()[2] assert sharpe > long_only_sharpe
def test_efficient_return_market_neutral(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)) w = ef.efficient_return(0.25, market_neutral=True) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), 0) assert (ef.weights < 1).all() and (ef.weights > -1).all() np.testing.assert_almost_equal( ef.portfolio_performance(), (0.25, 0.20567263154580923, 1.1182819914898223)) sharpe = ef.portfolio_performance()[2] ef_long_only = setup_efficient_frontier() ef_long_only.efficient_return(0.25) long_only_sharpe = ef_long_only.portfolio_performance()[2] assert long_only_sharpe > sharpe
def test_efficient_return_market_neutral(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)) w = ef.efficient_return(0.25, market_neutral=True) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), 0) assert (ef.weights < 1).all() and (ef.weights > -1).all() np.testing.assert_almost_equal( ef.portfolio_performance(), (0.25, 0.1833060046337015, 1.2547324920403273)) sharpe = ef.portfolio_performance()[2] ef_long_only = setup_efficient_frontier() ef_long_only.efficient_return(0.25) long_only_sharpe = ef_long_only.portfolio_performance()[2] assert long_only_sharpe < sharpe
def test_efficient_return_short(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)) w = ef.efficient_return(0.25) assert isinstance(w, dict) assert set(w.keys()) == set(ef.tickers) np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_allclose(ef.portfolio_performance(), (0.25, 0.17149595234895817, 1.3411395245760582)) sharpe = ef.portfolio_performance()[2] ef_long_only = setup_efficient_frontier() ef_long_only.efficient_return(0.25) long_only_sharpe = ef_long_only.portfolio_performance()[2] assert sharpe > long_only_sharpe
ef_maxquad = EfficientFrontier(exp_ret, S, weight_bounds=(0.025, 0.15)) ef_maxquad.max_quadratic_utility(risk_aversion=1, market_neutral=False) weights_maxquad = ef_maxquad.clean_weights() weights_maxquad ef_maxquad.portfolio_performance(verbose=True) # efficient risk ef_effrisk = EfficientFrontier(exp_ret, S, weight_bounds=(0.0, 0.15)) ef_effrisk.efficient_risk(0.15, market_neutral=False) weights_effrisk = ef_effrisk.clean_weights() weights_effrisk ef_effrisk.portfolio_performance(verbose=True) # efficient return ef_effret = EfficientFrontier(exp_ret, S, weight_bounds=(0.03, 0.15)) ef_effret.efficient_return(target_return=0.30, market_neutral=False) weights_effret = ef_effret.clean_weights() weights_effret ef_effret.portfolio_performance(verbose=True) def minimize_negative_skew(w, skew, negative=True): """ Calculate the (negative) "Skew" of a portfolio :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param skew: expected return of each asset :type skew: np.ndarray :param cov_matrix: covariance matrix :type cov_matrix: np.ndarray
weights = ef.efficient_risk(target_volatility=0.10) ef.portfolio_performance(verbose=True) """ Expected annual return: 21.0% Annual volatility: 16.8% Sharpe Ratio: 1.13 """ # A market-neutral Markowitz portfolio finding the minimum volatility # for a target return of 20%, taking into account transaction costs. ef = EfficientFrontier(mu, S, weight_bounds=(-1, 1)) # pretend we were initially equal-weighted old_weights = np.array([1 / ef.n_assets] * ef.n_assets) ef.add_objective(objective_functions.transaction_cost, w_prev=old_weights) weights = ef.efficient_return(target_return=0.20, market_neutral=True) ef.portfolio_performance(verbose=True) """ Expected annual return: 20.0% Annual volatility: 16.5% Sharpe Ratio: 1.09 """ # Custom convex objective from 60 Years of Portfolio Optimisation, Kolm et al (2014) def logarithmic_barrier(w, cov_matrix, k=0.1): log_sum = cp.sum(cp.log(w)) var = cp.quad_form(w, cov_matrix) return var - k * log_sum
ef.portfolio_performance(verbose=True) # In[63]: """ Let's now work with the assumption that we have a required rate of return, such as an investor to retire, or the actuarial level necessary to cover the liabilities of a pension fund or scheme. We also suppose our portfolio to be market neutral, equally exposed to long and short positions. """ # In[71]: ef = EfficientFrontier(mu, S, weight_bounds=(None, None)) ef.add_objective(objective_functions.L2_reg) ef.efficient_return(target_return=0.15, market_neutral=True) weights = ef.clean_weights() weights # In[72]: ef.portfolio_performance(verbose=True) # In[73]: pd.Series(weights).plot.barh(figsize=(10, 10)) # In[74]: print(f"Net weight: {sum(weights.values()):.2f}")