Skip to content

Commit 030eb79

Browse files
tdobrowolski1claude
andcommitted
v0.3.0: add live options screener endpoint
Add fa.screener() method wrapping POST /v1/screener/live. Supports filters (recursive AND/OR groups, cascading on expiries/strikes/contracts), sort, select, formulas, limit, offset. Growth tier gets the 10-symbol universe; Alpha tier adds ~250 symbols, formulas, and harvest/dealer-flow scores. Bump version to 0.3.0. Add SEO keywords (options-screener, VRP, harvest-score, vol-selling, etc.). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5443938 commit 030eb79

5 files changed

Lines changed: 201 additions & 5 deletions

File tree

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
66
[![CI](https://github.com/FlashAlpha-lab/flashalpha-python/actions/workflows/ci.yml/badge.svg)](https://github.com/FlashAlpha-lab/flashalpha-python/actions/workflows/ci.yml)
77

8-
**Python client for the FlashAlpha options analytics API.** Access real-time gamma exposure (GEX), delta exposure (DEX), vanna exposure (VEX), charm exposure (CHEX), 0DTE analytics, Black-Scholes greeks, implied volatility, volatility surfaces, dealer positioning, Kelly criterion sizing, and more — all from Python.
8+
**Python client for the FlashAlpha options analytics API.** Access a **live options screener** (filter/rank symbols by gamma exposure, VRP, IV, greeks, harvest scores, and custom formulas), real-time gamma exposure (GEX), delta exposure (DEX), vanna exposure (VEX), charm exposure (CHEX), 0DTE analytics, Black-Scholes greeks, implied volatility, volatility surfaces, dealer positioning, Kelly criterion sizing, and more — all from Python.
99

1010
```bash
1111
pip install flashalpha
@@ -31,6 +31,45 @@ Get your free API key at [flashalpha.com](https://flashalpha.com) — no credit
3131

3232
## Features
3333

34+
### Live Options Screener
35+
36+
Filter and rank symbols in real time across your universe by gamma exposure,
37+
VRP, implied volatility, greeks, harvest scores, dealer flow risk, and custom
38+
formulas. Data is live from an in-memory store refreshed every 5-10 seconds.
39+
40+
```python
41+
# Harvestable VRP setups with low dealer flow risk
42+
result = fa.screener(
43+
filters={
44+
"op": "and",
45+
"conditions": [
46+
{"field": "regime", "operator": "eq", "value": "positive_gamma"},
47+
{"field": "vrp_regime", "operator": "eq", "value": "harvestable"},
48+
{"field": "dealer_flow_risk", "operator": "lte", "value": 40},
49+
{"field": "harvest_score", "operator": "gte", "value": 65},
50+
],
51+
},
52+
sort=[{"field": "harvest_score", "direction": "desc"}],
53+
select=["symbol", "price", "harvest_score", "dealer_flow_risk"],
54+
)
55+
for row in result["data"]:
56+
print(f"{row['symbol']}: score={row['harvest_score']} risk={row['dealer_flow_risk']}")
57+
58+
# Custom formula — rank by IV premium over realized vol
59+
result = fa.screener(
60+
formulas=[{"alias": "iv_premium", "expression": "atm_iv - rv_20d"}],
61+
sort=[{"formula": "iv_premium", "direction": "desc"}],
62+
select=["symbol", "atm_iv", "rv_20d", "iv_premium"],
63+
limit=20,
64+
)
65+
```
66+
67+
Cascading filters on expiries, strikes, and contracts (e.g. `expiries.days_to_expiry`,
68+
`strikes.call_oi`, `contracts.delta`) trim the tree at each level and return only the
69+
matching subtree. See the [Screener spec](https://flashalpha.com/docs/lab-api-screener)
70+
and [cookbook](https://flashalpha.com/docs/lab-api-screener-cookbook) for all fields,
71+
operators, and recipes.
72+
3473
### Options Exposure Analytics
3574

3675
Gamma exposure, delta exposure, vanna exposure, and charm exposure by strike. See where dealers are positioned and how they need to hedge.
@@ -202,6 +241,7 @@ Get your API key at **[flashalpha.com](https://flashalpha.com)**
202241
| `fa.greeks(...)` | BSM greeks (1st, 2nd, 3rd order) | Free+ |
203242
| `fa.iv(...)` | Implied volatility solver | Free+ |
204243
| `fa.kelly(...)` | Kelly criterion sizing | Growth+ |
244+
| `fa.screener(...)` | **Live options screener** — filter/rank by GEX, VRP, IV, greeks, formulas | Growth+ |
205245
| `fa.volatility(symbol)` | Comprehensive volatility analytics | Growth+ |
206246
| `fa.adv_volatility(symbol)` | SVI, variance surface, arb detection | Alpha+ |
207247
| `fa.tickers()` | All available stock tickers | Free+ |
@@ -210,14 +250,24 @@ Get your API key at **[flashalpha.com](https://flashalpha.com)**
210250
| `fa.account()` | Account info and quota | Free+ |
211251
| `fa.health()` | Health check | Public |
212252

253+
## Other SDKs
254+
255+
| Language | Package | Repository |
256+
|----------|---------|------------|
257+
| JavaScript | `npm i flashalpha` | [flashalpha-js](https://github.com/FlashAlpha-lab/flashalpha-js) |
258+
| .NET | `dotnet add package FlashAlpha` | [flashalpha-dotnet](https://github.com/FlashAlpha-lab/flashalpha-dotnet) |
259+
| Java | Maven Central | [flashalpha-java](https://github.com/FlashAlpha-lab/flashalpha-java) |
260+
| Go | `go get github.com/FlashAlpha-lab/flashalpha-go` | [flashalpha-go](https://github.com/FlashAlpha-lab/flashalpha-go) |
261+
| MCP | Claude / LLM tool server | [flashalpha-mcp](https://github.com/FlashAlpha-lab/flashalpha-mcp) |
262+
213263
## Links
214264

215265
- [FlashAlpha](https://flashalpha.com) — API keys, docs, pricing
216266
- [API Documentation](https://flashalpha.com/docs)
267+
- [Examples](https://github.com/FlashAlpha-lab/flashalpha-examples) — runnable tutorials
217268
- [GEX Explained](https://github.com/FlashAlpha-lab/gex-explained) — gamma exposure theory and code
218269
- [0DTE Options Analytics](https://github.com/FlashAlpha-lab/0dte-options-analytics) — 0DTE pin risk, expected move, dealer hedging
219270
- [Volatility Surface Python](https://github.com/FlashAlpha-lab/volatility-surface-python) — SVI calibration, variance swap, skew analysis
220-
- [Examples](https://github.com/FlashAlpha-lab/flashalpha-examples) — runnable tutorials
221271
- [Awesome Options Analytics](https://github.com/FlashAlpha-lab/awesome-options-analytics) — curated resource list
222272

223273
## License

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "flashalpha"
7-
version = "0.2.1"
8-
description = "Python SDK for the FlashAlpha options analytics API — gamma exposure (GEX), delta, vanna, charm, greeks, 0DTE analytics, volatility surfaces, and more."
7+
version = "0.3.0"
8+
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"
1111
requires-python = ">=3.10"
1212
authors = [{ name = "FlashAlpha", email = "tom@flashalpha.com" }]
1313
keywords = [
1414
"options",
15+
"options screener",
16+
"live options screener",
1517
"gamma exposure",
1618
"gex",
1719
"greeks",
@@ -34,6 +36,12 @@ keywords = [
3436
"vol surface",
3537
"SVI",
3638
"variance swap",
39+
"variance risk premium",
40+
"VRP",
41+
"harvest score",
42+
"short vol",
43+
"vol selling",
44+
"dealer flow",
3745
]
3846
classifiers = [
3947
"Development Status :: 3 - Alpha",

src/flashalpha/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
TierRestrictedError,
1111
)
1212

13-
__version__ = "0.2.1"
13+
__version__ = "0.3.0"
1414
__all__ = [
1515
"FlashAlpha",
1616
"FlashAlphaError",

src/flashalpha/client.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def _get(self, path: str, params: dict[str, Any] | None = None) -> dict:
4545
resp = self._session.get(url, params=params, timeout=self.timeout)
4646
return self._handle(resp)
4747

48+
def _post(self, path: str, json_body: dict[str, Any] | None = None) -> dict:
49+
url = f"{self.base_url}{path}"
50+
resp = self._session.post(url, json=json_body, timeout=self.timeout)
51+
return self._handle(resp)
52+
4853
def _handle(self, resp: requests.Response) -> dict:
4954
if resp.status_code == 200:
5055
return resp.json()
@@ -294,6 +299,84 @@ def symbols(self) -> dict:
294299
"""Currently queried symbols with live data."""
295300
return self._get("/v1/symbols")
296301

302+
# ── Screener ────────────────────────────────────────────────────
303+
304+
def screener(
305+
self,
306+
*,
307+
filters: dict[str, Any] | None = None,
308+
sort: list[dict[str, Any]] | None = None,
309+
select: list[str] | None = None,
310+
formulas: list[dict[str, str]] | None = None,
311+
limit: int | None = None,
312+
offset: int | None = None,
313+
) -> dict:
314+
"""Live options screener — filter/rank symbols by gamma exposure, VRP,
315+
volatility, greeks, and more.
316+
317+
Powered by an in-memory store updated every 5-10s from live market data.
318+
Growth: 10-symbol universe, up to 10 rows. Alpha: ~250 symbols, up to 50
319+
rows, formulas, and harvest/dealer-flow-risk scores.
320+
321+
Parameters
322+
----------
323+
filters : dict, optional
324+
Recursive filter tree. Leaf: {"field": "atm_iv", "operator": "gte",
325+
"value": 20}. Group: {"op": "and", "conditions": [...]}. Supports
326+
dotted prefixes `expiries.X`, `strikes.X`, `contracts.X` for
327+
cascading filters.
328+
sort : list of dict, optional
329+
Sort specs (primary first), e.g.
330+
[{"field": "harvest_score", "direction": "desc"}].
331+
Also accepts {"formula": "alias_name", ...}.
332+
select : list of str, optional
333+
Field names to return, or ["*"] for the full flat object.
334+
formulas : list of dict, optional
335+
Computed fields (Alpha only), e.g.
336+
[{"alias": "vrp_ratio", "expression": "atm_iv / rv_20d"}].
337+
limit : int, optional
338+
Row cap. 1-10 on Growth, 1-50 on Alpha. Default 50.
339+
offset : int, optional
340+
Pagination offset (Alpha only).
341+
342+
Returns
343+
-------
344+
dict
345+
{"meta": {"total_count": ..., "tier": ..., ...},
346+
"data": [{"symbol": ..., ...}, ...]}
347+
348+
Examples
349+
--------
350+
Harvestable VRP screen:
351+
352+
>>> fa.screener(
353+
... filters={
354+
... "op": "and",
355+
... "conditions": [
356+
... {"field": "regime", "operator": "eq", "value": "positive_gamma"},
357+
... {"field": "vrp_regime", "operator": "eq", "value": "harvestable"},
358+
... {"field": "harvest_score", "operator": "gte", "value": 65},
359+
... ],
360+
... },
361+
... sort=[{"field": "harvest_score", "direction": "desc"}],
362+
... select=["symbol", "price", "harvest_score", "dealer_flow_risk"],
363+
... )
364+
"""
365+
body: dict[str, Any] = {}
366+
if filters is not None:
367+
body["filters"] = filters
368+
if sort is not None:
369+
body["sort"] = sort
370+
if select is not None:
371+
body["select"] = select
372+
if formulas is not None:
373+
body["formulas"] = formulas
374+
if limit is not None:
375+
body["limit"] = limit
376+
if offset is not None:
377+
body["offset"] = offset
378+
return self._post("/v1/screener/live", body)
379+
297380
# ── Account & System ────────────────────────────────────────────
298381

299382
def account(self) -> dict:

tests/test_client.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,61 @@ def test_health(fa):
321321
assert result["status"] == "Healthy"
322322

323323

324+
# ── Screener ───────────────────────────────────────────────────────
325+
326+
327+
@responses.activate
328+
def test_screener_empty(fa):
329+
payload = {
330+
"meta": {"total_count": 10, "returned_count": 10, "tier": "growth"},
331+
"data": [{"symbol": "SPY", "price": 656.01}],
332+
}
333+
responses.post(f"{BASE}/v1/screener/live", json=payload)
334+
result = fa.screener()
335+
assert result["meta"]["tier"] == "growth"
336+
assert result["data"][0]["symbol"] == "SPY"
337+
338+
339+
@responses.activate
340+
def test_screener_with_filters(fa):
341+
payload = {"meta": {"total_count": 2, "tier": "alpha"}, "data": []}
342+
responses.post(f"{BASE}/v1/screener/live", json=payload)
343+
fa.screener(
344+
filters={
345+
"op": "and",
346+
"conditions": [
347+
{"field": "regime", "operator": "eq", "value": "positive_gamma"},
348+
{"field": "harvest_score", "operator": "gte", "value": 65},
349+
],
350+
},
351+
sort=[{"field": "harvest_score", "direction": "desc"}],
352+
select=["symbol", "price", "harvest_score"],
353+
limit=20,
354+
)
355+
# Verify request body
356+
import json as _json
357+
body = _json.loads(responses.calls[0].request.body)
358+
assert body["filters"]["op"] == "and"
359+
assert len(body["filters"]["conditions"]) == 2
360+
assert body["limit"] == 20
361+
assert body["select"] == ["symbol", "price", "harvest_score"]
362+
363+
364+
@responses.activate
365+
def test_screener_with_formulas(fa):
366+
payload = {"meta": {"total_count": 1, "tier": "alpha"}, "data": []}
367+
responses.post(f"{BASE}/v1/screener/live", json=payload)
368+
fa.screener(
369+
formulas=[{"alias": "vrp_ratio", "expression": "atm_iv / rv_20d"}],
370+
filters={"formula": "vrp_ratio", "operator": "gte", "value": 1.2},
371+
sort=[{"formula": "vrp_ratio", "direction": "desc"}],
372+
)
373+
import json as _json
374+
body = _json.loads(responses.calls[0].request.body)
375+
assert body["formulas"][0]["alias"] == "vrp_ratio"
376+
assert body["filters"]["formula"] == "vrp_ratio"
377+
378+
324379
# ── Client config ──────────────────────────────────────────────────
325380

326381

0 commit comments

Comments
 (0)