Overall Statistics
Total Orders
194
Average Win
4.96%
Average Loss
-3.91%
Compounding Annual Return
-7.486%
Drawdown
44.400%
Expectancy
-0.080
Start Equity
100000
End Equity
67753.34
Net Profit
-32.247%
Sharpe Ratio
-0.196
Sortino Ratio
-0.237
Probabilistic Sharpe Ratio
0.212%
Loss Rate
59%
Win Rate
41%
Profit-Loss Ratio
1.27
Alpha
-0.037
Beta
-0.217
Annual Standard Deviation
0.271
Annual Variance
0.073
Information Ratio
-0.39
Tracking Error
0.32
Treynor Ratio
0.244
Total Fees
$558.22
Estimated Strategy Capacity
$610000000.00
Lowest Capacity Asset
ZW YY8E90VXTCTH
Portfolio Turnover
8.28%
Drawdown Recovery
1056
#region imports
from AlgorithmImports import *
#endregion


class CommidityMomentumEffect(QCAlgorithm):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100000)
        self.settings.seed_initial_prices = True

        symbols = [
            Futures.Meats.LIVE_CATTLE,
            Futures.Meats.FEEDER_CATTLE,
            Futures.Meats.LEAN_HOGS,
            Futures.Energy.CRUDE_OIL_WTI,
            Futures.Energy.GASOLINE,
            Futures.Grains.WHEAT,
            Futures.Grains.SOYBEANS,
            Futures.Grains.CORN
        ]

        period = 252
        self._futures = []
        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)
            future.multiplier = future.symbol_properties.contract_multiplier
            future.roc = self.roc(future.symbol, period, Resolution.DAILY)
            self.warm_up_indicator(future.symbol, future.roc, Resolution.DAILY)
            self._futures.append(future)

        # Rebalance the portfolio every month
        self.schedule.on(
            self.date_rules.month_start('SPY'),
            self.time_rules.after_market_open('SPY', 30),
            self._rebalance
        )

    def _rebalance(self):
        tradable_futures = [f for f in self._futures if f.roc.is_ready and f.mapped]
        number_futures = int(0.25*len(tradable_futures))
        # sorted futures by 12-month return reversely 
        sorted_roc = sorted(tradable_futures, key=lambda f: f.roc.current.value, reverse=True)           
        longs = [f for f in sorted_roc[:number_futures]]
        shorts = [f for f 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 [f.mapped for f in longs + shorts]:
                self.liquidate(symbol)
        
        self._trade(longs, 0.5/number_futures)
        self._trade(shorts, -0.5/number_futures)
    
    def _trade(self, futures, weight):
        for f in futures:
            qty = self.calculate_order_quantity(f.mapped, weight) // f.multiplier
            if qty:
                self.market_order(f.mapped, qty)