quadprog optimization
Asked Answered
D

3

9

Here's an interesting puzzle.

Below is an R snippet that identifies the tangency point of a quadratic function with respect to a line drawn from the point (0,rf) on the y-axis.

For those familiar with portfolio theory, this point is in return and risk space and the solution is set of weights that define the tangency portfolio (max sharpe ratio). The snippet allows for negative weights (i.e. shorts) and there is one equality weight constraint which requires the sum of the weights = 1.

require(quadprog)

# create artifical data
nO     <- 100     # number of observations
nA     <- 10      # number of assets
mData  <- array(rnorm(nO * nA, mean = 0.001, sd = 0.01), dim = c(nO, nA))
rf     <- 0.0001     # riskfree rate (2.5% pa)
mu     <- apply(mData, 2, mean)    # means
mu2    <- mu - rf                  # excess means

# qp
aMat  <- as.matrix(mu2)
bVec  <- 1 # set expectation of portfolio excess return to 1
zeros <- array(0, dim = c(nA,1))
solQP <- solve.QP(cov(mData), zeros, aMat, bVec, meq = 1)

# rescale variables to obtain weights
w <- as.matrix(solQP$solution/sum(solQP$solution))

# compute sharpe ratio
SR <- t(w) %*% mu2 / sqrt(t(w) %*% cov(mData) %*% w)

My question -- how to adapt the code to solve for the optimal set of weights such that the sum of weights sum to an arbitrary number (including the corner case of a self-financing portfolio where the sum of weights = 0) as opposed to unity?

Alternatively, you might consider adding an element 'cash' to the covariance matrix with variance-covariance of 0, and add an equality constraint requiring the weight on cash = 1. However this matrix would be not be positive semi-definite. Also I suspect the non-cash weights might be trivially zero.

Disable answered 10/5, 2012 at 1:5 Comment(2)
Within your solveQP call, I am not seeing a constraint enforcing that the weights sum to one. Instead, aMat, bVec, meq = 1 requires that your portfolio excess mean be one, which you can check with sum(aMat * solQP$solution). Inside your solve.QP call, should you not be using a vector of ones instead of aMat?Binkley
@Binkley - you are correct. Typo fixed, good catchDisable
T
9

Let us first explain why this actually produces the maximum Sharpe ratio portfolio.

We want w to maximize w' mu / sqrt( w' V w ). But that quantity is unchanged if we multiply w by a number (it is "homogeneous of degree 0"): we can therefore impose w' mu = 1, and the problem of maximizing 1 / sqrt( w' V w ) is equivalent to minimizing w' V w. The maximum Sharpe ratio portfolio is not unique: they form a line. If we want the weights to sum up to 1 (or any other non-zero number), we just have to rescale them.

If we want the weights to sum up to 0, we can add that constraint to the problem -- it only works because the constraint is also homogeneous of degree 0. You will still need to rescale the weights, e.g., to be 100% long and 100% short.

solQP <- solve.QP(cov(mData), zeros, 
  cbind(aMat,1), 
  c(bVec,0), 
  meq = 2
)

# Let us compare with another solver
V <- cov(mData)
library(Rsolnp)
r <- solnp(
  rep(1/length(mu), length(mu)),
  function(w) - t(w) %*% mu2 / sqrt( t(w) %*% V %*% w ),
  eqfun = function(w) sum(w),
  eqB   = 0,
  LB = rep(-1, length(mu))
)
solQP$solution / r$pars  # constant
Torpid answered 10/5, 2012 at 3:33 Comment(0)
B
2

Looking at the link you have included. Apparently, the role of aMat, bVec, meq = 1 inside the solve.QP call is to fix the value of the numerator (your return) in the Sharpe ratio formula, so the optimization is focused on minimizing the denominator. In a sense, it is perfectly legal to fix the numerator, it is like fixing the total size of your portfolio. Your portfolio can later be scaled up or down, it will keep the same Sharpe ratio. To help you realize that, you can run your code above for any value of bVec (granted, other than zero) and you will get the same result for the weights w and the Sharpe ratio SR.

So I feel you might be misinterpreting the notion of "portfolio weights". They are ratios representing what your portfolio is made of, and they should sum to one. Once you have found the optimal weights, which you already did, you are free to scale your portfolio to whatever level you want, just multiply w by the current value you want for your portfolio.

Binkley answered 10/5, 2012 at 3:33 Comment(0)
L
1

This is not a good technique for long portfolios. Even portfolios than can short stocks have allocations weights of the wrong sign after normalizing by the sum of weights.

These situations arise with negative excess returns. Forcing w'mu = 1 puts the solution to the left of the origin (negative risk) in these cases.

library(quadprog)
nA = 2 # two assets
mu2 = c(-.1,.1) # one negative excess return
Dmat = matrix(c(1,0,0,10),2,2)

aMat  <- as.matrix(mu2)
bVec  <- 1 # set expectation of portfolio excess return to 1
zeros <- array(0, dim = c(nA,1))
solQP <- solve.QP(Dmat, zeros, aMat, bVec, meq = 1)

rawW = solQP$solution
cat('\nraw weights ')
cat(rawW)

netW = rawW/sum(rawW)
cat('\nnormalized weights ')
cat(netW)

portfReturn = sum(netW*mu2)
cat('\nportfolio excess return ')
cat(portfReturn)
Lingua answered 3/12, 2015 at 18:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.