Why doesn't solve.QP and portfolio.optim generate identical results?

旧巷老猫 提交于 2019-12-04 19:33:44

The one and the only way to deal with such situations is to browse the source. In your case, it is accessible via tseries:::portfolio.optim.default.

Now, to find the difference between those two calls, we may narrow down the issue by defining an equivalent helper function:

foo <- function(x, pm = mean(x), covmat = cov(x), riskless = FALSE, rf = 0) 
{
  x <- mData
  pm <- mean(x)
  covmat <- cov(x)
  k <- dim(x)[2]
  Dmat <- covmat
  dvec <- rep.int(0, k)
  a1 <- colMeans(x) - rf
  a2 <- matrix(0, k, k)
  diag(a2) <- 1
  b2 <- rep.int(0, k)
  Amat <- t(rbind(a1, a2))
  b0 <- c(pm - rf, b2)
  solve.QP(Dmat, dvec, Amat, bvec = b0, meq = 1)$sol
}

identical(portfolio.optim(mData, riskless=TRUE, rf=rf)$pw,
          foo(mData, riskless=TRUE, rf=rf))
#[1] TRUE

With that, you can see that 1) riskless=rf is not the intended way, riskless=TRUE, rf=rf is the correct one; 2) there are several differences in Amat and bvec.

I am not an expert in portfolio optimization, so I do not know what's the explanation behind these additional constraints and if they should be there in the first place, but at least you can see what exactly causes the difference.

The difference in your example occurs due to the default value 'shorts=FALSE' in tseries::portfolio.optim(). Therefore you would have to either change the argument or add a non-negativity restriction in your solve.QP problem to reach the same results.

EDIT: While the answer still holds true, there seem to be some other weird default values with tseries::portfolio.optim(). For example it sets the minimum return requirement to pm = mean(x), leading to a random portfolio on the efficiency frontier instead of returning the global minimum variance portfolio if there is no return requirement. Bottom line: Stay with your quadprog::solve.QP solution. Enclosed an example of the wrapper function I use (I just started working with R and while I am quite sure that this delivers mathematically correct results, it might not be the cleanest piece of code):

# --------------------------------------------------------------------------
#' Quadratic Optimization
#' @description Wrapper for quadratic optimization to calculate the general
#'              mean-variance portfolio.
#' @param S           [matrix] Covariance matrix.
#' @param mu          [numeric] Optional. Vector of expected returns.
#' @param wmin        [numeric] Optional. Min weight per asset.
#' @param wmax        [numeric] Optional. Max weight per asset.
#' @param mu_target   [numeric] Optional. Required return, if empty the optimization returns the global minimum variance portfolio
#' @return Returns the mean-variance portfolio or the global minimum variance portfolio
# --------------------------------------------------------------------------

meanvar.pf <- function(S,
                      mu=NULL,
                      wmin=-1000,
                      wmax=1000,
                      mu_target=NULL){
  if (!try(require(quadprog)))
    stop("Execute 'install.packages('quadprog')' and try again")
  if (missing(S))
    stop("Covariance matrix is missing")
  if (!is.null(mu) & dim(S)[1] != length(mu))
    stop("S and mu have non-conformable dimensions")
  N <- ncol(S)
  if (wmin >= 1/N)
    stop("wmin >= 1/N is not feasible")
  if (wmax <= 1/N)
    stop("wmax <= 1/N is not feasible")    
  meq <- 1
  bvec <- c(1, rep(wmin,N), -rep(wmax,N))
  Amat <- cbind(rep(1, N), diag(N), -diag(N))
  if (!is.null(mu_target)) {
    if (is.null(mu))
      stop("Vector of asset returns is missing")
    Amat <- cbind(mu, Amat)
    bvec <- c(mu_target, bvec)
    meq <- 2
  }
  result <- quadprog::solve.QP(Dmat=S, 
                              dvec=rep(0, N), 
                              Amat=Amat, 
                              bvec=bvec, 
                              meq=meq)
  return(result)
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!