0

Let me start by saying I am new to Stack Exchange sites, so apologies if this turns out to be a low quality question.

I am trying to run a mean-variance portfolio optimization using Solve.QP in R and have looked at several previous questions regarding this on Stack Overflow to figure out how to do this correctly but with no luck.

The constraints I want are as follows:

  1. Long/Short portfolio: weights may be negative
  2. Market neutral: Sum of weights = 0
  3. No leverage: Every weight is bound between -1 and 1 + sum of all positive weights = 1 and sum of all negative weights = -1

I am running this optimization with approx. 100 assets, so for demonstration purposes, I have reproduced the optimization part of the code with 5 assets (using fake data). Full code is found at the end of the post.

Let's say I have the following 5 expected returns for my 5 assets

> dvec
[1]  1.0791124  0.9088370  0.0992847 -0.2028962 -1.1632315

Two assets are expected to perform negatively, whilst 3 are expected to give a positive return.

Amat and bvec then looks like this

> cbind(amat, bvec)
                     bvec
 [1,]  1  1  1  1  1    0
 [2,]  1  1  1  0  0    1
 [3,]  0  0  0  1  1   -1
 [4,]  1  0  0  0  0   -1
 [5,]  0  1  0  0  0   -1
 [6,]  0  0  1  0  0   -1
 [7,]  0  0  0  1  0   -1
 [8,]  0  0  0  0  1   -1
 [9,] -1  0  0  0  0   -1
[10,]  0 -1  0  0  0   -1
[11,]  0  0 -1  0  0   -1
[12,]  0  0  0 -1  0   -1
[13,]  0  0  0  0 -1   -1
  • First row indicates the market neutrality constraint
  • Second row indicates the long leg of the portfolio must have weights that add to 1
  • Third row indicates the short leg must have weights that add to -1
  • Next 5 rows indicates that each individual weight must be >= -1
  • Last 5 rows indicates that each individual weight must be <= 1

The optimization runs without throwing an error, but the leverage constraint is (not always) enforced

> sum(w_opt[w_opt>0])
[1] 1.22445
> sum(w_opt[w_opt<0])
[1] -1.22445

Where w_opt contains the optimized weights.

> w_opt
[1]  0.73304775  0.49140239 -0.22445014 -0.01629763 -0.98370237

I have set meq=3 to specify the three first constraints are equalities.

library(quadprog)

omega <- cov(data.frame(x=rnorm(200), y=rnorm(200), z=rnorm(200), p=rnorm(200), q=rnorm(200)))

N <- 5
maxw <- 1
minw <- -1
dvec <- rnorm(N)
dvec <- sort(dvec, decreasing=TRUE)
dmat <- omega
amat <- rbind(1, c(rep(1, length(dvec[dvec>0])),rep(0, length(dvec[dvec<0]))), c(rep(0,length(dvec[dvec>0])),rep(1, length(dvec[dvec<0]))), diag(N), -diag(N))
bvec <- c(0,1,-1, rep(minw,N), -rep(maxw, N))
meq <- 3

w_opt <- solve.QP(Dmat=dmat, dvec=dvec, Amat=t(amat), bvec=bvec, meq=meq)$solution

sum(w_opt[w_opt>0])
sum(w_opt[w_opt<0])
  • You may want to try CVXR. This supports different QP solvers and also the modeling is more intuitive. – Erwin Kalvelagen Jun 25 '21 at 10:26
  • @ErwinKalvelagen Thanks for the suggestion! I am not really a fan of solve.QP either but I am not entirely sure how viable it is for me to switch to anything else (this is work-related). I'll look into it though, thanks a lot! – blacksiddis Jun 25 '21 at 16:10
  • For commercial applications, you may want to consider commercial solvers. They are often much more reliable than freebies (just more engineering has gone into these solvers). But for prototypes, CVXR with standard open-source solvers is absolutely great. – Erwin Kalvelagen Jun 25 '21 at 17:23
  • @ErwinKalvelagen Yeah, for actual business purposes, we use a commercial one. This is for an analytics project. I switched to CVXR and it is indeed much more intuitive, thanks a lot! – blacksiddis Jun 27 '21 at 00:52

0 Answers0