<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tomasz dobrowolski</title>
    <description>The latest articles on DEV Community by tomasz dobrowolski (@tomasz_dobrowolski_35d32c).</description>
    <link>https://dev.to/tomasz_dobrowolski_35d32c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3827438%2F3874bebe-b7b5-43b5-a0e5-53b9c79dbe7b.png</url>
      <title>DEV Community: tomasz dobrowolski</title>
      <link>https://dev.to/tomasz_dobrowolski_35d32c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomasz_dobrowolski_35d32c"/>
    <language>en</language>
    <item>
      <title>I Backtested 96 SPY Put Credit Spread Strategies - Here's the Bug Log</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 29 Apr 2026 07:55:10 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/i-backtested-96-spy-put-credit-spread-strategies-heres-the-bug-log-1li3</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/i-backtested-96-spy-put-credit-spread-strategies-heres-the-bug-log-1li3</guid>
      <description>&lt;p&gt;I built a backtest engine for SPY put credit spreads. 7 years of 1-minute option chains. 96 grid cells across (delta, DTE, profit-target, stop-loss). 16,024 trades that survived validation.&lt;/p&gt;

&lt;p&gt;This post is the part most quant tutorials skip: the bugs, the lying simulators, and the one-line feature that humiliated my 3-layer composite signal. If you've ever shipped a backtest with great-looking numbers, you might want to read the bug log section.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first lesson: mid-fill backtests are lying to you
&lt;/h2&gt;

&lt;p&gt;The naive engine filled at the bid-ask mid. Numbers looked great. Then I implemented what an actual desk does - post a limit at &lt;code&gt;combo_ask + $0.04&lt;/code&gt;, wait for someone to cross it, accept rejection - and CAGR dropped 30–60% across the entire grid. Several cells flipped from positive to negative.&lt;/p&gt;

&lt;p&gt;Real fill stats from one run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Posted&lt;/th&gt;
&lt;th&gt;Filled&lt;/th&gt;
&lt;th&gt;Fill rate&lt;/th&gt;
&lt;th&gt;Avg wait&lt;/th&gt;
&lt;th&gt;Edge captured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;45Δ 30 DTE PT 50% No stop&lt;/td&gt;
&lt;td&gt;479&lt;/td&gt;
&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;23.4%&lt;/td&gt;
&lt;td&gt;12.4 min&lt;/td&gt;
&lt;td&gt;−$0.04 to −$0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45Δ 30 DTE PT 50% SL 100%&lt;/td&gt;
&lt;td&gt;633&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;td&gt;24.5%&lt;/td&gt;
&lt;td&gt;12.2 min&lt;/td&gt;
&lt;td&gt;−$0.04 to −$0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45Δ 30 DTE PT 50% SL 200%&lt;/td&gt;
&lt;td&gt;383&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;16.7%&lt;/td&gt;
&lt;td&gt;13.3 min&lt;/td&gt;
&lt;td&gt;−$0.04 to −$0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You fill ~20–25% of orders, after a ~12-minute wait, at 4–7 cents &lt;em&gt;worse&lt;/em&gt; than mid. The MM doesn't make money at the fill - they make it over the hold, when theta beats realized vol. &lt;strong&gt;Any backtest filling at mid is silently gifting your strategy that 4–7 cents per trade.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you remember nothing else from this post: build a real fill model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The single biggest finding: SL=100% IS the strategy
&lt;/h2&gt;

&lt;p&gt;Same cell. Same fills. Same period. Toggle the stop loss:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Trades&lt;/th&gt;
&lt;th&gt;Return&lt;/th&gt;
&lt;th&gt;CAGR&lt;/th&gt;
&lt;th&gt;MaxDD&lt;/th&gt;
&lt;th&gt;Calmar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10Δ 7 DTE PT 50% No stop&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;wipeout&lt;/td&gt;
&lt;td&gt;breaker&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10Δ 7 DTE PT 50% SL 100%&lt;/td&gt;
&lt;td&gt;460&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+5,439%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+66.0%&lt;/td&gt;
&lt;td&gt;30.1%&lt;/td&gt;
&lt;td&gt;2.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10Δ 7 DTE PT 75% SL 100%&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;+2,947%&lt;/td&gt;
&lt;td&gt;+53.9%&lt;/td&gt;
&lt;td&gt;31.1%&lt;/td&gt;
&lt;td&gt;1.73&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10Δ 7 DTE PT 25% SL 100%&lt;/td&gt;
&lt;td&gt;649&lt;/td&gt;
&lt;td&gt;+1,752%&lt;/td&gt;
&lt;td&gt;+44.6%&lt;/td&gt;
&lt;td&gt;30.2%&lt;/td&gt;
&lt;td&gt;1.48&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;+5,439% with a stop. −100% without. &lt;strong&gt;Same cell.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A single bad Monday on a no-stop short-vol position takes the account to zero. The stop loss isn't a parameter you sweep - at short DTE it's the entire risk model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap nobody warns you about: SL=200%
&lt;/h2&gt;

&lt;p&gt;I expected SL=200% to be the sensible compromise. Looser than 100% so I don't get noise-stopped, tighter than no-SL so a tail doesn't kill me. Wrong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SL setting&lt;/th&gt;
&lt;th&gt;Trades&lt;/th&gt;
&lt;th&gt;Return&lt;/th&gt;
&lt;th&gt;CAGR&lt;/th&gt;
&lt;th&gt;Sharpe&lt;/th&gt;
&lt;th&gt;MaxDD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No stop&lt;/td&gt;
&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;+30.6%&lt;/td&gt;
&lt;td&gt;+3.8%&lt;/td&gt;
&lt;td&gt;+0.23&lt;/td&gt;
&lt;td&gt;30.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SL 100%&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;td&gt;+28.0%&lt;/td&gt;
&lt;td&gt;+3.5%&lt;/td&gt;
&lt;td&gt;+0.22&lt;/td&gt;
&lt;td&gt;29.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SL 200% ← trap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−16.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−2.5%&lt;/td&gt;
&lt;td&gt;−0.17&lt;/td&gt;
&lt;td&gt;31.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both no-stop and SL=100% made money. SL=200% lost.&lt;/p&gt;

&lt;p&gt;Mechanics: by the time the loss has grown to 200% of credit, you're deep ITM and gamma is doing the marking, not theta. You stop out at the worst possible price, on the worst possible day, after letting the position breathe past the point of recovery. SL=100% stops you before gamma takes over. No SL at least gives you the chance to be bailed out by a recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick tight or pick none. Never the middle.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My fancy signal got beaten by a one-liner
&lt;/h2&gt;

&lt;p&gt;I built a 3-layer composite - Premium / Danger / Stabilization scores, z-scored macro inputs, continuous Kelly multiplier. Then I ran t-tests across all 16,024 trades.&lt;/p&gt;

&lt;p&gt;The strongest predictor in the entire feature set wasn't my composite. It was a single boolean flag on a free macro series - the kind of thing you write in three lines of pandas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pseudocode - the actual flag stays with me
&lt;/span&gt;&lt;span class="n"&gt;signal_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;macro_series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;macro_series&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;macro_series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;t-stat over 8, on 5,000+ trades. Bonferroni-correct it across the entire feature space and it still wins.&lt;/p&gt;

&lt;p&gt;The lesson generalizes: if your "edge" is a 47-feature gradient-boosted model, replace it with the most economically obvious single flag and check how much you actually lose. Often that flag does 80% of the work and your model is overfitting the residual.&lt;/p&gt;

&lt;p&gt;Two related findings worth knowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The market pays the most right before it bites.&lt;/strong&gt; Top-quintile VRP days had 66% winrate vs 74% baseline. By the time premium is that rich, something is actually wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't sell into rising fear with an inverted term curve.&lt;/strong&gt; Wait for the curve to normalize OR for the fear to fade. Either is fine. Both being wrong is the worst regime in the data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Multi-DTE pooling cost me 9pp of CAGR
&lt;/h2&gt;

&lt;p&gt;Seemed obvious: instead of committing to one tenor, rank candidates across 30/45/60-DTE chains every entry, pick the best EV-per-dollar-at-risk:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tenor selection&lt;/th&gt;
&lt;th&gt;Trades&lt;/th&gt;
&lt;th&gt;Return&lt;/th&gt;
&lt;th&gt;CAGR&lt;/th&gt;
&lt;th&gt;Sharpe&lt;/th&gt;
&lt;th&gt;MaxDD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pool 30/45/60 DTE No stop&lt;/td&gt;
&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;+30.6%&lt;/td&gt;
&lt;td&gt;+3.8%&lt;/td&gt;
&lt;td&gt;+0.23&lt;/td&gt;
&lt;td&gt;30.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pool 30/45/60 DTE SL 100%&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;td&gt;+28.0%&lt;/td&gt;
&lt;td&gt;+3.5%&lt;/td&gt;
&lt;td&gt;+0.22&lt;/td&gt;
&lt;td&gt;29.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Focused 30 DTE No stop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+70%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+12.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.72&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;27.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ranker correctly picked 30-DTE ~68% of the time. The other ~32% it picked 45-DTE specifically &lt;em&gt;because&lt;/em&gt; the 30-DTE chain looked worse than usual that bar - which is the textbook definition of adverse selection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More degrees of freedom = more ways for the optimizer to be wrong, not more ways to be right.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug log
&lt;/h2&gt;

&lt;p&gt;This is the part most quant blogs skip. Every one of these bugs would have inflated the headline numbers. Most were caught only after a code review:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug&lt;/th&gt;
&lt;th&gt;What it did&lt;/th&gt;
&lt;th&gt;Damage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mid-fill assumption&lt;/td&gt;
&lt;td&gt;Filled at bid-ask average always&lt;/td&gt;
&lt;td&gt;Flipped multiple losing strategies positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Look-ahead in signal&lt;/td&gt;
&lt;td&gt;Day-D signal used end-of-D data at 10:05 AM entry&lt;/td&gt;
&lt;td&gt;Inflated CAGR ~5–10pp on most cells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale-quote acceptance&lt;/td&gt;
&lt;td&gt;"Fills" at quotes that were no longer real liquidity&lt;/td&gt;
&lt;td&gt;~30% of fills had negative edge captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EV-sorted tiebreak&lt;/td&gt;
&lt;td&gt;Higher-EV candidate "filled" first when two crossed same bar&lt;/td&gt;
&lt;td&gt;Subtle but real per-trade lift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warmup sizing bug&lt;/td&gt;
&lt;td&gt;Full Kelly applied before signal had any history&lt;/td&gt;
&lt;td&gt;Cratered 2018 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation walk-back mismatch&lt;/td&gt;
&lt;td&gt;Validator used exact-date lookup, engine walked back 7 days&lt;/td&gt;
&lt;td&gt;Bogus regression stats on weekend dates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walk-the-limit (rejected)&lt;/td&gt;
&lt;td&gt;Drop limit a penny each minute if unfilled&lt;/td&gt;
&lt;td&gt;Caught before merge - adverse selection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pre-fix engine on the same data returned −1% to −8% CAGR across every cell. So when post-fix numbers turned positive, that wasn't simulator noise - it was what the bias was masking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your backtest looks great on the first run, you have a bug. Find it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The sizing layer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SIZING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kelly_default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# half-Kelly, conservative on purpose
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kelly_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kelly_f_hard_cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# leveraged math can multiply to 1.25 without it
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drawdown_breaker_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# halts the run, not the position
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;absolute_floor_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# secondary backstop
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warmup_multiplier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# cratered 2018 when this was 1.0
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_on_mult&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# 2.5 in the leveraged stress test
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Half-Kelly is conservative on purpose. Mean-variance Kelly assumes Gaussian returns; short-vol returns are skewed left with fat tails. The "true" Kelly under fat tails is &lt;em&gt;below&lt;/em&gt; the mean-variance Kelly, so half-Kelly isn't lazy - it's roughly correct.&lt;/p&gt;

&lt;p&gt;The 30% drawdown breaker doesn't reduce position size or pause for a day. It halts the entire run. If your strategy needs 30% drawdown to work, you don't have a strategy, you have a martingale.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mid-fill backtests overstate CAGR by 30–60%. Build a real fill model.&lt;/li&gt;
&lt;li&gt;Stop loss at 100% of credit IS the game at short DTE. +5,400% with, −100% without.&lt;/li&gt;
&lt;li&gt;SL=200% is worse than no stop. Pick tight or pick none.&lt;/li&gt;
&lt;li&gt;The strongest signal in 16k trades was a one-line boolean flag.&lt;/li&gt;
&lt;li&gt;Multi-DTE pooling cost 9pp of CAGR. Adverse selection.&lt;/li&gt;
&lt;li&gt;Higher delta moves equity vol, not alpha.&lt;/li&gt;
&lt;li&gt;The market pays the most right before it bites.&lt;/li&gt;
&lt;li&gt;If your backtest looks great on the first run, find the bug.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The full write-up with all 96 cells, the leveraged stress preset, and where the Calmar-leader cell sits in the grid is on FlashAlpha: &lt;a href="https://flashalpha.com/articles/spy-put-credit-spread-active-backtest-mm-fills-vrp-signal-drawdown-breaker" rel="noopener noreferrer"&gt;original article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The actual constraint on reproducing this isn't the engine (the engine is bookkeeping) - it's having minute-resolution SPY chains going back to 2018 with surface-consistent IVs. That's what FlashAlpha's &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Alpha tier historical API&lt;/a&gt; is for.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>python</category>
      <category>quant</category>
      <category>finance</category>
    </item>
    <item>
      <title>SPXW 0DTE: What Every Developer Building Options Tools Needs to Know</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 29 Apr 2026 07:54:51 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/spxw-0dte-what-every-developer-building-options-tools-needs-to-know-3jaa</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/spxw-0dte-what-every-developer-building-options-tools-needs-to-know-3jaa</guid>
      <description>&lt;p&gt;If you've built anything involving options data, you've probably noticed that "SPX 0DTE" is everywhere on fintwit, Discord, and trading blogs. The thing most guides gloss over is that &lt;strong&gt;plain SPX doesn't actually have same-day expirations every day&lt;/strong&gt;. What everyone means when they say "SPX 0DTE" is SPXW — a different ticker with different settlement and different Greeks behavior.&lt;/p&gt;

&lt;p&gt;I build an options analytics API, and the distinction matters enough that every developer touching options data should understand it before their dashboard lies to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPX, SPXW, XSP — the one that's actually 0DTE
&lt;/h2&gt;

&lt;p&gt;Three S&amp;amp;P 500 option tickers trade on Cboe. Only one gives you same-day expirations every trading day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ticker&lt;/th&gt;
&lt;th&gt;Listing cadence&lt;/th&gt;
&lt;th&gt;Settlement&lt;/th&gt;
&lt;th&gt;Contract size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SPX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Third-Friday AM expirations&lt;/td&gt;
&lt;td&gt;AM cash-settled&lt;/td&gt;
&lt;td&gt;$100 × index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SPXW&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Mon/Tue/Wed/Thu/Fri&lt;/strong&gt; PM expirations&lt;/td&gt;
&lt;td&gt;PM cash-settled&lt;/td&gt;
&lt;td&gt;$100 × index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XSP&lt;/td&gt;
&lt;td&gt;Mon-Fri PM&lt;/td&gt;
&lt;td&gt;PM cash-settled&lt;/td&gt;
&lt;td&gt;$10 × index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plain SPX has no Monday, Tuesday, Wednesday, or Thursday expirations. Only the monthly AM-settled contract. SPXW is where the daily 0DTE volume lives.&lt;/p&gt;

&lt;p&gt;Two consequences for anyone modeling it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No assignment risk.&lt;/strong&gt; SPXW is European-style and cash-settled. An SPXW put closing $0.05 ITM at 4:00 PM pays cash, not stock. You don't need early-exercise logic in your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner Greeks at close.&lt;/strong&gt; SPY 0DTE gets fuzzy near 4:00 PM because ITM American contracts can be assigned overnight. SPXW doesn't have that problem — the Greeks you compute at 3:55 PM describe the remaining five minutes exactly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is also why SPXW tends to have &lt;em&gt;higher&lt;/em&gt; 0DTE gamma concentration than SPY on comparable volume. European-style means every open contract contributes to hedging flow right through 4:00 PM instead of melting off via early exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it moves the tape
&lt;/h2&gt;

&lt;p&gt;The mechanical reason is identical to any 0DTE, just larger scale. At-the-money gamma scales as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Γ_ATM ≈ 1 / (S · σ · √(2π · T))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;T&lt;/code&gt; is measured in hours instead of weeks, ATM gamma is 3-10x higher than equivalent weekly contracts, and 10x+ in the final 30 minutes. Dealers short that gamma trade exponentially more shares for the same percentage move in the index.&lt;/p&gt;

&lt;p&gt;On a $6,000 SPX with $200B+ in SPXW open interest, this isn't a retail phenomenon. These hedging flows can dominate entire afternoon sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gamma flip is the switch
&lt;/h3&gt;

&lt;p&gt;Above the 0DTE gamma flip, dealers are net long gamma. They sell into rallies, buy into dips, dampen ranges. Below the flip, they amplify — a routine 20-point oscillation becomes a 60-point move.&lt;/p&gt;

&lt;p&gt;The part that burns people: &lt;strong&gt;the flip moves intraday&lt;/strong&gt;. A flip at $5,950 at 10 AM can slide to $5,935 by 2 PM as 0DTE positions decay and roll. A static GEX chart from yesterday's close is worse than useless — you need the current session's 0DTE aggregate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four numbers that actually matter
&lt;/h2&gt;

&lt;p&gt;If you're building a 0DTE dashboard, these four values are the entire point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gamma regime&lt;/strong&gt; — &lt;code&gt;positive_gamma&lt;/code&gt; or &lt;code&gt;negative_gamma&lt;/code&gt; for the session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin score (0-100)&lt;/strong&gt; — probability price gets magnetised to a strike by 4 PM. Composite of OI concentration, magnet proximity, time remaining, and gamma magnitude&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remaining expected move (1σ)&lt;/strong&gt; — how far price can credibly travel before close. Shrinks in real time — a $40 expected move at 10 AM is a $12 expected move at 2:30 PM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dealer hedging at ±0.5%&lt;/strong&gt; — estimated shares dealers must buy or sell for a half-percent move. Absolute value tells you how sharp intraday reactions will be&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Read together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regime&lt;/th&gt;
&lt;th&gt;Pin score&lt;/th&gt;
&lt;th&gt;Expected move&lt;/th&gt;
&lt;th&gt;Implication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Positive&lt;/td&gt;
&lt;td&gt;&amp;gt;70&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Classic pin day — range-bound, fade extremes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Positive&lt;/td&gt;
&lt;td&gt;&amp;lt;40&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;No pin, dampened — drift toward magnet, no breakout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negative&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Trend day risk — dealers amplify, buy breakouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negative&lt;/td&gt;
&lt;td&gt;Transitioning&lt;/td&gt;
&lt;td&gt;Large, widening&lt;/td&gt;
&lt;td&gt;Gamma flip breach incoming — highest-risk setup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These combinations replace 80% of the chart patterns most 0DTE traders reach for. Not a crystal ball — a statement about which way dealer hedging flows will push, which is the structural component every chart pattern is a shadow of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulling SPXW 0DTE from an API
&lt;/h2&gt;

&lt;p&gt;Here's how I fetch it. Replace with whatever provider you use, or swap &lt;code&gt;SPXW&lt;/code&gt; for &lt;code&gt;SPY&lt;/code&gt;, &lt;code&gt;QQQ&lt;/code&gt;, &lt;code&gt;NVDA&lt;/code&gt; — the shape is the same across symbols:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://lab.flashalpha.com/v1/exposure/zero-dte/SPXW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Regime:          &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0DTE gamma flip: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pin score:       &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pin_risk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pin_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Remaining move:  ±$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expected_move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;remaining_1sd_dollars&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pct of chain:    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pct_of_total_gex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful fields if you're building UI on top of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;regime.label&lt;/code&gt; — &lt;code&gt;positive_gamma&lt;/code&gt;, &lt;code&gt;negative_gamma&lt;/code&gt;, or &lt;code&gt;undetermined&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;regime.gamma_flip&lt;/code&gt; — 0DTE-specific flip level (distinct from the full-chain GEX flip, which matters less intraday)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pin_risk.pin_score&lt;/code&gt; — 0 to 100, weighted composite&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pin_risk.magnet_strike&lt;/code&gt; — the strike being pulled toward&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expected_move.remaining_1sd_dollars&lt;/code&gt; — shrinking dollar 1σ until close&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expected_move.upper_bound&lt;/code&gt; / &lt;code&gt;lower_bound&lt;/code&gt; — the band you draw on a GEX chart&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hedging.spot_up_half_pct&lt;/code&gt; / &lt;code&gt;spot_down_half_pct&lt;/code&gt; — dealer shares per ±0.5% move (more relevant for 0DTE than ±1%)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;decay.gamma_acceleration&lt;/code&gt; — 0DTE ATM gamma / 7DTE ATM gamma, typically 2-5x morning, 10x+ near close&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strikes[]&lt;/code&gt; — per-strike call GEX, put GEX, net GEX, call/put OI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're rate-limited on a free tier, cache the regime fields for 60 seconds and only refresh &lt;code&gt;strikes[]&lt;/code&gt; on user interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPXW vs SPY for 0DTE — developer-relevant differences
&lt;/h2&gt;

&lt;p&gt;Both are liquid enough to treat as interchangeable index exposure. For modeling purposes, the practical differences:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SPXW&lt;/th&gt;
&lt;th&gt;SPY&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Notional per contract&lt;/td&gt;
&lt;td&gt;~10× SPY&lt;/td&gt;
&lt;td&gt;1×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;US tax treatment&lt;/td&gt;
&lt;td&gt;60/40 under §1256&lt;/td&gt;
&lt;td&gt;Short-term capital gains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settlement&lt;/td&gt;
&lt;td&gt;Cash (European)&lt;/td&gt;
&lt;td&gt;Physical (American, assignment risk)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bid-ask at ATM 0DTE&lt;/td&gt;
&lt;td&gt;~$0.05-0.10 index&lt;/td&gt;
&lt;td&gt;~$0.01-0.02 on 1/10-size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing days&lt;/td&gt;
&lt;td&gt;Mon-Fri&lt;/td&gt;
&lt;td&gt;Mon/Wed/Fri (Tue/Thu added 2024)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For any systematic strategy, SPXW is the institutional default. For retail-sized accounts, SPY has finer-grained pricing. For the analytics code you write, the per-strike gamma and pin behavior are identical — just different symbol in the path parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TL;DR
&lt;/h2&gt;

&lt;p&gt;"SPX 0DTE" on any desk or chat means SPXW. Cash-settled, European-style, same-day expirations every trading day. The four numbers that describe today's session — regime, pin score, remaining expected move, hedging at ±0.5% — change every time the tape moves.&lt;/p&gt;

&lt;p&gt;Read them live. Don't cache them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I maintain &lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt; — an options analytics API with pre-computed GEX, DEX, VEX, CHEX and SVI volatility surfaces. Free tier, no card. Happy to answer questions about the data model in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>api</category>
      <category>finance</category>
    </item>
    <item>
      <title>I Backtested My Own GEX Product Across 8 Years of SPY. Most of It Is Just VIX.</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Thu, 23 Apr 2026 10:06:56 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/i-backtested-my-own-gex-product-across-8-years-of-spy-most-of-it-is-just-vix-a53</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/i-backtested-my-own-gex-product-across-8-years-of-spy-most-of-it-is-just-vix-a53</guid>
      <description>&lt;p&gt;I sell a dealer-exposure API. GEX, DEX, VEX, CHEX — the whole Greek-exposure stack. So when I tell you the backtest on my own product is mostly a VIX proxy, that is not a competitive hit piece. It is the result I got when I ran the test honestly, and it is the test I wanted before I bought any of this from anyone else.&lt;/p&gt;

&lt;p&gt;Most options dashboards present four dealer-exposure Greeks as four independent signals. The pitch is intuitive: dealers hedge, those hedges create flows, and those flows forecast vol, returns, or IV changes. The mechanical story is real. The predictive story is much thinner once you control for what a trader can already see on a free VIX chart.&lt;/p&gt;

&lt;p&gt;I pre-registered the hypotheses. Then I ran the statistics. Here is what 1,972 SPY trading days say.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tested
&lt;/h2&gt;

&lt;p&gt;One row per SPY EOD. Each row carries dealer-signed GEX, DEX, VEX, CHEX, gamma flip, VIX, ATM IV, realized volatility, and forward outcomes. Positive exposure means dealers are net long that Greek.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Universe&lt;/td&gt;
&lt;td&gt;SPY only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window&lt;/td&gt;
&lt;td&gt;2018-04-16 to 2026-04-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sample&lt;/td&gt;
&lt;td&gt;1,972 EOD snapshots; 1,971 usable next-day outcomes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary signals&lt;/td&gt;
&lt;td&gt;GEX, DEX, VEX, CHEX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary outcomes&lt;/td&gt;
&lt;td&gt;Next-day realized vol, next-day return, next-day ATM IV change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controls&lt;/td&gt;
&lt;td&gt;VIX, then VIX + ATM IV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary tests&lt;/td&gt;
&lt;td&gt;Quintile sorts, top-minus-bottom t-tests, Spearman rank correlation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The question is not "do dealer exposures mean anything?" They do. The question is: &lt;strong&gt;do they add predictive information after you already know VIX and ATM IV?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Naive GEX looks excellent
&lt;/h2&gt;

&lt;p&gt;Sort every day by GEX into quintiles. Measure next-day realized vol as &lt;code&gt;|log_return| * sqrt(252)&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GEX quintile&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Mean net_gex ($B)&lt;/th&gt;
&lt;th&gt;Mean next-day RV (%)&lt;/th&gt;
&lt;th&gt;Median next-day RV (%)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q1 — most negative&lt;/td&gt;
&lt;td&gt;395&lt;/td&gt;
&lt;td&gt;-7.91&lt;/td&gt;
&lt;td&gt;16.97&lt;/td&gt;
&lt;td&gt;13.54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q2 — moderately negative&lt;/td&gt;
&lt;td&gt;394&lt;/td&gt;
&lt;td&gt;-2.79&lt;/td&gt;
&lt;td&gt;18.57&lt;/td&gt;
&lt;td&gt;12.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3 — roughly neutral&lt;/td&gt;
&lt;td&gt;394&lt;/td&gt;
&lt;td&gt;+0.25&lt;/td&gt;
&lt;td&gt;12.71&lt;/td&gt;
&lt;td&gt;10.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4 — moderately positive&lt;/td&gt;
&lt;td&gt;394&lt;/td&gt;
&lt;td&gt;+2.97&lt;/td&gt;
&lt;td&gt;9.24&lt;/td&gt;
&lt;td&gt;6.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5 — most positive&lt;/td&gt;
&lt;td&gt;394&lt;/td&gt;
&lt;td&gt;+6.50&lt;/td&gt;
&lt;td&gt;6.34&lt;/td&gt;
&lt;td&gt;4.86&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Q5 minus Q1: &lt;strong&gt;-10.63 vol points&lt;/strong&gt;, t = -13.00, p = 1.0e-33. Spearman ρ = &lt;strong&gt;-0.36&lt;/strong&gt; on n=1,971.&lt;/p&gt;

&lt;p&gt;This is the chart that sells a subscription. It is also not enough. A strong raw GEX backtest can still be a volatility-regime proxy — negative-GEX and high-VIX days often describe the same market state.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Fact check: the Q1/Q2 mean inversion is real. Outlier-heavy COVID-era days; medians are clean. The rank correlation matters more than strict mean monotonicity.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The control that changes the story
&lt;/h2&gt;

&lt;p&gt;Regress both signal and outcome on the control set, then Spearman-correlate the residuals. Bonferroni significance for the four primary tests is p &amp;lt; 0.0125.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal → outcome&lt;/th&gt;
&lt;th&gt;Raw ρ (p)&lt;/th&gt;
&lt;th&gt;After VIX (p)&lt;/th&gt;
&lt;th&gt;After VIX + ATM IV (p)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GEX → next-day RV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.36 (4.6e-60)&lt;/td&gt;
&lt;td&gt;-0.14 (1.2e-9)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-0.03 (0.18)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEX → next-day return&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.03 (0.19)&lt;/td&gt;
&lt;td&gt;+0.01 (0.69)&lt;/td&gt;
&lt;td&gt;+0.02 (0.40)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VEX → next-day ATM IV change&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.16 (2.1e-13)&lt;/td&gt;
&lt;td&gt;-0.05 (0.02)&lt;/td&gt;
&lt;td&gt;-0.01 (0.77)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHEX → next-day return&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.05 (0.03)&lt;/td&gt;
&lt;td&gt;-0.01 (0.63)&lt;/td&gt;
&lt;td&gt;-0.00 (0.93)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The raw signals are mostly volatility signals wearing more sophisticated labels. GEX survives the VIX-only control, but at ~40% of the raw magnitude. Add ATM IV and the GEX residual drops to ρ = -0.03, p = 0.18. DEX, VEX, and CHEX do not carry robust independent predictive information in these residualized rank tests.&lt;/p&gt;

&lt;p&gt;GEX top-minus-bottom realized-vol difference tells the same story: &lt;strong&gt;-10.63&lt;/strong&gt; vol points raw, &lt;strong&gt;-3.15&lt;/strong&gt; after VIX, &lt;strong&gt;-0.99&lt;/strong&gt; after VIX + ATM IV (p = 0.25).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest result:&lt;/strong&gt; GEX is not fake. The raw effect is real. The &lt;em&gt;incremental&lt;/em&gt; information after VIX and ATM IV is the part that fails. Useful regime descriptor ≠ independent forecasting edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  GEX × VIX double-sort
&lt;/h2&gt;

&lt;p&gt;Double-sort: first split by VIX quintile, then within each VIX bucket split by GEX. Cells = mean next-day realized vol (%).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;VIX \ GEX&lt;/th&gt;
&lt;th&gt;Q1 most negative&lt;/th&gt;
&lt;th&gt;Q2&lt;/th&gt;
&lt;th&gt;Q3&lt;/th&gt;
&lt;th&gt;Q4&lt;/th&gt;
&lt;th&gt;Q5 most positive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;V1&lt;/strong&gt; lowest VIX&lt;/td&gt;
&lt;td&gt;8.02&lt;/td&gt;
&lt;td&gt;7.32&lt;/td&gt;
&lt;td&gt;6.62&lt;/td&gt;
&lt;td&gt;5.22&lt;/td&gt;
&lt;td&gt;5.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;V2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11.70&lt;/td&gt;
&lt;td&gt;10.30&lt;/td&gt;
&lt;td&gt;8.85&lt;/td&gt;
&lt;td&gt;6.48&lt;/td&gt;
&lt;td&gt;6.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;V3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.00&lt;/td&gt;
&lt;td&gt;12.05&lt;/td&gt;
&lt;td&gt;12.15&lt;/td&gt;
&lt;td&gt;9.62&lt;/td&gt;
&lt;td&gt;8.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;V4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.87&lt;/td&gt;
&lt;td&gt;15.35&lt;/td&gt;
&lt;td&gt;17.18&lt;/td&gt;
&lt;td&gt;12.31&lt;/td&gt;
&lt;td&gt;8.06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;V5&lt;/strong&gt; highest VIX&lt;/td&gt;
&lt;td&gt;20.57&lt;/td&gt;
&lt;td&gt;24.91&lt;/td&gt;
&lt;td&gt;37.69&lt;/td&gt;
&lt;td&gt;21.71&lt;/td&gt;
&lt;td&gt;15.91&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;V1 and V2 are textbook. V3 and V4 still show much lower RV in positive-GEX buckets, but not strictly monotonic. V5, the highest-VIX bucket, is a non-monotonic mess.&lt;/p&gt;

&lt;p&gt;Practical takeaway: GEX's predictive value is mostly a calm-to-moderate phenomenon. In the top VIX quintile, where an extra risk signal would matter most, this double-sort does not show a clean edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposure verdicts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GEX: weak survivor.&lt;/strong&gt; Useful as a regime descriptor; modestly incremental over VIX alone; not significant after VIX + ATM IV. Raw backtest is real, but it is mostly a vol-regime backtest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DEX: dead on arrival.&lt;/strong&gt; Top-minus-bottom next-day return difference: 0.00%, t = 0.04, p = 0.97. Raw Spearman ρ = -0.03, p = 0.19. DEX does not predict next-day SPY direction in this EOD test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VEX: mostly a VIX proxy.&lt;/strong&gt; Raw ρ = -0.16, p = 2.1e-13. But VEX correlates with VIX at +0.72 and with ATM IV at +0.76. After controlling for VIX + ATM IV, residualized VEX falls to ρ = -0.01, p = 0.77.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CHEX: weak and fragile.&lt;/strong&gt; One interesting raw result — sign agreement between CHEX and next-day return is 54.9% on n=1,967, p = 1.5e-5. But the pre-registered residualized rank test collapses under VIX control (ρ = -0.01, p = 0.63) and under VIX + ATM IV (ρ = -0.00, p = 0.93). A separate OLS spec does show a significant CHEX coefficient after VIX + AR(1) controls, so: &lt;em&gt;CHEX is not robust in the rank/quintile framework used for the primary article claim.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they overlap
&lt;/h2&gt;

&lt;p&gt;Spearman rank correlations across all 1,972 observations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GEX&lt;/th&gt;
&lt;th&gt;DEX&lt;/th&gt;
&lt;th&gt;VEX&lt;/th&gt;
&lt;th&gt;CHEX&lt;/th&gt;
&lt;th&gt;VIX&lt;/th&gt;
&lt;th&gt;ATM IV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GEX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;+0.73&lt;/td&gt;
&lt;td&gt;-0.54&lt;/td&gt;
&lt;td&gt;+0.59&lt;/td&gt;
&lt;td&gt;-0.49&lt;/td&gt;
&lt;td&gt;-0.63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+0.73&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;-0.89&lt;/td&gt;
&lt;td&gt;+0.68&lt;/td&gt;
&lt;td&gt;-0.58&lt;/td&gt;
&lt;td&gt;-0.67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VEX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.54&lt;/td&gt;
&lt;td&gt;-0.89&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;-0.65&lt;/td&gt;
&lt;td&gt;+0.72&lt;/td&gt;
&lt;td&gt;+0.76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CHEX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+0.59&lt;/td&gt;
&lt;td&gt;+0.68&lt;/td&gt;
&lt;td&gt;-0.65&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;-0.39&lt;/td&gt;
&lt;td&gt;-0.46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VIX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.49&lt;/td&gt;
&lt;td&gt;-0.58&lt;/td&gt;
&lt;td&gt;+0.72&lt;/td&gt;
&lt;td&gt;-0.39&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;td&gt;+0.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ATM IV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.63&lt;/td&gt;
&lt;td&gt;-0.67&lt;/td&gt;
&lt;td&gt;+0.76&lt;/td&gt;
&lt;td&gt;-0.46&lt;/td&gt;
&lt;td&gt;+0.91&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DEX ↔ VEX = -0.89. VEX ↔ VIX = +0.72. VIX ↔ ATM IV = +0.91. That does not make the exposure Greeks useless, but it makes them dangerous to treat as independent features. If someone sells GEX/DEX/VEX/CHEX as four unrelated predictive signals, this correlation matrix is the rebuttal. They are four views of the same options chain, and much of what they capture is already in volatility level. One-and-a-half signals, not four.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-VIX regime test
&lt;/h2&gt;

&lt;p&gt;The regime split is where a GEX backtest matters most for real trading decisions. The high-VIX row was pre-registered, not cherry-picked.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regime&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Top-minus-bottom RV diff&lt;/th&gt;
&lt;th&gt;t&lt;/th&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;th&gt;ρ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All days&lt;/td&gt;
&lt;td&gt;1,971&lt;/td&gt;
&lt;td&gt;-10.63&lt;/td&gt;
&lt;td&gt;-13.00&lt;/td&gt;
&lt;td&gt;1.0e-33&lt;/td&gt;
&lt;td&gt;-0.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-COVID&lt;/td&gt;
&lt;td&gt;463&lt;/td&gt;
&lt;td&gt;-11.43&lt;/td&gt;
&lt;td&gt;-6.78&lt;/td&gt;
&lt;td&gt;4.5e-10&lt;/td&gt;
&lt;td&gt;-0.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;COVID shock&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;-59.88&lt;/td&gt;
&lt;td&gt;-3.88&lt;/td&gt;
&lt;td&gt;0.001&lt;/td&gt;
&lt;td&gt;-0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-COVID&lt;/td&gt;
&lt;td&gt;1,436&lt;/td&gt;
&lt;td&gt;-9.62&lt;/td&gt;
&lt;td&gt;-9.58&lt;/td&gt;
&lt;td&gt;8.7e-20&lt;/td&gt;
&lt;td&gt;-0.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low-VIX days&lt;/td&gt;
&lt;td&gt;1,478&lt;/td&gt;
&lt;td&gt;-8.09&lt;/td&gt;
&lt;td&gt;-11.81&lt;/td&gt;
&lt;td&gt;3.9e-28&lt;/td&gt;
&lt;td&gt;-0.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High-VIX days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;493&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1.89&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-0.78&lt;/td&gt;
&lt;td&gt;0.44&lt;/td&gt;
&lt;td&gt;-0.02&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across all days GEX looks excellent. Inside high-VIX days the effect is statistically absent. That is the trading lesson: GEX is best at &lt;em&gt;labeling&lt;/em&gt; calm regimes. It is not a clean crisis detector in this EOD SPY sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Train/test stability
&lt;/h2&gt;

&lt;p&gt;A 70/30 chronological split looks impressive at first glance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Split&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Top-minus-bottom diff&lt;/th&gt;
&lt;th&gt;ρ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-sample&lt;/td&gt;
&lt;td&gt;2018-04 → 2023-11&lt;/td&gt;
&lt;td&gt;1,380&lt;/td&gt;
&lt;td&gt;-10.77&lt;/td&gt;
&lt;td&gt;-0.367&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Out-of-sample&lt;/td&gt;
&lt;td&gt;2023-12 → 2026-04&lt;/td&gt;
&lt;td&gt;591&lt;/td&gt;
&lt;td&gt;-10.79&lt;/td&gt;
&lt;td&gt;-0.374&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Naive read: GEX is exceptionally robust. Cautious read: GEX is proxying a stable volatility-regime variable, and VIX + ATM IV absorb the effect. The residualized controls point to the second explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What traders should do with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use GEX as a regime label, not a standalone forecast.&lt;/strong&gt; Positive GEX describes a calmer market state. It does not, by itself, survive the VIX + ATM IV control as an independent next-day realized-vol signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not count the exposure stack as four independent factors.&lt;/strong&gt; DEX and VEX are near mirror images in this sample; VEX is tightly linked to vol level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demand orthogonal tests.&lt;/strong&gt; A vendor chart that beats a coin flip is not enough. The right question is whether the signal adds anything after obvious baselines — VIX, ATM IV, prior return, outcome persistence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlation ≠ tradable PnL.&lt;/strong&gt; Even GEX's VIX-only residual effect (ρ = -0.14) needs transaction costs, latency, data availability, and execution rules before it is a strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EOD panel only.&lt;/strong&gt; 16:00 ET snapshots vs next-day outcomes. The canonical intraday CHEX claim — charm flow into the last hour — needs a separate minute-level study.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SPY only.&lt;/strong&gt; Deepest, most-hedged ETF. Single stocks and index options may behave differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linear residual controls.&lt;/strong&gt; OLS residualization. A nonlinear model could recover interactions not measured here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlation, not PnL.&lt;/strong&gt; Statistical association, not executable trades after costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0DTE deserves its own article.&lt;/strong&gt; Post-COVID includes the 0DTE era, but this is not a dedicated intraday 0DTE mechanics test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dealer-sign convention matters.&lt;/strong&gt; Positive = dealers net long that Greek. Opposite conventions invert signs but not conclusions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress obs are limited.&lt;/strong&gt; COVID, 2022, 2025 provide meaningful turmoil, but high-VIX inference still rests on fewer days than calm-market inference.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to reproduce
&lt;/h2&gt;

&lt;p&gt;Three endpoints on the FlashAlpha Historical API, looped across 1,972 trading days, joined on &lt;code&gt;(ts, symbol)&lt;/code&gt;, outcomes shifted -1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dealer-exposure summary (GEX, DEX, VEX, CHEX, gamma flip, walls)&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/exposure/summary/SPY?at=2024-06-14T20:00:00Z"&lt;/span&gt;

&lt;span class="c"&gt;# Stock summary (spot, VIX context, ATM IV, realized vol)&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/stock/SPY/summary?at=2024-06-14T20:00:00Z"&lt;/span&gt;

&lt;span class="c"&gt;# VRP snapshot (implied-vs-realized spread, regime, harvest score)&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/vrp/SPY?at=2024-06-14T20:00:00Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Residualize signals and outcomes on VIX, then on VIX + ATM IV. Re-run the quintile sorts, Spearman tests, and regime splits.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Gamma exposure works at the raw regime level, but the independent GEX edge is much smaller than the marketing version. Across 1,972 SPY days, raw GEX → next-day realized vol is ρ = -0.36. After VIX + ATM IV controls, it is ρ = -0.03 with p = 0.18. DEX has no next-day return signal, VEX is mostly a VIX/IV proxy, and CHEX is fragile in residualized rank tests. Use dealer exposure as context. Do not treat it as four clean standalone alpha signals.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://flashalpha.com/articles/gex-dex-vex-chex-8-year-backtest-spy-vix-control" rel="noopener noreferrer"&gt;flashalpha.com&lt;/a&gt;. The full version includes the raw CSVs, pre-registered hypotheses file, and Python re-run scripts for every table above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>options</category>
      <category>quant</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>I Priced 18 Million SPY Put Spreads Across 8 Years. Every Bucket Was -EV. Every Year Made Money.</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 22 Apr 2026 11:42:08 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/i-priced-18-million-spy-put-spreads-across-8-years-every-bucket-was-ev-every-year-made-money-4i3k</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/i-priced-18-million-spy-put-spreads-across-8-years-every-bucket-was-ev-every-year-made-money-4i3k</guid>
      <description>&lt;p&gt;When a blog post tells you "sell 30-delta puts at 45 DTE with a 50% profit take," you have no way to know whether that rule is near-optimal, arbitrary, or picked because it happens to look good in the author's sample. The only cure for cherry-picking is not to pick — compute the full surface and show where the edge actually lives.&lt;/p&gt;

&lt;p&gt;So I did.&lt;/p&gt;

&lt;p&gt;For every trading day from 2018-04-16 to 2026-04-02, for every SPY expiry with a usable SVI fit, for every short-strike delta in &lt;code&gt;[0.05, 0.40]&lt;/code&gt; and every width in &lt;code&gt;{1, 2, 3, 5, 10, 20}&lt;/code&gt;, I priced the theoretical credit spread under Black-Scholes with surface-consistent IVs, then joined each row with the actual SPY close at expiry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18.3M rows. 1,698 trading days. 1,525 unique expiries. ~48 seconds to rebuild from raw SVI parameters and daily forwards.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what fell out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The headline numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average theoretical EV (flat-vol BSM)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-$0.48&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average realized P&amp;amp;L (joined with spot at expiry)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$0.58&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realized edge (realized − theoretical)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$1.06&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realized winrate&lt;/td&gt;
&lt;td&gt;92.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max-loss rate&lt;/td&gt;
&lt;td&gt;5.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every bucket -EV by theory. Every year +P&amp;amp;L by reality. The gap is the variance risk premium plus SPY's structural drift — both invisible to flat-vol pricing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 — Every theoretical bucket is negative EV
&lt;/h2&gt;

&lt;p&gt;Across all 18,307,256 rows, average theoretical EV is &lt;strong&gt;-$0.484&lt;/strong&gt;. There is no slice of the (DTE, delta, width) grid where theoretical EV is positive. Under the flat-vol lens, put credit spreads are net losers in expectation.&lt;/p&gt;

&lt;p&gt;Flat-vol pricing is a trap. If it's your only model, the conclusion is clear and wrong: "don't sell put spreads." Flat-vol doesn't price VRP or SPY drift — it only prices the skew the market is literally quoting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 — Realized P&amp;amp;L is positive almost everywhere
&lt;/h2&gt;

&lt;p&gt;Average realized P&amp;amp;L across the full matrix: &lt;strong&gt;+$0.577&lt;/strong&gt;. The skew premium was paid fully, and then some.&lt;/p&gt;

&lt;p&gt;Year-by-year breakdown, restricted to a single strategy footprint (width = $5, short delta 0.20-0.30):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Theo EV&lt;/th&gt;
&lt;th&gt;Realized P&amp;amp;L&lt;/th&gt;
&lt;th&gt;Edge&lt;/th&gt;
&lt;th&gt;Winrate&lt;/th&gt;
&lt;th&gt;Max-loss&lt;/th&gt;
&lt;th&gt;Avg IV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;119,986&lt;/td&gt;
&lt;td&gt;-$0.43&lt;/td&gt;
&lt;td&gt;+$0.97&lt;/td&gt;
&lt;td&gt;+$1.39&lt;/td&gt;
&lt;td&gt;97.8%&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;21.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;124,447&lt;/td&gt;
&lt;td&gt;-$0.47&lt;/td&gt;
&lt;td&gt;+$0.86&lt;/td&gt;
&lt;td&gt;+$1.33&lt;/td&gt;
&lt;td&gt;97.5%&lt;/td&gt;
&lt;td&gt;1.8%&lt;/td&gt;
&lt;td&gt;18.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026 Q1&lt;/td&gt;
&lt;td&gt;29,114&lt;/td&gt;
&lt;td&gt;-$0.57&lt;/td&gt;
&lt;td&gt;+$0.86&lt;/td&gt;
&lt;td&gt;+$1.43&lt;/td&gt;
&lt;td&gt;97.0%&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;td&gt;25.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;131,318&lt;/td&gt;
&lt;td&gt;-$0.51&lt;/td&gt;
&lt;td&gt;+$0.84&lt;/td&gt;
&lt;td&gt;+$1.35&lt;/td&gt;
&lt;td&gt;95.6%&lt;/td&gt;
&lt;td&gt;3.7%&lt;/td&gt;
&lt;td&gt;32.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;117,429&lt;/td&gt;
&lt;td&gt;-$0.54&lt;/td&gt;
&lt;td&gt;+$0.77&lt;/td&gt;
&lt;td&gt;+$1.31&lt;/td&gt;
&lt;td&gt;96.3%&lt;/td&gt;
&lt;td&gt;3.1%&lt;/td&gt;
&lt;td&gt;21.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;70,470&lt;/td&gt;
&lt;td&gt;-$0.40&lt;/td&gt;
&lt;td&gt;+$0.50&lt;/td&gt;
&lt;td&gt;+$0.91&lt;/td&gt;
&lt;td&gt;89.8%&lt;/td&gt;
&lt;td&gt;8.3%&lt;/td&gt;
&lt;td&gt;19.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2018 (Apr-Dec)&lt;/td&gt;
&lt;td&gt;46,479&lt;/td&gt;
&lt;td&gt;-$0.41&lt;/td&gt;
&lt;td&gt;+$0.28&lt;/td&gt;
&lt;td&gt;+$0.69&lt;/td&gt;
&lt;td&gt;84.9%&lt;/td&gt;
&lt;td&gt;11.5%&lt;/td&gt;
&lt;td&gt;21.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;129,952&lt;/td&gt;
&lt;td&gt;-$0.62&lt;/td&gt;
&lt;td&gt;+$0.21&lt;/td&gt;
&lt;td&gt;+$0.83&lt;/td&gt;
&lt;td&gt;85.7%&lt;/td&gt;
&lt;td&gt;12.2%&lt;/td&gt;
&lt;td&gt;25.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;108,386&lt;/td&gt;
&lt;td&gt;-$0.52&lt;/td&gt;
&lt;td&gt;+$0.02&lt;/td&gt;
&lt;td&gt;+$0.54&lt;/td&gt;
&lt;td&gt;78.5%&lt;/td&gt;
&lt;td&gt;18.2%&lt;/td&gt;
&lt;td&gt;28.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every year profitable. Every year's theoretical EV negative. The only year close to breakeven: 2022 — the one true bear year in the sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3 — Capital efficiency flips the verdict on short DTE
&lt;/h2&gt;

&lt;p&gt;Absolute P&amp;amp;L per spread goes up with DTE. Annualized return on capital does not. For short delta 0.20-0.30, width $5:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DTE bucket&lt;/th&gt;
&lt;th&gt;Avg credit&lt;/th&gt;
&lt;th&gt;Realized P&amp;amp;L&lt;/th&gt;
&lt;th&gt;Per-trade ROC&lt;/th&gt;
&lt;th&gt;Annualized ROC&lt;/th&gt;
&lt;th&gt;Winrate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0-7&lt;/td&gt;
&lt;td&gt;$0.68&lt;/td&gt;
&lt;td&gt;+$0.042&lt;/td&gt;
&lt;td&gt;0.97%&lt;/td&gt;
&lt;td&gt;75.3%&lt;/td&gt;
&lt;td&gt;83.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8-21&lt;/td&gt;
&lt;td&gt;$0.74&lt;/td&gt;
&lt;td&gt;+$0.069&lt;/td&gt;
&lt;td&gt;1.62%&lt;/td&gt;
&lt;td&gt;41.0%&lt;/td&gt;
&lt;td&gt;84.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22-45&lt;/td&gt;
&lt;td&gt;$0.76&lt;/td&gt;
&lt;td&gt;+$0.121&lt;/td&gt;
&lt;td&gt;2.86%&lt;/td&gt;
&lt;td&gt;32.1%&lt;/td&gt;
&lt;td&gt;85.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46-90&lt;/td&gt;
&lt;td&gt;$0.79&lt;/td&gt;
&lt;td&gt;+$0.255&lt;/td&gt;
&lt;td&gt;6.05%&lt;/td&gt;
&lt;td&gt;32.7%&lt;/td&gt;
&lt;td&gt;88.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;91+&lt;/td&gt;
&lt;td&gt;$1.06&lt;/td&gt;
&lt;td&gt;+$0.752&lt;/td&gt;
&lt;td&gt;19.1%&lt;/td&gt;
&lt;td&gt;17.7%&lt;/td&gt;
&lt;td&gt;93.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four caveats that matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fill costs eat the 0-7 DTE number alive.&lt;/strong&gt; Mid-quote model prices + real bid/ask on 0DTE SPY = most of that $0.04 edge gone. Upper bound, not strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;91+ DTE locks capital for 394 days on average.&lt;/strong&gt; +19% per-trade only annualizes to ~18%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;22-90 DTE is the comfortable zone.&lt;/strong&gt; ~32-33% annualized, winrates in the high 80s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No profit takes, no stops, held to expiry.&lt;/strong&gt; Overlays change everything.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Finding 4 — Risk-adjusted: the best Sharpe isn't where you'd guess
&lt;/h2&gt;

&lt;p&gt;IID Sharpe from the per-trade P&amp;amp;L distribution, 15 representative profiles (one trade per day, closest to target delta):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profile&lt;/th&gt;
&lt;th&gt;Ann Sharpe&lt;/th&gt;
&lt;th&gt;Ann ROC&lt;/th&gt;
&lt;th&gt;Winrate&lt;/th&gt;
&lt;th&gt;Max-loss&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;45 DTE 40Δ $10 wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.49&lt;/td&gt;
&lt;td&gt;67.6%&lt;/td&gt;
&lt;td&gt;79.5%&lt;/td&gt;
&lt;td&gt;12.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;45 DTE 40Δ $5 wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.48&lt;/td&gt;
&lt;td&gt;74.8%&lt;/td&gt;
&lt;td&gt;78.2%&lt;/td&gt;
&lt;td&gt;17.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45 DTE 30Δ $5 wide&lt;/td&gt;
&lt;td&gt;0.47&lt;/td&gt;
&lt;td&gt;57.2%&lt;/td&gt;
&lt;td&gt;85.2%&lt;/td&gt;
&lt;td&gt;12.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180 DTE 25Δ $5 wide&lt;/td&gt;
&lt;td&gt;0.46&lt;/td&gt;
&lt;td&gt;22.8%&lt;/td&gt;
&lt;td&gt;91.0%&lt;/td&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45 DTE 25Δ $5 wide&lt;/td&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;44.3%&lt;/td&gt;
&lt;td&gt;87.9%&lt;/td&gt;
&lt;td&gt;9.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 DTE 40Δ $5 wide&lt;/td&gt;
&lt;td&gt;0.39&lt;/td&gt;
&lt;td&gt;142.5%&lt;/td&gt;
&lt;td&gt;74.1%&lt;/td&gt;
&lt;td&gt;16.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 DTE 40Δ $5 wide&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;65.9%&lt;/td&gt;
&lt;td&gt;76.9%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What Sharpe 0.48 actually means: SPY buy-and-hold over 2018-2026 runs Sharpe ~0.55-0.70. Typical long-only equity strategies: 0.4-0.8. The best put-spread profile here is &lt;strong&gt;competitive with passive SPY, not a free lunch&lt;/strong&gt;. The honest use case is diversification — theta/VRP returns only weakly correlated to daily SPY path, so a small allocation lowers total-portfolio vol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 5 — IV regime counter-intuitively favors crisis
&lt;/h2&gt;

&lt;p&gt;Bucketing the 22-45 DTE, 20-30Δ, $5-wide set by short-strike IV:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IV regime&lt;/th&gt;
&lt;th&gt;Theo EV&lt;/th&gt;
&lt;th&gt;Realized P&amp;amp;L&lt;/th&gt;
&lt;th&gt;Edge&lt;/th&gt;
&lt;th&gt;Winrate&lt;/th&gt;
&lt;th&gt;Max-loss&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;crisis (30+)&lt;/td&gt;
&lt;td&gt;-$0.42&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$0.54&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+$0.95&lt;/td&gt;
&lt;td&gt;92.8%&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;td&gt;15,555&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;high (22-30)&lt;/td&gt;
&lt;td&gt;-$0.42&lt;/td&gt;
&lt;td&gt;+$0.16&lt;/td&gt;
&lt;td&gt;+$0.59&lt;/td&gt;
&lt;td&gt;86.3%&lt;/td&gt;
&lt;td&gt;10.6%&lt;/td&gt;
&lt;td&gt;26,486&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;elevated (16-22)&lt;/td&gt;
&lt;td&gt;-$0.42&lt;/td&gt;
&lt;td&gt;+$0.05&lt;/td&gt;
&lt;td&gt;+$0.47&lt;/td&gt;
&lt;td&gt;85.1%&lt;/td&gt;
&lt;td&gt;11.5%&lt;/td&gt;
&lt;td&gt;30,132&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;normal (12-16)&lt;/td&gt;
&lt;td&gt;-$0.38&lt;/td&gt;
&lt;td&gt;-$0.10&lt;/td&gt;
&lt;td&gt;+$0.28&lt;/td&gt;
&lt;td&gt;81.7%&lt;/td&gt;
&lt;td&gt;13.1%&lt;/td&gt;
&lt;td&gt;17,848&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;calm (&amp;lt;12 IV)&lt;/td&gt;
&lt;td&gt;-$0.37&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-$0.66&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-$0.30&lt;/td&gt;
&lt;td&gt;71.3%&lt;/td&gt;
&lt;td&gt;22.6%&lt;/td&gt;
&lt;td&gt;1,676&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Calm is the worst regime. Crisis is the best. The VRP in one table — highest edge at the point of maximum perceived danger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats that should be loud
&lt;/h2&gt;

&lt;p&gt;A clean dashboard is seductive. Several assumptions push the real-world numbers down from the model numbers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No transaction costs.&lt;/strong&gt; Short-DTE in particular: average $0.11 credit at 7 DTE 40Δ, realistic slippage budget $0.10-0.20. The 142% annualized ROC is a model ceiling, not a target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theoretical prices are mid-quote proxies.&lt;/strong&gt; Reconstructed from SVI + BS. Executable credit closer to bid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No liquidity filter.&lt;/strong&gt; Thin strikes where SVI fit ≠ strikes that fill at modeled price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stops, no profit takes.&lt;/strong&gt; Held to expiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IID Sharpe is an upper bound.&lt;/strong&gt; Losses cluster in bear regimes (clearly 2022). Real drawdowns &amp;gt; IID math implies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single underlying, structural upward drift.&lt;/strong&gt; Single-name, earnings-driven underlyings look much worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 years is short.&lt;/strong&gt; One real bear year. Confidence intervals on "crisis wins" are wide.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None invalidate the shape; all push the magnitude down. The theoretical-vs-realized gap is real; the exact basis-point edge is smaller than this model suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I reproduced this
&lt;/h2&gt;

&lt;p&gt;Three ingredients: historical SVI surface parameters, daily forwards, SPY spot at expiry. All three via the &lt;code&gt;flashalpha&lt;/code&gt; Python package — same surface fits the live product uses, walk-forward by default, no look-ahead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;flashalpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flashalpha&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;surfaces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;historical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2018-04-16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-02&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# surfaces is a DataFrame: ts, expiry, dte, forward,
# svi_a, svi_b, svi_rho, svi_m, svi_sigma
# From there: reconstruct IV at every strike, price BS puts,
# enumerate spread permutations, join with spot at expiry.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Over 8 years and 18M simulated SPY put credit spreads, the market priced skew premium into every bucket. Flat-vol BSM theoretical EV averaged &lt;strong&gt;-$0.48&lt;/strong&gt;. Actual realized P&amp;amp;L, joined with real spot at expiry, averaged &lt;strong&gt;+$0.58&lt;/strong&gt;. The +$1.06 gap is the variance risk premium plus SPY's drift, paid out in cash. Put selling on SPY is renting the market's fear. The rent has been paid every year in the sample.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://flashalpha.com/articles/spy-put-credit-spread-matrix-8-year-backtest-theoretical-vs-realized" rel="noopener noreferrer"&gt;flashalpha.com&lt;/a&gt;. The full version has yearly stress-test breakdowns, delta-by-delta tables at 30-DTE, the full Sharpe math, and a $100k portfolio scaling example with 2022 drawdown stress.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>options</category>
      <category>quant</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Free GEX Data — Gamma Exposure Levels for 6,000+ US Tickers (No Signup, No Credit Card)</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Tue, 21 Apr 2026 15:27:10 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/free-gex-data-gamma-exposure-levels-for-6000-us-tickers-no-signup-no-credit-card-49da</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/free-gex-data-gamma-exposure-levels-for-6000-us-tickers-no-signup-no-credit-card-49da</guid>
      <description>&lt;p&gt;Most GEX providers paywall the levels that actually matter. You can find a SPY screenshot on Twitter, but try pulling data for ARM or SLV and you hit a subscription prompt.&lt;/p&gt;

&lt;p&gt;I've been building &lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt; on a different premise: the computed GEX data is &lt;strong&gt;free to view for every US ticker&lt;/strong&gt;, free via API on the starter tier, and there's no time-limited trial.&lt;/p&gt;

&lt;p&gt;This post is a developer-focused walkthrough — the chart, the endpoint, the limits, and what changes when you outgrow the free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "free" actually means
&lt;/h2&gt;

&lt;p&gt;Three things that don't need a signup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive GEX charts&lt;/strong&gt; for any US-listed ticker — per-strike gamma, gamma flip, call/put walls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GEX-derived key levels&lt;/strong&gt; surfaced on every ticker analytics page (net GEX, gamma flip, call wall, put wall).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free API tier&lt;/strong&gt; — 5 requests/day against the GEX endpoint. Enough to watch a small watchlist daily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paid plans buy higher rate limits and adjacent endpoints (DEX, VEX, CHEX, historical, full-chain aggregation). The GEX &lt;em&gt;values themselves&lt;/em&gt; are identical across tiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hitting the API
&lt;/h2&gt;

&lt;p&gt;Grab a key at &lt;a href="https://flashalpha.com/profile" rel="noopener noreferrer"&gt;flashalpha.com/profile&lt;/a&gt; — no card required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: YOUR_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://lab.flashalpha.com/v1/exposure/gex/SPY?expiration=2026-05-16"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you net GEX, gamma flip price, a regime label, and the per-strike array. If you only want the four headline levels (call wall, put wall, gamma flip, net GEX), there's a separate &lt;code&gt;/v1/exposure/levels/{symbol}&lt;/code&gt; endpoint that returns them in one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;flashalpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flashalpha&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlashAlpha&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlashAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exposure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net GEX:    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gamma flip: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Regime:     &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;net_gex_label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also published as &lt;code&gt;npm install flashalpha&lt;/code&gt; for JS/TS, plus C#, Go, and Java packages — same shape across all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  One free-tier gotcha
&lt;/h2&gt;

&lt;p&gt;Full-chain GEX (all expirations aggregated into one number) is on the Growth plan. On the free tier, pass &lt;code&gt;?expiration=yyyy-MM-dd&lt;/code&gt; to scope to a single expiry — which is what most intraday work needs anyway.&lt;/p&gt;

&lt;p&gt;5 calls/day works out to a morning pre-market scan over SPY, QQQ, and a few names on a watchlist. If you need to sweep a 50-ticker universe every few minutes, that's a Growth use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  How fresh the data is
&lt;/h2&gt;

&lt;p&gt;A few things worth knowing about how the levels get computed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Updated intraday.&lt;/strong&gt; GEX shifts as options open and close. End-of-day snapshots miss the moves that matter most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computed from full option chains.&lt;/strong&gt; Every active strike is included, not a sampled subset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dealer-sign convention.&lt;/strong&gt; Positive GEX = dealers long gamma (suppresses moves). Negative GEX = dealers short gamma (amplifies moves). Matches how SpotGamma and the academic literature report it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0DTE included.&lt;/strong&gt; 0DTE strikes contribute to the per-strike profile like any other expiration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Free vs paid — the honest comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Growth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GEX values (per-strike + net)&lt;/td&gt;
&lt;td&gt;Same data&lt;/td&gt;
&lt;td&gt;Same data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticker coverage&lt;/td&gt;
&lt;td&gt;All 6,000+&lt;/td&gt;
&lt;td&gt;All 6,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update cadence&lt;/td&gt;
&lt;td&gt;Intraday&lt;/td&gt;
&lt;td&gt;Intraday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API rate limit&lt;/td&gt;
&lt;td&gt;5/day&lt;/td&gt;
&lt;td&gt;2,500/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-chain GEX via API&lt;/td&gt;
&lt;td&gt;Single-expiry only&lt;/td&gt;
&lt;td&gt;All expirations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEX / VEX / CHEX via API&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Full access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical GEX&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If all you want is "today's call wall on TSLA," the free tier is the whole answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server, if you live in Claude/Cursor
&lt;/h2&gt;

&lt;p&gt;There's also an MCP server at &lt;code&gt;https://lab.flashalpha.com/mcp&lt;/code&gt; if you want to query GEX from Claude Desktop, Cursor, or Windsurf without writing code. Same auth, same endpoints.&lt;/p&gt;




&lt;p&gt;If you build something with it, tag me — I read every reply.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://flashalpha.com/articles/free-gex-data-gamma-exposure-levels-any-ticker" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>options</category>
      <category>api</category>
      <category>python</category>
      <category>trading</category>
    </item>
    <item>
      <title>Replay any minute of SPY options back to 2018 - historical GEX, DEX, VEX, CHEX in one API</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Mon, 20 Apr 2026 17:42:34 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/replay-any-minute-of-spy-options-back-to-2018-historical-gex-dex-vex-chex-in-one-api-20j7</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/replay-any-minute-of-spy-options-back-to-2018-historical-gex-dex-vex-chex-in-one-api-20j7</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Same JSON as the live FlashAlpha options analytics API. Add one query parameter - &lt;code&gt;at&lt;/code&gt; - and you get historical gamma exposure (GEX), delta exposure (DEX), vanna (VEX), charm (CHEX), max pain, VRP, the SVI surface, the 0DTE block, and the full stock summary &lt;strong&gt;as they stood at that minute&lt;/strong&gt;, with no future leakage. 6.7 billion option rows going back to &lt;strong&gt;April 2018&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host: &lt;code&gt;historical.flashalpha.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Endpoints: every live analytics route, mirrored&lt;/li&gt;
&lt;li&gt;Dataset start: 2018-04-16&lt;/li&gt;
&lt;li&gt;Resolution: 1 minute for quotes + greeks; EOD for OI, macro, SVI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a separate host instead of one URL with &lt;code&gt;?at=&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;So your SDK doesn't need to know anything new. Point your existing client at a different base URL, add &lt;code&gt;at=&lt;/code&gt;, done. Same auth header (&lt;code&gt;X-Api-Key&lt;/code&gt;), same response shapes, same code path - literally the same analytics service, reading from minute-level archives instead of the live feed.&lt;/p&gt;

&lt;p&gt;This matters when you're backtesting. You don't want to port a strategy onto a second set of response parsers just to replay history.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;at&lt;/code&gt; parameter
&lt;/h2&gt;

&lt;p&gt;It's ET wall-clock. Don't apply a UTC offset.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yyyy-MM-ddTHH:mm:ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-03-05T15:30:00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minute-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yyyy-MM-dd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-03-05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defaults to 16:00 ET (close)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Example: historical GEX at the COVID low
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FA_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/exposure/summary/SPY?at=2020-03-16T15:30:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SPY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"underlying_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;246.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-03-16T15:30:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gamma_flip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;270.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"negative_gamma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exposures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"net_gex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;-2633970601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"net_dex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-169419489077&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"net_vex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;152461756844&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"net_chex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;-13122349&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real dealer positioning at 15:30 ET on the day SPY closed -12%. Not a reconstruction - the same greeks that existed on the tape, run through today's analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What moves per minute vs per day
&lt;/h2&gt;

&lt;p&gt;Not every series is worth storing at minute granularity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Granularity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Option quotes + greeks (iv, δ, γ, θ, ν, vanna, charm)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 minute&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock bid/ask/mid&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 minute&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open interest&lt;/td&gt;
&lt;td&gt;EOD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVI params, forward prices&lt;/td&gt;
&lt;td&gt;EOD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VIX / VVIX / SKEW / MOVE / DGS10&lt;/td&gt;
&lt;td&gt;EOD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OI barely moves intraday and is dealer-positioning &lt;em&gt;context&lt;/em&gt; - so applying one EOD value to every intraday &lt;code&gt;at=&lt;/code&gt; on that day is defensible. Intraday exposure shifts are driven by greek + spot changes anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  No future leakage on VRP
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/v1/vrp/{symbol}&lt;/code&gt; endpoint computes percentile and z-score using &lt;strong&gt;only history prior to &lt;code&gt;at&lt;/code&gt;&lt;/strong&gt;. Query VRP at 2020-03-16T15:30 and the percentile is ranked against the distribution as it existed on that day, not against the full 2018–today window.&lt;/p&gt;

&lt;p&gt;Backtests don't lie when the data doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;p&gt;Every live endpoint is mirrored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/v1/stockquote/{ticker}&lt;/code&gt; — bid/ask/mid at a minute&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/optionquote/{ticker}&lt;/code&gt; — option quote + greeks + OI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/exposure/gex|dex|vex|chex/{symbol}&lt;/code&gt; — exposure by strike (historical GEX, DEX, VEX, CHEX)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/exposure/summary/{symbol}&lt;/code&gt; — regime, flip, net exposures&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/exposure/levels/{symbol}&lt;/code&gt; — flip, walls, magnets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/exposure/narrative/{symbol}&lt;/code&gt; — verbal regime + prior-day comparison&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/exposure/zero-dte/{symbol}&lt;/code&gt; — historical 0DTE block&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/maxpain/{symbol}&lt;/code&gt; — max pain + pin probability&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/volatility/{symbol}&lt;/code&gt; — RV ladder, IV-RV spread, skew&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/adv_volatility/{symbol}&lt;/code&gt; — SVI params, variance surface, arbitrage flags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/vrp/{symbol}&lt;/code&gt; — VRP dashboard, date-bounded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/stock/{symbol}/summary&lt;/code&gt; — everything in one call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus one historical-specific route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/v1/tickers&lt;/code&gt; — coverage map (first date, last date, gaps, healthy-day count) per symbol&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest field coverage gaps
&lt;/h2&gt;

&lt;p&gt;Upfront about what's zero/null vs what's real:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;bidSize&lt;/code&gt;, &lt;code&gt;askSize&lt;/code&gt;, &lt;code&gt;volume&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minute quotes don't carry sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;call_oi_change&lt;/code&gt;, &lt;code&gt;put_oi_change&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prior-day per-strike diff not yet computed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0DTE per-strike greeks&lt;/td&gt;
&lt;td&gt;often &lt;code&gt;0&lt;/code&gt; / &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Near-expiry minute greeks still backfilling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;summary.macro.vix_futures&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CME futures curves aren't archived historically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optionquote.svi_vol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-contract SVI derivation not yet exposed — use &lt;code&gt;/v1/adv_volatility&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything else — greeks, IV, exposures, flip, walls, regime, narrative, VRP percentiles, SVI fits, variance surface, arbitrage flags — is real data at &lt;code&gt;at&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coverage
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SPY&lt;/code&gt; is fully backfilled &lt;strong&gt;2018-04-16 → 2026-04-02&lt;/strong&gt; and extends on every refresh. Additional symbols are backfilled on request. Query the live coverage map anytime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$FA_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/tickers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Latency
&lt;/h2&gt;

&lt;p&gt;Point-in-time reads are ~50–300 ms typical. Composite endpoints (&lt;code&gt;/v1/adv_volatility&lt;/code&gt;, &lt;code&gt;/v1/stock/{symbol}/summary&lt;/code&gt;) rebuild surfaces on demand and take ~500–1500 ms cold. Responses are deterministic for a given &lt;code&gt;(symbol, at)&lt;/code&gt; tuple - cache indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full docs: &lt;a href="https://flashalpha.com/docs/historical-api" rel="noopener noreferrer"&gt;https://flashalpha.com/docs/historical-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Playground: &lt;a href="https://flashalpha.com/docs/historical-playground" rel="noopener noreferrer"&gt;https://flashalpha.com/docs/historical-playground&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get an API key: &lt;a href="https://flashalpha.com/profile" rel="noopener noreferrer"&gt;https://flashalpha.com/profile&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>options</category>
      <category>backtesting</category>
    </item>
    <item>
      <title>Backtesting Gamma-Exposure Strategies with Minute-Level Historical Data</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:30:36 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/backtesting-gamma-exposure-strategies-with-minute-level-historical-data-3k21</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/backtesting-gamma-exposure-strategies-with-minute-level-historical-data-3k21</guid>
      <description>&lt;p&gt;If you've ever read a blog post claiming "short straddles when GEX is positive returns 30% a year" and wondered why the claim evaporates when you try to reproduce it, the answer is usually the same: the backtest cheated somewhere. Either it used data that didn't exist at the decision time, it labelled regimes using end-of-day aggregates that smear across the true intraday flip, or it skipped execution costs that swamp the signal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flashalpha.com/articles/historical-options-analytics-api-replay-gex-vrp-dealer-positioning" rel="noopener noreferrer"&gt;FlashAlpha's Historical Analytics API&lt;/a&gt; makes the honest version of this backtest easier than the cheat. One &lt;code&gt;at&lt;/code&gt; parameter, minute-level regime labels since 2018, leak-free percentiles by design. This article walks through the whole pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Testing
&lt;/h2&gt;

&lt;p&gt;A simple, well-known hypothesis: &lt;em&gt;SPY mean-reverts intraday when dealers are long gamma and trends when dealers are short gamma&lt;/em&gt;. Specifically, we'll test whether short-term (30-minute) returns after 15:30 ET differ by dealer regime. If the hypothesis is real, 15:30-to-close returns should be smaller and more mean-reverting during positive-gamma regimes and larger (in either direction) during negative-gamma regimes.&lt;/p&gt;

&lt;p&gt;This is a textbook GEX claim. It's also exactly the kind of thing that looks real in a sloppy backtest and dies in a careful one. Let's do it carefully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Pull the Regime Labels
&lt;/h2&gt;

&lt;p&gt;We need, for every trading day in the test window, the dealer regime at 15:30 ET. That's &lt;code&gt;/v1/exposure/summary/{symbol}&lt;/code&gt; with &lt;code&gt;at&lt;/code&gt; set to 15:30 on each day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://historical.flashalpha.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bdate_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-01-03&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tqdm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT15:30:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/exposure/summary/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot_1530&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;underlying_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_dex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_dex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_dte_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_dte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pct_of_total_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;gex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About 1,000 business days. At roughly 200ms per call, this runs in ~3 minutes single-threaded. Run it once, cache to parquet, move on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leak check:&lt;/strong&gt; every number in &lt;code&gt;gex&lt;/code&gt; is computed from data available at or before 15:30 ET on that date. The &lt;code&gt;net_gex&lt;/code&gt; number reflects OI from that morning's EOD load (yesterday's OI) plus minute-level greek recomputation from 15:30 spot and vols. Nothing later than 15:30 ET on the given date contributes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Pull the Forward Returns
&lt;/h2&gt;

&lt;p&gt;For each date, we need the 15:30-to-16:00 ET return. Two stock-quote calls per day: one at 15:30, one at 16:00.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;close_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tqdm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/stockquote/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT16:00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;close_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;

&lt;span class="n"&gt;closes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;close_rows&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ret_30min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot_1530&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Leak check:&lt;/strong&gt; the forward return uses 16:00 ET data to measure performance of a signal generated at 15:30 ET. There's a 30-minute gap, which is the signal window. No feature crosses the signal-to-outcome boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Condition on Regime
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;by_regime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ret_30min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;mean_bps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;std_bps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;abs_mean_bps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by_regime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting column is &lt;code&gt;abs_mean_bps&lt;/code&gt; — the average absolute move. If the mean-reversion hypothesis is right, it should be smaller in &lt;code&gt;positive_gamma&lt;/code&gt; than in &lt;code&gt;negative_gamma&lt;/code&gt;. If the &lt;em&gt;directional&lt;/em&gt; hypothesis is right (short puts in positive gamma), &lt;code&gt;mean_bps&lt;/code&gt; should differ too.&lt;/p&gt;

&lt;p&gt;Running this against the 2022–2025 window, you'll typically find a real but small effect: absolute 15:30-to-close moves are roughly 30–40% smaller in positive-gamma days. That's enough to matter for an intraday vol-selling strategy and not nearly enough to matter as a directional overlay. This is the kind of nuance that clean backtests produce and messy ones miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Stratify on GEX Magnitude
&lt;/h2&gt;

&lt;p&gt;The regime label is a coarse cut. The underlying &lt;code&gt;net_gex&lt;/code&gt; is continuous. Bucket by deciles and look at the monotone relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gex_decile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;by_decile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gex_decile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;gex_mean&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ret_30min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;abs_move_bps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ret_30min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by_decile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you want to see is a monotone curve — low GEX deciles should show larger absolute moves, high GEX deciles smaller. If the curve is flat or non-monotone, the regime signal isn't really tracking what you think it's tracking and the rest of the strategy won't work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Scale Up — Every Minute, Every Day
&lt;/h2&gt;

&lt;p&gt;Once the daily-grid version works, the minute-level version is mechanical. Instead of one sample per day at 15:30, pull every minute from 9:30 to 16:00 and condition on intraday regime changes. That's 390 calls per day × 1,000 days = 390,000 calls.&lt;/p&gt;

&lt;p&gt;The minute-level grid lets you answer strictly-better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What happens in the 15 minutes after a regime flip?&lt;/strong&gt; Filter &lt;code&gt;df&lt;/code&gt; to rows where &lt;code&gt;regime[t] != regime[t-1]&lt;/code&gt;, compute forward returns over varying windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do moves compress as spot approaches the gamma flip?&lt;/strong&gt; Compute &lt;code&gt;spot_to_flip_pct&lt;/code&gt; and stratify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is 0DTE GEX a stronger signal intraday than all-expiry GEX?&lt;/strong&gt; The &lt;code&gt;zero_dte&lt;/code&gt; block in the summary gives you both; run the same test on each.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the questions the dealer-positioning literature claims to answer but rarely tests at the resolution of the claim. The Historical API makes them runnable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Traps This Setup Avoids
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Rolling-Percentile Leakage
&lt;/h3&gt;

&lt;p&gt;If your backtest uses VRP percentile or GEX percentile as a feature, make sure the percentile at time &lt;em&gt;t&lt;/em&gt; is computed only from data strictly before &lt;em&gt;t&lt;/em&gt;. FlashAlpha's VRP endpoint does this by default. &lt;a href="https://flashalpha.com/articles/historical-vrp-percentiles-no-lookahead-bias-backtesting" rel="noopener noreferrer"&gt;Full write-up →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. End-of-Day Smearing
&lt;/h3&gt;

&lt;p&gt;Net GEX can flip intraday. A backtest that uses close-of-day GEX to label a 10:30 ET entry is measuring a feature that didn't exist yet. The Historical API's minute resolution removes this problem — you can label each bar with the regime as it stood at that bar's open.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Survivorship in Symbol Selection
&lt;/h3&gt;

&lt;p&gt;If you backtest only on SPY, you don't have a survivorship problem. If you start adding single-names later, make sure your universe at each date reflects what was tradable &lt;em&gt;then&lt;/em&gt;, not what's listed &lt;em&gt;now&lt;/em&gt;. FlashAlpha's coverage endpoint is explicit about which symbols had data when.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going Beyond Regime Labels
&lt;/h2&gt;

&lt;p&gt;Everything above uses the scalar regime and net GEX as the signal. The full &lt;code&gt;exposure/summary&lt;/code&gt; response is richer — it includes net DEX (directional dealer positioning), net VEX (vanna), net CHEX (charm), and interpretation text. For ML workflows, pull the whole response and let the model sort out which fields matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;features_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at_ts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/exposure/summary/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;at_ts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/vrp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;at_ts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;underlying_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_dex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_dex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_vex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_vex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_chex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_chex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot_to_flip_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;underlying_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;underlying_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_dte_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_dte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pct_of_total_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;percentile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atm_iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atm_iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hv_20&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rv_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's ~15 features per timestamp with two API calls. For tabular models (XGBoost, LightGBM), this is already in the right shape. For transformers, pull the full nested responses and flatten lazily.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Pay For
&lt;/h2&gt;

&lt;p&gt;The cost of this backtest — end-to-end, against 4 years of history — is a function of how many minute-level samples you pull. Daily snapshots across 4 years ≈ 1,000 calls per endpoint per symbol. Minute-level is 390× that.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;alternative&lt;/em&gt; — building this dataset yourself — is a 3-to-6 month engineering project: ThetaData subscription, options parquets, BSM pipeline, SVI fitter, columnar store, minute-level partitioning, holidays/half-days handling, coverage reports. &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;FlashAlpha's Historical API&lt;/a&gt; is the bought-not-built version of that stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/historical-options-analytics-api-replay-gex-vrp-dealer-positioning" rel="noopener noreferrer"&gt;Historical Options Analytics API — full overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/historical-vrp-percentiles-no-lookahead-bias-backtesting" rel="noopener noreferrer"&gt;Leak-Free VRP Percentiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/spy-march-16-2020-dealer-positioning-replay-covid-crash" rel="noopener noreferrer"&gt;SPY COVID Crash Replay — a concrete case study&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/gex-trading-guide-gamma-exposure-api-spy-tsla" rel="noopener noreferrer"&gt;GEX Trading Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/vrp-z-score-timing-premium-selling-statistical-edge" rel="noopener noreferrer"&gt;VRP Z-Score Timing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Historical API is available on the Alpha tier. &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Get your free API key&lt;/a&gt; to start with the live endpoints.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>options</category>
      <category>python</category>
      <category>quant</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Look-Ahead Bias in Volatility Backtests — Why Most VRP Percentiles Silently Cheat (and How to Fix It)</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 15 Apr 2026 08:29:59 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/look-ahead-bias-in-volatility-backtests-why-most-vrp-percentiles-silently-cheat-and-how-to-fix-4jg0</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/look-ahead-bias-in-volatility-backtests-why-most-vrp-percentiles-silently-cheat-and-how-to-fix-4jg0</guid>
      <description>&lt;p&gt;Ask any quant what kills backtests and you'll get the same short list: survivorship bias, execution costs, overfitting. The one that gets mentioned less often — and is probably the most common in amateur-to-intermediate volatility research — is &lt;strong&gt;percentile leakage&lt;/strong&gt;. It's a specific, boring, easy-to-miss form of look-ahead bias that makes dead strategies look alive, and it's baked into almost every "VRP percentile" or "IV rank" column you'll find in public datasets.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Percentile Leakage in Options Backtesting?
&lt;/h2&gt;

&lt;p&gt;Consider a feature you've seen a hundred times: &lt;em&gt;VRP 20-day percentile&lt;/em&gt;. Take the current 20-day VRP, compare it to the distribution of historical VRPs, express as a percentile (0 to 100). High percentile = rich vol premium, time to sell. Low percentile = compressed, stand down.&lt;/p&gt;

&lt;p&gt;Now, how did you compute the distribution?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wrong way:&lt;/strong&gt; grab all historical VRP values in your dataset (say 2018–2026), sort them, and on any given date &lt;em&gt;t&lt;/em&gt;, compute the percentile of VRP[t] against that full sorted list.&lt;/p&gt;

&lt;p&gt;That's leakage. In 2019, your "percentile" is scored against a distribution that includes March 2020, August 2024, and every other extreme event that hadn't happened yet. The 2019 percentile a real trader would have seen — using only data before 2019 — is different, often &lt;em&gt;very&lt;/em&gt; different.&lt;/p&gt;

&lt;p&gt;When you bet on that cheated percentile in your backtest, you are using information that did not exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Compute Walk-Forward Percentiles Without Look-Ahead Bias
&lt;/h2&gt;

&lt;p&gt;At each date &lt;em&gt;t&lt;/em&gt;, compute the percentile of VRP[t] against the distribution of VRP values from &lt;strong&gt;strictly before *t&lt;/strong&gt;&lt;em&gt;. That's a *walk-forward&lt;/em&gt; or &lt;em&gt;expanding-window&lt;/em&gt; percentile. Every sample is scored against the knowledge available at its own moment, and nothing later.&lt;/p&gt;

&lt;p&gt;Straightforward to describe. A minor nuisance to implement correctly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store every historical VRP observation with its date.&lt;/li&gt;
&lt;li&gt;On each query, filter to observations dated &lt;strong&gt;strictly before&lt;/strong&gt; the query date.&lt;/li&gt;
&lt;li&gt;Compute percentile against that filtered set.&lt;/li&gt;
&lt;li&gt;Do this efficiently — the naive implementation is O(n) per query across thousands of queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most custom percentile code gets step 2 subtly wrong (off-by-one on the date filter), step 4 badly wrong (recomputing the full distribution per query), or both. A correct implementation shipped behind an API removes the excuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  How FlashAlpha's Historical VRP API Avoids Look-Ahead Bias
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt;'s Historical VRP endpoint is explicit about this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VRP percentile and z-score are computed from snapshot rows with date strictly less than the query date, so at any historical point the percentile reflects what was knowable at that moment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The filter is a SQL predicate in the query itself — not a convention, not a best-effort, not something you can accidentally bypass.&lt;/p&gt;

&lt;p&gt;Ask for &lt;code&gt;/v1/vrp/SPY?at=2022-06-14T15:30:00&lt;/code&gt; and the percentile is computed against data before 2022-06-14. Ask for the same symbol at a later date and the percentile changes, because the rolling window includes more data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/vrp/SPY?at=2022-06-14T15:30:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SPY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vrp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vrp_20d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"z_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"percentile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"history_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;percentile: 100&lt;/code&gt; means this VRP reading is above every observation in the preceding 60-day window — computed honestly, as a trader at 15:30 ET on June 14, 2022 would have actually seen it. Not as a backtest writer looking backwards from 2026 would retroactively assign it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Much Does Look-Ahead Bias Inflate Your Sharpe Ratio?
&lt;/h2&gt;

&lt;p&gt;The amplitude of the leakage depends on where in the history you're testing. A backtest across a 2018–2026 window using full-sample percentiles will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under-assign&lt;/strong&gt; extreme percentiles in early history (the extremes that hadn't yet happened are in the denominator).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-assign&lt;/strong&gt; extreme percentiles in late history (mirror image).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inflate Sharpe ratios&lt;/strong&gt; for any strategy that triggers on percentile thresholds, because the threshold breaches are non-random in time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the VRP case specifically, a short-strangle strategy gated on "VRP percentile &amp;gt; 80" will typically show &lt;strong&gt;15–30% higher Sharpe&lt;/strong&gt; on full-sample percentiles than on walk-forward percentiles over a 5+ year window. That's enough to turn a live-unviable strategy into a slide-deck-impressive one. It's also exactly the kind of edge that evaporates the moment you try to trade it, because the live version doesn't get to cheat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python: Walk-Forward VRP Backtest Using the Historical API
&lt;/h2&gt;

&lt;p&gt;Here's a practical research pattern: pull VRP daily across your test window, and at each date, record the percentile the API returns. That percentile is already walk-forward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://historical.flashalpha.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bdate_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tqdm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/vrp/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;percentile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atm_iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atm_iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rv_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rv_20d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;vrp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# vrp["vrp_pct"] is walk-forward by construction — no leakage
&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vrp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vrp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vrp_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;entries&lt;/code&gt; dataframe is the set of honest trigger days for a "short strangle when VRP percentile &amp;gt; 80" study. Join forward returns (next-day or next-20-day underlying path / straddle P&amp;amp;L), measure edge. If it works with the walk-forward percentile, it has a chance of working live. If it only works with a full-sample percentile, you're looking at a statistical artifact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Options Backtesting Features That Leak the Future
&lt;/h2&gt;

&lt;p&gt;Percentiles are the most common case, but the same class of look-ahead bias hides in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rolling z-scores&lt;/strong&gt; with expanding-window means and standard deviations. Same walk-forward fix required. FlashAlpha's &lt;code&gt;z_score&lt;/code&gt; is computed from the same date-bounded snapshot set as the percentile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regime labels&lt;/strong&gt; ("normal" vs "elevated") derived from thresholds on historical distributions. If the thresholds come from the full dataset, early samples get labelled against knowledge that didn't exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatility-of-volatility features.&lt;/strong&gt; Any derived statistic that rolls over the entire history is a candidate for leakage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-asset signals&lt;/strong&gt; that blend SPY and another underlying's history. The leakage compounds across instruments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything you compute yourself on top of the Historical API's raw outputs, the rule is the same: at time &lt;em&gt;t&lt;/em&gt;, use only data with timestamps strictly less than &lt;em&gt;t&lt;/em&gt;. For percentile and z-score on VRP specifically, the endpoint handles it for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Look-Ahead Bias Is Worse for Machine Learning Models
&lt;/h2&gt;

&lt;p&gt;For ML workflows, leakage is a larger problem because your model learns whatever signal exists — including whatever cheating signal the features leak. If VRP percentile is a feature and it's computed against the full dataset, the model will learn that "high percentile" is more informative in early history than it actually was, because the labelling itself encodes future information. Gradient boosters in particular are excellent at exploiting these micro-leaks.&lt;/p&gt;

&lt;p&gt;Using the Historical API's walk-forward percentile as a feature removes that failure mode for VRP. Apply the same discipline to your other derived features, and your cross-validated metrics start matching your paper-trading metrics — which is the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Point: Make the Right Thing Frictionless
&lt;/h2&gt;

&lt;p&gt;Calculator correctness gets a lot of ink in quant writing. Leakage discipline gets much less. But leakage is where most real-world research gets quietly ruined — not in the calculator, but in the feature pipeline that feeds it.&lt;/p&gt;

&lt;p&gt;The reason FlashAlpha's Historical VRP is notable isn't that percentiles are hard to compute. It's that shipping a historical API where the percentiles are honest by default means every user starts from a correct baseline. Research quality is defined by the lowest-friction option; make the right thing frictionless and most people will do the right thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/historical-options-analytics-api-replay-gex-vrp-dealer-positioning" rel="noopener noreferrer"&gt;Historical Options Analytics API — full overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/historical-gex-api-backtesting-gamma-exposure-strategies" rel="noopener noreferrer"&gt;Backtesting GEX-Regime Strategies with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/vrp-z-score-timing-premium-selling-statistical-edge" rel="noopener noreferrer"&gt;VRP Z-Score and Premium Selling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/volatility-risk-premium-trading-guide-vrp-edge-options" rel="noopener noreferrer"&gt;Volatility Risk Premium Trading Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/iv-rank-vs-iv-percentile-which-to-use" rel="noopener noreferrer"&gt;IV Rank vs IV Percentile — Which to Use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://flashalpha.com/docs/historical-api" rel="noopener noreferrer"&gt;API Spec&lt;/a&gt; | &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://flashalpha.com/articles/historical-vrp-percentiles-no-lookahead-bias-backtesting" rel="noopener noreferrer"&gt;flashalpha.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Historical Options Data API for Backtesting — Replay GEX, VRP &amp; Dealer Positioning at Any Minute Since 2018</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 15 Apr 2026 08:29:40 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/historical-options-data-api-for-backtesting-replay-gex-vrp-dealer-positioning-at-any-minute-22a8</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/historical-options-data-api-for-backtesting-replay-gex-vrp-dealer-positioning-at-any-minute-22a8</guid>
      <description>&lt;p&gt;Every serious options project eventually hits the same wall: the live API shows you &lt;em&gt;now&lt;/em&gt;, but the questions that actually matter — does this strategy work? what did dealers do during the last drawdown? how did VRP behave before earnings? — all require historical options data.&lt;/p&gt;

&lt;p&gt;And historical options data, especially &lt;em&gt;pre-computed historical analytics&lt;/em&gt;, is the one thing nobody sells.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt;'s Historical API changes that. The contract is simple: every live analytics endpoint, replayable at any minute since 2018-04-16, returned in the same response shape. One query parameter — &lt;code&gt;at&lt;/code&gt; — and you get what GEX, DEX, VEX, CHEX, VRP, max pain, dealer regime, or the full stock summary looked like at that exact minute in history.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Historical Options Analytics Don't Exist Anywhere Else
&lt;/h2&gt;

&lt;p&gt;Go look at the other options data providers. Polygon, ThetaData, ORATS, Intrinio, Tradier — all of them ship raw ticks or end-of-day snapshots. You can reconstruct &lt;em&gt;parts&lt;/em&gt; of the analytics layer yourself if you have the time and the pipeline, but you will spend six months writing the join, the BSM pass, the SVI fit, the per-strike aggregation, the regime classifier, the leak-free percentile calculator — and you will still end up with something that only resembles the live product on a good day.&lt;/p&gt;

&lt;p&gt;FlashAlpha's Historical API collapses that work into one HTTP call. The same &lt;code&gt;ExposureCalculator&lt;/code&gt;, &lt;code&gt;NarrativeBuilder&lt;/code&gt;, &lt;code&gt;VrpCalculator&lt;/code&gt;, &lt;code&gt;VolatilityAnalyzer&lt;/code&gt;, and &lt;code&gt;AdvancedVolatilityCalculator&lt;/code&gt; classes that power the live endpoints also power the historical service. Calculator bug-fixes land in both services simultaneously. What you see live, you see historically — exactly the same shape, exactly the same methodology.&lt;/p&gt;

&lt;p&gt;Pre-computed historical options analytics require minute-level options quotes with greeks for every strike and expiration, anchored to forward prices, joined to end-of-day open interest, fitted with SVI, and cross-referenced against macro (VIX, VVIX, SKEW, MOVE) — all aligned to the exact timestamp you ask for. The dataset behind it: 6.7 billion option rows, 2 million+ stock minute-bars, daily SVI fits, daily macro. Nobody else assembled this stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Historical Options API Works: The &lt;code&gt;at&lt;/code&gt; Parameter
&lt;/h2&gt;

&lt;p&gt;Every analytics endpoint takes one required query parameter: &lt;code&gt;at&lt;/code&gt;. That's your as-of timestamp. Pass it and you get the response as it would have been computed at that minute.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Semantics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yyyy-MM-ddTHH:mm:ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-03-05T15:30:00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minute-level as-of. ET wall-clock.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yyyy-MM-dd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-03-05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defaults to 16:00 ET (session close).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Option greeks, bid/ask, stock spot, and everything derived from them are truly intraday — one value per minute from 9:30 to 16:00 ET. Open interest, SVI parameters, and macro are EOD-stamped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://historical.flashalpha.com/v1/exposure/summary/SPY?at=2020-03-16T15:30:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call. Full exposure dashboard — net GEX, net DEX, net VEX, net CHEX, gamma flip, regime label, interpretations, ±1% hedging estimates, 0DTE contribution — as of 15:30 ET on March 16, 2020. The day SPY closed -12%.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full List of Replayable Historical Options Endpoints
&lt;/h2&gt;

&lt;p&gt;This is not a subset. The Historical API mirrors the live API endpoint-for-endpoint:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What you replay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Market data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/stockquote/{ticker}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stock bid/ask/mid/last at the minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/optionquote/{ticker}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contract-level quotes + BSM greeks + OI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/surface/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;50×50 implied-vol surface grid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/gex/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Historical gamma exposure by strike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/dex/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Historical delta exposure by strike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/vex/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Historical vanna exposure by strike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/chex/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Historical charm exposure by strike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/summary/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full composite dashboard + regime + hedging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/levels/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gamma flip, call wall, put wall, max gamma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/narrative/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verbal analysis + prior-day GEX delta + VIX context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exposure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/zero-dte/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0DTE regime, pin risk, expected move, decay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max pain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/maxpain/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pain curve, pin probability, dealer alignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/stock/{symbol}/summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Price, vol, flow, exposure, macro — one response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volatility&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/volatility/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RV ladder, IV-RV spread, skew, term structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volatility&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/adv_volatility/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SVI params, forwards, total variance, arb flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VRP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/vrp/{symbol}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full VRP dashboard with leak-free percentiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/v1/tickers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loaded symbols and date ranges&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  No Look-Ahead Bias: Leak-Free Historical Percentiles
&lt;/h2&gt;

&lt;p&gt;The feature that most historical options data services get wrong is the one that matters most for backtesting: &lt;strong&gt;no future leakage&lt;/strong&gt;. If you ask FlashAlpha for VRP percentile at &lt;code&gt;2022-06-14T15:30:00&lt;/code&gt;, the percentile is computed only from rows dated before 2022-06-14. The percentile at that moment reflects what was knowable at that moment.&lt;/p&gt;

&lt;p&gt;Most backtests silently cheat. If your "historical VRP percentile" uses a percentile computed against the full 2018–2026 dataset, every 2019 observation is scored against knowledge that didn't exist in 2019. That's look-ahead bias. It inflates Sharpe ratios. It makes dead strategies look live.&lt;/p&gt;

&lt;p&gt;FlashAlpha's Historical API makes the honest version the default — it's actually harder to cheat than to do it right. &lt;a href="https://flashalpha.com/articles/historical-vrp-percentiles-no-lookahead-bias-backtesting" rel="noopener noreferrer"&gt;Deep dive on leak-free percentiles →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Backtest a GEX-Regime Options Strategy With Python
&lt;/h2&gt;

&lt;p&gt;Every strategy that conditions on dealer positioning ("short premium when dealers are long gamma", "fade gaps when GEX is negative", "buy straddles when we cross gamma flip") needs historical regime labels per minute. That's &lt;code&gt;/v1/exposure/summary/{symbol}&lt;/code&gt; replayed across your training window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bdate_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT15:30:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://historical.flashalpha.com/v1/exposure/summary/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gamma_flip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;underlying_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Join next-day returns, condition on regime, measure edge
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the full research dataframe. Join forward returns, condition on regime, measure the edge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using Historical Dealer Positioning as ML Features
&lt;/h2&gt;

&lt;p&gt;For machine-learning workflows, the Historical API is the feature store you were about to build yourself. Every minute of SPY since 2018-04-16 gives you net GEX, gamma flip distance, VRP z-score, net DEX, 0DTE contribution, vol surface shape, macro regime — about 40 dealer-positioning features per timestamp, all aligned to a single &lt;code&gt;at&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;The numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 years × 252 trading days × 390 minutes ≈ &lt;strong&gt;786K minute-level sample rows&lt;/strong&gt; for SPY alone&lt;/li&gt;
&lt;li&gt;~40 features per row → roughly &lt;strong&gt;31 million feature observations&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Walk-forward percentiles and z-scores included — no leakage in your training set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pull the grid you want — daily close, half-hourly, or every minute — dump to parquet, and feed into XGBoost, a tabular transformer, or whatever architecture you prefer. Before FlashAlpha, assembling this with correct greeks and leak-free percentiles was a multi-engineer, multi-quarter project. Now it's a weekend script.&lt;/p&gt;




&lt;h2&gt;
  
  
  Historical Options Data Coverage and Pricing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symbols&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SPY (more backfill on request)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Date range&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2018-04-16 → today (extended each pipeline run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Option rows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.7 billion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stock minute-bars&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 million+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intraday granularity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 minute (quotes + greeks + spot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EOD layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open interest, SVI params, forwards, macro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum tier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alpha ($1,499/mo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://historical.flashalpha.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Authentication uses the same &lt;code&gt;X-Api-Key&lt;/code&gt; as the live API. &lt;code&gt;GET /v1/tickers&lt;/code&gt; returns loaded symbols, date ranges, and any pipeline gaps — self-describing coverage so you never waste a call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validate Historically, Deploy Live — Same Response Shape
&lt;/h2&gt;

&lt;p&gt;The historical and live services share the calculator layer. When a bug gets fixed, it's fixed in both places the same day. When a field gets added to the live response, it appears historically too.&lt;/p&gt;

&lt;p&gt;That means you can validate strategies historically and deploy them live without a rewrite. Same response shape, same field names, same regime labels, same interpretation text. The deploy is a config flip: swap the base URL from &lt;code&gt;historical.flashalpha.com&lt;/code&gt; to &lt;code&gt;api.flashalpha.com&lt;/code&gt; and change &lt;code&gt;at&lt;/code&gt; to "now."&lt;/p&gt;




&lt;h2&gt;
  
  
  Known Gaps in Historical Coverage
&lt;/h2&gt;

&lt;p&gt;A small number of live fields aren't reconstructible historically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minute-level volume:&lt;/strong&gt; not stored; volume fields return 0. Greeks, bid/ask, and OI all populated normally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VIX futures and CNN Fear &amp;amp; Greed:&lt;/strong&gt; external sources not archived; return &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option bid/ask sizes:&lt;/strong&gt; not carried at minute resolution. Bid/ask prices are populated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prior-day OI diffs in &lt;code&gt;narrative.top_oi_changes&lt;/code&gt;:&lt;/strong&gt; not computed yet; empty array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;svi_vol&lt;/code&gt; per contract in &lt;code&gt;/v1/optionquote&lt;/code&gt;:&lt;/strong&gt; SVI params are at the expiry level; &lt;code&gt;implied_vol&lt;/code&gt; (BSM) is populated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these affect the core exposure, VRP, or volatility surface workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started With Historical Options Data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📄 &lt;a href="https://flashalpha.com/docs/historical-api" rel="noopener noreferrer"&gt;Full Historical API Spec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔍 &lt;a href="https://historical.flashalpha.com/v1/tickers" rel="noopener noreferrer"&gt;Check Symbol Coverage&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💰 &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Pricing &amp;amp; Upgrade to Alpha&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📊 &lt;a href="https://flashalpha.com/articles/historical-vrp-percentiles-no-lookahead-bias-backtesting" rel="noopener noreferrer"&gt;Leak-Free VRP Percentiles — methodology deep dive&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://flashalpha.com/articles/historical-gex-api-backtesting-gamma-exposure-strategies" rel="noopener noreferrer"&gt;GEX Backtesting Walkthrough&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📉 &lt;a href="https://flashalpha.com/articles/spy-march-16-2020-dealer-positioning-replay-covid-crash" rel="noopener noreferrer"&gt;SPY March 16, 2020 — COVID crash replay&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://flashalpha.com/articles/historical-options-analytics-api-replay-gex-vrp-dealer-positioning" rel="noopener noreferrer"&gt;flashalpha.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Max Pain in Options: The Math, the Mechanics, and How to Query It via API</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Thu, 09 Apr 2026 06:18:15 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/max-pain-in-options-the-math-the-mechanics-and-how-to-query-it-via-api-2em8</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/max-pain-in-options-the-math-the-mechanics-and-how-to-query-it-via-api-2em8</guid>
      <description>&lt;p&gt;If you've ever watched SPY drift inexplicably toward a specific strike on expiration Friday — no news, no catalyst — you've seen max pain in action.&lt;/p&gt;

&lt;p&gt;This post explains what max pain is, how it's calculated, why it works mechanically, and how to pull it programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Max Pain?
&lt;/h2&gt;

&lt;p&gt;Max pain is the strike price where the total intrinsic value paid out across all open option contracts is minimised. In other words: the settlement price where dealers keep the most premium.&lt;/p&gt;

&lt;p&gt;The calculation iterates over every possible settlement price and sums the payouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pain(K) = Σ max(K - K_put, 0) × OI_put + Σ max(K_call - K, 0) × OI_call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The strike &lt;code&gt;K&lt;/code&gt; that minimises &lt;code&gt;Pain(K)&lt;/code&gt; is max pain. The inputs are just open interest by strike — calls and puts separately.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Price Move Toward It?
&lt;/h2&gt;

&lt;p&gt;Delta hedging. When dealers sell options, they hedge by trading the underlying. Near expiration, gamma spikes — delta changes rapidly with small price moves, so dealers must rebalance aggressively.&lt;/p&gt;

&lt;p&gt;In positive gamma, this creates mean-reversion: dealers buy dips, sell rallies. The net effect pulls price toward the highest-OI strike cluster, which tends to be max pain.&lt;/p&gt;

&lt;p&gt;Not a conspiracy. An emergent outcome of mechanical hedging.&lt;/p&gt;
&lt;h2&gt;
  
  
  Querying Max Pain via API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt; computes max pain in real time for 6,000+ US equities and ETFs. One call gives you everything:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: YOUR_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://lab.flashalpha.com/v1/maxpain/SPY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_pain_strike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;545&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"above"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dealer_alignment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alignment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"converging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gamma_flip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;546&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"call_wall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;555&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"put_wall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;538&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pin_probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"positive_gamma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pain_curve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_pain_by_expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_pain_strike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;547&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_oi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;520000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_pain_strike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;545&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"dte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_oi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1840000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Python Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://lab.flashalpha.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/maxpain/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max Pain: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max_pain_strike&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Distance: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;percent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;direction&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pin Probability: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pin_probability&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alignment: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dealer_alignment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alignment&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Regime: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Single expiry
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/maxpain/SPY?expiration=2026-04-17&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;April 17 Max Pain: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max_pain_strike&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Install the SDK
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;flashalpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flashalpha&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlashAlpha&lt;/span&gt;

&lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlashAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxpain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max Pain: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max_pain_strike&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Pin: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pin_probability&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Fields Explained
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pin_probability&lt;/code&gt;&lt;/strong&gt; (0–100): Combines OI concentration (30%), proximity to current price (25%), time to expiry (25%), and gamma magnitude (20%). Above 70 = strong pin conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dealer_alignment&lt;/code&gt;&lt;/strong&gt;: Shows whether max pain converges with GEX levels (gamma flip, call/put walls). "Converging" is the strongest signal — hedging flows and payout minimisation point the same direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pain_curve&lt;/code&gt;&lt;/strong&gt;: The full payout curve across all strikes. A steep V = strong pin. Flat = weak pin. Asymmetric = directional skew in the pinning pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;max_pain_by_expiration&lt;/code&gt;&lt;/strong&gt;: Per-expiry breakdown. The expiry with the most OI has the strongest gravitational pull. Useful for spotting step-downs when weeklies roll off.&lt;/p&gt;
&lt;h2&gt;
  
  
  Combining With Other Endpoints
&lt;/h2&gt;

&lt;p&gt;Max pain is most useful layered with exposure data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What It Adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/gex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-strike gamma — where hedging pressure concentrates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/levels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gamma flip, call/put walls — confirms or contradicts the pin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/zero-dte&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0DTE pin risk and expected move on expiration day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/exposure/summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Regime (positive/negative gamma), net dealer exposures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  When Max Pain Doesn't Work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Negative gamma regime&lt;/strong&gt; — dealers amplify moves instead of dampening them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Earnings / FOMC / CPI&lt;/strong&gt; — directional flow overwhelms hedging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low OI&lt;/strong&gt; — small caps with thin chains won't pin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OI shifts&lt;/strong&gt; — max pain from Monday can be stale by Thursday; always use fresh data&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Free vs. Growth
&lt;/h2&gt;

&lt;p&gt;The free tier (&lt;code&gt;/v1/exposure/levels&lt;/code&gt;) includes the max pain strike — just the number. The Growth plan ($299/mo, 2,500 req/day) unlocks the full &lt;code&gt;/v1/maxpain&lt;/code&gt; endpoint: pain curve, pin probability, dealer alignment, multi-expiry calendar, and expected move context.&lt;/p&gt;

&lt;p&gt;The difference: knowing max pain is $545 vs. knowing it's $545 with a 72/100 pin probability, converging dealer alignment, and price within the expected move.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/docs/lab-api-maxpain" rel="noopener noreferrer"&gt;Max Pain API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/articles/max-pain-options-explained-complete-guide" rel="noopener noreferrer"&gt;Full article on flashalpha.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Get a free API key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FlashAlpha-lab" rel="noopener noreferrer"&gt;Python SDK on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://flashalpha.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fflashalpha.com%2Fimages%2Fhero-image-2.jpg" height="317" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://flashalpha.com/" rel="noopener noreferrer" class="c-link"&gt;
            FlashAlpha | Options Analytics API  -  Greeks, Exposure &amp;amp; Vol Surfaces
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Real-time options analytics API  -  Greeks, gamma exposure, SVI vol surfaces for 6,000+ underlyings. Free tier available.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fflashalpha.com%2Fimages%2Flogo.png" width="500" height="440"&gt;
          flashalpha.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>python</category>
      <category>api</category>
      <category>finance</category>
      <category>options</category>
    </item>
    <item>
      <title>Volatility Surface API: How to Build, Visualize, and Trade the IV Surface with Code</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Wed, 08 Apr 2026 20:14:23 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/volatility-surface-api-how-to-build-visualize-and-trade-the-iv-surface-with-code-4p72</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/volatility-surface-api-how-to-build-visualize-and-trade-the-iv-surface-with-code-4p72</guid>
      <description>&lt;p&gt;Black-Scholes assumes one volatility number applies to every option on a stock. That's wrong. A $500 SPY put expiring Friday trades at 65% IV. A $700 call expiring in six months trades at 22%. Same underlying, wildly different volatilities.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;volatility surface&lt;/strong&gt; captures this reality — a 3D map of implied volatility across strike prices and expiration dates. If you trade anything beyond naked calls and puts, you need it. It's the single most important data structure in options.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Surface Looks Like
&lt;/h2&gt;

&lt;p&gt;Three slices have names you've probably heard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volatility skew&lt;/strong&gt; — IV across strikes at one expiry. For equities, OTM puts trade at higher IV than OTM calls because institutions buy downside protection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Term structure&lt;/strong&gt; — ATM IV across expirations. Usually upward-sloping (contango). When it inverts, the market is screaming "event ahead."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The full surface&lt;/strong&gt; — the complete 3D object. What SVI models fit, what arbitrageurs scan, and what every vol desk monitors in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why It Changes How You Trade
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Find mispriced options.&lt;/strong&gt; The SVI model fits a smooth curve through noisy market IVs. Any contract significantly above the curve is rich — sell it. Below the curve — buy it. A 3-vol-point residual on a 30-DTE SPY option translates to roughly $0.50 of edge per contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time your calendar spreads.&lt;/strong&gt; The term structure tells you when near-term vol is cheap relative to far-term. Steep contango = sell the front month, buy the second month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read fear in real time.&lt;/strong&gt; 25-delta skew above 6 vol points = the market is paying up for crash protection. Above 9 = extreme fear. Below 3 = complacency.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Vol Surface Is Constructed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Pull the raw option chain
&lt;/h3&gt;

&lt;p&gt;You need every listed contract with live bid/ask quotes, open interest, and volume. Last-trade prices are useless — options are illiquid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://lab.flashalpha.com/optionquote/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; contracts loaded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Output: 13710 contracts loaded
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each contract returns enriched with Greeks, IV (from mid price), and SVI-fitted IV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Solve for implied volatility
&lt;/h3&gt;

&lt;p&gt;Newton-Raphson root-finding on Black-Scholes: "what volatility produces this market price?" Edge cases that trip up every team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deep ITM options&lt;/strong&gt; — almost no extrinsic value, solver converges to garbage. Use OTM only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Penny options&lt;/strong&gt; (bid=0, ask=$0.01) — can't meaningfully solve. Require bid &amp;gt; 0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward price vs. spot&lt;/strong&gt; — using spot instead of dividend-adjusted forward makes near-ATM call IVs systematically wrong. The #1 implementation bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Filter the noise
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;Threshold&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zero bid&lt;/td&gt;
&lt;td&gt;bid = 0&lt;/td&gt;
&lt;td&gt;No real market&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide spread&lt;/td&gt;
&lt;td&gt;spread/mid &amp;gt; 50%&lt;/td&gt;
&lt;td&gt;Too much uncertainty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low OI&lt;/td&gt;
&lt;td&gt;OI &amp;lt; 10&lt;/td&gt;
&lt;td&gt;Price may be stale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extreme moneyness&lt;/td&gt;
&lt;td&gt;\&lt;/td&gt;
&lt;td&gt;delta\&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ITM options&lt;/td&gt;
&lt;td&gt;Use OTM only&lt;/td&gt;
&lt;td&gt;Solver instability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 4: Fit SVI
&lt;/h3&gt;

&lt;p&gt;Raw IVs are still noisy. You need a smooth, arbitrage-free curve through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  SVI: The Industry Standard
&lt;/h2&gt;

&lt;p&gt;The Stochastic Volatility Inspired model was introduced by Jim Gatheral in 2004. Five parameters, each with clear intuition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight mathematica"&gt;&lt;code&gt;&lt;span class="nv"&gt;w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rho&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sigma&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;What It Controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overall variance level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wing steepness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rho&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skew direction (negative = put skew)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Horizontal shift of minimum variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sigma&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ATM curvature&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why SVI dominates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Arbitrage-free by construction&lt;/strong&gt; with Gatheral's constraints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extrapolation&lt;/strong&gt; to unlisted strikes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 parameters&lt;/strong&gt; vs. 50+ knots for cubic splines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSVI extension&lt;/strong&gt; handles the full surface across expiries&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Get SVI parameters from an API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://lab.flashalpha.com/v1/adv_volatility/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ALPHA_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;svi_parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expiry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;days_to_expiry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; DTE): &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  b=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  rho=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rho&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  sigma=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sigma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ATM_IV=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;atm_iv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Symbol: SPY, Spot: $658.29
  2026-04-09 (  2 DTE): a=-0.00021  b=0.00920  rho=+0.032  ATM_IV=37.5%
  2026-04-10 (  3 DTE): a=+0.00012  b=0.00781  rho=-0.041  ATM_IV=32.1%
  2026-04-14 (  7 DTE): a=+0.00089  b=0.00534  rho=-0.187  ATM_IV=26.4%
  2026-04-17 ( 10 DTE): a=+0.00112  b=0.00478  rho=-0.231  ATM_IV=24.8%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;rho&lt;/code&gt; gets more negative at longer tenors — put skew steepens as DTE increases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Vol Surface in 15 Lines of Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mpl_toolkits.mplot3d&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Axes3D&lt;/span&gt;

&lt;span class="c1"&gt;# Public endpoint - no API key required
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://lab.flashalpha.com/v1/surface/SPY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meshgrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moneyness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tenors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot_surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;plasma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edgecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Log-Moneyness (k)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tenor (years)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_zlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IV (%)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SPY Implied Volatility Surface&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;azim&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;spy_vol_surface.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/v1/surface&lt;/code&gt; endpoint returns a 41-point moneyness grid × 37 tenor slices, SVI-fitted and interpolated. No filtering, no calibration, no edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Using spot instead of forward price&lt;/strong&gt; — OTM call IVs near ATM will be systematically wrong by 5-15 vol points. The #1 bug in homegrown SVI code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fitting ITM options&lt;/strong&gt; — a $100 ITM call with $0.50 of time value breaks the IV solver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oversmoothing short-dated slices&lt;/strong&gt; — 0-3 DTE options need special handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring calendar arbitrage&lt;/strong&gt; — independent per-expiry SVI fits can produce total variance that decreases in time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not filtering by open interest&lt;/strong&gt; — a strike with 2 contracts of OI will have meaningless IV.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reading the Surface
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Steep put skew (&amp;gt;6 vol points)&lt;/strong&gt; = fear or hedging demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat surface&lt;/strong&gt; = complacency, usually precedes vol expansion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Term structure inversion&lt;/strong&gt; = event premium (earnings, FOMC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large SVI residuals&lt;/strong&gt; = mispricing candidates for vol arb&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five Strategies Powered by the Surface
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Skew trades&lt;/strong&gt; — sell rich OTM puts, buy cheap OTM calls when skew is extreme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar spreads&lt;/strong&gt; — sell front month in contango, buy second month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vol arb from SVI residuals&lt;/strong&gt; — buy contracts below the fitted curve, delta-hedge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iron condors&lt;/strong&gt; — flat skew + contango + positive gamma regime = ideal setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanna-informed directional&lt;/strong&gt; — vol compression + large positive vanna = mechanical bid for the underlying&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The &lt;a href="https://flashalpha.com/docs/lab-api-volatility" rel="noopener noreferrer"&gt;FlashAlpha Volatility API&lt;/a&gt; returns SVI parameters, gridded surfaces, skew metrics, and arbitrage flags for 6,000+ US equities and ETFs. The public &lt;code&gt;/v1/surface&lt;/code&gt; endpoint requires no API key.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://flashalpha.com/articles/volatility-surface-api-how-to-build-visualize-trade-iv-surface" rel="noopener noreferrer"&gt;flashalpha.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




</description>
      <category>options</category>
      <category>volatility</category>
      <category>python</category>
      <category>api</category>
    </item>
    <item>
      <title>0DTE Options Scanner API — Screen Same-Day Contracts by Delta, OI, and Gamma Regime</title>
      <dc:creator>tomasz dobrowolski</dc:creator>
      <pubDate>Mon, 06 Apr 2026 15:13:21 +0000</pubDate>
      <link>https://dev.to/tomasz_dobrowolski_35d32c/0dte-options-scanner-api-screen-same-day-contracts-by-delta-oi-and-gamma-regime-kl2</link>
      <guid>https://dev.to/tomasz_dobrowolski_35d32c/0dte-options-scanner-api-screen-same-day-contracts-by-delta-oi-and-gamma-regime-kl2</guid>
      <description>&lt;p&gt;0DTE options have become the dominant force in intraday market structure. On any given day, same-day expiry contracts on SPY alone can account for 40–50% of total options volume. The problem: scanning for actionable 0DTE setups across dozens of symbols requires pulling chains, filtering by delta and OI, checking gamma regime, and doing it all before the contracts decay to nothing.&lt;/p&gt;

&lt;p&gt;I built this with one POST request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scanner vs endpoint — different questions
&lt;/h2&gt;

&lt;p&gt;FlashAlpha has a dedicated &lt;a href="https://flashalpha.com/docs/lab-api-zero-dte" rel="noopener noreferrer"&gt;0DTE analytics endpoint&lt;/a&gt; that returns pin risk, expected move, and gamma regime for one symbol at a time. That's the right tool when you know which name you're trading.&lt;/p&gt;

&lt;p&gt;A scanner answers a different question: &lt;strong&gt;which symbols have 0DTE setups worth looking at right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of looping through 20+ symbols and merging client-side, one screener POST returns only the symbols — and only the contracts within those symbols — that match your criteria.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key: cascading filters
&lt;/h2&gt;

&lt;p&gt;When you combine stock-level, expiry-level, and contract-level conditions inside an AND group, the server cascades the filter downward. Non-matching children get pruned at each level, and symbols with zero survivors are dropped entirely.&lt;/p&gt;

&lt;p&gt;For 0DTE, you set &lt;code&gt;expiries.days_to_expiry = 0&lt;/code&gt; to isolate same-day, then layer contract-level conditions on top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.delta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.oi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response contains, for every symbol in your universe: only the 0DTE expiry, only the call contracts, only the ones clearing the delta and OI floor. No other expiries, no puts, no low-delta noise. The response is already shaped like the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five 0DTE scanner recipes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 0DTE call seller (positive gamma only)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"positive_gamma"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.delta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.oi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;regime = positive_gamma&lt;/code&gt; condition means you're only selling calls where dealers are dampening moves, not amplifying them. Rich theta, friendly regime, liquid contracts.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 0DTE put scanner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.delta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"between"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.volume"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finds 0DTE puts that are actually trading (volume ≥ 500) in the 20–50 delta range. Useful for protective puts or speculative directional bets on names showing intraday weakness.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Negative-gamma 0DTE scanner (amplified move risk)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"negative_gamma"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"net_gex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"net_gex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gamma_flip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_net_gex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_pct_of_total"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surfaces names where 0DTE dealer gamma is negative, sorted by most-negative GEX. These are the names where intraday moves will be amplified by dealer hedging. Avoid selling premium here — or use them as breakout candidates where you buy directional 0DTE options and ride the gamma amplification.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. ATM 0DTE straddle scanner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.delta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"between"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.oi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"atm_spread_pct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;atm_spread_pct ≤ 0.03&lt;/code&gt; stock-level filter prunes names with bad microstructure before the contract-level cascade runs. This is your 0DTE iron condor scanner: find which names have the liquidity for a same-day structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 0DTE + high GEX concentration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_pct_of_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expiries.days_to_expiry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contracts.oi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_pct_of_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"desc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_pct_of_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero_dte_net_gex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regime"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surfaces names where 0DTE options account for 30%+ of total gamma. These are the most "0DTE-driven" symbols today — pin risk is elevated, gamma acceleration is concentrated, and theta decay is extreme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python workflow: scan then drill
&lt;/h2&gt;

&lt;p&gt;The pattern that works best: screener finds candidates, then the per-symbol 0DTE endpoint gives you pin risk and expected move for the ones you want to trade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flashalpha&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlashAlpha&lt;/span&gt;

&lt;span class="n"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlashAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: scan for 0DTE call-selling candidates
&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conditions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive_gamma&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expiries.days_to_expiry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contracts.type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contracts.delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contracts.oi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zero_dte_net_gex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: drill into top candidates
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_dte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Pin risk:      &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pin_risk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Expected move: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expected_move&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;range_1_sigma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Regime:        &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gamma_regime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;regime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two requests instead of N. The screener finds the best candidates; the 0DTE endpoint gives you pin risk, expected move, and dealer hedging detail for each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDK support
&lt;/h2&gt;

&lt;p&gt;The screener method is available in all five FlashAlpha SDKs (Python, JavaScript, .NET, Java, Go). No need to build the HTTP POST yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python
&lt;/span&gt;&lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// JavaScript&lt;/span&gt;
&lt;span class="nx"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screener&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .NET&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScreenerAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ScreenerRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full spec: &lt;a href="https://flashalpha.com/docs/lab-api-screener" rel="noopener noreferrer"&gt;Live Screener docs&lt;/a&gt;. More recipes: &lt;a href="https://flashalpha.com/docs/lab-api-screener-cookbook" rel="noopener noreferrer"&gt;Screener Cookbook&lt;/a&gt;. Per-symbol 0DTE analytics: &lt;a href="https://flashalpha.com/docs/lab-api-zero-dte" rel="noopener noreferrer"&gt;0DTE endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Live Screener is part of the &lt;a href="https://flashalpha.com" rel="noopener noreferrer"&gt;FlashAlpha&lt;/a&gt; Real-Time Options Analytics API. Growth gives you 20 symbols with cascading 0DTE filters. Alpha unlocks ~248 symbols, formulas, and strategy scores. &lt;a href="https://flashalpha.com/profile" rel="noopener noreferrer"&gt;Free tier&lt;/a&gt; at 5 requests/day. &lt;a href="https://flashalpha.com/pricing" rel="noopener noreferrer"&gt;Compare plans&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>options</category>
      <category>api</category>
      <category>python</category>
      <category>trading</category>
    </item>
  </channel>
</rss>
