Quant BuffetRelax, Not Over Thinking

Price Gap Strategy in the US Stock Market

Log in to collect

Academic paper

Price Gap Anomaly in the US Stock Market: The Whole Story

AuthorsOleksiy Plastun; Xolani Sibande; Rangan Gupta; Mark E. Wohar

Institute
  • UASumy State University
  • ZAUniversity of Pretoria
  • ?University of Pretoria - Department of Economics
  • University of Nebraska at Omaha

Strategy in a nutshell

The strategy trades a single S&P 500 vehicle (CFD, ETF, or futures). A gap threshold is defined based on 100 historical price gaps over ten years. When the opening–closing price difference exceeds this threshold, the index is bought at the open and sold at the close; otherwise, the investor stays out.

Economic rationale

Price gaps arise from timing differences between closes and opens, driven by weekends, holidays, after-hours trading, or unexpected events like earnings. While no fundamental cause for abnormal returns is specified, statistical evidence shows these gaps often create profitable opportunities when openings deviate significantly from prior closes.

Backtest performance

Annualised return5.9%
Beta0.082
Sortino ratio-0.047
Win rate61%

Full Python code

import numpy as np
from AlgorithmImports import *
class PriceGapStrategy(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2000, 1, 1)
self.set_cash(100000)
self.symbol: Symbol = self.add_equity('SPY', Resolution.MINUTE).symbol

self.close_price: float = 0    # recent close price
self.gaps: List[float] = []          # daily gaps
self.min_gap_count: int = 100

self.schedule.on(self.date_rules.every_day(self.symbol), self.time_rules.before_market_close(self.symbol, 1), self.close)
self.schedule.on(self.date_rules.every_day(self.symbol), self.time_rules.after_market_open(self.symbol, 1), self.open)

def open(self) -> None:
if self.securities.contains_key(self.symbol):
    if self.close_price != 0:
        # Store recent gap.
        open: float = self.securities[self.symbol].open
        self.gaps.append((open / self.close_price) - 1)
        self.close_price = 0
        
        if len(self.gaps) < self.min_gap_count: return
        
        # Gap signals.
        gaps_mean: float = np.mean(self.gaps)
        gaps_std: float = np.std(self.gaps)
        todays_gap: float = self.gaps[-1]
        
        if todays_gap > gaps_mean + 2 * gaps_std:
            self.set_holdings(self.symbol, 1)

def close(self) -> None:
self.liquidate(self.symbol)

if self.securities.contains_key(self.symbol):
    close: float = self.securities[self.symbol].close
    if close != 0:
        self.close_price = close