def test_custom_tracking_error(): df = get_data() historical_rets = expected_returns.returns_from_prices(df).dropna() benchmark_rets = historical_rets["AAPL"] historical_rets = historical_rets.drop("AAPL", axis=1) S = risk_models.sample_cov(historical_rets, returns_data=True) opt = BaseConvexOptimizer( n_assets=len(historical_rets.columns), tickers=list(historical_rets.columns), weight_bounds=(0, 1), ) opt.convex_objective( objective_functions.ex_post_tracking_error, historic_returns=historical_rets, benchmark_returns=benchmark_rets, ) w = opt.clean_weights() ef = EfficientFrontier(None, S) ef.convex_objective( objective_functions.ex_post_tracking_error, historic_returns=historical_rets, benchmark_returns=benchmark_rets, ) w2 = ef.clean_weights() assert w == w2
def test_custom_convex_abs_exposure(): ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)) ef.add_constraint(lambda x: cp.norm(x, 1) <= 2) ef.convex_objective( objective_functions.portfolio_variance, cov_matrix=ef.cov_matrix, weights_sum_to_one=False, )
def test_custom_convex_objective_market_neutral_efficient_risk(): target_risk = 0.19 ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)) ef.efficient_risk(target_risk, market_neutral=True) built_in = ef.weights # Recreate the market-neutral efficient_risk optimiser using this API ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)) ef.add_constraint(lambda x: cp.sum(x) == 0) ef.add_constraint( lambda x: cp.quad_form(x, ef.cov_matrix) <= target_risk**2) ef.convex_objective(lambda x: -x @ ef.expected_returns, weights_sum_to_one=False) custom = ef.weights np.testing.assert_allclose(built_in, custom, atol=1e-7)
def test_custom_convex_kelly(): lb = 0.01 ub = 0.3 ef = EfficientFrontier(*setup_efficient_frontier(data_only=True), weight_bounds=(lb, ub)) def kelly_objective(w, e_returns, cov_matrix, k=3): variance = cp.quad_form(w, cov_matrix) objective = variance * 0.5 * k - w @ e_returns return objective weights = ef.convex_objective(kelly_objective, e_returns=ef.expected_returns, cov_matrix=ef.cov_matrix) for w in weights.values(): assert w >= lb - 1e-8 and w <= ub + 1e-8
:type cov_matrix: np.ndarray :param negative: whether quantity should be made negative (so we can minimise) :type negative: boolean :return: (negative) Sharpe ratio :rtype: float """ sk = w @ skew sign = -1 if negative else 1 return sign * sk ef_skew = EfficientFrontier(exp_ret, S, weight_bounds=(0.0, 0.15)) ef_skew.add_sector_constraints(sector_mapper, sector_lower, sector_upper) ef_skew.add_objective(objective_functions.L2_reg, gamma=20) #ef.add_objective(minimize_negative_skew, skew= skw) ef_skew.convex_objective(minimize_negative_skew, skew=skw) #ef.max_sharpe() weights_skew = ef_skew.clean_weights() weights_skew ef_skew.portfolio_performance(verbose=True) # %% weight_dict = { "Miniumum Variance" : weights_minvol, "Maximum Sharpe Ratio" : weights_maxsharpe, "Maximum Quadratic Utility" : weights_maxquad, "Efficient Target Risk" : weights_effrisk, "Efficient Target Return" : weights_effret, "Maximum Skew" : weights_skew }
""" 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 = EfficientFrontier(mu, S) weights = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix) ef.portfolio_performance(verbose=True) """ Expected annual return: 24.0% Annual volatility: 21.1% Sharpe Ratio: 1.04 """ # Now try with a nonconvex objective from Kolm et al (2014) def deviation_risk_parity(w, cov_matrix): diff = w * np.dot(cov_matrix, w) - (w * np.dot(cov_matrix, w)).reshape( -1, 1) return (diff**2).sum().sum()