Strasmore Research
Market Recap

Market Recap: June 2026

Matt ConnorBy Matt Connor Updated 2026-07-26

June 2026 was a month of divergence: the large-cap indexes slipped while small caps rose, and beneath the index level, more stocks fell than rose. SPY finished at $746.32, -1.2% on the month, while IWM, the small-cap index, gained 4.2%. The month's other standout measured fact: one memory-chip maker put $995.7 billion through the tape in 21 sessions, more than SPY itself. Every number here is a stored query result; expand any panel for the exact SQL.

The month on the board

QueryJune 2026: open to close, range, and regular-hours turnover for the four index ETFs
The exact SQL behind every number
SELECT ticker,
    round(argMinIf(toFloat64(open), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS month_open,
    round(argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS month_close,
    round((argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) / argMinIf(toFloat64(open), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) - 1) * 100, 1) AS month_return_pct,
    round(maxIf(toFloat64(high), (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS month_high,
    round(minIf(toFloat64(low), (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS month_low,
    round(sumIf(toFloat64(close) * toFloat64(volume), (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) / 1e9, 1) AS rth_dollar_bn
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'QQQ', 'DIA', 'IWM')
  AND window_start >= toDateTime('2026-06-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
GROUP BY ticker
ORDER BY ticker
Run this yourself

The split reads directly off the table: DIA 2.4% and IWM 4.2% rose while SPY fell -1.2% and QQQ finished -0.2%, the mega-cap growth index flat-to-down in the same month small caps gained four percent.

June against the five months before it

Was June's dip unusual? The trailing panel recomputes the same monthly return for every month of the half in one query, the June rows are produced identically to January's, live at generation time.

QueryMonthly returns, January through June 2026, recomputed live (SPY and QQQ)
The exact SQL behind every number
SELECT toString(toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York')))) AS period_start, ticker,
    round((argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) / argMinIf(toFloat64(open), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) - 1) * 100, 1) AS month_return_pct,
    round(argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS month_close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'QQQ')
  AND window_start >= toDateTime('2026-01-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
GROUP BY period_start, ticker
ORDER BY period_start, ticker
Run this yourself

By that yardstick June was ordinary: SPY's -1.2% sits well inside the half's range, March fell -4.4% and April rose 9.9%. The month's character was rotation, not direction.

June against every June on the tape

QueryEvery June on the tape: SPY, recomputed identically by year (session counts shown)
The exact SQL behind every number
SELECT toYear(toTimeZone(window_start, 'America/New_York')) AS y,
       uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) AS sessions,
       round((argMax(toFloat64(close), window_start) / argMin(toFloat64(open), window_start) - 1) * 100, 1) AS june_return_pct
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY')
  AND window_start >= toDateTime('2003-01-01 00:00:00')
  AND window_start < toDateTime('2026-07-01 00:00:00')
  AND toMonth(toTimeZone(window_start, 'America/New_York')) = 6
  AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY y
HAVING sessions >= 17
ORDER BY y ASC
Run this yourself
QueryThe rank receipt: this June against every prior one (rank 1 = best; self-excluded)
The exact SQL behind every number
SELECT round(anyIf(ret, y = 2026), 1) AS june_2026_pct,
       arrayCount(x -> x > anyIf(ret, y = 2026), groupArrayIf(ret, y != 2026)) + 1 AS rank_best,
       count() AS junes_compared,
       min(y) AS first_year,
       anyIf(sessions, y = 2026) AS sessions_2026
FROM (
    SELECT toYear(toTimeZone(window_start, 'America/New_York')) AS y,
           uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) AS sessions,
           (argMax(toFloat64(close), window_start) / argMin(toFloat64(open), window_start) - 1) * 100 AS ret
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY')
      AND window_start >= toDateTime('2003-01-01 00:00:00')
      AND window_start < toDateTime('2026-07-01 00:00:00')
      AND toMonth(toTimeZone(window_start, 'America/New_York')) = 6
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY y
    HAVING sessions >= 17
)
Run this yourself

SPY's -1.2% ranks 15 of 23 Junes since 2004 (rank 1 = best), mid-pack, slightly below the middle. An ordinary June, and ordinary is a valid finding: nothing about the month's index-level move stands out against two decades of Junes. What stood out was the rotation underneath it, the breadth panel below. Basis: regular-hours open-to-close within each calendar June, identical arithmetic every year, upper bound pinned at this month's end so the comparison set cannot silently grow.

Session by session

QuerySPY, all 21 June sessions: close and close-over-close change
The exact SQL behind every number
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS et_date,
    round(argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199), 2) AS spy_close,
    round((argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) / any(prev) - 1) * 100, 1) AS change_pct
FROM global_markets.delayed_stocks_minute_aggs
INNER JOIN (
    SELECT d, lagInFrame(c) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev
    FROM (
        SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d, argMaxIf(toFloat64(close), window_start, (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) AS c
        FROM global_markets.delayed_stocks_minute_aggs
        WHERE ticker = 'SPY' AND window_start >= toDateTime('2026-05-29 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
        GROUP BY d
    )
) AS p ON toDate(toTimeZone(window_start, 'America/New_York')) = p.d
WHERE ticker = 'SPY' AND window_start >= toDateTime('2026-06-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
GROUP BY et_date
ORDER BY et_date
Run this yourself

The month's sharpest single-session fall came early, -2.6% on 2026-06-05, and the chart shows the shape of the month: a drift lower through mid-June, a floor near $716.58, and a partial recovery over the final sessions into the quarter turn. For the last week of that recovery in session-by-session detail, see the week recap.

Breadth: more fell than rose

QueryAdvancers vs decliners for June (close vs the last May close), liquidity filter disclosed
The exact SQL behind every number
SELECT
    countIf(chg > 0 AND NOT dropped) AS advancers,
    countIf(chg < 0 AND NOT dropped) AS decliners,
    countIf(chg = 0 AND NOT dropped) AS unchanged,
    countIf(dropped) AS dropped_by_liquidity_filter
FROM (
    SELECT ticker,
        argMaxIf(toFloat64(close), window_start, toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-06-01') AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199)
      - argMaxIf(toFloat64(close), window_start, toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-05-29') AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) AS chg,
        sumIf(toFloat64(close) * toFloat64(volume), toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-06-01')) < 5e6 AS dropped
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE window_start >= toDateTime('2026-05-29 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
    GROUP BY ticker
    HAVING countIf(toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-05-29') AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) > 0
       AND countIf(toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2026-06-01') AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) > 0
)
Run this yourself

4608 tickers fell on the month against 4240 that rose, negative breadth in a month where two of the four index ETFs gained. Index weighting and equal-count breadth answer different questions; June is a month where they disagreed. The liquidity filter set aside 2896 tickers under $5M of June volume, counted here rather than hidden. The day grain agrees with the ticker grain: 12 of the month's 21 sessions closed SPY lower against 9 higher.

QuerySPY sessions up vs down across the month, one cheap receipt
The exact SQL behind every number
SELECT countIf(day_ret > 0) AS up_sessions,
       countIf(day_ret < 0) AS down_sessions,
       countIf(day_ret = 0) AS flat_sessions,
       count() AS sessions_total
FROM (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           round((argMax(toFloat64(close), window_start) / argMin(toFloat64(open), window_start) - 1) * 100, 2) AS day_ret
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND window_start >= toDateTime('2026-06-01 00:00:00')
      AND window_start < toDateTime('2026-07-01 00:00:00')
      AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199
    GROUP BY d
)
Run this yourself

The tape's leaders

QueryJune regular-hours dollar volume, whole tape (one reused-symbol listing excluded pending entity verification)
The exact SQL behind every number
SELECT ticker,
    round(sum(toFloat64(close) * toFloat64(volume)) / 1e9, 1) AS dollar_bn,
    round(100 * sum(toFloat64(close) * toFloat64(volume)) / max(sum(toFloat64(close) * toFloat64(volume))) OVER (), 1) AS pct_of_leader
FROM global_markets.delayed_stocks_minute_aggs
WHERE window_start >= toDateTime('2026-06-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')
  AND (toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199
  AND ticker NOT IN ('SPCX')
GROUP BY ticker
ORDER BY dollar_bn DESC
LIMIT 8
Run this yourself

MU led the entire month at $995.7 billion, SPY, in second, did 77.5% of that. NVDA put $523 billion through in fourth; its month has its own tick-by-tick deep-dive. A semiconductor theme owns the list, four of the eight names. Basis: June 1–30 regular hours; one reused-symbol June listing is excluded pending entity verification, and its first month has its own post.

The options tape: June against May

QueryJune: whole-tape options contract volume and same-day-expiry share (one scan)
The exact SQL behind every number
SELECT round(sum(toFloat64(volume)) / 1e6, 1) AS contracts_mm,
       round(100.0 * sumIf(toFloat64(volume), toDateOrNull(concat('20', substring(ticker, length(ticker) - 14, 6))) = toDate(toTimeZone(window_start, 'America/New_York'))) / sum(toFloat64(volume)), 1) AS zero_dte_pct,
       uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) AS sessions
FROM global_markets.options_minute_aggs
WHERE window_start >= toDateTime('2026-06-01 00:00:00')
  AND window_start < toDateTime('2026-07-01 00:00:00')
Run this yourself
QueryMay, recomputed identically for contrast (one scan)
The exact SQL behind every number
SELECT round(sum(toFloat64(volume)) / 1e6, 1) AS contracts_mm,
       round(100.0 * sumIf(toFloat64(volume), toDateOrNull(concat('20', substring(ticker, length(ticker) - 14, 6))) = toDate(toTimeZone(window_start, 'America/New_York'))) / sum(toFloat64(volume)), 1) AS zero_dte_pct,
       uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) AS sessions
FROM global_markets.options_minute_aggs
WHERE window_start >= toDateTime('2026-05-01 00:00:00')
  AND window_start < toDateTime('2026-06-01 00:00:00')
Run this yourself

June's whole-tape options volume was 1477.9 million contracts with 34.3% trading on same-day expiries, against May's 1394.6 million and 30.3%. More volume AND a bigger same-day share: June's 0DTE share is the highest month of the half, and the January-onward evolution is in the H1 recap. A third of June's options volume expired the day it traded.

QuerySPY median quoted spread: the June sample session against May's (second Wednesdays)
The exact SQL behind every number
SELECT toDate(toTimeZone(sip_timestamp, 'America/New_York')) AS session,
       round(quantileDeterministicIf(0.5)((toFloat64(ask_price) - toFloat64(bid_price)) / ((toFloat64(ask_price) + toFloat64(bid_price)) / 2) * 10000, toUInt64(toUnixTimestamp64Micro(sip_timestamp)), bid_price > 0 AND ask_price >= bid_price), 3) AS med_spread_bps,
       round(count() / 1e6, 2) AS quote_updates_m,
       countIf(NOT (bid_price > 0 AND ask_price > 0 AND ask_price >= bid_price)) AS invalid_dropped
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'SPY'
  AND ((sip_timestamp >= toDateTime64('2026-05-13 13:30:00', 9) AND sip_timestamp < toDateTime64('2026-05-13 20:00:00', 9))
    OR (sip_timestamp >= toDateTime64('2026-06-10 13:30:00', 9) AND sip_timestamp < toDateTime64('2026-06-10 20:00:00', 9)))
GROUP BY session
ORDER BY session ASC
Run this yourself

The cost to trade the market's biggest ticket moved the same direction: SPY's median quoted spread on the June sample session (0.409 bps, June 10) is wider than May's (0.27 bps, May 13). Basis, disclosed: single labeled sample sessions, the second Wednesday of each month, not month-wide medians; invalid quotes are counted in the panel.

Rates: the curve barely moved

QueryTreasury yields through June: 10-year, 2-year, and the 2s10s spread
The exact SQL behind every number
SELECT toString(date) AS d,
    round(yield_10_year, 2) AS y10,
    round(yield_2_year, 2) AS y2,
    round((yield_10_year - yield_2_year) * 100, 0) AS spread_2s10s_bp
FROM global_markets.treasury_yields
WHERE date >= toDate('2026-06-01') AND date <= toDate('2026-06-30')
  AND isNotNull(yield_10_year) AND isNotNull(yield_2_year)
ORDER BY date
Run this yourself

The 10-year ended June at 4.44% with the 2s10s spread at 30 basis points, a quiet month for rates against a noisier half (the H1 recap carries the six-month curve story).

The calendar: heavy quarter-end traffic, one missing filing day

QueryJune's corporate calendar: dividends, splits, listings, filings (June 30 index gap disclosed)
The exact SQL behind every number
SELECT
    (SELECT count() FROM global_markets.stocks_dividends WHERE ex_dividend_date >= toDate('2026-06-01') AND ex_dividend_date <= toDate('2026-06-30')) AS ex_div_events,
    (SELECT count() FROM global_markets.stocks_splits WHERE execution_date >= toDate('2026-06-01') AND execution_date <= toDate('2026-06-30')) AS splits,
    (SELECT count() FROM global_markets.stocks_ipos WHERE listing_date >= toDate('2026-06-01') AND listing_date <= toDate('2026-06-30')) AS ipos,
    (SELECT uniqExact(accession_number) FROM global_markets.stocks_sec_edgar_index WHERE filing_date >= toDate('2026-06-01') AND filing_date <= toDate('2026-06-30')) AS june_filings,
    (SELECT uniqExact(accession_number) FROM global_markets.stocks_sec_edgar_index WHERE filing_date = toDate('2026-06-30')) AS filings_jun30,
    (SELECT count() FROM global_markets.stocks_news WHERE published_utc >= toDateTime('2026-06-01 00:00:00') AND published_utc < toDateTime('2026-07-01 00:00:00')) AS news_articles
Run this yourself

June carried 6651 ex-dividend events, 164 splits, and 35 new listings, among them the month's headline debut, covered receipt-by-receipt in the SpaceX first-month deep-dive. One disclosure on the filing count: the SEC index shows 68388 June filings, but June 30 itself carries only 31, the index for the month's last day is materially incomplete (neighboring days carry thousands), so the June total is understated until the feed backfills, part of a 2026 month-end pattern with its own diagnostic note.

The dividend wave

QueryEx-dividend events by day through June
The exact SQL behind every number
SELECT toString(ex_dividend_date) AS d, count() AS ex_div_events
FROM global_markets.stocks_dividends
WHERE ex_dividend_date >= toDate('2026-06-01') AND ex_dividend_date <= toDate('2026-06-30')
GROUP BY d
ORDER BY d
Run this yourself

Dividend traffic clusters at the turn of the month: June opened with 807 ex-dividend events in a single day, and the chart carries the month's full rhythm, the daily churn in the low hundreds punctuated by month-boundary spikes. What an ex-dividend date actually does to a price is covered in the ex-dividend explainer.

The shorts: both June prints on file

QueryShort-interest settlements on file: May 29, June 15, and June 30
The exact SQL behind every number
SELECT toString(settlement_date) AS settlement, count() AS tickers
FROM global_markets.stocks_short_interest
WHERE settlement_date >= toDate('2026-05-20')
  AND settlement_date <= toDate('2026-06-30')
GROUP BY settlement
ORDER BY settlement
Run this yourself

Short interest settles twice a month and publishes on a lag. Both June prints are now on file: the 2026-06-30 settlement carries 22207 tickers, after 22178 on the June 15 file and 21987 on May 29. Daily short volume is a different dataset; its June caveat is the truncated June 29 file, carried in the week recap and the June 29 deep-dive.

The session receipt

Query21 sessions, verified from the tape (Juneteenth closure receipt included)
The exact SQL behind every number
SELECT
    (SELECT uniqExact(toDate(toTimeZone(window_start, 'America/New_York'))) FROM global_markets.delayed_stocks_minute_aggs
     WHERE ticker = 'SPY' AND window_start >= toDateTime('2026-06-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')) AS june_sessions,
    (SELECT count() FROM global_markets.delayed_stocks_minute_aggs
     WHERE ticker = 'SPY' AND window_start >= toDateTime('2026-06-19 00:00:00') AND window_start < toDateTime('2026-06-20 00:00:00')) AS juneteenth_spy_bars,
    (SELECT countIf((toHour(window_start) * 60 + toMinute(window_start)) BETWEEN 810 AND 1199) FROM global_markets.delayed_stocks_minute_aggs
     WHERE ticker = 'SPY' AND window_start >= toDateTime('2026-06-01 00:00:00') AND window_start < toDateTime('2026-07-01 00:00:00')) AS regular_bars_june
Run this yourself

June ran 21 sessions, Juneteenth (June 19) shows 0 SPY bars, and the 8190 regular bars work out to exactly 21 full 390-minute sessions.

Data notes

Full data notes
  • Short interest runs through the June 30 settlement, both of the month's prints are on file and carried inline above.
  • The June 30 filing-index day is materially incomplete (a handful of administrative filings against thousands on neighboring days); June filing totals are understated until the feed backfills. Every 2026 month-end whose last calendar day was a weekday shows the same signature, the diagnostic note carries the receipts; the Q2 recap carries the quarter-scale caveat.
  • The June 29 FINRA short-volume file is truncated market-wide, receipts in the week recap and the June 29 deep-dive.
  • Volume-leader exclusion: one June listing trades under a reused symbol and is excluded from the leaderboard pending entity verification; its own post carries the verification receipts.
  • Breadth compares each ticker's last June close against its last May close; tickers without both closes are excluded by construction and the liquidity filter's exclusions are counted in the panel.

Methodology

  • The period is June 1–30, 2026, 21 sessions, verified from observed bars (the receipt panel above). Monthly returns are first regular-hours open to last regular-hours close within the period.
  • Timestamps are stored UTC and filtered with raw UTC bounds; June 2026 is entirely EDT, so regular hours are 13:30–20:00 UTC. Dollar volume is minute close times minute volume over regular hours.
  • Trailing-month comparisons are recomputed live in the same query as June's own row, never read from a stored value. The every-June history block does the same across the full minute tape (Eastern-wall-clock regular hours, the DST-safe multi-year convention, with its upper bound pinned at this month's end and a minimum-session guard shown per year); the depth verification is in the H1 recap's methodology.
  • Generation runs through the gated read-only path; the public page never queries live. Warehouse state as of July 5, 2026.

This is the first edition of the standing monthly recap, July's edition will link back here. For the month's final week in session-by-session detail, see the week-of-June-29 recap; for the quarter this month closed, see the Q2 recap.