If you've tried building a 0DTE options dashboard from raw market data, you know the pain: intraday chain snapshots, Greeks recomputation every few minutes, pin risk models, gamma aggregation, and infrastructure that needs to run reliably during market hours.
One endpoint replaces all of it: GET /v1/exposure/zero-dte/{symbol}. Pin risk scoring, expected move, gamma acceleration, dealer hedging scenarios, vol context, flow data, and per-strike breakdown. One JSON response.
Why 0DTE Is a Different Engineering Problem
Longer-dated options are stable. Greeks change slowly, hedging flows are predictable, and polling every 15-30 minutes is fine. 0DTE breaks every one of these assumptions.
| Property | 7DTE | 0DTE (Morning) | 0DTE (Last Hour) |
|---|---|---|---|
| ATM Gamma | 1x (baseline) | 2-3x | 8-15x |
| Theta Decay / Hour | Slow, linear | Moderate | 4-5x morning rate |
| Expected Move | Stable | Shrinks 30-40% by 2 PM | Shrinks 70-80% by 3:30 PM |
| Pin Risk | Negligible | Building | Dominant |
| Dealer Hedging per $1 Move | Moderate | Large | Massive |
Everything changes fast. A 0DTE dashboard showing stale data is worse than no dashboard. You need an API that recomputes continuously through the session.
Quick Start
Install
pip install flashalpha
Python SDK
from flashalpha import FlashAlpha
fa = FlashAlpha("YOUR_API_KEY")
dte = fa.zero_dte("SPY")
print(f"Gamma regime: {dte['regime']['label']}")
print(f"Expected move: +/-{dte['expected_move']['expected_move_pct']:.2f}%")
print(f"Pin score: {dte['pin_risk']['pin_score']}/100")
print(f"Magnet strike: {dte['pin_risk']['magnet_strike']}")
cURL
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY"
# Custom strike range (default 3% around spot)
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY?strike_range=0.05"
JavaScript
const resp = await fetch(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
{ headers: { "X-Api-Key": "YOUR_API_KEY" } }
);
const dte = await resp.json();
console.log(`Regime: ${dte.regime.label}`);
console.log(`Pin score: ${dte.pin_risk.pin_score}`);
console.log(`Expected move: ${dte.expected_move.expected_move_pct}%`);
What You Get Back
One call returns everything for a complete 0DTE monitor:
| Section | Key Fields | What It Tells You |
|---|---|---|
| Regime |
label, gamma_flip, net_gex
|
Positive gamma (mean-revert) or negative (trending)? |
| Expected Move |
expected_move_pct, upper_bound, lower_bound
|
Real-time intraday range, shrinks through the day |
| Pin Risk |
pin_score (0-100), magnet_strike, oi_concentration_top3_pct
|
Is price pinned? Where's the magnet? |
| Hedging |
spot_up_half_pct, spot_down_half_pct, spot_up_1pct, spot_down_1pct
|
Dealer share counts & notional at ±0.5% and ±1% moves |
| Decay |
theta_per_hour_remaining, gamma_acceleration, charm_description
|
How fast is premium bleeding? Gamma accel vs 7DTE |
| Vol Context |
iv_ratio_0dte_7dte, vanna_interpretation
|
Is 0DTE IV cheap or expensive vs term structure? |
| Flow |
net_call_premium, net_put_premium, put_call_ratio
|
Directional sentiment from order flow |
| Per-Strike | Array of {strike, call_gex, put_gex, net_gex, call_oi, put_oi, call_volume, put_volume}
|
Strike-by-strike breakdown for heatmaps |
| Exposures |
pct_of_total_gex, zero_dte_net_gex, full_chain_net_gex
|
How much of total GEX comes from 0DTE? |
Building the Dashboard Panels
A. Expected Move Gauge
The expected move shrinks in real-time through the day. At 9:30 AM it might be ±0.65%. By 2 PM it's ±0.34%. By 3:45 PM it could be ±0.08%.
em = dte["expected_move"]
hours_left = dte["time_to_close_hours"]
print(f"Expected Move: +/-{em['expected_move_pct']:.2f}%")
print(f"Range: {em['lower_bound']:.2f} - {em['upper_bound']:.2f}")
print(f"Time to Close: {hours_left:.1f} hours")
# Normalize for gauge visualization
gauge_fill = em["expected_move_pct"] / 0.65 # morning baseline
B. Pin Risk Score
The pin score (0-100) composites OI concentration, magnet proximity, time remaining, and gamma magnitude. Above 70 with less than 2 hours to close is a strong pinning signal.
pin = dte["pin_risk"]
print(f"Pin Score: {pin['pin_score']}/100")
print(f"Magnet Strike: {pin['magnet_strike']}")
print(f"Distance: {pin['distance_to_magnet_pct']:.2f}%")
print(f"Top 3 OI Concentration: {pin['oi_concentration_top3_pct']}%")
if pin["pin_score"] > 70 and hours_left < 2:
print("HIGH PIN RISK - price gravitates to magnet strike")
C. Gamma Acceleration Zone
The gamma_acceleration field shows how much more gamma 0DTE contracts carry vs equivalent 7DTE strikes. Above 3x, hedging flows start to dominate the tape.
decay = dte["decay"]
regime = dte["regime"]
print(f"Gamma Acceleration: {decay['gamma_acceleration']}x vs 7DTE")
print(f"Charm: {decay['charm_description']}")
if regime["label"] == "positive_gamma" and decay["gamma_acceleration"] > 3:
print("Strong mean-reversion - dealers absorb moves aggressively")
elif regime["label"] == "negative_gamma" and decay["gamma_acceleration"] > 3:
print("DANGER ZONE - dealer hedging amplifies moves, breakout risk elevated")
D. Dealer Hedging Estimates
What happens if SPY moves ±0.5% or ±1%? Share counts and notional values for each scenario.
hedging = dte["hedging"]
scenarios = [
("SPY +0.5%", hedging["spot_up_half_pct"]),
("SPY -0.5%", hedging["spot_down_half_pct"]),
("SPY +1.0%", hedging["spot_up_1pct"]),
("SPY -1.0%", hedging["spot_down_1pct"]),
]
print(f"{'Scenario':<12} {'Dir':<6} {'Shares':>12} {'Notional':>16}")
print("-" * 50)
for label, h in scenarios:
print(f"{label:<12} {h['direction']:<6} "
f"{h['dealer_shares_to_trade']:>12,} "
f"${h['notional_usd']:>14,.0f}")
In positive gamma, dealers sell rallies and buy dips (dampening). In negative gamma, they do the opposite (amplifying). The notional values tell you how much liquidity dealers add or remove at each price level.
E. Per-Strike GEX Heatmap
The per_strike array gives you everything for a heatmap or bar chart:
for strike in dte["per_strike"]:
bar_len = int(abs(strike["net_gex"]) / 10_000_000)
sign = "+" if strike["net_gex"] > 0 else "-"
print(f"{strike['strike']:.0f} | {sign}{'█' * bar_len} | "
f"Net GEX: ${strike['net_gex']:,.0f}")
F. Vol Context
Compare 0DTE IV against the term structure:
vol = dte["vol_context"]
print(f"0DTE/7DTE IV Ratio: {vol['iv_ratio_0dte_7dte']:.2f}")
print(f"Vanna: {vol['vanna_interpretation']}")
if vol["iv_ratio_0dte_7dte"] > 1.0:
print("Event premium - 0DTE IV elevated vs term structure")
Below 1.0 is normal. Above 1.0 means 0DTE has event premium (FOMC, CPI, intraday stress). Above 1.15 means 0DTE premium selling has edge.
Intraday Polling Pattern
Poll more frequently as expiration approaches:
import time
from datetime import datetime, time as dtime
from flashalpha import FlashAlpha
fa = FlashAlpha("YOUR_API_KEY")
def get_poll_interval(hours_left):
if hours_left > 4: return 900 # 15 min (morning)
elif hours_left > 2: return 600 # 10 min (midday)
elif hours_left > 1: return 300 # 5 min (afternoon)
else: return 180 # 3 min (last hour)
def is_market_hours():
now = datetime.now()
return dtime(9, 30) <= now.time() <= dtime(16, 0)
while is_market_hours():
dte = fa.zero_dte("SPY")
hours_left = dte["time_to_close_hours"]
# Update your dashboard panels here
update_expected_move(dte["expected_move"])
update_pin_risk(dte["pin_risk"])
update_gamma_zone(dte["decay"], dte["regime"])
update_hedging_chart(dte["hedging"])
update_gex_heatmap(dte["per_strike"])
# Alerts
if dte["pin_risk"]["pin_score"] > 70 and hours_left < 2:
send_alert(f"HIGH PIN: magnet at {dte['pin_risk']['magnet_strike']}")
if (dte["regime"]["label"] == "negative_gamma"
and dte["exposures"]["pct_of_total_gex"] > 50):
send_alert("NEGATIVE GAMMA DOMINANT: breakout risk elevated")
time.sleep(get_poll_interval(hours_left))
At this cadence you'll use 60-80 requests per symbol per day. Well within Growth's 2,500 daily limit. Scan 8 symbols and you're still under 640/day.
Multi-Symbol 0DTE Scanner
Scan multiple symbols and rank by pin risk, gamma acceleration, or expected move:
from flashalpha import FlashAlpha
import concurrent.futures
fa = FlashAlpha("YOUR_API_KEY")
symbols = ["SPY", "QQQ", "IWM", "AAPL", "TSLA", "NVDA", "AMZN", "META"]
def scan(symbol):
try:
dte = fa.zero_dte(symbol)
if dte.get("no_zero_dte"):
return None
return {
"symbol": symbol,
"pin": dte["pin_risk"]["pin_score"],
"gamma_x": dte["decay"]["gamma_acceleration"],
"em": dte["expected_move"]["expected_move_pct"],
"regime": dte["regime"]["label"],
"pct": dte["exposures"]["pct_of_total_gex"],
"magnet": dte["pin_risk"]["magnet_strike"]
}
except Exception:
return None
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
results = list(filter(None, pool.map(scan, symbols)))
results.sort(key=lambda x: x["pin"], reverse=True)
print(f"{'Sym':<6} {'Pin':>4} {'GamX':>5} {'EM%':>6} {'Regime':<16} {'0DTE%':>6} {'Magnet':>8}")
print("-" * 55)
for r in results:
print(f"{r['symbol']:<6} {r['pin']:>4} {r['gamma_x']:>4.1f}x "
f"{r['em']:>5.2f}% {r['regime']:<16} "
f"{r['pct']:>5.1f}% {r['magnet']:>8.0f}")
MCP Server for AI Agents
If you're building AI agents that need 0DTE context, FlashAlpha provides an MCP server. Configure it in Claude, Cursor, or any MCP-compatible tool:
{
"mcpServers": {
"flashalpha": {
"url": "https://lab.flashalpha.com/mcp",
"headers": {
"X-Api-Key": "YOUR_API_KEY"
}
}
}
}
Once connected, your agent can query conversationally: "What's the pin risk for SPY right now?" or "Scan SPY, QQQ, and IWM for the highest 0DTE pin score." The MCP server exposes the same endpoints as REST. The agent gets structured JSON, interprets it, and responds with actionable analysis.
Supporting Endpoints
Levels (Free tier)
levels = fa.exposure_levels("SPY")
print(f"Gamma Flip: {levels['levels']['gamma_flip']}")
print(f"Call Wall: {levels['levels']['call_wall']}")
print(f"Put Wall: {levels['levels']['put_wall']}")
print(f"0DTE Magnet: {levels['levels']['zero_dte_magnet']}")
Overlay these on your price chart. The zero_dte_magnet is the strike with the highest 0DTE GEX, acting as an intraday attractor.
GEX with Expiration Filter (Free tier)
# 0DTE GEX only, without needing the full zero-dte endpoint
gex = fa.gex("SPY", expiration="2026-03-29")
for strike in gex["strikes"]:
print(f"{strike['strike']}: net_gex=${strike['net_gex']:,.0f}")
This gives you per-strike 0DTE gamma on the free tier. You don't get pin risk, expected move, or hedging scenarios (those require Growth), but it's enough to see where 0DTE gamma is concentrated.
Why Not Build It Yourself?
Building this from raw data requires solving five problems simultaneously:
Intraday chain snapshots. Real-time chain data costs $500-2,000/mo from exchange vendors. Most free sources give end-of-day only.
Greeks recomputation. Every snapshot requires recalculating delta, gamma, theta, vanna, charm for every strike. For SPY with 100+ 0DTE strikes, that's thousands of BSM evaluations per snapshot.
Pin risk models. Not just max pain. Composites OI concentration, magnet proximity, time remaining, gamma magnitude, and historical pinning behavior.
Gamma aggregation. Summing exposure across strikes with dealer positioning assumptions that need validation against actual market behavior.
Infrastructure. Must run reliably during market hours. Exchange outages, stale quotes, cache invalidation, latency management.
The zero-dte endpoint does all of this server-side. You get JSON. You build the UI.
Pricing
| Plan | Price | Daily Requests | 0DTE Access |
|---|---|---|---|
| Free | $0 | 10 | GEX with expiration filter, levels |
| Growth | $299/mo | 2,500 | Full zero-dte endpoint + all exposure endpoints |
| Alpha | $1,199/mo annual | Unlimited | + SVI surfaces, advanced vol, zero cache |
Free tier lets you prototype with filtered GEX. Growth unlocks the full zero-dte endpoint with pin risk, expected move, hedging scenarios, and decay analytics.
- Get API access — free tier, no credit card
-
Python SDK:
pip install flashalpha - Full 0DTE endpoint docs
- GitHub examples
Need a custom computation or field added? Email support@flashalpha.com. You're talking to the engineer who built it. Turnaround is hours, not weeks.
Related reading:
Top comments (0)