Strategy Library

Commodities Futures Trend Following

Abstract

In this tutorial we implement a trend following strategy on commodities futures based on a 2014 paper "Two Centuries Of Trend Following" by Y. Lempérière, C. Deremble, P. Seager, M. Potters, and J. P. Bouchaud.

Introduction

The paper highlights the existence of trends as an anomaly that contradicts the efficient market hypothesis. If financial markets are completely efficient as the hypothesis suggests, then asset price changes should be totally unpredictable. In other words, no systematic excess return based on public information should exist since asset prices ought to reflect all public information available. However observationally, trend existence in the market do exist. They make it possible to use the simple trend following strategy which states, buy when prices goes up and sell when prices goes down. Numerous academic studies have demonstrated that trend following strategies generate persistent returns over long periods of time.

The paper extends the backtest period of trend following strategies to two centuries and demonstrates statistically significant systematic excess returns on four asset classes (commodities, currencies, stock indices, and bonds). It implements a risk managed strategy that buys or sells a quantity of \(\sigma_n^{-1}\) of the underlying contract depending on the sign of \(s_n\).

The signal \(s_n(t)\) at the beginning of month t is:

\[s_n(t) = \frac{p(t-1)-\text{<}p\text{>}_{n,t-1}}{\sigma_n(t-1)}\]

where \(\text{<}p\text{>}_{n,t-1}\) is last month's exponential moving average of past prices with a decay rate equal to n months,\(p(t-1)\) is the price of last month, and \(\sigma_n(t-1)\) is last month's volatility, estimated as the exponential moving average of the absolute monthly price changes, with a decay rate equal to n months. The decay rate was set to 5 months.

Below, we will implement the above monthly-rebalanced trend following strategy on commodities futures.

Method

The strategy requires the continuous futures contract, so we import the custom data from Quandl. We manually create a universe of tradable commodity futures. They are all liquid and active continuous contracts #1. The data from Quandl are non-adjusted price based on spot-month continuous contract calculations. The data resolution is daily.

Step 1: Import futures data from Quandl

The paper selected a well-balanced commodities pool to include 7 representative contracts: Crude oil, Henry Hub Natural Gas, Corn, Wheat, Super, Live Cattle and Copper. We will add continuous futures data of these contracts from Quandl. This implementation performs a backtest on 20 years as opposed to 200 years of data for the purpose of comparing to benchmark SPY.

from QuantConnect.Python import PythonQuandl
class ImprovedCommodityMomentumTrading(QCAlgorithm):
	def Initialize(self):
		tickers = ["CHRIS/CME_W1",  # Wheat Futures, Continuous Contract #1
                   "CHRIS/CME_C1",  # Corn Futures, Continuous Contract #1
                   "CHRIS/CME_LC1", # Live Cattle Futures, Continuous Contract #1 
                   "CHRIS/CME_CL1",  # Crude Oil Futures, Continuous Contract #1
                   "CHRIS/CME_NG1",  # Natural Gas (Henry Hub) Physical Futures, Continuous Contract #1
                   "CHRIS/LIFFE_W1", # White Sugar Future, Continuous Contract #1
                   "CHRIS/CME_HG1"] # Copper Futures, Continuous Contract #1
		for ticker in tickers:
			data = self.AddData(QuandlFutures, ticker, Resolution.Daily)
			data.SetLeverage(3) 
class QuandlFutures(PythonQuandl):
    def __init__(self):
        self.ValueColumnName = "Settle"

Step 2: Create a SymbolData class to store and update the number of contracts to trade for each security

In Initialize(), we create a dictionary to store the SymbolData object for each security. The strategy is designed to trade monthly, so we will create a monthly consolidator for each security as well. When a new monthly data becomes available, the consolidator calls an event handler CalendarHandler. Within this event handler, we will update the SymbolData object with the freshly received monthly data.

def Initialize(self):                   
	# Container to store the SymbolData object for each security
	self.Data = {}
	
	for ticker in tickers:
		# Add Quandl data and set desired leverage
		data = self.AddData(QuandlFutures, ticker, Resolution.Daily)
		data.SetLeverage(3) 
		
		# Create a monthly consolidator for each security
		self.Consolidate(ticker, CalendarType.Monthly, self.CalendarHandler)
		
		# Create a SymbolData object for each security to store relevant indicators 
		# and calculate quantity of contracts to Buy/Sell
		self.Data[data.Symbol] = SymbolData()
def CalendarHandler(self, bar):
	'''
	Event Handler that updates the SymbolData object for each security when a new monthly bar becomes available
	'''
	self.Data[bar.Symbol].Update(bar)

The SymbolData class is designed to contain everything we need for calculating how many contracts to Buy/Sell at the beginning of each month. LEAN provides helpful indicators to get the exponential moving average and momentum indicators. The Introduction section above detailed the formula for calculating the number of contracts to Buy/Sell. We implement the formula in the Update function.

class SymbolData:
    '''
    Contains the relevant indicators used to calculate number of contracts to Buy/Sell
    '''
    def __init__(self):
        self.ema = ExponentialMovingAverage("MonthEMA", 5)

        # Volatility estimation is defined as the EMA of absolute monthly price changes
        # Use Momentum indicator to get absolute monthly price changes. 
        # Then use the IndicatorExtensions.EMA and pass the momentum indicator values to get the volatility
        self.mom = Momentum("MonthMOM", 1)
        # Note: self.vol will automatically be updated with self.mom
        self.vol = IndicatorExtensions.EMA(self.mom, 5)

        self.Quantity = 0


    def Update(self, bar):
        self.ema.Update(bar.Time, bar.Value)
        self.mom.Update(bar.Time, bar.Value)
        
        if self.ema.IsReady and self.vol.IsReady:
            # Equation 1 in [1]
            signal = (bar.Value - self.ema.Current.Value) / self.vol.Current.Value
            # Equation 2 in [1]
            self.Quantity = np.sign(signal)/abs(self.vol.Current.Value)

        return self.Quantity != 0

Step 3: Buy and Sell at the beginning of each month

Now we’ll place orders based on the quantity of contracts calculated from previous month stored in the SymbolData object. Note that we warm up the algorithm with 150 days of data to allow the algorithm to execute trades on the start date.

def OnData(self, data):
        '''
        Buy/Sell security every month
        '''
        if self.Time < self.nextRebalance or self.IsWarmingUp:
            return

        for symbol in data.Keys:
            symbolData = self.Data[symbol]
            if symbolData.Quantity != 0:
                self.MarketOrder(symbol, symbolData.Quantity)

        self.nextRebalance = Expiry.EndOfMonth(self.Time)

Summary

For the backtest period (January 1998 to September 2019), the trend following strategy produced a Sharpe ratio of 0.266, compared to SPY’s Sharpe ratio of 0.459. The positive performance of the trend-following strategy over the approximately 20-year time horizon indeed suggests the existence of statistically significant, anomalous, systematic, excess returns from trends. Furthermore, the paper suggests the anomaly is universal across 3 other asset classes (currencies, stock indices and bonds).

This tutorial demonstrates trends as one of the most powerful sources of anomalous excess returns in financial markets. We hope to inspire the community to develop more trend-based strategies and encourage you to test out this strategy on other asset classes from the original paper.

Algorithm

References

  1. Y. Lempérière, C. Deremble, P. Seager, M. Potters, J. P. Bouchaud (2014). Two centuries of trend following. Online Copy

    You can also see our Documentation and Videos. You can also get in touch with us via Chat.

    Did you find this page helpful?