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

Step 1: Subscribe to continuous futures data

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 in daily resolution.

  1. from QuantConnect.Python import PythonQuandl
  2. class ImprovedCommodityMomentumTrading(QCAlgorithm):
  3. def initialize(self):
  4. tickers = [Futures.grains.wheat,
  5. Futures.grains.corn,
  6. Futures.meats.live_cattle,
  7. Futures.energies.CRUDE_OIL_WTI,
  8. Futures.energies.natural_gas,
  9. Futures.softs.sugar11,
  10. Futures.metals.copper]
  11. for ticker in tickers:
  12. # subscribe to continuous future data and set desired leverage
  13. future = self.add_future(ticker,
  14. resolution = Resolution.DAILY,
  15. extended_market_hours = True,
  16. data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
  17. data_mapping_mode = DataMappingMode.OPEN_INTEREST,
  18. contract_depth_offset = 0
  19. )
  20. future.set_leverage(3)
+ Expand

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 the consolidator event handler CalendarHandler. Within this event handler, we will update the SymbolData object with the freshly received monthly data.

  1. def initialize(self):
  2. # Container to store the SymbolData object for each security
  3. self.data = {}
  4. for ticker in tickers:
  5. # subscribe to continuous future data and set desired leverage
  6. future = self.add_future(ticker,
  7. resolution = Resolution.DAILY,
  8. extended_market_hours = True,
  9. data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
  10. data_mapping_mode = DataMappingMode.OPEN_INTEREST,
  11. contract_depth_offset = 0
  12. )
  13. future.set_leverage(3)
  14. # Create a monthly consolidator for each security
  15. self.consolidate(future.symbol, CalendarType.MONTHLY, self.calendar_handler)
  16. # Create a SymbolData object for each security to store relevant indicators
  17. # and calculate quantity of contracts to Buy/Sell
  18. self.data[future.symbol] = SymbolData(future)
  19. def calendar_handler(self, bar):
  20. '''
  21. Event Handler that updates the SymbolData object for each security when a new monthly bar becomes available
  22. '''
  23. self.data[bar.symbol].update(bar)
+ Expand

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. We also hold the pointer of mapped contract and contract multiplier as properties of the object for later ordering use. The Introduction section above detailed the formula for calculating the number of contracts to buy/sell. We implement the formula in the Update function.

  1. class SymbolData:
  2. '''
  3. Contains the relevant indicators used to calculate number of contracts to Buy/Sell
  4. '''
  5. def __init__(self, future):
  6. self.future = future
  7. self.ema = ExponentialMovingAverage("MonthEMA", 5)
  8. # Volatility estimation is defined as the EMA of absolute monthly price changes
  9. # Use Momentum indicator to get absolute monthly price changes
  10. # Then use the IndicatorExtensions.EMA and pass the momentum indicator values to get the volatility
  11. self.mom = Momentum("MonthMOM", 1)
  12. # Note: self.vol will automatically be updated with self.mom
  13. self.vol = IndicatorExtensions.EMA(self.mom, 5)
  14. self.quantity = 0
  15. @property
  16. def mapped(self):
  17. return self.future.mapped
  18. @property
  19. def contract_multiplier(self):
  20. return self.future.symbol_properties.contract_multiplier
  21. def update(self, bar):
  22. self.ema.update(bar.time, bar.value)
  23. self.mom.update(bar.time, bar.value)
  24. if self.ema.is_ready and self.vol.is_ready:
  25. # Equation 1 in [1]
  26. signal = (bar.value - self.ema.current.value) / self.vol.current.value
  27. # Equation 2 in [1], assuming price change is the same next step
  28. self.quantity = np.sign(signal) * self.mom.current.value / abs(self.vol.current.value)
  29. return self.quantity != 0
+ Expand

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.

  1. def on_data(self, data):
  2. '''
  3. Buy/Sell security every month
  4. '''
  5. if self.is_warming_up:
  6. return
  7. for symbol, symbolData in self.data.items():
  8. if not data.bars.contains_key(symbol) or self.time < self.next_rebalance[symbol]:
  9. continue
  10. if symbolData.quantity != 0:
  11. # divided by the contract multiplier
  12. self.market_order(symbolData.mapped, self.leverage * symbolData.quantity // symbolData.contract_multiplier)
  13. self.next_rebalance[symbol] = Expiry.end_of_month(self.time)

Summary

For the backtest period (January 2010 to January 2020), the trend following strategy produced a Sharpe ratio of -0.131, compared to SPY’s Sharpe ratio of 0.805, while information ratio is -0.767 and alpha is 0.003. The performance of suggests that the trend information no longer provide extra information for alpha profit generation, unlike the period of the paper tested on.

Trend following was considered one of the best information source of excess return, especially via Futures assets, in the 60s-70s. However, by the technological progression particularly from the last decade, this simple information is absorbed in miliseconds by the more efficient market.



Reference

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

Author

Alethea Lin

September 2019