Problem with calculating tangency portfolio

Hi.
I have been struggling with the implantation of tangency portfolio in R for a while now, and was hoping some of you might know the answer :slight_smile:

This is the way my professor solved the problem, which works fine:

source("performance.r")
source("markowitz.r")
library("quadprog")

#*********************************************
# Importing the data and selecting the time window
#*********************************************

# read data
data = read.table("marketdata.txt", header=TRUE)

# construct the time series object
data <- ts(data[,2:ncol(data)], start=c(1926,1), frequency = 12)
''
# find the time index of portfolio start 
year.start <- 2000
ind.start <- which(time(data) == year.start)

# you can index the columns in a ts-object by names!
r.stocks <- as.numeric(data[,"STOCKS"])
r.bonds <- as.numeric(data[,"BONDS"])
r.tbill <- as.numeric(data[,"TBILLS"])

nobs <- length(r.stocks)

#*********************************************
# Portfolio management part of the program
#*********************************************

lookback.period = 3*12    # length of lookback period in a number of months
n <- nobs - ind.start + 1 # number of monthly portfolio returns 
r.tanportfolio = rep(0,n)
assets <- cbind(r.stocks, r.bonds)

for (i in 1:n) {
  # find the indices of the lookback period
  period.end <- ind.start + i - 2
  period.start <- period.end - lookback.period + 1
  if (period.start < 1) period.start <- 1 # index cannot be less than 1
  
  # estimate the covariance matrix and mean returns
  covmat <- cov(assets[period.start:period.end,])
  er <- apply(assets[period.start:period.end,], 2, mean)
  # get the risk-free rate of return
  rf <- r.tbill[period.end+1]
  Rf = rf

    w.tanportfolio = tanportfolio(er, covmat, Rf , shorts=FALSE)
    r.tanportfolio[i] = sum(w.tanportfolio*assets[period.end+1])

}

The problems occur when I am trying to replicate the method with another dataset:

source("performance.r")
source("markowitz.r")
source("functions.r")
source("ols.r")
library("quadprog")



data = read.csv("Portfolios_Formed_on_BE-ME.csv", header = TRUE)
data = ts((data[,11:20]/100), start=c(1926,7), frequency = 12)

asset.names = colnames(data) 

factors = read.table(file="F-F_Research_Data_Factors.txt", header=TRUE)
factors = ts((factors[,2:ncol(factors)]/100), start=c(1926,7), frequency = 12)

year.start = 1964
ind.start = which(time(data) == year.start)

assets = as.matrix(data) #transform ts to a matrix


assets1 = as.numeric(assets[,1])
assets2 = as.numeric(assets[,2])
assets3 = as.numeric(assets[,3])
assets4 = as.numeric(assets[,4])
assets5 = as.numeric(assets[,5])
assets6 = as.numeric(assets[,6])
assets7 = as.numeric(assets[,7])
assets8 = as.numeric(assets[,8])
assets9 = as.numeric(assets[,9])
assets10 = as.numeric(assets[,10])

risk.free <- as.numeric(factors[,"RF"])

assets = cbind(assets1, assets2, assets3, assets4, assets5, assets6, assets7,
               assets8, assets9, assets10)

nobs = length(assets1)


nAssets = ncol(assets)

lookback.period = 5*12    # length of lookback period in a number of months
n = nobs - ind.start + 1 # number of monthly portfolio returns 
r.tanportfolio = rep(0,n)

for (i in 1:n) {
  # find the indices of the lookback period
  period.end <- ind.start + i - 2
  period.start <- period.end - lookback.period + 1
  if (period.start < 1) period.start <- 1 # index cannot be less than 1
  
  # estimate the covariance matrix and mean returns
  covmat <- cov(assets[period.start:period.end,])
  er <- apply(assets[period.start:period.end,], 2, mean)
  # get the risk-free rate of return
  
  Rf <- risk.free[period.end+1]
  
  w.tanportfolio = tanportfolio(er, covmat, Rf , shorts=FALSE)
  r.tanportfolio[i] = sum(w.tanportfolio*assets[period.end+1])
}

The error message is as follows: Error in tanportfolio(er, covmat, Rf, shorts = FALSE) :
Risk-free rate greater than mean return on global minimum variance portfolio.
However, when i calculate the values this is not the case..

Here is the code for the tanportfolio:

tanportfolio <- function(er, covmat, Rf, shorts=TRUE) {
  # computes the tangency portfolio
  #
  # inputs:
  # er    			    N x 1 vector of expected returns
  # covmat  			  N x N covariance matrix of returns
  # r          	    scalar, risk-free rate return
  #
  # output is a vector of portfolio weights
  
  #
  # check for valid inputs
  #
  er <- as.vector(er)
  covmat <- as.matrix(covmat)
  if (!is.numeric(Rf) || length(Rf) !=1)
    stop("Risk-free rate is not a scalar")
  if(length(er) != nrow(covmat))
    stop("Mismatch in number of rows")
  if(any(diag(chol(covmat)) <= 0))
    stop("Covariance matrix is not positive definite")
  
  #
  # compute global minimum variance portfolio
  #
  w <- gmvportfolio(covmat, shorts=shorts)
  if(w %*% er < Rf)
    stop("Risk-free rate greater than mean return on global minimum variance portfolio")
  #
  # compute the weights of the tangency portfolio
  #
  if(shorts==TRUE){
    # closed-form solutions when short sales are allowed
    ones <- rep(1, nrow(covmat))  # vector of ones
    covmat.inv <- solve(covmat)   # inverse of covariance matrix
    w <- (covmat.inv %*% (er-Rf))/as.numeric(ones %*% covmat.inv %*% (er-Rf))
    w <- as.vector(w)  
  } else if(shorts==FALSE){
    # numerical solution with no short sales
    n = nrow(covmat)
    Dmat <- covmat
    dvec <- rep.int(0, n)
    Amat <- cbind(er-Rf, diag(1,n))
    bvec <- c(1, rep(0,n))
    result <- solve.QP(Dmat=Dmat,dvec=dvec,Amat=Amat,bvec=bvec,meq=1)
    w <- round(result$solution/sum(result$solution), 6)
  } else {
    stop("shorts needs to be logical. For no-shorts, shorts=FALSE.")
  }  
  return(w)
}  

Big thanks to anyone able to help! :slight_smile:

Hi, and welcome!

Preliminarily, while the code blocks are helpful, they aren't quite a reproducible example, called a reprex, which is always helpful in attracting more and better answers.

In this case, however, it seems pretty clear that differences with the FALSE condition in tanportfolio() vs. w.tanportfolio <- tanportfolio(er, covmat, Rf , shorts=FALSE) is due to some difference in the datasets passed to the er argument, but I have no way of testing that.

2 Likes

Thank you for answering! :slight_smile:
Here is the link to download all the datasets applied https://gofile.io/?c=i2c3Bc

Is this enough to reprex?

Which one is er? If it's constructed from two or more of the files, can you post a reprex with the code, assuming the data linked to is in the working directory?