From b9193ee02afcfc65e4f44ed476d254557eba04a0 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Wed, 16 Mar 2016 00:50:13 -0400 Subject: [PATCH 01/15] Need to compute derivative of sharpe ratio --- optimization/opt_sharpe.py | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 optimization/opt_sharpe.py diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py new file mode 100644 index 0000000..33096e8 --- /dev/null +++ b/optimization/opt_sharpe.py @@ -0,0 +1,70 @@ +import numpy as np +from cvxopt import solvers, matrix + +solvers.options["show_progress"] = False + +def opt_sharpe(returns, r_f=1, short_sales=False): + returns = np.asmatrix(returns) + N, T = returns.shape + Q = np.cov(returns) + means = np.mean(returns, axis=1) + + def f(x_mat=None, z=None): + print x_mat, z + if x_mat is None: + x0 = (1./N)*np.ones((N, 1)) + return 0, matrix(x0) + else: + x = np.asmatrix(x_mat) + sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) + return sharpe + + + G = np.zeros((1, N)) + h = [0.0] + + A = np.ones((1, N)) + b = [1.0] + + solvers.cp( + f, + G=matrix(G), + h=matrix(h), + A=matrix(A), + b=matrix(b) + ) + + return solvers['x'] + +if __name__ == '__main__': + rets = { + 'Stocks': [ + 26.81, -8.78, 22.69, 16.36, 12.36, -10.10, 23.94, 11.00, -8.47, 3.94, 14.30, 18.99, + -14.69, -26.47, 37.23, 23.93, -7.16, 6.57, 18.61, 32.50, -4.92, 21.55, 22.56, 6.27, + 31.17, 18.67, 5.25, 16.61, 31.69, -3.10, 30.46, 7.62, 10.08, 1.32, 37.58, 22.96, + 33.36, 28.58, 21.04, -9.10, -11.89, -22.10, 28.68 + ], + 'Bonds': [ + 2.20, 5.72, 1.79, 3.71, 0.93, 5.12, -2.86, 2.25, -5.63, 18.92, 11.24, 2.39, 3.29, + 4.00, 5.52, 15.56, 0.38, -1.26, -1.26, -2.48, 4.04, 44.28, 1.29, 15.29, 32.27, + 22.39, -3.03, 6.84, 18.54, 7.74, 19.36, 7.34, 13.06, -7.32, 25.94, 0.13, 12.02, + 14.45, -7.51, 17.22, 5.51, 15.15, 0.54 + ], + 'MM': [ + 2.33, 2.93, 3.38, 3.85, 4.32, 5.40, 4.51, 6.02, 8.97, 4.90, 4.14, 5.33, 9.95, 8.53, + 5.20, 4.65, 6.56, 10.03, 13.78, 18.90, 12.37, 8.95, 9.47, 8.38, 8.27, 6.91, 6.77, + 8.76, 8.45, 7.31, 4.43, 2.92, 2.96, 5.45, 5.60, 5.29, 5.50, 4.68, 5.30, 6.40, 1.82, + 1.24, 0.98 + ] + } + + r = [] + for k in sorted(rets.keys()): + r.append([1.0+i/100 for i in rets[k]]) + means = np.mean(r, axis=1) + steps = np.linspace(min(means), max(means), num=100) + + xs = map(lambda x: np.transpose(opt_sharpe(r, x))[0], steps) + for i, row in enumerate(xs): + print steps[i], map(lambda x: "%0.3f"%x, row), np.dot(means, row) + From 0f3466a3518d4e7a3694b4a3f0561f501e2bc731 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Wed, 16 Mar 2016 01:04:48 -0400 Subject: [PATCH 02/15] Need Df and H --- optimization/opt_sharpe.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index 33096e8..bc77f0a 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -10,14 +10,18 @@ def opt_sharpe(returns, r_f=1, short_sales=False): means = np.mean(returns, axis=1) def f(x_mat=None, z=None): - print x_mat, z if x_mat is None: x0 = (1./N)*np.ones((N, 1)) return 0, matrix(x0) else: x = np.asmatrix(x_mat) sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) - return sharpe + if z is None: + # Return val, d(f)/dx + return sharpe, matrix(np.zeros((1, N))) + else: + # Return val, d(f)/dx, and hessian + return sharpe, matrix(np.zeros((1, N))), matrix(np.eye(N)) G = np.zeros((1, N)) @@ -26,7 +30,7 @@ def f(x_mat=None, z=None): A = np.ones((1, N)) b = [1.0] - solvers.cp( + sol = solvers.cp( f, G=matrix(G), h=matrix(h), @@ -34,7 +38,7 @@ def f(x_mat=None, z=None): b=matrix(b) ) - return solvers['x'] + return sol['x'] if __name__ == '__main__': rets = { From eac98302fe87c2bb4359145890ef4d37bf2c0e6d Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Wed, 16 Mar 2016 02:38:44 -0400 Subject: [PATCH 03/15] Probably messed up dF and H, but it sometimes is optimal or unknown due to early termination --- optimization/opt_sharpe.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index bc77f0a..accc093 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -16,12 +16,18 @@ def f(x_mat=None, z=None): else: x = np.asmatrix(x_mat) sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) + # Needs more... + dF = np.transpose(means*(1.0/np.sqrt(np.transpose(x)*Q*x))) + \ + (r_f-np.transpose(means)*x)*(-1/(2*float(np.transpose(x)*Q*x)**(2.0/3)))*np.transpose(x)*(Q + np.transpose(Q)) if z is None: # Return val, d(f)/dx - return sharpe, matrix(np.zeros((1, N))) + return sharpe, matrix(dF) else: # Return val, d(f)/dx, and hessian - return sharpe, matrix(np.zeros((1, N))), matrix(np.eye(N)) + H = means*(-1/(float(x.T*Q*x)**(2/3)))*x.T*(Q.T + Q) + \ + (r_f - means.T*x)*(-1/(2*float(x.T*Q*x)**(2/3)))+(Q.T + Q).T + \ + (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) + return sharpe, matrix(dF), matrix(H) G = np.zeros((1, N)) @@ -37,7 +43,7 @@ def f(x_mat=None, z=None): A=matrix(A), b=matrix(b) ) - + #print sol['status'] return sol['x'] if __name__ == '__main__': @@ -66,9 +72,9 @@ def f(x_mat=None, z=None): for k in sorted(rets.keys()): r.append([1.0+i/100 for i in rets[k]]) means = np.mean(r, axis=1) - steps = np.linspace(min(means), max(means), num=100) + steps = np.linspace(0.0, 1.0, num=50) xs = map(lambda x: np.transpose(opt_sharpe(r, x))[0], steps) for i, row in enumerate(xs): - print steps[i], map(lambda x: "%0.3f"%x, row), np.dot(means, row) + print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) From a6c3592e5e81505b8ec9f8a1c441d3864d9e4b8e Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Wed, 16 Mar 2016 03:00:05 -0400 Subject: [PATCH 04/15] Still not really right, running into singular KKT matricies and divide by zeros when dividing by the gap --- optimization/opt_sharpe.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index accc093..a1be2d2 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -1,20 +1,21 @@ import numpy as np from cvxopt import solvers, matrix -solvers.options["show_progress"] = False +#solvers.options["show_progress"] = False def opt_sharpe(returns, r_f=1, short_sales=False): returns = np.asmatrix(returns) N, T = returns.shape Q = np.cov(returns) means = np.mean(returns, axis=1) - + last_x = (1./N)*np.ones((N, 1)) def f(x_mat=None, z=None): if x_mat is None: x0 = (1./N)*np.ones((N, 1)) return 0, matrix(x0) else: x = np.asmatrix(x_mat) + last_x = x sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) # Needs more... dF = np.transpose(means*(1.0/np.sqrt(np.transpose(x)*Q*x))) + \ @@ -35,16 +36,20 @@ def f(x_mat=None, z=None): A = np.ones((1, N)) b = [1.0] + solvers.options['maxiters'] = 500 + try: + sol = solvers.cp( + f, + G=matrix(G), + h=matrix(h), + A=matrix(A), + b=matrix(b), + ) + print sol['status'] + return sol['x'] + except ZeroDivisionError, e: + return last_x - sol = solvers.cp( - f, - G=matrix(G), - h=matrix(h), - A=matrix(A), - b=matrix(b) - ) - #print sol['status'] - return sol['x'] if __name__ == '__main__': rets = { @@ -73,7 +78,6 @@ def f(x_mat=None, z=None): r.append([1.0+i/100 for i in rets[k]]) means = np.mean(r, axis=1) steps = np.linspace(0.0, 1.0, num=50) - xs = map(lambda x: np.transpose(opt_sharpe(r, x))[0], steps) for i, row in enumerate(xs): print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) From 3cc019c2e8e9aaa69e965f562f394fce97170bd9 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 17 Mar 2016 22:27:32 -0400 Subject: [PATCH 05/15] I think cvxopt has a bug when gap and relative gap = 0, will investigate --- optimization/opt_sharpe.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index a1be2d2..ed774ab 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -37,18 +37,15 @@ def f(x_mat=None, z=None): A = np.ones((1, N)) b = [1.0] solvers.options['maxiters'] = 500 - try: - sol = solvers.cp( - f, - G=matrix(G), - h=matrix(h), - A=matrix(A), - b=matrix(b), - ) - print sol['status'] - return sol['x'] - except ZeroDivisionError, e: - return last_x + sol = solvers.cp( + f, + G=matrix(G), + h=matrix(h), + A=matrix(A), + b=matrix(b), + ) + print sol['status'] + return sol['x'] if __name__ == '__main__': From 6e1f7d141f7088e650aef52fc6286e5458e4df98 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Fri, 18 Mar 2016 21:58:46 -0400 Subject: [PATCH 06/15] Shut off output from solver --- optimization/opt_sharpe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index ed774ab..b69575a 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -1,7 +1,7 @@ import numpy as np from cvxopt import solvers, matrix -#solvers.options["show_progress"] = False +solvers.options["show_progress"] = False def opt_sharpe(returns, r_f=1, short_sales=False): returns = np.asmatrix(returns) From 373b3bbc6663a10627dce57b2d073c5e7cb611c8 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Fri, 18 Mar 2016 22:23:36 -0400 Subject: [PATCH 07/15] moved maxiters out of func --- optimization/opt_sharpe.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index b69575a..c330295 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -2,6 +2,7 @@ from cvxopt import solvers, matrix solvers.options["show_progress"] = False +solvers.options['maxiters'] = 500 def opt_sharpe(returns, r_f=1, short_sales=False): returns = np.asmatrix(returns) @@ -36,7 +37,7 @@ def f(x_mat=None, z=None): A = np.ones((1, N)) b = [1.0] - solvers.options['maxiters'] = 500 + sol = solvers.cp( f, G=matrix(G), @@ -44,7 +45,7 @@ def f(x_mat=None, z=None): A=matrix(A), b=matrix(b), ) - print sol['status'] + print r_f, sol['status'] return sol['x'] From 7357eba89ed67675a62ff11ab7ff4713fa814778 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Tue, 29 Mar 2016 23:19:05 -0400 Subject: [PATCH 08/15] Switched to scipy.optimize.fmin over cvxopt as I don't need first and second order info --- optimization/opt_sharpe.py | 41 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index c330295..e47f6e9 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -1,6 +1,6 @@ import numpy as np from cvxopt import solvers, matrix - +import scipy.optimize solvers.options["show_progress"] = False solvers.options['maxiters'] = 500 @@ -30,23 +30,32 @@ def f(x_mat=None, z=None): (r_f - means.T*x)*(-1/(2*float(x.T*Q*x)**(2/3)))+(Q.T + Q).T + \ (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) return sharpe, matrix(dF), matrix(H) + def f2(x): + t = list(x) + t.append(1-sum(t)) + x = np.asmatrix(t).T + sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) + return sharpe + #G = np.zeros((1, N)) + #h = [0.0] - G = np.zeros((1, N)) - h = [0.0] - - A = np.ones((1, N)) - b = [1.0] + #A = np.ones((1, N)) + #b = [1.0] - sol = solvers.cp( - f, - G=matrix(G), - h=matrix(h), - A=matrix(A), - b=matrix(b), - ) - print r_f, sol['status'] - return sol['x'] + #sol = solvers.cp( + # f, + # G=matrix(G), + # h=matrix(h), + # A=matrix(A), + # b=matrix(b), + #) + #print r_f, sol['status'] + #return sol['x'] + + sol = list(scipy.optimize.fmin(f2, scipy.ones(N-1, dtype=float) * 1./N, disp=False, full_output=False)) + sol.append(1-sum(sol)) + return sol if __name__ == '__main__': @@ -76,7 +85,7 @@ def f(x_mat=None, z=None): r.append([1.0+i/100 for i in rets[k]]) means = np.mean(r, axis=1) steps = np.linspace(0.0, 1.0, num=50) - xs = map(lambda x: np.transpose(opt_sharpe(r, x))[0], steps) + xs = map(lambda x: np.transpose(opt_sharpe(r, x)), steps) for i, row in enumerate(xs): print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) From 1bc43699038e418c081b79362f83aa5c590c3f88 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Tue, 5 Apr 2016 22:11:01 -0400 Subject: [PATCH 09/15] Commented out cvxopt func --- optimization/opt_sharpe.py | 43 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index e47f6e9..5350a11 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -10,26 +10,28 @@ def opt_sharpe(returns, r_f=1, short_sales=False): Q = np.cov(returns) means = np.mean(returns, axis=1) last_x = (1./N)*np.ones((N, 1)) - def f(x_mat=None, z=None): - if x_mat is None: - x0 = (1./N)*np.ones((N, 1)) - return 0, matrix(x0) - else: - x = np.asmatrix(x_mat) - last_x = x - sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) - # Needs more... - dF = np.transpose(means*(1.0/np.sqrt(np.transpose(x)*Q*x))) + \ - (r_f-np.transpose(means)*x)*(-1/(2*float(np.transpose(x)*Q*x)**(2.0/3)))*np.transpose(x)*(Q + np.transpose(Q)) - if z is None: - # Return val, d(f)/dx - return sharpe, matrix(dF) - else: - # Return val, d(f)/dx, and hessian - H = means*(-1/(float(x.T*Q*x)**(2/3)))*x.T*(Q.T + Q) + \ - (r_f - means.T*x)*(-1/(2*float(x.T*Q*x)**(2/3)))+(Q.T + Q).T + \ - (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) - return sharpe, matrix(dF), matrix(H) + #def f(x_mat=None, z=None): + # if x_mat is None: + # x0 = (1./N)*np.ones((N, 1)) + # return 0, matrix(x0) + # else: + # x = np.asmatrix(x_mat) + # last_x = x + # sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) + # # Needs more... + # dF = np.transpose(means*(1.0/np.sqrt(np.transpose(x)*Q*x))) + \ + # (r_f-np.transpose(means)*x)*(-1/(2*float(np.transpose(x)*Q*x)**(2.0/3)))*np.transpose(x)*(Q + np.transpose(Q)) + # if z is None: + # # Return val, d(f)/dx + # return sharpe, matrix(dF) + # else: + # # Return val, d(f)/dx, and hessian + # H = means*(-1/(float(x.T*Q*x)**(2/3)))*x.T*(Q.T + Q) + \ + # (r_f - means.T*x)*(-1/(2*float(x.T*Q*x)**(2/3)))+(Q.T + Q).T + \ + # (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) + # return sharpe, matrix(dF), matrix(H) + + def f2(x): t = list(x) t.append(1-sum(t)) @@ -37,6 +39,7 @@ def f2(x): sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) return sharpe + #G = np.zeros((1, N)) #h = [0.0] From 21271f5ef1b7e94c37a678da6191bf86a86a2b97 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 20:05:35 -0400 Subject: [PATCH 10/15] Moved transform code out --- optimization/opt_sharpe.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index 5350a11..15f49bc 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -31,11 +31,13 @@ def opt_sharpe(returns, r_f=1, short_sales=False): # (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) # return sharpe, matrix(dF), matrix(H) - - def f2(x): + def transform_input(x): t = list(x) t.append(1-sum(t)) - x = np.asmatrix(t).T + return t + + def neg_sharpe(x): + x = np.asmatrix(transform_input(x)).T sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) return sharpe @@ -56,9 +58,12 @@ def f2(x): #print r_f, sol['status'] #return sol['x'] - sol = list(scipy.optimize.fmin(f2, scipy.ones(N-1, dtype=float) * 1./N, disp=False, full_output=False)) - sol.append(1-sum(sol)) - return sol + sol = scipy.optimize.fmin( + neg_sharpe, + scipy.ones(N-1, dtype=float) * 1./N, + disp=1, + full_output=False) + return transform_input(sol) if __name__ == '__main__': @@ -87,7 +92,7 @@ def f2(x): for k in sorted(rets.keys()): r.append([1.0+i/100 for i in rets[k]]) means = np.mean(r, axis=1) - steps = np.linspace(0.0, 1.0, num=50) + steps = np.linspace(0.0, max(means), num=50) xs = map(lambda x: np.transpose(opt_sharpe(r, x)), steps) for i, row in enumerate(xs): print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) From 1b838affb60fcb93427f2f6b39a2c5d9ca2a28ea Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 20:47:49 -0400 Subject: [PATCH 11/15] Minor tweak --- optimization/opt_sharpe.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index 15f49bc..32ca29c 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -61,8 +61,9 @@ def neg_sharpe(x): sol = scipy.optimize.fmin( neg_sharpe, scipy.ones(N-1, dtype=float) * 1./N, - disp=1, - full_output=False) + disp=False, + full_output=False, + maxfun=1000) return transform_input(sol) @@ -92,7 +93,8 @@ def neg_sharpe(x): for k in sorted(rets.keys()): r.append([1.0+i/100 for i in rets[k]]) means = np.mean(r, axis=1) - steps = np.linspace(0.0, max(means), num=50) + + steps = np.linspace(0.0, min(means), num=100) xs = map(lambda x: np.transpose(opt_sharpe(r, x)), steps) for i, row in enumerate(xs): print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) From 8fe252cd33e112c4045fa4f2f07381618fb57c55 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 20:51:00 -0400 Subject: [PATCH 12/15] Code cleanup, need to break out test --- optimization/opt_sharpe.py | 49 ++++---------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py index 32ca29c..6b1f107 100644 --- a/optimization/opt_sharpe.py +++ b/optimization/opt_sharpe.py @@ -6,30 +6,9 @@ def opt_sharpe(returns, r_f=1, short_sales=False): returns = np.asmatrix(returns) - N, T = returns.shape - Q = np.cov(returns) + num_stocks, _ = returns.shape + covar = np.cov(returns) means = np.mean(returns, axis=1) - last_x = (1./N)*np.ones((N, 1)) - #def f(x_mat=None, z=None): - # if x_mat is None: - # x0 = (1./N)*np.ones((N, 1)) - # return 0, matrix(x0) - # else: - # x = np.asmatrix(x_mat) - # last_x = x - # sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) - # # Needs more... - # dF = np.transpose(means*(1.0/np.sqrt(np.transpose(x)*Q*x))) + \ - # (r_f-np.transpose(means)*x)*(-1/(2*float(np.transpose(x)*Q*x)**(2.0/3)))*np.transpose(x)*(Q + np.transpose(Q)) - # if z is None: - # # Return val, d(f)/dx - # return sharpe, matrix(dF) - # else: - # # Return val, d(f)/dx, and hessian - # H = means*(-1/(float(x.T*Q*x)**(2/3)))*x.T*(Q.T + Q) + \ - # (r_f - means.T*x)*(-1/(2*float(x.T*Q*x)**(2/3)))+(Q.T + Q).T + \ - # (Q.T + Q).T*(1/(4*float(x.T*Q*x)**(4/3))) - # return sharpe, matrix(dF), matrix(H) def transform_input(x): t = list(x) @@ -38,32 +17,14 @@ def transform_input(x): def neg_sharpe(x): x = np.asmatrix(transform_input(x)).T - sharpe = (r_f-np.transpose(means)*x)/np.sqrt(np.transpose(x)*Q*x) + sharpe = (r_f-means.T*x)/np.sqrt(x.T*covar*x) return sharpe - - #G = np.zeros((1, N)) - #h = [0.0] - - #A = np.ones((1, N)) - #b = [1.0] - - #sol = solvers.cp( - # f, - # G=matrix(G), - # h=matrix(h), - # A=matrix(A), - # b=matrix(b), - #) - #print r_f, sol['status'] - #return sol['x'] - sol = scipy.optimize.fmin( neg_sharpe, - scipy.ones(N-1, dtype=float) * 1./N, + scipy.ones(num_stocks-1, dtype=float) * 1./num_stocks, disp=False, - full_output=False, - maxfun=1000) + full_output=False) return transform_input(sol) From 60fc1084b6abb393f0ceaf9a84e4636d60757299 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 21:22:25 -0400 Subject: [PATCH 13/15] Cleaned up sharpe, will put in PR --- optimization/opt_sharpe.py | 62 -------------------------------------- optimization/sharpe_opt.py | 38 +++++++++++++++++++++++ tests/test_optimization.py | 18 ++++++++++- 3 files changed, 55 insertions(+), 63 deletions(-) delete mode 100644 optimization/opt_sharpe.py create mode 100644 optimization/sharpe_opt.py diff --git a/optimization/opt_sharpe.py b/optimization/opt_sharpe.py deleted file mode 100644 index 6b1f107..0000000 --- a/optimization/opt_sharpe.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np -from cvxopt import solvers, matrix -import scipy.optimize -solvers.options["show_progress"] = False -solvers.options['maxiters'] = 500 - -def opt_sharpe(returns, r_f=1, short_sales=False): - returns = np.asmatrix(returns) - num_stocks, _ = returns.shape - covar = np.cov(returns) - means = np.mean(returns, axis=1) - - def transform_input(x): - t = list(x) - t.append(1-sum(t)) - return t - - def neg_sharpe(x): - x = np.asmatrix(transform_input(x)).T - sharpe = (r_f-means.T*x)/np.sqrt(x.T*covar*x) - 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) - - -if __name__ == '__main__': - rets = { - 'Stocks': [ - 26.81, -8.78, 22.69, 16.36, 12.36, -10.10, 23.94, 11.00, -8.47, 3.94, 14.30, 18.99, - -14.69, -26.47, 37.23, 23.93, -7.16, 6.57, 18.61, 32.50, -4.92, 21.55, 22.56, 6.27, - 31.17, 18.67, 5.25, 16.61, 31.69, -3.10, 30.46, 7.62, 10.08, 1.32, 37.58, 22.96, - 33.36, 28.58, 21.04, -9.10, -11.89, -22.10, 28.68 - ], - 'Bonds': [ - 2.20, 5.72, 1.79, 3.71, 0.93, 5.12, -2.86, 2.25, -5.63, 18.92, 11.24, 2.39, 3.29, - 4.00, 5.52, 15.56, 0.38, -1.26, -1.26, -2.48, 4.04, 44.28, 1.29, 15.29, 32.27, - 22.39, -3.03, 6.84, 18.54, 7.74, 19.36, 7.34, 13.06, -7.32, 25.94, 0.13, 12.02, - 14.45, -7.51, 17.22, 5.51, 15.15, 0.54 - ], - 'MM': [ - 2.33, 2.93, 3.38, 3.85, 4.32, 5.40, 4.51, 6.02, 8.97, 4.90, 4.14, 5.33, 9.95, 8.53, - 5.20, 4.65, 6.56, 10.03, 13.78, 18.90, 12.37, 8.95, 9.47, 8.38, 8.27, 6.91, 6.77, - 8.76, 8.45, 7.31, 4.43, 2.92, 2.96, 5.45, 5.60, 5.29, 5.50, 4.68, 5.30, 6.40, 1.82, - 1.24, 0.98 - ] - } - - r = [] - for k in sorted(rets.keys()): - r.append([1.0+i/100 for i in rets[k]]) - means = np.mean(r, axis=1) - - steps = np.linspace(0.0, min(means), num=100) - xs = map(lambda x: np.transpose(opt_sharpe(r, x)), steps) - for i, row in enumerate(xs): - print "Rf:", steps[i], map(lambda x: "%0.3f"%x, row) - diff --git a/optimization/sharpe_opt.py b/optimization/sharpe_opt.py new file mode 100644 index 0000000..bb44323 --- /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..912661f 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,25 @@ 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]) From 42b680f649e6e981cc36a25476c83ab5f3a0c0d0 Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 21:38:16 -0400 Subject: [PATCH 14/15] Fixed overindeneted line --- optimization/sharpe_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimization/sharpe_opt.py b/optimization/sharpe_opt.py index bb44323..1792eec 100644 --- a/optimization/sharpe_opt.py +++ b/optimization/sharpe_opt.py @@ -27,7 +27,7 @@ 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) + / np.sqrt(dec_vars.T * covar * dec_vars) return sharpe sol = scipy.optimize.fmin( From c8e0c60a7396475c65b3284c6e1566ad6678182a Mon Sep 17 00:00:00 2001 From: Jack Lamberti Date: Thu, 7 Apr 2016 21:39:14 -0400 Subject: [PATCH 15/15] Shrunk line len --- tests/test_optimization.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 912661f..9bbdbae 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -66,8 +66,10 @@ def smoke_test2(optimizers): 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] + 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)