Overall Statistics
Total Trades
657
Average Win
0.23%
Average Loss
-0.14%
Compounding Annual Return
3.567%
Drawdown
45.000%
Expectancy
1.133
Net Profit
113.776%
Sharpe Ratio
0.273
Loss Rate
19%
Win Rate
81%
Profit-Loss Ratio
1.65
Alpha
0.041
Beta
0.142
Annual Standard Deviation
0.193
Annual Variance
0.037
Information Ratio
-0.127
Tracking Error
0.248
Treynor Ratio
0.371
Total Fees
$0.00
from QuantConnect.Python import PythonQuandl
from datetime import timedelta
import numpy as np
import pandas as pd

class CommodityFutureTrendFollowing(QCAlgorithm):
    '''
    Two Centuries of Trend Following
    
    This paper demonstrates the existence of anomalous excess returns based on trend following strategies across commodities, currencies, stock indices, and bonds, and over very long time scales.
    
    Reference:
    [1] Y. Lempérière, C. Deremble, P. Seager, M. Potters, J. P. Bouchaud, "Two centuries of trend following", April 15, 2014.
        URL: https://arxiv.org/pdf/1404.3274.pdf
    '''
    def Initialize(self):

        self.SetStartDate(1998,1, 1) 
        self.SetEndDate(2019, 9, 1)  
        self.SetCash(25000)
                   
        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
                   
        # 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
            MonthlyConsolidator = self.Consolidate(ticker, CalendarType.Monthly, self.CalendarHandler)
            
            # Create a SymbolData object for each security to store relevant indicators and calculated quantity of contracts to Buy/Sell
            self.Data[data.Symbol] = SymbolData()

        # Set decay rate equal to 5 months and warm up period
        period = 150
        self.SetWarmUp(period)
        
        # Set monthly rebalance
        self.nextRebalance = self.Time
        
    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)
        
    def OnData(self, data):
        '''
        Buy/Sell security every month
        '''
        if self.IsWarmingUp: return
        if self.Time < self.nextRebalance: 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)
                
class QuandlFutures(PythonQuandl):
    def __init__(self):
        self.ValueColumnName = "Settle"
        
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.Of and pass the momentum indicator values to get the volatility
        self.mom = Momentum("MonthMOM", 1)
        self.vol = IndicatorExtensions.Of(ExponentialMovingAverage("Vol", 5), self.mom)
        self.Quantity = 0
        
    def Update(self, bar):
        self.ema.Update(bar.Time, bar.Value)
        self.mom.Update(bar.Time, bar.Value)
        self.vol.Update(bar.Time, self.mom.Current.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