Overall Statistics
Total Orders
120
Average Win
5.08%
Average Loss
-5.90%
Compounding Annual Return
-34.380%
Drawdown
77.700%
Expectancy
-0.393
Start Equity
100000
End Equity
28221.44
Net Profit
-71.779%
Sharpe Ratio
-0.167
Sortino Ratio
-0.174
Probabilistic Sharpe Ratio
0.805%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
0.86
Alpha
-0.117
Beta
0.191
Annual Standard Deviation
0.621
Annual Variance
0.386
Information Ratio
-0.279
Tracking Error
0.627
Treynor Ratio
-0.543
Total Fees
$360.62
Estimated Strategy Capacity
$0
Lowest Capacity Asset
GNF WTDK2AZMTCZL
Portfolio Turnover
9.58%
#region imports
from AlgorithmImports import *
#endregion


class CommidityMomentumEffect(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2015,1, 1) 
        self.set_end_date(2018, 1, 1)  
        self.set_cash(100000)
        self.set_security_initializer(
            BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))
        )

        symbols = [
            Futures.Dairy.CASH_SETTLED_BUTTER,
            Futures.Dairy.CASH_SETTLED_CHEESE,
            Futures.Dairy.CLASS_III_MILK,
            Futures.Dairy.DRY_WHEY,
            Futures.Dairy.CLASS_IV_MILK,
            Futures.Dairy.NONFAT_DRY_MILK,
            Futures.Meats.LIVE_CATTLE,
            Futures.Meats.FEEDER_CATTLE,
            Futures.Meats.LEAN_HOGS,
            Futures.Forestry.RANDOM_LENGTH_LUMBER
        ]

        period = 252
        self._data = {}
        for symbol in symbols:
            future = self.add_future(symbol,
                resolution=Resolution.MINUTE,
                extended_market_hours=True,
                data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
                data_mapping_mode=DataMappingMode.OPEN_INTEREST,
                contract_depth_offset=0
            )
            future.set_leverage(1)
            self._data[future.symbol] = SymbolData(self, future, period)

        # Rebalance the portfolio every month
        self._rebalance = self.time

    def on_data(self, slice):
        if self._rebalance <= self.time:
            # sorted futures by 12-month return reversely 
            sorted_roc = sorted([x for x in self._data.values() if x.is_ready], key = lambda x: x.value, reverse=True)
            number_futures = int(0.25*len(sorted_roc))
            if number_futures == 0: return
            
            longs = [x for x in sorted_roc[:number_futures]]
            shorts = [x for x in sorted_roc[-number_futures:]]

            for symbol, security_holding in self.portfolio.items():
                # liquidate the futures which is no longer in the trading list
                if security_holding.invested and symbol not in [x.mapped for x in longs + shorts]:
                    self.liquidate(symbol)
                    
            for long_ in longs:
                qty = self.calculate_order_quantity(long_.mapped, 0.5/number_futures) // long_.multiplier
                if qty:
                    self.market_order(long_.mapped, qty)
            for short in shorts:
                qty = self.calculate_order_quantity(short.mapped, -0.5/number_futures) // short.multiplier
                if qty:
                    self.market_order(short.mapped, qty)
                
            self._rebalance = Expiry.end_of_month(self.time)


class SymbolData:

    def __init__(self, algorithm, future, period):
        self._future = future
        symbol = future.symbol
        self._roc = algorithm.roc(symbol, period, Resolution.DAILY)
    
        # warm up indicator
        algorithm.warm_up_indicator(symbol, self._roc, Resolution.DAILY)

    @property
    def is_ready(self):
        return self._roc.is_ready 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.symbol_properties.contract_multiplier