Overall Statistics
Total Trades
636
Average Win
0.36%
Average Loss
-0.30%
Compounding Annual Return
-0.683%
Drawdown
11.800%
Expectancy
0.169
Net Profit
-6.626%
Sharpe Ratio
-0.131
Probabilistic Sharpe Ratio
0.001%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
1.19
Alpha
0.003
Beta
-0.071
Annual Standard Deviation
0.032
Annual Variance
0.001
Information Ratio
-0.767
Tracking Error
0.135
Treynor Ratio
0.059
Total Fees
$1025.05
Estimated Strategy Capacity
$0
Lowest Capacity Asset
HG VDOL5LMM7FKT
#region imports
from AlgorithmImports import *
#endregion

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(2010, 1, 1)
        self.SetEndDate(2020, 1, 1)
        self.SetCash(1000000)
        self.leverage = 3

        tickers = [Futures.Grains.Wheat,
                   Futures.Grains.Corn,
                   Futures.Meats.LiveCattle,
                   Futures.Energies.CrudeOilWTI,
                   Futures.Energies.NaturalGas,
                   Futures.Softs.Sugar11,
                   Futures.Metals.Copper]

        # Container to store the SymbolData object and rebalancing timer for each security
        self.Data = {}
        self.nextRebalance = {}

        for ticker in tickers:
            # subscribe to continuous future data and set desired leverage
            future = self.AddFuture(ticker,
                resolution = Resolution.Daily,
                extendedMarketHours = True,
                dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
                dataMappingMode = DataMappingMode.OpenInterest,
                contractDepthOffset = 0
            )
            future.SetLeverage(self.leverage) 

            # Create a monthly consolidator for each security
            self.Consolidate(future.Symbol, 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[future.Symbol] = SymbolData(future)

            # Set monthly rebalance
            self.nextRebalance[future.Symbol] = self.Time

        # Set decay rate equal to 5 months (150 days) and warm up period
        self.SetWarmUp(150)

    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

        for symbol, symbolData in self.Data.items():
            if self.Time < self.nextRebalance[symbol] or not data.Bars.ContainsKey(symbol):
                continue

            if symbolData.Quantity != 0:
                # divided by the contract multiplier
                self.MarketOrder(symbolData.Mapped, self.leverage * symbolData.Quantity // symbolData.ContractMultiplier)
            
            self.nextRebalance[symbol] = Expiry.EndOfMonth(self.Time)


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 ContractMultiplier(self):
        return self.future.SymbolProperties.ContractMultiplier

    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], 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