The investment universe contains five futures contracts traded on the Chicago Mercantile Exchange—5 equity indices (DJIA, NASDAQ, NIKKEI 225, S&P400, and S&P500). (Always select the most liquid contract; the most liquid contract is generally the nearest-to-delivery (front) contract.)

I. 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.

II. 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.

III. SOURCE PAPER

Overnight-Intraday Reversal Everywhere [Click to Open PDF]

Robert Kosowski, Imperial College Business School; Chun Liu, CEPR (Centre for Economic Policy Research), University of Toronto; Yang Liu, Tsinghua University – School of Economics and Management; Tianyu Wang, Hunan University – College of Finance and Statistics; [Next Author], Tsinghua University, School of Economics and Management

<Abstract>

A strategy that buys securities with low past overnight returns and sells securities with high past overnight returns generates sizeable out-of-sample intraday returns and Sharpe ratios in all major asset classes. This strategy – labeled as overnight-intraday reversal – delivers an average return that is about two to five times larger than those generated by the conventional reversal strategy. Investor sentiment, macro-news announcements, and market uncertainty fail to explain this overnight-intraday reversal return. Our findings are consistent with an asset class-specific market maker liquidity provision mechanism, and we find that cross sectional return dispersion could well predict the strategy returns.

IV. BACKTEST PERFORMANCE

Annualised Return59.98%
Volatility10.87%
Beta0.017
Sharpe Ratio5.52
Sortino Ratio-0.657
Maximum DrawdownN/A
Win Rate48%

V. 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

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading