DEV Community

tomasz dobrowolski
tomasz dobrowolski

Posted on • Originally published at flashalpha.com

Quantifying Dealer Positioning with GEX, VEX, and CHEX — A Developer's Guide

Every time an options market maker sells you a call or put, they inherit Greek exposure they didn't ask for. To stay delta-neutral, they must hedge — and that hedging creates mechanical, involuntary flows that move the underlying.

This isn't sentiment analysis. It's physics.

I've been building tools to quantify these flows programmatically. Here's the framework — with code you can run today.

What GEX Actually Measures

Gamma Exposure (GEX) per strike:

GEX_k = Γ_k × OI_k × 100 × S
Enter fullscreen mode Exit fullscreen mode

Where Γ is option gamma, OI is open interest, S is spot price. The sign convention:

  • Call OI → positive dealer gamma (customers buy calls → dealers short calls → dealers are long gamma)
  • Put OI → negative dealer gamma (customers buy puts → dealers short puts → dealers are short gamma)

Positive net GEX: Dealers buy dips, sell rallies. Dampens moves. Compresses realized vol.

Negative net GEX: Dealers sell into declines, buy into rallies. Amplifies moves. Expands realized vol.

GEX tells you how much price will move, not which direction.

Pulling Per-Strike GEX Data

The /v1/exposure/gex/{symbol} endpoint returns per-strike gamma exposure with call/put decomposition. Available on the free tier.

import requests

API_KEY = "YOUR_API_KEY"
BASE    = "https://lab.flashalpha.com"
HEADERS = {"X-Api-Key": API_KEY}

r = requests.get(f"{BASE}/v1/exposure/gex/SPY", headers=HEADERS)
data = r.json()

print(f"SPY @ ${data['underlying_price']:.2f}")
print(f"Net GEX: ${data['net_gex']:,.0f} ({data['net_gex_label']})")
print(f"Gamma Flip: ${data['gamma_flip']:.2f}")

# Top strikes by absolute GEX
strikes = sorted(
    data["strikes"], 
    key=lambda x: abs(x["net_gex"]), 
    reverse=True
)[:10]

for s in strikes:
    print(f"  ${s['strike']:>7.0f}  "
          f"call={s['call_gex']:>12,}  "
          f"put={s['put_gex']:>12,}  "
          f"net={s['net_gex']:>12,}")
Enter fullscreen mode Exit fullscreen mode

Key Levels: Where Flows Concentrate

The /v1/exposure/levels/{symbol} endpoint (also free) extracts the critical levels:

r = requests.get(f"{BASE}/v1/exposure/levels/SPY", headers=HEADERS)
lvl = r.json().get("levels", r.json())

print(f"Gamma Flip:        ${lvl['gamma_flip']:.2f}")
print(f"Call Wall:         ${lvl['call_wall']:.2f}")
print(f"Put Wall:          ${lvl['put_wall']:.2f}")
print(f"Max Positive GEX:  ${lvl['max_positive_gamma']:.2f}")
print(f"0DTE Magnet:       ${lvl['zero_dte_magnet']:.2f}")
Enter fullscreen mode Exit fullscreen mode
Level What It Means How to Use It
Gamma flip Net dealer gamma crosses zero The most important level. Above = dampened. Below = amplified.
Call wall Peak positive gamma Mechanical resistance — dealers sell rallies here.
Put wall Peak negative gamma Mechanical support — dealers buy dips here.
Max gamma Strongest dampening Price magnet. Expiration pinning is strongest here.
0DTE magnet Intraday pin from same-day options Relevant for SPY/QQQ intraday structures.

Beyond GEX: Second-Order Flows

Here's where most GEX analysis stops — and where quantitative positioning analysis starts. Dealers also hedge against volatility changes (vanna) and time decay (charm). These flows are often larger than gamma hedging.

VEX: Vanna Exposure

Vanna = ∂Δ/∂σ — sensitivity of delta to implied volatility.

When IV drops, negative vanna shifts dealer delta → dealers buy shares → supports price → IV drops further. Virtuous cycle for short vol.

When IV rises, the reverse: dealers sell shares → price falls → IV rises further. Vicious cycle.

r = requests.get(f"{BASE}/v1/exposure/vex/SPY", headers=HEADERS)
vex = r.json()
print(f"Net VEX: ${vex['net_vex']:,.0f}")
Enter fullscreen mode Exit fullscreen mode

CHEX: Charm Exposure

Charm = ∂Δ/∂t — sensitivity of delta to time. Even with no price or vol movement, time passing shifts delta and forces dealer rebalancing. This is the primary driver of 0DTE intraday behavior and end-of-day drift.

r = requests.get(f"{BASE}/v1/exposure/chex/SPY", headers=HEADERS)
chex = r.json()
print(f"Net CHEX: ${chex['net_chex']:,.0f}")
Enter fullscreen mode Exit fullscreen mode

All four exposure endpoints (GEX, DEX, VEX, CHEX) are free tier for per-strike data.

Building a Regime Classifier

Combining GEX sign + distance from gamma flip + VEX alignment gives you a multi-dimensional regime model:

def classify_regime(gex_data, vex_data, levels_data, spot):
    net_gex = gex_data.get("net_gex", 0)
    net_vex = vex_data.get("net_vex", 0)
    flip    = levels_data.get("gamma_flip", spot)

    dist = (spot - flip) / spot * 100 if spot > 0 else 0
    gamma_regime = "positive" if net_gex > 0 else "negative"

    # Vanna aligned = supports the gamma regime
    vanna_aligned = (gamma_regime == "positive" and net_vex < 0)

    # Confidence based on distance from flip
    if abs(dist) > 2.0:
        confidence = "high"
    elif abs(dist) > 0.5:
        confidence = "moderate"
    else:
        confidence = "low"  # regime could flip

    return {
        "regime": gamma_regime,
        "vanna_aligned": vanna_aligned,
        "dist_to_flip": dist,
        "confidence": confidence,
    }
Enter fullscreen mode Exit fullscreen mode
Gamma Vanna Confidence What It Means
Positive Aligned High Strongly dampened. Premium selling paradise.
Positive Adverse Any Dampened by gamma, but vanna working against. Watch for vol shift.
Negative Either Moderate+ Amplified moves. Defined risk only. Cascades possible.
Any Any Low Near the flip. Reduce exposure or wait.

Multi-Symbol Dashboard

The real power is screening across your universe:

UNIVERSE = ["SPY", "QQQ", "IWM", "AAPL", "TSLA", 
            "NVDA", "META", "AMZN", "AMD", "GOOGL"]

for sym in UNIVERSE:
    gex = requests.get(f"{BASE}/v1/exposure/gex/{sym}", 
                       headers=HEADERS).json()

    # Alpha tier: add VRP intelligence
    vrp = requests.get(f"{BASE}/v1/vrp/{sym}", 
                       headers=HEADERS).json()

    spot = gex["underlying_price"]
    flip = gex["gamma_flip"]
    dist = (spot - flip) / spot * 100
    z    = vrp.get("vrp", {}).get("z_score", 0)

    pos = gex["net_gex"] > 0
    high = z >= 1.0

    if pos and high:     cell = "A"  # Premium Paradise
    elif not pos and high: cell = "B"  # Tempting Trap
    elif pos:            cell = "C"  # Grind
    else:                cell = "D"  # No Trade

    print(f"{sym:<6} Cell {cell}  "
          f"dist={dist:+.1f}%  z={z:+.2f}")
Enter fullscreen mode Exit fullscreen mode

The Bridge: From GEX to VRP

This is the critical distinction:

Free tier (GEX/DEX/VEX/CHEX) tells you what dealers are doing.

Alpha tier (VRP analytics) tells you what it means for your trade:

  • VRP z-scores & percentiles — is premium statistically elevated?
  • Directional VRP — is it in puts, calls, or both?
  • GEX-conditioned harvest score — how favorable is this regime for premium selling?
  • Strategy scores — which of 5 common structures scores highest right now?
  • Dealer flow risk (0–100) — how much risk do dealer flows pose?
  • Unlimited API calls — monitor your entire universe without throttling

The daily workflow: screen for VRP signals → classify gamma regime → select structure → set entry at dealer levels → manage with exit rules. Two API calls per symbol. Runs in 5 minutes.

The complete workflow with Kelly sizing, term VRP selection, and the full runnable morning scanner is in the companion article.

Links:

Top comments (0)