embotech/ecos

How to setup an SOCP with multiple conic constraints?

Closed this issue · 1 comments

Hi,

I am trying to solve a portfolio optimization problem using ecos in R. The objective functions is to maximize the portfolio return, subject to bounds on the weights, a portfolio volatility constraint (weights %% covariance matrix %% weights), and a tracking error constraint ( (portfolio weights - benchmark weights) %% covariance matrix %% (portfolio weights - benchmark weights ) ).

My issue is that I cannot get ecos to work with these two conic constraints simultaneously(i.e. one for portfolio volatility, and one for tracking error). I can get it working perfectly using either of these constraints on their own, however.

For example, here's how to solve the problem using only the portfolio volatility constraint:

The decision variables are

x = portfolio weights
w_a = active weights (not used for now)
r = portfolio return (what is being maximized)
f = portfolio risk
g = asset contribution to portfolio risk

and f >= g is the conic constraint

require(Matrix)
require(ECOSolveR)




# create returns data
ret <- cbind(rnorm(1000,.05/252,.15/sqrt(252)),
             rnorm(1000,.03/252,.12/sqrt(252)),
             rnorm(1000,.06/252,.16/sqrt(252)),
             rnorm(1000,.08/252,.15/sqrt(252)),
             rnorm(1000,.03/252,.15/sqrt(252)))


# number of assets / factors
n <- ncol(ret)

# treat other factor returns as contrast to baseline factor
ret[,2:n] <- ret[,2:n] - ret[,1]


# covariance matrix
covmat <- cov(ret)


# mu
mu <- colMeans(ret)


# benchmark weights
w_b <- c(1,runif(4))

# factorize covariance matrix
G <- chol(covmat)


# define equality matrix
Amat <- rbind(
  cbind(mu,rep(0,n), -1, 0, Matrix(0,ncol = n,sparse = T)),      # portfolio return            = r
  cbind(diag(n),-diag(n), rep(0,n),rep(0,n),Matrix(0,n,n)),      # active weights              = w_a
  cbind(G,Matrix(0,n,n), rep(0,n), rep(0,n), -diag(n)),          # portfolio risk              = g
  cbind(Matrix(c(1,rep(0,n-1)),n),                               # baseline factor loading     = 1
        Matrix(0,n,n-1),
        Matrix(0,n,n),
        Matrix(0,n),
        Matrix(0,n),
        Matrix(0,n,n))
)

bvec <- c(0,              # portfolio return        = t
          w_b,            # benchmark weights
          rep(0,n),       # portfolio risk          = g
          c(1,rep(0,n-1)) # baseline factor loading = 1
          )


# define inequality matrix

Gmat <- rbind(
  cbind(-diag(n), Matrix(0,n,n), rep(0,n),rep(0,n), Matrix(0,n,n)),    # weight lower bounds
  cbind(diag(n), Matrix(0,n,n), rep(0,n),rep(0,n) ,Matrix(0,n,n)),     # weight upper bounds
  cbind(rep(0,n),rep(0,n), 0, 1,Matrix(0,ncol = n)),                   # f <= maxRisk
  cbind(rep(0,n), rep(0,n) ,0, -1,Matrix(0,ncol = n)),                 # f >= g
  cbind(Matrix(0,n,n), Matrix(0,n,n),rep(0,n), rep(0,n), -diag(n))     # g <= f
)

hvec <- c(c(1,rep(0,n-1)), # weight lower bounds
          c(1,rep(1,n-1)), # weight upper bounds
          .15/sqrt(252),   # max risk
          0,               # f >= g
          rep(0,n)         # g <= f  
          )



# Define the number of inequality CONSTRAINTS (nrows in Gmat)
dims <- list(l=as.integer(n + n + 1),          # portfolio weights, active weights, portfolio return
             q= as.integer(c(n+1)),            # The cone - portfolio volatility
             e=0L)
  
  
  
solution_ecos <- ECOS_csolve(c = c(rep(0,n),rep(0,n),-1,0,rep(0,n))       # maximize return
                             ,A = Amat
                             ,b = bvec
                             ,G = Gmat
                             ,h = hvec
                             ,dims = dims
                             ,control = ecos.control(maxit = 500L,verbose = 1L)
)

  
  




Now here is how I am trying to solve the problem with both constraints:

The decision variables are:

x = portfolio weights
w_a = active weights (portfolio weight - benchmark weight)
r = portfolio return (what is maximized)
f = portfolio risk
g = asset contribution to portfolio risk
f2 = portfolio tracking error
g2 = asset contribution to tracking error

I placed the two conic constraints
f >= g
f2 >= g2

in the last elements of the inequality vector and specified their dimensions in the "dims" argument as a vector of length 2, with each element of that vector being the combined size f and g (1 + 5 in this example).

# define Amat

Amat <- rbind(
  cbind(mu,rep(0,n), -1, 0, Matrix(0,ncol = n,sparse = T),0,Matrix(0,ncol = n)),        # portfolio return            = r
  cbind(diag(n),-diag(n), rep(0,n),rep(0,n),Matrix(0,n,n),rep(0,n),Matrix(0,n,n)),      # active weights              = w_a
  cbind(G,Matrix(0,n,n), rep(0,n), rep(0,n), -diag(n),rep(0,n),Matrix(0,n,n)),          # portfolio risk              = g
  cbind(Matrix(0,n,n), G, rep(0,n), rep(0,n),Matrix(0,n,n),rep(0,n), -diag(n)),         # portfolio active risk       = g2
  cbind(Matrix(c(1,rep(0,n-1)),n),                                                      # baseline factor loading     = 1
        Matrix(0,n,n-1),
        Matrix(0,n,n),
        Matrix(0,n),
        Matrix(0,n),
        Matrix(0,n,n),
        Matrix(0,n),
        Matrix(0,n,n))
)


# define bvec
bvec <- c(0,              # portfolio return = t
          w_b,            # benchmark weights
          rep(0,n),       # portfolio risk = g,
          rep(0,n),       # portfolio tracking error = g2  
          c(1,rep(0,n-1)) # baseline factor loading = 1
)



Gmat <- rbind(
  cbind(-diag(n), Matrix(0,n,n), rep(0,n),rep(0,n), Matrix(0,n,n),rep(0,n),Matrix(0,n,n)),    # weight lower bounds
  cbind(diag(n), Matrix(0,n,n), rep(0,n),rep(0,n) ,Matrix(0,n,n),rep(0,n),Matrix(0,n,n)),     # weight upper bounds
  cbind(rep(0,n),rep(0,n), 0, 1,Matrix(0,ncol = n),0,Matrix(0,ncol = n)),                     # f <= maxRisk
  cbind(rep(0,n),rep(0,n), 0, 0 ,Matrix(0,ncol = n),1,Matrix(0,ncol = n)),                    # f2 <= max tracking error
  cbind(rep(0,n), rep(0,n) ,0, -1,Matrix(0,ncol = n),0,Matrix(0,ncol = n)),                   # f >= g
  cbind(Matrix(0,n,n), Matrix(0,n,n),rep(0,n), rep(0,n), -diag(n),rep(0,n),Matrix(0,n,n)),    # g <= f
  cbind(rep(0,n), rep(0,n) ,0, 0, Matrix(0,ncol = n),-1,Matrix(0,ncol = n)),                  # f2 >= g2
  cbind(Matrix(0,n,n), Matrix(0,n,n),rep(0,n), rep(0,n), Matrix(0,n,n),rep(0,n),-diag(n))     # g2 <= f2
)


hvec <- c(c(1,rep(0,n-1)), # weight lower bounds
          c(1,rep(1,n-1)), # weight upper bounds
          .15/sqrt(252),   # max risk
          .05/sqrt(252),   # max tracking error
          0,               # f >= g
          rep(0,n),        # g <= f
          0,               # f2 >= g 
          rep(0,n)         # g2 <= f2
)


# Defines the number of inequality CONSTRAINTS (nrows in Gmat)
dims <- list(l=as.integer(n + n + 1),          # portfolio weights, active weights, portfolio return
             q= as.integer(c(n+1,n+1)),        # The cones - portfolio volatility + tracking error
             e=0L)



solution_ecos <- ECOS_csolve(c = c(rep(0,n),rep(0,n),-1,0,rep(0,n),0,rep(0,n)) # maximize return
                             ,A = Amat
                             ,b = bvec
                             ,G = Gmat
                             ,h = hvec
                             ,dims = dims
                             ,control = ecos.control(maxit = 500L,verbose = 1L)
)

When I run this program, ecos returns a NULL value, with no error messages at all, even with the verbose = 1L setting.

It's important to note that both the portfolio volatility and tracking error constraints work fine on their own. It's only when I try to include both that this happens. This leads me to believe I have not set up the two conic constraints the way ecos wants.

What am I doing wrong here? How should I go about specifying a problem with more than one conic constraint?

Never mind, I found the problem. I had forgotten to update the number of linear constraints in dims 😔