I am able to use cvxopt to calculate an efficient frontier, per the docs:
http://cvxopt.org/examples/book/portfolio.html
However, I cannot figure out how to add a constraint so that there is an upper bound on a particular asset's maximum allowed weight. Is that possible using cvxopt?
Here is my code so far that produces an efficient frontier with no constraints, except I believe b, which sets the max sum of weights to 1. I'm not sure what G, h, A, and mus do, and the docs don't really explain. Where does the 10**(5.0*t/N-1.0) in the formula for mus come from?
from math import sqrt
from cvxopt import matrix
from cvxopt.blas import dot
from cvxopt.solvers import qp, options
# Number of assets
n = 4
# Convariance matrix
S = matrix( [[ 4e-2, 6e-3, -4e-3, 0.0 ],
[ 6e-3, 1e-2, 0.0, 0.0 ],
[-4e-3, 0.0, 2.5e-3, 0.0 ],
[ 0.0, 0.0, 0.0, 0.0 ]] )
# Expected return
pbar = matrix([.12, .10, .07, .03])
# nxn matrix of 0s
G = matrix(0.0, (n,n))
# Convert G to negative identity matrix
G[::n+1] = -1.0
# nx1 matrix of 0s
h = matrix(0.0, (n,1))
# 1xn matrix of 1s
A = matrix(1.0, (1,n))
# scalar of 1.0
b = matrix(1.0)
N = 100
mus = [ 10**(5.0*t/N-1.0) for t in range(N) ]
options['show_progress'] = False
xs = [ qp(mu*S, -pbar, G, h, A, b)['x'] for mu in mus ]
returns = [ dot(pbar,x) for x in xs ]
risks = [ sqrt(dot(x, S*x)) for x in xs ]
#Efficient frontier
plt.plot(risks, returns)
You are using the quadratic programming solver of the cvxopt package, check out the documentation.
As you can see from the formula there, Gx <= h
are the inequality constraints and Ax = b
are the equality constraints. G
and A
are matrices, while h
and b
and are vectors.
Let's assume you want to restrict you're first asset to weights between 2% and 5%, you would formulate this as follows:
G = matrix([[-1, 0, 0, 0],
[ 1, 0, 0, 0]])
h = matrix([[-0.02],
[0.05]])
Note that the first row is turning the inequality condition into Gx >= h
by multiplying by -1.
The conditions you have above set all assets to bounds between 0% and 100%, the common no short selling condition.
The formula your using is explained here: your mu
is q
in the Wikipedia article and therefore a risk-tolerance parameter. It doesn't really matter if you attach it to the covariance or return part of the target function. It just means that you are either walking from the top right corner to the bottom left or the other way round for each value of mu
. So it's just a function that gives you a risk-tolerance from very small to very big. I think there is a mistake though in that the series start at 0.1 but should really start at 0.