Skip to content

Commit c7c5f7e

Browse files
tdobrowolski1claude
andcommitted
v0.3.4: typed ZeroDteResponse models + comprehensive typed integration test
Add a strongly-typed mirror of the GET /v1/exposure/zero-dte/{symbol} response. The new types live in flashalpha.types as a TypedDict tree (runtime-equal to plain dict — fully backward-compatible with existing dict-style access). - ZeroDteResponse covers all 145 documented fields including the new liquidity and metadata sections, hedging fine-grained buckets (10bp/25bp/half_pct), pin_risk component sub-scores, level cluster score, per-strike greeks/quotes/spreads, and the conditional warnings array. - zero_dte() return annotation tightened from `dict` to `ZeroDteResponse`. No runtime change — TypedDict is dict at runtime. - Re-export all type aliases from flashalpha.__init__ for convenient import. - Add test_zero_dte_typed_response — comprehensive end-to-end check that every documented field is populated when accessed via the typed paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d103b8b commit c7c5f7e

5 files changed

Lines changed: 385 additions & 5 deletions

File tree

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 = "0.3.3"
7+
version = "0.3.4"
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: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,25 @@
99
ServerError,
1010
TierRestrictedError,
1111
)
12+
from .types import (
13+
ZeroDteDecay,
14+
ZeroDteExpectedMove,
15+
ZeroDteExposures,
16+
ZeroDteFlow,
17+
ZeroDteHedging,
18+
ZeroDteHedgingBucket,
19+
ZeroDteLevels,
20+
ZeroDteLiquidity,
21+
ZeroDteMetadata,
22+
ZeroDtePinComponents,
23+
ZeroDtePinRisk,
24+
ZeroDteRegime,
25+
ZeroDteResponse,
26+
ZeroDteStrike,
27+
ZeroDteVolContext,
28+
)
1229

13-
__version__ = "0.3.2"
30+
__version__ = "0.3.4"
1431
__all__ = [
1532
"FlashAlpha",
1633
"FlashAlphaError",
@@ -19,4 +36,19 @@
1936
"NotFoundError",
2037
"RateLimitError",
2138
"ServerError",
39+
"ZeroDteResponse",
40+
"ZeroDteRegime",
41+
"ZeroDteExposures",
42+
"ZeroDteExpectedMove",
43+
"ZeroDtePinRisk",
44+
"ZeroDtePinComponents",
45+
"ZeroDteHedging",
46+
"ZeroDteHedgingBucket",
47+
"ZeroDteDecay",
48+
"ZeroDteVolContext",
49+
"ZeroDteFlow",
50+
"ZeroDteLevels",
51+
"ZeroDteLiquidity",
52+
"ZeroDteMetadata",
53+
"ZeroDteStrike",
2254
]

src/flashalpha/client.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
5+
from typing import TYPE_CHECKING, Any
66

77
import requests
88

@@ -15,6 +15,9 @@
1515
TierRestrictedError,
1616
)
1717

18+
if TYPE_CHECKING:
19+
from .types import ZeroDteResponse
20+
1821
BASE_URL = "https://lab.flashalpha.com"
1922

2023

@@ -192,8 +195,13 @@ def narrative(self, symbol: str) -> dict:
192195
"""Verbal narrative analysis of exposure. Requires Growth+."""
193196
return self._get(f"/v1/exposure/narrative/{symbol}")
194197

195-
def zero_dte(self, symbol: str, *, strike_range: float | None = None) -> dict:
196-
"""Real-time 0DTE analytics: regime, expected move, pin risk, hedging, decay. Requires Growth+."""
198+
def zero_dte(self, symbol: str, *, strike_range: float | None = None) -> "ZeroDteResponse":
199+
"""Real-time 0DTE analytics: regime, expected move, pin risk, hedging, decay. Requires Growth+.
200+
201+
Returns a ``ZeroDteResponse`` (a ``TypedDict`` — runtime-equivalent to
202+
``dict``). Existing ``result["field"]`` access continues to work; new
203+
callers get autocomplete and type-checking on the documented fields.
204+
"""
197205
params: dict[str, Any] = {}
198206
if strike_range is not None:
199207
params["strike_range"] = strike_range

src/flashalpha/types.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
"""Typed response models for FlashAlpha SDK.
2+
3+
These are ``TypedDict`` aliases — at runtime each is a plain ``dict``. Existing
4+
code that does ``result["field"]`` keeps working unchanged. Static type
5+
checkers (mypy/pyright) and IDEs see the field shape and provide autocomplete.
6+
7+
Currently typed:
8+
- ``ZeroDteResponse`` (full payload of GET /v1/exposure/zero-dte/{symbol})
9+
10+
All numeric fields are typed ``Optional[float]``/``Optional[int]`` because
11+
the API returns ``null`` for any value it can't compute (insufficient data,
12+
market closed, etc.). Treat the typed shape as a *hint*, not a guarantee —
13+
unknown fields added by the API in future revisions will still pass through.
14+
"""
15+
16+
from typing import List, Literal, Optional, TypedDict
17+
18+
19+
class ZeroDteRegime(TypedDict, total=False):
20+
label: str
21+
description: str
22+
gamma_flip: Optional[float]
23+
spot_vs_flip: Literal["above", "below"]
24+
spot_to_flip_pct: Optional[float]
25+
distance_to_flip_dollars: Optional[float]
26+
distance_to_flip_sigmas: Optional[float]
27+
28+
29+
class ZeroDteExposures(TypedDict, total=False):
30+
net_gex: float
31+
net_dex: float
32+
net_vex: float
33+
net_chex: float
34+
pct_of_total_gex: Optional[float]
35+
total_chain_net_gex: float
36+
37+
38+
class ZeroDteExpectedMove(TypedDict, total=False):
39+
implied_1sd_dollars: Optional[float]
40+
implied_1sd_pct: Optional[float]
41+
remaining_1sd_dollars: Optional[float]
42+
remaining_1sd_pct: Optional[float]
43+
upper_bound: Optional[float]
44+
lower_bound: Optional[float]
45+
straddle_price: Optional[float]
46+
atm_iv: Optional[float]
47+
48+
49+
class ZeroDtePinComponents(TypedDict, total=False):
50+
oi_score: int
51+
proximity_score: int
52+
time_score: int
53+
gamma_score: int
54+
55+
56+
class ZeroDtePinRisk(TypedDict, total=False):
57+
magnet_strike: Optional[float]
58+
magnet_gex: Optional[float]
59+
distance_to_magnet_pct: Optional[float]
60+
pin_score: int
61+
components: ZeroDtePinComponents
62+
max_pain: Optional[float]
63+
oi_concentration_top3_pct: Optional[float]
64+
description: str
65+
66+
67+
class ZeroDteHedgingBucket(TypedDict, total=False):
68+
dealer_shares_to_trade: float
69+
direction: Literal["buy", "sell"]
70+
notional_usd: float
71+
72+
73+
class ZeroDteHedging(TypedDict, total=False):
74+
spot_up_10bp: ZeroDteHedgingBucket
75+
spot_down_10bp: ZeroDteHedgingBucket
76+
spot_up_25bp: ZeroDteHedgingBucket
77+
spot_down_25bp: ZeroDteHedgingBucket
78+
spot_up_half_pct: ZeroDteHedgingBucket
79+
spot_down_half_pct: ZeroDteHedgingBucket
80+
spot_up_1pct: ZeroDteHedgingBucket
81+
spot_down_1pct: ZeroDteHedgingBucket
82+
convexity_at_spot: Optional[float]
83+
84+
85+
class ZeroDteDecay(TypedDict, total=False):
86+
net_theta_dollars: Optional[float]
87+
theta_per_hour_remaining: Optional[float]
88+
charm_regime: str
89+
charm_description: str
90+
gamma_acceleration: Optional[float]
91+
description: str
92+
93+
94+
class ZeroDteVolContext(TypedDict, total=False):
95+
zero_dte_atm_iv: Optional[float]
96+
seven_dte_atm_iv: Optional[float]
97+
iv_ratio_0dte_7dte: Optional[float]
98+
vix: Optional[float]
99+
vanna_exposure: Optional[float]
100+
vanna_interpretation: str
101+
description: str
102+
103+
104+
class ZeroDteFlow(TypedDict, total=False):
105+
total_volume: int
106+
call_volume: int
107+
put_volume: int
108+
net_call_minus_put_volume: int
109+
total_oi: int
110+
call_oi: int
111+
put_oi: int
112+
pc_ratio_volume: Optional[float]
113+
pc_ratio_oi: Optional[float]
114+
volume_to_oi_ratio: Optional[float]
115+
atm_volume_share_pct: Optional[float]
116+
top3_strike_volume_pct: Optional[float]
117+
118+
119+
class ZeroDteLevels(TypedDict, total=False):
120+
call_wall: Optional[float]
121+
call_wall_gex: Optional[float]
122+
call_wall_strength: Optional[float]
123+
distance_to_call_wall_pct: Optional[float]
124+
put_wall: Optional[float]
125+
put_wall_gex: Optional[float]
126+
put_wall_strength: Optional[float]
127+
distance_to_put_wall_pct: Optional[float]
128+
distance_to_magnet_dollars: Optional[float]
129+
highest_oi_strike: Optional[float]
130+
highest_oi_total: Optional[int]
131+
max_positive_gamma: Optional[float]
132+
max_negative_gamma: Optional[float]
133+
level_cluster_score: Optional[int]
134+
135+
136+
class ZeroDteLiquidity(TypedDict, total=False):
137+
atm_spread_pct: Optional[float]
138+
weighted_spread_pct: Optional[float]
139+
execution_score: Optional[int]
140+
141+
142+
class ZeroDteMetadata(TypedDict, total=False):
143+
snapshot_age_seconds: Optional[float]
144+
chain_contract_count: int
145+
data_quality_score: Optional[int]
146+
greek_smoothness_score: Optional[int]
147+
148+
149+
class ZeroDteStrike(TypedDict, total=False):
150+
strike: float
151+
distance_from_spot_pct: float
152+
call_symbol: str
153+
put_symbol: str
154+
call_gex: Optional[float]
155+
put_gex: Optional[float]
156+
net_gex: Optional[float]
157+
call_dex: Optional[float]
158+
put_dex: Optional[float]
159+
net_dex: Optional[float]
160+
net_vex: Optional[float]
161+
net_chex: Optional[float]
162+
call_oi: Optional[int]
163+
put_oi: Optional[int]
164+
call_volume: Optional[int]
165+
put_volume: Optional[int]
166+
gex_share_pct: Optional[float]
167+
oi_share_pct: Optional[float]
168+
volume_share_pct: Optional[float]
169+
call_iv: Optional[float]
170+
put_iv: Optional[float]
171+
call_delta: Optional[float]
172+
put_delta: Optional[float]
173+
call_gamma: Optional[float]
174+
put_gamma: Optional[float]
175+
call_theta: Optional[float]
176+
put_theta: Optional[float]
177+
call_mid: Optional[float]
178+
put_mid: Optional[float]
179+
call_spread_pct: Optional[float]
180+
put_spread_pct: Optional[float]
181+
182+
183+
class ZeroDteResponse(TypedDict, total=False):
184+
"""Full response for GET /v1/exposure/zero-dte/{symbol}.
185+
186+
On weekends/holidays or symbols without 0DTE today, ``no_zero_dte`` is
187+
``True`` and most fields are absent — only ``symbol``, ``as_of``,
188+
``message``, and ``next_zero_dte_expiry`` are populated.
189+
"""
190+
191+
symbol: str
192+
underlying_price: float
193+
expiration: Optional[str]
194+
as_of: str
195+
market_open: bool
196+
time_to_close_hours: Optional[float]
197+
time_to_close_pct: Optional[float]
198+
regime: ZeroDteRegime
199+
exposures: ZeroDteExposures
200+
expected_move: ZeroDteExpectedMove
201+
pin_risk: ZeroDtePinRisk
202+
hedging: ZeroDteHedging
203+
decay: ZeroDteDecay
204+
vol_context: ZeroDteVolContext
205+
flow: ZeroDteFlow
206+
levels: ZeroDteLevels
207+
liquidity: ZeroDteLiquidity
208+
metadata: ZeroDteMetadata
209+
strikes: List[ZeroDteStrike]
210+
# Optional — only present near close (<5 min) when greeks may be unstable.
211+
warnings: List[str]
212+
# ── No-0DTE fallback ─────────────────────────────────────────────
213+
no_zero_dte: bool
214+
message: str
215+
next_zero_dte_expiry: Optional[str]

0 commit comments

Comments
 (0)