Overall Statistics
Total Trades
206
Average Win
0.15%
Average Loss
-0.20%
Compounding Annual Return
-1.022%
Drawdown
4.400%
Expectancy
-0.154
Net Profit
-1.701%
Sharpe Ratio
-0.29
Probabilistic Sharpe Ratio
2.215%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
0.77
Alpha
-0.005
Beta
-0.029
Annual Standard Deviation
0.023
Annual Variance
0.001
Information Ratio
-0.486
Tracking Error
0.137
Treynor Ratio
0.234
Total Fees
$654.55
Estimated Strategy Capacity
$1200000000.00
Lowest Capacity Asset
ZM XAB1S6F46C6D
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/118

from QuantConnect.Python import PythonQuandl
import numpy as np

class TimeSeriesMomentumEffect(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2018, 1, 1) 
        self.SetEndDate(2019, 9, 1)  
        self.SetCash(1000000)

        symbols = [Futures.Meats.LiveCattle,
                    Futures.Meats.LeanHogs,
                    Futures.Energies.BrentCrude,
                    Futures.Energies.LowSulfurGasoil,
                    Futures.Softs.Cotton2,
                    Futures.Softs.Coffee,
                    Futures.Softs.Cocoa,
                    Futures.Softs.Sugar11,
                    Futures.Grains.Wheat,
                    Futures.Grains.Corn,
                    Futures.Grains.Soybeans,
                    Futures.Grains.SoybeanMeal,
                    Futures.Grains.SoybeanOil,
                    ]

        # Last trading date tracker to achieve rebalancing the portfolio every month
        self.RebalancingTime = datetime.min

        self.period = 252
        self.symbol_data = {}

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

    def OnData(self, data):
        '''
        Monthly rebalance at the beginning of each month.
        '''
        # Rollover for future contract mapping change
        for symbol, symbol_data in self.symbol_data.items():
            if data.SymbolChangedEvents.ContainsKey(symbol):
                changed_event = data.SymbolChangedEvents[symbol]
                old_symbol = changed_event.OldSymbol
                new_symbol = changed_event.NewSymbol
                tag = f"Rollover - Symbol changed at {self.Time}: {old_symbol} -> {new_symbol}"
                quantity = self.Portfolio[old_symbol].Quantity

                # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract
                self.Liquidate(old_symbol, tag = tag)
                self.MarketOrder(new_symbol, quantity // self.Securities[new_symbol].SymbolProperties.ContractMultiplier, tag = tag)

        # skip if less than 30 days passed since the last trading date
        if self.Time < self.RebalancingTime:
            return
            
        # dataframe that contains the historical data for all securities
        history = self.History(list(self.symbol_data.keys()), self.period, Resolution.Daily)
        history = history.droplevel([0]).unstack(level=0).close.pct_change().dropna()
        
        # Compute the inverse of the volatility
        # The weights are the normalized inverse of the volatility
        vol_inv = 1 / history.std(ddof=1)
        vol_sum = vol_inv.sum()
        weights = (vol_inv / vol_sum).fillna(0).to_dict()

        #np.sign(roc.Current.Value) tells us whether to long or short
        for symbol, symbol_data in self.symbol_data.items():
            symbol_id = symbol.ID.ToString()
            if symbol_id in weights:
                weight = np.sign(symbol_data.Value) * weights[symbol_id] *.5

                mapped = symbol_data.Mapped
                qty = self.CalculateOrderQuantity(mapped, np.clip(weight, -1, 1))
                multiplier = self.Securities[mapped].SymbolProperties.ContractMultiplier
                order_qty = (qty - self.Portfolio[mapped].Quantity) // multiplier
                self.MarketOrder(mapped, order_qty)

        # Set next rebalance time
        self.RebalancingTime = Expiry.EndOfMonth(self.Time)


class SymbolData:

    def __init__(self, algorithm, future, period):
        self._future = future
        self.Symbol = future.Symbol
        self.ROC = algorithm.ROC(future.Symbol, period)
        algorithm.WarmUpIndicator(future.Symbol, self.ROC, Resolution.Daily)
    
    @property
    def Value(self):
        return self.ROC.Current.Value
    
    @property
    def Mapped(self):
        return self._future.Mapped
        
    @property
    def IsReady(self):
        return self.ROC.IsReady