diff --git a/optimization/sharpe_opt.py b/optimization/sharpe_opt.py new file mode 100644 index 0000000..1792eec --- /dev/null +++ b/optimization/sharpe_opt.py @@ -0,0 +1,38 @@ +"""An optimizer for Sharpe Ratio""" + +import numpy as np +import scipy.optimize +# from cvxopt import solvers, matrix +# solvers.options["show_progress"] = False +# solvers.options['maxiters'] = 500 + + +def optimize_sharpe(returns, r_f=1): + """ + Optimize the Sharpe Ratio of a portfolio, + short_sales not working yet... + """ + returns = np.asmatrix(returns) + num_stocks, _ = returns.shape + covar = np.cov(returns) + means = np.mean(returns, axis=1) + + def transform_input(decision_vars): + """Make sure the decision vars sum to 1""" + mod_vars = list(decision_vars) + mod_vars.append(1 - sum(mod_vars)) + return mod_vars + + def neg_sharpe(dec_vars): + """Compute -1*sharpe_ratio""" + dec_vars = np.asmatrix(transform_input(dec_vars)).T + sharpe = (r_f - means.T * dec_vars) \ + / np.sqrt(dec_vars.T * covar * dec_vars) + return sharpe + + sol = scipy.optimize.fmin( + neg_sharpe, + scipy.ones(num_stocks - 1, dtype=float) * 1. / num_stocks, + disp=False, + full_output=False) + return transform_input(sol) diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 9e8f2e0..9bbdbae 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -3,7 +3,7 @@ import csv from collections import defaultdict import numpy as np -from optimization import mad, mvo, downside_var +from optimization import mad, mvo, downside_var, sharpe_opt # Might want to consider breaking this function out @@ -57,9 +57,27 @@ def smoke_test(optimizers): assert not all(shorting_test) +def smoke_test2(optimizers): + """A different smoke test for other models""" + rets = load_data() + gross_returns = [] + for k in sorted(rets.keys()): + gross_returns.append([1.0 + i / 100 for i in rets[k]]) + means = np.mean(gross_returns, axis=1) + steps = np.linspace(0.0, min(means), num=100) + for opt_model in optimizers: + allocations = [np.transpose(opt_model(gross_returns, x)) + for x in steps] + shorting_test = [all([x >= -np.finfo(np.float32).eps for x in row]) + for row in allocations] + assert not all(shorting_test) + + def test_models(): """Run over all the models""" smoke_test([ mvo.optimize_mv, mad.optimize_mad, downside_var.optimize_downside_variance]) + # Sharpe doesn't support turning on and off short sales + smoke_test2([sharpe_opt.optimize_sharpe])