Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Open-Source GEX Dashboard: How It Works

How an open-source GEX dashboard computes gamma exposure: the per strike formula, the dealer sign convention it assumes, and the limits of the output.

An open-source GEX dashboard turns one option chain snapshot into a gamma exposure profile: a dollar figure at every strike estimating how much option hedging demand shifts for a 1% move in the underlying. The arithmetic is one line of code. The assumption sitting underneath it, that dealers hold every call long and every put short, is never measured anywhere on the chain, and that gap is where most misreadings of the number begin. If you have read what gamma exposure measures, the next question is how the figure gets produced.

How is gamma exposure calculated?

Start with a single contract. Gamma is the rate at which an option's delta changes for a $1 move in the underlying, quoted per share. A standard US equity option covers 100 shares, so the contract's gamma in share terms is the quoted gamma times that multiplier.

A dashboard scales that to dollars and to a percentage move. Five terms go into the per strike figure:

  • gamma: per share gamma for the contract, read from the chain snapshot or computed locally from implied volatility
  • open_interest: contracts still outstanding at that strike and expiry
  • 100: the contract multiplier
  • spot * spot: converts a delta change into a dollar notional change at the current underlying price
  • 0.01: rescales the answer from a $1 move to a 1% move

The per strike figure is gex = gamma * open_interest * 100 * spot * spot * 0.01. Sum it across every strike on the chain, entering calls positive and puts negative, and that total is the headline number. Everything else the dashboard draws, the per strike bars, the cumulative profile, the flip level, and the by expiry breakdown, is a rearrangement of that one sum.

What does a per strike GEX profile look like?

The panel below builds the profile for SPY across every strike inside 1.5% of the underlying's close, over contracts expiring within 30 days.

QuerySPY gamma exposure by strike, latest session, contracts inside 30 days
The exact SQL behind every number
SELECT
    concat('$', toString(round(k)))                                         AS strike,
    round(sumIf(gex, side = 'call') / 1e6, 1)                               AS call_gex_musd,
    round(-sumIf(gex, side = 'put') / 1e6, 1)                               AS put_gex_musd,
    round((sumIf(gex, side = 'call') - sumIf(gex, side = 'put')) / 1e6, 1)  AS net_gex_musd,
    any(snapshot_label)                                                     AS snapshot_label
FROM
(
    SELECT
        toFloat64(strike_price)                                AS k,
        if(lower(option_type) LIKE 'c%', 'call', 'put')        AS side,
        toFloat64(gamma) * toFloat64(volume) * 100
            * pow(toFloat64(underlying_close), 2) * 0.01       AS gex,
        formatDateTime(date, '%b %e, %Y')                      AS snapshot_label
    FROM global_markets.options_greeks
    WHERE underlying_symbol = 'SPY'
      AND date = (SELECT max(date) FROM global_markets.options_greeks WHERE underlying_symbol = 'SPY')
      AND iv_converged = 1
      AND volume > 0
      AND days_to_expiry BETWEEN 0 AND 30
      AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.015
)
GROUP BY k
ORDER BY k
Run this yourself

One substitution matters here, and it is the same substitution many free dashboards make without saying so. The data behind these panels carries per contract greeks and traded volume, not open interest, so every strike is weighted by the session's traded contract volume instead. Those count different things: volume counts the day's trades, open interest counts positions still outstanding at the close. The shape of the curve is the lesson, and the level would move with a true open interest column. The distinction is worth reading in full in options volume versus open interest.

The band holds 23 strikes, running from $758 up to $780, all measured on Aug 6, 2026. At the lowest strike in view the signed net comes to -653.1 million dollars per 1% move. At the highest it comes to 849.4 million. Notice how uneven the bars are from one strike to the next. Round numbers and heavily traded expiries pile up, and the strikes between them barely register.

Is the dealer sign convention measured or assumed?

Nothing in an option chain records who is on which side of a contract. The chain reports strikes, expiries, greeks, and the count of contracts outstanding. It is silent on whether the market maker who quoted them finished the day long or short, and no public feed fills that in.

The standard construction covers the gap with a convention: dealers are assumed long every call and short every put. Calls then enter the sum positive and puts enter negative. That single choice fixes the sign of the headline number, and every reading built on the sign inherits it. Flip the convention and a positive gamma reading becomes a negative one over identical data.

The convention has a rationale. Retail flow leans toward selling covered calls and cash secured puts, and toward buying puts as protection, which leaves a dealer on the other side in roughly the assumed direction. It stays an assumption about ownership applied uniformly to strikes where it is often wrong, including a strike a large institutional call buyer has just filled. A dashboard that prints the number without printing the convention has hidden its most important input.

Where does the gamma sit across expiries?

A GEX figure with no expiry filter adds contracts expiring this afternoon to contracts expiring next quarter and treats the total as one quantity. Gamma concentrates in the front, and the front rolls over every session.

QuerySigned SPY gamma exposure by expiry, next three weeks
The exact SQL behind every number
SELECT
    formatDateTime(expiry_date, '%b %e')                                    AS expiry,
    round(sumIf(gex, side = 'call') / 1e9, 2)                               AS call_gex_bn,
    round(-sumIf(gex, side = 'put') / 1e9, 2)                               AS put_gex_bn,
    round((sumIf(gex, side = 'call') - sumIf(gex, side = 'put')) / 1e9, 2)  AS net_gex_bn
FROM
(
    SELECT
        expiration_date                                        AS expiry_date,
        if(lower(option_type) LIKE 'c%', 'call', 'put')        AS side,
        toFloat64(gamma) * toFloat64(volume) * 100
            * pow(toFloat64(underlying_close), 2) * 0.01       AS gex
    FROM global_markets.options_greeks
    WHERE underlying_symbol = 'SPY'
      AND date = (SELECT max(date) FROM global_markets.options_greeks WHERE underlying_symbol = 'SPY')
      AND iv_converged = 1
      AND volume > 0
      AND days_to_expiry BETWEEN 0 AND 21
)
GROUP BY expiry_date
ORDER BY expiry_date
Run this yourself

Across the 11 expiries inside three weeks, the nearest one, Aug 7, carries a call side of $20.57 billion per 1% move and a signed net of -5.1 billion. The far end of the panel is a fraction of that. A dashboard chart worth reading names its expiry filter on the chart itself.

The zero gamma level, often called the flip, is the underlying price at which the summed profile crosses zero. Producing it properly means repricing the chain at a grid of hypothetical spot prices and recomputing every contract's gamma at each one, since gamma itself moves as spot moves. Many projects skip the repricing and interpolate the crossing straight from the current strike profile. That shortcut is cheaper and it lands at a different price. Check which one a project implements before quoting its flip level.

How much does the profile move from day to day?

Open interest is not a stock that sits still. Contracts expire, new strikes list, and positions open and close through every session.

QueryGross SPY call and put gamma against the net, trailing 60 calendar days
The exact SQL behind every number
SELECT
    toString(d)                                                             AS session_date,
    formatDateTime(d, '%b %e')                                              AS session_label,
    round(sumIf(gex, side = 'call') / 1e9, 2)                               AS call_gex_bn,
    round(-sumIf(gex, side = 'put') / 1e9, 2)                               AS put_gex_bn,
    round((sumIf(gex, side = 'call') - sumIf(gex, side = 'put')) / 1e9, 2)  AS net_gex_bn
FROM
(
    SELECT
        date                                                   AS d,
        if(lower(option_type) LIKE 'c%', 'call', 'put')        AS side,
        toFloat64(gamma) * toFloat64(volume) * 100
            * pow(toFloat64(underlying_close), 2) * 0.01       AS gex
    FROM global_markets.options_greeks
    WHERE underlying_symbol = 'SPY'
      AND date >= today() - 60
      AND iv_converged = 1
      AND volume > 0
      AND days_to_expiry BETWEEN 0 AND 45
)
GROUP BY d
ORDER BY d
Run this yourself

Two things stand out over the 41 sessions in view. The gross sides are large and the net is the difference between them: on Aug 6, the last session in the panel, the call side measured $29.2 billion against a put side of -34.53 billion, for a net of -5.32 billion. A small change in either gross side moves the net a long way. The line is also anything but smooth. The net printed -4.9 billion back on Jun 9, and the panel shows how far it travels between sessions. A GEX chart with no timestamp is decorative.

How is GEX different from max pain?

Both numbers read the same chain and ask different questions. GEX weights each strike by gamma and asks how much hedging demand a 1% move would create. Max pain ignores gamma completely and asks a settlement question: at which price would the aggregate in the money value owed to option holders be smallest at expiry. The panel below builds that curve from the same snapshot, over contracts expiring inside a week.

QueryThe same chain, a different question: in the money value by settlement price
The exact SQL behind every number
WITH chain AS
(
    SELECT
        toFloat64(strike_price)                                             AS k,
        if(lower(option_type) LIKE 'c%', 'call', 'put')                     AS side,
        sum(toFloat64(volume))                                              AS contracts,
        min(abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1)) AS moneyness
    FROM global_markets.options_greeks
    WHERE underlying_symbol = 'SPY'
      AND date = (SELECT max(date) FROM global_markets.options_greeks WHERE underlying_symbol = 'SPY')
      AND volume > 0
      AND days_to_expiry BETWEEN 0 AND 7
    GROUP BY k, side
)
SELECT
    concat('$', toString(round(sk)))          AS settle_price,
    round(sum(multiIf(
        side = 'call' AND sk > k, (sk - k) * contracts * 100,
        side = 'put'  AND sk < k, (k - sk) * contracts * 100,
        0)) / 1e9, 2)                         AS itm_value_bn
FROM (SELECT DISTINCT k AS sk FROM chain WHERE moneyness <= 0.015) AS grid
CROSS JOIN chain
GROUP BY sk
ORDER BY sk
Run this yourself

The curve covers 23 candidate settlement prices. At $758 the aggregate in the money value comes to $0.86 billion, and at $780 it comes to $0.7 billion. The low point of the curve is the max pain strike. It can sit several dollars away from the strike carrying the largest gamma exposure on the same snapshot, and neither point is a forecast of where the underlying settles.

What do you need to run an open-source GEX dashboard?

Most of these projects are small Python packages with a plotting layer on top. What they need from you is short:

  • a chain snapshot with strike, expiry, option type, open interest, and either a vendor gamma or an implied volatility to compute one from
  • the underlying's spot price captured at the same instant as the chain
  • a timestamp, stored alongside the output rather than assumed
  • an expiry filter you choose deliberately
  • a market data key

Install from a tagged release rather than the default branch. git clone --branch <tag> --depth 1 <repo-url> gives you a build someone signed off on, while main gives you whatever was pushed this morning. Work inside a virtual environment: python3 -m venv .venv, activate it, then pip install -r requirements.txt. Read the requires-python line in the project's pyproject.toml first. A package pinned to an older interpreter fails in confusing ways on a newer one.

Market data is the part that is rarely free. A snapshot carrying both open interest and greeks for every contract on a chain is a paid entitlement at most vendors as of August 2026. Free endpoints tend to hand back quotes without open interest, or open interest published the following morning. A profile built on yesterday's open interest is stale in exactly the place the calculation is most sensitive.

What a GEX dashboard cannot know

Real dealer inventory is not published. Firms report positions in aggregate and on a delay, and strike level detail never appears in a public feed at all. A GEX dashboard models hedging demand under an ownership assumption, and it stays a model however precise the arithmetic looks.

Four things sit outside what any chain based build can see:

  • who actually holds each side of each strike
  • positions opened away from the listed market
  • each desk's hedging policy, including desks that hedge on a schedule rather than continuously
  • whether a given open interest print came from opening or closing trades

None of that makes the number useless. It does mean the number carries a stated assumption, and the assumption belongs next to the chart. The same discipline shows up in portfolio level greek work, where beta weighting a portfolio's delta makes the assumption behind an aggregate explicit instead of burying it.

How these panels were computed

Each panel reads daily per contract greeks for SPY on the most recent session available, filtered to converged implied volatility fits and to contracts that traded. Every strike is weighted by that session's contract volume in place of open interest, which this data set does not carry. Gamma dollars use the formula given above, with the underlying's close as spot. The max pain panel drops the greeks entirely and uses strikes, sides, and contract counts only.

FAQ

What is GEX in options trading?

GEX, or gamma exposure, estimates how much option hedging demand changes for a 1% move in the underlying. It multiplies each contract's gamma by open interest, the 100 share multiplier, and the square of spot, then sums across strikes with an assumed sign for calls and puts.

Do you need open interest to calculate GEX?

The standard construction uses open interest, which counts positions still outstanding. Some dashboards substitute traded volume when open interest is unavailable or delayed, and swapping one for the other changes the level of the profile.

What is the zero gamma or gamma flip level?

It is the underlying price at which the summed gamma exposure profile crosses zero. A careful implementation recomputes every contract's gamma at each candidate spot price rather than interpolating from the current strike profile, since gamma moves as spot moves.

Is a positive GEX reading bullish?

The sign describes an assumed hedging posture, not an outcome, and it rests entirely on the convention chosen for who holds calls and puts. Flipping that convention flips the sign over identical data, so the reading describes a modelled position rather than a direction.

Can I build a GEX dashboard without a paid data feed?

You can build and test the calculation on any chain snapshot, including a delayed or partial one. Producing a current profile generally needs a feed carrying open interest and greeks together for the full chain, which most vendors charge for as of August 2026.


Every panel here ships with the exact SQL underneath it. Open one, change the ticker or the expiry window, and run the same calculation yourself on the Strasmore terminal.

#gamma exposure#options#open source#dealer positioning#tools