Overall Statistics
Total Trades
116
Average Win
4.48%
Average Loss
-4.21%
Compounding Annual Return
-18.568%
Drawdown
56.500%
Expectancy
-0.266
Net Profit
-46.086%
Sharpe Ratio
-0.059
Probabilistic Sharpe Ratio
1.360%
Loss Rate
64%
Win Rate
36%
Profit-Loss Ratio
1.06
Alpha
-0.025
Beta
-0.039
Annual Standard Deviation
0.476
Annual Variance
0.227
Information Ratio
-0.227
Tracking Error
0.488
Treynor Ratio
0.715
Total Fees
$335.92
Estimated Strategy Capacity
$72000.00
Lowest Capacity Asset
CSC WQWXTW2FXGJL
#region imports
from AlgorithmImports import *
#endregion

class CommidityMomentumEffect(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015,1, 1) 
        self.SetEndDate(2018, 1, 1)  
        self.SetCash(100000)

        self.symbols = [
            Futures.Dairy.CashSettledButter,
            Futures.Dairy.CashSettledCheese,
            Futures.Dairy.ClassIIIMilk,
            Futures.Dairy.DryWhey,
            Futures.Dairy.ClassIVMilk,
            Futures.Dairy.NonfatDryMilk,
            Futures.Meats.LiveCattle,
            Futures.Meats.FeederCattle,
            Futures.Meats.LeanHogs,
            Futures.Forestry.RandomLengthLumber
        ]

        period = 252
        self.data = {}
        for symbol in self.symbols:
            future = self.AddFuture(symbol,
                resolution = Resolution.Daily,
                extendedMarketHours = True,
                dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
                dataMappingMode = DataMappingMode.OpenInterest,
                contractDepthOffset = 0
            )
            future.SetLeverage(1)
            self.data[future.Symbol] = SymbolData(self, future, period)

        # Rebalance the portfolio every month
        self.rebalance = self.Time

    def OnData(self, slice):
        # Update the indicator value every day
        for symbol, symbol_data in self.data.items():
            if slice.Bars.ContainsKey(symbol):
                symbol_data.Update(slice.Bars[symbol])

        if self.rebalance <= self.Time:
            # sorted futures by 12-month return reversely 
            self.sorted_roc = sorted([x for x in self.data.values() if x.IsReady], key = lambda x: x.Value, reverse=True)
            number_futures = int(0.25*len(self.sorted_roc))
            if number_futures == 0: return
            
            self.long = [x for x in self.sorted_roc[:number_futures]]
            self.short = [x for x in self.sorted_roc[-number_futures:]]

            for symbol in self.Portfolio.Keys:
                # liquidate the futures which is no longer in the trading list
                if self.Portfolio[symbol].Invested and symbol not in [x.Mapped for x in self.long + self.short]:
                    self.Liquidate(symbol)
                    
            for long in self.long:
                qty = self.CalculateOrderQuantity(long.Mapped, 0.5/number_futures)
                self.MarketOrder(long.Mapped, qty // long.Multiplier)
            for short in self.short:
                qty = self.CalculateOrderQuantity(short.Mapped, -0.5/number_futures)
                self.MarketOrder(short.Mapped, qty // short.Multiplier)
                
            self.rebalance = Expiry.EndOfMonth(self.Time)

class SymbolData:

    def __init__(self, algorithm, future, period):
        self._future = future
        self.Symbol = future.Symbol
        self.ROC = RateOfChange(period)
    
        # warm up indicator
        algorithm.WarmUpIndicator(self.Symbol, self.ROC, Resolution.Daily)

    @property
    def IsReady(self):
        return self.ROC.IsReady and self._future.Mapped is not None

    @property
    def Value(self):
        return self.ROC.Current.Value
    
    @property
    def Mapped(self):
        return self._future.Mapped
    
    @property
    def Multiplier(self):
        return self._future.SymbolProperties.ContractMultiplier
    
    def Update(self, bar):
        self.ROC.Update(bar.EndTime, bar.Close)