# Compute tangency portfolio with asset allocation constraints

I am looking to compute the tangency portfolio of the efficient frontier, but taking into account min_allocations and max_allocations for asset weights in the portfolio. These constraints make me think I need to use an optimization tool such as cvxopt. The tangency portfolio is the portfolio that maximizes the Sharpe ratio and I believe computing the tangency portfolio requires the inputs compute_tanp(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf). These lecture notes are able to transform the optimization problem above to the standard quadratic format below, but I am not exactly sure how to properly form the matrices for this approach. How do I form the matrices to properly use cvoxpt to find the portfolio with the max Sharpe ratio? I am also open to other techniques to calculate the tangency portfolio with constraints.

Below I have a working function that will find the efficient portfolio weights $$W$$ when passed a desired target return. It uses cvxopt to handle optimization of the form: import pandas as pd
import numpy as np
import cvxopt as opt

def compute_ep(target_ret, exp_ret_vec, cov_mat, min_allocations, max_allocations):
"""
computes efficient portfolio with min variance for given target return

"""
# number of assets
n = len(exp_ret_vec)

one_vec = np.ones(n)

# objective
# minimize (0.5)x^TPx _ q^Tx
P = opt.matrix(cov_mat.values) # covariance matrix
q = opt.matrix(np.zeros(n)) # zero

# constraints Gx <= h
# >= target return, >= min allocations, <= max allocations
G = opt.matrix(np.vstack((-exp_ret_vec,-np.identity(n), np.identity(n))))

h = opt.matrix(np.hstack((-target_ret,-min_allocations, max_allocations)))

# constraints Ax = b
A = opt.matrix(np.ones(n)).T
b = opt.matrix(1.0) # sum(w) = 1; not market-netural

# convex optimization
opt.solvers.options['show_progress'] = False
sol = opt.solvers.qp(P, q, G, h, A, b)

weights = pd.Series(sol['x'], index = cov_mat.index)

w = pd.DataFrame(weights, columns=['weight'])

return(w)


There are two transformations of the input data to be made to go from the first problem to the second:

• the $$\hat{\mu}$$ are found by subtracting the scalar $$r_f$$ from all the $$\mu$$ vector components: $$\hat{\mu}=\mu-r_f=(\mu_1-r_f,\mu_2-r_f,\cdots,\mu_N-r_f)^T$$

in other words the $$\mu$$ are returns and the $$\hat\mu$$ are "excess returns".

• the $$\hat{A}$$ matrix is found by subtracting the $$b$$ column vector from each column of the $$A$$ matrix, i.e. $$\hat{a}_{ij}=a_{ij}-b_i$$

• the $$Q$$ matrix (covariance matrix) is unchanged in problem 2 compared to problem 1

Once you solve problem 2, you have the optimal $$y$$. You can find the optimal $$x$$ for Problem 1 by doing $$x=\frac{y}{1^T y}$$. This makes the x components add up to 1 (as desired) even though the y components do not.

HTH

(I don't know R and cvxopt well enough to write the code, but it should be straightfoward).

• What does the $b$ column vector refer to in this case? – cpage Nov 25 '18 at 2:18
• The b column is on the right side of the inequality constraint $A x >= b$ in Problem 1. These are for example limits on exposure to certain stocks or stock sectors, etc. – Alex C Nov 25 '18 at 2:33
• For example if the weight of the first stock must be between 0.05 and 0.2, that would be two constraints $x_1>= 0.05$ and $-x_1 >= -0.2$ which would be expressed in the A matrix and the b vector. – Alex C Nov 25 '18 at 3:01

If you are willing to switch to CVXPY, it comes with a pretty example of exactly this exercise:

http://nbviewer.jupyter.org/github/cvxgrp/cvx_short_course/blob/master/applications/portfolio_optimization.ipynb

The simplest is to get the admissible return range using the cvxopt optimizer with $$q = \alpha \mu$$ and $$q = -\alpha \mu$$ for a large $$\alpha$$ instead of $$q=0$$ and then run the function compute_ep iteratively to find the portfolio with the highest Sharpe ratio in this range.

Using code similar to the one posted above, one can get the bounds and optimal Sharpe ratio as follow:

def compute_ep_bound(alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations):
n = len(exp_ret_vec)
one_vec = np.ones(n)
P = opt.matrix(cov_mat.values) # covariance matrix
q = opt.matrix(exp_ret_vec * alpha) # zero
G = opt.matrix(np.vstack((-np.identity(n), np.identity(n))))
h = opt.matrix(np.hstack((-min_allocations, max_allocations)))
A = opt.matrix(np.ones(n)).T
b = opt.matrix(1.0) # sum(w) = 1; not market-netural
opt.solvers.options['show_progress'] = False
sol = opt.solvers.qp(P, q, G, h, A, b)
weights = pd.Series(sol['x'], index = cov_mat.index)
w = pd.DataFrame(weights, columns=['weight'])
return np.dot(mu,sol['x'])

def compute_tanp(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf):
alpha = 10000
e1 = compute_ep_bound(alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations)
e2 = compute_ep_bound(-alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations)
sharpemax = -1e300
wmax = 1
for target_ret in np.linspace(e2,e1):
w = compute_ep(target_ret, exp_ret_vec, cov_mat, min_allocations, max_allocations)
sharpe = (np.dot(mu,w)-rf)/np.dot(w.T,np.dot(cov_mat,w))
if sharpe > sharpemax:
print("sharpe=%f" % sharpe)
wmax = w
sharpemax = sharpe
return wmax


The alternative presented in the paper is to compute the maximum Sharpe ratio portfolio directly using the equivalent problem $$\hat{\mu}^t y = 1$$ and $$\hat{A} y > 0$$:

def compute_tanp_direct(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf):
# number of assets
n = len(exp_ret_vec)
mu_hat = exp_ret_vec - rf

# objective minimize (0.5)x^TPx _ q^Tx
P = opt.matrix(cov_mat.values) # covariance matrix
q = opt.matrix(np.zeros(n)) # zero

# constraints Gx <= h: -hatA y > 0 matrix
G = opt.matrix(np.vstack((-np.identity(n)+min_allocations, np.identity(n)-max_allocations)))
h = opt.matrix(np.zeros(2*n))

# constraints Ax = b: hatmu . y = 1
A = opt.matrix(mu_hat).T
b = opt.matrix(1.0)

# convex optimization
opt.solvers.options['show_progress'] = False
sol = opt.solvers.qp(P, q, G, h, A, b)

weights = pd.Series(sol['x'], index = cov_mat.index)
w = pd.DataFrame(weights, columns=['weight'])
w = w/np.dot(np.ones(n), w)
sharpe = (np.dot(exp_ret_vec,w)-rf)/np.sqrt(np.dot(w.T,np.dot(cov_mat,w)))
print("sharpe=%f" % sharpe)
return(w)


I verified on a practical example with 6 assets that the iterative methods converges towards the optimal Sharpe ratio computed directly.