Quant BuffetRelax, Not Over Thinking

Short-Term Reversal in Equity Index Futures

Log in to collect

Academic paper

Strategy in a nutshell

Trade seven EU index futures (Germany, France, Italy, Spain, Netherlands, Belgium, Austria) using nearest contracts. Classify countries as winners or losers based on the past five days’ returns. Go long on losers, short on winners, weighted by deviation from the benchmark. Rebalance weekly to capture mean-reversion.

Economic rationale

Short-term contrarian strategies exploit overreaction: past winners tend to underperform, and past losers tend to outperform. These predictable reversals provide opportunities to profit from temporary mispricing in EU index futures.

Backtest performance

Annualised return29.6%
Beta0.03
Sortino ratio-0.001
Win rate49%

Full Python code

import numpy as np
from AlgorithmImports import *
class ShortTermReversal(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

self.symbols = ['EWG', 'EWQ', 'EWI', 'EWP', 'EWN', 'EWK', 'EWO']

# Daily price data.
self.data = {}
self.period = 5
self.SetWarmUp(self.period)

for symbol in self.symbols:
    data = self.AddEquity(symbol, Resolution.Daily)
    data.SetFeeModel(CustomFeeModel())
    data.SetLeverage(5)
    
    self.data[symbol] = RollingWindow[float](self.period)

self.Schedule.On(self.DateRules.Every(DayOfWeek.Thursday), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)
def OnData(self, data):
for symbol in self.data:
    symbol_obj = self.Symbol(symbol)
    if symbol_obj in data.Keys:
        if data[symbol_obj]:
            price = data[symbol_obj].Value
            if price != 0:
                self.data[symbol].Add(price)
def Rebalance(self):
self.Liquidate()

symbol_return = {}
for symbol in self.symbols:
    if self.data[symbol].IsReady: 
        if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days < 5:
            closes = [x for x in self.data[symbol]]
            symbol_return[symbol] = closes[0] / closes[-1] - 1

if len(symbol_return) != 0:
    avg_ret = np.average([x[1] for x in symbol_return.items()])
    
    # Average return weighting.
    return_diff = {x[0] : abs(x[1] - avg_ret) for x in symbol_return.items()}

    total_diff = sum(x[1] for x in return_diff.items())
    weight_ratio = float(1 / total_diff)

    # Trade execution.
    winners = [x[0] for x in return_diff.items() if symbol_return[x[0]] > avg_ret]
    losers = [x[0] for x in return_diff.items() if symbol_return[x[0]] < avg_ret]
    
    for symbol in winners + losers:
        weight = return_diff[symbol] * weight_ratio
        if symbol in winners:
            self.SetHoldings(symbol, -weight)
        elif symbol in losers:
            self.SetHoldings(symbol, weight)

# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))