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.

from QuantConnect.Python import PythonQuandl

class ImprovedCommodityMomentumTrading(QCAlgorithm):
    def initialize(self):
        tickers = [Futures.grains.wheat,
                   Futures.grains.corn,
                   Futures.meats.live_cattle,
                   Futures.energies.CRUDE_OIL_WTI,
                   Futures.energies.natural_gas,
                   Futures.softs.sugar11,
                   Futures.metals.copper]
        for ticker in tickers:
            # subscribe to continuous future data and set desired leverage
            future = self.add_future(ticker,
                resolution = Resolution.DAILY,
                extended_market_hours = True,
                data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
                data_mapping_mode = DataMappingMode.OPEN_INTEREST,
                contract_depth_offset = 0
            )
            future.set_leverage(3)

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.

def initialize(self):                   
    # Container to store the SymbolData object for each security
    self.data = {}

    for ticker in tickers:
        # subscribe to continuous future data and set desired leverage
            future = self.add_future(ticker,
                resolution = Resolution.DAILY,
                extended_market_hours = True,
                data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
                data_mapping_mode = DataMappingMode.OPEN_INTEREST,
                contract_depth_offset = 0
            )
            future.set_leverage(3) 

        # Create a monthly consolidator for each security
        self.consolidate(future.symbol, CalendarType.MONTHLY, self.calendar_handler)

        # Create a SymbolData object for each security to store relevant indicators 
        # and calculate quantity of contracts to Buy/Sell
        self.data[future.symbol] = SymbolData(future)

def calendar_handler(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. 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.

class SymbolData:
    '''
    Contains the relevant indicators used to calculate number of contracts to Buy/Sell
    '''
    def __init__(self, future):
        self.future = future
        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

    @property
    def mapped(self):
        return self.future.mapped

    @property
    def contract_multiplier(self):
        return self.future.symbol_properties.contract_multiplier

    def update(self, bar):
        self.ema.update(bar.time, bar.value)
        self.mom.update(bar.time, bar.value)

        if self.ema.is_ready and self.vol.is_ready:
            # Equation 1 in [1]
            signal = (bar.value - self.ema.current.value) / self.vol.current.value
            # Equation 2 in [1], assuming price change is the same next step
            self.quantity = np.sign(signal) * self.mom.current.value / 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 on_data(self, data):
    '''
    Buy/Sell security every month
    '''
    if self.is_warming_up:
        return

    for symbol, symbolData in self.data.items():
        if not data.bars.contains_key(symbol) or self.time < self.next_rebalance[symbol]:
            continue

        if symbolData.quantity != 0:
            # divided by the contract multiplier
            self.market_order(symbolData.mapped, self.leverage * symbolData.quantity // symbolData.contract_multiplier)

        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