Overnight-Intraday Reversal in Futures
Log in to collectAcademic paper
Overnight-Intraday Reversal Everywhere
Robert Kosowski; Chun Liu; Yang Liu; Tianyu Wang
- University of Oxford
- Quantitative BioSciences
- Centre for Economic Policy Research
- ?CEPR (Centre for Economic Policy Research)
- ?Imperial College Business School
- ?University of Oxford, Oxford-Man Institute of Quantitative Finance
- CAUniversity of Toronto
- Tsinghua University
- ?Tsinghua University - School of Economics and Management
- Hunan University of Finance and Economics
- ?Hunan University - College of Finance and Statistics
- ?Tsinghua University, School of Economics and Management
Strategy in a nutshell
Universe: Five CME equity index futures (DJIA, NASDAQ, NIKKEI 225, S&P400, S&P500). CO-OC reversal strategy: buy the two overnight losers and short the two overnight winners at the next day’s open, closing positions at day’s end. Portfolio is equally weighted and rebalanced daily.
Economic rationale
Returns are driven by market microstructure and liquidity frictions rather than standard asset pricing factors or macroeconomic news, highlighting persistent inefficiencies even in highly liquid futures markets.
Backtest performance
Annualised return59.98%
Volatility10.87%
Beta0.017
Sharpe ratio5.52
Sortino ratio-0.657
Win rate48%
Full Python code
from AlgorithmImports import *
# endregion
class OvernightIntradayReversalinFutures(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.traded_percentage:float = 0.1
self.futures:List[Symbol] = []
self.last_close:Dict[Symbol, float] = {}
self.traded_count:int = 2
symbols:List[str] = [
Futures.Indices.SP400MidCapEmini,
Futures.Indices.SP500EMini,
Futures.Indices.MicroDow30EMini,
Futures.Indices.NASDAQ100EMini,
Futures.Indices.Nikkei225Dollar,
]
for symbol in symbols:
future:Future = self.AddFuture(symbol, Resolution.Minute, dataNormalizationMode=DataNormalizationMode.BackwardsRatio, contractDepthOffset=0)
self.futures.append(future.Symbol)
self.day_close_flag:bool = False
self.day_open_flag:bool = False
self.Schedule.On(self.DateRules.EveryDay(self.futures[1]), self.TimeRules.BeforeMarketClose(self.futures[1], 1), self.DayClose)
self.Schedule.On(self.DateRules.EveryDay(self.futures[1]), self.TimeRules.AfterMarketOpen(self.futures[1], 1), self.DayOpen)
def OnData(self, data: Slice) -> None:
if self.day_open_flag:
self.day_open_flag = False
returns:Dict[Symbol, float] = {symbol: data[symbol].Open / self.last_close[symbol] - 1 for symbol in self.futures if symbol in self.last_close and symbol in data and data[symbol]}
self.last_close.clear()
traded_count:int = self.traded_count if len(returns) >= len(self.futures) else 1
sorted_returns:List[Symbol] = sorted(returns, key=returns.get)
long:List[Symbol] = sorted_returns[:traded_count]
short:List[Symbol] = sorted_returns[-traded_count:]
for i, portfolio in enumerate([long, short]):
for symbol in portfolio:
self.SetHoldings(self.Securities[symbol].Mapped, ((-1) ** i) / len(portfolio) * self.traded_percentage)
if self.day_close_flag:
self.day_close_flag = False
self.Liquidate()
self.last_close = { symbol: data[symbol].Close for symbol in self.futures if symbol in data and data[symbol] }
def DayClose(self) -> None:
self.day_close_flag = True
def DayOpen(self) -> None:
self.day_open_flag = True