Overall Statistics
Total Trades
3576
Average Win
0.91%
Average Loss
-0.86%
Compounding Annual Return
4.345%
Drawdown
29.300%
Expectancy
0.062
Net Profit
124.525%
Sharpe Ratio
0.407
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.05
Alpha
0.052
Beta
-0.029
Annual Standard Deviation
0.123
Annual Variance
0.015
Information Ratio
-0.063
Tracking Error
0.23
Treynor Ratio
-1.723
Total Fees
$0.00
 
 
# Create a universe of tradable commodity futures. Rank futures performance for each commodity for the last 12 months and divide them into quintiles. 
# Go long on the quintile with the highest momentum and go short on the quintile with the lowest momentum. Rebalance each month.

from datetime import datetime
import numpy as np
from collections import deque

class Momentum_Effect_Commodities(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetEndDate(2019, 1, 1)
        self.SetCash(100000)

        self.symbols = ["CME_S1",   # Soybean Futures, Continuous Contract
                        "CME_W1",   # Wheat Futures, Continuous Contract
                        "CME_SM1",  # Soybean Meal Futures, Continuous Contract
                        "CME_BO1",  # Soybean Oil Futures, Continuous Contract
                        "CME_C1",   # Corn Futures, Continuous Contract
                        "CME_O1",   # Oats Futures, Continuous Contract
                        "CME_LC1",  # Live Cattle Futures, Continuous Contract
                        "CME_FC1",  # Feeder Cattle Futures, Continuous Contract
                        "CME_LN1",  # Lean Hog Futures, Continuous Contract
                        "CME_GC1",  # Gold Futures, Continuous Contract
                        "CME_SI1",  # Silver Futures, Continuous Contract
                        "CME_PL1",  # Platinum Futures, Continuous Contract
                        "CME_CL1",  # Crude Oil Futures, Continuous Contract
                        "CME_HG1",  # Copper Futures, Continuous Contract
                        "CME_LB1",  # Random Length Lumber Futures, Continuous Contract
                        "CME_NG1",  # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
                        "CME_PA1",  # Palladium Futures, Continuous Contract 
                        "CME_RR1",  # Rough Rice Futures, Continuous Contract
                        
                        "ICE_CC1",  # Cocoa Futures, Continuous Contract 
                        "ICE_CT1",  # Cotton No. 2 Futures, Continuous Contract
                        "ICE_KC1",  # Coffee C Futures, Continuous Contract
                        "ICE_O1",   # Heating Oil Futures, Continuous Contract
                        "ICE_OJ1",  # Orange Juice Futures, Continuous Contract
                        "ICE_SB1"   # Sugar No. 11 Futures, Continuous Contract
                        ]
        
        self.lookup_period = 12* 21
        self.SetWarmUp(self.lookup_period)

        # True -> Quantpedia data
        # False -> Quandl free data
        self.use_quantpedia_data = True
        self.data = {}
        
        if not self.use_quantpedia_data:
            self.symbols = ['CHRIS/' + x for x in self.symbols]
        
        for symbol in self.symbols:
            if self.use_quantpedia_data:
                self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
            else:
                self.AddData(QuandlFutures, symbol, Resolution.Daily)
            self.data[symbol] = deque(maxlen=self.lookup_period)
        
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)
    
    def OnData(self, data):
        for key in data.Keys:
            self.Log(f'{data[key].Value}')
        for symbol in self.symbols:
            if self.Securities.ContainsKey(symbol):
                price = self.Securities[symbol].Price
                if price != 0:
                    self.data[symbol].append(float(price))
    
    def Rebalance(self):
        if self.IsWarmingUp: return
        
        # Return sorting
        returns = {}
        for symbol in self.symbols:
            if len(self.data[symbol]) == self.data[symbol].maxlen:
                prices = [x for x in self.data[symbol]]
                returns[symbol] = self.Return(prices)
        
        if len(returns) == 0: return
        
        sorted_by_return = sorted(returns.items(), key = lambda x: x[1], reverse = True)
        quintile = int(len(sorted_by_return)/5)
        long = [x[0] for x in sorted_by_return[:quintile]]
        short = [x[0] for x in sorted_by_return[-quintile:]]

        # Trade execution
        count = len(long + short)
        
        self.Liquidate()
        for symbol in long:
            self.SetHoldings(symbol, 1/count)
        for symbol in short:
            self.SetHoldings(symbol, -1/count)
    
    def Return(self, history):
        return (history[-1] - history[0]) / history[0]

# Quantpedia data
class QuantpediaFutures(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("http://data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        try:
            if not line[0].isdigit(): return None
            split = line.split(';')
            
            data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
            data['settle'] = float(split[1])
            data.Value = float(split[1])
        except:
            return None
            
        return data

# Quandl free data
class QuandlFutures(PythonQuandl):
    def __init__(self):
        self.ValueColumnName = "settle"