flashalpha 0.3.3__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashalpha-0.3.4/CLAUDE.md +67 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/PKG-INFO +1 -1
- {flashalpha-0.3.3 → flashalpha-0.3.4}/pyproject.toml +1 -1
- flashalpha-0.3.4/src/flashalpha/__init__.py +54 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/src/flashalpha/client.py +11 -3
- flashalpha-0.3.4/src/flashalpha/types.py +215 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/tests/test_integration.py +254 -0
- flashalpha-0.3.3/src/flashalpha/__init__.py +0 -22
- {flashalpha-0.3.3 → flashalpha-0.3.4}/.claude/settings.json +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/.github/workflows/ci.yml +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/.gitignore +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/CHANGELOG.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/CONTRIBUTING.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/LICENSE +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/README.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/article-flashalpha-python-sdk.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome-investing/CODE_OF_CONDUCT.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome-investing/LICENSE +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome-investing/PULL_REQUEST_TEMPLATE.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome-investing/README.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome-investing/contributing.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome_investing/README.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome_investing/src/static/img/.gitkeep +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/awesome_investing/src/static/img/markus-spiske-5gGcn2PRrtc-unsplash.jpg +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/docs/api.md +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/examples/quickstart.py +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/src/flashalpha/exceptions.py +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/tests/__init__.py +0 -0
- {flashalpha-0.3.3 → flashalpha-0.3.4}/tests/test_client.py +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
|
4
|
+
|
|
5
|
+
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
|
6
|
+
|
|
7
|
+
## 1. Think Before Coding
|
|
8
|
+
|
|
9
|
+
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
|
10
|
+
|
|
11
|
+
Before implementing:
|
|
12
|
+
- State your assumptions explicitly. If uncertain, ask.
|
|
13
|
+
- If multiple interpretations exist, present them - don't pick silently.
|
|
14
|
+
- If a simpler approach exists, say so. Push back when warranted.
|
|
15
|
+
- If something is unclear, stop. Name what's confusing. Ask.
|
|
16
|
+
|
|
17
|
+
## 2. Simplicity First
|
|
18
|
+
|
|
19
|
+
**Minimum code that solves the problem. Nothing speculative.**
|
|
20
|
+
|
|
21
|
+
- No features beyond what was asked.
|
|
22
|
+
- No abstractions for single-use code.
|
|
23
|
+
- No "flexibility" or "configurability" that wasn't requested.
|
|
24
|
+
- No error handling for impossible scenarios.
|
|
25
|
+
- If you write 200 lines and it could be 50, rewrite it.
|
|
26
|
+
|
|
27
|
+
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
|
28
|
+
|
|
29
|
+
## 3. Surgical Changes
|
|
30
|
+
|
|
31
|
+
**Touch only what you must. Clean up only your own mess.**
|
|
32
|
+
|
|
33
|
+
When editing existing code:
|
|
34
|
+
- Don't "improve" adjacent code, comments, or formatting.
|
|
35
|
+
- Don't refactor things that aren't broken.
|
|
36
|
+
- Match existing style, even if you'd do it differently.
|
|
37
|
+
- If you notice unrelated dead code, mention it - don't delete it.
|
|
38
|
+
|
|
39
|
+
When your changes create orphans:
|
|
40
|
+
- Remove imports/variables/functions that YOUR changes made unused.
|
|
41
|
+
- Don't remove pre-existing dead code unless asked.
|
|
42
|
+
|
|
43
|
+
The test: Every changed line should trace directly to the user's request.
|
|
44
|
+
|
|
45
|
+
## 4. Goal-Driven Execution
|
|
46
|
+
|
|
47
|
+
**Define success criteria. Loop until verified.**
|
|
48
|
+
|
|
49
|
+
Transform tasks into verifiable goals:
|
|
50
|
+
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
|
51
|
+
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
|
52
|
+
- "Refactor X" → "Ensure tests pass before and after"
|
|
53
|
+
|
|
54
|
+
For multi-step tasks, state a brief plan:
|
|
55
|
+
```
|
|
56
|
+
1. [Step] → verify: [check]
|
|
57
|
+
2. [Step] → verify: [check]
|
|
58
|
+
3. [Step] → verify: [check]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
|
66
|
+
|
|
67
|
+
_Source: [forrestchang/andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)_
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashalpha
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: 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.
|
|
5
5
|
Project-URL: Homepage, https://flashalpha.com
|
|
6
6
|
Project-URL: Documentation, https://flashalpha.com/docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flashalpha"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.4"
|
|
8
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."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""FlashAlpha Python SDK — options exposure analytics API."""
|
|
2
|
+
|
|
3
|
+
from .client import FlashAlpha
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
FlashAlphaError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ServerError,
|
|
10
|
+
TierRestrictedError,
|
|
11
|
+
)
|
|
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
|
+
)
|
|
29
|
+
|
|
30
|
+
__version__ = "0.3.4"
|
|
31
|
+
__all__ = [
|
|
32
|
+
"FlashAlpha",
|
|
33
|
+
"FlashAlphaError",
|
|
34
|
+
"AuthenticationError",
|
|
35
|
+
"TierRestrictedError",
|
|
36
|
+
"NotFoundError",
|
|
37
|
+
"RateLimitError",
|
|
38
|
+
"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",
|
|
54
|
+
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
|
|
@@ -15,6 +15,9 @@ from .exceptions import (
|
|
|
15
15
|
TierRestrictedError,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .types import ZeroDteResponse
|
|
20
|
+
|
|
18
21
|
BASE_URL = "https://lab.flashalpha.com"
|
|
19
22
|
|
|
20
23
|
|
|
@@ -192,8 +195,13 @@ class FlashAlpha:
|
|
|
192
195
|
"""Verbal narrative analysis of exposure. Requires Growth+."""
|
|
193
196
|
return self._get(f"/v1/exposure/narrative/{symbol}")
|
|
194
197
|
|
|
195
|
-
def zero_dte(self, symbol: str, *, strike_range: float | None = None) ->
|
|
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
|
+
"""
|
|
197
205
|
params: dict[str, Any] = {}
|
|
198
206
|
if strike_range is not None:
|
|
199
207
|
params["strike_range"] = strike_range
|
|
@@ -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]
|
|
@@ -136,6 +136,260 @@ def test_zero_dte(fa):
|
|
|
136
136
|
assert "decay" in result
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def test_zero_dte_new_fields(fa):
|
|
140
|
+
"""Validate the full zero-DTE response shape including the v0.3.4 fields:
|
|
141
|
+
distance-to-flip in dollars/sigmas, sub-score breakdown for pin_score,
|
|
142
|
+
fine-grained hedging buckets (10bp/25bp/50bp/100bp + convexity_at_spot),
|
|
143
|
+
flow concentration (atm/top-3 + net_call_minus_put_volume), wall-strength
|
|
144
|
+
and level-cluster scores, the new liquidity and metadata sections, and
|
|
145
|
+
per-strike greeks/quotes/spreads.
|
|
146
|
+
|
|
147
|
+
Uses SPX which has daily 0DTE — falls back gracefully on weekends/holidays.
|
|
148
|
+
"""
|
|
149
|
+
result = fa.zero_dte("SPX")
|
|
150
|
+
assert result["symbol"] == "SPX"
|
|
151
|
+
|
|
152
|
+
if result.get("no_zero_dte"):
|
|
153
|
+
# Weekend / holiday — response is just a stub, nothing else to verify
|
|
154
|
+
assert "next_zero_dte_expiry" in result
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
# ── top-level ──────────────────────────────────────────────────────
|
|
158
|
+
for k in ("underlying_price", "expiration", "as_of", "market_open",
|
|
159
|
+
"time_to_close_hours", "time_to_close_pct"):
|
|
160
|
+
assert k in result, f"top-level {k} missing"
|
|
161
|
+
|
|
162
|
+
# ── regime ─────────────────────────────────────────────────────────
|
|
163
|
+
regime = result["regime"]
|
|
164
|
+
for k in ("label", "description", "gamma_flip", "spot_vs_flip", "spot_to_flip_pct",
|
|
165
|
+
"distance_to_flip_dollars", "distance_to_flip_sigmas"):
|
|
166
|
+
assert k in regime, f"regime.{k} missing"
|
|
167
|
+
|
|
168
|
+
# ── exposures ──────────────────────────────────────────────────────
|
|
169
|
+
exposures = result["exposures"]
|
|
170
|
+
for k in ("net_gex", "net_dex", "net_vex", "net_chex",
|
|
171
|
+
"pct_of_total_gex", "total_chain_net_gex"):
|
|
172
|
+
assert k in exposures, f"exposures.{k} missing"
|
|
173
|
+
|
|
174
|
+
# ── expected_move ──────────────────────────────────────────────────
|
|
175
|
+
em = result["expected_move"]
|
|
176
|
+
for k in ("implied_1sd_dollars", "implied_1sd_pct", "remaining_1sd_dollars",
|
|
177
|
+
"remaining_1sd_pct", "upper_bound", "lower_bound",
|
|
178
|
+
"straddle_price", "atm_iv"):
|
|
179
|
+
assert k in em, f"expected_move.{k} missing"
|
|
180
|
+
|
|
181
|
+
# ── pin_risk ───────────────────────────────────────────────────────
|
|
182
|
+
pr = result["pin_risk"]
|
|
183
|
+
for k in ("magnet_strike", "magnet_gex", "distance_to_magnet_pct",
|
|
184
|
+
"pin_score", "components", "max_pain",
|
|
185
|
+
"oi_concentration_top3_pct", "description"):
|
|
186
|
+
assert k in pr, f"pin_risk.{k} missing"
|
|
187
|
+
components = pr["components"]
|
|
188
|
+
for k in ("oi_score", "proximity_score", "time_score", "gamma_score"):
|
|
189
|
+
assert k in components, f"pin_risk.components.{k} missing"
|
|
190
|
+
|
|
191
|
+
# ── hedging — fine-grained buckets + convexity ─────────────────────
|
|
192
|
+
hedging = result["hedging"]
|
|
193
|
+
for bucket in ("spot_up_10bp", "spot_down_10bp",
|
|
194
|
+
"spot_up_25bp", "spot_down_25bp",
|
|
195
|
+
"spot_up_half_pct", "spot_down_half_pct",
|
|
196
|
+
"spot_up_1pct", "spot_down_1pct"):
|
|
197
|
+
assert bucket in hedging, f"hedging.{bucket} missing"
|
|
198
|
+
b = hedging[bucket]
|
|
199
|
+
for k in ("dealer_shares_to_trade", "direction", "notional_usd"):
|
|
200
|
+
assert k in b, f"hedging.{bucket}.{k} missing"
|
|
201
|
+
assert "convexity_at_spot" in hedging
|
|
202
|
+
|
|
203
|
+
# ── decay ──────────────────────────────────────────────────────────
|
|
204
|
+
decay = result["decay"]
|
|
205
|
+
for k in ("net_theta_dollars", "theta_per_hour_remaining", "charm_regime",
|
|
206
|
+
"charm_description", "gamma_acceleration", "description"):
|
|
207
|
+
assert k in decay, f"decay.{k} missing"
|
|
208
|
+
|
|
209
|
+
# ── vol_context ────────────────────────────────────────────────────
|
|
210
|
+
vc = result["vol_context"]
|
|
211
|
+
for k in ("zero_dte_atm_iv", "seven_dte_atm_iv", "iv_ratio_0dte_7dte",
|
|
212
|
+
"vix", "vanna_exposure", "vanna_interpretation", "description"):
|
|
213
|
+
assert k in vc, f"vol_context.{k} missing"
|
|
214
|
+
|
|
215
|
+
# ── flow ───────────────────────────────────────────────────────────
|
|
216
|
+
flow = result["flow"]
|
|
217
|
+
for k in ("total_volume", "call_volume", "put_volume",
|
|
218
|
+
"net_call_minus_put_volume",
|
|
219
|
+
"total_oi", "call_oi", "put_oi",
|
|
220
|
+
"pc_ratio_volume", "pc_ratio_oi", "volume_to_oi_ratio",
|
|
221
|
+
"atm_volume_share_pct", "top3_strike_volume_pct"):
|
|
222
|
+
assert k in flow, f"flow.{k} missing"
|
|
223
|
+
|
|
224
|
+
# ── levels ─────────────────────────────────────────────────────────
|
|
225
|
+
levels = result["levels"]
|
|
226
|
+
for k in ("call_wall", "call_wall_gex", "call_wall_strength",
|
|
227
|
+
"distance_to_call_wall_pct",
|
|
228
|
+
"put_wall", "put_wall_gex", "put_wall_strength",
|
|
229
|
+
"distance_to_put_wall_pct",
|
|
230
|
+
"distance_to_magnet_dollars",
|
|
231
|
+
"highest_oi_strike", "highest_oi_total",
|
|
232
|
+
"max_positive_gamma", "max_negative_gamma",
|
|
233
|
+
"level_cluster_score"):
|
|
234
|
+
assert k in levels, f"levels.{k} missing"
|
|
235
|
+
|
|
236
|
+
# ── liquidity (new section) ────────────────────────────────────────
|
|
237
|
+
liquidity = result["liquidity"]
|
|
238
|
+
for k in ("atm_spread_pct", "weighted_spread_pct", "execution_score"):
|
|
239
|
+
assert k in liquidity, f"liquidity.{k} missing"
|
|
240
|
+
|
|
241
|
+
# ── metadata (new section) ─────────────────────────────────────────
|
|
242
|
+
metadata = result["metadata"]
|
|
243
|
+
for k in ("snapshot_age_seconds", "chain_contract_count",
|
|
244
|
+
"data_quality_score", "greek_smoothness_score"):
|
|
245
|
+
assert k in metadata, f"metadata.{k} missing"
|
|
246
|
+
|
|
247
|
+
# ── per-strike entries ─────────────────────────────────────────────
|
|
248
|
+
strikes = result["strikes"]
|
|
249
|
+
assert isinstance(strikes, list)
|
|
250
|
+
if strikes:
|
|
251
|
+
s = strikes[0]
|
|
252
|
+
for k in ("strike", "distance_from_spot_pct",
|
|
253
|
+
"call_symbol", "put_symbol",
|
|
254
|
+
"call_gex", "put_gex", "net_gex",
|
|
255
|
+
"call_dex", "put_dex", "net_dex",
|
|
256
|
+
"net_vex", "net_chex",
|
|
257
|
+
"call_oi", "put_oi", "call_volume", "put_volume",
|
|
258
|
+
"gex_share_pct", "oi_share_pct", "volume_share_pct",
|
|
259
|
+
"call_iv", "put_iv",
|
|
260
|
+
"call_delta", "put_delta",
|
|
261
|
+
"call_gamma", "put_gamma",
|
|
262
|
+
"call_theta", "put_theta",
|
|
263
|
+
"call_mid", "put_mid",
|
|
264
|
+
"call_spread_pct", "put_spread_pct"):
|
|
265
|
+
assert k in s, f"strikes[0].{k} missing"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_zero_dte_typed_response(fa):
|
|
269
|
+
"""Comprehensive end-to-end test of the typed ``ZeroDteResponse``.
|
|
270
|
+
|
|
271
|
+
Mirrors ``test_zero_dte_new_fields`` field-for-field, but reads via the
|
|
272
|
+
typed paths (``r["regime"]["distance_to_flip_dollars"]`` is fine because
|
|
273
|
+
``ZeroDteResponse`` is a ``TypedDict`` — at runtime it's identical to
|
|
274
|
+
``dict``). The point is to lock in that every documented field name
|
|
275
|
+
in the Python type definitions matches what the API actually ships.
|
|
276
|
+
"""
|
|
277
|
+
from flashalpha.types import ZeroDteResponse
|
|
278
|
+
|
|
279
|
+
r: ZeroDteResponse = fa.zero_dte("SPX")
|
|
280
|
+
assert r["symbol"] == "SPX"
|
|
281
|
+
|
|
282
|
+
if r.get("no_zero_dte"):
|
|
283
|
+
assert "next_zero_dte_expiry" in r
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
# Top-level
|
|
287
|
+
assert isinstance(r["underlying_price"], (int, float))
|
|
288
|
+
assert "expiration" in r
|
|
289
|
+
assert isinstance(r["as_of"], str)
|
|
290
|
+
assert isinstance(r["market_open"], bool)
|
|
291
|
+
assert "time_to_close_hours" in r
|
|
292
|
+
assert "time_to_close_pct" in r
|
|
293
|
+
|
|
294
|
+
# regime
|
|
295
|
+
regime = r["regime"]
|
|
296
|
+
for k in ("label", "description", "gamma_flip", "spot_vs_flip", "spot_to_flip_pct",
|
|
297
|
+
"distance_to_flip_dollars", "distance_to_flip_sigmas"):
|
|
298
|
+
assert k in regime, f"regime.{k} missing in typed access"
|
|
299
|
+
|
|
300
|
+
# exposures
|
|
301
|
+
exposures = r["exposures"]
|
|
302
|
+
for k in ("net_gex", "net_dex", "net_vex", "net_chex",
|
|
303
|
+
"pct_of_total_gex", "total_chain_net_gex"):
|
|
304
|
+
assert k in exposures, f"exposures.{k} missing in typed access"
|
|
305
|
+
|
|
306
|
+
# expected_move
|
|
307
|
+
em = r["expected_move"]
|
|
308
|
+
for k in ("implied_1sd_dollars", "implied_1sd_pct",
|
|
309
|
+
"remaining_1sd_dollars", "remaining_1sd_pct",
|
|
310
|
+
"upper_bound", "lower_bound",
|
|
311
|
+
"straddle_price", "atm_iv"):
|
|
312
|
+
assert k in em, f"expected_move.{k} missing in typed access"
|
|
313
|
+
|
|
314
|
+
# pin_risk
|
|
315
|
+
pr = r["pin_risk"]
|
|
316
|
+
for k in ("magnet_strike", "magnet_gex", "distance_to_magnet_pct",
|
|
317
|
+
"pin_score", "components", "max_pain",
|
|
318
|
+
"oi_concentration_top3_pct", "description"):
|
|
319
|
+
assert k in pr, f"pin_risk.{k} missing in typed access"
|
|
320
|
+
for k in ("oi_score", "proximity_score", "time_score", "gamma_score"):
|
|
321
|
+
assert k in pr["components"], f"pin_risk.components.{k} missing"
|
|
322
|
+
|
|
323
|
+
# hedging
|
|
324
|
+
hedging = r["hedging"]
|
|
325
|
+
for bucket in ("spot_up_10bp", "spot_down_10bp",
|
|
326
|
+
"spot_up_25bp", "spot_down_25bp",
|
|
327
|
+
"spot_up_half_pct", "spot_down_half_pct",
|
|
328
|
+
"spot_up_1pct", "spot_down_1pct"):
|
|
329
|
+
assert bucket in hedging, f"hedging.{bucket} missing in typed access"
|
|
330
|
+
for k in ("dealer_shares_to_trade", "direction", "notional_usd"):
|
|
331
|
+
assert k in hedging[bucket], f"hedging.{bucket}.{k} missing"
|
|
332
|
+
assert "convexity_at_spot" in hedging
|
|
333
|
+
|
|
334
|
+
# decay
|
|
335
|
+
for k in ("net_theta_dollars", "theta_per_hour_remaining", "charm_regime",
|
|
336
|
+
"charm_description", "gamma_acceleration", "description"):
|
|
337
|
+
assert k in r["decay"], f"decay.{k} missing in typed access"
|
|
338
|
+
|
|
339
|
+
# vol_context
|
|
340
|
+
for k in ("zero_dte_atm_iv", "seven_dte_atm_iv", "iv_ratio_0dte_7dte",
|
|
341
|
+
"vix", "vanna_exposure", "vanna_interpretation", "description"):
|
|
342
|
+
assert k in r["vol_context"], f"vol_context.{k} missing in typed access"
|
|
343
|
+
|
|
344
|
+
# flow
|
|
345
|
+
for k in ("total_volume", "call_volume", "put_volume",
|
|
346
|
+
"net_call_minus_put_volume",
|
|
347
|
+
"total_oi", "call_oi", "put_oi",
|
|
348
|
+
"pc_ratio_volume", "pc_ratio_oi", "volume_to_oi_ratio",
|
|
349
|
+
"atm_volume_share_pct", "top3_strike_volume_pct"):
|
|
350
|
+
assert k in r["flow"], f"flow.{k} missing in typed access"
|
|
351
|
+
|
|
352
|
+
# levels
|
|
353
|
+
for k in ("call_wall", "call_wall_gex", "call_wall_strength",
|
|
354
|
+
"distance_to_call_wall_pct",
|
|
355
|
+
"put_wall", "put_wall_gex", "put_wall_strength",
|
|
356
|
+
"distance_to_put_wall_pct",
|
|
357
|
+
"distance_to_magnet_dollars",
|
|
358
|
+
"highest_oi_strike", "highest_oi_total",
|
|
359
|
+
"max_positive_gamma", "max_negative_gamma",
|
|
360
|
+
"level_cluster_score"):
|
|
361
|
+
assert k in r["levels"], f"levels.{k} missing in typed access"
|
|
362
|
+
|
|
363
|
+
# liquidity
|
|
364
|
+
for k in ("atm_spread_pct", "weighted_spread_pct", "execution_score"):
|
|
365
|
+
assert k in r["liquidity"], f"liquidity.{k} missing in typed access"
|
|
366
|
+
|
|
367
|
+
# metadata
|
|
368
|
+
for k in ("snapshot_age_seconds", "chain_contract_count",
|
|
369
|
+
"data_quality_score", "greek_smoothness_score"):
|
|
370
|
+
assert k in r["metadata"], f"metadata.{k} missing in typed access"
|
|
371
|
+
|
|
372
|
+
# strikes[0] — every per-strike field
|
|
373
|
+
strikes = r["strikes"]
|
|
374
|
+
assert isinstance(strikes, list)
|
|
375
|
+
if strikes:
|
|
376
|
+
s = strikes[0]
|
|
377
|
+
for k in ("strike", "distance_from_spot_pct",
|
|
378
|
+
"call_symbol", "put_symbol",
|
|
379
|
+
"call_gex", "put_gex", "net_gex",
|
|
380
|
+
"call_dex", "put_dex", "net_dex",
|
|
381
|
+
"net_vex", "net_chex",
|
|
382
|
+
"call_oi", "put_oi", "call_volume", "put_volume",
|
|
383
|
+
"gex_share_pct", "oi_share_pct", "volume_share_pct",
|
|
384
|
+
"call_iv", "put_iv",
|
|
385
|
+
"call_delta", "put_delta",
|
|
386
|
+
"call_gamma", "put_gamma",
|
|
387
|
+
"call_theta", "put_theta",
|
|
388
|
+
"call_mid", "put_mid",
|
|
389
|
+
"call_spread_pct", "put_spread_pct"):
|
|
390
|
+
assert k in s, f"strikes[0].{k} missing in typed access"
|
|
391
|
+
|
|
392
|
+
|
|
139
393
|
# ── Pricing ─────────────────────────────────────────────────────────
|
|
140
394
|
|
|
141
395
|
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""FlashAlpha Python SDK — options exposure analytics API."""
|
|
2
|
-
|
|
3
|
-
from .client import FlashAlpha
|
|
4
|
-
from .exceptions import (
|
|
5
|
-
AuthenticationError,
|
|
6
|
-
FlashAlphaError,
|
|
7
|
-
NotFoundError,
|
|
8
|
-
RateLimitError,
|
|
9
|
-
ServerError,
|
|
10
|
-
TierRestrictedError,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
__version__ = "0.3.2"
|
|
14
|
-
__all__ = [
|
|
15
|
-
"FlashAlpha",
|
|
16
|
-
"FlashAlphaError",
|
|
17
|
-
"AuthenticationError",
|
|
18
|
-
"TierRestrictedError",
|
|
19
|
-
"NotFoundError",
|
|
20
|
-
"RateLimitError",
|
|
21
|
-
"ServerError",
|
|
22
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|