Build an Options Screener with an API: IV Scanner, Exposure Alerts, and Flow Detection | FlashAlpha

Build an Options Screener with an API: IV Scanner, Exposure Alerts, and Flow Detection

How to build an options screener using the FlashAlpha API. Scan for high IV stocks, unusual exposure shifts, rich VRP, and dealer positioning changes across 6,000+ tickers. Includes Python examples for IV rank scanning, GEX regime filtering, and multi-factor screening.

T
Tomasz Dobrowolski Quant Engineer
Mar 29, 2026
37 min read
OptionsScreener Scanner API Python IV DeveloperGuide OptionsAnalytics

If you're searching for an options screener API, a way to scan for high IV stocks programmatically, or trying to build a screening tool without computing everything yourself, this is the guide. One endpoint per ticker returns IV rank, IV percentile, VRP, GEX regime, skew, term structure, and exposure data — everything you need to score and rank 6,000+ US equities.


What Makes a Good Options Screener

A useful options screener filters the universe of tradeable tickers down to actionable opportunities. The screening dimensions that matter:

DimensionMetricWhat It Tells You
IV Rank / PercentileWhere current IV sits relative to its own 252-day historyIs volatility elevated? Good for premium-selling entries.
VRP (Volatility Risk Premium)IV minus realized vol, z-score, percentileIs the premium rich? Is selling options compensated right now?
GEX RegimeNet gamma exposure, gamma regime (positive/negative/neutral)Are dealers long or short gamma? Negative gamma = amplified moves.
SkewPut/call skew ratio, 25-delta skewIs downside protection expensive relative to upside?
Unusual OI / VolumeDay-over-day open interest changes, volume spikesIs someone building a position? Flow detection.
Term StructureContango vs backwardation, near-term vs far-term IVIs short-dated IV elevated relative to longer-dated? Event premium.

Building all of this from raw option chains for 6,000 tickers requires sourcing millions of rows of data per day, computing IV for every strike (which requires a root-finding algorithm per contract), fitting vol surfaces, calculating realized vol across multiple windows, estimating dealer positioning, and maintaining the pipeline. That's a multi-month engineering project.

The API Approach: Which Endpoints for Each Dimension

FlashAlpha pre-computes all of these analytics. Here's which endpoint covers each screening dimension:

Screening DimensionEndpointPlan
IV rank, IV percentile, ATM IV, skew, term structure, GEX regime, VRP, exposure dataGET /v1/stock/{symbol}/summaryFree+
Realized vol (5d-60d), IV-RV spreads, skew profiles, liquidityGET /v1/volatility/{symbol}Growth+
Net GEX/DEX/VEX/CHEX, gamma regime, dealer hedgingGET /v1/exposure/summary/{symbol}Growth+
VRP z-score, percentile, strategy scoresGET /v1/vrp/{symbol}Alpha
Full option chain with Greeks, IV, OI, volumeGET /optionquote/{ticker}Growth+
All available tickersGET /v1/tickersFree

The key insight: the /v1/stock/{symbol}/summary endpoint is your screener workhorse. A single call returns price, ATM IV, historical vol, VRP, skew, term structure, and full exposure data (GEX/DEX/VEX/CHEX, gamma flip, walls, max pain, regime, macro context). One call per ticker gives you everything needed to score and rank it. For a 50-ticker watchlist, that's 50 API calls — not 50 chain pulls and 50 IV computations.

Start screening with 5 free requests per day

The summary endpoint is available on the Free tier. No credit card required.

Get API Access

Quick Start: Install and First Scan

from flashalpha import FlashAlpha

fa = FlashAlpha("YOUR_API_KEY")
summary = fa.stock_summary("AAPL")
print(f"IV Rank: {summary['iv_rank']}, Regime: {summary['regime']}")
import { FlashAlpha } from 'flashalpha';

const fa = new FlashAlpha("YOUR_API_KEY");
const summary = await fa.stockSummary("AAPL");
console.log(`IV Rank: ${summary.iv_rank}, Regime: ${summary.regime}`);
using FlashAlpha;

var fa = new FlashAlphaClient("YOUR_API_KEY");
var summary = await fa.StockSummaryAsync("AAPL");
Console.WriteLine($"IV Rank: {summary.IvRank}, Regime: {summary.Regime}");
package main

import (
    "fmt"
    flashalpha "github.com/FlashAlpha-lab/flashalpha-go"
)

func main() {
    fa := flashalpha.NewClient("YOUR_API_KEY")
    summary, _ := fa.StockSummary("AAPL")
    fmt.Printf("IV Rank: %d, Regime: %s\n", summary.IvRank, summary.Regime)
}
import com.flashalpha.FlashAlphaClient;
import com.flashalpha.model.StockSummary;

FlashAlphaClient fa = new FlashAlphaClient("YOUR_API_KEY");
StockSummary summary = fa.stockSummary("AAPL");
System.out.printf("IV Rank: %d, Regime: %s%n", summary.getIvRank(), summary.getRegime());
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/stock/AAPL/summary"
$ pip install flashalpha  |  npm install flashalpha  |  dotnet add package FlashAlpha  |  go get github.com/FlashAlpha-lab/flashalpha-go

Use Case 1: IV Rank Scanner — Find High IV Stocks

The most common screener: find stocks where implied volatility is elevated relative to their own history. High IV rank means options are expensive, which favors premium-selling strategies (short straddles, iron condors, credit spreads).

import time
from flashalpha import FlashAlpha

fa = FlashAlpha("YOUR_API_KEY")

# Get all available tickers
tickers = fa.tickers()

# Scan a watchlist (or loop through all tickers on a Growth+ plan)
watchlist = ["AAPL", "TSLA", "NVDA", "AMZN", "META", "GOOGL", "MSFT",
             "AMD", "NFLX", "SPY", "QQQ", "IWM", "GLD", "TLT", "XLE"]

results = []
for ticker in watchlist:
    try:
        s = fa.stock_summary(ticker)
        results.append({
            "ticker": ticker,
            "iv_rank": s.get("iv_rank"),
            "iv_percentile": s.get("iv_percentile"),
            "atm_iv": s.get("atm_iv"),
            "price": s.get("price"),
        })
    except Exception as e:
        print(f"Skipping {ticker}: {e}")
    time.sleep(0.5)  # respect rate limits

# Sort by IV rank descending
results.sort(key=lambda x: x["iv_rank"] or 0, reverse=True)

print(f"{'Ticker':<8} {'IV Rank':>8} {'IV Pctl':>8} {'ATM IV':>8} {'Price':>10}")
print("-" * 50)
for r in results:
    print(f"{r['ticker']:<8} {r['iv_rank']:>8} {r['iv_percentile']:>8} "
          f"{r['atm_iv']:>7.1f}% {r['price']:>10.2f}")

Output:

Ticker   IV Rank  IV Pctl   ATM IV      Price
--------------------------------------------------
TSLA          82       87    54.2%     245.30
NFLX          76       81    38.1%     712.50
AMD           71       74    42.8%     158.20
XLE           68       72    28.5%      89.40
NVDA          65       69    46.3%     890.10
...

To filter to only high-IV candidates, add a threshold:

# Filter: IV rank > 70 AND IV percentile > 75
high_iv = [r for r in results if r["iv_rank"] > 70 and r["iv_percentile"] > 75]
print(f"\n{len(high_iv)} stocks with elevated IV:")

Use Case 2: GEX Regime Filter — Find Negative Gamma Stocks

Negative gamma regime means dealers are short gamma and will amplify directional moves by hedging in the same direction as the market. These are the tickers where big moves are more likely. Useful for directional traders looking for breakout candidates, or for premium sellers who want to avoid these names.

negative_gamma = []
for ticker in watchlist:
    try:
        s = fa.stock_summary(ticker)
        if s.get("regime") == "negative_gamma":
            negative_gamma.append({
                "ticker": ticker,
                "net_gex": s.get("net_gex"),
                "gamma_flip": s.get("gamma_flip"),
                "price": s.get("price"),
                "iv_rank": s.get("iv_rank"),
            })
    except Exception:
        continue
    time.sleep(0.5)

print(f"\nStocks in negative gamma regime ({len(negative_gamma)}):")
for r in negative_gamma:
    distance = ((r["price"] - r["gamma_flip"]) / r["price"]) * 100
    print(f"  {r['ticker']}: GEX={r['net_gex']:,.0f}, "
          f"flip={r['gamma_flip']:.2f} ({distance:+.1f}% from price), "
          f"IV rank={r['iv_rank']}")

For more detailed exposure data, use the dedicated exposure endpoint:

# Growth+ plan: get full exposure breakdown
exposure = fa.exposure_summary("TSLA")
print(f"Net GEX: {exposure['net_gex']:,.0f}")
print(f"Net DEX: {exposure['net_dex']:,.0f}")
print(f"Net VEX: {exposure['net_vex']:,.0f}")
print(f"Net CHEX: {exposure['net_chex']:,.0f}")
print(f"Regime: {exposure['regime']}")
print(f"Dealer hedging estimate: {exposure['dealer_hedging']}")

Use Case 3: VRP Scanner — Find Richest Premium-Selling Opportunities

VRP (volatility risk premium) measures the spread between implied and realized volatility. A high VRP z-score means the premium is unusually rich relative to history. Combined with a favorable gamma regime, this identifies optimal premium-selling entries.

# Alpha plan: VRP z-score scanning
vrp_results = []
for ticker in watchlist:
    try:
        v = fa.vrp(ticker)
        vrp_results.append({
            "ticker": ticker,
            "z_score": v.get("z_score"),
            "percentile": v.get("percentile"),
            "vrp_20d": v.get("vrp_20d"),
        })
    except Exception:
        continue
    time.sleep(0.5)

# Sort by z-score descending
vrp_results.sort(key=lambda x: x["z_score"] or 0, reverse=True)

print(f"{'Ticker':<8} {'Z-Score':>8} {'Pctl':>6} {'VRP 20d':>8}")
print("-" * 35)
for r in vrp_results:
    print(f"{r['ticker']:<8} {r['z_score']:>8.2f} {r['percentile']:>5}% "
          f"{r['vrp_20d']:>7.1f}%")

With the Free tier, you can approximate VRP using the summary endpoint:

# Free tier approximation using summary endpoint
s = fa.stock_summary("SPY")
approx_vrp = s["atm_iv"] - s["historical_vol"]
print(f"Approximate VRP: {approx_vrp:.1f}%")

Use Case 4: Unusual Exposure Shifts

Detect day-over-day changes in open interest, GEX flips, and unusual option flow. This requires polling the summary endpoint on a schedule and comparing current values to previous readings.

import json
from pathlib import Path

CACHE_FILE = Path("screener_cache.json")

def load_previous():
    if CACHE_FILE.exists():
        return json.loads(CACHE_FILE.read_text())
    return {}

def save_current(data):
    CACHE_FILE.write_text(json.dumps(data, indent=2))

previous = load_previous()
current = {}
alerts = []

for ticker in watchlist:
    try:
        s = fa.stock_summary(ticker)
        current[ticker] = {
            "net_gex": s.get("net_gex"),
            "regime": s.get("regime"),
            "iv_rank": s.get("iv_rank"),
            "max_pain": s.get("max_pain"),
        }

        if ticker in previous:
            prev = previous[ticker]
            # Detect regime flip
            if prev["regime"] != s.get("regime"):
                alerts.append(
                    f"REGIME FLIP: {ticker} changed from "
                    f"{prev['regime']} to {s.get('regime')}"
                )
            # Detect IV rank spike (>15 points in one day)
            if s.get("iv_rank") and prev.get("iv_rank"):
                iv_delta = s["iv_rank"] - prev["iv_rank"]
                if abs(iv_delta) > 15:
                    alerts.append(
                        f"IV SPIKE: {ticker} IV rank moved {iv_delta:+d} "
                        f"({prev['iv_rank']} -> {s['iv_rank']})"
                    )
    except Exception:
        continue
    time.sleep(0.5)

save_current(current)

if alerts:
    print(f"\n{len(alerts)} alerts detected:")
    for a in alerts:
        print(f"  * {a}")
else:
    print("No unusual activity detected.")

For full chain-level analysis, use the option quote endpoint to drill down into specific tickers flagged by the screener:

# Growth+ plan: drill into flagged ticker's chain
chain = fa.option_quote("TSLA")  # full chain
# Or filter by expiry/strike/type:
# chain = fa.option_quote("TSLA", expiry="2026-04-04", type="call")

# Find contracts with unusual volume relative to OI
for contract in chain:
    if contract["open_interest"] > 0:
        vol_oi_ratio = contract["volume"] / contract["open_interest"]
        if vol_oi_ratio > 3.0:
            print(f"  {contract['symbol']} "
                  f"Vol={contract['volume']} OI={contract['open_interest']} "
                  f"Ratio={vol_oi_ratio:.1f}x IV={contract['iv']:.1f}%")

Need full chain data + exposure analytics?

Growth plan includes option chains, exposure breakdowns, and volatility analytics for 6,000+ tickers.

Get API Access

Use Case 5: Multi-Factor Screener — Composite Scoring

The most powerful screener combines multiple dimensions into a composite score. Here's an example that scores tickers on IV rank, VRP approximation, and GEX regime, then ranks them for premium-selling opportunities:

def score_ticker(summary):
    """Score a ticker 0-100 for premium-selling suitability."""
    score = 0

    # IV rank component (0-35 points)
    iv_rank = summary.get("iv_rank", 0) or 0
    score += min(35, iv_rank * 0.35)

    # VRP approximation component (0-30 points)
    atm_iv = summary.get("atm_iv", 0) or 0
    hist_vol = summary.get("historical_vol", 0) or 0
    vrp = atm_iv - hist_vol
    if vrp > 0:
        score += min(30, vrp * 2)

    # GEX regime component (0-20 points)
    regime = summary.get("regime", "")
    if regime == "positive_gamma":
        score += 20  # favorable: pinning, mean reversion
    elif regime == "neutral":
        score += 10
    elif regime == "negative_gamma":
        score += 0   # dangerous for premium sellers

    # Skew component (0-15 points)
    skew = summary.get("skew", 0) or 0
    if skew > 1.1:  # put skew is steep, puts are expensive to sell
        score += min(15, (skew - 1.0) * 50)

    return round(score, 1)


# Run the screener
scored = []
for ticker in watchlist:
    try:
        s = fa.stock_summary(ticker)
        composite = score_ticker(s)
        scored.append({
            "ticker": ticker,
            "score": composite,
            "iv_rank": s.get("iv_rank"),
            "regime": s.get("regime"),
            "atm_iv": s.get("atm_iv"),
            "price": s.get("price"),
        })
    except Exception:
        continue
    time.sleep(0.5)

# Rank by composite score
scored.sort(key=lambda x: x["score"], reverse=True)

print(f"{'Rank':<6} {'Ticker':<8} {'Score':>6} {'IV Rank':>8} "
      f"{'ATM IV':>8} {'Regime':<18}")
print("-" * 65)
for i, r in enumerate(scored[:10], 1):
    print(f"{i:<6} {r['ticker']:<8} {r['score']:>6.1f} {r['iv_rank']:>8} "
          f"{r['atm_iv']:>7.1f}% {r['regime']:<18}")

Adjust the weights based on your strategy. Directional traders might weight GEX regime higher and IV rank lower. Premium sellers want high IV rank + positive gamma + rich VRP. Volatility arbitrage traders care most about VRP z-score and skew.


Building Alerts: Polling Pattern with Rate Limits

A screener that runs on a schedule and sends alerts when conditions are met. Key consideration: respect rate limits. The Free tier allows 5 requests/day. Growth plans support higher throughput.

import time
import smtplib
from email.message import EmailMessage

def run_screener_cycle(fa, watchlist, thresholds):
    """Run one screening cycle and return alerts."""
    alerts = []
    for ticker in watchlist:
        try:
            s = fa.stock_summary(ticker)
            # Check thresholds
            if (s.get("iv_rank") or 0) > thresholds.get("iv_rank_min", 70):
                alerts.append({
                    "type": "HIGH_IV",
                    "ticker": ticker,
                    "iv_rank": s["iv_rank"],
                    "iv_percentile": s.get("iv_percentile"),
                })
            if s.get("regime") == "negative_gamma":
                alerts.append({
                    "type": "NEG_GAMMA",
                    "ticker": ticker,
                    "net_gex": s.get("net_gex"),
                })
        except Exception:
            continue
        time.sleep(0.5)  # 500ms between requests
    return alerts

def send_alert_email(alerts, recipient):
    """Send alert summary via email."""
    msg = EmailMessage()
    msg["Subject"] = f"Options Screener: {len(alerts)} alerts"
    msg["From"] = "[email protected]"
    msg["To"] = recipient
    body = "\n".join(
        f"[{a['type']}] {a['ticker']}: {a}" for a in alerts
    )
    msg.set_content(body)
    # Configure your SMTP server here
    # with smtplib.SMTP("smtp.example.com") as server:
    #     server.send_message(msg)

# Run every 30 minutes during market hours
thresholds = {"iv_rank_min": 70}
while True:
    alerts = run_screener_cycle(fa, watchlist, thresholds)
    if alerts:
        send_alert_email(alerts, "[email protected]")
        print(f"Sent {len(alerts)} alerts")
    time.sleep(1800)  # 30 minutes

AI Agents: Let Claude Scan for You

FlashAlpha provides an MCP (Model Context Protocol) server that lets AI assistants query options data directly. Configure your AI agent with the MCP server and it can run screening logic, interpret results, and surface opportunities without you writing screening code at all.

MCP server URL: https://lab.flashalpha.com/mcp

Example prompt to Claude with MCP configured:

"Scan SPY, TSLA, NVDA, AAPL, and AMD. Which ones have IV rank above 70
and are in a negative gamma regime? For those, what's the VRP z-score
and which premium-selling strategy scores highest?"

The agent calls the summary endpoint for each ticker, filters based on your criteria, drills into VRP for the matches, and returns a ranked list with trade structure recommendations — all grounded in live data, not hallucinated numbers.

This works with Claude (Anthropic), Cursor, Windsurf, and any MCP-compatible agent. Configure it once and your AI assistant becomes an options screener.


Why Not Build It Yourself?

You could build all of this from raw OPRA data. Here's what that looks like:

ComponentWhat's Involved
Data sourcingPull option chains for 6,000+ tickers. OPRA generates ~100M+ quotes/day. You need a feed handler, a normalizer, and storage.
IV computationRoot-finding (Newton-Raphson or Brent) for every strike on every expiry on every ticker. That's millions of computations per snapshot.
Vol surface fittingSVI or SABR calibration per ticker per expiry slice. Handle non-convergence, arbitrage violations, low-liquidity names.
IV rank / percentileStore 252 trading days of ATM IV history per ticker. Compute rolling rank and percentile daily.
Realized volYang-Zhang or close-to-close estimators across 5d, 10d, 20d, 30d, 60d windows. Handle corporate actions, splits, dividends.
GEX / exposureModel dealer positioning from OI data. Estimate gamma, delta, vanna, charm exposure per strike. Aggregate across the chain.
VRPIV minus RV across multiple windows. Rolling z-scores. Directional decomposition. Strategy scoring.
InfrastructureScheduling, caching, monitoring, alerting, API layer, rate limiting, auth. Ongoing maintenance.

Realistically, this is 6-12 months of engineering for a small team and significant ongoing infrastructure costs. The API approach lets you focus on the screening logic and trading decisions rather than the data pipeline.

Pricing

PlanPriceRequestsScreener Capabilities
Free$0/mo5/daySummary endpoint: IV rank, regime, basic screening for a small watchlist
Basicfrom $63/mo100/daySummary endpoint with more throughput for larger watchlists
Growthfrom $239/mo2,500/day+ Volatility, exposure, option chains. Full screening across all dimensions.
Alphafrom $1,199/moUnlimited+ VRP z-scores, strategy scores, advanced vol surfaces. Institutional screening.

For a screener that scans a 20-ticker watchlist using the summary endpoint, the Free tier works. For scanning hundreds of tickers with exposure and VRP data, Growth or Alpha is the right fit.

Build your screener today

Free tier includes 5 requests/day. Start scanning IV rank and GEX regime in minutes.

Get API Access

Frequently Asked Questions

An options screener API provides pre-computed analytics (IV rank, IV percentile, VRP, GEX regime, skew, exposure data) via REST endpoints, letting you filter and rank thousands of tickers programmatically without sourcing raw option chains or computing metrics yourself. FlashAlpha's /v1/stock/{symbol}/summary endpoint returns all key screening metrics in a single call.
Call the summary endpoint for each ticker in your watchlist, extract the iv_rank and iv_percentile fields, and filter. IV rank above 70 means current implied volatility is in the top 30% of its 252-day range. Loop through your watchlist, sort by IV rank descending, and you have a high-IV scanner in 20 lines of Python.
Yes. Poll the summary endpoint on a schedule and compare readings over time. Detect GEX regime flips, IV rank spikes (>15 points in one day), and gamma flip level crossovers. For contract-level analysis, use the /optionquote/{ticker} endpoint to find contracts where volume exceeds open interest by 3x or more.
FlashAlpha covers 6,000+ US equities. The /v1/tickers endpoint returns the full list. Your scan rate depends on your plan's rate limits. Free tier: 5 requests/day (good for a small watchlist). Growth and Alpha plans support scanning hundreds or thousands of tickers per session.
GEX (gamma exposure) regime indicates whether dealers are net long or short gamma. In negative gamma, dealers hedge in the same direction as price movement, amplifying moves. In positive gamma, dealers hedge against price movement, dampening volatility. Screening for negative gamma identifies stocks with higher breakout potential; screening for positive gamma identifies pinned, mean-reverting names.
Yes. FlashAlpha provides an MCP server at lab.flashalpha.com/mcp that works with Claude, Cursor, Windsurf, and other MCP-compatible agents. Configure your API key and prompt the agent to scan tickers, filter by criteria, and interpret results. The agent handles the screening logic automatically.

Live Market Pulse

Get tick-by-tick visibility into market shifts with full-chain analytics streaming in real time.

Intelligent Screening

Screen millions of option pairs per second using your custom EV rules, filters, and setups.

Execution-Ready

Instantly send structured orders to Interactive Brokers right from your scan results.

Join the Community

Discord

Engage in real time conversations with us!

Twitter / X

Follow us for real-time updates and insights!

GitHub

Explore our open-source SDK, examples, and analytics resources!