Monday, July 13, 2015

[11b] Long-Short Portfolio Optimization (equal-weighted, tangency, minimum volatility, and target return portfolio) and Efficient Frontier

Create the following r source code files by Eric Zivot on a certain directory.
  • portfolio.r


portfolio.r
# portfolio.r

# Functions for portfolio analysis
# to be used in Introduction to Computational Finance & Financial Econometrics
# last updated: November 7, 2000 by Eric Zivot
#               Oct 15, 2003 by Tim Hesterberg
#               November 18, 2003 by Eric Zivot
#               November 9, 2004 by Eric Zivot
#             November 9, 2008 by Eric Zivot
#               August 11, 2011 by Eric Zivot
#
# Functions:
# 1. efficient.portfolio compute minimum variance portfolio
#                 subject to target return
# 2. globalMin.portfolio compute global minimum variance portfolio
# 3. tangency.portfolio   compute tangency portfolio
# 4. efficient.frontier   compute Markowitz bullet
# 5. getPortfolio     create portfolio object
#

getPortfolio <-
function(er, cov.mat, weights)
{
# contruct portfolio object
#
# inputs:
# er    N x 1 vector of expected returns
# cov.mat    N x N covariance matrix of returns
# weights  N x 1 vector of portfolio weights
#
# output is portfolio object with the following elements
# call original function call
# er   portfolio expected return
# sd   portfolio standard deviation
# weights N x 1 vector of portfolio weights
#
call <- match.call()

#
# check for valid inputs
#
asset.names <- names(er)
weights <- as.vector(weights)
names(weights) = names(er)
   er <- as.vector(er) # assign names if none exist
if(length(er) != length(weights))
stop("dimensions of er and weights do not match")
  cov.mat <- as.matrix(cov.mat)
if(length(er) != nrow(cov.mat))
stop("dimensions of er and cov.mat do not match")
if(any(diag(chol(cov.mat)) <= 0))
stop("Covariance matrix not positive definite")

#
# create portfolio
#
er.port <- crossprod(er,weights)
sd.port <- sqrt(weights %*% cov.mat %*% weights)
ans <- list("call" = call,
      "er" = as.vector(er.port),
      "sd" = as.vector(sd.port),
      "weights" = weights) 
class(ans) <- "portfolio"
ans
}

efficient.portfolio <-
function(er, cov.mat, target.return)
{
  # compute minimum variance portfolio subject to target return
  #
  # inputs:
  # er     N x 1 vector of expected returns
  # cov.mat     N x N covariance matrix of returns
  # target.return   scalar, target expected return
  #
  # output is portfolio object with the following elements
  # call     original function call
  # er     portfolio expected return
  # sd     portfolio standard deviation
  # weights     N x 1 vector of portfolio weights
  #
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er) # assign names if none exist
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semidefinite

  #
  # compute efficient portfolio
  #
  ones <- rep(1, length(er))
  top <- cbind(2*cov.mat, er, ones)
  bot <- cbind(rbind(er, ones), matrix(0,2,2))
  A <- rbind(top, bot)
  b.target <- as.matrix(c(rep(0, length(er)), target.return, 1))
  x <- solve(A, b.target)
  w <- x[1:length(er)]
  names(w) <- asset.names

  #
  # compute portfolio expected returns and variance
  #
  er.port <- crossprod(er,w)
  sd.port <- sqrt(w %*% cov.mat %*% w)
  ans <- list("call" = call,
      "er" = as.vector(er.port),
      "sd" = as.vector(sd.port),
      "weights" = w) 
  class(ans) <- "portfolio"
  ans
}

globalMin.portfolio <-
function(er, cov.mat)
{
  # Compute global minimum variance portfolio
  #
  # inputs:
  # er N x 1 vector of expected returns
  # cov.mat N x N return covariance matrix
  #
  # output is portfolio object with the following elements
  # call original function call
  # er portfolio expected return
  # sd portfolio standard deviation
  # weights N x 1 vector of portfolio weights
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er) # assign names if none exist
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semi-definite

  #
  # compute global minimum portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  one.vec <- rep(1,length(er))
#  w.gmin <- cov.mat.inv %*% one.vec/as.vector(one.vec %*% cov.mat.inv %*% one.vec)
  w.gmin <- rowSums(cov.mat.inv) / sum(cov.mat.inv)
  w.gmin <- as.vector(w.gmin)
  names(w.gmin) <- asset.names
  er.gmin <- crossprod(w.gmin,er)
  sd.gmin <- sqrt(t(w.gmin) %*% cov.mat %*% w.gmin)
  gmin.port <- list("call" = call,
    "er" = as.vector(er.gmin),
    "sd" = as.vector(sd.gmin),
    "weights" = w.gmin)
  class(gmin.port) <- "portfolio"
  gmin.port
}


tangency.portfolio <- 
function(er,cov.mat,risk.free)
{
  # compute tangency portfolio
  #
  # inputs:
  # er    N x 1 vector of expected returns
  # cov.mat    N x N return covariance matrix
  # risk.free  scalar, risk-free rate
  #
  # output is portfolio object with the following elements
  # call   captures function call
  # er   portfolio expected return
  # sd   portfolio standard deviation
  # weights  N x 1 vector of portfolio weights
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  if(risk.free < 0)
    stop("Risk-free rate must be positive")
  er <- as.vector(er)
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semi-definite

  #
  # compute global minimum variance portfolio
  #
  gmin.port <- globalMin.portfolio(er,cov.mat)
  if(gmin.port$er < risk.free)
    stop("Risk-free rate greater than avg return on global minimum variance portfolio")

  # 
  # compute tangency portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  w.t <- cov.mat.inv %*% (er - risk.free) # tangency portfolio
  w.t <- as.vector(w.t/sum(w.t)) # normalize weights
  names(w.t) <- asset.names
  er.t <- crossprod(w.t,er)
  sd.t <- sqrt(t(w.t) %*% cov.mat %*% w.t)
  tan.port <- list("call" = call,
   "er" = as.vector(er.t),
   "sd" = as.vector(sd.t),
   "weights" = w.t)
  class(tan.port) <- "portfolio"
  tan.port
}

efficient.frontier <- 
function(er, cov.mat, nport=20, alpha.min=-0.5, alpha.max=1.5)
{
  # Compute efficient frontier with no short-sales constraints
  #
  # inputs:
  # er   N x 1 vector of expected returns
  # cov.mat   N x N return covariance matrix
  # nport   scalar, number of efficient portfolios to compute
  #
  # output is a Markowitz object with the following elements
  # call   captures function call
  # er   nport x 1 vector of expected returns on efficient porfolios
  # sd   nport x 1 vector of std deviations on efficient portfolios
  # weights  nport x N matrix of weights on efficient portfolios 
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er)
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")

  #
  # create portfolio names
  #
  port.names <- rep("port",nport)
  ns <- seq(1,nport)
  port.names <- paste(port.names,ns)

  #
  # compute global minimum variance portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  one.vec <- rep(1,length(er))
  port.gmin <- globalMin.portfolio(er,cov.mat)
  w.gmin <- port.gmin$weights

  #
  # compute efficient frontier as convex combinations of two efficient portfolios
  # 1st efficient port: global min var portfolio
  # 2nd efficient port: min var port with ER = max of ER for all assets
  #
  er.max <- max(er)
  port.max <- efficient.portfolio(er,cov.mat,er.max)
  w.max <- port.max$weights    
  a <- seq(from=alpha.min,to=alpha.max,length=nport) # convex combinations
  we.mat <- a %o% w.gmin + (1-a) %o% w.max # rows are efficient portfolios
  er.e <- we.mat %*% er # expected returns of efficient portfolios
  er.e <- as.vector(er.e)
  names(er.e) <- port.names
  cov.e <- we.mat %*% cov.mat %*% t(we.mat) # cov mat of efficient portfolios
  sd.e <- sqrt(diag(cov.e)) # std devs of efficient portfolios
  sd.e <- as.vector(sd.e)
  names(sd.e) <- port.names
  dimnames(we.mat) <- list(port.names,asset.names)

  # 
  # summarize results
  #
  ans <- list("call" = call,
      "er" = er.e,
      "sd" = sd.e,
      "weights" = we.mat)
  class(ans) <- "Markowitz"
  ans
}

#
# print method for portfolio object
print.portfolio <- function(x, ...)
{
  cat("Call:\n")
  print(x$call, ...)
  cat("\nPortfolio expected return:    ", format(x$er, ...), "\n")
  cat("Portfolio standard deviation: ", format(x$sd, ...), "\n")
  cat("Portfolio weights:\n")
  print(round(x$weights,4), ...)
  invisible(x)
}

#
# summary method for portfolio object
summary.portfolio <- function(object, risk.free=NULL, ...)
# risk.free risk-free rate. If not null then
# compute and print Sharpe ratio

{
  cat("Call:\n")
  print(object$call)
  cat("\nPortfolio expected return:    ", format(object$er, ...), "\n")
  cat(  "Portfolio standard deviation: ", format(object$sd, ...), "\n")
  if(!is.null(risk.free)) {
    SharpeRatio <- (object$er - risk.free)/object$sd
    cat("Portfolio Sharpe Ratio:       ", format(SharpeRatio), "\n")
  }
  cat("Portfolio weights:\n")
  print(round(object$weights,4), ...)
  invisible(object)
}
# hard-coded 4 digits; prefer to let user control, via ... or other argument

#
# plot method for portfolio object
plot.portfolio <- function(object, ...)
{
  asset.names <- names(object$weights)
  barplot(object$weights, names=asset.names,
  xlab="Assets", ylab="Weight", main="Portfolio Weights", ...)
  invisible()
}

#
# print method for Markowitz object
print.Markowitz <- function(x, ...)
{
  cat("Call:\n")
  print(x$call)
  xx <- rbind(x$er,x$sd)
  dimnames(xx)[[1]] <- c("ER","SD")
  cat("\nFrontier portfolios' expected returns and standard deviations\n")
  print(round(xx,4), ...)
  invisible(x)
}
# hard-coded 4, should let user control

#
# summary method for Markowitz object
summary.Markowitz <- function(object, risk.free=NULL)
{
  call <- object$call
  asset.names <- colnames(object$weights)
  port.names <- rownames(object$weights)
  if(!is.null(risk.free)) {
    # compute efficient portfolios with a risk-free asset
    nport <- length(object$er)
    sd.max <- object$sd[1]
    sd.e <- seq(from=0,to=sd.max,length=nport)
    names(sd.e) <- port.names

    #
    # get original er and cov.mat data from call 
    er <- eval(object$call$er)
    cov.mat <- eval(object$call$cov.mat)

    #
    # compute tangency portfolio
    tan.port <- tangency.portfolio(er,cov.mat,risk.free)
    x.t <- sd.e/tan.port$sd # weights in tangency port
    rf <- 1 - x.t # weights in t-bills
    er.e <- risk.free + x.t*(tan.port$er - risk.free)
    names(er.e) <- port.names
    we.mat <- x.t %o% tan.port$weights # rows are efficient portfolios
    dimnames(we.mat) <- list(port.names, asset.names)
    we.mat <- cbind(rf,we.mat) 
  }
  else {
    er.e <- object$er
    sd.e <- object$sd
    we.mat <- object$weights
  }
  ans <- list("call" = call,
      "er"=er.e,
      "sd"=sd.e,
      "weights"=we.mat)
  class(ans) <- "summary.Markowitz"
  ans
}

print.summary.Markowitz <- function(x, ...)
{
xx <- rbind(x$er,x$sd)
port.names <- names(x$er)
asset.names <- colnames(x$weights)
dimnames(xx)[[1]] <- c("ER","SD")
cat("Frontier portfolios' expected returns and standard deviations\n")
print(round(xx,4), ...)
cat("\nPortfolio weights:\n")
print(round(x$weights,4), ...)
invisible(x)
}
# hard-coded 4, should let user control

#
# plot efficient frontier
#
# things to add: plot original assets with names
# tangency portfolio
# global min portfolio
# risk free asset and line connecting rf to tangency portfolio
#
plot.Markowitz <- function(object, plot.assets=FALSE, ...)
# plot.assets logical. If true then plot asset sd and er
{
  if (!plot.assets) {
     y.lim=c(0,max(object$er))
     x.lim=c(0,max(object$sd))
     plot(object$sd,object$er,type="b",xlim=x.lim, ylim=y.lim,
          xlab="Portfolio SD", ylab="Portfolio ER", 
          main="Efficient Frontier", ...)
     }
  else {
  call = object$call
  mu.vals = eval(call$er)
  sd.vals = sqrt( diag( eval(call$cov.mat) ) )
  y.lim = range(c(0,mu.vals,object$er))
  x.lim = range(c(0,sd.vals,object$sd))
  plot(object$sd,object$er,type="b", xlim=x.lim, ylim=y.lim,
          xlab="Portfolio SD", ylab="Portfolio ER", 
          main="Efficient Frontier", ...)
        text(sd.vals, mu.vals, labels=names(mu.vals))
  }
  invisible()
}



Source the r source codes above.

sample code(s)
source("H:/Quantitative Research/R/Optimization/portfolio.r")






Sample ticker code:

SPY SPDR S&P 500 ETF TRUST
EFA ISHARES MSCI EAFE ETF
LQD ISHARES IBOXX INVESTMENT GRA
HYG ISHARES IBOXX HIGH YIELD COR


Weekly basis stats (in decimal numbers):

(1) Risk-free return

Risk-free 0.00018631 

(2) Average Return and Standard Deviation of Returns

Weekly SPY EFA LQD HYG
Average 0.00260290 0.00120427 0.00110844 0.00132166
SD 0.02029120 0.02491309 0.00788386 0.01151264


(3) Covariance matrix

Cov SPY EFA LQD HYG
SPY 0.00041173 0.00045214 -0.00000957 0.00017600
EFA 0.00045214 0.00062066 -0.00000215 0.00021265
LQD -0.00000957 -0.00000215 0.00006216 0.00002826
HYG -0.00000617 -0.00000570 0.00000461 -0.00000108


Annual basis stats (in decimal numbers):

(1') Geometrical-Average Risk-free return

Annual
Risk-free 0.00973449


(2') Geometrical-Average Return and Standard Deviation of Returns (SD figures of (2) are multiplied by 52^0.5.)

Annual SPY EFA LQD HYG
Average 0.14473728 0.06458429 0.05929884 0.07109433
SD 0.14632190 0.17965088 0.05685135 0.08301885

(3') Covariance matrix --- all the numbers of the covariance of (3) are multiplied by 52 (weeks).

Cov SPY EFA LQD HYG
SPY 0.02141010 0.02351144 -0.00049749 0.00915216
EFA 0.02351144 0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169 0.00323208 0.00146939
HYG 0.00915216 0.01105802 0.00146939 0.00689213



Stats on a annual basis:

sample code(s)
# annual

r.free <- 0.00973449

r.free
[1] 0.00973449


er <- c(
 "SPY" = 0.14473728,
 "EFA" = 0.06458429,
 "LQD" = 0.05929884,
 "EEM" = 0.07109433
)

er
       SPY        EFA        LQD        EEM
0.14473728 0.06458429 0.05929884 0.07109433



covmat <- matrix(c(0.02141010, 0.02351144, -0.00049749, 0.00915216, 0.02351144, 0.03227444, -0.00011169, 0.01105802, -0.00049749, -0.00011169, 0.00323208, 0.00146939, 0.00915216, 0.01105802, 0.00146939, 0.00689213), nrow = 4, ncol = 4, byrow = TRUE)


rownames(covmat) <- c("SPY", "EFA", "LQD", "HYG")
colnames(covmat) <- c("SPY", "EFA", "LQD", "HYG")


covmat
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213





Equal-weighted Portfolio

sample code(s)
# [1 1 1 1]
rep(1,4)


# equally-weighted
ew = rep(1,4)/4
ew

[1] 0.25 0.25 0.25 0.25


equalWeight.portfolio = getPortfolio(er=er,cov.mat=covmat,weights=ew)


equalWeight.portfolio
Call:
getPortfolio(er = er, cov.mat = covmat, weights = ew)

Portfolio expected return:     0.08492869
Portfolio standard deviation:  0.09777922
Portfolio weights:
 SPY  EFA  LQD  EEM
0.25 0.25 0.25 0.25


plot(equalWeight.portfolio)
# If you want to change the color, then try:
# plot(equalWeight.portfolio, col="blue")





Tangency Portfolio (long-short)


sample code(s)

tan.port <- tangency.portfolio(er, covmat, r.free)
# You can also run without a "shorts" option.
# tan.port <- tangency.portfolio(er, covmat, r.free)


tan.port
Call:
tangency.portfolio(er = er, cov.mat = covmat, risk.free = r.free,
    shorts = TRUE)

Portfolio expected return:     0.1371406
Portfolio standard deviation:  0.06950249
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.9766 -0.5690  0.8118 -0.2194


print(tan.port)
Call:
tangency.portfolio(er = er, cov.mat = covmat, risk.free = r.free,
    shorts = TRUE)

Portfolio expected return:     0.1371406
Portfolio standard deviation:  0.06950249
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.9766 -0.5690  0.8118 -0.2194


plot(tan.port)






Minimum Volatility Portfolio (long-short)


sample code(s)

gmin.port <- globalMin.portfolio(er, covmat)

gmin.port
Call:
globalMin.portfolio(er = er, cov.mat = covmat, shorts = TRUE)

Portfolio expected return:     0.07863945
Portfolio standard deviation:  0.05111289
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.2210 -0.1071  0.7990  0.0872


attributes(gmin.port)
$names
[1] "call"    "er"      "sd"      "weights"

$class
[1] "portfolio"


print(gmin.port)
Call:
globalMin.portfolio(er = er, cov.mat = covmat, shorts = TRUE)

Portfolio expected return:     0.07863945
Portfolio standard deviation:  0.05111289
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.2210 -0.1071  0.7990  0.0872


plot(gmin.port)






Target Return Portfolio (long-short)


sample code(s)

target.return = er["SPY"]


target.return
      SPY
0.1447373


e.port.SPY = efficient.portfolio(er, covmat, target.return)

e.port.SPY
Call:
efficient.portfolio(er = er, cov.mat = covmat, target.return = target.return,
    shorts = TRUE)

Portfolio expected return:     0.1447373
Portfolio standard deviation:  0.0737838
Portfolio weights:
    SPY     EFA     LQD     EEM
 1.0747 -0.6290  0.8135 -0.2592

plot(e.port.SPY)





Efficient Frontier (long-short)

sample code(s)

ef = efficient.frontier(er, covmat, alpha.min = -2, alpha.max = 1.5, nport = 20)


attributes(ef)
$names
[1] "call"    "er"      "sd"      "weights"

$class
[1] "Markowitz"


summary(ef)
Frontier portfolios' expected returns and standard deviations
   port 1 port 2 port 3 port 4 port 5 port 6 port 7 port 8 port 9 port 10
ER 0.2769 0.2648 0.2526 0.2404 0.2282 0.2161 0.2039 0.1917 0.1795  0.1673
SD 0.1676 0.1583 0.1491 0.1399 0.1308 0.1219 0.1130 0.1044 0.0960  0.0878
   port 11 port 12 port 13 port 14 port 15 port 16 port 17 port 18 port 19
ER  0.1552  0.1430  0.1308  0.1186  0.1065  0.0943  0.0821  0.0699  0.0578
SD  0.0801  0.0728  0.0662  0.0604  0.0558  0.0526  0.0512  0.0516  0.0538
   port 20
ER  0.0456
SD  0.0576

Portfolio weights:
            SPY     EFA    LQD     EEM
port 1   2.7822 -1.6727 0.8425 -0.9520
port 2   2.6249 -1.5766 0.8399 -0.8882
port 3   2.4676 -1.4804 0.8372 -0.8244
port 4   2.3104 -1.3843 0.8345 -0.7606
port 5   2.1531 -1.2882 0.8318 -0.6968
port 6   1.9958 -1.1920 0.8292 -0.6330
port 7   1.8386 -1.0959 0.8265 -0.5692
port 8   1.6813 -0.9998 0.8238 -0.5053
port 9   1.5240 -0.9036 0.8211 -0.4415
port 10  1.3668 -0.8075 0.8185 -0.3777
port 11  1.2095 -0.7114 0.8158 -0.3139
port 12  1.0522 -0.6152 0.8131 -0.2501
port 13  0.8950 -0.5191 0.8104 -0.1863
port 14  0.7377 -0.4230 0.8078 -0.1225
port 15  0.5804 -0.3268 0.8051 -0.0587
port 16  0.4232 -0.2307 0.8024  0.0051
port 17  0.2659 -0.1346 0.7998  0.0689
port 18  0.1086 -0.0385 0.7971  0.1327
port 19 -0.0486  0.0577 0.7944  0.1966
port 20 -0.2059  0.1538 0.7917  0.2604


plot(ef, plot.assets=T, col="blue", pch=16)


points(gmin.port$sd, gmin.port$er,
col="green", pch=16, cex=2)


points(tan.port$sd, tan.port$er, col="red",
pch=16, cex=2)


text(gmin.port$sd, gmin.port$er,
labels="GLOBAL MIN", pos=2)


text(tan.port$sd, tan.port$er,
labels="TANGENCY", pos=2)


sr.tan = (tan.port$er - r.free)/tan.port$sd


abline(a=r.free, b=sr.tan, col="green", lwd=2)









No comments:

Post a Comment