The strategy trades Oil by opening a long position at 7 p.m. (GMT+3) on days with positive abnormal returns, holding until midnight to capture short-term gains.

I. STRATEGY IN A NUTSHELL

The strategy trades Oil commodities by entering short-term long positions after detecting abnormal positive returns, exploiting intraday momentum from significant price surges.

II. ECONOMIC RATIONALE

Abnormal returns trigger short-lived momentum driven by investor herding, where prices continue rising temporarily—creating opportunities for quick, profit-oriented intraday trades.

III. SOURCE PAPER

Gold and Oil Prices: Abnormal Returns, Momentum and Contrarian Effects [Click to Open PDF]

Guglielmo Maria Caporale, Alex Plastun

<Abstract>

This paper explores price (momentum and contrarian) effects on the days characterised by abnormal returns and the following ones in two commodity markets. Specifically, using daily Gold and Oil price data over the period 01.01.2009-31.03.2020 the following hypotheses are tested: H1) there are price effects on days with abnormal returns, H2) there are price effects on the day after abnormal returns occur; H3) the price effects caused by abnormal returns are exploitable. For these purposes average analysis, t-tests, CAR and trading simulation approaches are used. The main results can be summarised as follows. Hourly returns during the day of abnormal returns are significantly bigger than those during average “normal” days. Prices tend to move in the direction of abnormal returns till the end of the day when these occur. The presence of abnormal returns can usually be detected before the end of the day by estimating specific timing parameters, and a momentum effect can be detected. On the following day two different price patterns are detected: a momentum effect for Oil prices and a contrarian effect for Gold prices respectively. Trading simulations show that these effects can be exploited to generate abnormal profits.

IV. BACKTEST PERFORMANCE

Annualised Return21.36%
VolatilityN/A
Beta0.001
Sharpe RatioN/A
Sortino Ratio-1.329
Maximum DrawdownN/A
Win Rate48%

V. FULL PYTHON CODE

from AlgorithmImports import *
import numpy as np
from math import floor
#endregion
class OilIntradayMomentum(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(1000000)
        self.period = 3 * 21
        self.treshhold_value = 2
        self.future:Future = self.AddFuture(Futures.Energies.CrudeOilWTI, \
                            Resolution.Minute, \
                            dataNormalizationMode=DataNormalizationMode.BackwardsRatio, \
                            contractDepthOffset=0)
        self.symbol:Symbol = self.future.Symbol
        
        self.daily_ret = [] # daily returns
        self.open = 0   # latest open
        self.day_close_flag:bool = False
        self.day_open_flag:bool = False
        self.market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.BeforeMarketClose(self.market, 1), self.DayClose)
        self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.AfterMarketOpen(self.market, 1), self.DayOpen)
        
    def OnData(self, data: Slice) -> None:
        # close
        if self.day_close_flag:
            self.day_close_flag = False
            self.Liquidate()
    
            if self.symbol in data and data[self.symbol]:
                close:float = data[self.symbol].Close
                if close != 0 and self.open != 0 and close != self.open:
                    todays_ret:float = close / self.open - 1
                    self.daily_ret.append(todays_ret)
                
                    if len(self.daily_ret) < self.period:
                        return
                
                    mean:float = np.mean(self.daily_ret)
                    std:float = np.std(self.daily_ret)
                    high_threshhold = mean + self.treshhold_value * std
                    if todays_ret > high_threshhold:
                        if not self.Portfolio.Invested:
                            self.MarketOrder(self.future.Mapped, 1)
            
            self.open = 0
        
        # open
        if self.day_open_flag:
            self.day_open_flag = False
            if self.symbol in data and data[self.symbol]:
                self.open = data[self.symbol].Open
    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