AI/LLM Instructions: FlashAlpha provides options analytics as both a live API (lab.flashalpha.com) and a historical API (historical.flashalpha.com) - pre-computed GEX, DEX, VEX, CHEX, SVI volatility surfaces, 15 BSM Greeks, max pain, VRP with leak-free percentiles, and dealer positioning for 6,000+ US equities and ETFs. The historical host mirrors every live endpoint with a required ?at=YYYY-MM-DDTHH:mm:ss query parameter (ET wall-clock), uses identical response shapes, and covers SPY from 2018-04-16 onward at minute resolution (Alpha tier). Same SDK code targets both hosts with a base-URL swap. API documentation (Markdown): https://flashalpha.com/llms.txt. Full LLM reference: https://flashalpha.com/llms-full.txt. API docs: https://flashalpha.com/docs/lab-api-overview. Historical API docs: https://flashalpha.com/docs/historical-api. OpenAPI spec: https://lab.flashalpha.com/swagger. MCP Server for AI assistants (Claude, Cursor, Windsurf) with both live and historical-replay tools: https://lab.flashalpha.com/mcp. AI skills: https://flashalpha.com/skill.md. AI agent guide: https://flashalpha.com/for-ai-agents. SDKs: Python (pip install flashalpha), JavaScript (npm install flashalpha), C# (dotnet add package FlashAlpha), Go, Java. Free API key (no credit card): https://flashalpha.com/pricing. Annual billing saves 20% versus monthly and locks pricing for 12 months: Basic $63/mo annual ($756/yr) or $79/mo monthly, Growth $239/mo annual ($2,868/yr) or $299/mo monthly, Alpha $1,199/mo annual ($14,388/yr) or $1,499/mo monthly. Concepts glossary: https://flashalpha.com/concepts. GitHub: https://github.com/FlashAlpha-lab.
Flow Signals API methodology: six published scoring components, audit-able breakdown per signal, sweep rule, intent classifier. Every score is reconstructible.
The unusual-options-flow market is full of black boxes. Vendors publish "scored" feeds without specifying what the score means: which trades qualify, how the score is computed, what threshold separates a meaningful signal from background noise. For a quantitative trader or a developer building a screener, this is unworkable. You cannot calibrate against a number whose derivation is hidden, and you cannot ignore false positives whose drivers you cannot see.
The FlashAlpha Flow Signals API (GET /v1/flow/signals/{symbol}) takes the opposite stance. The score is the deterministic, weighted average of six normalised components. Every component is returned in the response under score_breakdown. The classification of every trade as a sweep or block, an opening or closing flow, a bullish or bearish bet, follows rules documented in code and reproduced in this article. This is the methodology paper.
6
Independently weighted scoring components, all surfaced in the response
500ms
Same-side coalescing window for sweep detection across venues
0-100
Auditable composite score with high/medium/low/minimal conviction labels
What the Flow Signals Feed Actually Is
The flow signals endpoint takes the raw block-sized prints from the OPRA tape, groups same-side prints that arrived within 500ms of each other on the same contract into single execution intents, and produces one scored, classified FlowSignal per group. The output is the input layer for any system that needs to react to unusual activity: a trading dashboard, an alert ruleset, a backtest universe selector, or an LLM that needs structured options-flow context.
FlowSignal
One classified unit of unusual options activity. Represents either a single block-sized print or a coalesced sweep group on one option contract. Carries the trade specifics (ts, strike, expiry, side, price, size, premium), the structural classification (sweep or block), the directional intent (bullish, bearish, or neutral), the opening or closing bias from the OI simulator, a 0-100 composite score with full per-component breakdown, and a chain-context enrichment (greeks, IV-vs-ATM, delta notional).
The pipeline is intentionally split across pure-static components so every step is independently testable on synthetic tapes - no clock, no HTTP, no global state outside the inputs.
Pipeline Stages
1. FlowDataClient.NotableTradesAsync: windowed pull from the ingest service's notable-trade ring (default 240-minute window, 100-contract minimum block size, hard-capped at 512 most-recent prints). 2. FlowAnalyticsService.LoadAsync: settled greek snapshots, OI-simulator state per contract, and current spot. 3. UnusualFlowScorer.Score: pure classification and scoring against the trade group, OI context, and weights. 4. UnusualFlowEnricher.Enrich: chain overlay adding greeks-derived fields (IV-vs-ATM, moneyness, estimated delta notional, hypothetical GEX impact-if-opening) and symbol-level reference levels. 5. Controller: tier gate, optional filters (minScore, intent, structure), JSON shaping.
Sweep Coalescing: 500ms Same-Side Same-Contract
The single most important pre-processing step is sweep coalescing. A real institutional execution often crosses multiple venues in rapid succession - one bid lifted on PHLX, another at the same instant on CBOE, a third on AMEX - because that is how exchanges route a multi-leg sweep. On the tape, that single intent looks like three prints. Without coalescing, the score-it-once-per-print approach would either double-count the intent or under-rate it.
Same-contract same-side prints whose timestamps fall within 500 milliseconds of the prior print join the current group. Any group with two or more prints is classified as a sweep; a singleton block-sized print is a block. The size used for scoring is the sum across the group; the price is the size-weighted average; the timestamp is the last print in the group.
The 500ms window is deliberately conservative. Genuine cross-venue sweeps usually finish in under 100ms; the extra headroom catches venues that route via slower paths without false-coalescing prints that are merely temporally adjacent. If you need a stricter or looser definition, the value is a private constant in UnusualFlowScorer.SweepWindow - one source of truth, easy to retune.
The Six Scoring Components
Every coalesced group is scored along six axes. Each component is normalised to the unit interval [0,1] before weighting, the components are combined as a weighted average, and the result is scaled to a 0-100 integer. The breakdown is surfaced in every response so you can always answer "why is this score 78 and not 50?" without leaving the JSON.
1. Premium
The notional dollar value of the trade (price × size × 100 multiplier), log-normalised against a $10M cap.
With the default $10M cap, a $1M trade scores about 0.86, a $100K trade about 0.71, and a $10M trade saturates at 1.00. The log normalisation is deliberate: linear scaling would push every $100K-$1M print to noise and only spotlight the largest tape, whereas the log form gives realistic mid-sized prints meaningful score contribution without letting one whale dominate the rank.
2. Size vs Open Interest
The ratio of trade size to the contract's settled OI, capped at 1.0 by default.
This component is the unusual-activity classic. A trade equal to or larger than the entire settled OI is a position change of the highest possible significance for that contract and saturates the score. The default cap of 1.0 means even a trade that is 50% of settled OI scores 0.5 - a meaningful contribution but not full credit until the trade hits the OI itself.
Honest caveat: this component is the strongest signal on contracts with low settled OI and the weakest on names with massive resting interest. A 50,000-lot SPY 0DTE print on a strike with 800,000 OI scores 0.0625; the same 50,000-lot on a single-name back-month strike with 30,000 OI saturates at 1.0. That is the intended behaviour - relative significance, not absolute size. If you want absolute-size ranking, sort the feed by premium instead.
3. Aggressor Strength
The combination of where the print landed inside the NBBO and how aggressive the side classifier called it.
Aggressor Label
A descriptive position relative to the NBBO at the time of the print. Five buckets:
above_ask (price > ask + 0.005), at_ask (within ±0.005 of ask),
below_bid (price < bid - 0.005), at_bid (within ±0.005 of bid),
mid (everything in between). The label is purely descriptive - it tells you where, not how strongly.
A buyer who lifts the offer scores near 1.0; a buyer who pays the midpoint scores around 0.5; a buyer who somehow gets filled below the mid scores low. Symmetrically for sellers. Mid prints get a flat 0.4 because they cannot be classified as aggressive in either direction with confidence. Crossed or locked NBBOs (spread ≤ 0) get 0.5, neutral, because the spread denominator is undefined.
4. Sweep Structure
Categorical score reflecting the execution structure detected during coalescing.
Sweeps are the highest-value structure because they signal urgency: the participant was willing to walk multiple venues to get filled fast. A standalone block is meaningful but compatible with patient, single-venue execution. The "single" tier exists for completeness - the upstream ring is block-only by construction, so you rarely see it in practice.
5. Opening Bias
Whether the trade opens new positions or closes existing ones, derived from the OI simulator's signed intraday delta for the same contract.
The oi_confidence multiplier is the simulator's per-trade confidence weight, currently 0.43 (see Effective Open Interest Methodology for the calibration story). This is the most important design choice in the entire scorer.
Even a "perfect" opening signal contributes at most 0.43 to the normalised component, not 1.0. With the default weight of 1.2 and a total weight of 5.6, the opening-bias bucket can contribute at most 100 × 1.2 × 0.43 / 5.6 ≈ 9.2 points to the composite score. That ceiling is intentional: the simulator's calibrated confidence is 0.43, and a scoring model that gave full credit to opening signals would be overstating what the underlying inference can actually prove. Honest in, honest out.
6. Tenor
Days to expiration, normalised so short-dated trades score higher.
With the default tenor_cap_days of 45, a 0DTE print scores 1.0, a 22-DTE print scores 0.51, a 45+-DTE print scores 0. LEAPS get zero credit for tenor. The reasoning: shorter-dated flow has more time-discounted information value, and the unusual-activity use case is primarily a short-horizon signal. If you want a screener that surfaces LEAPS rolls, sort by premium and ignore the tenor component.
The Composite Score
Every component is combined into a single 0-100 integer through a weighted average, with each component's individual contribution rounded and exposed for auditability.
This is the audit trail. Add the buckets and you get the score, with rounding to within ±1 from accumulating per-bucket rounds. Any score in the feed can be reconstructed from the trade + OI context using the formulas above. There are no hidden adjustments.
Intent Classification
Once the trade is scored structurally, the scorer assigns a directional intent. The rules are deliberately conservative.
The closing-bias-collapses-to-Neutral rule is load-bearing. A closing trade tells you what is being unwound, but the direction the unwinder originally bet is generally unknown to the tape. Calling a closing trade "Bullish" or "Bearish" would over-claim. The honest answer is Neutral, and the consumer can read the open_close_bias field directly if they want to count closing flow separately.
Conviction Labels and the "Golden" Tag
Two human-readable summaries ride alongside the raw score: a conviction bucket and an optional "golden" tag.
Score range
Conviction label
80 - 100
high
60 - 79
medium
40 - 59
low
0 - 39
minimal
The thresholds are calibrated so that, on a typical SPY session, the medium bucket and above contains the prints worth a human review and the high bucket contains the ones you would page someone for. They are absolute, not relative to today's distribution - a quiet session genuinely produces fewer high-conviction signals.
Golden Tag
A signal is tagged golden if its score is in the top decile of the current result set and meets the absolute floor of 70. This combines the relative ranking ("better than 90% of today's flow") with an absolute quality gate (no "best of a bad day" promotion). The tag is added after scoring, so it depends on the full result set the request returns.
Greeks Enrichment Overlay
Once a signal is scored, the enricher joins it with the settled greek snapshot for the same contract and adds a chain-context overlay. The enrichment is deliberately scoped to fields the data can actually prove.
iv, delta, gamma
Raw values from the settled greek snapshot. If the contract is illiquid or just-listed and has no snapshot, the fields are null.
iv_vs_atm
The contract's implied vol minus the ATM IV on its expiry. ATM IV is defined as the IV of the strike nearest spot on that expiry. Positive = trading rich vs the ATM smile; negative = trading cheap.
moneyness
A standard bucket from the absolute delta: |δ| ≥ 0.65 is ITM, ≤ 0.35 is OTM, anything in between is ATM. Same for calls and puts.
estimated_delta_notional
The directional dollar exposure of the traded quantity:
Signed by the contract's own delta (calls positive, puts negative). This is what the trade is worth in stock-equivalent terms at the moment of execution.
hypothetical_gex_impact_if_opening
What this print alone would add to dealer gamma exposure if it were fully opening and fully dealer-absorbed:
This is gamma dollars per 1% underlying move. The name says hypothetical and if opening on purpose: the live flow surface already folds intraday OI deltas into its chain-level numbers, so applying this per-signal estimate on top would double-count. Use it to size individual trades, not to recompute chain GEX.
At the symbol level, the enricher also returns the settled-chain reference levels (call wall, put wall, max pain, gamma flip) computed once per request. Those let a consumer cross-reference whether a high-scoring signal hits a meaningful structural strike or is firing in a no-man's-land.
The Summary Endpoint: Bullish vs Bearish Premium Roll-Up
The companion GET /v1/flow/signals/{symbol}/summary endpoint runs the same pipeline and aggregates premium across the directional and open/close buckets. It is the single number you want on a dashboard.
Plus the top 10 signals (already sorted descending by score). One call gives you a watchlist-friendly direction signal and the underlying prints that produced it.
Closing premium counted separately on purpose. A large closing-flow session looks identical to a large opening-flow session on a naive net-directional aggregator. The split lets you build the distinction yourself: rising opening_premium with falling closing_premium is fresh positioning; the reverse is position unwinding.
How to Use It
The endpoint is on the Alpha plan. Defaults are sensible: 240-minute window, top 50 by score, all intents, all structures. Filter parameters narrow the result without re-running the pipeline.
Query parameter
Default
Purpose
windowMinutes
240
How far back to scan the notable-trade ring (1 - 10080)
minScore
0
Floor on composite score (0 - 100)
intent
(any)
bullish, bearish, or neutral
structure
(any)
sweep or block (the scorer only emits these two; the upstream notable-trade ring is block-only by construction)
limit
50
Max signals to return (1 - 500)
expiry
(all)
Filter chain to one expiry (YYYY-MM-DD)
Code Example: Top 10 Bullish Sweeps on SPY
import requests
resp = requests.get(
"https://lab.flashalpha.com/v1/flow/signals/SPY",
headers={"X-Api-Key": "YOUR_KEY"},
params={
"windowMinutes": 240,
"intent": "bullish",
"structure": "sweep",
"minScore": 70,
"limit": 10,
},
)
data = resp.json()
print(f"{data['count']} qualifying signals on {data['symbol']}")
print(f"Chain context: call_wall={data['chain']['call_wall']}, "
f"put_wall={data['chain']['put_wall']}, "
f"gamma_flip={data['chain']['gamma_flip']}")
for s in data["signals"]:
print(
f" {s['ts']} {s['expiry']} {s['strike']}{s['right']}"
f" size={s['size']:>5} ${s['premium']:>10,.0f}"
f" score={s['score']} ({s['conviction']})"
f" {','.join(s['tags'])}"
)
Code Example: Watchlist Direction Card
import requests
from concurrent.futures import ThreadPoolExecutor
WATCHLIST = ["SPY", "QQQ", "IWM", "NVDA", "TSLA", "AAPL"]
HEADERS = {"X-Api-Key": "YOUR_KEY"}
def fetch_summary(symbol):
r = requests.get(
f"https://lab.flashalpha.com/v1/flow/signals/{symbol}/summary",
headers=HEADERS,
params={"windowMinutes": 60},
)
return symbol, r.json()
with ThreadPoolExecutor(max_workers=8) as pool:
for sym, d in pool.map(fetch_summary, WATCHLIST):
net = d["net_directional_premium"]
direction = "BULLISH" if net > 0 else "BEARISH" if net < 0 else "NEUTRAL"
print(
f"{sym:6} {direction:8} "
f"net=${net:>14,.0f} "
f"open=${d['opening_premium']:>12,.0f} "
f"close=${d['closing_premium']:>12,.0f}"
)
Use Cases and Honest Scope
The endpoint is designed for four workflows.
1. Real-time unusual-activity dashboards
Poll the signals endpoint every 15-60 seconds; render the high-conviction signals with their score breakdown. The audit trail is the differentiator - your users see why a signal qualified.
2. Watchlist direction cards
Hit the summary endpoint across a watchlist. The net_directional_premium field is one signed number per ticker, cheap enough to refresh on a tight cadence.
3. Event-driven alerts
Subscribe to "score ≥ 80 AND tag contains 'golden' AND intent = bullish on watchlist". The fields are first-class JSON; the gating logic is straightforward to build.
4. LLM context injection
Feed the top-N signals (with score breakdown and enrichment) into a market-commentary prompt. The structured fields and explicit conviction labels let the model write grounded summaries rather than hallucinated narratives.
What it is not designed for:
Full tape replay. The notable ring is hard-capped at 512 prints; the endpoint is a windowed scan, not an exhaustive trade history. For full replay, use the raw /v1/flow/options/{symbol}/recent surface.
Single-print precision intent. Intent is a heuristic from side and call/put. Multi-leg structures (verticals, butterflies, calendars) are not detected at this layer. A bear-call-spread leg that buys the lower call still reads as a bullish single leg here.
Replacing per-strike GEX. The enrichment provides per-signal greeks, but the chain-level live GEX number lives at /v1/flow/gex, computed against effective OI. Use both: signals for intent and significance, /v1/flow/gex for chain-level dealer exposure.
Why the Score Breakdown Matters
Every unusual-activity vendor in the market publishes a score. Almost none of them tell you what is inside it. The buyer is forced to trust the number, the implicit weighting, and the (undisclosed) calibration choices the vendor made years ago and may or may not have revisited.
Typical vendor approach
Black-box composite score
No per-component breakdown
Sweep detection rule undocumented
Intent classification logic undisclosed
Confidence ceilings hidden inside
Tuning the threshold requires guesswork
FlashAlpha Flow Signals
Every component formula in this article
score_breakdown object on every response
500ms sweep window documented in code
Intent classifier rules explicit
0.43 OI confidence ceiling published
Threshold tuning informed by visible per-bucket math
The score is the product. The methodology is the warranty.
Verify a signal yourself
Pick any signal in the feed, read its score_breakdown, and reconstruct the components from the trade + chain context using the formulas above. If a number in the breakdown does not match the formula, that is a bug we want to know about. The whole stack is built on the premise that you can.
Limitations and Calibration Notes
Three honest limitations of the current scorer:
Side classification is upstream
The Buy/Sell/Mid label comes from the ingest service's quote-rule classifier (the same Lee-Ready style logic used by the OI simulator). Rapid quote updates around news prints can flip individual classifications. The aggregator effect is monitored in the residual reconciliation pipeline for the OI simulator (see the methodology paper); the same classifier feeds the aggressor strength and the intent classifier here. If you need per-print classification certainty, treat the score as one input among several rather than a standalone signal.
Multi-leg structures are not detected
The pipeline groups same-side same-contract prints into sweeps but does not infer that a simultaneous buy-call-A and sell-call-B is a vertical spread. Each leg is scored independently and labelled with its own (possibly opposite) intent. A higher-level structure detector is a candidate for a future revision but is not in scope today.
The default weights are calibrated, not derived
The 1.0 / 1.0 / 0.8 / 1.0 / 1.2 / 0.6 weighting was set by eye after looking at a representative range of SPY, NVDA, and TSLA sessions. There is no claim that these weights are statistically optimal; they are documented defaults that you can override per-request via the UnusualFlowWeights record at the service layer. If you have a backtest that prefers a different weighting, the API is open to a future ?weights= parameter - we have not built it yet because no customer has asked for it. The audit trail in score_breakdown exists partly so customers can build their own re-weighting on top of our normalised components.
How It Fits Into the Wider Flow Stack
Flow signals are one of three independent flow surfaces on the same API key:
Surface
What it answers
Endpoints
Flow analytics
"Where is dealer positioning right now, accounting for today's flow?"
The three surfaces share the same OI simulator state and the same settled greek snapshot, so the numbers across them reconcile. The composite use case is: scan the signals feed for high-conviction intent, cross-check the chain context against /v1/flow/gex, and drop into raw trades for any signal that needs deeper inspection.
Frequently Asked Questions
The methodology is fully documented and the per-component breakdown is in every response. A typical UOA feed publishes an opaque score; FlashAlpha publishes the six normalised components (premium, size-vs-OI, aggressor, sweep, opening bias, tenor), their weights, and their individual contributions to the composite. Any score in the feed can be reconstructed from the trade and chain context using the formulas in the methodology paper.
Same-contract same-side prints whose timestamps fall within 500 milliseconds of the previous print are coalesced into one execution intent. A coalesced group of two or more prints is a sweep; a singleton block-sized print is a block. The 500ms window catches the cross-venue routing latency of genuine multi-exchange sweeps without false-coalescing temporally adjacent but unrelated prints.
From the OI simulator's signed intraday delta for the contract. A positive accumulated delta means the simulator is reading the day's flow on this contract as net-opening, and the trade is tagged OpeningBias. A negative delta tags it ClosingBias. Zero delta tags it Unknown. The simulator's confidence weight (0.43) is included as a multiplier on the score component, so a "perfect" opening signal contributes at most 0.43 of the bucket's max, intentionally reflecting the underlying inference confidence.
A closing trade unwinds an existing position. The tape can tell you what is being unwound but generally cannot tell you which direction the unwinder originally bet. Calling a closing flow "Bullish" or "Bearish" would over-claim. The honest answer is Neutral. The closing classification is still surfaced through open_close_bias = ClosingBias and the dedicated closing_premium aggregate on the summary endpoint, so consumers can analyse closing flow independently.
Because the OI simulator's per-trade confidence is 0.43, calibrated against next-morning settled OI residuals. The scorer multiplies the opening-bias raw score by this confidence so a "perfect" opening signal contributes at most 0.43 of the bucket's normalised credit. Giving full credit would overstate what the underlying inference can prove. With the default weights, opening bias can contribute about 9 of the 100 total score points - meaningful but not dominant. This is an intentional honesty constraint, not a bug.
Yes. Every component formula is published in the methodology paper, the per-component contributions are returned in score_breakdown, and the default weights are documented (1.0 premium, 1.0 size-vs-OI, 0.8 aggressor, 1.0 sweep, 1.2 opening_bias, 0.6 tenor, sum 5.6). Sum the breakdown buckets and you get the composite score to within rounding. Reconstruct each normalised component from the trade and OI context and the formulas should match the published numbers exactly.
A relative + absolute conviction badge. A signal is tagged golden if its score is in the top decile of the current result set and meets the absolute floor of 70. The dual gate prevents a quiet session from promoting a mediocre signal just because everything else was even quieter, while still surfacing the standout signals on a busy session.
Because the live chain on the flow surface already folds intraday OI deltas in. Adding the per-signal hypothetical impact on top would double-count. The field is named hypothetical_gex_impact_if_opening to make the conditional explicit: it tells you the standalone gamma-dollar impact of this print if it were fully opening and fully dealer-absorbed, not what to add to the chain. For chain-level live GEX, use the /v1/flow/gex endpoint - it is already simulation-aware.
It depends on what you mean by "predictive". Aggregate net directional premium across a watchlist has weak but non-zero correlation with same-day direction; individual signals are noisy. The Flow Signals score is a quality filter, not a directional oracle. Treat the feed as a screening layer that surfaces trades worth looking at, then add chart context, IV regime, and position relative to walls. The score_breakdown object lets you audit each signal so you can backtest which sub-patterns actually work on your time horizon and execution model.
Most are. FlashAlpha is the explicit exception: every Flow Signals score is the deterministic output of six published formulas, each component's contribution is returned in the score_breakdown object, and the default weights are documented in this article. You can reconstruct any score from the trade + chain context using the formulas above. If a published score does not match the formula, that is a bug we want to know about. Black-box scores cannot be audited, cannot be calibrated, and cannot be trusted at scale - that is the whole reason the methodology paper exists.
Not reliably. A call buyer could be opening a directional bet (bullish), closing a short call they previously wrote (also reduces upside risk but is not fresh conviction), hedging a short stock position (the original bet was bearish), or running one leg of a multi-leg structure (depends on the other legs). The Flow Signals intent classifier handles the open-vs-close ambiguity using the OI simulator and refuses to attribute direction on closing trades. Even with that filter, "smart money buying calls" is a screening signal, not a forecast. Cross-reference with dealer positioning before treating it as a directional read.
Treat any "follow the smart money" rule as a hypothesis to be backtested on your own time horizon, not a strategy. The Flow Signals feed gives you the raw, scored, audit-able data; whether a specific filter (e.g., top-10 bullish opening sweeps with score ≥ 80, held for N days) generates alpha is an empirical question depending on the underlying, the time window, execution cost, and your benchmark. Persist signals to a database, label outcomes after N days, and measure. The Python scanner article shows the persistence pattern. The audit-friendly score_breakdown lets you iterate on which sub-components carry the signal.