Overall Statistics
Total Orders
208
Average Win
0.15%
Average Loss
-0.21%
Compounding Annual Return
-1.116%
Drawdown
4.700%
Expectancy
-0.161
Start Equity
1000000
End Equity
981424.92
Net Profit
-1.858%
Sharpe Ratio
-1.395
Sortino Ratio
-1.622
Probabilistic Sharpe Ratio
2.080%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
0.73
Alpha
-0.033
Beta
-0.028
Annual Standard Deviation
0.024
Annual Variance
0.001
Information Ratio
-0.49
Tracking Error
0.137
Treynor Ratio
1.192
Total Fees
$642.20
Estimated Strategy Capacity
$150000000.00
Lowest Capacity Asset
ZL X7TG3O4R7OKL
Portfolio Turnover
1.13%
#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.set_start_date(2018, 1, 1) 
        self.set_end_date(2019, 9, 1)  
        self.set_cash(1000000)

        symbols = [Futures.Meats.LIVE_CATTLE,
                    Futures.Meats.LEAN_HOGS,
                    Futures.Energies.BRENT_CRUDE,
                    Futures.Energies.LOW_SULFUR_GASOIL,
                    Futures.Softs.COTTON_2,
                    Futures.Softs.COFFEE,
                    Futures.Softs.COCOA,
                    Futures.Softs.SUGAR_11,
                    Futures.Grains.WHEAT,
                    Futures.Grains.CORN,
                    Futures.Grains.SOYBEANS,
                    Futures.Grains.SOYBEAN_MEAL,
                    Futures.Grains.SOYBEAN_OIL,
                    ]

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

        self.period = 252
        self.symbol_data = {}

        for symbol in symbols:
            future = self.add_future(symbol,
                resolution = Resolution.DAILY,
                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.symbol_data[future.symbol] = SymbolData(self, future, self.period)

    def on_data(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.symbol_changed_events.contains_key(symbol):
                changed_event = data.symbol_changed_events[symbol]
                old_symbol = changed_event.old_symbol
                new_symbol = changed_event.new_symbol
                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.market_order(new_symbol, quantity // self.securities[new_symbol].symbol_properties.contract_multiplier, tag = tag)

        # skip if less than 30 days passed since the last trading date
        if self.time < self.rebalancing_time:
            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.to_string()
            if symbol_id in weights:
                weight = np.sign(symbol_data.value) * weights[symbol_id] *.5

                mapped = symbol_data.mapped
                qty = self.calculate_order_quantity(mapped, np.clip(weight, -1, 1))
                multiplier = self.securities[mapped].symbol_properties.contract_multiplier
                order_qty = (qty - self.portfolio[mapped].quantity) // multiplier
                self.market_order(mapped, order_qty)

        # Set next rebalance time
        self.rebalancing_time = Expiry.end_of_month(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.warm_up_indicator(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 is_ready(self):
        return self.ROC.is_ready