Which book is the zero gamma?

A +30 point disagreement between two feeds, joined minute by minute — 155 minutes on two sessions, then 463 on six

Published 2026-08-24 · Sample 2026-07-24 (n=78 joined minutes) and 2026-07-31 (n=77) for the diagnosis; 2026-07-24 through 2026-07-31, six sessions, 463 joined minutes, for the confirmation · Data our per-minute book vs a widely followed public dealer-gamma feed's published snapshots, both at 5-minute cadence · Status measurement, not a signal

Two services publish a "zero gamma" level for the same SPX session and the numbers differ by thirty points. The reader's instinct is that one of them is broken. It is worth resisting, because there is a third possibility that turns out to be the true one: the word names more than one object, and both feeds are reporting their own object correctly. A level is not a number. It is a convention — a weight, an expiry scope, a sign rule and a root method — and two of those four differing is enough to put thirty points between two correct answers.

This post is the diagnosis, with the arithmetic. It does not name the other feed; the claim under test is a published number, not a vendor.

The observation

On 2026-07-24 our published zero-gamma flip sat above the other feed's level all day. Joined on the ET minute, one row per snapshot, zeroed rows dropped, the difference was mean +30.32 pt, median +34.18 pt over n=78 minutes — not a single bad print, a whole session. It was reproduced on a held-out session, 2026-07-31, n=77. And the correlation between the two levels across the day was −0.017: not a biased tracker of the same thing, but a different thing.

Three books wear the same word

Every dealer-gamma level is the root of a gamma-vs-hypothetical-spot curve. What differs is what each strike is weighted by. Three weights are in common use, and this site publishes all three per session:

Then the second axis: expiry scope. Our book carries 0DTE plus three near expiries on the sessions here. The other feed publishes two tabs — a 0DTE one and a whole-chain one that prices roughly 90 days of paper we do not carry, whose positive major was pinned at one far-dated strike for all of 07-24 and whose net gamma runs 1.70× the 0DTE tab's. Only its 0DTE tab is comparable to anything we build.

One detail decides the shape of the whole investigation: that feed's zero-gamma level is essentially the same number in both tabs — 53.2% byte-identical between them, mean absolute difference 1.40 pt. It does not publish a scope-specific zero gamma. So no choice of our scope can be validated against a scope difference on their side.

What the gap is not

Four candidates were tested and eliminated before the answer was reached.

Not expiry scope. Tightening our scope closes about nine points of the thirty, monotonically, and then stops:

our scope (cp×OI root) − their level, 07-24, n=78meanmedianIQRsd
0DTE only+21.28+24.63[+11.03, +30.86]12.97
0DTE + next expiry+23.63+26.56[+13.07, +33.42]12.93
0DTE + 2+25.74+28.64[+14.88, +35.23]12.84
whole book (all 4) — what we published+30.32+34.18[+18.96, +39.76]12.83

Twenty-one points survive at the tightest scope available, and the correlation with their level is +0.010 at every scope. Scope is a real contributor and worth fixing; it is not the mechanism.

Not spot. Our parity-solved spot minus theirs is mean +0.85 pt, median +0.69, sd 3.12. Feeding their own published spot into our root solver moves the level by < 0.02 pt at every minute (+30.32 → +30.33).

Not the sign convention. A sign-flipped OI book (−cp×OI) and an absolute-OI book (|OI|) have no root on any minute (n=0) — those curves are one-signed, so they have no zero gamma to publish at all. Whatever the other feed is doing, it is the +cp convention, same as ours; its positive major sits above spot at call strikes and its negative major below spot at put strikes.

Not the root method. Seven alternative definitions of "the root", all on the same 0DTE cp×OI ladder, all differenced against their level on 07-24:

root definition (cp×OI, 0DTE) − their levelmeanmediansd
our repriced root, fine grid+21.28+24.6312.97
repriced on a copied 60-point ±20% grid+20.80+25.2612.99
flat ATM IV (smile removed)+17.67+21.0612.05
call-OI / put-OI crossover strike+14.10+12.1616.86
per-strike sign change+12.75+12.2319.29
|gamma|-weighted centroid−9.14−14.4223.31
cumulative over strikes, upward−334.0−259.1336.6
cumulative over strikes, downward+192.0+9.4285.6

Grid resolution buys half a point. Killing the smile buys 3.6. The nearest candidate is still off by 12.75 points and the cumulative-ladder constructions — the ones most often drawn by hand — are wrong by hundreds. Nothing on this list lands on their level.

The answer: their level belongs to the volume book

Swap the weight from open interest to cumulative session volume, same sign rule, same root solver, and the bias disappears:

our root − their level, 07-24, n=78meanmedianIQRsdcorr with their level
cp×OI, 0DTE+21.28+24.63[+11.03, +30.86]12.97+0.010
cp×OI, whole book+30.32+34.18[+18.96, +39.76]12.83−0.017
cp×VOLUME, 0DTE+0.32−0.86[−4.42, +3.57]7.49+0.798
cp×VOLUME, whole book+0.22−1.09[−4.50, +3.53]7.50+0.796

Thirty points of bias become a third of a point, and a correlation of zero becomes +0.798. Note the last two rows: the volume book is almost scope-insensitive — 0DTE and whole book differ by 0.1 pt — because 0DTE is essentially all of the session's volume. That is also why a feed can publish one zero-gamma number for both its tabs without contradicting itself.

Two further checks say the same thing without using our pipeline at all.

Their own numbers force it. For any cp-signed book the net-gamma curve rises with spot, so the sign of net gamma at spot dictates which side of spot the root must be on: net gamma negative implies the level is above spot. That feed publishes two net-gamma series, an OI one and a volume one. Which does its own zero-gamma level obey?

share of minutes where the level's side of spot matches the series' signtheir OI net gammatheir VOLUME net gamma
07-24, whole-chain tab0.4560.937
07-24, 0DTE tab0.6580.924
07-31, whole-chain tab0.8970.949
07-31, 0DTE tab0.8180.948
pooled, 0DTE tab0.7370.936
pooled, whole-chain tab0.6750.943
our own 0DTE cp×OI object, same test1.000

On 07-24 that feed reported its OI net gamma negative on 100% of whole-chain snapshots while publishing a zero gamma below spot on 55% of them. That is internally impossible for a level belonging to the OI book and entirely routine for one belonging to the volume book. Our own object passes the same test at 1.000.

The magnitude is first-order, not a rounding argument. Evaluate our 0DTE curve at their published level. If the level belonged to our OI book, the curve should read about zero there. On 07-24 it reads −181.5 $M per point, which is 31.2% of the book's gross gamma; at a local slope of 6.75 $M per point per point, that is a 26.9 pt level error. On the volume book the same evaluation reads +37.0 $M per point, 7.2% of gross, slope 136.6 — a 0.3 pt error. On 07-31 the two read 5.7% and 4.7% of gross. The OI comparison asks you to believe a third of the book's gross gamma has gone missing; the volume comparison is consistently 5–7% of gross on both sessions.

One more thing worth stating because it closes a separate open question: that feed's volume series is convention-signed, not order flow. Correlated against our flow-signed measured position it gives +0.575 on 07-24 and −0.419 on 07-31; against convention cp × cumulative volume at 0DTE it gives +0.9956 and +0.9970. It is cp × traded volume. Three books, and the naming collision runs across all three.

Units are not part of the disagreement, incidentally, and it is worth checking them first when two feeds differ. Their net gamma correlates +0.9966 (OI) and +0.9971 (volume) with ours, at a median ratio of 13,205 and 13,645. The expected ratio if a feed quotes $M of dealer delta per 1% move is 1e6 / (spot × 0.01) = 13,467 on 07-24 and 13,387 on 07-31. Both land on it: same greek, same sign convention, different weight and a different unit label.

The confirmation pass

A diagnosis on two sessions is a hypothesis. So the volume book was built into the pipeline — a new per-(minute, strike, expiry) weight of greek × cp × cumulative session volume, the same cumulative object the existing volume columns carry, merely cp-signed rather than summed — and six sessions were rebuilt and rescored end to end against the other feed's 0DTE tab. Nothing was tuned; every number below comes from the first and only run of the new code.

our cp×VOL 0DTE root − their levelnmeanmediansdcorrsame day, old cp×OI whole-book mean
2026-07-2478+0.35−0.857.50+0.797+30.34
2026-07-2777+1.77+3.583.85+0.993+10.72
2026-07-2877−2.64−1.183.88+0.974+20.16
2026-07-2977+1.64+1.115.76+0.926+40.55
2026-07-3077+1.12−1.5611.55+0.294+36.91
2026-07-3177+5.22−1.4717.47−0.268+1.45
pooled463+1.24−0.549.91+0.896+23.37

Pooled bias falls from +23.37 to +1.24 pt and pooled sd from 20.05 to 9.91; the mean absolute bias by session falls from 23.4 to 2.1 pt. The diagnosis had predicted +0.32 / −0.86 / sd 7.49 / corr +0.798 on 07-24 at the one-minute grain, and the rebuilt code returned exactly that — the same numbers to the second decimal. The pooled +0.896 correlation is flattered by across-session level variation (spot moves ~90 pt over the week); the honest read is the per-session column, which runs from +0.993 down to −0.268.

Coverage of the new root is 1.000 at the minute grain and 0.9999 per second, the missing seconds being those before the session's first print, when the volume book is empty and the root is correctly reported as degenerate rather than fabricated. The measured book was checked for contamination against frozen fixtures and a pre-edit whole-session ladder — 351,405 rows × 15 shared columns, max absolute difference exactly 0.

The majors are a second, independent naming collision

The same investigation started from a second complaint: our call and put walls disagreed with theirs too. That turned out to be a different error with a different fix — wrong scope and wrong column, not wrong weight — and the two corrections are orthogonal. Restricting our OI-convention peaks to dte == 0:

exact-match rate (|d| ≤ 0.5 pt) vs their 0DTE majorspositive major, 0DTE scopepositive, whole booknegative major, 0DTE scopenegative, whole book
2026-07-240.7440.5260.8460.603
2026-07-270.9480.5580.9610.506
2026-07-280.9090.7530.8830.000
2026-07-290.4160.6750.9480.753
2026-07-300.7140.1040.6230.636
2026-07-310.7660.5840.6750.714
pooled0.7490.5330.8230.536

Pooled median absolute difference on the positive major goes 0.08 → 0.00 pt, and the sd of the difference 46.8 → 37.4. Scoring the volume-book peaks against their volume majors instead gives 0.851 and 0.793 pooled, with a median absolute difference of 0.00 pt on every session. Scoring the OI ladder against the volume major — one wrong column — gives 0.013 on 07-24.

The single worst reported disagreement resolves completely. At 15:30 on 07-24 we published 7565.00 against their 7424.49, a 140.51 pt gap. Two errors were stacked: an OI ladder scored against a volume major, and whole-book scope. The in-band positive OI-gamma peaks at that minute are all non-0DTE — 7500 at 3DTE carrying 3.19 $M per point, 7550 at 4DTE carrying 1.72, 7525 at 4DTE carrying 1.51 — against a 0DTE peak of only 0.46 $M per point at 7425. Restricted to 0DTE we say 7425.00 and they say 7425.00.

Three of the rescored columns got worse, and they are named rather than buried. On 07-29 the positive major fell 0.675 → 0.416: that day's 0DTE positive ridge is flat — the top three strikes at 10:00 are 7485 at 4.24 $M per point, 7475 at 4.20 and 7500 at 4.06, a 4% spread — so a winner-take-all major is a coin flip in either scope, and the whole-book ladder had matched by accident of far-dated OI piling on a round strike. On 07-31 the negative major fell 0.714 → 0.675, a band artefact: 15.6% of that day's negative-major prints sit outside our ±2.5% ladder window and cannot match at any scope. On 07-30 the negative major fell 0.636 → 0.623, one minute.

What it does not say

It does not say the other feed is right, or that we are. It says the two levels are roots of different books, and that once the same book is compared the disagreement is 1.24 points instead of 23.4. Which book should be drawn on a chart is a separate question this post does not answer; this site draws the measured book because its own research is that most 0DTE positions never appear in an open-interest print, but that is an argument made elsewhere and not settled here.

The bias is explained; the path is not. Two of the six sessions remain bad. On 07-31 the residual correlates −0.268 with their level, its sd of 17.47 is the worst of the week, and on that one session the old whole-book OI object has the smaller mean bias (+1.45 against +5.22). "The OI book is always the wrong series" is false at the level of one session's mean. On 07-30 the correlation is +0.294. Both are path problems, not level problems: the two volume books still agree at spot at +0.995 that day. And the sign of the residual mean flips across sessions (+1.8, −2.6, +1.6, +1.1, +5.2), so what remains is not a constant offset that could be calibrated away.

Two structural facts bound how well any two feeds can ever agree. First, their levels are lattice-quantised: every published level sits at a strike plus one of {0, 0.08, 0.26, 0.51, 0.83, 1.20, 1.61, 2.05, 2.50} inside the 5-point strike grid — 17 distinct fractional positions for the zero-gamma level, 7 and 3 for the two majors. Their spot field is not quantised (97 distinct offsets), so this is not rounding; their levels are a blend of two adjacent strikes, post-processed rather than a free interpolated root. That puts a floor of roughly ±2.5 pt on any level match. Second, their level is far less spot-sensitive than ours: correlation with their own spot is +0.116 on 07-24 and −0.482 on 07-31, against +0.928 and +0.967 for ours. Our root tracks spot because sticky-strike IV updates pull it along; whatever they do damps that out.

The sample is small and it is one counterparty. Two sessions and 155 joined minutes for the diagnosis, six sessions and 463 for the confirmation, all in one week of one summer, against one public feed at 5-minute cadence. Nothing here generalises to a third feed without repeating the work on it.

Nothing on this page is investment advice; see the Terms.

Before you call either feed wrong

The generalisable part of this is a checklist. Every one of these questions changed a level by more than a rounding error somewhere above.

  1. What is each strike weighted by? Call-minus-put open interest, call-minus-put cumulative volume, or aggressor-signed flow. This was the whole thirty points.
  2. Which expiries? 0DTE, the near book, or the whole chain. Worth about nine points here on the OI book — and almost nothing on the volume book, which is a fact about the book, not a general rule.
  3. Are the units the same greek in the same denominator? Check the ratio against 1e6 / (spot × 0.01) before concluding anything: a feed quoting $M of dealer delta per 1% move differs from one quoting $ per point by a factor near 13,400.
  4. What does the feed's own net gamma sign say? For any cp-signed book, net gamma below zero at spot forces the level above spot. If a feed's published level disobeys its own published net gamma on half the day, the level belongs to a different series of that feed's — this test needs nothing from your own pipeline.
  5. How is the root defined? A repriced root, a per-strike sign change and a cumulative-over-strikes crossover are three different numbers on the same ladder, spanning 12 points to several hundred.
  6. Is the level quantised? If published levels land on a small set of fractional offsets inside the strike grid while spot does not, the feed is blending adjacent strikes and no amount of care on your side will match it below a couple of points.
  7. Is the level in your comparison window at all? A level outside your own ladder band cannot match at any scope, and will look like disagreement rather than absence.

Reproduce it

All three books are published per session, so you can rebuild every level in this post and check which one your other feed's number belongs to. Each finished session's JSON is at https://gex.live/snapshots/YYYY-MM-DD.json (about 6 MB per day — please fetch sequentially); the dates are the ones listed at /sessions. The example below uses 2026-08-11.

pythonreproduce
import json, urllib.request
d = json.load(urllib.request.urlopen("https://gex.live/snapshots/2026-08-11.json"))

# THE THREE LEVELS, per second. sec["secs"] is 23,400 timestamps, 09:30:00 to
# 15:59:59, sec["step"] seconds apart; every array below is parallel to it.
s = d["sec"]
s["flip"]         # MEASURED book: aggressor-signed flow, whole book -- what the site draws
s["flip_struct"]  # CONVENTION OI book: cp * open interest, whole book
s["flip_volcp"]   # CONVENTION VOLUME book: cp * cumulative session volume, dte == 0
s["ngv_meas"], s["ngv_conv"], s["ngv_volcp"]    # each book's net gamma at spot, $M per point
s["spot"]

# The majors, same three books, plus the pre-correction whole-book scope kept for audit.
s["cr0"], s["ps0"]                    # measured peaks
s["cr0_oi"], s["ps0_oi"]              # cp*OI peaks, dte == 0
s["cr0_oi_book"], s["ps0_oi_book"]    # cp*OI peaks, whole book
s["cr0_volcp"], s["ps0_volcp"]        # cp*VOL peaks, dte == 0

# THE LADDERS behind them: one frame every d["step"] minutes, d["exp_n"] post-0DTE
# expiries in the book. Strikes are the +-2.5% window around that minute's spot.
f = d["frames"][0]
f["strikes"]
f["g"],   f["v"],  f["c"]     # measured gamma / vanna / charm, whole book
f["go"],  f["vo"], f["co"]    # cp * open interest
f["gv"],  f["vv"], f["cv"]    # cp * cumulative volume
f["g0"],  f["go0"], f["gv0"]  # the same three books, dte == 0 only

# THE SIGN TEST -- section 5b above, and the one check that needs no repricing.
# For any cp-signed book the net-gamma curve rises with spot, so net gamma below
# zero at spot forces the level ABOVE spot. Run it on any feed that publishes a
# level, a spot and a net gamma; a series that scores near 0.5 is not the series
# that level came from.
for name, ngv, lvl in (("measured", s["ngv_meas"],  s["flip"]),
                       ("cp*OI",    s["ngv_conv"],  s["flip_struct"]),
                       ("cp*VOL",   s["ngv_volcp"], s["flip_volcp"])):
    ok = [(g < 0) == (l > p) for g, l, p in zip(ngv, lvl, s["spot"])
          if g is not None and l is not None and p is not None]
    print(name, len(ok), round(sum(ok) / len(ok), 3))

# The whole-day difference between two books, the way this post joins them:
import statistics as st
pair = [(a - b) for a, b in zip(s["flip_struct"], s["flip_volcp"])
        if a is not None and b is not None]
print(len(pair), round(st.mean(pair), 2), round(st.median(pair), 2))

The other side of the join — a third-party feed's published snapshots — is not ours to redistribute; that half has to be collected by whoever wants to repeat the comparison, at whatever cadence the feed offers.

Rebuilding the books themselves from raw ticks is reproducible but not free: they are computed from licensed ThetaData SPXW trade and NBBO tick data, and redoing it from scratch means subscribing to that feed. The published session JSON above is free and carries the finished books, which is enough for every number in this post except the third-party column. Related: how much of the 0DTE book is opened intraday and therefore invisible to any OI convention is measured in the daily-turnover study, and how often these levels hold in the hold-rate study.

Part of gex.live research. Measured on the free session archive; every session is free to replay.