Strasmore Research
Market recaps wey dey break am down Matt ConnorBy Matt Connor · Updated 2026-07-24 · data as of July 24, 2026 · refreshed weekly

unusual options activity last session

See which underlyings trade far above their 20-session average for the last session and how calls and puts split for the whole US options tape.

Unusual options activity dey mean say one underlying instrument dey trade many more option contracts for one session than its normal level before. This board dey rank am for the last completed options session, Jul 22: total contracts traded against that same underlying's average over the prior twenty sessions. The options tape dey follow equity tape later, so the session wey dem list here na the newest complete one for our data, no be today's session.

One thing wey must clear before the table: this na volume screen. Most unusual-options alerts wey dem publish dey compare one day's volume against open interest, which na the count of contracts wey still dey outstanding after market close. The options tape wey we store dey carry trades, no be positions, and we no get open-interest field for anywhere inside am. Every ratio wey dey below dey compare one underlying's volume against its own volume history. Options volume vs. open interest dey explain wetin each measurement dey answer.

Unusual options activity: the last completed session

QueryUnusual options activity: last session vs. their own 20-session average
The exact SQL behind every number
WITH tape AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY d
),
ranked AS (
    SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS raw_rn
    FROM tape
),
cal AS (
    SELECT d, vol, rn, sum(if(rn BETWEEN 2 AND 21, 1, 0)) OVER () AS baseline_sessions
    FROM (
        SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS rn
        FROM ranked
        WHERE vol >= 0.75 * (SELECT quantileExact(0.5)(vol) FROM ranked WHERE raw_rn > 1)
    )
),
day_root AS (
    SELECT substring(ticker, 3, length(ticker) - 17) AS root,
           toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY root, d
),
scored AS (
    SELECT r.root AS root,
           sumIf(r.vol, c.rn = 1) AS last_vol,
           avgIf(r.vol, c.rn BETWEEN 2 AND 21) AS base_vol,
           maxIf(r.vol, c.rn BETWEEN 2 AND 21) AS prior_high,
           countIf(c.rn BETWEEN 2 AND 21) AS root_sessions,
           max(c.baseline_sessions) AS baseline_session_count,
           maxIf(toYYYYMMDD(c.d), c.rn = 1) AS session_id,
           maxIf(formatDateTime(c.d, '%b %e'), c.rn = 1) AS session_label
    FROM day_root r INNER JOIN cal c ON r.d = c.d
    WHERE c.rn <= 21
      AND r.root NOT IN ('SPCX')
      AND r.root NOT IN ('KORU','SOXL','SOXS','TQQQ','SQQQ','NVDL','NVDS','NVD','TSLL','TSLQ','TSLZ','SPXL','SPXS','UPRO','SPXU','LABU','LABD','FAS','FAZ','TNA','TZA','YINN','YANG','UDOW','SDOW','BOIL','KOLD','UCO','SCO','USD','SSO','SDS','QLD','QID','ERX','ERY','DRN','DRV','CURE','SOXY','MUU','SNXX','UVXY','SVXY','UVIX','SVIX','BULZ','WEBL','WEBS','DPST','DRIP','GUSH','AGQ','ZSL','BITX','ETHU','MSTX','MSTU','CONL','DUST','JNUG','JDST','NUGT')
    GROUP BY r.root
    HAVING last_vol >= 25000 AND base_vol >= 5000 AND root_sessions >= 18
)
SELECT root AS ticker,
       round(last_vol / base_vol, 1) AS vol_ratio,
       round(last_vol / 1000, 1) AS session_volume_k,
       round(base_vol / 1000, 1) AS baseline_volume_k,
       round(prior_high / 1000, 1) AS prior_high_volume_k,
       session_label,
       session_id,
       baseline_session_count
FROM scored
ORDER BY vol_ratio DESC, ticker ASC
LIMIT 10

Three columns dey carry the reading. The ratio dey show how far the underlying trade go pass its normal level. The baseline dey show wetin normal be, and if multiple big pass small chain, e no be big event like if e big pass busy chain. The 20-session high dey show if the session beat anything for the previous month. The last three columns na the audit trail: the session wey the board build from, wey dem print as label and as plain number, plus the count of eligible sessions wey dey behind the baseline, wey must reach twenty for the page to publish.

CVS dey top the board at 7.2x its own average: 138.2 thousand contracts against a baseline of 19.3 thousand, with a prior 20-session high of 59.3 thousand.

  • INFY at 4.2x: 29.5 thousand contracts against a 7 thousand baseline.
  • GM at 3.6x, 67.1 thousand contracts on a 18.6 thousand baseline.
  • SMCI at 3.6x, 737 thousand contracts against a 20-session high of 361.8 thousand.
  • WDAY at 3.6x on 28 thousand contracts.

The last row of the board, NUAI, still trade 3x its own average. Multiple and size na separate readings, na why dem both dey for the board. The share-market version of this same idea dey for unusual volume stocks this week, and the arithmetic itself dey for relative volume.

Calls or puts: wetin de tape dey show

Di split between call and put volume na wetin di alert dey really point to. Same board, same session, we sort am by call share:

QueryCalls or puts: how many call and put contract volume dem do for that session
The exact SQL behind every number
WITH tape AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY d
),
ranked AS (
    SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS raw_rn
    FROM tape
),
cal AS (
    SELECT d, vol, rn, sum(if(rn BETWEEN 2 AND 21, 1, 0)) OVER () AS baseline_sessions
    FROM (
        SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS rn
        FROM ranked
        WHERE vol >= 0.75 * (SELECT quantileExact(0.5)(vol) FROM ranked WHERE raw_rn > 1)
    )
),
day_root AS (
    SELECT substring(ticker, 3, length(ticker) - 17) AS root,
           toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol,
           sumIf(toFloat64(volume), substring(ticker, length(ticker) - 8, 1) = 'C') AS calls,
           sumIf(toFloat64(volume), substring(ticker, length(ticker) - 8, 1) = 'P') AS puts
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY root, d
),
scored AS (
    SELECT r.root AS root,
           round(sumIf(r.vol, c.rn = 1) / avgIf(r.vol, c.rn BETWEEN 2 AND 21), 1) AS vol_ratio,
           sumIf(r.calls, c.rn = 1) AS calls,
           sumIf(r.puts, c.rn = 1) AS puts,
           maxIf(toYYYYMMDD(c.d), c.rn = 1) AS session_id
    FROM day_root r INNER JOIN cal c ON r.d = c.d
    WHERE c.rn <= 21
      AND r.root NOT IN ('SPCX')
      AND r.root NOT IN ('KORU','SOXL','SOXS','TQQQ','SQQQ','NVDL','NVDS','NVD','TSLL','TSLQ','TSLZ','SPXL','SPXS','UPRO','SPXU','LABU','LABD','FAS','FAZ','TNA','TZA','YINN','YANG','UDOW','SDOW','BOIL','KOLD','UCO','SCO','USD','SSO','SDS','QLD','QID','ERX','ERY','DRN','DRV','CURE','SOXY','MUU','SNXX','UVXY','SVXY','UVIX','SVIX','BULZ','WEBL','WEBS','DPST','DRIP','GUSH','AGQ','ZSL','BITX','ETHU','MSTX','MSTU','CONL','DUST','JNUG','JDST','NUGT')
    GROUP BY r.root
    HAVING sumIf(r.vol, c.rn = 1) >= 25000
       AND avgIf(r.vol, c.rn BETWEEN 2 AND 21) >= 5000
       AND countIf(c.rn BETWEEN 2 AND 21) >= 18
),
board AS (
    SELECT root, vol_ratio, calls, puts, session_id
    FROM scored
    ORDER BY vol_ratio DESC, root ASC
    LIMIT 10
)
SELECT root AS ticker,
       round(calls / 1000, 1) AS call_volume_k,
       round(puts / 1000, 1) AS put_volume_k,
       round(100.0 * calls / (calls + puts), 0) AS call_share_pct,
       session_id
FROM board
WHERE calls + puts > 0
ORDER BY call_share_pct DESC, ticker ASC

For di side wey call plenty, CVS put 95% of im contracts into calls: 130.7 thousand calls against 7.5 thousand puts. For di other end of di same table, WDAY ran 30% calls, 8.3 thousand against 19.7 thousand puts.

Dat split important to read, but person fit over-read am easy. Every contract wey print get buyer and seller, and di tape no dey show which side start di trade. Heavy call volume fit be outright long position or na covered call wey dem write against stock wey di seller already own. Buying and selling call options go explain wetin each side of dat trade dey take on. Where di tape dey loud, option prices usually dey loud too, and dat na wetin di highest implied volatility stocks board dey show.

Wetin the session contracts be made of

QueryWetin the session's contracts be: options volume by days to expiry
The exact SQL behind every number
WITH tape AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY d
),
ranked AS (
    SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS raw_rn
    FROM tape
),
cal AS (
    SELECT d, vol, rn, sum(if(rn BETWEEN 2 AND 21, 1, 0)) OVER () AS baseline_sessions
    FROM (
        SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS rn
        FROM ranked
        WHERE vol >= 0.75 * (SELECT quantileExact(0.5)(vol) FROM ranked WHERE raw_rn > 1)
    )
),
bars AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           toFloat64(volume) AS v,
           toDate(concat('20', substring(ticker, length(ticker) - 14, 2), '-',
                         substring(ticker, length(ticker) - 12, 2), '-',
                         substring(ticker, length(ticker) - 10, 2))) AS expiry
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 12, 'America/New_York')
      AND toDate(toTimeZone(window_start, 'America/New_York')) IN (SELECT d FROM cal WHERE rn = 1)
),
scored AS (
    SELECT multiIf(expiry - d <= 0, 1, expiry - d <= 7, 2, expiry - d <= 30, 3,
                   expiry - d <= 90, 4, expiry - d <= 365, 5, 6) AS bk,
           v, d
    FROM bars
)
SELECT arrayElement(['same day', '1 to 7 days', '8 to 30 days', '31 to 90 days', '91 to 365 days', 'over a year'], bk) AS dte_bucket,
       round(sum(v) / 1e6, 2) AS contracts_m,
       round(100.0 * sum(v) / sum(sum(v)) OVER (), 1) AS pct_of_volume,
       toYYYYMMDD(max(d)) AS session_id
FROM scored
GROUP BY bk
ORDER BY bk ASC

Contracts wey dey expire same-day take 36.3% of the whole session, wey be 20.1 million contracts. Another 27.1% expire inside one week. For the far end, contracts wey get more than one year left take 1.2%.

This mix dey change how person suppose read unusual-activity headline. Most of the volume for any board like this na short-dated, and same-day contract dey settle when market close instead of dem dey carry am go next day. 0DTE options cover that end of the chain.

Na expiration session be this?

Options volume increase for whole market when monthly expiration dey, when big block of listed contracts dey leave the board at once. Ratio screen wey dem run for expiration day show half of the market, so the panel below label every session inside the window.

QueryMarket-wide options volume by session, with monthly expirations label
The exact SQL behind every number
WITH tape AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           sum(toFloat64(volume)) AS vol
    FROM global_markets.options_minute_aggs
    WHERE window_start >= toDateTime(today() - 45, 'America/New_York')
    GROUP BY d
),
ranked AS (
    SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS raw_rn
    FROM tape
),
cal AS (
    SELECT d, vol, rn, sum(if(rn BETWEEN 2 AND 21, 1, 0)) OVER () AS baseline_sessions
    FROM (
        SELECT d, vol, row_number() OVER (ORDER BY d DESC) AS rn
        FROM ranked
        WHERE vol >= 0.75 * (SELECT quantileExact(0.5)(vol) FROM ranked WHERE raw_rn > 1)
    )
),
w AS (
    SELECT d, vol,
           toStartOfMonth(d) + toIntervalDay(((5 - toDayOfWeek(toStartOfMonth(d)) + 7) % 7) + 14) AS third_friday
    FROM cal
    WHERE rn <= 25
),
marked AS (
    SELECT d, vol,
           (d = max(if(d <= third_friday, d, toDate('1970-01-01'))) OVER (PARTITION BY toStartOfMonth(d)))
             AND (third_friday <= max(d) OVER ()) AS is_expiry
    FROM w
),
latest AS (
    SELECT d, vol, is_expiry,
           max(if(is_expiry, d, toDate('1970-01-01'))) OVER () AS last_expiry_d
    FROM marked
)
SELECT formatDateTime(d, '%b %e') AS session,
       round(vol / 1e6, 1) AS contracts_m,
       multiIf(is_expiry, 'monthly expiration', 'ordinary') AS session_type,
       round(max(if(d = last_expiry_d, vol, 0)) OVER () / 1e6, 1) AS monthly_expiry_m,
       toYYYYMMDD(d) AS session_id
FROM latest
ORDER BY d ASC

The newest session for this panel na the one the board report, the same date for both tables, and its label na ordinary. E print 55.4 million contracts for whole US tape. The most recent monthly expiration inside the same window print 76.8 million. The 20-session baseline behind every ratio for the board normally get exactly one monthly expiration, so the denominator carry the same lump from session to session.

Does unusual options activity lead the stock?

The question worth answering is whether a heavy options session is followed by a heavy move in the underlying. Measured over the last 200 days: every name-session clearing the same volume floors, bucketed by its ratio, matched against the absolute change in the underlying's close on the next session. The typical column is each name's own median daily change across the window, so every bucket is compared with itself.

QueryWetin follow heavy options session: next-session absolute move vs. same names for ordinary day
The exact SQL behind every number
WITH daily AS (
    SELECT underlying_symbol AS sym,
           date AS d,
           sum(volume) AS vol,
           max(underlying_close) AS px
    FROM global_markets.options_greeks
    WHERE date >= today() - 200
      AND underlying_close > 0
      AND underlying_symbol NOT IN ('SPCX')
      AND underlying_symbol NOT IN ('KORU','SOXL','SOXS','TQQQ','SQQQ','NVDL','NVDS','NVD','TSLL','TSLQ','TSLZ','SPXL','SPXS','UPRO','SPXU','LABU','LABD','FAS','FAZ','TNA','TZA','YINN','YANG','UDOW','SDOW','BOIL','KOLD','UCO','SCO','USD','SSO','SDS','QLD','QID','ERX','ERY','DRN','DRV','CURE','SOXY','MUU','SNXX','UVXY','SVXY','UVIX','SVIX','BULZ','WEBL','WEBS','DPST','DRIP','GUSH','AGQ','ZSL','BITX','ETHU','MSTX','MSTU','CONL','DUST','JNUG','JDST','NUGT')
      AND underlying_symbol NOT IN (SELECT ticker FROM global_markets.stocks_splits
                                    WHERE execution_date BETWEEN today() - 230 AND today())
    GROUP BY sym, d
),
seq AS (
    SELECT sym, d, vol, px,
           avg(vol) OVER (PARTITION BY sym ORDER BY d ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING) AS base,
           count() OVER (PARTITION BY sym ORDER BY d ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING) AS base_n,
           any(px) OVER (PARTITION BY sym ORDER BY d ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) AS next_px,
           any(px) OVER (PARTITION BY sym ORDER BY d ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_px
    FROM daily
),
moves AS (
    SELECT sym, vol, base, base_n,
           if(next_px > 0, 100 * abs(next_px / px - 1), -1) AS next_abs,
           if(next_px > 0, 100 * (next_px / px - 1), -999) AS next_signed,
           if(prev_px > 0, 100 * abs(px / prev_px - 1), -1) AS own_abs
    FROM seq
),
typical AS (
    SELECT sym, quantileExact(0.5)(own_abs) AS typ
    FROM moves
    WHERE own_abs >= 0
    GROUP BY sym
)
SELECT arrayElement(['5x or more', '3x to 5x', '2x to 3x', '1x to 2x', 'below 1x'], bk) AS rvol_bucket,
       count() AS event_count,
       round(quantileExact(0.5)(next_abs), 2) AS median_next_move_pct,
       round(quantileExact(0.5)(typ), 2) AS median_typical_move_pct,
       round(quantileExact(0.5)(next_abs) - quantileExact(0.5)(typ), 2) AS gap_pp,
       round(quantileExact(0.5)(next_signed), 2) AS median_next_signed_pct
FROM (
    SELECT m.sym AS sym,
           multiIf(m.vol / m.base >= 5, 1, m.vol / m.base >= 3, 2, m.vol / m.base >= 2, 3,
                   m.vol / m.base >= 1, 4, 5) AS bk,
           m.next_abs AS next_abs,
           m.next_signed AS next_signed,
           t.typ AS typ
    FROM moves m INNER JOIN typical t ON m.sym = t.sym
    WHERE m.base_n = 20 AND m.base >= 5000 AND m.vol >= 25000 AND m.next_abs >= 0
)
GROUP BY bk
ORDER BY bk ASC

Following a session at five times its own baseline or more, the median absolute change on the next session was 2.52%, against 1.92% for those same names on an ordinary day, a gap of 0.6 percentage points. At the quiet end of the table the two columns read 1.87% and 2.09%, a gap of -0.22 points.

The honest answer is small, and it is about size rather than direction. Heavier options sessions are followed by slightly bigger moves in absolute terms, and the gap at the median is a fraction of a percentage point. The median signed change on the session after a five-times day was -0.34%, and after a below-baseline day -0.05%. Neither is the move an alert headline implies. Rarity is worth holding in view too: 652 name-sessions cleared five times their own baseline across the study window, against 11781 that traded below it.

How dem dey measure dis screen

Screen wey get hidden rules dey read as data, but na opinion dem dey give, so every rule dey here.

  1. Source. Full US options minute tape. Dem dey extract the underlying from each contract OCC symbol, so dem include index option roots and same-day expiries alongside ordinary stock and fund chains.
  2. The ratio. Total contract volume for last completed session divide by the same underlying average for the prior 20 eligible sessions. At least eighteen of those 20 must get volume. The board dey print the window length for its own column, and if window no reach 20, dem go hold the page instead of to print shorter average under 20-session label.
  3. Floors. 25,000 contracts for the session and 5,000-contract baseline. Without dem, the board go full with chains where one order fit be 10x reading.
  4. Session eligibility. Every panel for dis page dey read its sessions from one 45-day window of the same tape, under one rule: session count once market-wide volume reach 75% of the median of the older sessions for that window. Dem leave the newest day out of the median wey e must clear, so day wey no full yet no fit pull its own bar down to meet am. The tape dey arrive for front edge in pieces, and day wey carry less than three quarters of normal session go stay off the board until e full. Each panel dey print the session wey e land on, so dem fit compare all four together.
  5. Exclusions. Dem dey remove Leveraged and inverse funds by name: 3x product chain dey move with its multiplier and e go crowd the list every week. One reused ticker symbol dey also excluded, because vendor history dey splice two companies under am.
  6. No open interest. No open-interest field dey for the warehouse, so nothing for dis page dey compare volume against outstanding positions. Where competitor's screen say volume beat open interest, ours say volume beat its own 20-session average.
  7. Expiration days. Monthly expiration dey lift options volume market-wide. The session panel dey label the condition, and board wey build on expiration session suppose be read against other expiration sessions.

The follow-through study dey use second table, the daily options greeks file, wey carry clean underlying symbol and the underlying's close. E exclude same-day expiries and index options and e dey settle few sessions later than the minute tape, so its universe narrow pass the board. Names wey get stock split inside the study window dey removed, because split dey fake huge move.

FAQ

Wetin be unusual options activity?

E mean say one underlying stock dey trade many more option contracts for one session than its own recent average. For this page, we use arithmetic calculation instead of editorial judgment: we divide the session's contract volume by the same underlying's average over the prior 20 sessions, with a floor of 25,000 contracts for the session and 5,000 for the baseline.

Unusual options activity dey predict stock moves?

E no dey predict much, and e no dey show direction. Over the last 200 days of our data, sessions wey reach five times an underlying's own options-volume baseline follow by a median absolute move of 2.52%, compared to 1.92% for the same names on an ordinary day. The median signed change on that next session na -0.34%, so the volume talk something about how far the stock move, but e no talk much about which way e go go.

When dem dey update options volume data?

Contract volume dey show live for the consolidated options tape as the session dey run. The stored tape wey dey behind this page dey settle with lag, so the board dey report the newest complete session instead of one wey still dey run, and e dey stamp its date. Nothing for here na real-time feed.

Why this unusual options activity scanner no dey use open interest?

Clearinghouse dey publish open interest after market close, and we no get open-interest field for the data wey we hold. Comparing one session's volume against the same underlying's own volume history na something wey person fit measure directly from the tape, and e answer similar question: whether this chain dey busier than usual.


Every number for above na stored, versioned query. Open the SQL under any panel to audit the measurement, or run the same screen over any window wey you like for the Strasmore terminal.

#options#unusual options activity#options volume#market structure