Почему Solve.QP и Portugal.optim не дают одинаковых результатов?

В документации для port.optim {tseries} сказано, что Solve.QP {quadprog} используется для создания решения для нахождения касательного портфеля, максимизирующего коэффициент Шарпа. Это означает, что результаты должны быть идентичными для любой функции. Возможно, я что-то упускаю из виду, но в этом простом примере я получаю похожие, но не идентичные решения для оценки оптимальных весов портфеля с помощью portsport.optim иsolve.QP. Разве результаты не должны быть идентичными? Если да, то где я ошибаюсь? Вот код:

library(tseries)
library(quadprog)


# 1. Generate solution with solve.QP via: comisef.wikidot.com/tutorial:tangencyportfolio

# create artifical data
set.seed(1)
nO     <- 100     # number of observations
nA     <- 10      # number of assets
mData  <- array(rnorm(nO * nA, mean = 0.001, sd = 0.01), dim = c(nO, nA))
rf     <- 0.0001     # riskfree rate (2.5% pa)
mu     <- apply(mData, 2, mean)    # means
mu2    <- mu - rf                  # excess means

# qp
aMat  <- as.matrix(mu2)
bVec  <- 1
zeros <- array(0, dim = c(nA,1))
solQP <- solve.QP(cov(mData), zeros, aMat, bVec, meq = 1)

# rescale variables to obtain weights
w <- as.matrix(solQP$solution/sum(solQP$solution))

# 2. Generate solution with portfolio.optim (using artificial data from above)
port.1 <-portfolio.optim(mData,riskless=rf)
port.1.w <-port.1$pw
port.1.w <-matrix(port.1.w)

# 3. Compare portfolio weights from the two methodologies:

compare <-cbind(w,port.1$pw)

compare

             [,1]        [,2]
 [1,]  0.337932967 0.181547633
 [2,]  0.073836572 0.055100415
 [3,]  0.160612441 0.095800361
 [4,]  0.164491490 0.102811562
 [5,]  0.005034532 0.003214622
 [6,]  0.147473396 0.088792283
 [7,] -0.122882875 0.000000000
 [8,]  0.127924865 0.067705050
 [9,]  0.026626940 0.012507530
[10,]  0.078949672 0.054834759

person James Picerno    schedule 24.08.2014    source источник


Ответы (2)


Единственный способ справиться с такими ситуациями — просмотреть источник. В вашем случае он доступен через tseries:::portfolio.optim.default.

Теперь, чтобы найти разницу между этими двумя вызовами, мы можем сузить проблему, определив эквивалентную вспомогательную функцию:

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

При этом вы можете видеть, что 1) riskless=rf не предназначен, riskless=TRUE, rf=rf - правильный; 2) есть несколько отличий в амате и бвеке.

Я не эксперт по оптимизации портфеля, поэтому я не знаю, чем объясняются эти дополнительные ограничения и должны ли они вообще быть, но, по крайней мере, вы можете увидеть, что именно вызывает разницу.

person tonytonov    schedule 25.08.2014
comment
Спасибо, Тонитонов. Это хороший способ углубиться в разгадку этой тайны. Я рассмотрю ваше предложение свежим взглядом и сообщу о своих выводах в установленном порядке. - person James Picerno; 25.08.2014
comment
рекомендация tonyonov полезна, но после анализа кода через tseries:::portfolio.optim.default я понимаю, что у меня нет набора навыков, чтобы найти ответ на эту загадку. Тем не менее, вот более четкое представление вопроса в том виде, в котором он был поставлен изначально. Сократив количество активов до 2 — изменив приведенный выше код на: nA ‹- 2 — мы видим совершенно другие рекомендации по весам портфеля: [1,] 0,7919881 0,50,7919881 0,5 [2,] 0,2080119 0,50,2080119 0,5 - person James Picerno; 26.08.2014
comment
Что-то странное происходит, когда я запускаю Portugal.optim, и я не знаю, почему. В частности, обратите внимание, что я получаю совсем другие результаты при изменении без риска=ИСТИНА на без риска=ЛОЖЬ. Возможно, ошибка в portal.optim. В любом случае, я могу легко воспроизвести результатыsolve.QP в Excel с помощью решателя, но не могу сделать это с Portugal.optim. Итог: я уверен в результатахsolve.QP, но по-прежнему скептически отношусь к Portugal.optim. Возможно, кто-то может предложить более глубокую ясность. - person James Picerno; 27.08.2014
comment
Спасибо за обновление, я уверен, что это будет полезно для будущих читателей. - person tonytonov; 27.08.2014

Разница в вашем примере возникает из-за значения по умолчанию «shorts = FALSE» в tseries::portfolio.optim(). Поэтому вам придется либо изменить аргумент, либо добавить ограничение неотрицательности в вашу задачуsolve.QP, чтобы получить те же результаты.

РЕДАКТИРОВАТЬ: Хотя ответ по-прежнему остается верным, похоже, есть некоторые другие странные значения по умолчанию с tseries::portfolio.optim(). Например, он устанавливает требование минимальной доходности равным pm = mean(x), что приводит к случайному портфелю на границе эффективности вместо возврата глобального портфеля с минимальной дисперсией, если требования к доходности отсутствуют. Итог: оставайтесь со своим решением quadprog::solve.QP. Прилагаю пример функции-оболочки, которую я использую (я только начал работать с R, и хотя я совершенно уверен, что это дает математически правильные результаты, это может быть не самый чистый фрагмент кода):

# --------------------------------------------------------------------------
#' 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)
}
person Frank    schedule 18.08.2015
comment
Спасибо, Фрэнк. Поскольку я изначально разместил вопрос, я провел дополнительное исследование, и результаты подтверждают ваш вывод: придерживайтесь quadprog::solve.QP. - person James Picerno; 19.08.2015