options(tinytex.verbose = TRUE)
knitr::opts_knit$set(
  root.dir = normalizePath('..')
)
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  echo = TRUE,
  warning = FALSE,
  error = FALSE
)

Introduction

This vignette aims at reproducing [@levine-al-2018]. Authors find that returns of commodity futures indexes are positive over the long run. Return premiums are associated with both carry and spot returns, and in different economic states vary mostly as a result of moves in the underlying spot price as opposed to the carry component. Economic states are examined as important drivers of commodity returns, even after conditioning on whether commodity markets are in backwardation or contango. Also, commodity returns predictability is analyzed through investment style measures. Each of these analyses is in turn repeated across different horizons for insights on the predictive power variables may exhibit with respect to commodities portfolios' returns. Lastly, to address whether investors should consider allocations in commodities portfolios, authors also tackle a performance and drawdown analysis in comparison with more popular stocks/bonds portfolios. Needless to say, these sort of comparisons are certainly of fundamental importance for investors, however we are more concerned assessing commodities portfolios performance themselves.

First of all, let us introduce some definitions. By the cost of carry model, commodity futures prices are $$ F_{t,T} = S_t e^{(r - \psi)(T - t)} $$ where authors assume the risk-free interest rate $r$ and convenience yield net of storage costs $\psi$ to be constant for simplicity. Carry is the opposite of $(r - \psi)$.

Commodity futures continuously compounded returns are then $$ r_{t,t+1}^{F_T} = \ln(S_{t+1}/S_t) + (\psi - r) $$ with the first term being the spot return and the second the carry component. Or, rearranging terms, $$ r_{t,t+1}^{F_T} = [\ln(S_{t+1}/S_t) - r] + \psi $$ where the first term is the excess of cash spot returns, defined $r_{t,t+1}^{\textrm{ES}}$, while the latter is interpreted as the interest rate-adjusted carry. The equivalent of last equation in simple discrete returns, for a given commodity, is written by authors as $$ R_{t,t+1}^{F_T} = (1 + \psi_{t,t+1})R_{t,t+1}^{\textrm{ES}} + \psi_{t,t+1} \ R_{t,t+1}^{\textrm{ES}} = [(1 + R_{t,t+1}^{\textrm{S}})(1 + R_{t,t+1})] - 1 $$

Commodity portfolios returns performance

construct two commodity futures portfolios, an equal-weighted portfolio to capture the average return across commodities securities and a long-short backwardation-based mimicking portfolio. Economic drivers and investment styles measures are investigated to explain their performance. Also, analyses extends with respect to different sampling periods (1877-2015, 1877-1945, and 1946-2015 as originally done in the paper, and 1877-2020 until the available updated data set permits) and with respect to different analysis horizons (monthly, one year, and five years) to provide some guidance on the predictive power such drivers may exhibit.

Let us begin with an exploratory analysis of these commodities portfolios returns. Summary statistics and graphical representations are presented.

library(quantmod)

parser.path <- file.path('inst', 'parsers', 'COMLR.R')
source(parser.path)

USREC <- getSymbols.FRED('USREC', auto.assign=FALSE)
index(USREC) <- as.yearmon(index(USREC))
CalcReturnMeasures <- function(R, periods, const=100) {
  # @param R An `xts` object with portfolios returns series
  # @param periods A character vector specifying subperiod(s) to subset `R` by 
  # @param const A numeric, multiplication constant. Default to 100 used to express percent values
  out <- vector('list', length(periods))
  res <- matrix(NA, 4, ncol(R), 
    dimnames=list(
      c(
        'Arith.Avg.Ret'
        , 'Geom.Avg.Ret'
        , 'Volatility'
        , 'Skewness'
      ),
      colnames(R)
    )
  )
  for (p in 1:length(periods)) {
    COMLR.sub <- R[periods[p], ]
    # Annualized returns
    for (g in c(FALSE, TRUE)) {
      i <- as.integer(g)
      res[i + 1, ] <- PerformanceAnalytics::Return.annualized(
        COMLR.sub, scale=12, geometric=g
      ) * const
    }
    # Volatility
    res[3, ] <- apply(COMLR.sub, 2, function(x) sd(x, na.rm=TRUE) * sqrt(12) * const)
    # Skewness
    res[4, ] <- as.numeric(PerformanceAnalytics::skewness(COMLR.sub, na.rm=TRUE))
    out[[p]] <- res
  }
  names(out) <- periods
  return(out)
}
CalcReturnMeasures(COMLR[, 1:8], periods=c('1877/2020', '1877/2015', '1877/1945', '1946/2015'))
library(PerformanceAnalytics)

chart.CumReturns(COMLR[,c("XRET.EW","SXRET.EW","CARRY.ADJ.EW")], legend.loc = "topleft", wealth.index = TRUE, geometric = FALSE, main = 'Excess Spot Return/Interest Rate-Adjusted Carry Return \n(cumulative returns %)')

chart.CumReturns(COMLR[,c("XRET.EW","SRET.EW","CARRY.EW")], legend.loc = "topleft", wealth.index = TRUE, geometric = FALSE, main = 'Spot/Carry Return (cumulative returns %)')

Commodity portfolios and macro indicators

In this section the three state variables adopted in are introduced and studied.

  1. Commodity futures market backwardation/contango state
    This ex-ante measure is whether the commodity futures market is in a state of backwardation or contango. Authors determine the state based on the so called aggregate backwardation (contango) as the average level of backwardation (contango) reflecting inventories and hedging demand level across commodities, that is $$ \frac{1}{N}\sum_{i=1}^{N}\frac{F_{i,t,T_{1}} - F_{i,t,T_{2}}}{(T_{2} - T_{1})F_{i,t,T_{1}}} > 0 $$ With $F_{i,t,T_{k}}$ the price of the $i$-th future derivative contract with maturity $T_{k}$, in period $t$.

  2. Economic expansion or recession state
    This variable is regularly estimated and published by The National Bureau of Economic Research from macroeconomic business cycle data. More explicitly, the economy is considered to be expanding when the business cycle moves trough to peak, vice versa an economic recession is registered. We use data series disseminated by the Federal Reserve Bank of St. Louis via FRED.

  3. Inflation state
    Measured by the unexpected inflation as one-year change in one-year inflation, expresses whether the inflation rate lies above or below its full sample mean. It is calculated from the US Consumer Price Index published by the US Bureau of Labor Statistics since 1913, whereas series before 1913 are provided by Prof. Robert Shiller.

LmHorizon <- function(Y, X, horizon, data) {
  # @param Y A character vector, dependent variables. Must be present in `data`, matching column names
  # @param X A character vector, independent variables. Must be present in `data`, matching column names
  # @param horizon A numeric vector, number of months to shift independent variables by
  # @param data An xts object, the data set whose independent variables `X` will be shifted of `horizon`
  out <- vector('list', length(Y))
  for (h in horizon) {
    for(y in Y) {
      i <- match(y, Y)
      j <- match(h, horizon)
      if (is.list(X)) {
        if (length(Y) != length(X)) {
          stop("Y and X must have same length.")
        }
        x <- X[[i]]
      } else {
        x <- X
      }
      data[, x] <- lag(data[, x], h)
      out[[i]][[j]] <- lm(
        formula(paste(y, paste0(x, collapse='+'), sep='~')),
        data=data
      )
      names(out[[i]])[] <- suppressWarnings(
        paste('H', horizon, sep='')
      )
    }
  }
  names(out) <- Y
  return(out)
}
# Prepare data set
data.macros <- merge(
                     COMLR['1878/2015', c('XRET.EW', 'XRET.LS', 'PFC.STATE', 'INFL.STATE')],
                     'USREC' = as.numeric(USREC['1878/2015'])
)

# Regressions
macro.regs <- LmHorizon(
  Y = c('XRET.EW', 'XRET.LS'), 
  X = c('USREC', 'PFC.STATE', 'INFL.STATE'), 
  horizon = c(0, 11, 59),
  data = data.macros 
)

lapply(unlist(macro.regs, recursive=FALSE), summary)

library(FactorAnalytics)

data.macros_df <- data.frame('Date'=index(data.macros), coredata(data.macros))
data.macros_df$PFC.STATE <- as.factor(data.macros_df$PFC.STATE)
data.macros_df$INFL.STATE <- as.factor(data.macros_df$INFL.STATE)


fitTsfm(asset.names = c("XRET.EW", "XRET.LS"),
        factor.names = c('USREC', 'PFC.STATE', 'INFL.STATE'),
        data=data.macros)

Commodity portfolios and investment styles

Next, following authors we run multivariate regressions of the returns of equal-weighted and long-short commodities portfolios on investment styles. Styles they analyze are momentum, measured by previous 12-month return, value measured as the negative 48-month return 12-months ago (which they call "long-term reversal"), and carry measured as the aggregated backwardation (contango) across commodities.

# Value
VAL.EW <- (-1) * lag(PerformanceAnalytics::apply.rolling(COMLR$XRET.EW, 48, cumsum, by=1), 12)
VAL.LS <- (-1) * lag(PerformanceAnalytics::apply.rolling(COMLR$XRET.LS, 48, cumsum, by=1), 12)

# Momentum
MOM.EW <- PerformanceAnalytics::apply.rolling(COMLR$XRET.EW, 12, cumsum, by=1)
MOM.LS <- PerformanceAnalytics::apply.rolling(COMLR$XRET.LS, 12, cumsum, by=1)

# Carry
# measured as the backwardated/contango value of the commodity index
# COMLR$PFC.AGG

data.styles <- merge(
  COMLR['1877/2015', c('XRET.EW', 'XRET.LS', 'PFC.AGG')]
  , VAL.EW
  , VAL.LS
  , MOM.EW
  , MOM.LS
)
colnames(data.styles) <- c(
  'XRET.EW', 'XRET.LS', 'PFC.AGG'
  , 'VAL.EW', 'VAL.LS'
  , 'MOM.EW', 'MOM.LS'
)

# Regressions
style.regs <- LmHorizon(
  Y = c('XRET.EW', 'XRET.LS'), 
  X = list(
    c('MOM.EW', 'VAL.EW', 'PFC.AGG'), 
    c('MOM.LS', 'VAL.LS', 'PFC.AGG')
  ), 
  horizon = c(0, 11, 59),
  data = data.styles
)
lapply(unlist(style.regs, recursive=FALSE), summary)

References



JustinMShea/ExpectedReturns documentation built on Sept. 9, 2023, 9:41 p.m.