If you’re already familiar with the Live Screener, skip to the recipes. If not, the short version: POST /v1/screener accepts a recursive filter tree, applies it across 20–248 symbols (depending on your plan), and returns a ranked, trimmed result. Filters at the expiries.*, strikes.*, and contracts.* levels cascade inside an AND group, meaning non-matching expiries, strikes, and contracts are pruned from each symbol before the response ships. That’s what makes it a scanner, not just a filter: the response is already shaped like the answer.
Why a 0DTE scanner is different from a 0DTE endpoint
FlashAlpha has a dedicated 0DTE analytics endpoint (GET /v1/exposure/zero-dte/{symbol}) that returns pin risk, expected move, gamma regime, dealer hedging estimates, and theta decay for one symbol at a time. That’s the right tool when you already know which name you’re trading.
A scanner answers a different question: which symbols have 0DTE setups worth looking at right now? Instead of calling the 0DTE endpoint for each of 20 symbols and merging client-side, one screener POST returns only the symbols (and only the contracts within those symbols) that match your criteria.
| Question | Best tool |
| “What’s the pin risk and expected move for SPY today?” | GET /v1/exposure/zero-dte/SPY |
| “Which symbols have high-delta 0DTE calls with OI above 1,000?” | POST /v1/screener |
| “Show me every 0DTE put with delta between -0.3 and -0.5 in positive-gamma names.” | POST /v1/screener |
| “Build a chart of SPY’s 0DTE GEX profile by strike.” | GET /v1/exposure/gex/SPY |
How cascading filters work for 0DTE
The key concept is cascading AND. When you put stock-level, expiry-level, and contract-level conditions inside a single AND group, the server:
- Passes or fails each symbol on the stock-level conditions
- For surviving symbols, removes expiries that don’t match the expiry-level conditions
- For surviving expiries, removes contracts that don’t match the contract-level conditions
- Drops any symbol that has zero surviving contracts
This is exactly what you want for 0DTE scanning. You set expiries.days_to_expiry = 0 to isolate same-day, then layer contract-level conditions on top.
{
"filters": {
"op": "and",
"conditions": [
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 },
{ "field": "contracts.type", "operator": "eq", "value": "C" },
{ "field": "contracts.delta", "operator": "gte", "value": 0.3 },
{ "field": "contracts.oi", "operator": "gte", "value": 1000 }
]
},
"select": ["*"]
}
That returns, for every symbol in your universe: only the 0DTE expiry, only the call contracts, only the ones with delta ≥ 0.3 and OI ≥ 1,000. No other expiries, no puts, no low-delta or low-OI noise.
Five 0DTE scanner recipes
1. 0DTE call seller (high-delta, high-OI calls)
The classic: find 0DTE calls worth selling. Cascading trims to just the tradable contracts.
{
"filters": {
"op": "and",
"conditions": [
{ "field": "regime", "operator": "eq", "value": "positive_gamma" },
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 },
{ "field": "contracts.type", "operator": "eq", "value": "C" },
{ "field": "contracts.delta", "operator": "gte", "value": 0.3 },
{ "field": "contracts.oi", "operator": "gte", "value": 1000 }
]
},
"select": ["*"]
}
The regime = positive_gamma condition means you’re only selling calls where dealers are dampening moves, not amplifying them. This is the core 0DTE premium selling setup: rich theta, friendly regime, liquid contracts.
What comes back:
{
"meta": {
"total_count": 4,
"returned_count": 4,
"universe_size": 248,
"tier": "alpha",
"as_of": "2026-04-06T15:22:10Z"
},
"data": [
{
"symbol": "SPY",
"price": 563.42,
"regime": "positive_gamma",
"zero_dte_net_gex": 1284000000,
"contracts": [
{ "strike": 565, "type": "C", "delta": 0.42, "iv": 0.18, "bid": 1.85, "ask": 1.88, "oi": 14200, "volume": 8340 },
{ "strike": 570, "type": "C", "delta": 0.31, "iv": 0.19, "bid": 0.72, "ask": 0.75, "oi": 9800, "volume": 5120 }
]
},
{
"symbol": "QQQ",
"price": 478.15,
"regime": "positive_gamma",
"contracts": [
{ "strike": 480, "type": "C", "delta": 0.38, "iv": 0.21, "bid": 2.10, "ask": 2.14, "oi": 6300, "volume": 3200 }
]
}
]
}
Notice: only 0DTE expiry survives, only calls, only the ones clearing the delta and OI floor. Every other expiry, put, and low-OI contract was pruned server-side. That’s the cascading at work.
Plan note: cascading 0DTE filters work on both Growth (20 symbols) and Alpha (~248 symbols). Growth returns up to 10 rows; Alpha returns up to 50 with offset pagination.
2. 0DTE put scanner (protective or speculative)
{
"filters": {
"op": "and",
"conditions": [
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 },
{ "field": "contracts.type", "operator": "eq", "value": "P" },
{ "field": "contracts.delta", "operator": "between", "value": [-0.5, -0.2] },
{ "field": "contracts.volume", "operator": "gte", "value": 500 }
]
},
"select": ["*"]
}
Useful for finding 0DTE puts that are actually trading (volume ≥ 500) in the 20–50 delta range. Use this to scan for protective put opportunities when you’re long stock, or for speculative directional bets on names showing weakness intraday. The volume floor filters out illiquid strikes where you’d get a bad fill.
3. Negative-gamma 0DTE scanner (amplified move risk)
{
"filters": {
"op": "and",
"conditions": [
{ "field": "regime", "operator": "eq", "value": "negative_gamma" },
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 }
]
},
"sort": [{ "field": "net_gex", "direction": "asc" }],
"select": ["symbol", "price", "regime", "net_gex", "gamma_flip", "zero_dte_net_gex", "zero_dte_pct_of_total"]
}
This surfaces the names where 0DTE dealer gamma is negative and sorted by most-negative GEX. These are the names where intraday moves will be amplified by dealer hedging today. What to do with it: avoid selling premium on these names (dealers will run against you), or use them as breakout candidates where you buy directional 0DTE options and ride the gamma amplification. Widen stops on any existing positions in these symbols.
4. ATM 0DTE straddle scanner
{
"filters": {
"op": "and",
"conditions": [
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 },
{ "field": "contracts.delta", "operator": "between", "value": [-0.55, 0.55] },
{ "field": "contracts.oi", "operator": "gte", "value": 2000 },
{ "field": "atm_spread_pct", "operator": "lte", "value": 0.03 }
]
},
"select": ["*"],
"limit": 10
}
Finds symbols with liquid, tight-spread 0DTE ATM contracts. The atm_spread_pct ≤ 0.03 stock-level filter prunes names with bad microstructure before the contract-level cascade runs. This is your 0DTE iron condor scanner: once you have the ATM contracts, you know which names have the liquidity and spread width for a same-day iron condor or straddle.
5. 0DTE + high short-term GEX concentration
{
"filters": {
"op": "and",
"conditions": [
{ "field": "zero_dte_pct_of_total", "operator": "gte", "value": 0.3 },
{ "field": "expiries.days_to_expiry", "operator": "eq", "value": 0 },
{ "field": "contracts.oi", "operator": "gte", "value": 500 }
]
},
"sort": [{ "field": "zero_dte_pct_of_total", "direction": "desc" }],
"select": ["symbol", "price", "zero_dte_pct_of_total", "zero_dte_net_gex", "regime"]
}
Surfaces names where 0DTE options account for 30%+ of total gamma. These are the most “0DTE-driven” symbols today, where short-dated flow dominates the exposure profile. High concentration means the name will behave differently from its usual regime: pin risk is elevated, gamma acceleration is concentrated, and theta decay is extreme. Use this as an intraday options scanner for the most mechanically-interesting names of the session.
Full Python workflow: scan then drill
The pattern that works best for 0DTE trading: use the screener to find candidates, then hit the per-symbol 0DTE endpoint for the ones you want to trade.
from flashalpha import FlashAlpha
fa = FlashAlpha("YOUR_API_KEY")
# Step 1: scan for 0DTE call-selling candidates
scan = fa.screener(
filters={
"op": "and",
"conditions": [
{"field": "regime", "operator": "eq", "value": "positive_gamma"},
{"field": "expiries.days_to_expiry", "operator": "eq", "value": 0},
{"field": "contracts.type", "operator": "eq", "value": "C"},
{"field": "contracts.delta", "operator": "gte", "value": 0.3},
{"field": "contracts.oi", "operator": "gte", "value": 1000},
],
},
select=["symbol", "price", "regime", "zero_dte_net_gex"],
limit=5,
)
# Step 2: drill into top candidates
for row in scan["data"]:
sym = row["symbol"]
dte = fa.zero_dte(sym)
print(f"\n{sym}:")
print(f" Pin risk: {dte['pin_risk']['score']}/100")
print(f" Expected move: {dte['expected_move']['range_1_sigma']}")
print(f" Regime: {dte['gamma_regime']['regime']}")
Two requests instead of N. The screener finds the 5 best candidates; the 0DTE endpoint gives you pin risk, expected move, and dealer hedging detail for each one. This is also how you’d build a 0DTE options filter in production: scan on a timer, drill into the survivors, and alert when conditions align.
Plan note: fa.screener() requires Growth+. fa.zero_dte() also requires Growth+. Both draw from the same daily quota (2,500/day on Growth, unlimited on Alpha).
SDK support
The screener method is available in all five FlashAlpha SDKs (v0.3.0+):
- Python:
fa.screener(filters=..., select=...)
- JavaScript:
fa.screener({ filters, select })
- .NET:
client.ScreenerAsync(new ScreenerRequest { ... })
- Java:
client.screener(Map.of(...))
- Go:
client.Screener(ctx, flashalpha.ScreenerRequest{...})
No need to build the HTTP POST yourself. Install the SDK, call screener(), and the filter tree goes over the wire as typed JSON.
Further reading
Scan 0DTE setups today
The Live Screener is part of the FlashAlpha Real-Time Options Analytics API. Growth gives you 20 symbols with cascading 0DTE filters. Alpha unlocks ~248 symbols, formulas, and strategy scores.
Get an API key →
Compare plans →