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:
- 50% profit target. Captures ~85% of expected P&L with ~40% of the variance. The Sharpe improvement is significant.
- Gamma flip breach. If spot drops below the flip, your Cell A trade is now Cell B. Cut half or close.
- 21 DTE time stop. Gamma accelerates in the final 3 weeks. The remaining theta isn't worth the path risk.
- 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:
- FlashAlpha API Docs
-
Python SDK (
pip install flashalpha) - Full article with Kelly sizing + term VRP
- GEX-Conditioned VRP deep dive
Top comments (0)