0DTE SPY Playbook: 3 Setups Using Live Gamma & Dealer Data
SPY 0DTE: gamma profile, Mon/Wed/Fri schedule, 3 intraday setups, SPY vs SPX, risk management, backed by real-time dealer positioning data.
SPY 0DTE: gamma profile, Mon/Wed/Fri schedule, 3 intraday setups, SPY vs SPX, risk management, backed by real-time dealer positioning data.
SPY isn't just the most liquid ETF - it's the epicenter of 0DTE options flow. On any given Monday, Wednesday, or Friday, SPY 0DTE options account for 40-60% of total SPY options volume. That concentration matters because it means dealer hedging from 0DTE contracts is the primary force driving intraday SPY price action on expiration days.
Several structural features make SPY the default 0DTE vehicle:
The practical consequence: when you pull SPY's 0DTE levels from the API, the call wall, put wall, and gamma flip are precise to the dollar. On SPX, the same levels have $5 granularity - which means the "wall" could be anywhere in a $5 range. For intraday trading where $1-2 matters, SPY's resolution is a meaningful edge.
Unlike SPX (which has daily expirations, Monday through Friday), SPY 0DTE options expire on Monday, Wednesday, and Friday only. This schedule creates a predictable rhythm:
| Day | SPY 0DTE? | Nearest SPY Expiry | Implication |
|---|---|---|---|
| Monday | Yes | Same day | Full 0DTE playbook applies. Gamma accelerates into close. |
| Tuesday | No | Wednesday (1DTE) | No 0DTE gamma spike. Wednesday expiry behaves like a normal short-dated option. |
| Wednesday | Yes | Same day | Mid-week 0DTE. Often highest volume day - dealers rebalance from Monday. |
| Thursday | No | Friday (1DTE) | No 0DTE. Friday's chain starts attracting flow but gamma is muted. |
| Friday | Yes | Same day | Monthly/weekly OpEx overlap. Highest OI, strongest pin risk. |
On Tuesday and Thursday, the API's 0DTE endpoint returns no_zero_dte: true for SPY. Don't force the playbook on non-0DTE days - the gamma mechanics don't apply when the nearest expiry is 1DTE.
import requests
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
if d.get("no_zero_dte"):
print("No SPY 0DTE today - check if it's Tuesday or Thursday")
print("Use full-chain GEX levels instead: /v1/exposure/levels/SPY")
else:
print(f"SPY 0DTE active - expiration: {d['expiration']}")
print(f"Time to close: {d['time_to_close_hours']:.1f} hours")
print(f"Regime: {d['regime']['label']}")
print(f"Gamma flip: ${d['regime']['gamma_flip']}")
print(f"0DTE as % of total GEX: {d['exposures']['pct_of_total_gex']:.0f}%")
Friday SPY 0DTE deserves special attention. It often coincides with weekly or monthly option expirations, stacking OI from multiple series into the same strikes. This creates the highest pin scores of the week - the pin_risk.pin_score on Friday regularly exceeds 60-70, compared to 40-50 on Monday and Wednesday. If you trade pin plays, Friday is your day.
See SPY's live 0DTE levels right now
Gamma flip, call wall, put wall, pin score, dealer hedging - updated in real-time during market hours.
SPY's gamma profile has characteristics you won't find in individual stocks or even QQQ:
SPY OI clusters heavily at round numbers - $580, $585, $590, $595, $600. The $5 increments typically carry 2-5x more OI than the $1 strikes between them. This creates a "picket fence" gamma profile where the walls almost always land on round numbers.
Why this matters: when SPY is trading at $591.50, the dominant gamma forces are the $590 and $595 strikes. The $591 and $592 strikes contribute, but the round numbers carry the bulk of the positioning. This makes SPY's support and resistance levels more predictable than stocks where OI is distributed randomly.
SPY's gamma flip typically sits within 0.5-1.0% of spot price - roughly $3-6 at current levels. This is tighter than most single stocks (where the flip can be 2-3% away) because SPY's OI is so massive that even small imbalances create significant net GEX. The practical implication: SPY regime changes happen quickly. A $3 move can take you from positive to negative gamma, fundamentally changing how dealers interact with price.
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
price = d["underlying_price"]
flip = d["regime"]["gamma_flip"]
call_wall = d["levels"]["call_wall"]
put_wall = d["levels"]["put_wall"]
# SPY-specific: check round number alignment
flip_round = round(flip)
cw_round = round(call_wall)
pw_round = round(put_wall)
print(f"SPY: ${price:.2f}")
print(f"Gamma flip: ${flip} (nearest round: ${flip_round})")
print(f"Call wall: ${call_wall} (nearest round: ${cw_round})")
print(f"Put wall: ${put_wall} (nearest round: ${pw_round})")
print(f"Flip distance: {abs(price - flip) / price * 100:.2f}% from spot")
print(f"Range: ${put_wall} to ${call_wall} (${call_wall - put_wall:.0f} wide)")
Pin risk in SPY is structurally stronger than in other tickers for three reasons:
The Zero-DTE endpoint returns everything you need for the SPY 0DTE playbook in a single API call. Here's how to pull and interpret the key levels specifically for SPY:
import requests
import json
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
price = d["underlying_price"]
regime = d["regime"]
levels = d["levels"]
pin = d["pin_risk"]
em = d["expected_move"]
hedging = d["hedging"]
print("=" * 55)
print(f" SPY 0DTE Dashboard - ${price:.2f}")
print("=" * 55)
# Regime
print(f"\nRegime: {regime['label'].upper()}")
print(f" Gamma flip: ${regime['gamma_flip']}")
if price > regime["gamma_flip"]:
print(f" Spot is ABOVE flip - dealers dampen moves (buy dips, sell rallies)")
else:
print(f" Spot is BELOW flip - dealers amplify moves (sell into drops)")
# Key levels
print(f"\nLevels:")
print(f" Call wall: ${levels['call_wall']} - dealer resistance")
print(f" Put wall: ${levels['put_wall']} - dealer support")
print(f" Range: ${levels['call_wall'] - levels['put_wall']:.0f} wide")
# Pin risk
print(f"\nPin Risk:")
print(f" Pin score: {pin['pin_score']}/100")
print(f" Magnet strike: ${pin['magnet_strike']}")
print(f" Distance to magnet: {pin['distance_to_magnet_pct']:.2f}%")
# Expected move
print(f"\nExpected Move:")
print(f" Remaining 1SD: +/-${em['remaining_1sd_dollars']:.2f}")
print(f" Range: ${em['lower_bound']:.2f} to ${em['upper_bound']:.2f}")
# Dealer hedging - this tells you the fuel behind SPY moves
print(f"\nDealer Hedging (if SPY moves 1%):")
up = hedging["spot_up_1pct"]
down = hedging["spot_down_1pct"]
print(f" +1%: dealers {up['direction']} {abs(up['dealer_shares_to_trade']):,} shares (${abs(up['notional_usd']):,.0f})")
print(f" -1%: dealers {down['direction']} {abs(down['dealer_shares_to_trade']):,} shares (${abs(down['notional_usd']):,.0f})")
The hedging estimates are particularly important for SPY because the notional amounts are so large. When the API shows dealers needing to sell 500,000 shares on a 1% drop, that's $300M+ of mechanical selling - enough to move SPY further and create a feedback loop. These aren't theoretical numbers; they represent the actual hedging flow that market makers execute via their automated systems.
For a deeper dive into how gamma exposure creates these mechanical levels, see the complete GEX explainer.
SPY's 0DTE session follows a predictable intraday pattern driven by the interaction of gamma, theta, and dealer hedging. Understanding the rhythm helps you time entries and manage risk.
The first 30 minutes are noisy. Overnight flow sets initial OI, but the opening burst of volume reshuffles positioning. Levels from the API are establishing - the gamma flip, call wall, and put wall are valid but may shift as the first wave of orders settles.
By 10:00 AM, SPY's 0DTE levels are well-established. Volume has confirmed or adjusted the overnight OI, and the walls are reliable. This is the prime window for gamma fade trades in positive gamma regime - dealers actively buy dips toward the put wall and sell rallies toward the call wall.
SPY volume drops 30-50% during lunch. Realized volatility compresses. This quiet period has specific implications for 0DTE:
This is where 0DTE gets interesting. Theta decay begins its exponential ramp - 2x the morning rate by 1:00 PM, 3.5x by 2:30 PM. For premium sellers, this is the window.
decay.gamma_acceleration - when it crosses 2.5x, the theta/gamma tradeoff favors premium sellers if the regime is positive.Monitor SPY theta decay and gamma acceleration in real-time
The Zero-DTE endpoint updates every field continuously - including theta/hour, gamma acceleration, and regime shifts.
The last hour is where 0DTE gamma goes parabolic. Gamma acceleration hits 6-10x or higher. Every $1 SPY move forces dealer hedging flows that dwarf the morning session. This is simultaneously the highest-reward and highest-risk window of the day.
These setups exploit structural features unique to SPY's 0DTE profile. For the general 0DTE strategy framework, see the complete 0DTE trading guide.
When: SPY is within $1 of a round $5 strike ($585, $590, $595, etc.), pin score > 65, positive gamma regime, after 1:00 PM ET.
SPY pins to round $5 numbers with remarkable consistency. The OI concentration at these strikes is typically 3-5x the surrounding strikes, creating a gravitational pull that strengthens as expiration approaches. The trade is simple: sell premium centered on the round number and let the pin mechanics work.
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
price = d["underlying_price"]
pin = d["pin_risk"]
regime = d["regime"]
hours_left = d["time_to_close_hours"]
# Find nearest $5 round number
nearest_5 = round(price / 5) * 5
# Check if conditions favor the round number pin
if (abs(price - nearest_5) < 1.0 and
pin["pin_score"] > 65 and
regime["label"] == "positive_gamma" and
hours_left < 5):
magnet = pin["magnet_strike"]
em = d["expected_move"]["remaining_1sd_dollars"]
print(f"ROUND NUMBER PIN SETUP")
print(f" SPY: ${price:.2f}")
print(f" Target pin: ${nearest_5} (magnet: ${magnet})")
print(f" Pin score: {pin['pin_score']}/100")
print(f" Distance: ${abs(price - nearest_5):.2f} from round number")
print(f" Expected move: +/-${em:.2f}")
print(f"")
print(f" Trade: Sell ${nearest_5} butterfly")
print(f" Buy 1x ${nearest_5 - 3} put")
print(f" Sell 2x ${nearest_5} straddle")
print(f" Buy 1x ${nearest_5 + 3} call")
print(f" Max profit if SPY closes at ${nearest_5}")
else:
print(f"No round number pin - SPY ${price:.2f}, nearest $5: ${nearest_5}")
print(f" Distance: ${abs(price - nearest_5):.2f}")
print(f" Pin score: {pin['pin_score']}/100")
print(f" Regime: {regime['label']}")
flow.volume_to_oi_ratio exceeds 1.5 and the put/call ratio is heavily skewed (>1.2 or <0.4), aggressive directional flow is overpowering the pin. Also check the pin risk analysis guide for additional failure modes.
When: Positive gamma regime, SPY is near the put wall or call wall during the 11:30 AM - 1:00 PM lunch window, and realized vol has compressed.
During lunch, organic flow dries up and dealer hedging becomes the dominant market force. If SPY has drifted toward a wall during the morning session, the lunch period often produces a mean-reversion move back toward center. The low-volume environment makes dealer hedging flows proportionally larger - they don't need as much buying or selling to push price back.
import requests
from datetime import datetime
import pytz
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
price = d["underlying_price"]
regime = d["regime"]
levels = d["levels"]
hours_left = d["time_to_close_hours"]
# Check if we're in the lunch window (roughly 3-5 hours to close)
et = pytz.timezone("US/Eastern")
now_et = datetime.now(et)
is_lunch = 11 <= now_et.hour <= 13
if regime["label"] == "positive_gamma" and is_lunch:
put_wall = levels["put_wall"]
call_wall = levels["call_wall"]
range_width = call_wall - put_wall
mid = (call_wall + put_wall) / 2
# How far from center?
dist_to_put = (price - put_wall) / range_width
dist_to_call = (call_wall - price) / range_width
if dist_to_put < 0.25:
print(f"LUNCH REVERSAL: SPY ${price:.2f} near put wall ${put_wall}")
print(f" Dealers buying here - low volume amplifies the support")
print(f" Entry: Buy SPY or ${int(put_wall)} call")
print(f" Target: ${mid:.2f} (midpoint)")
print(f" Stop: Close below ${put_wall - 0.50:.2f} (wall broken)")
elif dist_to_call < 0.25:
print(f"LUNCH REVERSAL: SPY ${price:.2f} near call wall ${call_wall}")
print(f" Dealers selling here - low volume amplifies the resistance")
print(f" Entry: Short SPY or buy ${int(call_wall)} put")
print(f" Target: ${mid:.2f} (midpoint)")
print(f" Stop: Close above ${call_wall + 0.50:.2f} (wall broken)")
else:
print(f"SPY ${price:.2f} is mid-range - no lunch reversal setup")
else:
print(f"Not in lunch window or not positive gamma - skip")
When: Negative gamma regime, after 2:30 PM ET, SPY approaching or crossing the gamma flip.
The last 90 minutes of trading in negative gamma is when SPY makes its biggest intraday moves. Gamma acceleration is 4-8x, meaning every dollar move forces massive dealer hedging that pushes price further. If SPY breaks through the gamma flip during power hour, the cascade is often swift and violent.
resp = requests.get(
"https://lab.flashalpha.com/v1/exposure/zero-dte/SPY",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
d = resp.json()
price = d["underlying_price"]
regime = d["regime"]
hedging = d["hedging"]
hours_left = d["time_to_close_hours"]
decay = d["decay"]
if regime["label"] == "negative_gamma" and hours_left < 1.5:
flip = regime["gamma_flip"]
down_1pct = hedging["spot_down_1pct"]
up_1pct = hedging["spot_up_1pct"]
print(f"POWER HOUR BREAKOUT - NEGATIVE GAMMA")
print(f" SPY: ${price:.2f}")
print(f" Gamma flip: ${flip}")
print(f" Gamma acceleration: {decay['gamma_acceleration']:.1f}x")
print(f" Hours left: {hours_left:.2f}")
print(f"")
print(f" Hedging fuel:")
print(f" -1% move: dealers {down_1pct['direction']} {abs(down_1pct['dealer_shares_to_trade']):,} shares")
print(f" +1% move: dealers {up_1pct['direction']} {abs(up_1pct['dealer_shares_to_trade']):,} shares")
print(f"")
if price < flip:
print(f" SPY BELOW flip - bearish cascade risk")
print(f" Trade: Buy ATM put or put debit spread")
print(f" Target: put wall ${d['levels']['put_wall']}")
print(f" Stop: SPY reclaims ${flip} (regime change)")
else:
print(f" SPY ABOVE flip - bullish breakout potential")
print(f" Trade: Buy ATM call or call debit spread")
print(f" Target: call wall ${d['levels']['call_wall']}")
print(f" Stop: SPY drops below ${flip} (regime change)")
else:
if regime["label"] != "negative_gamma":
print(f"Regime is {regime['label']} - not a breakout setup")
if hours_left >= 1.5:
print(f"{hours_left:.1f} hours left - too early for power hour breakout")
The power hour breakout is a high-conviction, high-risk setup. SPY can move $2-4 in the last hour on extreme negative gamma days. Use defined-risk structures (debit spreads, not naked options) and accept that timing matters - entering 5 minutes too early can mean being on the wrong side of a whipsaw before the breakout materializes.
Build your own SPY 0DTE scanner
Use the Zero-DTE endpoint to automate these setups. Try it in the interactive playground - no code required.
SPY and SPX track the same index but differ in ways that matter for 0DTE trading. The right choice depends on your strategy, account size, and tax situation.
| Feature | SPY | SPX |
|---|---|---|
| 0DTE schedule | Mon / Wed / Fri | Mon / Tue / Wed / Thu / Fri (daily) |
| Strike spacing | $1 | $5 (some $1 weeklies) |
| Bid-ask spread (ATM) | $0.02 - $0.05 | $0.10 - $0.30 |
| Exercise style | American (can be assigned early) | European (cash-settled at expiry) |
| Settlement | Physical delivery of shares | Cash (no share delivery) |
| Notional per contract | ~$59,000 (100 shares x ~$590) | ~$590,000 (100x ~$5,900 index) |
| Tax treatment (US) | Short-term capital gains | Section 1256: 60% long-term / 40% short-term |
| Gamma magnitude | Higher per-strike (more OI per $1) | Concentrated in $5 increments |
| Pin reliability | Higher (tighter strikes, diversified underlying) | Lower ($5 gaps allow more price wander) |
| Early assignment risk | Yes - ITM 0DTE options can be assigned | None - European exercise |
Many professional 0DTE traders use both: SPY for Mon/Wed/Fri pin plays and gamma fades (where strike precision matters), SPX for Tue/Thu 0DTE and for all premium-selling strategies (where tax treatment and no-assignment benefit outweigh the wider spreads).
SPY's typical daily range is $2-4 (roughly 0.3-0.7%). On high-vol days (CPI, FOMC, geopolitical shocks), this can expand to $6-10. Your position sizing must account for both scenarios.
A simple rule: your maximum loss on any single 0DTE trade should not exceed 1-2% of your trading account. For a $50,000 account, that's $500-1,000 max loss per trade. Structure your trades accordingly:
| Account Size | Max Loss per Trade (1%) | Example Structure |
|---|---|---|
| $25,000 | $250 | 1x $3-wide iron condor on SPY |
| $50,000 | $500 | 2x $3-wide butterflies or 1x $5-wide iron condor |
| $100,000 | $1,000 | 3-5x $3-wide iron condors or 2x $5-wide butterflies |
Certain market conditions break the normal 0DTE mechanics. Sit out on these days:
iv_ratio_0dte_7dte) will typically exceed 1.15 on these days - the market is pricing the event, and your gamma fade or pin play is fighting that pricing.# Pre-trade checklist for SPY 0DTE
def spy_0dte_preflight(data):
issues = []
if data.get("no_zero_dte"):
return False, ["No SPY 0DTE today (Tue/Thu)"]
if data["vol_context"]["iv_ratio_0dte_7dte"] > 1.15:
issues.append(f"Event premium elevated: IV ratio {data['vol_context']['iv_ratio_0dte_7dte']:.2f}x")
if data["exposures"]["pct_of_total_gex"] < 30:
issues.append(f"0DTE only {data['exposures']['pct_of_total_gex']:.0f}% of total GEX - full chain dominates")
if data["regime"]["label"] == "undetermined":
issues.append("No clear gamma regime - positioning is balanced")
if len(issues) == 0:
return True, [f"Clear to trade - regime: {data['regime']['label']}"]
return False, issues
ready, notes = spy_0dte_preflight(d)
print(f"SPY 0DTE: {'GO' if ready else 'NO-GO'}")
for note in notes:
print(f" {'OK' if ready else 'XX'} {note}")
For each of the three setups, define your max loss before entering:
Cross-reference your risk with the vanna and charm guide to understand how second-order effects might accelerate or dampen your P&L in the final hours.
Know your regime before you trade
Positive gamma = fade. Negative gamma = trend. The regime determines everything - check it in real-time before every SPY 0DTE entry.
Every signal and level in this playbook comes from the Zero-DTE API endpoint - a single call to /v1/exposure/zero-dte/SPY. Available on the Growth plan ($299/mo monthly or $2,868/yr billed annually, 2,500 requests/day) and Alpha plan ($1,499/mo monthly or $14,388/yr billed annually, unlimited requests).
SPY Dashboard Get API Access Zero-DTE API Docs Try in Playground
by Tomasz Dobrowolski
by Tomasz Dobrowolski
by Tomasz Dobrowolski
Get tick-by-tick visibility into market shifts with full-chain analytics streaming in real time.
Screen millions of option pairs per second using your custom EV rules, filters, and setups.
Instantly send structured orders to Interactive Brokers right from your scan results.