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 = {}