@@ -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