Диаграмма аналитики портфеля R. Функция EfficientFrontier

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

Сначала я создал годовую функцию стандартного отклонения, используя этот код.

pasd <- function(R, weights){
  as.numeric(StdDev(R=R, weights=weights)*sqrt(12)) # hardcoded for monthly data
  # as.numeric(StdDev(R=R, weights=weights)*sqrt(4)) # hardcoded for quarterly data
}

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

> prt
**************************************************
PortfolioAnalytics Portfolio Specification 
**************************************************

Call:
portfolio.spec(assets = colnames(returns))

Number of assets: 3 
Asset Names
[1] "Global REITs"      "Au REITs"          "Au Util and Infra"

Constraints
Enabled constraint types
        - leverage 
        - long_only 

Objectives:
Enabled objective names
        - mean 
        - pasd 

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

prt.ef <- create.EfficientFrontier(R = returns, portfolio = prt, type = "DEoptim", match.col = "pasd")

Но когда я пытаюсь построить его, я получаю следующие сообщения об ошибках.

> chart.EfficientFrontier(prt.ef, match.col="pasd")
Error in StdDev(R = R, weights = weights) : 
  argument "weights" is missing, with no default
In addition: There were 26 warnings (use warnings() to see them)
Error in StdDev(R = R, weights = weights) : 
  argument "weights" is missing, with no default
Error in StdDev(R = R, weights = weights) : 
  argument "weights" is missing, with no default
Error in xlim[2] * 1.15 : non-numeric argument to binary operator

Кто-нибудь знает, почему это так? Когда я использую summary(prt.ef), я вижу веса, но почему функция chart.EfficientFrontier не работает?


person user3381431    schedule 03.06.2015    source источник
comment
chart.EfficientFrontier предполагает, что сможет вызывать pasd без весов для вычисления сигмы для каждого актива и с весами для вычисления сигмы портфеля. Чтобы устранить сообщение об ошибке, заставьте pasd работать как StdDev, используя pasd <- function(R, weights=NULL) as.numeric(StdDev(R=R, weights=weights)*sqrt(12)) . Кроме того, чтобы провести последовательный анализ, вы должны привести доходность в годовом исчислении, но тогда оптимизация будет рассчитывать одинаковые веса для месячного и годового портфелей.   -  person WaltS    schedule 03.06.2015
comment
Я изменил функцию pasd, как вы предложили, но chart.EfficientFrontier по-прежнему выдает мне ошибки. chart.EfficientFrontier(prt.ef, match.col="pasd") Error in StdDev(R = R, weights = weights) : object 'NuLL' not found Error in StdDev(R = R, weights = weights) : object 'NuLL' not found Error in StdDev(R = R, weights = weights) : object 'NuLL' not found Error in xlim[2] * 1.15 : non-numeric argument to binary operator   -  person user3381431    schedule 04.06.2015


Ответы (4)


Как предложил @WaltS, вы должны быть последовательны в реализации функций для годового среднего и рискового дохода.

Но на самом деле, чтобы получить годовую статистику, у вас есть два варианта, которые вы не используете:

1) Проведите оптимизацию по месячным данным, с оригинальными функциями доходности риска в спецификации. Для построения графика вы можете сделать

Port.Anua.Returns=prt.ef$frontier[,1]*12 
Port.Anua.StDev=prt.ef$frontier[,2]*12^.5

Веса будут одинаковыми для месячных или годовых портфелей.

prt.ef$frontier[,-(1:3)]

2) Преобразуйте свою месячную доходность в годовую доходность, умножив ее на 12. Затем проведите оптимизацию по обычной процедуре, все риски и доходность уже будут пересчитаны в годовом исчислении в prt.ef$frontier.

Относится к неровной линии в EF. Используя спецификацию вашего портфолио, я также смог воссоздать такое же поведение. Для следующего графика я использовал данные edhec, вашу спецификацию с исходными mean и StdDev в целях:

data(edhec)
returns <- edhec[,1:3]

введите здесь описание изображения

На это поведение должна влиять спецификация или алгоритм оптимизации, который вы используете. Такую же оптимизацию я проделал с solve.QP из пакета quadprog. Это результат. введите здесь описание изображения

Обновлять

Код здесь:

require(quadprog) 
#min_x(-d^T x + 1/2 b^T D x) r.t A.x>=b
MV_QP<-function(nx, tarRet, Sig=NULL,long_only=FALSE){
  if (is.null(Sig)) Sig=cov(nx)
  dvec=rep(0,ncol(Sig))
  meq=2
  Amat=rbind(rep(1,ncol(Sig)),
             apply(nx,2,mean) )
  bvec=c(1,tarRet )
  if (long_only) {
    meq=1
    Amat=Amat[-1,]
    Amat=rbind(Amat,
               diag(1,ncol(Sig)),
               rep(1,ncol(Sig)),
               rep(-1,ncol(Sig)))
    bvec=bvec[-1]
    bvec=c(bvec,
               rep(0,ncol(Sig)),.98,-1.02)
  }
  sol  <- solve.QP(Dmat=Sig, dvec, t(Amat), bvec, meq=meq)$solution 
}

steps=50
x=returns
 µ.b <- apply(X = x, 2, FUN = mean) 
long_only=TRUE
range.bl <- seq(from = min(µ.b), to = max(µ.b)*ifelse(long_only,1,1.6), length.out = steps) 
risk.bl <- t(sapply(range.bl, function(targetReturn) { 
  w <- MV_QP(x, targetReturn,long_only=long_only) 
  c(sd(x %*% w),w)  }))

weigthsl=round(risk.bl[,-1],4)
colnames(weigthsl)=colnames(x)
weigthsl
risk.bl=risk.bl[,1]
rets.bl= weigthsl%*%µ.b
fan=12
plot(x = risk.bl*fan^.5, y = rets.bl*fan,col=2,pch=21,
     xlab = "Annualized Risk ", 
     ylab = "Annualized Return", main = "long only EF with solve.QP")
person Robert    schedule 04.06.2015
comment
Спасибо, я попробую это сделать, но чего я не понимаю, так это пересчета ежемесячных доходов в годовом исчислении путем умножения их на 12. Я уже импортирую серию ежемесячных доходов, и у меня будет годовой доход, связанный цепочкой между каждым ежемесячным доходом (1+ г1)*(1+г2)*(1+г3).....(1+г12) - 1 - person user3381431; 05.06.2015
comment
Также не могли бы вы опубликовать код для оптимизации с использованием solve.QP? - person user3381431; 05.06.2015
comment
Я так понял, вам нужна эквивалентная годовая доходность и годовое стандартное отклонение от месячных данных EF, для этого вы просто включаете шкалу = 12. В этом случае вы должны инвестировать каждый месяц. Другая история заключается в том, чтобы трансформировать проблему в инвестиции каждый год, для этого вам понадобится длинная серия годовых доходов, в этом случае да, вы можете составить ежемесячные доходы. - person Robert; 05.06.2015

В дополнение к комментариям Роберта, расчет оптимизации с ежемесячной доходностью представляет собой задачу квадратичного программирования с линейными ограничениями. Когда mean является целью return, а StdDev или var является целью risk, optimize.portfolio и create.EfficientFrontier выбирают метод ROI в качестве решателя, который использует solve.QP, эффективный решатель для подобных задач. Когда цель risk изменяется на pasd, эти функции не распознают это как задачу QP, поэтому используйте DEoptim общий нелинейный решатель задач, который, возможно, лучше подходит для решения невыпуклых задач, а не выпуклых задач QP. См. раздел Дифференциальная эволюция с помощью DEoptim. Это, по-видимому, является причиной неровной границы эффективности.

Чтобы create.EfficientFrontier использовал solve.QP, что гораздо более эффективно и точно для этого типа задач, вы можете создать пользовательскую функцию момента для вычисления среднего значения и дисперсии, а затем указать ее с аргументом momentFUN. Однако create.EfficientFrontier по крайней мере частично использует средства, вычисленные непосредственно из доходов, а не используя mu из momentFUN. Чтобы справиться с этим, умножьте доходность и разделите дисперсию на 12, как показано в примере ниже.

library(PortfolioAnalytics)
  data(edhec)
  returns <- edhec[,1:3]
#  define moment function
  annualized.moments <- function(R, scale=12, portfolio=NULL){
    out <- list()
    out$mu <-    matrix(colMeans(R), ncol=1)
    out$sigma <- cov(R)/scale
    return(out)
  }
# define portfolio
  prt <- portfolio.spec(assets=colnames(returns))
  prt <- add.constraint(portfolio=prt, type="long_only")
  #  leverage defaults to weight_sum = 1 so is equivalent to full_investment constraint
  prt <- add.constraint(portfolio=prt, type="leverage")
  prt <- add.objective(portfolio=prt, type="risk", name="StdDev")
# calculate and plot efficient frontier
  prt_ef <- create.EfficientFrontier(R=12*returns, portfolio=prt, type="mean-StdDev", 
                                      match.col = "StdDev", momentFUN="annualized.moments", scale=12)
  xlim <- range(prt_ef$frontier[,2])*c(1, 1.5)
  ylim <- range(prt_ef$frontier[,1])*c(.80, 1.05)
  chart.EfficientFrontier(prt_ef, match.col="StdDev", chart.assets = FALSE, 
                          labels.assets = FALSE, xlim=xlim, ylim=ylim )
  points(with(annualized.moments(12*returns, scale=12), cbind(sqrt(diag(sigma)), mu)), pch=19 ) 
  text(with(annualized.moments(12*returns, scale=12), cbind(sqrt(diag(sigma)), mu)), 
       labels=colnames(returns), cex=.8, pos=4) 
  chart.EF.Weights(prt_ef, match.col="StdDev")

Средние значения и стандартные отклонения активов также необходимо скорректировать, поэтому они нанесены на график за пределами chart.EfficientFrontier и показаны на диаграмме ниже.

введите здесь описание изображения

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

person WaltS    schedule 05.06.2015
comment
Спасибо за ваш подробный ответ. Я только начал использовать R, и мне потребовались годы, чтобы что-то в нем сделать. На данный момент я решил сделать это в Excel, но было бы неплохо оглянуться на это и попытаться понять код. - person user3381431; 08.06.2015

Причину ошибки не находит, но установка лимитов частично работает!

prt.ef$frontier  #see the EF
xylims=apply(prt.ef$frontier[,c(2,1)],2,range)*c(.98,1.01)
chart.EfficientFrontier(prt.ef, match.col="pasd", 
      main="Portfolio Optimization", 
      xlim=xylims[,1], ylim=xylims[,2])
#or
plot(prt.ef$frontier[,c(2,1)],col=2)
person Robert    schedule 03.06.2015
comment
Не могли бы вы объяснить, что делают ограничения? По какой-то причине граница, которую я вижу, представляет собой зубчатую линию, а не плавную кривую. - person user3381431; 04.06.2015

хорошо, поэтому я попробовал функцию pasd, которую предложил WaltS, и chart.EfficientFrontier, казалось, работал, но он дал мне зубчатую линию, а не гладкую линию.

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

pamean <- function(R, weights=NULL){Return.annualized(apply(as.xts(t(t(R) * weights)),1,sum))}

и добавил это в качестве цели в свое портфолио prt.

> prt
**************************************************
PortfolioAnalytics Portfolio Specification 
**************************************************

Call:
portfolio.spec(assets = colnames(returns))

Number of assets: 3 
Asset Names
[1] "Global REITs"      "Au REITs"          "Au Util and Infra"

Constraints
Enabled constraint types
        - long_only 
        - leverage 

Objectives:
Enabled objective names
        - pamean 
        - pasd 

Затем я снова создаю эффективную границу, используя эту строку:

> prt.ef <- create.EfficientFrontier(R=returns, portfolio=prt, type="DEoptim", match.col="pasd")

но когда я использую сводную функцию, я вижу, что была сгенерирована только 1 граничная точка. Что означает сообщение об ошибке и почему было сгенерировано только 1 очко?

> summary(prt.ef)
**************************************************
PortfolioAnalytics Efficient Frontier 
**************************************************

Call:
create.EfficientFrontier(R = returns, portfolio = prt, type = "DEoptim", 
    match.col = "pasd")

Efficient Frontier Points: 1 

Error in `colnames<-`(`*tmp*`, value = character(0)) : 
  attempt to set 'colnames' on an object with less than two dimensions
person user3381431    schedule 04.06.2015