Skip to content

Commit e1bc976

Browse files
tdobrowolski1claude
andcommitted
1.0.1: add flow signals endpoints
- flow_signals(symbol, ...) — scored, classified unusual-flow feed (block/sweep, NBBO aggressor, opening/closing bias, intent) with a 0-100 composite score, transparent component breakdown, and chain-context enrichment (greeks, IV-vs-ATM, moneyness, est. delta-notional). - flow_signals_summary(symbol, ...) — net bullish/bearish + opening/closing premium roll-up plus the top 10 signals. - New TypedDicts: FlowSignal, FlowSignalsResponse, FlowSignalsSummaryResponse, FlowSignalsChain, FlowSignalScoreBreakdown, FlowSignalEnrichment. - llms.txt + CHANGELOG.md updated for the 24-endpoint flow surface. - Live integration tests for both endpoints. All endpoints require Alpha+. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 12cf1ef commit e1bc976

7 files changed

Lines changed: 317 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## 1.0.1 - 2026-05-21
4+
5+
### Added
6+
- `flow_signals(symbol, ...)` and `flow_signals_summary(symbol, ...)`
7+
scored, classified unusual-flow feed for one underlying. Each notable
8+
print is coalesced into a signal (block/sweep, NBBO aggressor,
9+
opening/closing bias, intent), scored 0-100 with a transparent
10+
component breakdown, and enriched with chain context (greeks,
11+
IV-vs-ATM, moneyness, estimated delta-notional). Summary endpoint
12+
rolls up net bullish/bearish and opening/closing premium plus the top
13+
10 signals. New `TypedDict` types: `FlowSignal`, `FlowSignalsResponse`,
14+
`FlowSignalsSummaryResponse`, `FlowSignalsChain`,
15+
`FlowSignalScoreBreakdown`, `FlowSignalEnrichment`. Requires Alpha.
16+
- Live integration tests for both signals endpoints.
17+
318
## 1.0.0 - 2026-05-15
419

520
### Added

llms.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,19 @@ narrative = client.exposure_narrative("SPY")
8181
## Flow — live, simulation-aware (Alpha+)
8282

8383
Intraday trade-tape-adjusted dealer exposure plus the raw options/stock
84-
flow feed — 22 endpoints under `/v1/flow/*`:
84+
flow feed — 24 endpoints under `/v1/flow/*`:
8585

8686
- **Analytics** (snake_case): `flow_levels`, `flow_pin_risk`,
8787
`flow_summary`, `flow_oi`, `flow_gex`, `flow_dex`, `flow_dealer_risk`,
8888
`flow_live` — live gamma flip / call & put walls / max pain, 0DTE
8989
pin score, flow direction + headline GEX shift, OI simulator state,
9090
flow-adjusted GEX/DEX per strike, settled-vs-live dealer risk, and an
9191
everything-at-once bundle. Optional `expiry="YYYY-MM-DD"` slice.
92+
- **Unusual-flow signals** (snake_case): `flow_signals` /
93+
`flow_signals_summary` — scored, classified per-print signals
94+
(block/sweep, NBBO aggressor, opening/closing bias, intent) with a
95+
0-100 composite score, chain-context enrichment, and a net
96+
bullish/bearish + opening/closing premium roll-up.
9297
- **Raw flow** (camelCase wire keys): `flow_option_recent` /
9398
`flow_option_summary` / `flow_option_blocks` / `flow_option_history` /
9499
`flow_option_cumulative`, the `flow_stock_*` equivalents, and

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "flashalpha"
7-
version = "1.0.0"
7+
version = "1.0.1"
88
description = "Python SDK for the FlashAlpha options analytics API — live options screener, gamma exposure (GEX), VRP, delta, vanna, charm, greeks, 0DTE analytics, volatility surfaces, and more."
99
readme = "README.md"
1010
license = "MIT"

src/flashalpha/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
FlowOutlierRow,
3939
FlowPinRiskBreakdown,
4040
FlowPinRiskResponse,
41+
FlowSignal,
42+
FlowSignalEnrichment,
43+
FlowSignalScoreBreakdown,
44+
FlowSignalsChain,
45+
FlowSignalsResponse,
46+
FlowSignalsSummaryResponse,
4147
FlowStockBlock,
4248
FlowStockBlocksResponse,
4349
FlowStockCumulativeResponse,
@@ -347,4 +353,10 @@
347353
"FlowStockLeaderRow",
348354
"FlowStockLeaderboardResponse",
349355
"FlowStockOutliersResponse",
356+
"FlowSignalsChain",
357+
"FlowSignalScoreBreakdown",
358+
"FlowSignalEnrichment",
359+
"FlowSignal",
360+
"FlowSignalsResponse",
361+
"FlowSignalsSummaryResponse",
350362
]

src/flashalpha/client.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
FlowOptionRecentResponse,
3939
FlowOptionSummaryResponse,
4040
FlowPinRiskResponse,
41+
FlowSignalsResponse,
42+
FlowSignalsSummaryResponse,
4143
FlowStockBlocksResponse,
4244
FlowStockCumulativeResponse,
4345
FlowStockHistoryResponse,
@@ -479,6 +481,65 @@ def flow_stocks_outliers(
479481
params["windowMinutes"] = window_minutes
480482
return self._get("/v1/flow/stocks/outliers", params or None)
481483

484+
# Flow signals (unusual-flow feed, Alpha+).
485+
486+
def flow_signals(
487+
self,
488+
symbol: str,
489+
*,
490+
min_score: int | None = None,
491+
intent: str | None = None,
492+
structure: str | None = None,
493+
window_minutes: int | None = None,
494+
limit: int | None = None,
495+
expiry: str | None = None,
496+
) -> FlowSignalsResponse:
497+
"""Scored unusual-flow feed for one underlying. Requires Alpha.
498+
499+
Each notable print is coalesced into a signal, classified
500+
(block/sweep, NBBO aggressor, opening/closing bias, intent), and
501+
scored 0–100 with a transparent component breakdown. Ranked by
502+
score, highest first.
503+
504+
``min_score`` drops signals below the threshold; ``intent``
505+
filters to ``"bullish"``/``"bearish"``/``"neutral"``;
506+
``structure`` filters to ``"block"``/``"sweep"``;
507+
``window_minutes`` sets the look-back (1–10080, default 240);
508+
``limit`` caps the response (1–500, default 50); ``expiry``
509+
filters to a single ``YYYY-MM-DD`` cycle.
510+
"""
511+
params: dict[str, Any] = {}
512+
if min_score is not None:
513+
params["minScore"] = min_score
514+
if intent:
515+
params["intent"] = intent
516+
if structure:
517+
params["structure"] = structure
518+
if window_minutes is not None:
519+
params["windowMinutes"] = window_minutes
520+
if limit is not None:
521+
params["limit"] = limit
522+
if expiry:
523+
params["expiry"] = expiry
524+
return self._get(f"/v1/flow/signals/{_seg(symbol)}", params or None)
525+
526+
def flow_signals_summary(
527+
self,
528+
symbol: str,
529+
*,
530+
window_minutes: int | None = None,
531+
expiry: str | None = None,
532+
) -> FlowSignalsSummaryResponse:
533+
"""Net bullish/bearish + opening/closing premium roll-up plus
534+
the top 10 signals. Cheap "smart-money tilt" read. Requires Alpha.
535+
"""
536+
params: dict[str, Any] = {}
537+
if window_minutes is not None:
538+
params["windowMinutes"] = window_minutes
539+
if expiry:
540+
params["expiry"] = expiry
541+
return self._get(f"/v1/flow/signals/{_seg(symbol)}/summary", params or None)
542+
482543
# ── Pricing & Sizing ────────────────────────────────────────────
483544

484545
def greeks(

src/flashalpha/types.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3457,3 +3457,181 @@ class FlowStockOutliersResponse(TypedDict, total=False):
34573457
limit: int
34583458
# Imbalance-ranked flagged symbols.
34593459
outliers: List[FlowOutlierRow]
3460+
3461+
3462+
# ── Flow signals (unusual-flow feed, Alpha+) ─────────────────────────
3463+
#
3464+
# Per-underlying scored/classified unusual-flow signals. Snake_case wire
3465+
# shape (analytics family). Both endpoints reuse ``FlowSignal``.
3466+
3467+
3468+
class FlowSignalsChain(TypedDict, total=False):
3469+
"""Settled-chain reference levels echoed alongside the signals.
3470+
3471+
Computed once per request from the settled snapshot — independent of
3472+
the live flow surface. All fields are ``None`` when the chain
3473+
snapshot is unavailable.
3474+
"""
3475+
3476+
call_wall: Optional[float]
3477+
put_wall: Optional[float]
3478+
max_pain: Optional[float]
3479+
gamma_flip: Optional[float]
3480+
3481+
3482+
class FlowSignalScoreBreakdown(TypedDict, total=False):
3483+
"""Component contributions that sum to the headline ``score``.
3484+
3485+
Weights are server-tunable so absolute values may shift, but the
3486+
ordering of components is stable.
3487+
"""
3488+
3489+
premium: int
3490+
size_vs_oi: int
3491+
aggressor: int
3492+
sweep: int
3493+
opening_bias: int
3494+
tenor: int
3495+
3496+
3497+
class FlowSignalEnrichment(TypedDict, total=False):
3498+
"""Chain-derived context attached to a signal.
3499+
3500+
All numeric fields are ``None`` and ``moneyness`` is ``"unknown"``
3501+
when the contract isn't in the settled chain snapshot.
3502+
"""
3503+
3504+
iv: Optional[float]
3505+
delta: Optional[float]
3506+
gamma: Optional[float]
3507+
# IV minus the nearest ATM IV (signed).
3508+
iv_vs_atm: Optional[float]
3509+
# ``"OTM"`` / ``"ATM"`` / ``"ITM"`` / ``"unknown"``.
3510+
moneyness: str
3511+
# Estimated dollar delta-notional of this print.
3512+
estimated_delta_notional: Optional[float]
3513+
# Standalone gamma-$ this print would add if it were opening and
3514+
# fully dealer-absorbed. **Not** applied to the live chain — don't
3515+
# sum it against ``/v1/flow/gex``.
3516+
hypothetical_gex_impact_if_opening: Optional[float]
3517+
3518+
3519+
class FlowSignal(TypedDict, total=False):
3520+
"""One scored unusual-flow signal.
3521+
3522+
A signal is a coalesced view of one notable (block-sized) print on a
3523+
single contract: classification, scoring, and chain-context
3524+
enrichment. Same shape across ``/v1/flow/signals/{symbol}`` and
3525+
``/v1/flow/signals/{symbol}/summary``'s ``top_signals``.
3526+
"""
3527+
3528+
# Trade timestamp (ISO-8601 UTC).
3529+
ts: str
3530+
# Contract expiry (``YYYY-MM-DD``).
3531+
expiry: str
3532+
# Contract strike price.
3533+
strike: float
3534+
# ``"C"`` (call) or ``"P"`` (put).
3535+
right: str
3536+
# Upstream buy/sell/mid aggressor classification (distinct from the
3537+
# NBBO ``aggressor`` label).
3538+
side: str
3539+
# Trade price.
3540+
price: float
3541+
# Trade size in contracts.
3542+
size: int
3543+
# Dollar premium of this print: ``price * size * 100``.
3544+
premium: float
3545+
# Days to expiry at trade time.
3546+
dte: int
3547+
# ``"block"`` (lone block-sized print) or ``"sweep"`` (≥2 same-side
3548+
# prints on one contract within ~500ms).
3549+
structure: str
3550+
# NBBO position at trade: ``"above_ask"`` / ``"at_ask"`` / ``"mid"`` /
3551+
# ``"at_bid"`` / ``"below_bid"``.
3552+
aggressor: str
3553+
# Contract-level OI-simulator inference: ``"opening_bias"`` /
3554+
# ``"closing_bias"`` / ``"unknown"``. Not a per-print label.
3555+
open_close_bias: str
3556+
# Simulator confidence weight for the bias above.
3557+
open_close_confidence: float
3558+
# Signed simulator estimate of contracts opened (+) or closed (−)
3559+
# today on this contract.
3560+
contract_net_oi_delta: int
3561+
# ``"bullish"`` / ``"bearish"`` / ``"neutral"``. Neutral whenever
3562+
# ``open_close_bias == "closing_bias"`` (can't attribute on unwinds)
3563+
# or ``side == "mid"``.
3564+
intent: str
3565+
# 0–100 composite (components sum to this).
3566+
score: int
3567+
# ``"low"`` / ``"medium"`` / ``"high"``.
3568+
conviction: str
3569+
# Subset of ``"sweep"``, ``"block"``, ``"opening"``, ``"closing"``,
3570+
# ``"0dte"``, ``"whale"`` (premium ≥ $1M), ``"golden"`` (top decile
3571+
# in this response set *and* score ≥ 70 absolute).
3572+
tags: List[str]
3573+
score_breakdown: FlowSignalScoreBreakdown
3574+
enrichment: FlowSignalEnrichment
3575+
3576+
3577+
class FlowSignalsResponse(TypedDict, total=False):
3578+
"""Scored, classified unusual-flow feed from
3579+
``GET /v1/flow/signals/{symbol}``.
3580+
3581+
Each notable print in the look-back window is coalesced into a
3582+
signal, scored 0–100, and ranked highest score first. Requires the
3583+
Alpha plan.
3584+
"""
3585+
3586+
# Underlying ticker echoed from the request path.
3587+
symbol: str
3588+
# Timestamp this snapshot was computed for (ISO-8601 UTC).
3589+
as_of: str
3590+
# Look-back window applied (minutes).
3591+
window_minutes: int
3592+
# Expiration filter echoed back, or ``None``.
3593+
expiry: Optional[str]
3594+
# Spot mid at the snapshot time.
3595+
underlying_price: Optional[float]
3596+
# Settled-chain reference levels (computed once per request).
3597+
chain: FlowSignalsChain
3598+
# Number of signals returned (after server-side filtering).
3599+
count: int
3600+
# Signals, highest score first.
3601+
signals: List[FlowSignal]
3602+
3603+
3604+
class FlowSignalsSummaryResponse(TypedDict, total=False):
3605+
"""Net-directional roll-up from
3606+
``GET /v1/flow/signals/{symbol}/summary``.
3607+
3608+
Sums classified premium across the window into bullish/bearish and
3609+
opening/closing buckets — a cheap "smart-money tilt" read for one
3610+
underlying. Requires the Alpha plan.
3611+
"""
3612+
3613+
# Underlying ticker echoed from the request path.
3614+
symbol: str
3615+
# Timestamp this snapshot was computed for (ISO-8601 UTC).
3616+
as_of: str
3617+
# Look-back window applied (minutes).
3618+
window_minutes: int
3619+
# Expiration filter echoed back, or ``None``.
3620+
expiry: Optional[str]
3621+
# Spot mid at the snapshot time.
3622+
underlying_price: Optional[float]
3623+
# Total signal count in the window (full count, not the
3624+
# ``top_signals`` length).
3625+
signal_count: int
3626+
# Sum of signal premium with ``intent == "bullish"``.
3627+
bullish_premium: float
3628+
# Sum of signal premium with ``intent == "bearish"``.
3629+
bearish_premium: float
3630+
# ``bullish_premium - bearish_premium``.
3631+
net_directional_premium: float
3632+
# Sum of signal premium with ``open_close_bias == "opening_bias"``.
3633+
opening_premium: float
3634+
# Sum of signal premium with ``open_close_bias == "closing_bias"``.
3635+
closing_premium: float
3636+
# Highest-scoring signals (≤ 10). Same shape as ``FlowSignal``.
3637+
top_signals: List[FlowSignal]

tests/test_integration.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,3 +1804,47 @@ def test_flow_stocks_outliers(fa):
18041804
"lastVwap", "lastTradeUtc",
18051805
"lastTradeAgeSec"],
18061806
"flow/stocks/outliers.outliers[0]")
1807+
1808+
1809+
_SIGNAL_FIELDS = ["ts", "expiry", "strike", "right", "side", "price", "size",
1810+
"premium", "dte", "structure", "aggressor",
1811+
"open_close_bias", "open_close_confidence",
1812+
"contract_net_oi_delta", "intent", "score", "conviction",
1813+
"tags", "score_breakdown", "enrichment"]
1814+
1815+
1816+
def test_flow_signals(fa):
1817+
r = _flow(lambda: fa.flow_signals(FLOW_SYMBOL, window_minutes=240,
1818+
limit=10))
1819+
_require(r, ["symbol", "as_of", "window_minutes", "expiry",
1820+
"underlying_price", "chain", "count", "signals"],
1821+
"flow/signals")
1822+
assert r["symbol"] == FLOW_SYMBOL
1823+
_require(r["chain"], ["call_wall", "put_wall", "max_pain", "gamma_flip"],
1824+
"flow/signals.chain")
1825+
assert isinstance(r["signals"], list)
1826+
if r["signals"]:
1827+
_require(r["signals"][0], _SIGNAL_FIELDS, "flow/signals.signals[0]")
1828+
_require(r["signals"][0]["score_breakdown"],
1829+
["premium", "size_vs_oi", "aggressor", "sweep",
1830+
"opening_bias", "tenor"],
1831+
"flow/signals.signals[0].score_breakdown")
1832+
_require(r["signals"][0]["enrichment"],
1833+
["iv", "delta", "gamma", "iv_vs_atm", "moneyness",
1834+
"estimated_delta_notional",
1835+
"hypothetical_gex_impact_if_opening"],
1836+
"flow/signals.signals[0].enrichment")
1837+
1838+
1839+
def test_flow_signals_summary(fa):
1840+
r = _flow(lambda: fa.flow_signals_summary(FLOW_SYMBOL, window_minutes=240))
1841+
_require(r, ["symbol", "as_of", "window_minutes", "expiry",
1842+
"underlying_price", "signal_count", "bullish_premium",
1843+
"bearish_premium", "net_directional_premium",
1844+
"opening_premium", "closing_premium", "top_signals"],
1845+
"flow/signals/summary")
1846+
assert r["symbol"] == FLOW_SYMBOL
1847+
assert isinstance(r["top_signals"], list)
1848+
if r["top_signals"]:
1849+
_require(r["top_signals"][0], _SIGNAL_FIELDS,
1850+
"flow/signals/summary.top_signals[0]")

0 commit comments

Comments
 (0)