Building a Volatility Scanner in Python (With Free API) | FlashAlpha Research

Building a Volatility Scanner in Python (With Free API)

Build a Python volatility scanner that pulls IV rank and IV percentile for a watchlist, filters for premium selling and long vol opportunities, adds GEX context to confirm dealer positioning, and outputs a formatted table of actionable trades — all using the FlashAlpha free tier with 10 API calls per day.

T
Tomasz Dobrowolski
Quant Engineer
Mar 17, 2026 · 33 min read
Python VolatilityScanner OptionsAPI IVRank FreeTier Tutorial

Building a Volatility Scanner in Python (With Free API)

Every morning, options traders ask the same question: where is implied volatility unusually high or low right now? The answer decides whether you sell premium, buy cheap options, or sit on your hands. Doing this manually across a watchlist is tedious. A Python script that scans 10 tickers and flags opportunities takes about 50 lines of code and zero dollars.

This tutorial builds a complete volatility scanner from scratch. It uses the FlashAlpha API free tier — 10 requests per day, no credit card — to pull IV rank, IV percentile, and gamma exposure data, then filters and formats the results into a clean table you can run every morning before the open.

What We're Building

A Python script that:

  1. Scans a watchlist of 10 tickers for IV rank and IV percentile
  2. Flags high IV rank (>80) tickers as premium selling candidates
  3. Flags low IV rank (<20) tickers as cheap options / long vol candidates
  4. Adds GEX context — checks whether dealer gamma positioning supports the trade direction
  5. Outputs a formatted table with signals and recommended actions

Total API calls: 10 (one per ticker). Fits entirely within the free tier.

10
Tickers Scanned
10
API Calls Used
~50
Lines of Code
$0
Cost
Watchlist Pull IV Rank Filter High/Low IV Add GEX Context Output

Scanner pipeline: one API call per ticker flows through filtering and context enrichment to a formatted report.

Prerequisites

You need three things:

  • Python 3.8+ — any recent version works
  • requests library — or install the FlashAlpha SDK (pip install flashalpha)
  • Free API key — sign up at flashalpha.com/pricing, no credit card required
pip install flashalpha requests

(GitHub · PyPI)

SDK or raw requests? This tutorial shows both approaches. The FlashAlpha Python SDK wraps the REST API with typed methods. If you prefer raw HTTP calls, every example includes the equivalent requests version.

Step 1: Set Up API Access

Start by initializing the client. Here's both the SDK approach and the raw requests equivalent:

Using the FlashAlpha SDK

from flashalpha import FlashAlpha

fa = FlashAlpha("YOUR_API_KEY")

Using raw requests

import requests

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

def api_get(endpoint):
    """Make an authenticated GET request to the FlashAlpha API."""
    resp = requests.get(f"{BASE_URL}{endpoint}", headers=HEADERS)
    resp.raise_for_status()
    return resp.json()

Replace YOUR_API_KEY with the key from your profile page. The free tier gives you 10 calls per day — exactly enough to scan a 10-ticker watchlist.

Step 2: Pull IV Rank and IV Percentile

The GET /v1/stock/{symbol}/summary endpoint returns IV rank, IV percentile, ATM implied volatility, and other summary metrics for any ticker. One call per symbol.

IV Rank vs IV Percentile

IV rank measures where current IV sits between its 52-week high and low (0-100 scale). IV percentile counts the percentage of days in the past year where IV was lower than today. They can diverge significantly — a single spike day distorts IV rank but barely affects percentile. Read the full comparison in our IV Rank vs IV Percentile deep dive.

IV Rank $$ \text{IV Rank} = \frac{\sigma_{\text{current}} - \sigma_{\text{52w low}}}{\sigma_{\text{52w high}} - \sigma_{\text{52w low}}} \times 100 $$
IV Percentile $$ \text{IV Percentile} = \frac{\#\{\text{days where } \sigma_t < \sigma_{\text{current}}\}}{252} \times 100 $$
# SDK approach
from flashalpha import FlashAlpha

fa = FlashAlpha("YOUR_API_KEY")

tickers = ["SPY", "QQQ", "AAPL", "TSLA", "NVDA", "AMZN", "META", "MSFT", "AMD", "GOOGL"]

results = []
for ticker in tickers:
    summary = fa.stock_summary(ticker)
    results.append({
        "ticker": ticker,
        "price": summary["price"],
        "iv_rank": summary["iv_rank"],
        "iv_percentile": summary["iv_percentile"],
        "atm_iv": summary["atm_iv"],
    })
    print(f"  {ticker}: IV Rank {summary['iv_rank']:.1f}, IV%ile {summary['iv_percentile']:.1f}")

Raw requests equivalent:

# Raw requests approach
for ticker in tickers:
    data = api_get(f"/stock/{ticker}/summary")
    results.append({
        "ticker": ticker,
        "price": data["price"],
        "iv_rank": data["iv_rank"],
        "iv_percentile": data["iv_percentile"],
        "atm_iv": data["atm_iv"],
    })

Sample output:

  SPY: IV Rank 42.3, IV%ile 55.1
  QQQ: IV Rank 38.7, IV%ile 48.9
  AAPL: IV Rank 15.2, IV%ile 22.4
  TSLA: IV Rank 87.6, IV%ile 91.3
  NVDA: IV Rank 83.1, IV%ile 78.5
  AMZN: IV Rank 31.4, IV%ile 40.2
  META: IV Rank 12.8, IV%ile 18.7
  MSFT: IV Rank 45.9, IV%ile 52.3
  AMD: IV Rank 92.4, IV%ile 88.7
  GOOGL: IV Rank 28.3, IV%ile 35.6

Already interesting — TSLA, NVDA, and AMD are showing elevated volatility, while AAPL and META look historically cheap.

Step 3: Filter for High IV Rank (>80) — Premium Selling Candidates

When IV rank is above 80, current implied volatility is near the top of its 52-week range. Options are expensive relative to history. This is when premium selling strategies — iron condors, credit spreads, strangles — have a statistical edge because IV tends to mean-revert.

high_iv = [r for r in results if r["iv_rank"] > 80]

print("\n=== HIGH IV RANK (>80) — Premium Selling Candidates ===")
for r in high_iv:
    print(f"  {r['ticker']:>5}  IV Rank: {r['iv_rank']:5.1f}  IV%ile: {r['iv_percentile']:5.1f}  ATM IV: {r['atm_iv']:.1%}")

if not high_iv:
    print("  No tickers above 80 IV rank today.")
=== HIGH IV RANK (>80) — Premium Selling Candidates ===
   TSLA  IV Rank:  87.6  IV%ile:  91.3  ATM IV: 62.4%
   NVDA  IV Rank:  83.1  IV%ile:  78.5  ATM IV: 54.8%
    AMD  IV Rank:  92.4  IV%ile:  88.7  ATM IV: 58.1%
Why IV rank and not just raw IV? TSLA always has higher IV than MSFT. A raw IV of 60% means nothing without context. IV rank normalizes it — 60% IV on TSLA might be rank 40 (mid-range), while 30% IV on MSFT might be rank 90 (historically extreme). Always compare IV to its own history.

Step 4: Filter for Low IV Rank (<20) — Cheap Options / Long Vol

When IV rank is below 20, options are cheap relative to history. This is the environment for buying options — long straddles, debit spreads, or directional plays where you want time decay to be minimal and a vol expansion to work in your favor.

low_iv = [r for r in results if r["iv_rank"] < 20]

print("\n=== LOW IV RANK (<20) — Long Vol / Cheap Options ===")
for r in low_iv:
    print(f"  {r['ticker']:>5}  IV Rank: {r['iv_rank']:5.1f}  IV%ile: {r['iv_percentile']:5.1f}  ATM IV: {r['atm_iv']:.1%}")

if not low_iv:
    print("  No tickers below 20 IV rank today.")
=== LOW IV RANK (<20) — Long Vol / Cheap Options ===
   AAPL  IV Rank:  15.2  IV%ile:  22.4  ATM IV: 21.3%
   META  IV Rank:  12.8  IV%ile:  18.7  ATM IV: 28.9%

Sample Scanner Output

Ticker Price IV Rank IV %ile ATM IV Signal
AMD 152.30 92.4 88.7 58.1% SELL PREMIUM
TSLA 248.75 87.6 91.3 62.4% SELL PREMIUM
NVDA 131.20 83.1 78.5 54.8% SELL PREMIUM
SPY 581.40 42.3 55.1 16.2% --
AAPL 213.45 15.2 22.4 21.3% LONG VOL
META 512.30 12.8 18.7 28.9% LONG VOL

IV Rank cells are color-coded: red for high IV (>80, premium selling candidates) and green for low IV (<20, long vol opportunities).

Step 5: Add GEX Context — Confirm Dealer Positioning

IV rank tells you whether options are cheap or expensive. GEX tells you whether market structure supports the trade direction. This is the edge most scanners miss.

Why GEX Matters for Vol Trades

Positive gamma (dealers long gamma) means dealers hedge against moves — dampening volatility. This supports premium selling. Negative gamma (dealers short gamma) means dealers amplify moves — increasing realized vol. This supports long vol trades. Matching your vol trade to the gamma regime dramatically improves win rates. Read the full explanation in our GEX explainer.

The GET /v1/exposure/{symbol}/gex endpoint returns net GEX and the gamma flip level. We will add this context to our flagged tickers without using extra API calls — the stock summary already includes the data we need. For a dedicated GEX deep dive, we make separate calls:

# Pull GEX for flagged tickers only
flagged = high_iv + low_iv

for r in flagged:
    gex = fa.gex(r["ticker"])
    r["net_gex"] = gex["net_gex"]
    r["gamma_flip"] = gex["gamma_flip"]
    r["gamma_regime"] = "Positive" if gex["net_gex"] > 0 else "Negative"

Raw requests equivalent:

for r in flagged:
    gex = api_get(f"/exposure/{r['ticker']}/gex")
    r["net_gex"] = gex["net_gex"]
    r["gamma_flip"] = gex["gamma_flip"]
    r["gamma_regime"] = "Positive" if gex["net_gex"] > 0 else "Negative"
Budget note: If you are on the free tier (10 calls/day), you have already used all 10 calls scanning the watchlist. To add GEX calls, either reduce your watchlist to 5 tickers (5 summary + 5 GEX = 10 calls), or upgrade to a paid plan. The full scanner below uses 10 summary calls. The GEX enhancement is shown separately and requires additional calls.

Step 6: Display Results in a Formatted Table

Bring everything together into a clean output. No external dependencies needed — just Python string formatting:

def print_scanner_results(results):
    """Print a formatted scanner output table."""
    print("\n" + "=" * 78)
    print("  VOLATILITY SCANNER — Pre-Market Report")
    print("=" * 78)

    # Header
    print(f"  {'Ticker':>6}  {'Price':>8}  {'IV Rank':>8}  {'IV %ile':>8}  {'ATM IV':>8}  {'Signal':<20}")
    print("  " + "-" * 74)

    for r in sorted(results, key=lambda x: x["iv_rank"], reverse=True):
        # Determine signal
        if r["iv_rank"] > 80:
            signal = "SELL PREMIUM"
        elif r["iv_rank"] < 20:
            signal = "LONG VOL"
        else:
            signal = "-"

        print(f"  {r['ticker']:>6}  {r['price']:>8.2f}  {r['iv_rank']:>7.1f}%  {r['iv_percentile']:>7.1f}%  {r['atm_iv']:>7.1%}  {signal:<20}")

    print("=" * 78)

    # Summary
    sell_count = sum(1 for r in results if r["iv_rank"] > 80)
    buy_count = sum(1 for r in results if r["iv_rank"] < 20)
    print(f"  Opportunities: {sell_count} premium selling, {buy_count} long vol")
    print(f"  Scanned {len(results)} tickers using {len(results)} API calls")
    print()

print_scanner_results(results)

Output:

==============================================================================
  VOLATILITY SCANNER — Pre-Market Report
==============================================================================
  Ticker     Price   IV Rank   IV %ile    ATM IV  Signal
  --------------------------------------------------------------------------
     AMD    152.30    92.4%    88.7%    58.1%  SELL PREMIUM
    TSLA    248.75    87.6%    91.3%    62.4%  LONG VOL
    NVDA    131.20    83.1%    78.5%    54.8%  SELL PREMIUM
    MSFT    428.50    45.9%    52.3%    22.8%  -
     SPY    581.40    42.3%    55.1%    16.2%  -
     QQQ    498.60    38.7%    48.9%    19.4%  -
    AMZN    192.15    31.4%    40.2%    32.1%  -
   GOOGL    172.80    28.3%    35.6%    27.5%  -
    AAPL    213.45    15.2%    22.4%    21.3%  LONG VOL
    META    512.30    12.8%    18.7%    28.9%  LONG VOL
==============================================================================
  Opportunities: 3 premium selling, 2 long vol
  Scanned 10 tickers using 10 API calls

Full Working Code (~50 Lines)

Here is the complete scanner as a single, copy-paste-ready script:

#!/usr/bin/env python3
"""Volatility scanner — scans a watchlist for IV rank opportunities.
Uses the FlashAlpha free tier (10 calls/day). No credit card required.
Sign up: https://flashalpha.com/pricing
"""
from flashalpha import FlashAlpha

API_KEY = "YOUR_API_KEY"
WATCHLIST = ["SPY", "QQQ", "AAPL", "TSLA", "NVDA", "AMZN", "META", "MSFT", "AMD", "GOOGL"]
HIGH_IV_THRESHOLD = 80
LOW_IV_THRESHOLD = 20

fa = FlashAlpha(API_KEY)

# --- Scan ---
results = []
for ticker in WATCHLIST:
    try:
        s = fa.stock_summary(ticker)
        results.append({
            "ticker": ticker,
            "price": s["price"],
            "iv_rank": s["iv_rank"],
            "iv_percentile": s["iv_percentile"],
            "atm_iv": s["atm_iv"],
        })
    except Exception as e:
        print(f"  [WARN] {ticker}: {e}")

# --- Display ---
print("\n" + "=" * 78)
print("  VOLATILITY SCANNER — Pre-Market Report")
print("=" * 78)
print(f"  {'Ticker':>6}  {'Price':>8}  {'IV Rank':>8}  {'IV %ile':>8}  {'ATM IV':>8}  {'Signal':<16}")
print("  " + "-" * 74)

for r in sorted(results, key=lambda x: x["iv_rank"], reverse=True):
    if r["iv_rank"] > HIGH_IV_THRESHOLD:
        signal = "SELL PREMIUM"
    elif r["iv_rank"] < LOW_IV_THRESHOLD:
        signal = "LONG VOL"
    else:
        signal = "-"
    print(f"  {r['ticker']:>6}  {r['price']:>8.2f}  {r['iv_rank']:>7.1f}%  {r['iv_percentile']:>7.1f}%  {r['atm_iv']:>7.1%}  {signal}")

print("=" * 78)
sell = sum(1 for r in results if r["iv_rank"] > HIGH_IV_THRESHOLD)
buy = sum(1 for r in results if r["iv_rank"] < LOW_IV_THRESHOLD)
print(f"  Opportunities: {sell} premium selling, {buy} long vol")
print(f"  Scanned {len(results)} tickers | {len(results)} API calls used")

Save as vol_scanner.py, replace YOUR_API_KEY, and run:

python vol_scanner.py

Extending the Scanner

The basic scanner covers IV rank screening on the free tier. With a Growth plan (higher rate limits), you can add significantly more depth:

Add Volatility Skew Data

The /v1/volatility/{symbol} endpoint (Growth+) returns skew metrics, put-call IV ratio, and term structure state. This tells you not just whether IV is high, but where in the surface the premium is concentrated:

# Requires Growth plan
vol = fa.volatility("TSLA")
print(f"Skew: {vol['skew']:.3f}")
print(f"Put/Call IV Ratio: {vol['put_call_iv_ratio']:.2f}")
print(f"Term Structure: {vol['term_structure_state']}")  # contango / backwardation

Add Term Structure Analysis

An inverted term structure (backwardation) means the market is pricing near-term risk higher than long-term — often around earnings, FOMC, or macro events. This is a strong signal for calendar spreads:

# Flag term structure inversions
if vol["term_structure_state"] == "backwardation":
    print(f"  {ticker}: INVERTED term structure — calendar spread opportunity")

Add Volatility Risk Premium (VRP)

VRP — the spread between implied and realized volatility — is the core edge in premium selling. When IV is high and VRP is wide, the odds of collecting theta profitably are strongest:

# Requires Growth plan
vol = fa.volatility("AMD")
vrp = vol["atm_iv"] - vol["realized_vol_30d"]
print(f"VRP: {vrp:.1%}")  # Positive = IV overpriced relative to realized
Key Insight

The free tier scanner gives you the "what" — which tickers have extreme IV. The Growth plan endpoints give you the "why" — skew shape, term structure, and VRP that tell you exactly how to structure the trade.

Scheduling with Cron for Daily Pre-Market Scans

Run the scanner automatically every weekday at 9:00 AM ET (before the US market open at 9:30):

Linux / macOS (cron)

# Edit crontab
crontab -e

# Add this line (9:00 AM ET = 14:00 UTC during EST, 13:00 UTC during EDT)
0 14 * * 1-5 /usr/bin/python3 /path/to/vol_scanner.py >> /path/to/scanner.log 2>&1

Windows (Task Scheduler)

# PowerShell — create a scheduled task
$action = New-ScheduledTaskAction -Execute "python" -Argument "C:\path\to\vol_scanner.py"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -At 9:00am
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "VolScanner" -Description "Pre-market volatility scan"

Sending Results to Email or Discord

Pipe the output to wherever you want it:

import smtplib
from email.mime.text import MIMEText

def email_results(body):
    msg = MIMEText(body, "plain")
    msg["Subject"] = "Vol Scanner — Pre-Market Report"
    msg["From"] = "scanner@yourdomain.com"
    msg["To"] = "you@yourdomain.com"
    with smtplib.SMTP("smtp.yourdomain.com", 587) as s:
        s.starttls()
        s.login("scanner@yourdomain.com", "password")
        s.send_message(msg)

Or post to a Discord webhook in three lines:

import requests

def post_to_discord(webhook_url, message):
    requests.post(webhook_url, json={"content": f"```\n{message}\n```"})

API Endpoints Used

Endpoint Tier Returns
/v1/stock/{symbol}/summary Free IV rank, IV percentile, ATM IV, price
/v1/exposure/{symbol}/gex Free Net GEX, gamma flip, per-strike breakdown
/v1/volatility/{symbol} Growth Skew, term structure, VRP, put/call IV ratio

Free tier: 10 requests/day. Growth plan: 2,500 requests/day. See full details at /pricing.

Build Your Own Volatility Scanner

IV rank, IV percentile, GEX, and volatility analytics for 4,000+ tickers.

Get Free API Key → Try the Playground

Frequently Asked Questions

No. The free tier allows 10 API calls per day, and each ticker requires one call. To scan larger watchlists — 50, 100, or 500+ tickers — you need a Growth plan which provides 2,500 calls per day. See pricing for details.
Use both. IV rank is better for identifying extremes because it reacts faster to spikes, but it can be distorted by a single outlier day. IV percentile is more robust. The strongest signals are when both agree — IV rank above 80 AND IV percentile above 80 means IV is unambiguously elevated. Read our full comparison at IV Rank vs IV Percentile.
Most systematic sellers use IV rank above 50 as the minimum entry criteria, with 80+ being the sweet spot. At 80+ IV rank, implied volatility is near the top of its annual range and historically tends to revert downward. Research from tastytrade and academic studies confirms that selling premium at high IV rank and buying at low IV rank outperforms random entry timing over large sample sizes.
Call the /v1/exposure/{symbol}/gex endpoint for each flagged ticker. This endpoint is available on the free tier. However, each GEX call counts toward your daily limit, so on the free tier you will need to reduce your watchlist size to make room. On the Growth plan, you have 2,500 calls — enough to pull both summary and GEX for 1,250 tickers daily.

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!