Introduction

Time series momentum is related to, but different from the phenomenon known as “momentum” in the finance literature, which is primarily cross-sectional in nature. The momentum literature focuses on the relative performance of securities in the cross section, finding that securities that recently outperformed their peers over the past 3 to 12 months continue to do so on average over the next month. Rather than focus on the relative returns of securities in the cross section, this time series momentum strategy focuses purely on the past returns of each individual Futures contract. Every month, the investor considers whether the excess return of each asset over the past 12 months is positive or negative and goes long on the contract if it is positive and short if negative. The position size is set to be inversely proportional to the volatility of the security's returns. A univariate GARCH model could be used to estimate volatility. However, other simple models could probably be easily used with good results (for example, the easiest one would be using historical volatility). For the sake of simplicity, we will use historical volatility. The portfolio is rebalanced monthly.

Method

We manually create a universe of tradable commodity Futures from all available commodity Futures traded on CME and ICE. The subscribed data resolution is daily.

Step 1: Import the data

As the strategy needs the continuous Futures contract, we subscribe to continuous Futures data by AddFuture in daily resolution.

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)

Step 2: Set the portfolio target volatility and decide rebalance schedule

def Initialize(self):
    # Last trading date tracker to achieve rebalancing the portfolio every month
    self.RebalancingTime = self.Time

Step 3: Set up SymbolData class to hold momentum and other information

Here we use a 12-month RateOfChange indicator to simulate the momentum returns. We will use a SymbolData class object to hold the indicator per contract, as well as other information like their canonical symbol and mapped contract's symbol. All SymbolData objects are saved in the dictionary self.symbol_data.

self.period = 252
self.symbol_data = {}
for symbol in self.symbols:
    self.symbol_data[future.Symbol] = SymbolData(self, future, self.period)

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

Step 4: Construct/Rebalance the Portfolio

We set up handler for Future contracts rollover events, as well as flag-check of rebalancing time

def OnData(self, data):
    # 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

We use a history request to obtain historical prices. Here, history is the daily close-price returns, which will be used to calculate volatilities.

history = self.History(list(self.symbol_data.keys()), self.period, Resolution.Daily)
history = history.droplevel([0]).unstack(level=0).close.pct_change().dropna()

Then we calculate the historical volatilities and place orders. Note that the weights are inversely proportional to volatilities and np.sign determines whether to long or short.

# 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)

At last, we need to reset the rebalancing timestamp

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


Reference

  1. Quantpedia - Time Series Momentum Effect

Author