VOOZH about

URL: https://dev.to/tomasz_dobrowolski_35d32c/i-built-a-5-minute-vrp-trading-scanner-in-python-heres-the-code-3a8i

⇱ I Built a 5-Minute VRP Trading Scanner in Python — Here's the Code - DEV Community


Options implied volatility overestimates realized vol. This is the variance risk premium (VRP) — and it's one of the most persistent edges in financial markets.

The problem: most people who know this still lose money selling premium. Not because the edge isn't there, but because they lack a repeatable process for deciding when, what, and how much to trade.

I built a 5-step daily workflow that answers all three questions using two API calls per symbol. It runs in under 5 minutes. Here's the full code.

The Stack

  • Data: FlashAlpha API — pre-computed VRP analytics, GEX regime data, dealer positioning levels across 4,000+ US equities/ETFs
  • Language: Python (also works in JS/cURL)
  • Dependencies: requests (that's it)

Step 1: Screen for VRP Signals

The /v1/vrp/{symbol} endpoint returns a full VRP dashboard per symbol. The two fields that matter for screening:

  • z_score — how many standard deviations above the 252-day mean. Above +1.0 = elevated.
  • vrp_20d — raw IV minus realized vol over 20 days. Positive = premium exists.
import requests

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

WATCHLIST = ["SPY", "QQQ", "IWM", "AAPL", "TSLA", 
 "NVDA", "AMZN", "META", "MSFT", "AMD"]

signals = []

for sym in WATCHLIST:
 r = requests.get(f"{BASE}/v1/vrp/{sym}", headers=HEADERS)
 if r.status_code != 200:
 continue
 d = r.json()
 vrp = d.get("vrp", {})

 z = vrp.get("z_score", 0)
 spread = vrp.get("vrp_20d", 0)

 if z >= 1.0 and spread > 2.0:
 signals.append({
 "symbol": sym, 
 "z_score": z,
 "vrp_20d": spread,
 "atm_iv": vrp.get("atm_iv", 0),
 "rv_20d": vrp.get("rv_20d", 0),
 "_raw": d,
 })

signals.sort(key=lambda x: x["z_score"], reverse=True)
print(f"Found {len(signals)} symbols with actionable VRP")

Filter logic: z-score ≥ 1.0 AND vrp_20d > 2.0 vol points. Conservative on purpose. A z-score of +2.0 on a 1-point spread isn't worth the transaction costs.

Step 2: Classify the Gamma Regime

A high VRP signal without regime context is incomplete. The same API response includes GEX-conditioned data — no extra call needed.

The key insight: dealer gamma positioning determines whether your short vol trade gets dampened (positive gamma = dealers buy your dips) or amplified (negative gamma = dealers sell into your drawdown).

for s in signals:
 d = s["_raw"]
 regime = d.get("regime", {})

 s["gamma_regime"] = regime.get("gamma", "unknown")
 s["gamma_flip"] = regime.get("gamma_flip", 0)
 s["spot"] = d.get("underlying_price", 0)

 # The 4-cell GEX-VRP matrix
 pos_gamma = s["gamma_regime"] == "positive_gamma"
 high_vrp = s["z_score"] >= 1.0

 if pos_gamma and high_vrp:
 s["cell"], s["size"] = "A", 1.75 # Premium Paradise
 elif not pos_gamma and high_vrp:
 s["cell"], s["size"] = "B", 0.50 # Tempting Trap
 elif pos_gamma and not high_vrp:
 s["cell"], s["size"] = "C", 0.50 # Grind It Out
 else:
 s["cell"], s["size"] = "D", 0.0 # No Trade

signals = [s for s in signals if s["cell"] != "D"]

This is the core decision matrix:

High VRP (z ≥ 1.0) Low VRP
+Gamma Cell A — full size, any structure Cell C — half size, tight structures
-Gamma Cell B — half size, defined risk only Cell D — no trade

Cell A is where the magic happens: dealers dampen moves (suppressing realized vol) while implied vol is elevated. Your short premium position benefits on both sides.

Cell B is the trap that blows up accounts. Premium looks rich, but dealers are amplifying moves against you.

Step 3: Pick the Structure

The VRP response includes strategy_scores — five common premium structures scored 0–100 based on current VRP, skew, term structure, and regime:

for s in signals:
 scores = s["_raw"].get("strategy_scores", {})
 directional = s["_raw"].get("directional", {})

 s["put_vrp"] = directional.get("downside_vrp", 0)
 s["call_vrp"] = directional.get("upside_vrp", 0)

 # Cell B = defined risk only
 if s["cell"] == "B":
 eligible = {k: v for k, v in scores.items() 
 if k in ("iron_condor", "jade_lizard")}
 else:
 eligible = scores

 s["best"] = max(eligible, key=eligible.get) if eligible else "none"

Pro tip: The directional object decomposes VRP into put-side vs. call-side. 70–80% of the time, premium is asymmetric. Defaulting to symmetric iron condors leaves edge on the table. If put VRP is 7.4 and call VRP is 4.2, you want put-heavy structures.

Step 4: Set Entry Levels from Dealer Positioning

Now the second API call — /v1/exposure/levels/{symbol} returns key dealer positioning levels:

for s in signals:
 r = requests.get(
 f"{BASE}/v1/exposure/levels/{s['symbol']}", 
 headers=HEADERS
 )
 if r.status_code != 200:
 continue
 lvl = r.json().get("levels", r.json())

 s["put_wall"] = lvl.get("put_wall", 0)
 s["call_wall"] = lvl.get("call_wall", 0)
 s["max_gamma"] = lvl.get("max_positive_gamma", 0)
  • Put wall → place short put strikes here (dealers buy at this level = mechanical backstop)
  • Call wall → place short call strikes here (dealers sell at this level = structural ceiling)
  • Max gamma → price magnet, ideal straddle center
  • Gamma flip → the line in the sand. Above = dampened. Below = amplified.

Step 5: Exit Rules (Define Before Entry)

Four non-negotiable rules:

  1. 50% profit target. Captures ~85% of expected P&L with ~40% of the variance. The Sharpe improvement is significant.
  2. Gamma flip breach. If spot drops below the flip, your Cell A trade is now Cell B. Cut half or close.
  3. 21 DTE time stop. Gamma accelerates in the final 3 weeks. The remaining theta isn't worth the path risk.
  4. VRP inversion. If z-score drops below -0.5, realized vol is beating implied. Close immediately.
for s in signals:
 vrp = s["_raw"].get("vrp", {})
 z = vrp.get("z_score", 0)
 flip = s["gamma_flip"]
 spot = s["spot"]
 dist = ((spot - flip) / spot * 100) if spot > 0 else 0

 if z < -0.5:
 status = "EXIT — VRP inversion"
 elif dist < 0:
 status = "EXIT — below gamma flip"
 elif dist < 0.5:
 status = "WARNING — near flip"
 else:
 status = "OK"

 print(f"{s['symbol']:<6} z={z:+.2f} dist={dist:+.1f}% → {status}")

The Complete Morning Script

All 5 steps in one runnable file → outputs a trade plan with symbol, cell classification, structure, strikes, and sizing. Full script is in the original article.

Sample output:

══════════════════════════════════════════════════════════════════
 VRP MORNING SCAN
══════════════════════════════════════════════════════════════════

──────────────────────────────────────────────────────────────────
 SPY — Cell A: PREMIUM PARADISE
──────────────────────────────────────────────────────────────────
 VRP z-score: +1.34 (82nd percentile)
 ATM IV: 18.5% RV20d: 13.0% Spread: +5.4
 Put VRP: +7.4 Call VRP: +4.2
 Gamma regime: positive_gamma Flip: $572.50 Distance: +1.4%
 Best structure: iron_condor (score: 85)
 Size: 1.75x standard
 Levels: put_wall=$570 call_wall=$590 max_gamma=$575

What You Need

The VRP endpoint, strategy scores, directional VRP, GEX-conditioned regime data, and term VRP are on the Alpha plan. Per-strike GEX and key levels are free.

If you're building systematic premium-selling workflows, Alpha is where the actionable analytics live — VRP z-scores, harvest scores, strategy scoring, vanna outlook, dealer flow risk, and unlimited API calls to run this scanner across your entire universe every morning without throttling.

Links: