Quant BuffetRelax, Not Over Thinking

Overnight-Intraday Daily Reversal in Commodities

Log in to collect

Academic paper

Market Closure and Short-Term Reversal

AuthorsPasquale Della Corte; Robert Kosowski; Tianyu Wang

Institute
  • Centre for Economic Policy Research
  • ?Centre for Economic Policy Research (CEPR)
  • ?Imperial College Business School
  • University of Oxford
  • Quantitative BioSciences
  • ?CEPR (Centre for Economic Policy Research)
  • ?University of Oxford, Oxford-Man Institute of Quantitative Finance
  • Tsinghua University
  • ?Tsinghua University, School of Economics and Management

Strategy in a nutshell

The strategy targets 11 commodity futures, including Corn, Ethanol CBOT, Lean Hogs, Live Cattle, and others, selecting the most liquid contracts. A zero-investment portfolio is constructed by buying the previous day's overnight losers and selling overnight winners. Commodity weights are determined using formulas from the academic paper, with 1/2 of the calculated weight used to estimate portfolio return and volatility, as full weights indicate excessive volatility (60% annually). The portfolio is rebalanced daily, with trades executed from the next open-to-close period after the formation period. This approach aims to exploit short-term price movements in commodities.

Economic rationale

Price reversals often occur due to investor overreaction to news, followed by a price correction. Market closures, like overnight or weekend breaks, typically feature low liquidity and trading activity. Research by Nagel (2012) shows that uncertainty, measured by the VIX index, plays a significant role in explaining profits from the Close-to-Open (CO-OC) reversal strategy. This effect is particularly strong in futures markets. The CO-OC reversal pattern aligns with Hong and Wang’s (2000) continuous-time model, suggesting that hedging demands during market closures contribute to these reversals, leading to predictable profit opportunities.

Backtest performance

Annualised return45.75%
Volatility31.02%
Beta-0.012
Sharpe ratio1.47
Sortino ratio-2.897
Win rate44%

Full Python code

from AlgorithmImports import *
#endregion
class OvernightIntradayDailyReversalinCommodities(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(1000000)
symbols:List[str] = [
    Futures.Grains.Corn,
    Futures.Meats.LeanHogs,
    Futures.Meats.LiveCattle,
    Futures.Forestry.Lumber,
    Futures.Grains.Oats,
    Futures.Grains.SoybeanMeal,
    Futures.Grains.Soybeans,
    Futures.Grains.Wheat,
]
self.traded_percentage:float = 0.2
self.futures:List[Symbol] = []
self.recent_close:Dict[Symbol, float] = {}

for symbol in symbols:
    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[3]), self.TimeRules.BeforeMarketClose(self.futures[3], 1), self.DayClose)
self.Schedule.On(self.DateRules.EveryDay(self.futures[3]), self.TimeRules.AfterMarketOpen(self.futures[3], 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.recent_close[symbol] - 1 for symbol in self.futures if symbol in self.recent_close and symbol in data and data[symbol] }
    self.recent_close.clear()
    long:List[Symbol] = [x[0] for x in returns.items() if x[1] < 0]
    short:List[Symbol] = [x[0] for x in returns.items() if x[1] > 0]
    
    for i, portfolio in enumerate([long, short]):
        for symbol in portfolio:
            # notional value = asset price * contract multiplier
            notional_value:float = (self.Securities[symbol].Price * self.Securities[symbol].SymbolProperties.ContractMultiplier)
            quantity:int = int((((-1) ** i) * self.Portfolio.TotalPortfolioValue) / len(portfolio) * self.traded_percentage) // notional_value
            self.MarketOrder(self.Securities[symbol].Mapped, quantity)
if self.day_close_flag:
    self.day_close_flag = False
    self.Liquidate()
    self.recent_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