This paper examines the informational content of commodity futures term structures over time. Time series of commodity prices and returns are analyzed by means of static and rolling principal component analysis. We use weekly data from January 1998 to July 2009 of 23 commodity underlyings from Energy, Metals, Agriculture and Livestock. We find high stability of the principal components and their explanatory power over time. The first component identified as a level factor is paramount for the interpretation of term structure dynamics for most underlyings. This result suggests that an investor can exploit the information contained within the term structure and revealed by principal component analysis. We formulate three distinctive investment strategies based on term structure information which optimize roll yields. By creating portfolios according to a principal component ranking we significantly outperform a long-only benchmark.
Keywords: Futures term structure, Roll yield, Convenience yield, Contango, Backwardation, Commodity trading strategy, Principal component analysis
- The investment universe is all commodity futures contracts.
- The approach involves buying the top quintile (20%) of commodities exhibiting the highest roll-returns.
- Simultaneously, sell short the bottom quintile (20%) of commodities showing the lowest roll-returns.
- Maintain the long-short positions for a duration of one month.
- Contracts in each quintile are equally-weighted.
import backtrader as bt class RollRankStrategy(bt.Strategy): def __init__(self): # divide investment universe into quintiles self.quintiles = 5 # get the number of contracts in each quintile self.contracts_per_quintile = len(self.datas) // self.quintiles # get the length of roll period self.roll_period = 30 # assuming the trading period is 1 month # calculate the number of contracts to trade long/short self.num_longs_shorts = self.contracts_per_quintile // 5 def next(self): # calculate roll-returns for all contracts roll_returns = [d.close[-1] / d(-self.roll_period).close for d in self.datas] # sort contracts by roll-returns ranks = sorted(range(len(roll_returns)), key=lambda x: roll_returns[x]) # go long on top 20% of contracts for i in ranks[-self.num_longs_shorts:]: self.buy(datalist[i], size=1/self.num_longs_shorts) # go short on bottom 20% of contracts for i in ranks[:self.num_longs_shorts]: self.sell(datalist[i], size=1/self.num_longs_shorts)
Note: This is just an example, and the code may need to be adjusted based on the specifics of the commodity futures contracts being traded.