Search code examples
roptimization

CVar portfolio optimisation - different results using Rglpk_solve_LP vs LSopt


Optimising a portfolio for CVaR using Rglpk_solve_LP vs LSopt generates different results. CVaR from Rglpk_solve_LP is larger in absolute terms (i.e., more negative) than the one from LSopt. I am rather certain it comes down to a different treatment of the mean return that a portfolio generates because if I input expected returns as zeros for all assets, the difference disappers.

How can the Rglpk_solve_LP approach be changed to generate CVaR that takes into account the mean return of the portfolio?

library(neighbours)
library(NMOF)
library(Rglpk)

# Return / vol
ret <- c(0.039, 0.036, 0.092, 0.139, 0.094)
# ret <- rep(rep(0,5)
vol <- c(0.125, 0.074, 0.101, 0.204, 0.167)

ns <- 1000
na <- length(ret)
R <- randomReturns(na, ns, mean=ret, sd = vol)

b <- 0.95

## Optimisation with Rglpk_solve_LP
f.obj <- c(alpha = 1, x = rep(0, na), u = 1/rep((1 - b)*ns, ns))

C <- cbind(1, R, diag(nrow(R)))
C <- rbind(c(alpha = 0, x = rep(1, na), u = rep(0, nrow(R))), C)

const.dir <- c("==", rep(">=", nrow(C) - 1))
const.rhs <- c(1, rep(0, nrow(C) - 1))

sol.lp <- Rglpk_solve_LP(f.obj,
                         C,
                         const.dir,
                         rhs = const.rhs,
                         control = list(verbose = TRUE, presolve = TRUE))  

lp.w <- sol.lp$solution[2:(1+na)]

## Optimisation with LSopt
CVaR <- function(w, R, b) {
  Rw <- R %*% w
  mean(Rw[Rw >= quantile(Rw, b)])
}

nb <- neighbourfun(0, 1, type = "numeric", stepsize = 0.05)

sol.ls <- LSopt(CVaR,
                list(x0 = rep(1/na, na),
                     neighbour = nb,
                     nI = 1000),
                R = R, b = b)

ls.w <- sol.ls$xbest

# Compare results
CVaR(lp.w, R, b)
CVaR(ls.w, R, b)

Solution

  • The code looks much like Minimising Conditional Value-at-Risk (CVaR).

    The trouble/confusion comes from working with losses or returns. Try this objective function, which works with returns, for Local Search:

    CVaR <- function(w, R, b) {
        Rw <- R %*% w
       -mean(Rw[Rw <= quantile(Rw, 1 - b)])
    }
    

    Also, the NMOF package has implemented CVaR in function minCVaR. So after having run your code with the updated objective function for LSopt, all three expressions should give roughly the same results:

    CVaR(lp.w, R, b)
    CVaR(ls.w, R, b)
    CVaR(NMOF::minCVaR(R, q = 1 - b), R, b)
    

    (Disclosure: I am the maintainer of package NMOF.)