Overall Statistics
Total Orders
146
Average Win
10.68%
Average Loss
-10.44%
Compounding Annual Return
-55.949%
Drawdown
78.600%
Expectancy
-0.046
Start Equity
10000000
End Equity
4394766.78
Net Profit
-56.052%
Sharpe Ratio
-0.314
Sortino Ratio
-0.391
Probabilistic Sharpe Ratio
5.246%
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
1.02
Alpha
-0.24
Beta
-0.017
Annual Standard Deviation
0.769
Annual Variance
0.591
Information Ratio
-0.41
Tracking Error
0.777
Treynor Ratio
14.259
Total Fees
$76883.69
Estimated Strategy Capacity
$840000.00
Lowest Capacity Asset
PA WHL6E0BVU5KX
Portfolio Turnover
55.38%
#region imports
from AlgorithmImports import *

from math import floor
#endregion
# https://quantpedia.com/Screener/Details/23


class CommodityMomentumCombinedWithTermStructureAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2016, 1, 1)
        self.set_end_date(2017, 1, 1)
        self.set_cash(10000000)
        tickers = [
            Futures.Softs.COCOA,
            Futures.Softs.COFFEE,
            Futures.Grains.CORN,
            Futures.Softs.COTTON_2,
            Futures.Grains.OATS,
            Futures.Softs.ORANGE_JUICE,
            Futures.Grains.SOYBEAN_MEAL,
            Futures.Grains.SOYBEAN_OIL,
            Futures.Grains.SOYBEANS,
            Futures.Softs.SUGAR_11,
            Futures.Grains.WHEAT,
            Futures.Meats.FEEDER_CATTLE,
            Futures.Meats.LEAN_HOGS,
            Futures.Meats.LIVE_CATTLE,
            Futures.Energies.CRUDE_OIL_WTI,
            Futures.Energies.HEATING_OIL,
            Futures.Energies.NATURAL_GAS,
            Futures.Energies.GASOLINE,
            Futures.Metals.GOLD,
            Futures.Metals.PALLADIUM,
            Futures.Metals.PLATINUM,
            Futures.Metals.SILVER
        ]
        for ticker in tickers:
            future = self.add_future(ticker)
            future.set_filter(timedelta(0), timedelta(days=90))       
        self.add_equity("SPY", Resolution.MINUTE)
        self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("SPY", 30), self._rebalance)

    def _rebalance(self):
        # Saves the Futures Chains 
        chains = {}
        for chain in self.current_slice.future_chains:
            if chain.value.contracts.count < 2: 
                continue
            chains[chain.value.symbol.value] = [i for i in chain.value]

        self.liquidate()
        roll_return = {}
        for symbol, chain in chains.items():
            contracts = sorted(chain, key=lambda x: x.expiry)
            expiry_nearest = contracts[0].expiry
            price_nearest = float(contracts[0].last_price) if contracts[0].last_price>0 else 0.5*float(contracts[0].ask_price+contracts[0].bid_price)
            for x in contracts[1:]:
                roll_return[x] = (price_nearest-float(x.last_price))*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.id.to_string()]
            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.id.to_string()]
            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.25/len(low_losers)
        for short in low_losers:
            self.set_holdings(short.symbol, -short_weight)
            
        long_weight = 0.25/len(high_winners)
        for long_ in high_winners:
            self.set_holdings(long_.symbol, long_weight)