Short version of a post on gex.live/research; the full write-up, definitions and reproduce block live there.
Most published dealer-gamma numbers are built from open interest: yesterday's outstanding contracts, multiplied by a convention about who holds which side. Whether the convention is right is a separate question. The prior question is simpler: how much of what trades today was already in that book this morning — and how much of tomorrow's book is being created today? Open interest and volume are enough to answer it, with no assumption about who bought.
Sample: SPX and SPXW, 2022-04-14 to 2026-08-14, 1,081 trading days, every expiry within about a month (0DTE plus the 21 nearest), 8.6 million contract-days, 4.3 million with volume.
Definitions
Per contract (expiry, strike, right) and session D: OI(D) is open interest at the start of D, OI(D+1) at the start of the next session, ΔOI = OI(D+1) − OI(D), vol the day's volume in that contract. |ΔOI| / vol is a lower bound on how one-sided the day's trading in that contract was — 1.0 means every lot opened (or every lot closed), 0 means opens and closes cancelled. Contracts expiring on D have no next-day OI and drop out of the ΔOI statistics; 4.1% of rows (3.8% of volume) show |ΔOI| > vol, which is impossible (OI snapshot timing) and are excluded.
The book grows by 40% of what trades, every day
| days to expiry on D | net ΔOI / volume | |ΔOI| / volume (lower bound on one-sidedness) | share of volume in contracts whose OI rose | contract-days |
|---|---|---|---|---|
| 1–5 | 37.8% | 41.8% | 90.3% | 813,013 |
| 6–21 | 42.7% | 53.3% | 81.1% | 2,206,446 |
| 22+ | 42.8% | 57.6% | 76.5% | 831,896 |
Across the whole book, net ΔOI is 39.9% of the day's volume on the median session (IQR 36.2–44.0%), positive in every year and every expiry bucket: the SPX book is always being built faster than it is unwound, until expiry does the unwinding. Far expiries are open-and-hold (a day's trading in a 22+ DTE contract is at least 58% one-sided); the nearest expiries churn (42% at 1–5 DTE). Per contract-day the median lower bound is 0.50, and 22% of contract-days are above 0.9.
Tomorrow's 0DTE book is made today
Take the contracts at 1 DTE today; their OI(D+1) is tomorrow's opening 0DTE book. On the median session 44% of that book did not exist this morning — it was opened today — and the share rises through the sample: 40% in 2024, 50% in 2025, 52% in 2026. Today's volume in those contracts is about equal to tomorrow's opening OI in them (median ratio 1.06).
On its own day, 0DTE trades six times its opening book
| year | 2022 | 2023 | 2024 | 2025 | 2026 (to Aug) | all |
|---|---|---|---|---|---|---|
| 0DTE volume ÷ 0DTE opening OI, median session | 4.5× | 5.1× | 5.5× | 7.7× | 7.8× | 5.9× |
This is the number an OI-based 0DTE book is up against. On the median 2026 session, the contracts expiring that day trade nearly eight times the open interest they started with; most 0DTE positions are opened and closed inside the session and never appear in any OI print. Whatever convention is applied to the morning's open interest is being applied to roughly an eighth of the day's 0DTE activity. That is a statement about coverage, not about the convention's sign.
What it does not say
Nothing here signs a trade. ΔOI says positions were opened or closed, not who opened them. The one-sidedness numbers are lower bounds — a contract can open 300 and close 300 in a day and show ΔOI = 0. One archive, one vendor's end-of-day chain.
Reproduce it
Any end-of-day option-chain feed with open interest and volume per contract reproduces every number above — two consecutive days' OI and one day's volume per contract is the whole input.
d["d_oi"] = d["oi_next"] - d["oi"] # per (expiry, strike, right, date)
d = d[d.vol > 0]
d = d[d.d_oi.abs() <= d.vol] # drop impossible rows (snapshot timing)
d["dte"] = (d.expiration - d.date).dt.days
net = d.groupby("dte_bucket").apply(lambda g: g.d_oi.sum() / g.vol.sum())
one_sided = d.groupby("dte_bucket").apply(lambda g: g.d_oi.abs().sum() / g.vol.sum())
t1 = d[d.dte == 1].groupby("date").agg(oi_next=("oi_next","sum"),
created=("d_oi", lambda x: x.clip(lower=0).sum()))
(t1.created / t1.oi_next).median() # tomorrow's 0DTE book made today
t0 = d0[d0.dte == 0].groupby("date").agg(oi=("oi","sum"), vol=("vol","sum"))
(t0.vol / t0.oi).median() # 0DTE volume / opening OI
Not a signal, not advice — a measurement with its sample attached. More of these, including how often dealer-gamma levels actually hold: gex.live/research.
Top comments (0)