Overall Statistics
Total Trades
286
Average Win
0.95%
Average Loss
-1.12%
Compounding Annual Return
-5.266%
Drawdown
26.400%
Expectancy
-0.103
Net Profit
-12.656%
Sharpe Ratio
-0.18
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
0.85
Alpha
-0.083
Beta
3.493
Annual Standard Deviation
0.161
Annual Variance
0.026
Information Ratio
-0.276
Tracking Error
0.161
Treynor Ratio
-0.008
Total Fees
$2643.65
 
 
# http://quantpedia.com/Screener/Details/22
from datetime import timedelta
from math import floor
from decimal import Decimal
import numpy as np

class CommodityTermStructureAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2016, 1, 1)
        self.SetEndDate(2018, 7, 1)
        self.SetCash(1000000)
        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)
       
    def OnData(self,slice):
        # 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]
                 
    
    def Rebalance(self):

        self.Liquidate()
        quintile = floor(len(self.chains)/5)
        roll_returns = {}
        for symbol, chain in self.chains.items():
            contracts = sorted(chain, key = lambda x: x.Expiry)
            
            # R = (log(Pn) - log(Pd)) * 365 / (Td - Tn)
            # R - Roll returns
            # Pn - Nearest contract price
            # Pd - Distant contract price
            # Tn - Nearest contract expire date
            # Pd - Distant contract expire date

            near_contract = contracts[0]
            distant_contract = contracts[-1]
            price_near = near_contract.LastPrice if near_contract.LastPrice>0 else 0.5*float(near_contract.AskPrice+near_contract.BidPrice)
            price_distant = distant_contract.LastPrice if distant_contract.LastPrice>0 else 0.5*float(distant_contract.AskPrice+distant_contract.BidPrice)
            
            if distant_contract.Expiry == near_contract.Expiry:
                self.Debug("ERROR: Near and distant contracts have the same expiry!" + str(near_contract))
                return
            expire_range = 365 / (distant_contract.Expiry - near_contract.Expiry).days
            roll_returns[symbol] = (np.log(float(price_near)) - np.log(float(price_distant)))*expire_range
            positive_roll_returns = { symbol: returns for symbol, returns in roll_returns.items() if returns > 0 }
            negative_roll_returns = { symbol: returns for symbol, returns in roll_returns.items() if returns < 0 }

        backwardation = sorted(positive_roll_returns , key = lambda x: positive_roll_returns[x], reverse = True)[:quintile]
        contango = sorted(negative_roll_returns , key = lambda x: negative_roll_returns[x])[:quintile]
        count = min(len(backwardation), len(contango))
        if  count != quintile:
            backwardation = backwardation[:count]
            contango = contango[:count]
        
        #  We cannot long-short if count is zero
        if count == 0:
            self.chains = {}
            return

        for short_symbol in contango:
            sort = sorted(self.chains[short_symbol], key = lambda x: x.Expiry)
            self.SetHoldings(sort[1].Symbol, -0.5/count)

        for long_symbol in backwardation:
            sort = sorted(self.chains[long_symbol], key = lambda x: x.Expiry)
            self.SetHoldings(sort[1].Symbol, 0.5/count)

        
        self.chains = {}