Quant BuffetRelax, Not Over Thinking

Intraday Momentum in Crude Oil ETF

Log in to collect

Academic paper

Strategy in a nutshell

Each trading day, the strategy computes the first half-hour return by comparing the previous trading day’s closing price to the price at 10:00 a.m. If this return is positive (negative), a long (short) position is initiated at the beginning of the last half-hour of the trading session. All positions are closed at the end of the day, ensuring purely intraday exposure and daily rebalancing.

Economic rationale

Momentum effects are well established across asset classes—equities, currencies, commodities, and bonds—where past performance predicts near-term future returns. At the intraday level, the first half-hour return significantly predicts the last half-hour return. The morning return captures market reactions to overnight and pre-market information, such as earnings announcements and macroeconomic data. Trading volume peaks early as traders digest new information, then subsides until the final half-hour, when institutional rebalancing and order execution intensify. This coordinated trading behavior leads to return continuation within the same day. The strategy capitalizes on this predictable pattern, profiting from the persistence of intraday momentum driven by information flow and institutional trading dynamics.

Backtest performance

Annualised return1.85%
Beta0.003
Win rate53%

Full Python code

from AlgorithmImports import *
#endregion
class IntradayMomentumCrudeOil(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.last_day_close:float = 0
self.symbol:Symbol = self.AddEquity('USO', Resolution.Minute).Symbol
self.ret:float|None = None

def OnData(self, data:Slice) -> None:
if self.symbol in data and data[self.symbol]:
    # day close
    if self.Time.hour == 16 and self.Time.minute == 0:
        self.last_day_close = data[self.symbol].Value
    
    # rebalance
    elif self.Time.hour == 15 and self.Time.minute == 30:
        q:float = self.CalculateOrderQuantity(self.symbol, 1)
        
        if self.ret:
            if self.ret > 0:
                self.MarketOrder(self.symbol, q)
                self.MarketOnCloseOrder(self.symbol, -q)
            else:
                self.MarketOrder(self.symbol, -q)
                self.MarketOnCloseOrder(self.symbol, q)
        
        self.ret = None
    
    # day open - calculation
    elif self.Time.hour == 10 and self.Time.minute == 0:
        if self.last_day_close == 0: return
        
        price:float = data[self.symbol].Value
        self.ret = price / self.last_day_close - 1
        self.last_day_close = 0