Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Do Stock Gaps Always Get Filled? The Data

Do stock gaps always get filled? Not on any fixed horizon. See same session, 5, 20 and 60 session fill rates for large caps, with the SQL behind them.

Do stock gaps always get filled? Not inside any horizon short enough to trade, and the claim survives mostly when nobody states one. A gap is the distance between today's open and yesterday's regular-session close, a fill is the moment price trades back through that prior close, and the whole argument lives in how long a reader is willing to wait for it.

What counts as a gap, and what counts as a fill

A gap is measured open against prior close. Yesterday's session ended at one price, today's opened at another, and the difference is the gap. It is not the distance from yesterday's intraday high or low. That is a different and far easier target, and swapping one for the other is how a "gap" quietly becomes something that closes by lunchtime on any ordinary day. Small ones appear in almost every session: trading in other time zones, index changes, single-name news, and ex-dividend adjustments all move the fair price while the US market is shut. Why stocks gap overnight covers where that overnight repricing comes from.

A fill is the first moment the stock trades back through the prior closing price. That definition is incomplete without a deadline attached. Over an unlimited horizon a liquid stock crosses almost any level a reader names at some point, which makes "gaps always fill" unfalsifiable rather than true. Every number below carries a stated horizon.

One sample runs through the page: eight large-cap US stocks, gap days from January 2021 through February 2026, fills tested against regular-session one-minute highs and lows. Gaps under 0.25% are dropped as rounding.

Do stock gaps always get filled the same day?

Start with size. Group every gap by how large it was, then ask whether the prior close was touched again before that same session ended.

QuerySame-session gap fill rate by gap size, eight large caps, 2021 to 2026
The exact SQL behind every number
WITH sessions AS
(
    SELECT
        ticker,
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        argMin(toFloat64(open), window_start)                AS session_open,
        argMax(toFloat64(close), window_start)               AS session_close,
        toFloat64(max(high))                                 AS session_high,
        toFloat64(min(low))                                  AS session_low
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'JPM', 'KO', 'WMT', 'XOM')
      AND window_start >= '2021-01-01'
      AND window_start <  '2026-07-01'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY ticker, d
),
gapped AS
(
    SELECT
        d,
        session_open,
        session_high,
        session_low,
        lagInFrame(session_close) OVER (PARTITION BY ticker ORDER BY d ASC
            ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prior_close
    FROM sessions
),
measured AS
(
    SELECT
        abs(100 * (session_open / prior_close - 1)) AS gap_pct,
        toUInt8(if(session_open > prior_close,
                   session_low  <= prior_close,
                   session_high >= prior_close))    AS filled_same_session
    FROM gapped
    WHERE prior_close > 0
      AND d <= toDate('2026-02-28')
      AND abs(100 * (session_open / prior_close - 1)) >= 0.25
)
SELECT
    multiIf(gap_pct < 0.5, '0.25 to 0.5%',
            gap_pct < 1,   '0.5 to 1%',
            gap_pct < 2,   '1 to 2%',
            gap_pct < 4,   '2 to 4%',
                           '4% or more')         AS gap_bucket,
    count()                                      AS gap_days,
    round(100 * avg(filled_same_session), 1)     AS same_session_fill_pct
FROM measured
GROUP BY gap_bucket
ORDER BY min(gap_pct) ASC
Run this yourself

The smallest gaps in the sample, 0.25% to 0.5%, closed the same session 74.6% of the time across 2381 occurrences. Gaps of 4% or more closed the same session 14.8% of the time. The slope down the chart is most of the lesson: the smaller the gap, the more often the stock wanders back across it during the day, which is another way of saying that small gaps are noise and noise reverses.

How long does a gap take to fill?

Extend the deadline and the fill rate can only rise. A longer window adds fills and never removes them. The question is how much of the total arrives late enough to be useless.

QueryShare of 1%+ gaps filled, by how long you wait
The exact SQL behind every number
WITH sessions AS
(
    SELECT
        ticker,
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        argMin(toFloat64(open), window_start)                AS session_open,
        argMax(toFloat64(close), window_start)               AS session_close,
        toFloat64(max(high))                                 AS session_high,
        toFloat64(min(low))                                  AS session_low
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'JPM', 'KO', 'WMT', 'XOM')
      AND window_start >= '2021-01-01'
      AND window_start <  '2026-07-01'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY ticker, d
),
paths AS
(
    SELECT
        d,
        session_open,
        session_high,
        session_low,
        lagInFrame(session_close) OVER (PARTITION BY ticker ORDER BY d ASC
            ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)                                                          AS prior_close,
        min(session_low)  OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING)   AS low_5,
        max(session_high) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING)   AS high_5,
        min(session_low)  OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 19 FOLLOWING)  AS low_20,
        max(session_high) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 19 FOLLOWING)  AS high_20,
        min(session_low)  OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 59 FOLLOWING)  AS low_60,
        max(session_high) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 59 FOLLOWING)  AS high_60
    FROM sessions
),
gaps AS
(
    SELECT
        session_open > prior_close AS gap_up,
        prior_close,
        session_low,
        session_high,
        low_5,
        high_5,
        low_20,
        high_20,
        low_60,
        high_60
    FROM paths
    WHERE prior_close > 0
      AND d <= toDate('2026-02-28')
      AND abs(100 * (session_open / prior_close - 1)) >= 1
),
flags AS
(
    SELECT arrayJoin([
        (1, '1 session',   toUInt8(if(gap_up, session_low <= prior_close, session_high >= prior_close))),
        (2, '5 sessions',  toUInt8(if(gap_up, low_5  <= prior_close, high_5  >= prior_close))),
        (3, '20 sessions', toUInt8(if(gap_up, low_20 <= prior_close, high_20 >= prior_close))),
        (4, '60 sessions', toUInt8(if(gap_up, low_60 <= prior_close, high_60 >= prior_close)))
    ]) AS f
    FROM gaps
)
SELECT
    tupleElement(f, 2)                        AS horizon,
    count()                                   AS gaps_measured,
    round(100 * avg(tupleElement(f, 3)), 1)   AS filled_pct
FROM flags
GROUP BY horizon
ORDER BY min(tupleElement(f, 1)) ASC
Run this yourself

Across 2191 gaps of 1% or more, 34% filled inside the same session and 88.7% had filled within 60 sessions. The second figure is the one that usually gets quoted, with the horizon left off. Sixty sessions is roughly three trading months, a long hold for a position premised on a one-day dislocation, and a gap that fills on day 47 after the stock has traveled far the other way first still counts as filled in that statistic.

Noise gaps and information gaps are two populations

A gap on a quiet day with ordinary volume is a small pricing adjustment. A gap on earnings news or a takeover approach carries a change in what the company is worth, and there is no old price left to revert to. Volume separates the two reasonably well: a repricing the whole market wants to trade prints many times normal turnover.

QueryGap fill rate by how heavy the gap day's volume was
The exact SQL behind every number
WITH sessions AS
(
    SELECT
        ticker,
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        argMin(toFloat64(open), window_start)                AS session_open,
        argMax(toFloat64(close), window_start)               AS session_close,
        toFloat64(max(high))                                 AS session_high,
        toFloat64(min(low))                                  AS session_low,
        sum(toFloat64(volume))                               AS session_volume
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'JPM', 'KO', 'WMT', 'XOM')
      AND window_start >= '2021-01-01'
      AND window_start <  '2026-07-01'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY ticker, d
),
paths AS
(
    SELECT
        d,
        session_open,
        session_high,
        session_low,
        session_volume,
        lagInFrame(session_close) OVER (PARTITION BY ticker ORDER BY d ASC
            ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)                                                         AS prior_close,
        avg(session_volume) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN 21 PRECEDING AND 2 PRECEDING) AS normal_volume,
        min(session_low)  OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 19 FOLLOWING) AS low_20,
        max(session_high) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 19 FOLLOWING) AS high_20
    FROM sessions
),
measured AS
(
    SELECT
        session_volume / normal_volume AS volume_ratio,
        toUInt8(if(session_open > prior_close, session_low <= prior_close, session_high >= prior_close)) AS filled_same_session,
        toUInt8(if(session_open > prior_close, low_20 <= prior_close, high_20 >= prior_close))           AS filled_20_sessions
    FROM paths
    WHERE prior_close > 0
      AND normal_volume > 0
      AND d >= toDate('2021-03-01')
      AND d <= toDate('2026-02-28')
      AND abs(100 * (session_open / prior_close - 1)) >= 1
)
SELECT
    multiIf(volume_ratio < 1.5, 'Under 1.5x normal',
            volume_ratio < 3,   '1.5x to 3x normal',
                                '3x or more')        AS volume_regime,
    count()                                          AS gap_days,
    round(100 * avg(filled_same_session), 1)         AS same_session_fill_pct,
    round(100 * avg(filled_20_sessions), 1)          AS within_20_sessions_fill_pct
FROM measured
WHERE isFinite(volume_ratio)
GROUP BY volume_regime
ORDER BY min(volume_ratio) ASC
Run this yourself

Gaps of 1% or more on volume under 1.5 times the stock's normal turnover filled the same session 35.9% of the time. The same size gaps on three times normal volume or more filled the same session 15.3% of the time, and 45.8% within 20 sessions, across 59 of them. The heavy-volume group is where the earnings gaps and the deal gaps sit. Averaging the two groups into one number describes neither.

Why the first minutes of the session distort gap statistics

A fill recorded at 9:31 a.m. is a fragile thing. The opening price comes out of an auction, a single batched cross that sets one price for every order queued overnight, and our opening auction explainer walks through the mechanics. In the minutes after that cross, quoted spreads sit at their widest of the day and one small print can register a high or low that no size ever traded at. Spreads widen at the open covers why the quote is so thin there.

The chart below shows the shape on the basket's own tape: average one-minute high-low range in basis points (one basis point is one hundredth of a percent), by fifteen-minute clock bucket, through 2025.

QueryAverage one-minute range and volume by time of day, 2025
The exact SQL behind every number
SELECT
    formatDateTime(toStartOfInterval(toTimeZone(window_start, 'America/New_York'), INTERVAL 15 MINUTE), '%H:%i') AS et_time,
    round(10000 * avg(toFloat64(high) / toFloat64(low) - 1), 1) AS avg_range_bps,
    round(avg(toFloat64(volume)) / 1000, 1)                     AS avg_volume_k
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'JPM', 'KO', 'WMT', 'XOM')
  AND window_start >= '2025-01-01'
  AND window_start <  '2026-01-01'
  AND toFloat64(low) > 0
  AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
       + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
  AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
       + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY et_time
ORDER BY et_time ASC
Run this yourself

The opening bucket averages 24.4 bps of range per minute against 6.9 bps in the 12:30 bucket, on 303.9 thousand shares a minute versus 69.6 thousand. A fill stamped inside that first bucket rests on the least reliable prices of the day. The same-session numbers above include those minutes, which biases them a little high, and that caveat applies to every same-day fill statistic, this one included.

What to check before treating a gap as a trade

  1. The horizon. A fill rate with no deadline attached is not a statistic.
  2. The population. Whether news gaps were separated from quiet ones, and how that line was drawn.
  3. The measurement. Whether fills were tested against real traded ranges, and whether the first minutes of the session counted.
  4. The survivors. Whether the sample holds only stocks that still trade today, which quietly deletes the gaps that never filled.

The honest version of the claim is narrower than the slogan. Small gaps on ordinary volume revert often and quickly. Large gaps on heavy volume are repricings that frequently stand for months. For a live view of which names are gapping, the biggest stock movers this week page tracks the current week, and how markets recover from crashes applies the same horizon discipline to index drawdowns.

FAQ

Do stock gaps always get filled?

No. In the sample here, 88.7% of gaps of 1% or more traded back through the prior close within 60 sessions, which leaves a real share of them unfilled after about three trading months. The "always" version works only with no time limit attached, at which point it stops being testable.

How long does it take for a gap to fill?

It depends on the gap. Here, 34% of gaps of 1% or more filled inside the same session, and the remaining fills arrive across the following weeks and months. Small gaps concentrate in the same-day bucket, and the heavy-volume ones are the slowest.

What is the difference between a common gap and a breakaway gap?

Chart vocabulary splits gaps by context. A common gap opens on an ordinary day with ordinary volume and no specific news, while a breakaway gap opens on heavy volume alongside new information, often out of a price range the stock has held for a while. The volume split in the panel above is a measurable version of that same distinction.

Why do gap fill statistics vary so much between sources?

Four choices move the number a long way: the fill horizon, the basket of stocks, the period covered, and whether the gap is measured from the prior close or from the prior high and low. A quoted fill rate missing any of the four cannot be compared with another one.


How these numbers were measured

Sessions are built from one-minute bars inside regular trading hours in New York time. The opening price is the first regular-session bar's open, the close is the last bar's close, and the day's high and low are the extremes of those bars. Each gap is measured against the same ticker's previous session close.

The 5, 20 and 60 session horizons include the gap day itself. Gap days stop at the end of February 2026 while the tape runs through June 2026, so every gap in the horizon panel has at least sixty later sessions available and none is scored unfilled for want of data.

Normal volume in the third panel is the average regular-session volume over the 20 sessions ending two days before the gap, which keeps the gap day and the day before it out of their own benchmark.

The basket is eight liquid large caps. Thinner names gap more often and fill differently, so these rates do not carry over to small caps without re-running the same test on them.

Every panel here ships with the exact SQL beneath it. To run the same test on a ticker you follow, ask the question in plain English on the Strasmore terminal.

#gaps#gap fill#opening auction#overnight returns#technical analysis