#' @title Calculate portfolio weights for Garlappi model 1.2.2-1.2.3
#'
#' @description Calculate portfolio weights for Garlappi model 1.2.2-1.2.3: Uncertainty about expected returns
#' estimated jointly for all assets and subgroups thereof.
#'
#'
#' @param mu, list of vectors of mus for portfolio subsets
#' @param Sigma, overall covariance matrix
#' @param gamma optional, coefficient of risk aversion
#' @param epsilon, list of coefficients of parameter uncertainty for each subset of parameters defined by mu
#' @param rf optional, should the case with or without the risk-free rate be calculated (only for one PU parameter)
#'
#' @return output of function:
#' - vector of optimal weights given PU
#' - sturb: by how much is investment in the optimal portfolio reduced (epsilon vs Sharpe ratio of the other portfolio)
#'
#' @examples
#' require(xts)
#' data(X)
#' mu_in <- list(mu1=colMeans(X[,1:4]),mu2=colMeans(X[,5:7]))
#' Sigma_in <- cov(X[,1:7])
#' gamma <- 3
#'
#' ## First calculate the tangency portfolio for the first 4 assets without parameter uncertainty
#'
#' round(c(1/gamma*.mpinv(Sigma_in[1:4,1:4])%*%mu_in[[1]]),4)
#' # Then do it using the function
#' round(glPF(mu=mu_in[1],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=0),rf=TRUE)$w,4)
#' # Now, we allow for parameter uncertainty for all 4 assets together
#' round(glPF(mu=mu_in[[1]],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=0.1),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in[[1]],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=0.2),rf=TRUE)$w,4)
#' # Now we calculate the GMVP
#' round(c(.mpinv(Sigma_in[1:4,1:4])%*%matrix(1,nrow=4,ncol=1)/drop(matrix(1,nrow=1,ncol=4)%*%.mpinv(Sigma_in[1:4,1:4])%*%matrix(1,nrow=4,ncol=1))),4)
#' # and observe how the assets shift towards it
#' round(glPF(mu=mu_in[[1]],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=0.1),rf=FALSE)$w,4)
#' round(glPF(mu=mu_in[[1]],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=0.8),rf=FALSE)$w,4)
#' round(glPF(mu=mu_in[[1]],Sigma=Sigma_in[1:4,1:4],gamma=gamma,epsilon=list(eps1=10000),rf=FALSE)$w,4)
#'
#' ## Second: We use two groups for the uncertainty example now
#'
#' # Tangency portfolio for all 7 assets
#' round(c(1/gamma*.mpinv(Sigma_in)%*%unlist(mu_in)),4)
#' # The same using the function
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0),rf=TRUE)$w,4)
#' # Introduce parameter uncertainty for single assets and groups
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0.1),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0.05),rf=TRUE)$w,4)
#'
#' ## Third: We do it for single assets (two)
#'
#' mu_in <- list(mu1=colMeans(X[,1]),mu2=colMeans(X[,2]))
#' Sigma_in <- cov(X[,1:2])
#' gamma <- 3
#' # Tangency portfolio for 2 assets
#' round(c(1/gamma*.mpinv(Sigma_in)%*%unlist(mu_in)),4)
#' # The same using the function
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0),rf=TRUE)$w,4)
#' # Introduce parameter uncertainty for single assets and groups
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0.1),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.0002,eps2=0.01),rf=TRUE)$w,4)
#' # Sturb
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0),rf=TRUE)$sturb,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0.1),rf=TRUE)$sturb,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.0002,eps2=0.01),rf=TRUE)$sturb,4)
#'
#' ## Fourth: Many single assets
#' mu_in <- as.list(colMeans(X[,1:7]))
#' Sigma_in <- cov(X[,1:7])
#' eps <- as.list(rep(0.1,7))
#' gamma <- 3
#' # Tangency portfolio for 2 assets
#' round(c(1/gamma*.mpinv(Sigma_in)%*%unlist(mu_in)),4)
#' # The same using the function
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=eps,rf=TRUE)$w,4)
#' # Introduce parameter uncertainty for single assets and groups
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0.1),rf=TRUE)$w,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.0002,eps2=0.01),rf=TRUE)$w,4)
#' # Sturb
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.1,eps2=0),rf=TRUE)$sturb,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0,eps2=0.1),rf=TRUE)$sturb,4)
#' round(glPF(mu=mu_in,Sigma=Sigma_in,gamma=gamma,epsilon=list(eps1=0.0002,eps2=0.01),rf=TRUE)$sturb,4)
#'
#'
#' @import nleqslv
#' @import xts
#'
#' @keywords internal
#'
#' @export
glPF <- function(mu, Sigma, gamma=3, epsilon, rf){
if (is.list(mu)){n <- length(mu)} else {mu <- list(mu); n<-1}
N <- length(unlist(mu))
if (n==1){
if(rf){
# with risk-free rate
w <- c(max(1-epsilon[[1]]/drop(mu[[1]]%*%.mpinv(Sigma)%*%mu[[1]]),0)*1/gamma*.mpinv(Sigma)%*%mu[[1]])
} else {
# without risk-free rate
A <- drop(rep(1,ncol(Sigma))%*%.mpinv(Sigma)%*%rep(1,ncol(Sigma)))
B <- drop(mu[[1]]%*%.mpinv(Sigma)%*%rep(1,ncol(Sigma)))
C <- drop(mu[[1]]%*%.mpinv(Sigma)%*%mu[[1]])
d_sigma_p <- function(sigma_p,A,B,C,gamma,epsilon){
return(A*gamma^2*sigma_p^4 +
2*A*gamma*sqrt(epsilon[[1]])*sigma_p^3 +
(A*epsilon[[1]] - A*C + B^2-gamma^2)*sigma_p^2 - 2*gamma*sqrt(epsilon[[1]])*sigma_p-epsilon[[1]]-sigma_p)
}
sigma_p <- nleqslv::nleqslv(0.1,d_sigma_p,A=A,B=B,C=C,gamma=gamma,epsilon=epsilon[[1]])$x
# weights
w <- c(sigma_p/(sqrt(epsilon[[1]]) + gamma*sigma_p)*.mpinv(Sigma)%*%t(mu[[1]]-1/A*(B-(sqrt(epsilon[[1]])+gamma*sigma_p)/sigma_p)*matrix(1,ncol=length(mu[[1]]),nrow=1)))
}
} else {
if(rf){
#cat("Calculating PU optimal portfolio for",n,"groups and",N,"assets.\n")
# define helper function
sub_fn <- function(mu_in,mu_out,Sigma_in,Sigma_out,eps_in,w_out,gamma){
g <- function(w_out,mu_in,gamma,Sigma_out){
return(mu_in - gamma * (Sigma_out %*% w_out))
}
out <- 1/gamma * .mpinv(Sigma_in) %*% g(w_out,mu_in,gamma,Sigma_out) *
max(1-sqrt(eps_in/t(g(w_out,mu_in,gamma,Sigma_out)) %*% .mpinv(Sigma_in) %*% g(w_out,mu_in,gamma,Sigma_out)),0)
return(out)
}
# now create optimizer function
fn <- function(w,mu,Sigma,epsilon,gamma){
n <- length(mu); nvec <- plyr::laply(mu,length)
N <- length(unlist(mu))
dw <- NULL
vec <- list();k<-0
# create vec list
for (l in 1:length(mu)){vec[[l]]<-seq_len(length(mu[[l]]))+k;k<-k+length(mu[[l]])}
# now run loop
for (i in 1:n){
temp_dw <- (c(sub_fn(mu_in=unlist(mu[i]),
mu_out=unlist(mu[-i]),
Sigma_in=Sigma[vec[[i]],vec[[i]],drop=FALSE],
Sigma_out=Sigma[vec[[i]],-vec[[i]],drop=FALSE],
w_out=w[-vec[[i]]],
eps_in=epsilon[[i]],gamma=gamma)) - w[vec[[i]]])
dw <- c(dw,temp_dw)
}
return(dw)
}
#fn(rep(1/N,N),mu,Sigma,epsilon,gamma)
# now optimize
w <- nleqslv::nleqslv(rep(1/N,N), fn,
mu=mu,
Sigma=Sigma,
epsilon=epsilon,
gamma=gamma)$x
} else {stop("version without risk-free rate is not implemented yet!")}
}
# sturb: Similar to before, see how far this moves the asset out of the market in relation to its former weights
sub_sturb <- function(mu_in,mu_out,Sigma_in,Sigma_out,eps_in,w_out,gamma){
g <- function(w_out,mu_in,gamma,Sigma_out){
return(mu_in - gamma * (Sigma_out %*% w_out))
}
out <- max(1-sqrt(eps_in/t(g(w_out,mu_in,gamma,Sigma_out)) %*% .mpinv(Sigma_in) %*% g(w_out,mu_in,gamma,Sigma_out)),0)
return(out)
}
n <- length(mu); nvec <- plyr::laply(mu,length)
N <- length(unlist(mu))
vec <- list();k<-0
# create vec list
for (l in 1:length(mu)){vec[[l]]<-seq_len(length(mu[[l]]))+k;k<-k+length(mu[[l]])}
sturb <- NULL
for (i in 1:n){
temp_sturb <- (c(sub_sturb(mu_in=unlist(mu[i]),
mu_out=unlist(mu[-i]),
Sigma_in=Sigma[vec[[i]],vec[[i]],drop=FALSE],
Sigma_out=Sigma[vec[[i]],-vec[[i]],drop=FALSE],
w_out=w[-vec[[i]]],
eps_in=epsilon[[i]],gamma=gamma)) - w[vec[[i]]])
sturb <- c(sturb,temp_sturb)
}
names(w) <- names(unlist(mu))
names(sturb) <- names(unlist(factor))
return(list(w=w,sturb=sturb))
}
#' @title Calculate portfolio weights for Garlappi model 1.2.3
#'
#' @description Calculate portfolio weights for Garlappi model 1.2.2-1.2.3: Uncertainty about expected returns
#' estimated jointly for all assets and subgroups thereof.
#'
#' @param factors, list of factor premia xts
#' @param PFs, list of portfolios (xts) to calculate PU for each factor premium
#' @param gamma optional, coefficient of risk aversion (standard = 3)
#' @param s.k optional, lookback window for current observations that are related to the long-term mean
#' @param imp optional, should missing values be imputed? (standard: FALSE)
#' @param rolling.PF optional, should portfolio moments be calculated based on a rolling window?
#' @param rolling.eps optional, should uncertainty moments be calculated based on a rolling window?
#' @param roll.obs.PF optional, rolling.PF=TRUE: Length of rolling window; rolling.PF=FALSE: Initial estimation window size
#' @param roll.obs.eps optional, rolling.eps=TRUE: Length of rolling window; rolling.eps=FALSE: Initial estimation window size
#' @param rf optional, should the case with or without the risk-free rate be calculated (only for one PU parameter)
#'
#' @return
#'
#' @examples
#' data(factors)
#' data(X)
#' data(Y)
#' # out-of-sample exercise
#' factors <- list(fact1=factors[,1],fact2=factors[,2])
#' PFs <- list(PF1=X,PF2=Y)
#' w1 <- OSglPF(factors,PFs, s.k=12, imp=FALSE, rolling.PF=FALSE, rolling.eps=FALSE, roll.obs.PF=120, roll.obs.eps=120)
#' plot(w1$epsilon)
#' plot(w1$weights)
#' plot(w1$sturb)
#' w2 <- OSglPF(factors,PFs, s.k=12, imp=FALSE, rolling.PF=TRUE, rolling.eps=TRUE, roll.obs.PF=120, roll.obs.eps=120)
#' plot(w2$epsilon)
#' plot(w2$weights)
#' plot(w2$sturb)
#' w3 <- OSglPF(factors,PFs, s.k=12, imp=FALSE, rolling.PF=FALSE, rolling.eps=TRUE, roll.obs.PF=120, roll.obs.eps=120)
#' plot(w3$epsilon)
#' plot(w3$weights)
#' plot(w3$sturb)
#'
#' @importFrom plyr ldply
#' @import xts
#' @import zoo
#' @importFrom stats na.omit
#'
#' @keywords internal
#'
#' @export
OSglPF <- function(factors,PFs, s.k=1, imp=FALSE, rolling.PF=FALSE, rolling.eps=FALSE, roll.obs.PF=120,roll.obs.eps=120, rf=TRUE, gamma=3){
# method = c("cov", "mve", "mcd", "MCD", "OGK", "nnve", "shrink", "bagged")
if (!requireNamespace("xts", quietly = TRUE)) {
stop("Package \"xts\" needed for this function to work. Please install it.",call. = FALSE)}
if (!requireNamespace("fAssets", quietly = TRUE)) {
stop("Package \"fAssets\" needed for this function to work. Please install it.",call. = FALSE)}
if(imp){
if (!requireNamespace("mice", quietly = TRUE)) {
stop("Package \"mice\" needed for this function to work. Please install it.",call. = FALSE)}
}
## Function Main
N <- length(factors)
epsilon <- list()
# 0. Adjust factors and all datasets if necessary
min_date1 <- max(plyr::ldply(PFs,function(x){as.Date(stats::time(x)[1])})$V1)
min_date2 <- max(plyr::ldply(factors,function(x){as.Date(stats::time(x)[1])})$V1)
PFs <- plyr::llply(PFs,function(x){x[paste0(min_date1,"/")]})
# 1. Now calculate OS sturb and save epsilons
for (i in 1:N){
epsilon[[i]] <- OSminimax_one(X = PFs[[i]], s.k = s.k, rolling=rolling.eps, roll.obs = roll.obs.eps, NOUT=N)$epsilon
}
min_date_eps <- min(plyr::ldply(epsilon,function(x){as.Date(stats::time(stats::na.omit(x))[1])})$V1)
stopifnot(as.yearmon(min_date_eps)<=(as.yearmon(min_date2)+roll.obs.PF/12))
# 2. Now loop through each point in time and create portfolio weights
T <- nrow(factors[[1]])
weights <- matrix(NA,nrow=T,ncol=N)
sturb <- matrix(NA,nrow=T,ncol=N)
for (j in roll.obs.PF:T){
if (rolling.PF==FALSE) {
mu_in <- plyr::llply(factors,function(x,j){mean(x[1:(j-1)])},j=j)
Sigma_in <- cov(t(plyr::laply(factors,function(x,j){x[1:(j-1)]},j=j)))
epsilon_in <- plyr::llply(epsilon,function(x,j){x[j]},j=j)
temp <- glPF(mu=mu_in, Sigma=Sigma_in, gamma=gamma, epsilon=epsilon_in, rf=rf)
weights[j,] <- temp$w
sturb[j,] <- temp$sturb
} else {
mu_in <- plyr::llply(factors,function(x,j,roll.obs){mean(x[max(1,j-roll.obs):(j-1)])},j=j,roll.obs=roll.obs.PF)
Sigma_in <- cov(t(plyr::laply(factors,function(x,j,roll.obs){x[max(1,j-roll.obs):(j-1)]},j=j,roll.obs=roll.obs.PF)))
epsilon_in <- plyr::llply(epsilon,function(x,j,roll.obs){mean(x[j])},j=j)
temp <- glPF(mu=mu_in, Sigma=Sigma_in, gamma=gamma, epsilon=epsilon_in, rf=rf)
weights[j,] <- temp$w
sturb[j,] <- temp$sturb
}
}
weights <- reclass(weights,try.xts(factors[[1]]))
sturb <- reclass(sturb,try.xts(factors[[1]]))
epsilon.out <- Reduce(cbind,epsilon)
return(list(weights=weights,epsilon=epsilon.out,sturb=sturb))
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.