From 3d15574efd559c0ba90120e9598273a23fdc3721 Mon Sep 17 00:00:00 2001 From: Tomasz Dobrowolski Date: Wed, 9 Sep 2026 18:00:48 +0300 Subject: [PATCH 1/2] feat: surface gamma_flip_status and handle null gamma_flip The API (endpoint version 2026.09.09) made gamma_flip nullable and added a sibling gamma_flip_status string that reads "available" when a level is published and otherwise carries a reason code (no_boundary, stored_sign_mismatch, insufficient_local_coverage, insufficient_quote_quality, sensitive_root, uncertain_root_path, search_budget, quality_budget). Roughly two in three chains now return a null flip, with regime falling back to "unknown". This adds gamma_flip_status alongside every gamma_flip declaration in the response types and fixes the quickstart example, which formatted the level with :.2f and therefore raised TypeError on NoneType.__format__ for the now-common null case; it prints the reason code instead. The README quick start is updated to match, since it is rendered on PyPI. Clients should treat any status other than "available", including codes added later, as no level published. Co-Authored-By: Claude Opus 5 --- README.md | 16 ++++++++++++++-- examples/quickstart.py | 18 ++++++++++++++++-- src/flashalpha/types.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e30c18f..d9f6bf2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ fa = FlashAlpha("YOUR_API_KEY") # Get a free key at flashalpha.com # Gamma exposure by strike gex = fa.gex("SPY") print(f"Net GEX: ${gex['net_gex']:,.0f}") -print(f"Gamma flip: {gex['gamma_flip']}") +# gamma_flip is None unless gamma_flip_status == "available" -- never format it +# as a number without checking first. +print(f"Gamma flip: {gex['gamma_flip']} ({gex['gamma_flip_status']})") for strike in gex["strikes"][:5]: print(f" {strike['strike']}: net ${strike['net_gex']:,.0f}") @@ -31,6 +33,15 @@ for strike in gex["strikes"][:5]: Get your free API key at [flashalpha.com](https://flashalpha.com) — no credit card required. +> **`gamma_flip` is nullable.** A dealer gamma flip is only published when the +> level is well-determined, which is a minority of chains. When it is withheld, +> `gamma_flip` is `null`, `regime` is `"unknown"`, and `gamma_flip_status` carries +> a reason code (`no_boundary`, `insufficient_local_coverage`, +> `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, +> `stored_sign_mismatch`, `search_budget`, `quality_budget`). Only +> `gamma_flip_status == "available"` guarantees a number; treat any other value - +> including codes added in future - as "no level published". + ## Data provenance: `data_as_of` Every successful JSON-object response carries `data_as_of`, reporting when each upstream @@ -148,7 +159,8 @@ chex = fa.chex("NVDA") # Charm exposure levels = fa.exposure_levels("SPY") # Key levels print(f"Call wall: {levels['levels']['call_wall']}") print(f"Put wall: {levels['levels']['put_wall']}") -print(f"Gamma flip: {levels['levels']['gamma_flip']}") +print(f"Gamma flip: {levels['levels']['gamma_flip']} " + f"({levels['levels']['gamma_flip_status']})") summary = fa.exposure_summary("SPY") # Full summary (Growth+) narrative = fa.narrative("SPY") # AI narrative (Growth+) diff --git a/examples/quickstart.py b/examples/quickstart.py index d43498d..e68bbb5 100644 --- a/examples/quickstart.py +++ b/examples/quickstart.py @@ -2,13 +2,27 @@ from flashalpha import FlashAlpha + +def flip(payload): + """Format ``gamma_flip``, which is ``None`` for most chains. + + When no level is published the API returns ``gamma_flip: null`` and a + ``gamma_flip_status`` reason code (e.g. ``"no_boundary"``). Anything + other than ``"available"`` means there is no flip to show. + """ + level = payload.get("gamma_flip") + if level is None: + return f"n/a ({payload.get('gamma_flip_status') or 'unavailable'})" + return f"{level:.2f}" + + # 1. Initialize with your API key fa = FlashAlpha("YOUR_API_KEY") # 2. Get gamma exposure for SPY gex = fa.gex("SPY") print(f"SPY Net GEX: ${gex['net_gex']:,.0f}") -print(f"Gamma flip: {gex['gamma_flip']:.2f}") +print(f"Gamma flip: {flip(gex)}") print(f"Regime: {gex['net_gex_label']}") print() @@ -16,7 +30,7 @@ levels = fa.exposure_levels("SPY")["levels"] print(f"Call wall (resistance): {levels['call_wall']}") print(f"Put wall (support): {levels['put_wall']}") -print(f"Gamma flip: {levels['gamma_flip']:.2f}") +print(f"Gamma flip: {flip(levels)}") print(f"0DTE magnet: {levels['zero_dte_magnet']}") print() diff --git a/src/flashalpha/types.py b/src/flashalpha/types.py index 160a19b..9a155fc 100644 --- a/src/flashalpha/types.py +++ b/src/flashalpha/types.py @@ -53,6 +53,7 @@ class ZeroDteRegime(TypedDict, total=False): label: str description: str gamma_flip: Optional[float] + gamma_flip_status: Optional[str] spot_vs_flip: Literal["above", "below"] spot_to_flip_pct: Optional[float] distance_to_flip_dollars: Optional[float] @@ -420,6 +421,15 @@ class ExposureSummaryResponse(TypedDict, total=False): # following likely). One of the two or three numbers most experienced # users actually look at on this endpoint. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published. ``"available"`` when a + # level is returned; otherwise a reason code for the withholding: + # ``"no_boundary"``, ``"stored_sign_mismatch"``, + # ``"insufficient_local_coverage"``, ``"insufficient_quote_quality"``, + # ``"sensitive_root"``, ``"uncertain_root_path"``, ``"search_budget"``, + # ``"quality_budget"``. New codes may be added server-side, so treat + # ANY value other than ``"available"`` as "no flip published" -- in + # that case ``gamma_flip`` is ``None`` and ``regime`` is ``"unknown"``. + gamma_flip_status: Optional[str] # Dealer-positioning regime classification: # - ``"positive_gamma"``: spot above gamma_flip # - ``"negative_gamma"``: spot below gamma_flip @@ -615,6 +625,10 @@ class VrpRegime(TypedDict, total=False): # Strike where net dealer gamma crosses zero. Same as # exposure_summary.gamma_flip. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] class VrpStrategyScores(TypedDict, total=False): @@ -863,6 +877,10 @@ class MaxPainDealerAlignment(TypedDict, total=False): # Strike where net dealer gamma crosses zero. Same definition as # ``exposure_summary.gamma_flip``. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Strike with highest absolute call GEX (dealer-side resistance). call_wall: Optional[float] # Strike with highest absolute put GEX (dealer-side support). @@ -1194,6 +1212,10 @@ class StockSummaryExposure(TypedDict, total=False): # gamma regime (mean-reverting); spot BELOW = negative-gamma # (trend-following). gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Strike with highest absolute call GEX (dealer-side resistance). call_wall: Optional[float] # Strike with highest absolute put GEX (dealer-side support). @@ -1446,6 +1468,10 @@ class NarrativeData(TypedDict, total=False): vix: Optional[float] # Strike where net dealer gamma crosses zero. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Strike with the highest absolute call GEX (dealer-side resistance). call_wall: Optional[float] # Strike with the highest absolute put GEX (dealer-side support). @@ -1535,6 +1561,10 @@ class ExposureLevels(TypedDict, total=False): # Strike where net dealer gamma crosses zero. Spot above = positive- # gamma regime; spot below = negative-gamma. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Strike carrying the largest positive net GEX (dealers most long # gamma here — strongest local mean-reversion magnet). max_positive_gamma: Optional[float] @@ -2282,6 +2312,10 @@ class GexResponse(TypedDict, total=False): as_of: str # Strike where net dealer gamma crosses zero across the chain. gamma_flip: Optional[float] + # Why ``gamma_flip`` is or isn't published -- ``"available"``, else a + # reason code. Same values as ``ExposureSummaryResponse.gamma_flip_status``; + # treat anything other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Net GEX across the chain (dollars per 1% spot move). net_gex: Optional[float] # Plain-text categorical label for the GEX regime @@ -3698,6 +3732,7 @@ class FlowSignalsChain(TypedDict, total=False): put_wall: Optional[float] max_pain: Optional[float] gamma_flip: Optional[float] + gamma_flip_status: Optional[str] class FlowSignalScoreBreakdown(TypedDict, total=False): @@ -4204,6 +4239,7 @@ class EarningsDealerLevels(TypedDict, total=False): """Dealer levels scoped to the event-week expiries.""" gamma_flip: Optional[float] + gamma_flip_status: Optional[str] call_wall: Optional[float] put_wall: Optional[float] highest_oi_strike: Optional[float] @@ -5176,6 +5212,7 @@ class FlowZeroDteSeriesBar(TypedDict, total=False): net_gex: Optional[float] net_dex: Optional[float] gamma_flip: Optional[float] + gamma_flip_status: Optional[str] call_wall: Optional[float] put_wall: Optional[float] magnet: Optional[float] From 4ac39882660e1d1d9b86904d6bf07f5ee87bf5ef Mon Sep 17 00:00:00 2001 From: Tomasz Dobrowolski Date: Wed, 9 Sep 2026 18:08:44 +0300 Subject: [PATCH 2/2] feat: add gamma_flip_status to flow responses and reference docs The three flow endpoints (/v1/flow/levels, /v1/flow/gex, /v1/flow/live) pair their value field live_gamma_flip with a status field named plainly gamma_flip_status, not live_gamma_flip_status, so searching only for the gamma_flip identifier missed them and FlowLevelsResponse, FlowGexResponse and FlowLiveResponse were dropping a field the API actually returns. This adds the declaration to all three, noting the asymmetric wire name at each site, which brings the package to fourteen gamma_flip_status declarations. It also brings the reference docs in line: docs/api.md gains the field in thirteen JSON examples, five response-field tables and three notable-metrics lists, marks gamma_flip nullable where it was not already, and carries a callout explaining the reason codes and the flow naming asymmetry; AGENTS.md gains an entry in its silent-null traps list warning that formatting gamma_flip as a number raises TypeError; llms.txt gains a nullability note beside its typed-response example. article-flashalpha-python-sdk.md is left alone as marketing prose rather than a field reference, and its snippets do not format the level numerically. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 15 +++++++++++++- docs/api.md | 43 +++++++++++++++++++++++++++++++++-------- llms.txt | 14 ++++++++++++++ src/flashalpha/types.py | 19 ++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4b14712..7efc17c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -127,7 +127,8 @@ summary = client.stock_summary("SPY") # Or a focused exposure snapshot. exp = client.exposure_summary("SPY") -print(exp["gamma_flip"], exp["regime"], exp["exposures"]["net_gex"]) +# gamma_flip is None unless gamma_flip_status == "available". +print(exp["gamma_flip"], exp["gamma_flip_status"], exp["regime"]) ``` ## Typed responses @@ -144,6 +145,18 @@ silent-null traps: MAGNITUDE on this endpoint (the `direction` field carries the sign). On `zero_dte` the same field is signed. Don't copy code between the two without re-checking signs. +- `gamma_flip` (every exposure/flow endpoint): nullable, and `null` + for the MAJORITY of chains. The sibling `gamma_flip_status` says why + — `"available"` means a level was published, anything else is a + reason code (`no_boundary`, `insufficient_local_coverage`, + `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, + `stored_sign_mismatch`, `search_budget`, `quality_budget`) and + `regime` degrades to `"unknown"`. NEVER generate code that formats + `gamma_flip` as a number without a null check — `f"{flip:.2f}"` + raises `TypeError` on `NoneType.__format__`. Treat unrecognised + status values as unavailable. On the flow endpoints the value field + is `live_gamma_flip` but the status is still named plain + `gamma_flip_status`. - `pricing/greeks` response: `additional.lambda` collides with the Python `lambda` keyword — the typed model uses the functional `TypedDict` constructor so the JSON name is preserved. Read it as diff --git a/docs/api.md b/docs/api.md index 2904d29..c314e45 100644 --- a/docs/api.md +++ b/docs/api.md @@ -4,6 +4,15 @@ Real-time options exposure analytics. Live gamma (GEX), delta (DEX), vanna (VEX) > 📖 **Canonical, always-current docs:** https://flashalpha.com/docs · 🔑 [Get a free API key](https://flashalpha.com) · 🧪 [Interactive playground](https://lab.flashalpha.com/swagger) +> ⚠️ **`gamma_flip` is nullable.** A dealer gamma flip is only published when the level +> is well-determined, which is a minority of chains. When it is withheld, `gamma_flip` +> (and `live_gamma_flip` on the flow endpoints) is `null`, `regime` is `"unknown"`, and +> the sibling `gamma_flip_status` string carries the reason code. Only +> `gamma_flip_status == "available"` guarantees a number — treat every other value, +> including codes added in future, as "no level published". Note that the flow endpoints +> pair `live_gamma_flip` with a status field named plainly `gamma_flip_status`, not +> `live_gamma_flip_status`. The examples below all show the `"available"` case. + --- ## Playground @@ -429,6 +438,7 @@ curl "https://lab.flashalpha.com/v1/stock/SPY/summary" "net_vex": 1200000000, "net_chex": 850000000, "gamma_flip": 575.25, + "gamma_flip_status": "available", "call_wall": 585.0, "put_wall": 570.0, "max_pain": 578.0, @@ -489,7 +499,8 @@ curl "https://lab.flashalpha.com/v1/stock/SPY/summary" | `volatility.iv_term_structure` | IV at ATM for each active expiration (filtered to 5–200% to exclude bad SVI fits) | | `options_flow` | Aggregate OI, volume, and put/call ratios across all active expirations | | `exposure.net_gex/dex/vex/chex` | Net gamma/delta/vanna/charm exposure | -| `exposure.gamma_flip` | Strike where net GEX crosses zero | +| `exposure.gamma_flip` | Strike where net GEX crosses zero; `null` when no level is published | +| `exposure.gamma_flip_status` | `"available"` when a level is published, otherwise the reason code it was withheld (`no_boundary`, `insufficient_local_coverage`, `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, `search_budget`, `quality_budget`). Treat anything other than `"available"` as no level published | | `exposure.call_wall` / `put_wall` | Strikes with highest call/put GEX concentration | | `exposure.max_pain` | Strike where total option holder loss is maximized | | `exposure.highest_oi_strike` | Strike with highest total open interest | @@ -553,6 +564,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "underlying_price": 597.505, "as_of": "2026-02-28T16:30:45Z", "gamma_flip": 595.25, + "gamma_flip_status": "available", "net_gex": 2850000000, "net_gex_label": "positive", "strikes": [ @@ -734,6 +746,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "underlying_price": 597.505, "as_of": "2026-02-28T16:30:45Z", "gamma_flip": 595.25, + "gamma_flip_status": "available", "regime": "positive_gamma", "exposures": { "net_gex": 2850000000, @@ -802,6 +815,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "as_of": "2026-02-28T16:30:45Z", "levels": { "gamma_flip": 595.25, + "gamma_flip_status": "available", "max_positive_gamma": 600.0, "max_negative_gamma": 585.0, "call_wall": 600.0, @@ -816,7 +830,8 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ | Field | Description | |-------|-------------| -| `gamma_flip` | Price where net GEX crosses zero — above = positive gamma, below = negative | +| `gamma_flip` | Price where net GEX crosses zero — above = positive gamma, below = negative; `null` when no level is published | +| `gamma_flip_status` | `"available"` when a level is published, otherwise the reason code it was withheld (`no_boundary`, `insufficient_local_coverage`, `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, `search_budget`, `quality_budget`). Treat anything other than `"available"` as no level published | | `call_wall` | Strike with highest call GEX — acts as resistance | | `put_wall` | Strike with highest put GEX — acts as support | | `max_positive_gamma` | Strike with highest positive net GEX | @@ -867,6 +882,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "net_gex_change_pct": 9.6, "vix": 18.5, "gamma_flip": 595.25, + "gamma_flip_status": "available", "call_wall": 600.0, "put_wall": 595.0, "regime": "positive_gamma", @@ -936,6 +952,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "label": "positive_gamma", "description": "Dealers long gamma — moves dampened, mean reversion likely", "gamma_flip": 588.50, + "gamma_flip_status": "available", "spot_vs_flip": "above", "spot_to_flip_pct": 0.33, "distance_to_flip_dollars": 1.92, @@ -1968,6 +1985,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "underlying_price": 597.50, "expiry": "2026-05-15", "live_gamma_flip": 595.50, + "gamma_flip_status": "available", "live_call_wall": 600, "live_put_wall": 590, "live_max_pain": 595 @@ -2125,6 +2143,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "live_net_gex": 12500000000, "live_net_gex_label": "positive", "live_gamma_flip": 595.50, + "gamma_flip_status": "available", "strikes": [ { "strike": 595.0, @@ -2355,6 +2374,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "live_gex": 12500000000, "live_gex_delta": -450000000, "live_gamma_flip": 595.50, + "gamma_flip_status": "available", "live_call_wall": 600, "live_put_wall": 590, "live_max_pain": 595, @@ -2430,7 +2450,8 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "call_wall": 950.0, "put_wall": 850.0, "max_pain": 900.0, - "gamma_flip": 905.0 + "gamma_flip": 905.0, + "gamma_flip_status": "available" }, "count": 1, "signals": [ @@ -2648,6 +2669,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "net_gex": 1842000000, "net_dex": 48200000000, "gamma_flip": 588.50, // nullable + "gamma_flip_status": "available", // null flip -> reason code "call_wall": 595.0, // nullable "put_wall": 585.0, // nullable "magnet": 590.0, // nullable @@ -3374,6 +3396,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "alignment": "converging", "description": "Max pain (545) near gamma flip (546) between walls (538–555) — strong converging magnet.", "gamma_flip": 546, + "gamma_flip_status": "available", "call_wall": 555, "put_wall": 538 }, @@ -3401,7 +3424,8 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ | `oi_by_strike` | Per-strike OI and volume for calls and puts | | `max_pain_by_expiration` | Per-expiry max pain with DTE and total OI. Only present when no `?expiration=` filter. | | `dealer_alignment.alignment` | `converging` (max pain near gamma flip, between walls), `moderate` (between walls, far from flip), `diverging` (outside walls), `unknown` (insufficient data) | -| `dealer_alignment.gamma_flip` | Strike where net GEX crosses zero | +| `dealer_alignment.gamma_flip` | Strike where net GEX crosses zero; `null` when no level is published | +| `dealer_alignment.gamma_flip_status` | `"available"` when a level is published, otherwise the reason code it was withheld (`no_boundary`, `insufficient_local_coverage`, `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, `search_budget`, `quality_budget`). Treat anything other than `"available"` as no level published | | `dealer_alignment.call_wall` / `put_wall` | Strikes with highest absolute call/put GEX | | `regime` | `positive_gamma` or `negative_gamma` based on spot vs gamma flip | | `expected_move.straddle_price` | ATM straddle mid price | @@ -4122,7 +4146,8 @@ Full Volatility Risk Premium dashboard. Combines live IV/RV/GEX data with histor | `regime.gamma` | `string` | `positive_gamma` or `negative_gamma` | | `regime.vrp_regime` | `string?` | `harvestable`, `event_only`, `toxic_short_vol`, `cheap_convexity`, or `surface_distorted` | | `regime.net_gex` | `number` | Net gamma exposure ($) | -| `regime.gamma_flip` | `number` | Gamma flip strike | +| `regime.gamma_flip` | `number?` | Gamma flip strike; `null` when no level is published | +| `regime.gamma_flip_status` | `string` | `"available"` when a level is published, otherwise the reason code it was withheld (`no_boundary`, `insufficient_local_coverage`, `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, `search_budget`, `quality_budget`). Treat anything other than `"available"` as no level published | | **Strategy Scores** (0-100) | | | | `strategy_scores.short_put_spread` | `number` | Short put spread suitability | | `strategy_scores.short_strangle` | `number` | Short strangle suitability | @@ -4422,7 +4447,7 @@ curl "https://lab.flashalpha.com/v1/strategies/expiry-positioning/SPY?expiry=202 Returns the [strategy decision envelope](#strategy-decision-envelope) with strategy-specific `metrics` and `regime`. -**Notable `metrics`:** `max_pain_strike`, `distance_to_pain_pct`, `oi_concentration_score`, `total_open_interest`, `expiry`, `days_to_expiry`, `gamma_flip`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `underlying_price`. +**Notable `metrics`:** `max_pain_strike`, `distance_to_pain_pct`, `oi_concentration_score`, `total_open_interest`, `expiry`, `days_to_expiry`, `gamma_flip`, `gamma_flip_status`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `underlying_price`. **`regime` values:** `strong_pin_likely`, `moderate_pin`, `no_pin_setup`. @@ -4460,7 +4485,7 @@ curl "https://lab.flashalpha.com/v1/strategies/zero-dte/SPY" \ Returns the [strategy decision envelope](#strategy-decision-envelope) with strategy-specific `metrics` and `regime`. -**Notable `metrics`:** `max_pain_strike`, `distance_to_pain_pct`, `oi_concentration_score`, `total_open_interest`, `gamma_flip`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `minutes_to_close`, `session_open_spot`, `expected_move_today`, `expected_move_consumed_pct`, `theta_acceleration`, `underlying_price`. +**Notable `metrics`:** `max_pain_strike`, `distance_to_pain_pct`, `oi_concentration_score`, `total_open_interest`, `gamma_flip`, `gamma_flip_status`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `minutes_to_close`, `session_open_spot`, `expected_move_today`, `expected_move_consumed_pct`, `theta_acceleration`, `underlying_price`. **`regime` values:** `pin_risk_positive_gamma`, `range_compression`, `trend_risk_or_no_setup`; plus `no_same_day_expiry` / `no_expiry_chain` (returned with `decision: insufficient_data` when no chain exists for the selected expiry). @@ -4496,7 +4521,7 @@ curl "https://lab.flashalpha.com/v1/strategies/dealer-regime/SPY" \ Returns the [strategy decision envelope](#strategy-decision-envelope) with strategy-specific `metrics` and `regime`. -**Notable `metrics`:** `net_gamma`, `net_delta`, `gamma_source`, `gamma_flip`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `net_vex`, `net_chex`, `underlying_price`. +**Notable `metrics`:** `net_gamma`, `net_delta`, `gamma_source`, `gamma_flip`, `gamma_flip_status`, `call_wall`, `put_wall`, `distance_to_flip_pct`, `spot_position_label`, `net_vex`, `net_chex`, `underlying_price`. **`regime` values:** `positive_gamma_compression`, `negative_gamma_acceleration`, `transition`. @@ -5099,6 +5124,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ "event_expiry": "2026-06-12", "levels": { "gamma_flip": 210.0, + "gamma_flip_status": "available", "call_wall": 220.0, "put_wall": 205.0, "highest_oi_strike": 215.0 @@ -5122,6 +5148,7 @@ curl -H "X-Api-Key: YOUR_API_KEY" \ |-------|-------------| | `event_expiry` | Closest options expiry on or after the earnings date; null if none found. | | `levels.gamma_flip` | Strike where net GEX flips sign (event-week scope); nullable. | +| `levels.gamma_flip_status` | `"available"` when a level is published, otherwise the reason code it was withheld (`no_boundary`, `insufficient_local_coverage`, `insufficient_quote_quality`, `sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, `search_budget`, `quality_budget`). Treat anything other than `"available"` as no level published. | | `levels.call_wall` / `put_wall` | Largest positive-GEX strike above / largest below spot; nullable. | | `levels.highest_oi_strike` | Strike with the most open interest (event-week scope); nullable. | | `gex_by_dte_bucket[]` | Net GEX and contract count for the `pre_event`, `event_week`, and `post_event` expiry buckets (buckets with no contracts are omitted). | diff --git a/llms.txt b/llms.txt index 6ab79a4..fed3517 100644 --- a/llms.txt +++ b/llms.txt @@ -212,8 +212,22 @@ from flashalpha import StockSummaryResponse, ExposureSummaryResponse summary: StockSummaryResponse = client.stock_summary("SPY") gamma_flip = summary["exposure"]["gamma_flip"] # autocompleted + +# gamma_flip is None for most chains; the sibling status says why. +# Never format it as a number without checking. +if summary["exposure"]["gamma_flip_status"] != "available": + ... # no level published; regime is "unknown" ``` +Nullability note: `gamma_flip` (and `live_gamma_flip` on the flow +endpoints) is only populated when the level is well-determined, which is +a minority of chains. Otherwise it is `null`, `regime` is `"unknown"`, +and `gamma_flip_status` carries a reason code (`no_boundary`, +`insufficient_local_coverage`, `insufficient_quote_quality`, +`sensitive_root`, `uncertain_root_path`, `stored_sign_mismatch`, +`search_budget`, `quality_budget`). Only `"available"` guarantees a +number; treat any other value as no level published. + ## Tier breakdown - **Free**: dual-mode preview tier — `stock_summary` returns a diff --git a/src/flashalpha/types.py b/src/flashalpha/types.py index 9a155fc..f5a5b6c 100644 --- a/src/flashalpha/types.py +++ b/src/flashalpha/types.py @@ -2857,6 +2857,15 @@ class FlowLevelsResponse(TypedDict, total=False): expiry: Optional[str] # Spot where live net dealer gamma crosses zero. ``None`` if no flip. live_gamma_flip: Optional[float] + # Why ``live_gamma_flip`` is or isn't published. NOTE the asymmetric + # name: the value field is ``live_gamma_flip`` but its status field is + # plain ``gamma_flip_status`` on the wire -- there is no + # ``live_gamma_flip_status``. ``"available"`` when a level is returned; + # otherwise a reason code (``"no_boundary"``, + # ``"insufficient_local_coverage"``, ...). See + # ``ExposureSummaryResponse.gamma_flip_status`` for the full code list; + # treat ANY value other than ``"available"`` as no flip published. + gamma_flip_status: Optional[str] # Strike of the largest live call-gamma concentration (upside magnet). live_call_wall: Optional[float] # Strike of the largest live put-gamma concentration (downside magnet). @@ -2993,6 +3002,11 @@ class FlowGexResponse(TypedDict, total=False): live_net_gex_label: str # Live gamma-flip spot. ``None`` if no sign change. live_gamma_flip: Optional[float] + # Why ``live_gamma_flip`` is or isn't published -- ``"available"``, else + # a reason code. Named ``gamma_flip_status`` (not + # ``live_gamma_flip_status``) on the wire; see + # ``FlowLevelsResponse.gamma_flip_status``. + gamma_flip_status: Optional[str] # Per-strike rows (identical schema to settled GEX). See ``GexStrikeRow``. strikes: List[GexStrikeRow] @@ -3131,6 +3145,11 @@ class FlowLiveResponse(TypedDict, total=False): live_gex_delta: Optional[float] # Live gamma-flip spot. ``None`` if no sign change. live_gamma_flip: Optional[float] + # Why ``live_gamma_flip`` is or isn't published -- ``"available"``, else + # a reason code. Named ``gamma_flip_status`` (not + # ``live_gamma_flip_status``) on the wire; see + # ``FlowLevelsResponse.gamma_flip_status``. + gamma_flip_status: Optional[str] # Largest live call-gamma concentration strike (upside magnet). live_call_wall: Optional[float] # Largest live put-gamma concentration strike (downside magnet).