SOAP2Docs ← Website
SOAP2 top banner

GEX, VEX, TRACE & Hedging Flow: A Tutorial

This page is a from-scratch explanation of the theory, the arithmetic, and the assumptions behind SOAP2's four "dealer-positioning" tools — GEX, VEX, TRACE and Hedging Flow. It assumes you know calculus and probability at the level of a university-entry maths course (derivatives, the normal distribution, logarithms) but nothing about options market microstructure. It is not a click-by-click how-to — for that, see each feature's own user guide linked below — this page exists to explain why the numbers are what they are, and where SOAP2's numbers depart from an idealized calculation because of what Interactive Brokers actually gives us.

Read this together with the individual guides for UI-level detail and the currently-known open issues on each feature:



1. The big picture: why any of this exists

Every listed option has two sides: a customer (you, another retail trader, a fund) and, typically, a market maker quoting the other side of the trade. Market makers generally do not want to carry directional risk — they want to earn the bid/ask spread, not bet on where the underlying goes. So after taking on an option position, a market maker (a "dealer") typically hedges it by trading the underlying stock or index future, in a quantity set by the option's Delta (its first-order sensitivity to the underlying price). This is the standard "delta-hedging" practice covered in any options textbook.

The four tools on this page are all downstream of one idea: if you can estimate how dealers are positioned in aggregate, you can estimate the hedging flow that positioning forces on them as the market moves — and that flow itself can influence price. This is a two-step inference chain (open interest → assumed dealer position → hedging behaviour), not a direct observation, and every limitation discussed later in this page traces back to one of those two steps being estimated rather than known.


2. A Greeks refresher, with the actual formulas SOAP2 uses

SOAP2's own Black-Scholes engine (BS.cs) is what computes Delta/Gamma/Vanna whenever IB doesn't supply them directly, and it's also literally what TRACE's sweep and GEX's Flip-level sweep run internally (see §4 and §6). All of it rests on the standard Black-Scholes-Merton setup with continuous dividend yield q:

$$ d_1 = \frac{\ln(S/K) + (r - q + \tfrac12\sigma^2)T}{\sigma\sqrt{T}} \qquad d_2 = d_1 - \sigma\sqrt{T} $$

where S = underlying price, K = strike, T = time to expiry in years, r = risk-free rate, q = dividend yield, σ = implied volatility. N(x) is the standard normal CDF and φ(x) = \frac{1}{\sqrt{2\pi}}e^{-x^2/2} its density (the PDF).

Delta

The rate of change of the option's price with respect to a $1 move in the underlying:

$$ \Delta_{\text{call}} = e^{-qT} N(d_1) \qquad \Delta_{\text{put}} = e^{-qT} N(d_1) - 1 $$

Delta is what tells a dealer how many shares of the underlying offset one option contract. It runs from 0 (far out-of-the-money, negligible chance of finishing in the money) to 1 for calls / −1 for puts (deep in-the-money, behaves like the stock itself).

Gamma

The rate of change of Delta itself as the underlying moves — the option's convexity. Calls and puts at the same strike/expiry have identical Gamma (a fact that matters below):

$$ \Gamma = \frac{e^{-qT}\,\varphi(d_1)}{S\,\sigma\sqrt{T}} $$

Gamma peaks for options that are near-the-money and close to expiry — which is exactly why 0DTE options dominate short-term dealer hedging behaviour: a small move in the underlying near expiry can flip Delta from near 0 to near 1 almost instantly, forcing a correspondingly large hedge adjustment.

Vanna

The cross-sensitivity of Delta to implied volatility (equivalently, of Vega to the underlying price):

$$ \text{Vanna} = \text{Vega} \left(1 - \frac{d_1}{\sigma\sqrt{T}}\right)\Big/ S, \qquad \text{Vega} = \frac{S\sqrt{T}\,\varphi(d_1)}{100} $$

(the /100 on Vega is just SOAP2's convention of quoting Vega per 1 percentage point of IV rather than per unit of IV — Vanna is derived from that same scaling, matching BS.OptionVanna in the codebase.) Like Gamma, Vanna is the same for a call and a put at one strike and expiry, but unlike Gamma it changes sign with moneyness (positive for out-of-the-money calls, negative for in-the-money ones) — it's what makes a sudden implied-vol move (an IV "crush" after an earnings report or FOMC, or a spike into a selloff) translate into forced stock buying or selling even when spot itself hasn't moved.


3. The one hypothesis everything else is built on

Open interest (OI) tells you how many contracts exist at a strike. It does not tell you who is long and who is short, and it certainly doesn't tell you whether the market maker on the other side of a trade is still holding that position unhedged, fully hedged, or has since laid it off to someone else. GEX/VEX/ TRACE/Hedging Flow all adopt the same standard simplifying convention used across the retail dealer-flow literature (SqueezeMetrics' original GEX metric, and its many derivatives):

This produces the familiar sign convention: call open interest contributes positive Gamma exposure, put open interest negative — even though a call and a put at the identical strike/expiry have the same Gamma value, per the formula in §2. The sign comes entirely from the assumed direction of the dealer's position, not from the option's own mathematics. (For a gentler build-up of this idea, see Gamma & Vanna: The Idea Behind GEX and VEX.) Because this is a convention and not a law of physics, different data providers flip it differently — which is precisely why SOAP2 ships sign-flip toggles rather than asserting one convention is "correct."

Every number described below is therefore a model estimate conditioned on this hypothesis being true for the day/strike in question — not a measurement of what dealers actually hold. Large directional institutional trades, a dealer base that happens to be positioned the other way round, or unusually thin open interest can all make any of these tools point the wrong way for a given session.


4. GEX — Gamma Exposure

The per-strike formula

SOAP2's GEX (ported from the "GEX3.1" reference implementation) computes, for every strike in the visible range:

$$ \text{CallGEX} = \Gamma_{\text{call}} \times \text{OI}_{\text{call}} \times S^2, \qquad \text{PutGEX} = -\,\Gamma_{\text{put}} \times \text{OI}_{\text{put}} \times S^2 $$

$$ \text{NetGEX} = \sum_{\text{strikes}} \left(\text{CallGEX} + \text{PutGEX}\right) \Big/ 10^{9} $$

where Γ here is the Gamma per share (i.e. before SOAP2's own contract multiplier is applied — see §8 on why that division matters), OI is contracts open at that strike, and S is the current underlying price. Squaring S converts a per-share, per-$1-move sensitivity into a dollar-notional exposure that scales correctly with the price level of the underlying — this is the standard "dollar Gamma" trick used across the industry, not something SOAP2 invented. Dividing by 109 just converts the result to a human-readable "$ billions" unit.

Worked numeric feel: Suppose SPX is at 5,000, and the 5,000-strike call has Gamma Γ = 0.0009 per share (a typical near-the-money value for a several-week expiry) with 20,000 contracts of open interest. CallGEX ≈ 0.0009 × 20,000 × 5,000² = 0.0009 × 20,000 × 25,000,000 = 450,000,000 ($450mm) at that one strike. Summing signed contributions like this across every strike in the chain, and adding the equivalent put side with a negative sign, is what produces the Net GEX bar chart and the aggregate Net GEX figure.

Why the "Flip" level needs a second calculation, not just the bar chart

The bar chart above answers "how much Gamma sits at each strike, at today's actual spot price." The Flip level (where aggregate dealer Gamma is expected to cross from positive to negative) requires answering a different question: "what would total dealer Gamma look like at every other hypothetical spot price?" — because Gamma itself depends on S through d₁, moving the hypothetical spot changes every single strike's Gamma, not just which strike is nearest the money.

SOAP2 does this with an explicit spot-price sweep: it holds every strike's own IV, time to expiry, rate and dividend yield fixed at their currently-observed values, then walks a hypothetical spot price across the configured strike range (spaced at half the strike interval) and recomputes total signed Gamma — using the Black-Scholes Γ formula from §2 directly, not the live IB Gamma tick — at each hypothetical spot:

$$ G(S_{\text{hyp}}) = \sum_{\text{strikes}} \Big[\Gamma_{BS}(S_{\text{hyp}}, K,\dots)\,\text{OI}_{\text{call}} - \Gamma_{BS}(S_{\text{hyp}}, K,\dots)\,\text{OI}_{\text{put}}\Big] \times S_{\text{hyp}}^2 $$

The resulting curve's zero-crossing nearest the actual current spot is the Flip level; its trough and peak are the "Min"/"Max" gamma-wall markers. This sweep — not the bar chart — is exactly what "GEX3.1.py" does, and SOAP2 mirrors it because there's no shortcut: you cannot read a slope-dependent quantity like a sign-crossing off a single-spot snapshot.

Attractor vs. repellor, in one paragraph

When aggregate dealer Gamma is positive (typically above Flip), dealers are net long Gamma: as price rises their hedge means selling into strength, and as price falls they buy back into weakness. That dampens volatility and tends to pin price near large open-interest strikes — an "attractor." When aggregate Gamma is negative (typically below Flip), the hedge direction reverses: dealers sell into declines and buy into rallies, which is pro-cyclical and amplifies moves rather than damping them — a "repellor." See the GEX/VEX user guide for a longer treatment with real-world framing (OPEX pinning, vol spirals).


5. VEX — Vanna Exposure

Same open-interest-weighted-exposure idea as GEX, but using Vanna instead of Gamma, and with no separate sweep (VEX has no Flip-style level computed in SOAP2 — see the caveat in the user guide):

$$ \text{CallVEX} = \text{Vanna}_{\text{call}} \times \text{OI}_{\text{call}} \times S^2 / 10, \qquad \text{PutVEX} = \text{Vanna}_{\text{put}} \times \text{OI}_{\text{put}} \times S^2 / 10 $$

with strikes carrying under 5 contracts of open interest zeroed on that side before summing (an explicit "don't let noise strikes move the aggregate" floor, ported from the reference script), and the aggregate scaled by 106 ("$ millions") rather than GEX's billions, purely for readable axis numbers.

Vanna (unlike Gamma) carries a sign that depends on moneyness, and it is identical for a call and a put at the same strike. SOAP2's VEX, following its reference script, does not apply GEX's "negate the puts" step: each contract's Vanna enters with its own natural sign. The buy/sell reading of a VEX bar therefore depends on which side of the trade dealers are assumed to be on, which is why the Flip VEX Sign toggle exists; treat VEX as the less settled of the two.

Intuition for when VEX matters most: around scheduled vol events (CPI, FOMC), month/quarter -end, and OPEX weeks — anytime implied vol itself is about to move sharply rather than spot. A large IV crush into a level with heavy positive net Vanna is the textbook setup people mean by a "vanna crush" rally: vol collapses, dealers who are Vanna-positive there see their Delta shrink without spot having moved, so they buy stock to rebalance back to hedge-neutral — an attractor effect driven by vol, not price.


6. TRACE — the gamma heatmap

GEX gives you one number per strike, evaluated at today's real time-to-expiry. But Gamma is not static — per the formula in §2, it grows sharply as T → 0 for near-the-money strikes. TRACE turns that into a picture: instead of one time-to-expiry, it sweeps a whole range of hypothetical "days remaining" values (independent of the real calendar) and asks, at each one, "if this same open interest still existed with only this many days left, how much Gamma notional would it imply?"

For a swept day-count d (converted to years T_d) and a single contract at strike K with open interest OI:

$$ g = \Gamma_{BS}(S, K, T_d, r, \sigma, q), \qquad \text{notional} = g \times S^2 \times m \times \text{OI} $$

where m is the contract multiplier (100 for SPX/RUT). A contract whose real days-to -expiry is already less than the swept hypothetical day-count contributes nothing to that column (you can't have negative time remaining) — so the heatmap naturally "runs out" of contracts as the swept horizon exceeds what's actually in the chain.

Why each contract is smeared across neighbouring strikes

A raw plot of one spike per real strike (wherever open interest happens to sit) doesn't look anything like the smooth "gamma wall" market commentary describes. TRACE instead spreads each contract's notional across the whole strike axis using a Gaussian kernel centred on its own real strike:

$$ \text{heat}[i, d] \mathrel{+}= \text{notional}(K, d) \times \exp\!\left(-\left(\frac{K_i - K}{w}\right)^2\right) $$

summed over every contract, where K_i is the axis point and w is a smoothing half -width (a percentage of the underlying's own price, so it scales sensibly whether you're looking at a $50 or a $5,000 underlying). This turns a scatter of point masses into a continuous surface — the same qualitative picture ("a wall of gamma spanning a strike neighbourhood, not one exact strike") that GEX's own attractor/ repellor language already describes informally, made explicit as an actual convolution. Contributions beyond a few standard deviations of w are skipped entirely (the kernel is negligibly small there), purely as a performance optimisation — it doesn't change the result, just how much of the strike axis is walked per contract.

The three panels (Calls, Puts, Combined) are the unsigned Call and Put surfaces on a shared colour scale, and their signed difference (Calls − Puts, same convention as GEX's Net) on its own zero-centred scale — again mirroring GEX's own sign convention from §3–4 rather than inventing a new one.


7. Hedging Flow — from exposure to implied trades

GEX, VEX and TRACE all describe positioning — a stock of exposure that exists at a moment (or across a swept range of hypothetical moments). Hedging Flow asks about the flow that positioning implies: as trading happens, how many actual shares are dealers being forced to buy or sell to stay hedged?

The per-trade hedge logic

For a single trade of size N contracts, dealer hedge shares are:

$$ \text{shares} = -\,\text{sign}(\text{customer side}) \times \Delta_{\text{signed}} \times N \times m $$

with the standard four cases spelled out (customer buys a call → dealer short → dealer buys stock; customer sells a call → dealer long → dealer sells stock; and the mirrored pair for puts — see the Hedging Flow guide for the full table). Note this needs two things that no data feed states outright: which side of the trade the customer was on, and the option's Delta at that instant. SOAP2 estimates the former from where a print sits relative to the quoted bid/ask (trading at/near the ask → customer buy; at/near the bid → customer sell — the standard "Lee-Ready"-style quote rule used throughout the market-microstructure literature) and the latter from either live IB Greeks or a Black-Scholes recompute (§2) when IB doesn't supply one directly.

Multiplying signed hedge shares by the underlying price converts a share count into a $-notional, and accumulating that over a session — split into Calls and Puts lines — produces the running cumulative chart. This is the same idea popularised under names like "HIRO": not a single end-of-day number, but a continuously updating read on whether options-driven dealer hedging is currently a net tailwind or headwind for the underlying, separate from whatever "organic" buying/selling is happening in the stock itself.

Three different proxies for "a trade happened"

The formula above needs a trade size and a customer side for every print. SOAP2 has no single data source that supplies both continuously for every symbol/expiry, so it offers three different proxies, each measuring something slightly different (see the full comparison table in the user guide):

None of these is a genuine trade-by-trade tape received live; they're all periodic aggregates standing in for one. §8 explains why, and what that costs in fidelity.


8. SOAP2's implementation compromises (IB vs. a "proper" dataset)

A dedicated, well-funded dealer-flow product (SqueezeMetrics-style, or an institutional OPRA-tape vendor) typically has access to a consolidated tape across every U.S. options exchange, real per-print trade classification data, and sometimes even broker-reported customer/firm flags. SOAP2 has none of that — it has whatever a single retail IB connection provides, plus one delayed CBOE CDN feed for symbols/times an IB subscription doesn't cover live. Every compromise below follows directly from that gap.

8.1 Single-broker Open Interest and Greeks, not a consolidated tape

IB reports the Open Interest and Greeks it has, from whichever exchanges it aggregates for that instrument — not a guaranteed match to CBOE's own published OI, which some competing tools use as their source of truth. All of GEX/VEX/TRACE inherit whatever discrepancy exists between IB's view and any other source's view. This was directly observed once: SOAP2's computed GEX Flip level for SPX 0DTE diverged from an independent third-party reference by a couple of percent of spot, and after ruling out a rendering bug and two other candidate causes by direct calculation, "a real difference in the live open interest / IV SOAP2's feed sees vs. the reference source's" remains the leading (unresolved) hypothesis — see the GEX/VEX guide's Limitations.

8.2 No real trade tape for Hedging Flow — proxies standing in for prints

A "proper" flow product ingests every individual print as it happens and classifies each one. SOAP2's live-IB path instead polls cumulative traded Volume every 1–5 minutes and treats the delta as one aggregate blob — a big single print and many small prints in opposite directions within that window are indistinguishable to the formula in §7. This was a deliberate choice, not an oversight: a fourth, tick-by-tick design (subscribing to every print via reqTickByTickData) was considered and rejected in favour of the coarser poll-diff, to keep the feature within what a normal IB market-data subscription budget can sustain across a whole options chain. The CDN path is coarser still — it stands in trade activity with a once-per-day open-interest change (see 8.3), so successive fetches minutes apart on the same day read as essentially the same number.

8.3 The CBOE delayed feed's Open Interest is frozen intraday

The standalone CDN snapshot forms (used by the CDN tiers of GEX/VEX, TRACE and Hedging Flow) read CBOE's delayed quotes feed. Its Open Interest field has been confirmed static across an entire trading session — only bid/ask/theoretical price/IV genuinely update tick-to-tick on that feed. Any CDN-tier chart is therefore really a same-day-vs-yesterday comparison dressed up as a live chart, not a live positioning read. The live-IB paths inside an open OA don't have this problem — IB's own OI tick does update through the session — which is the main reason the OA tiers exist as a separate, more-work-to-set-up alternative rather than SOAP2 relying solely on the simpler CDN snapshot.

8.4 Dealer side is assumed, never observed

As covered in §3, nothing in the IB feed (or the CBOE CDN feed) states which side of a trade a market maker took, or whether they're still holding a position unhedged, delta-hedged, or laid off elsewhere. Every one of the four tools inherits the standard "customers are net long options, dealers are net short and hedge" convention wholesale from the wider dealer-flow literature — it is a market-wide simplifying assumption, not something SOAP2's implementation could independently verify even with a perfect data feed.

8.5 Day-count convention drift between tiers

SOAP2's own OA-wide Greeks use a calendar-days/360 time convention throughout (matching every other Greek already shown on the OA grid). The CDN-tier forms instead use calendar-days/365, to stay consistent with the already-established CBOE CDN GEX/VEX form's own business-days/262 basis. The two conventions differ by roughly 1.4%, which propagates into sub-1% differences in computed Gamma between an OA-tier and a CDN-tier chart for what is nominally "the same" expiry — small, but real, and the two tiers are not bit-for-bit reconcilable because of it. (SOAP2 tracks four different day-count bases in different parts of the codebase for different historical reasons — see the internal day-count reference if you're trying to reconcile numbers across features.)

8.6 Smoothing width is a carried-over default, not a fitted parameter

TRACE's Gaussian smoothing half-width (§6) is derived from strike spacing and a fixed percentage of the underlying price — a reasonable generalisation of the original single-symbol reference script's hard-coded constant, but not a value that has been calibrated against observed dealer behaviour for any particular symbol. A too-wide kernel over-smooths distinct strikes together; a too-narrow one reverts toward the "scatter of spikes" TRACE was built to avoid. Treat the exact shape of a TRACE surface as directionally informative, not as precisely calibrated.

8.7 No calibration against real fills

Hedging Flow's classification of "how much weight to give a print near the bid vs. the ask vs. the mid" (the quote rule in §7) is the original literature default. It has not been checked against SOAP2's own account executions or any other ground truth, for any of the three data paths. Until that calibration is done, every Hedging Flow number should be read as a hypothesis about direction, not a validated measurement of size.

8.8 Everything here is unreleased or newly released, and largely unverified against live markets

As of this writing, GEX/VEX is live and has been checked once against an independent reference (with the open discrepancy noted in 8.1); TRACE and Hedging Flow are both built, unit-tested for their internal maths, and wired into the OA, but neither has been run through a live trading session and compared against real numbers the way GEX/VEX was. This isn't a data-limitation compromise in the same sense as 8.1–8.7 — it's simply software that hasn't yet accumulated live track record — but it belongs in the same honesty ledger: don't treat TRACE or Hedging Flow numbers as more trustworthy than "plausible and internally consistent" until that changes.


9. Reading the four tools together

A practical way to combine them, keeping every caveat above in mind:

  1. GEX tells you the broad regime for today's actual expiry: are you above or below Flip (mean-reverting vs. accelerating), and where are the big call/put walls.
  2. VEX tells you whether an upcoming vol event (FOMC, CPI, earnings) is likely to add to or fight against that same picture once IV itself moves, independent of spot.
  3. TRACE shows you how today's GEX picture is likely to build as expiry approaches — useful for judging whether a wall is a today-only phenomenon or something that intensifies going into OPEX.
  4. Hedging Flow is the closest of the four to "is this actually happening right now" — a running read on whether the exposure GEX/VEX/TRACE describe is currently translating into real buying or selling pressure, or sitting dormant.

None of the four should be read in isolation as a trading signal — they're all lenses on the same underlying, unverifiable hypothesis (§3), built on a single retail data feed with the specific gaps catalogued in §8. Used together, and cross-checked against price action you can actually observe, they're a reasonable framework for thinking about dealer-driven flow — not a guarantee of what will happen next.


Further reading


Go back to the main help page: SOAP2 Help Guide

⬅ Back