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)
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
.)