3
$\begingroup$

I am interested in calculating the effect of the roll return using different roll strategies. In specific I want to mimic a long-only futures investments. I have historical data for several agricultural commodities. In order to compare the returns of the different roll strategies I created ratio back-adjusted time series using the following formula's.

Ratio adjustment = (Price new contract - Price old contract)/(Price old contract) Adjusted prices = (all old futures * ratio adjustment) + Prices new contract

Since I use historical data ranging from the 80's to the end of 2014 there are multiple rolls. For every I make these adjustments (iterative process). So the latest contract is not adjusted while the first contract is adjusted tens of times (depending on the roll strategy). I want to know what the return of the various roll strategies are. Hence, I am not interested in the prices of the futures contracts itself (I realize that these get distorted by the ratio adjustment). Furthermore, since futures are traded on margin I will take a more academic approach and make the assumption that the futures are fully collateralized. Meaning that a 'x' dollar amount is invested in T-bills, where x is equal to the price of the futures contract. Hence I calculate the return of the roll strategies as follows:

Return = (new adjustment price - old adjustment price)/(old adjustment price) - (t-bill interest earned on amount x) * 100

However, I think I am making a mistake since the return on a soybean position is extraordinary. Somewhere in the 600% (when I do not consider the t-bill interest earned) over a time period from 1997 to 2014 when rolling on the last trading day. What am I missing? How should I calculate and compare the performance of the different roll strategies if this approach is wrong?

** EDIT: Added code R ** This is the function which I use to calculate the roll return in R. Every roll date I iterate over this function:

backAdjTSFun <- function(ts, rollRow, pastRollRow, rollFromColumn,df,adjRatio = FALSE,nextRollRow = NULL, lastColumn = NULL) {
  #adjust the exisiting data by the ratio
  if (is.null(pastRollRow)) {
    currentContractPrices <- df[1:rollRow, rollFromColumn]
    #because you do not necessarily start at the beginning of the dataset
    #there might be some na's from the first row onwards
    #hence, delete these first.
    currentContractPrices <- na.omit(currentContractPrices)
    updatedTS <- as.numeric(currentContractPrices) * as.numeric(adjRatio)
  } else {
    tsRatioAdj <- as.numeric(ts) * as.numeric(adjRatio)
    #You roll at the end of the last trading day to the next contract. Hence, you only need to start counting from the NEXT day. The last trading day is included in the rollFromColumn vector.
    dayAfterPastRoll <- pastRollRow + 1

    #For the deferred roll the last roll is not made. Due to data constraints.
    #Hence you need to make an adjustment and make sure that the data of the second last
    #roll is not adjusted.
    if(!is.null(lastColumn) && lastColumn == TRUE) {
      currentContractPrices <- df[dayAfterPastRoll:rollRow, rollFromColumn]
      updatedTS <- c(tsRatioAdj, currentContractPrices)
    } 
    #For the front month roll and last day roll you should add the contract prices of the
    #last roll unadjusted.
    else {  
      currentContractPrices <- df[dayAfterPastRoll:rollRow, rollFromColumn] * as.numeric(adjRatio)
      #combine the timeseries for the current contract and the already existing adjusted timeseries
      updatedTS <- c(tsRatioAdj, currentContractPrices)
      #if the column is the last column in the sample then
      #select the data in this column and append it to the price vector
      #without adjusting it.
      nextColumn <- rollFromColumn + 1
      if(nextColumn == length(df) && !is.null(nextRollRow)) {
        if(!is.null(lastColumn) && lastColumn == TRUE) {
          nextColumn <- lastColumn
        }
        currentContractPrices <- df[(rollRow+1):nextRollRow, nextColumn]
        updatedTS <- c(updatedTS, currentContractPrices)
      }
    }
  }
  return(updatedTS)
}


ldr <- function (df, settlement = FALSE, ret="roll", startAdj = F) {
  error <- NULL
  rollReturn <- NULL
  retMatrix <- matrix(,nrow=length(df),ncol=13)
  colnames(retMatrix) <- c("previousContract", "newContract", "difference", "rollDate","startDate", "endDate", "Mean roll return", "Std.", "Number of rolls", "Min", "25% - 75%", "Median", "Max")  
  # Ok, there are two possibilities here...
  # Possibility number 1: determine the last trading date of a contract.
  # The last trading is the settlement date
  firstTradingDay <- sapply(df, function (x) min(which(!is.na(x))))
  if (settlement == FALSE) {
    lastTradingDay <- sapply(df, function (x) max(which(!is.na(x))))
  }
  else {
    lastTradingDay <- settlement
  }
  n <- 1
  z <- 1
  pastRollRow <- NULL
  for (i in lastTradingDay) {
    if (n < length(df)) {
      z <- z + 1
      rollFrom <- as.numeric(as.character(df[i,n]))
      rollTo <-  as.numeric(as.character(df[i,n+1]))


      #backadj
      adjRatio <- rollTo/rollFrom
      ts <- backAdjTSFun(ts,i,pastRollRow,n,df,adjRatio,lastTradingDay[z]);
      pastRollRow <- i


      retMatrix[n,1] <- rollFrom
      retMatrix[n,2] <- rollTo
      retMatrix[n,3] <- calcReturn(rollFrom,rollTo)
      retMatrix[n,4] <- as.character(date[i])
    }
    n <- n + 1
  }
  transFormed <- as.numeric(retMatrix[,3])
  quantiles <- quantile(transFormed, c(0.25,0.75), na.rm=T)
  retMatrix[1,5] <- as.character(date[firstTradingDay[1]])
  retMatrix[1,6] <- as.character(date[tail(lastTradingDay,1)])
  retMatrix[1,7] <- mean(transFormed, na.rm=T)   
  retMatrix[1,8] <- sd(transFormed, na.rm=T)   
  retMatrix[1,9] <- length(na.omit(retMatrix[,3]))
  retMatrix[1,10] <- min(transFormed, na.rm=T)
  retMatrix[1:2,11] <- quantiles
  retMatrix[1,12] <- median(transFormed, na.rm=T)
  retMatrix[1,13] <- max(transFormed, na.rm=T)
  #Let the dates overlap/correspond with eachother at every entry across strategies --> add NA's before
  if (startAdj == TRUE && ret == 'ts' || ret == 'tsReturn') {
    ts <- c(rep('NA',firstTradingDay[1]-1),ts)
  }
  return(returnVal(ret, error, retMatrix, ts))  
}

Where df is my dataframe which is Datastream output and data is a vector containing all the dates in the sample.

** EDIT Results **

[1] https://i.stack.imgur.com/FMTlH.jpg "Results"

Where the FMR1 rolls the contract rolls the position on the last day of the month prior to the last settlement day. FMR2 rolls the contract on the last day two months prior to the last settlement day and LDR rolls the contract on the last settlement day of a contract.

$\endgroup$

1 Answer 1

0
$\begingroup$

Well. A naive quick adjustment method that I ran showed about a 230% increase over the same period excluding any interest so this result is not necessarily unreasonable by itself. Adjusted returns can occasionally depend drastically on what your roll rules are especially as you go deep into history.

Perhaps take a quick scan over your prices/returns. Some contracts (November for Soy plus others?) are very thinly traded and should be excluded from roll series. A bad price on a roll date can mess up a series.

$\endgroup$
5
  • $\begingroup$ Thank you for your answer @rhaskett ! I think depending on your findings I am making a mistake in my code but I don't know what. I added my function to the original question. Do you know what I'm doing wrong? Furthermore, thank you for pointing out the liquidity issue. This is definitely something which I have to look into when my code works properly. $\endgroup$
    – user15050
    Jan 14, 2015 at 16:37
  • $\begingroup$ My ability to read R code is limited as I do most of my work in Python/Pandas. I would recommend two things. Do a small example (two rolls) by hand in excel and make sure it matches your code. If that works, then look at the output of your code for extreme returns and make sure that this isn't a problem with bad data or data processing. This may sound crazy but without more evidence the difference between 600% and 230% is actually not large enough for me to be sure that your code is in error. Illiquid contracts can have weird returns. $\endgroup$
    – rhaskett
    Jan 14, 2015 at 19:42
  • $\begingroup$ I did that already and it seems to me as if the data is correct. However, I was just flabbergasted by the return. This return seemed to me as impossible (especially since the party on the other side of the trade is structurally losing money). Can you share the details of your analysis with me, than I can compare our findings. What is the adjustment you made? What is your start and end date and which day do you roll the position? I edited my original post again and added a graph of my results. $\endgroup$
    – user15050
    Jan 14, 2015 at 20:20
  • $\begingroup$ The source for my estimate is Bloomberg Terminal the details of which are proprietary. Though I have coded ratio/difference adjusted series skipping illiquid months myself in the past. I can tell you that over the same period the graph you show is very similar to what I see but the magnitude seems a bit large. The 230% return at least should not be that shocking over 20 years. Cattle had similar returns. Remember though that you likely don't include trading costs and illiquid months or bad roll rules can mess up your results significantly. You are wondering down a very thorny path. $\endgroup$
    – rhaskett
    Jan 14, 2015 at 21:22
  • $\begingroup$ Ok. Thank you for your responses and the guidance it provided! $\endgroup$
    – user15050
    Jan 15, 2015 at 3:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.