Skip to content

Commit d103b8b

Browse files
tdobrowolski1claude
andcommitted
Add zero-DTE integration tests covering all v0.3.4 response fields
Asserts the full zero-DTE response shape against the live API: top-level scalars, regime (incl. distance_to_flip_dollars/sigmas), exposures, expected_move (incl. straddle_price), pin_risk (incl. component sub-scores + max_pain), hedging fine-grained buckets (10bp/25bp/half_pct + convexity_at_spot), decay, vol_context, flow (incl. concentration metrics), levels (incl. wall strengths + level_cluster_score), the new liquidity and metadata sections, and per-strike greeks/quotes/spreads. Uses SPX (daily 0DTE), gracefully early-returns on weekends/holidays via the no_zero_dte path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 029078d commit d103b8b

1 file changed

Lines changed: 129 additions & 0 deletions

File tree

tests/test_integration.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,135 @@ def test_zero_dte(fa):
136136
assert "decay" in result
137137

138138

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+
139268
# ── Pricing ─────────────────────────────────────────────────────────
140269

141270

0 commit comments

Comments
 (0)