Intraday Momentum in Crude Oil ETF
Log in to collectAcademic paper
Intraday Momentum: Evidence from the Crude Oil Market
Zhuzhu Wen; Ro Cho; Diandian Ma; Yahua Xu
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
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