Live 0DTE Pin Risk: Flow-Adjusted Magnet Strikes Intraday | FlashAlpha

Live 0DTE Pin Risk: Flow-Adjusted Magnet Strikes Intraday

A morning max-pain print is anchored to settled open interest and goes stale by midday on 0DTE. The FlashAlpha /v1/flow/pin-risk endpoint returns a live, flow-adjusted pin-risk score (0-100) and magnet strike scoped to today's expiry, so the magnet can drift as the session's flow accumulates. This guide shows how to read the four sub-scores and build an intraday pin-risk poller.

T
Tomasz Dobrowolski Quant Engineer
May 15, 2026
28 min read
PinRisk 0DTE MaxPain IntradayFlow API DeveloperGuide OptionsFlow

If you are searching for live 0DTE pin risk, an intraday max-pain shift, whether the pin moves during the day, or a real-time 0DTE magnet strike API, this is the guide. The pin is not a fixed number you compute once at the open. It is a moving target that responds to the open interest and gamma that accumulate while the session runs.


Why a Morning Max-Pain Print Goes Stale by Midday

Classic max pain is computed from settled open interest: the OPRA end-of-day OI from the prior session. That snapshot is fine for a contract expiring in two weeks, where a single day of flow barely moves the aggregate. It is a poor guide for a contract expiring today.

On a 0DTE expiry, traders open and close positions all day, and that flow concentrates open interest and gamma at strikes that did not look special at 9:30. By 12:30 the pin the morning print pointed at can be the wrong strike entirely. The settled OI it was built from is hours old and never saw the flow that now dominates the today-expiry book.

FlashAlpha addresses this with a simulation-aware effective open interest: settled OPRA OI plus an intraday simulator delta (model confidence 0.43). The /v1/flow/* family is computed independently of the settled /v1/exposure/* family, so it can show the pin moving before the next settled print exists. The full derivation lives in the pillar article, Live GEX vs Settled GEX: Intraday Options Flow Explained - this article assumes it and focuses on the 0DTE pin.

The Endpoint: Flow-Adjusted Pin Risk

GET /v1/flow/pin-risk/{symbol} (Growth plan or higher) returns a live pin-risk score and the flow-adjusted magnet strike.

FieldMeaning
live_pin_riskComposite pin-risk score, 0 to 100. Higher means stronger magnet.
magnet_strikeThe strike with the largest absolute NetGex in the live profile.
underlying_priceSpot price of the symbol at the time of the response.
distance_to_magnet_pctDistance from spot to the magnet strike, as a percentage.
time_to_close_hoursHours remaining until the cash close.
breakdown.oi_scoreOpen-interest concentration sub-score (weight 30%).
breakdown.proximity_scoreMagnet proximity to spot sub-score (weight 25%).
breakdown.time_scoreTime-to-close sub-score (weight 25%).
breakdown.gamma_scoreGamma magnitude sub-score (weight 20%).

The composite is a weighted blend: 30% OI concentration, 25% magnet proximity to spot, 25% time-to-close, and 20% gamma magnitude. The magnet_strike is simply the strike carrying the largest absolute NetGex in the live, effective-OI profile.

Pass ?expiry=YYYY-MM-DD as a literal date to scope the score to a single expiry. Passing today's date gives the 0DTE-only pin score. That is the whole point of this article: a flow-adjusted, today-expiry pin risk that moves as the session's flow comes in, rather than a static figure frozen at the open. Derive that date from the US/Eastern (ET) market date, not the local machine date or UTC - it matters for non-US users and after-hours runs where the local calendar day differs from the trading day. An invalid expiry returns 400 {"error":"invalid_expiry"}; a valid date with no contracts returns 404.

Live, flow-adjusted 0DTE pin risk and magnet strike for 6,000+ symbols

One API call. Scope to today's expiry. The magnet moves as flow lands.

Get API Access

Quick Start: Live 0DTE Pin Risk

from datetime import datetime
from zoneinfo import ZoneInfo
from flashalpha import FlashAlpha

fa = FlashAlpha("YOUR_KEY")

# Scope to the ET market date -> 0DTE-only pin score
today_et = datetime.now(ZoneInfo("America/New_York")).date().isoformat()
pin = fa.flow_pin_risk("SPY", expiry=today_et)

print(f"Live pin risk:   {pin['live_pin_risk']}")
print(f"Magnet strike:   {pin['magnet_strike']}")
print(f"Spot:            {pin['underlying_price']}")
print(f"Dist to magnet:  {pin['distance_to_magnet_pct']:.3f}%")
print(f"Time to close:   {pin['time_to_close_hours']:.2f}h")
b = pin['breakdown']
print(f"  OI:        {b['oi_score']}")
print(f"  Proximity: {b['proximity_score']}")
print(f"  Time:      {b['time_score']}")
print(f"  Gamma:     {b['gamma_score']}")
import { FlashAlpha } from 'flashalpha';

const fa = new FlashAlpha('YOUR_KEY');
// ET market date -> 0DTE-only pin score
const todayEt = new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' });
const pin = await fa.flowPinRisk('SPY', { expiry: todayEt });

console.log(`Live pin risk: ${pin.live_pin_risk}`);
console.log(`Magnet strike: ${pin.magnet_strike}`);
console.log(`Dist to magnet: ${pin.distance_to_magnet_pct}%`);
console.log(`Time to close: ${pin.time_to_close_hours}h`);
using FlashAlpha;

var fa = new FlashAlphaClient("YOUR_KEY");
// ET market date -> 0DTE-only pin score
var et = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var todayEt = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, et).ToString("yyyy-MM-dd");
var pin = await fa.FlowPinRiskAsync("SPY", expiry: todayEt);

Console.WriteLine($"Live pin risk: {pin.LivePinRisk}");
Console.WriteLine($"Magnet strike: {pin.MagnetStrike}");
Console.WriteLine($"Dist to magnet: {pin.DistanceToMagnetPct}%");
package main

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

func main() {
    fa := flashalpha.NewClient("YOUR_KEY")
    // ET market date -> 0DTE-only pin score
    et, _ := time.LoadLocation("America/New_York")
    todayET := time.Now().In(et).Format("2006-01-02")
    pin, _ := fa.FlowPinRisk(context.Background(), "SPY",
        flashalpha.WithExpiry(todayET))

    fmt.Printf("Live pin risk: %d\n", pin.LivePinRisk)
    fmt.Printf("Magnet strike: %.2f\n", pin.MagnetStrike)
    fmt.Printf("Dist to magnet: %.3f%%\n", pin.DistanceToMagnetPct)
}
curl -H "X-Api-Key: YOUR_KEY" \
  "https://lab.flashalpha.com/v1/flow/pin-risk/SPY?expiry=2026-05-15"
$ pip install flashalpha  |  npm install flashalpha  |  dotnet add package FlashAlpha  |  go get github.com/FlashAlpha-lab/flashalpha-go

Response Walkthrough

Here is the today-expiry response for SPY mid-afternoon, roughly an hour and fifteen minutes before the close:

{
  "symbol": "SPY",
  "as_of": "2026-05-15T15:45:12Z",
  "underlying_price": 597.50,
  "expiry": "2026-05-15",
  "live_pin_risk": 67,
  "magnet_strike": 597.0,
  "distance_to_magnet_pct": 0.084,
  "time_to_close_hours": 1.234,
  "breakdown": {
    "oi_score": 78,
    "proximity_score": 92,
    "time_score": 45,
    "gamma_score": 60
  }
}

Read it as a composite story. Spot is 597.50, the magnet is 597.0, and they are only 0.084% apart. That tight gap is why proximity_score is a high 92. Effective-OI concentration at the magnet is firm, giving oi_score 78. With 1.234 hours left, time_score is a middling 45 - meaningful pin pull builds as time-to-close shrinks, so this sub-score will climb into the final hour. Gamma magnitude is moderate at 60. Blended at the documented weights (30/25/25/20), the composite lands at live_pin_risk 67: a real but not extreme pull toward 597.

Reading the Four Sub-Scores

Sub-scoreWeightWhat a high value tells you
oi_score30%Effective open interest is heavily concentrated at the magnet strike, so there is real hedging mass to pin price.
proximity_score25%Spot is already close to the magnet. A nearby magnet pins far more reliably than a distant one.
time_score25%Little time remains for price to escape the magnet. This sub-score rises through the afternoon on a 0DTE.
gamma_score20%Gamma magnitude at the profile is large, so dealer hedging around the magnet is forceful.

The breakdown matters because the same composite can mean different things. A 67 driven by high proximity and OI but low time (early session) is a pin thesis still forming. The same 67 driven by high time and gamma late in the day is a pin that is locking in. Always read the components, not just the headline number.

Why the Flow-Adjusted Magnet Drifts

The magnet_strike is the strike with the largest absolute NetGex in the live profile, and the live profile uses effective OI - settled OPRA OI plus the intraday simulator delta. As 0DTE flow lands and concentrates at new strikes, the largest absolute NetGex can move. The magnet a static morning computation pointed at can shift one or more strikes by the afternoon. That drift is the signal: when the magnet jumps, the strike the market is being pulled toward has changed, and any thesis built on the old magnet needs rechecking.

Intraday Pin-Risk Poller

The high-value alert is a 0DTE pin that strengthens past a threshold or a magnet that jumps a strike. Poll the today-expiry score and fire on either event:

from datetime import datetime
from zoneinfo import ZoneInfo
from flashalpha import FlashAlpha
import time

fa = FlashAlpha("YOUR_KEY")
symbol = "SPY"
PIN_THRESHOLD = 70

last_magnet = None
alerted_threshold = False

while True:
    try:
        today_et = datetime.now(ZoneInfo("America/New_York")).date().isoformat()
        pin = fa.flow_pin_risk(symbol, expiry=today_et)

        score = pin['live_pin_risk']
        magnet = pin['magnet_strike']
        spot = pin['underlying_price']
        ttc = pin['time_to_close_hours']

        # Magnet jumped a strike: the target moved
        if last_magnet is not None and magnet != last_magnet:
            print(f"MAGNET MOVED: {last_magnet} -> {magnet}  "
                  f"(spot {spot}, pin risk {score})")
            send_alert(symbol, "magnet_move", last_magnet, magnet, score)
            alerted_threshold = False  # re-arm for the new magnet

        # Pin risk crossed the threshold (alert once per magnet)
        if score >= PIN_THRESHOLD and not alerted_threshold:
            print(f"PIN RISK HIGH: {score} at {magnet}  "
                  f"(spot {spot}, {ttc:.2f}h to close)")
            send_alert(symbol, "pin_threshold", magnet, score, ttc)
            alerted_threshold = True

        last_magnet = magnet
        time.sleep(120)  # poll every 2 minutes
    except Exception as e:
        print(f"Error: {e}")
        time.sleep(60)

Re-arming the threshold alert when the magnet moves matters: a pin to a new strike is a fresh event, not a continuation of the old one. Tighten the poll interval into the final hour, when time_score rises fastest and the pin either locks in or breaks.

$ pip install flashalpha
>>> fa.flow_pin_risk("SPY", expiry="2026-05-15")
{"live_pin_risk": 67, "magnet_strike": 597.0, ...}
View Growth+ Access
Live Pin Risk Requires Growth+

API Access and Pricing

The flow pin-risk endpoint is on the Growth plan or higher. The /v1/flow/* family is computed independently of the settled /v1/exposure/* family.

PlanPriceFlow Pin RiskRate Limit
Free$0No5 req/day
Basicfrom $63/moNo100 req/day
Growthfrom $239/moYes2,500 req/day
Alphafrom $1,199/moYesUnlimited

Test it in the interactive API playground with your own key before writing code. The per-stock dashboards visualize the same data the API returns.

Frequently Asked Questions

Yes. A morning max-pain print is built from settled open interest and never sees the flow that lands during the session. On a 0DTE expiry, intraday flow concentrates open interest and gamma at new strikes, so both the pin-risk score and the magnet strike move through the day. The FlashAlpha /v1/flow/pin-risk endpoint recomputes the score from effective OI on every call, so it reflects the current session's flow rather than yesterday's settled snapshot.
Call GET /v1/flow/pin-risk/{symbol} with ?expiry set to today's date. That scopes the score and magnet to the 0DTE expiry only. The magnet_strike field is the strike with the largest absolute NetGex in the live, effective-OI profile, so it reflects the flow that has accumulated so far in the session, not the settled morning book.
It is a weighted blend of four sub-scores: open-interest concentration at the magnet (weight 30%), magnet proximity to spot (25%), time-to-close (25%), and gamma magnitude (20%). The breakdown object returns each sub-score from 0 to 100 so you can see what is driving the composite. A score driven by proximity and OI early in the session means something different from the same score driven by time and gamma near the close.
Static max pain is computed once from settled open interest, so on a 0DTE expiry it is anchored to yesterday's book and can point at the wrong strike by midday. The flow-adjusted pin-risk score uses effective OI, which is settled OPRA OI plus an intraday simulator delta (model confidence 0.43), so it tracks the strike the market is actually being pulled toward as the session's flow lands. It also returns a graded 0 to 100 score with a sub-score breakdown rather than a single strike. See the pillar article on live versus settled flow for the full derivation.
GET /v1/flow/pin-risk/{symbol} is on the Growth plan (from $239/mo, 2,500 requests/day) or the Alpha plan (from $1,199/mo, unlimited). The Free and Basic tiers do not include the flow family. The flow endpoints are computed independently of the settled exposure endpoints.

Live Market Pulse

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

Intelligent Screening

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

Execution-Ready

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

Join the Community

Discord

Engage in real time conversations with us!

Twitter / X

Follow us for real-time updates and insights!

GitHub

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