Overall Statistics
Total Trades
142
Average Win
1.16%
Average Loss
-1.16%
Compounding Annual Return
5.874%
Drawdown
12.800%
Expectancy
0.026
Net Profit
5.907%
Sharpe Ratio
0.541
Loss Rate
49%
Win Rate
51%
Profit-Loss Ratio
0.99
Alpha
0.112
Beta
-4.095
Annual Standard Deviation
0.089
Annual Variance
0.008
Information Ratio
0.367
Tracking Error
0.089
Treynor Ratio
-0.012
Total Fees
$12818.65
# https://quantpedia.com/Screener/Details/23
from datetime import timedelta
from math import floor
from decimal import Decimal
import numpy as np

class CommodityMomentumCombinedWithTermStructureAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2016, 1, 1)
        self.SetEndDate(2017, 1, 1)
        self.SetCash(10000000)
        tickers = [
                Futures.Softs.Cocoa,
                Futures.Softs.Coffee,
                Futures.Grains.Corn,
                Futures.Softs.Cotton2,
                Futures.Grains.Oats,
                Futures.Softs.OrangeJuice,
                Futures.Grains.SoybeanMeal,
                Futures.Grains.SoybeanOil,
                Futures.Grains.Soybeans,
                Futures.Softs.Sugar11,
                Futures.Grains.Wheat,
                Futures.Meats.FeederCattle,
                Futures.Meats.LeanHogs,
                Futures.Meats.LiveCattle,
                Futures.Energies.CrudeOilWTI,
                Futures.Energies.HeatingOil,
                Futures.Energies.NaturalGas,
                Futures.Energies.Gasoline,
                Futures.Metals.Gold,
                Futures.Metals.Palladium,
                Futures.Metals.Platinum,
                Futures.Metals.Silver
            ]
        for ticker in tickers:
            future = self.AddFuture(ticker)
            future.SetFilter(timedelta(0), timedelta(days = 90))       
        self.chains = {}
        self.AddEquity("SPY", Resolution.Minute)
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 30), self.Rebalance)
        self.rebalance = False

        
    def OnData(self,slice):
        if not self.rebalance: return
        # Saves the Futures Chains 
        for chain in slice.FutureChains:
            if chain.Value.Contracts.Count < 2: 
                continue
            if chain.Value.Symbol.Value not in self.chains:
                self.chains[chain.Value.Symbol.Value] =  [i for i in chain.Value]
                
            self.chains[chain.Value.Symbol.Value] = [i for i in chain.Value]

        self.Liquidate()
        roll_return = {}
        for symbol, chain in self.chains.items():
            contracts = sorted(chain, key = lambda x: x.Expiry)
            expiry_nearest = contracts[0].Expiry
            price_nearest = float(contracts[0].LastPrice) if contracts[0].LastPrice>0 else 0.5*float(contracts[0].AskPrice+contracts[0].BidPrice)
            for x in contracts[1:]:
                roll_return[x] = (price_nearest-float(x.LastPrice))*365 / (x.Expiry-expiry_nearest).days
        
        sorted_by_roll_return = sorted(roll_return, key = lambda x: roll_return[x], reverse =True)
        
        tertile = floor(1/3*len(sorted_by_roll_return))
        high = sorted_by_roll_return[:tertile]
        low = sorted_by_roll_return[-tertile:]
        
        mean_return_high = {}
        for i in high:
            hist = self.History(i.Symbol, timedelta(days = 21), Resolution.Minute)
            if hist.empty:
                continue
            hist_close = hist['close'][i.Expiry][i.Symbol.Value]
            mean_return_high[i] = np.mean(hist_close.pct_change())
        
        high_winners = sorted(mean_return_high, key = lambda x: mean_return_high[x], reverse=True)[:int(len(high)*0.5)]


        mean_return_low = {}
        for i in low:
            hist = self.History(i.Symbol, timedelta(days = 21), Resolution.Minute)
            if hist.empty:
                continue
            hist_close = hist['close'][i.Expiry][i.Symbol.Value]
            mean_return_low[i] = np.mean(hist_close.pct_change())
        
        low_losers = sorted(mean_return_low, key = lambda x: mean_return_low[x], reverse=True)[-int(len(low)*0.5):]


        short_weight = 0.5/len(low_losers)
        for short in low_losers:
            self.SetHoldings(short.Symbol, -short_weight)
            
        long_weight = 0.5/len(high_winners)
        for long in high_winners:
            self.SetHoldings(long.Symbol, long_weight)
            
        self.rebalance = False
    
    def Rebalance(self):
        self.rebalance = True