From bf0ee25d1ab7ccf0331b37602286c57d27bfbd90 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 17 May 2026 17:21:49 +0100 Subject: [PATCH 1/8] Add fitting of forward and discount factor --- .github/copilot-instructions.md | 2 +- app/volatility_surface.py | 45 ++++- docs/api/data/index.md | 2 +- docs/api/options/black.md | 4 +- docs/api/options/parity.md | 6 + docs/api/rates/index.md | 14 ++ docs/api/rates/nelson_siegel.md | 3 + docs/glossary.md | 42 ++++- docs/theory/forwards.md | 68 ++++++++ docs/theory/option_pricing.md | 19 +- docs/tutorials/heston_calibration.md | 152 ++++++++++++++++ docs/tutorials/index.md | 3 +- docs/tutorials/volatility_surface.md | 187 ++++---------------- mkdocs.yml | 4 + quantflow/data/yahoo.py | 86 ++++++++- quantflow/options/bs.py | 9 +- quantflow/options/parity.py | 141 +++++++++++++++ quantflow/options/surface.py | 252 ++++++++++++++++++++++----- quantflow/rates/interest_rate.py | 9 + quantflow/rates/nelson_siegel.py | 10 +- quantflow/rates/yield_curve.py | 64 +++++++ quantflow/utils/plot.py | 28 ++- quantflow/utils/price.py | 37 ++++ quantflow_tests/test_rates.py | 4 +- 24 files changed, 957 insertions(+), 234 deletions(-) create mode 100644 docs/api/options/parity.md create mode 100644 docs/api/rates/nelson_siegel.md create mode 100644 docs/theory/forwards.md create mode 100644 docs/tutorials/heston_calibration.md create mode 100644 quantflow/options/parity.py create mode 100644 quantflow/utils/price.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2727217..704f9fb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -32,7 +32,7 @@ applyTo: '/**' * Math in documentation and docstrings: always use `\begin{equation}...\end{equation}` for any formula or equation. Use `$...$` only for brief inline references to variables (e.g. $F$, $K$). Do not use `$$...$$`, `` `...` ``, or RST syntax (`.. math::`, `:math:`). * Math notation convention: use $\Phi$ for the characteristic function and $\phi$ for the characteristic exponent, where $\Phi = e^{-\phi}$. * Glossary entries in `docs/glossary.md` must be kept in alphabetical order. -* Do not repeat concept definitions inline in tutorials or docstrings — link to the glossary instead using a relative markdown link (e.g. `[moneyness](../glossary.md#moneyness)`). +* Do not repeat concept definitions inline in tutorials or docstrings, link to the glossary instead using a relative markdown link (e.g. `[moneyness](../glossary.md#moneyness)`). * Prefer mkdocstrings relative cross-references whenever the target is visible from the current scope: write `[label][.member]` (same class) or `[label][..Sibling]` (same module) instead of repeating the fully-qualified path. Use the full path only when the target lives in a different module than the current docstring. * To rebuild doc examples run `uv run ./dev/build-examples` — runs all scripts in `docs/examples/` and writes their output to `docs/examples_output/` diff --git a/app/volatility_surface.py b/app/volatility_surface.py index b0ac27e..5a133b9 100644 --- a/app/volatility_surface.py +++ b/app/volatility_surface.py @@ -1,6 +1,6 @@ import marimo -__generated_with = "0.22.0" +__generated_with = "0.23.5" app = marimo.App(width="medium") @@ -28,6 +28,8 @@ def _(mo): async with Deribit() as cli: loader = await cli.volatility_surface_loader("eth", exclude_open_interest=0) + # calibrate discount curve for the quoting asset (usd) + loader.calibrate_curves(quote_curve=NelsonSiegel) # build the volatility surface surface = loader.surface() # calculate black implied volatilities @@ -54,6 +56,7 @@ def _(mo): async def _(asset, inverse, mo): import pandas as pd from quantflow.data.deribit import Deribit + from quantflow.rates.nelson_siegel import NelsonSiegel async with Deribit() as cli: loader = await cli.volatility_surface_loader( @@ -62,6 +65,7 @@ async def _(asset, inverse, mo): use_perp=not inverse.value ) + loader.calibrate_curves(quote_curve=NelsonSiegel, asset_curve=NelsonSiegel) # build the volatility surface surface = loader.surface() # calculate black implied volatilities @@ -81,7 +85,7 @@ def int_or_none(v): label="Maturity" ) maturity_dropdown - return int_or_none, maturity_dropdown, pd, surface + return int_or_none, loader, maturity_dropdown, pd, surface @app.cell @@ -114,5 +118,42 @@ def _(ts): return +@app.cell +def _(loader): + loader.quote_curve.plot(ttm_max=2) + return + + +@app.cell +def _(loader): + loader.asset_curve.plot(ttm_max=2) + return + + +@app.cell +def _(loader): + cross = loader.maturities[sorted(loader.maturities)[-2]] + p = cross.put_call_parities(loader.spot.mid, max_pairs=100) + da=None + return da, p + + +@app.cell +def _(da, p): + p.fit_discounts(da=da) + return + + +@app.cell +def _(da, p): + p.plot(da=da) + return + + +@app.cell +def _(): + return + + if __name__ == "__main__": app.run() diff --git a/docs/api/data/index.md b/docs/api/data/index.md index db92556..5ea4094 100644 --- a/docs/api/data/index.md +++ b/docs/api/data/index.md @@ -19,7 +19,7 @@ pip install quantflow[data] | [Financial Modeling Prep](fmp.md) | Equity prices, company profiles, and sector data | | [FRED](fred.md) | US macroeconomic time series from the St. Louis Fed | | [Federal Reserve](fed.md) | Federal Reserve H.15 selected interest rate data | -| [Yahoo](yahoo.md) | Equity option chains from Yahoo Finance | +| [Yahoo](yahoo.md) | Equity prices and option chains from Yahoo Finance | ## Usage diff --git a/docs/api/options/black.md b/docs/api/options/black.md index 5107fdb..9da130a 100644 --- a/docs/api/options/black.md +++ b/docs/api/options/black.md @@ -3,10 +3,10 @@ Here we define the [log strike](../../glossary.md#log-strike) `k` as \begin{equation} - k = \log{\frac{K}{F}} + k = \log{\frac{K}{F_\tau}} \end{equation} -where $K$ is the strike price and $F$ is the forward price of the underlying asset. +where $K$ is the strike price and $F_\tau$ is the forward price of the underlying asset at time to maturity $\tau$. ::: quantflow.options.bs.black_price diff --git a/docs/api/options/parity.md b/docs/api/options/parity.md new file mode 100644 index 0000000..35f2fa9 --- /dev/null +++ b/docs/api/options/parity.md @@ -0,0 +1,6 @@ +# Put-Call Parity + + +::: quantflow.options.parity.PutCallParity + +::: quantflow.options.parity.PutCallParities diff --git a/docs/api/rates/index.md b/docs/api/rates/index.md index bb62d8e..82514be 100644 --- a/docs/api/rates/index.md +++ b/docs/api/rates/index.md @@ -1 +1,15 @@ # Interest Rates + +The `quantflow.rates` module provides primitives for interest rate modelling: flat rates, yield curves, and curve fitting. + +The central concept is the [discount factor](../../glossary.md#discount-factor) $D_\tau$, the present value of one unit of currency paid at time $\tau$. Every class in this module exposes a `discount_factor` method that computes $D_\tau$ from the configured rate or curve. + +**[Rate](interest_rate.md)** represents a spot or forward interest rate with a chosen compounding frequency (continuous by default) and day count convention. It supports continuous and periodic compounding and can be bootstrapped directly from a spot/forward pair. + +**[YieldCurve](yield_curve.md)** is the abstract base for term-structure models. It defines the interface via `discount_factor` and `instantaneous_forward_rate`, with the two quantities linked by + +\begin{equation} + f(\tau) = -\frac{\partial \ln D_\tau}{\partial \tau} +\end{equation} + +**[NelsonSiegel](nelson_siegel.md)** is a concrete `YieldCurve` implementation that fits a smooth parametric curve to observed zero-coupon rates using the Nelson-Siegel functional form. diff --git a/docs/api/rates/nelson_siegel.md b/docs/api/rates/nelson_siegel.md new file mode 100644 index 0000000..9b30a71 --- /dev/null +++ b/docs/api/rates/nelson_siegel.md @@ -0,0 +1,3 @@ +# Nelson Siegel Curve + +::: quantflow.rates.nelson_siegel.NelsonSiegel diff --git a/docs/glossary.md b/docs/glossary.md index 04880f3..a848a0c 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -43,6 +43,19 @@ of a real-valued random variable $x$ is the function given by where ${\mathbb P}_x$ is the distrubution measure of $x$. +## Discount Factor + +The discount factor $D\left(\tau\right)$ is the present value of one unit of currency paid at [time to maturity](#time-to-maturity-ttm) $\tau$. +It is equal to the price of a zero-coupon bond maturing at $\tau$: a contract that pays exactly 1 at maturity with no intermediate cashflows. + +\begin{equation} + D\left(\tau\right) = e^{-r \tau} +\end{equation} + +where $r$ is the continuously compounded risk-free rate. + +Under a zero interest rate assumption, $D\left(\tau\right) = 1\ \ \ \forall\ \tau$. + ## Feller Condition The Feller condition is a parameter constraint on a square-root diffusion process @@ -71,6 +84,16 @@ condition holds. The `feller_enforce` flag (default `True`) that imposes this as a hard inequality constraint during optimisation. +## Forwards + +The forward price $F$ of an asset at maturity $\tau$ is the price agreed upon today for delivery of the asset at time $\tau$. It is given by the ratio of two discount factors: one for the asset $D_a(\tau)$ and one for the quote $D_q(\tau)$. + +\begin{equation} +F(\tau) = S \cdot \frac{D_a(\tau)}{D_q(\tau)} +\end{equation} + +See [Forwards and Discount Factors](theory/forwards.md) for more details. + ## Forward Space Forward space is the unit-free convention in which option prices are @@ -172,21 +195,26 @@ The [probability density function](https://en.wikipedia.org/wiki/Probability_den ## Put-Call Parity Put-call parity is a no-arbitrage relationship between the prices of European call -and put options with the same strike $K$ and maturity. Denoting forward-space prices -$c = C/F$ and $p = P/F$ (see [Black Pricing](api/options/black.md)), the relationship -reads: +and put options with the same strike $K$ and time to maturity. \begin{equation} - c - p = 1 - \frac{K}{F} = 1 - e^k + C - P = D_q \left(F - K\right) \end{equation} -where $k$ is the [log-strike](#log-strike). -In quoting currency terms, multiplying through by $F$: +where $D_q$ is the [discount factor](#discount-factor) of the quoting asset (generally a currency) +at maturity and $F$ is the forward price +of the underlying asset at maturity. + +Denoting forward-space prices +$c = C/(D_q\ F)$ and $p = P/(D_q\ F)$ (see [Black Pricing](api/options/black.md)), the relationship +reads: \begin{equation} - C - P = F - K + c - p = 1 - \frac{K}{F} = 1 - e^k \end{equation} +where $k$ is the [log-strike](#log-strike). + ## Time To Maturity (TTM) Time to maturity is the time remaining until an option or forward contract expires, diff --git a/docs/theory/forwards.md b/docs/theory/forwards.md new file mode 100644 index 0000000..ff0558a --- /dev/null +++ b/docs/theory/forwards.md @@ -0,0 +1,68 @@ +# Forwards and Discount Factors + +## Discount Factors + +A [discount factor](../glossary.md#discount-factor) $D(\tau)$ is the present value of +one unit of currency delivered at time $\tau$. + +In quantflow, discount factors are provided by a +[YieldCurve][quantflow.rates.yield_curve.YieldCurve]. Different implementations capture +different term structures: a flat zero-rate curve, a fitted +[Nelson-Siegel][quantflow.rates.nelson_siegel.NelsonSiegel] curve, or any custom term +structure. + +## Forward Price + +The forward price of an asset at maturity $\tau$ is defined under the assumption that +two discount factors are available: one for the asset $D_a(\tau)$ and one for the +quote $D_q(\tau)$. + +\begin{equation} +F(\tau) = S \cdot \frac{D_a(\tau)}{D_q(\tau)} +\end{equation} + +where $S$ is the current spot price. + +For an asset that pays no dividends and has no carry costs, the discount factor +for the asset is constant and equal to one. In this case +the forward price is simply given by the discount factor for the quote: + +\begin{equation} +F(\tau) = \frac{S}{D_q(\tau)} +\end{equation} + +When the quote is cash, the forward price is the spot price compounded at the risk-free rate, +which means that forward prices are typically higher than the spot price. + +## Put-Call Parity + +The Put call parity is a fundamental relationship between the prices of European call and put options with the same strike and maturity. +It states that the difference between the call price $C$ and the put price $P$ is equal to the discounted difference between the forward price $F$ and the strike price $K$: + +\begin{equation} +C - P = S \cdot D_a - D_q \cdot K +\end{equation} + +Dividing by the Spot price + +\begin{equation} +\frac{C - P}{S} = D_a - \frac{D_q}{S} \cdot K +\end{equation} + +This is linear in $K$, with slope $-D_q / S$ and intercept $D_a$. + +Fitting a linear regression across multiple strikes at the same maturity therefore +identifies both discount factors simultaneously: $D_q$ from the slope and $D_a$ from +the intercept. + +### Inverse Options + +For inverse options, prices are quoted in units of the underlying asset, which acts as +the quote. The relevant discount factor is therefore $D_a$. Put-call parity becomes: + +\begin{equation} +c - p = D_a \left(1 - \frac{K}{F}\right) = D_a - \frac{D_q}{S} \cdot K +\end{equation} + +This is again linear in $K$, with intercept $D_a$ and slope $-D_q / S$, exactly the same as for regular options. +The same regression therefore identifies both discount factors as before. diff --git a/docs/theory/option_pricing.md b/docs/theory/option_pricing.md index fb6884c..30dfba0 100644 --- a/docs/theory/option_pricing.md +++ b/docs/theory/option_pricing.md @@ -1,24 +1,31 @@ # Option Pricing -We use characteristic function inversion to price European call options on an underlying $S_t = S_0 e^{s_t}$, where $S_0$ is the spot price at time 0. We assume zero interest rates, so the forward equals the spot. The log-return $s_t = x_t - c_t$ is constructed from a driving process $x_t$ and a deterministic [convexity correction](convexity_correction.md) $c_t$ that enforces the martingale condition ${\mathbb E}[e^{s_t}] = 1$. +We use characteristic function inversion to price European call options on an underlying $S_t = F_t e^{s_t}$, where $F_t$ is the forward price at time $t$. +The log-return $s_t = x_t - c_t$ is constructed from a driving process $x_t$ and a deterministic [convexity correction](convexity_correction.md) $c_t$ +that enforces the martingale condition ${\mathbb E}[e^{s_t}] = 1$. ## Call Option -The price $C$ of a call option with strike $K$ is defined as +The price $C$ of a call option with expiry $\tau$ and strike $K$ is defined as \begin{equation} \begin{aligned} -C &= S_0 c_k \\ -k &= \ln\frac{K}{S_0} \\ +C &= D_\tau F_\tau c_k \\ +k &= \ln\frac{K}{F_\tau} \\ c_k &= {\mathbb E}\left[\left(e^{s_t} - e^k\right)^+\right] = \int_{-\infty}^\infty \left(e^s - e^k\right)^+ f_{s_t}(s)\, ds \end{aligned} \label{call-price} \end{equation} -$k$ is the [log-strike](../glossary.md#log-strike) and $f_{s_t}$ is the probability density function of $s_t$. The call price is the discounted expected payoff under the risk-neutral measure, which simplifies to the undiscounted expected payoff when interest rates are zero. +$k$ is the [log-strike](../glossary.md#log-strike) with respect to the forward $F_\tau$ and $f_{s_t}$ is the probability density function of $s_t$. +$D_\tau$ is the [discount factor](../glossary#discount-factor) to time $\tau$, which is 1 under a zero interest rate assumption. -All three methods share this starting point. They all express $c_k$ via the characteristic function $\Phi_{s_t}$, but differ in how the integration contour is chosen, how the payoff is handled, and the discretisation strategy. +## Fourier Inversion Methods + +The key insight is that while the density $f_{s_t}$ may not have a closed form, its [characteristic function](../glossary.md#characteristic-function) $\Phi_{s_t}$ is available analytically for a wide class of stochastic processes. The integral in $\eqref{call-price}$ can therefore be evaluated by inverting $\Phi_{s_t}$ numerically. + +Quantflow implements three Fourier inversion approaches: [Carr and Madan (1999)](../bibliography.md#carr_madan), [Lewis (2001)](../bibliography.md#lewis), and the [COS method](../bibliography.md#cos) (Fang and Oosterlee, 2008). All three share the same starting point and express $c_k$ via $\Phi_{s_t}$, but differ in how the integration contour is chosen, how the payoff is handled, and the discretisation strategy. ## Carr & Madan diff --git a/docs/tutorials/heston_calibration.md b/docs/tutorials/heston_calibration.md new file mode 100644 index 0000000..df3140c --- /dev/null +++ b/docs/tutorials/heston_calibration.md @@ -0,0 +1,152 @@ +# Heston Volatility Model + +## Calibrating the Heston Model + +[HestonCalibration][quantflow.options.calibration.heston.HestonCalibration] fits the +five Heston parameters ($v_0$, $\theta$, $\kappa$, $\sigma$, $\rho$) to the implied +volatility surface using a two-stage optimisation: + +1. **L-BFGS-B** minimises the scalar cost function (sum of squared weighted price + residuals) to reach a good basin of attraction. +2. **Trust-region reflective** (`least_squares` with `method="trf"`) refines the + solution on the residual vector with tight tolerances and enforces parameter bounds. + +Residuals are computed as `weight * (model_call_price - mid_call_price)` where +`mid_call_price` is the average of the bid and ask call prices. + +The weight is $\min(e^{w \cdot m^2}, w_\text{max})$ controlled by +`moneyness_weight` (the coefficient $w$) and `max_cost_weight` (the cap +$w_\text{max}$), with $m = \log(K/F)/\sqrt{T}$ the standardised moneyness. +The quadratic exponent matches the gaussian shape of $1/\nu$ (inverse vega), +so a positive `moneyness_weight` puts wing residuals on the same footing as +ATM ones. The cap prevents a single deep-wing option from dominating the +loss. + +A penalty for violating the Feller condition ($2\kappa\theta \geq \sigma^2$) +is added during stage 1 to keep the variance process well-behaved. + +```python +--8<-- "docs/examples/vol_surface_heston_calibration.py" +``` + +### Output + +--8<-- "docs/examples/output/vol_surface_heston_calibration.out" + +### Calibration Options + +The `moneyness_weight` parameter up-weights far-from-the-money options via +$e^{w \cdot m^2}$ where $m = \log(K/F)/\sqrt{T}$ is the standardised +moneyness. The result is capped at `max_cost_weight` (default 10) so a +single deep-wing option cannot dominate the loss. + +### Plotting the Calibrated Smile + +Use [plot_maturities()][quantflow.options.calibration.base.VolModelCalibration.plot_maturities] +to produce a Plotly figure overlaying market bid/ask implied vols against the model smile +for all maturities at once: + +```python +fig = calibration.plot_maturities(max_moneyness_ttm=1.5, support=101) +fig.write_image("heston_calibrated_smile.png", width=1200) +``` + +The x axis is [moneyness](../glossary.md#moneyness). + +![Heston calibrated smile](../assets/examples/heston_calibrated_smile.png) + +### Model Limitations at Short Maturities + +Inspecting the calibrated smiles across all maturities reveals a systematic pattern: +the Heston model fits long-dated options reasonably well but struggles with short-term +maturities, where the market smile is steeper than the model can reproduce. + +This is a fundamental structural limitation, not a numerical issue. The Heston model +generates an implied volatility smile through two mechanisms: the correlation $\rho$ +between spot and variance (which creates skew) and the volatility-of-variance $\sigma$ +(which inflates the wings). Both effects accumulate diffusively over time. For a maturity +$T$, the smile roughly scales as $\sigma \sqrt{T}$, so as $T \to 0$ the distribution +collapses toward a Gaussian and the smile flattens. + +More precisely, the Heston characteristic function at short maturities satisfies: + +\begin{equation} +\log \phi(u, T) \approx i u \mu T - \tfrac{1}{2} u^2 v_0 T + O(T^2) +\end{equation} + +which is the characteristic function of a Gaussian with variance $v_0 T$. The higher +cumulants that produce skew and excess kurtosis are all $O(T^2)$ or smaller, so they +vanish faster than the Gaussian term as $T \to 0$. + +In practice this means the Heston model essentially reduces to Black-Scholes for +near-expiry options. The market, however, exhibits pronounced short-term skew driven by +jump risk and the market microstructure of short-dated hedging demand. A diffusion-only +model cannot reproduce this behaviour regardless of how its parameters are tuned. + +The natural extension is to add a jump component to the dynamics, which contributes +a term of order $O(T)$ to the cumulants and restores the short-term smile. This is +the motivation for the Heston jump-diffusion model described in the next section. + +## Calibrating the Heston Jump-Diffusion Model + +[HestonJCalibration][quantflow.options.calibration.heston.HestonJCalibration] extends the +Heston calibration with a compound Poisson jump component via the +[HestonJ][quantflow.sp.heston.HestonJ] model. Jumps are drawn from a +[DoubleExponential][quantflow.utils.distributions.DoubleExponential] distribution, +which captures asymmetric jump behaviour common in equity and crypto markets. + +```python +--8<-- "docs/examples/vol_surface_hestonj_calibration.py" +``` + +--8<-- "docs/examples/output/vol_surface_hestonj_calibration.out" + +### Plotting the Calibrated Smile + +```python +fig = calibration.plot_maturities(max_moneyness_ttm=1.5, support=101) +fig.write_image("hestonj_calibrated_smile.png", width=1200) +``` + +![HestonJ calibrated smile](../assets/examples/hestonj_calibrated_smile.png) + +### Remaining Limitations at Short Maturities + +Adding jumps improves the short-term smile significantly compared to plain Heston, but +the fit at the nearest maturities is still imperfect. Several structural reasons combine: + +**Jump parameters are global.** The compound Poisson component has a single intensity +$\lambda$, jump variance, and asymmetry shared across all maturities. Increasing +$\lambda$ to steepen the short-term smile simultaneously distorts the long-term smile, +so the optimizer settles on a compromise. + +**Long maturities dominate the cost function.** They have more liquid strikes and +therefore more data points. The optimizer minimizes total squared residuals across the +whole surface, so short maturities — with fewer strikes — are outvoted and their fit is +systematically sacrificed. + +**The jump distribution is not rich enough.** The short-term smile in crypto is driven +by large, rare, asymmetric events. A [DoubleExponential][quantflow.utils.distributions.DoubleExponential] +with fixed parameters cannot simultaneously match the wing curvature at short and long +maturities. + +The natural next step is a rough volatility model (for example rough Heston with Hurst +parameter $H < \tfrac{1}{2}$). Because the variance process has long memory and does not +behave diffusively at short time scales, rough models produce a steep short-term skew +without requiring jumps, and the skew decays as a power law $T^H$ rather than the +$T^{1/2}$ rate of classical stochastic volatility. + +### Parameter Reference + +The calibrated parameter vector for the jump-diffusion model is: + +| Parameter | Description | +|---|---| +| `vol` | Initial volatility ($\sqrt{v_0}$) | +| `theta` | Long-run volatility ($\sqrt{\theta}$) | +| `kappa` | Mean reversion speed | +| `sigma` | Volatility of variance | +| `rho` | Spot-variance correlation | +| `jump intensity` | Jump arrival rate (jumps per year) | +| `jump variance` | Variance of a single jump | +| `jump asymmetry` | Asymmetry of the jump distribution ([DoubleExponential][quantflow.utils.distributions.DoubleExponential]) | diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 0b129bb..ff01f8e 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -5,6 +5,7 @@ Step-by-step guides for common quantflow workflows. | Tutorial | Description | |---|---| | [Option Pricing](option_pricing.md) | Price a European option with the Black-Scholes and Heston-jump-diffusion models | -| [Volatility Surface](volatility_surface.md) | Fetch live option data, build an implied volatility surface, and calibrate Heston and jump-diffusion models | +| [Volatility Surface](volatility_surface.md) | Fetch live option data, build an implied volatility surface, and extract forwards and discount factors from option prices | +| [Heston Volatility Model](heston_calibration.md) | Calibrate the Heston and Heston-jump-diffusion models to an implied volatility surface | | [SPX Volatility Surface](spx_vol_surface.md) | Build a 3D implied volatility surface for the S&P 500 from a Yahoo Finance option chain | | [BNS Volatility Model](bns_calibration.md) | Calibrate the Barndorff-Nielsen and Shephard stochastic-volatility model to an implied volatility surface | diff --git a/docs/tutorials/volatility_surface.md b/docs/tutorials/volatility_surface.md index b9eb2c9..2249623 100644 --- a/docs/tutorials/volatility_surface.md +++ b/docs/tutorials/volatility_surface.md @@ -1,8 +1,8 @@ # Volatility Surface -This tutorial covers the full workflow for building and calibrating an implied volatility -surface: fetching option quotes from Deribit, inspecting the surface inputs, and -calibrating the Heston and Heston-jump-diffusion models. +This tutorial covers the full workflow for building an implied volatility surface: +fetching option quotes from Deribit, extracting implied forwards and discount factors +from option prices, and inspecting the surface inputs. ## Fetching Data from Deribit @@ -98,153 +98,34 @@ inputs = surface.inputs(converged=True) # VolSurface -> VolSurfaceInputs surface2 = surface_from_inputs(inputs) # VolSurfaceInputs -> VolSurface ``` -## Calibrating the Heston Model - -[HestonCalibration][quantflow.options.calibration.heston.HestonCalibration] fits the -five Heston parameters ($v_0$, $\theta$, $\kappa$, $\sigma$, $\rho$) to the implied -volatility surface using a two-stage optimisation: - -1. **L-BFGS-B** minimises the scalar cost function (sum of squared weighted price - residuals) to reach a good basin of attraction. -2. **Trust-region reflective** (`least_squares` with `method="trf"`) refines the - solution on the residual vector with tight tolerances and enforces parameter bounds. - -Residuals are computed as `weight * (model_call_price - mid_call_price)` where -`mid_call_price` is the average of the bid and ask call prices. - -The weight is $\min(e^{w \cdot m^2}, w_\text{max})$ controlled by -`moneyness_weight` (the coefficient $w$) and `max_cost_weight` (the cap -$w_\text{max}$), with $m = \log(K/F)/\sqrt{T}$ the standardised moneyness. -The quadratic exponent matches the gaussian shape of $1/\nu$ (inverse vega), -so a positive `moneyness_weight` puts wing residuals on the same footing as -ATM ones. The cap prevents a single deep-wing option from dominating the -loss. - -A penalty for violating the Feller condition ($2\kappa\theta \geq \sigma^2$) -is added during stage 1 to keep the variance process well-behaved. - -```python ---8<-- "docs/examples/vol_surface_heston_calibration.py" -``` - -### Output - ---8<-- "docs/examples/output/vol_surface_heston_calibration.out" - -### Calibration Options - -The `moneyness_weight` parameter up-weights far-from-the-money options via -$e^{w \cdot m^2}$ where $m = \log(K/F)/\sqrt{T}$ is the standardised -moneyness. The result is capped at `max_cost_weight` (default 10) so a -single deep-wing option cannot dominate the loss. - -### Plotting the Calibrated Smile - -Use [plot_maturities()][quantflow.options.calibration.base.VolModelCalibration.plot_maturities] -to produce a Plotly figure overlaying market bid/ask implied vols against the model smile -for all maturities at once: - -```python -fig = calibration.plot_maturities(max_moneyness_ttm=1.5, support=101) -fig.write_image("heston_calibrated_smile.png", width=1200) -``` - -The x axis is [moneyness](../glossary.md#moneyness). - -![Heston calibrated smile](../assets/examples/heston_calibrated_smile.png) - -### Model Limitations at Short Maturities - -Inspecting the calibrated smiles across all maturities reveals a systematic pattern: -the Heston model fits long-dated options reasonably well but struggles with short-term -maturities, where the market smile is steeper than the model can reproduce. - -This is a fundamental structural limitation, not a numerical issue. The Heston model -generates an implied volatility smile through two mechanisms: the correlation $\rho$ -between spot and variance (which creates skew) and the volatility-of-variance $\sigma$ -(which inflates the wings). Both effects accumulate diffusively over time. For a maturity -$T$, the smile roughly scales as $\sigma \sqrt{T}$, so as $T \to 0$ the distribution -collapses toward a Gaussian and the smile flattens. - -More precisely, the Heston characteristic function at short maturities satisfies: - -\begin{equation} -\log \phi(u, T) \approx i u \mu T - \tfrac{1}{2} u^2 v_0 T + O(T^2) -\end{equation} - -which is the characteristic function of a Gaussian with variance $v_0 T$. The higher -cumulants that produce skew and excess kurtosis are all $O(T^2)$ or smaller, so they -vanish faster than the Gaussian term as $T \to 0$. - -In practice this means the Heston model essentially reduces to Black-Scholes for -near-expiry options. The market, however, exhibits pronounced short-term skew driven by -jump risk and the market microstructure of short-dated hedging demand. A diffusion-only -model cannot reproduce this behaviour regardless of how its parameters are tuned. - -The natural extension is to add a jump component to the dynamics, which contributes -a term of order $O(T)$ to the cumulants and restores the short-term smile. This is -the motivation for the Heston jump-diffusion model described in the next section. - -## Calibrating the Heston Jump-Diffusion Model - -[HestonJCalibration][quantflow.options.calibration.heston.HestonJCalibration] extends the -Heston calibration with a compound Poisson jump component via the -[HestonJ][quantflow.sp.heston.HestonJ] model. Jumps are drawn from a -[DoubleExponential][quantflow.utils.distributions.DoubleExponential] distribution, -which captures asymmetric jump behaviour common in equity and crypto markets. - -```python ---8<-- "docs/examples/vol_surface_hestonj_calibration.py" -``` - ---8<-- "docs/examples/output/vol_surface_hestonj_calibration.out" - -### Plotting the Calibrated Smile - -```python -fig = calibration.plot_maturities(max_moneyness_ttm=1.5, support=101) -fig.write_image("hestonj_calibrated_smile.png", width=1200) -``` - -![HestonJ calibrated smile](../assets/examples/hestonj_calibrated_smile.png) - -### Remaining Limitations at Short Maturities - -Adding jumps improves the short-term smile significantly compared to plain Heston, but -the fit at the nearest maturities is still imperfect. Several structural reasons combine: - -**Jump parameters are global.** The compound Poisson component has a single intensity -$\lambda$, jump variance, and asymmetry shared across all maturities. Increasing -$\lambda$ to steepen the short-term smile simultaneously distorts the long-term smile, -so the optimizer settles on a compromise. - -**Long maturities dominate the cost function.** They have more liquid strikes and -therefore more data points. The optimizer minimizes total squared residuals across the -whole surface, so short maturities — with fewer strikes — are outvoted and their fit is -systematically sacrificed. - -**The jump distribution is not rich enough.** The short-term smile in crypto is driven -by large, rare, asymmetric events. A [DoubleExponential][quantflow.utils.distributions.DoubleExponential] -with fixed parameters cannot simultaneously match the wing curvature at short and long -maturities. - -The natural next step is a rough volatility model (for example rough Heston with Hurst -parameter $H < \tfrac{1}{2}$). Because the variance process has long memory and does not -behave diffusively at short time scales, rough models produce a steep short-term skew -without requiring jumps, and the skew decays as a power law $T^H$ rather than the -$T^{1/2}$ rate of classical stochastic volatility. - -### Parameter Reference - -The calibrated parameter vector for the jump-diffusion model is: - -| Parameter | Description | -|---|---| -| `vol` | Initial volatility ($\sqrt{v_0}$) | -| `theta` | Long-run volatility ($\sqrt{\theta}$) | -| `kappa` | Mean reversion speed | -| `sigma` | Volatility of variance | -| `rho` | Spot-variance correlation | -| `jump intensity` | Jump arrival rate (jumps per year) | -| `jump variance` | Variance of a single jump | -| `jump asymmetry` | Asymmetry of the jump distribution ([DoubleExponential][quantflow.utils.distributions.DoubleExponential]) | +## Extracting Forwards and Discount Factors + +Pricing an option requires two market inputs beyond the option price itself: the forward +price $F$ of the underlying at expiry, and the discount factor $D$ for that maturity. + +In liquid markets these quantities are directly observable. Futures and forward contracts +give $F$ outright, and interest rate swaps or government bond strips give $D$. In many +option markets, however, neither is quoted directly. Crypto options on Deribit are a +clear example: there is no liquid term structure of interest rates and the forward for +each expiry must be inferred from the options themselves. + +Even when forwards are available, the discount factor used to value options may differ +from the rate implied by the forward-spot basis. For equity options the carry includes +dividends and repo costs that are not captured by a simple interest rate curve. For +crypto inverse options the discount factor reflects funding in the underlying asset +rather than in dollars. + +For these reasons, quantflow can extract $D_q$ and $D_a$ directly from the market prices +of options using put-call parity. The +[calibrate_curves][quantflow.options.surface.GenericVolSurfaceLoader.calibrate_curves] +method supports three modes: + +- **Both curves**: pass a [YieldCurve][quantflow.rates.yield_curve.YieldCurve] type for + both `quote_curve` and `asset_curve`. A single OLS regression per maturity identifies + $D_q$ and $D_a$ simultaneously from the slope and intercept. +- **Asset curve only**: pass a type for `asset_curve` and leave `quote_curve` as `None`. + The existing `quote_curve` on the loader is treated as known and $D_a$ is computed + analytically from each put-call pair using the known $D_q$. +- **Quote curve only**: pass a type for `quote_curve` and leave `asset_curve` as `None`. + The same simultaneous OLS is run but only the quote discount factors are used to fit + the curve. diff --git a/mkdocs.yml b/mkdocs.yml index 564efbe..6fa5c46 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,7 @@ nav: - Black-Scholes: api/options/black.md - Calibration: api/options/calibration.md - Deep IV Factor Model: api/options/divfm.md + - Put-Call Parity: api/options/parity.md - Pricer: api/options/pricer.md - SVI Smile: api/options/svi.md - Volatility Surface: api/options/vol_surface.md @@ -99,6 +100,7 @@ nav: - Rates: - api/rates/index.md - Interest Rate: api/rates/interest_rate.md + - Nelson Siegel Curve: api/rates/nelson_siegel.md - Yield Curve: api/rates/yield_curve.md - Utilities: - api/utils/index.md @@ -111,6 +113,7 @@ nav: - tutorials/index.md - BNS Volatility Model: tutorials/bns_calibration.md - CIR Process: tutorials/cir.md + - Heston Volatility Model: tutorials/heston_calibration.md - Option Pricing: tutorials/option_pricing.md - Pricing Method Comparison: tutorials/pricing_method_comparison.md - SPX Volatility Surface: tutorials/spx_vol_surface.md @@ -119,6 +122,7 @@ nav: - theory/index.md - Characteristic Function: theory/characteristic.md - Convexity Correction: theory/convexity_correction.md + - Forwards and Discount Factors: theory/forwards.md - Inversion: theory/inversion.md - Lévy Process: theory/levy.md - Option Pricing: theory/option_pricing.md diff --git a/quantflow/data/yahoo.py b/quantflow/data/yahoo.py index 302d9a0..bfe9dfa 100644 --- a/quantflow/data/yahoo.py +++ b/quantflow/data/yahoo.py @@ -3,7 +3,8 @@ import gzip import json from dataclasses import dataclass, field -from datetime import timezone +from datetime import date, timezone +from enum import StrEnum from pathlib import Path import pandas as pd @@ -12,6 +13,7 @@ from quantflow.options.inputs import DefaultVolSecurity, OptionType from quantflow.options.surface import VolSurfaceLoader +from quantflow.utils.dates import as_utc from quantflow.utils.numbers import to_decimal @@ -19,13 +21,22 @@ class Yahoo(HttpxClient): """Yahoo Finance API client - Minimal client for fetching option chains used to build volatility surfaces. + Minimal client for fetching historical prices and option chains. - ## Example + ## Examples + + Fetch daily prices for a symbol: ```python from quantflow.data.yahoo import Yahoo + async with Yahoo() as yahoo: + df = await yahoo.prices("AAPL", range="1y") + ``` + + Build a volatility surface from the option chain: + + ```python async with Yahoo() as yahoo: loader = await yahoo.volatility_surface_loader("AAPL") surface = loader.surface() @@ -47,6 +58,21 @@ class Yahoo(HttpxClient): ) _crumb: str | None = None + class freq(StrEnum): + """Yahoo Finance chart intervals""" + + one_min = "1m" + two_min = "2m" + five_min = "5m" + fifteen_min = "15m" + thirty_min = "30m" + one_hour = "1h" + one_day = "1d" + five_day = "5d" + one_week = "1wk" + one_month = "1mo" + three_month = "3mo" + async def option_chain( self, symbol: Annotated[str, Doc("Underlying ticker symbol")], @@ -142,6 +168,60 @@ def loader_from_chain( ) return loader + async def prices( + self, + symbol: Annotated[str, Doc("Ticker symbol")], + *, + interval: Annotated[ + str | freq, Doc("Bar interval — use Yahoo.freq members or a raw string") + ] = freq.one_day, + from_date: Annotated[date | None, Doc("Start date (inclusive)")] = None, + to_date: Annotated[date | None, Doc("End date (inclusive)")] = None, + range: Annotated[ + str | None, + Doc( + "Shorthand period when dates are omitted: '1mo', '3mo', '6mo', " + "'1y', '2y', '5y', 'ytd', 'max', etc." + ), + ] = None, + ) -> pd.DataFrame: + """Historical OHLCV prices for `symbol`. + + Returns a DataFrame with columns `timestamp`, `open`, `high`, `low`, + `close`, `volume`, and `adj_close` (when available). + + Pass `from_date` / `to_date` for a specific window, or `range` for a + shorthand period. When neither is given Yahoo defaults to one month. + """ + params: dict = {"interval": str(interval)} + if from_date: + params["period1"] = int(as_utc(from_date).timestamp()) + if to_date: + params["period2"] = int(as_utc(to_date).timestamp()) + if range and not from_date and not to_date: + params["range"] = range + data = await self.get( + f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}", + params=params, + ) + result = data["chart"]["result"][0] + timestamps = result.get("timestamps") or result.get("timestamp", []) + quote = result["indicators"]["quote"][0] + adj = result["indicators"].get("adjclose", [{}])[0] + df = pd.DataFrame( + { + "timestamp": pd.to_datetime(timestamps, unit="s", utc=True), + "open": quote.get("open"), + "high": quote.get("high"), + "low": quote.get("low"), + "close": quote.get("close"), + "volume": quote.get("volume"), + } + ) + if "adjclose" in adj: + df["adj_close"] = adj["adjclose"] + return df + async def save_fixture( self, symbol: Annotated[str, Doc("Underlying ticker symbol")], diff --git a/quantflow/options/bs.py b/quantflow/options/bs.py index cf2df1c..363a741 100644 --- a/quantflow/options/bs.py +++ b/quantflow/options/bs.py @@ -165,13 +165,13 @@ def black_price( ), ], ) -> FloatArray: - r"""Calculate the Black call/put option prices in forward terms + r"""Calculate the undiscounted Black call/put option prices in forward terms from the following params $$ \begin{align} - c &= \frac{C}{F} = N(d1) - e^k N(d2) \\ - p &= \frac{P}{F} = -N(-d1) + e^k N(-d2) \\ + c &= \frac{C}{D_\tau F_\tau} = N(d1) - e^k N(d2) \\ + p &= \frac{P}{D_\tau F_\tau} = -N(-d1) + e^k N(-d2) \\ d1 &= \frac{-k + \frac{\sigma^2 t}{2}}{\sigma \sqrt{t}} \\ d2 &= d1 - \sigma \sqrt{t} \end{align} @@ -180,7 +180,8 @@ def black_price( The results are option prices divided by the forward price also known as option prices in forward terms. These are non-dimensional prices that can be easily converted to actual option prices by multiplying with the - forward price of the underlying asset. + forward price of the underlying asset at time to maturity $\tau$ + and a suitable discount factor if interest rates are non-zero. """ sig2 = sigma * sigma * ttm sig = np.sqrt(sig2) diff --git a/quantflow/options/parity.py b/quantflow/options/parity.py new file mode 100644 index 0000000..da7b64f --- /dev/null +++ b/quantflow/options/parity.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +from decimal import Decimal +from typing import Any, Self + +import numpy as np +from pydantic import BaseModel, Field + +from quantflow.utils.price import Price +from quantflow.utils.types import FloatArray + + +class DiscountPair(BaseModel, frozen=True): + asset_discount: float = Field( + description="Discount factor for the underlying asset" + ) + quote_discount: float = Field(description="Discount factor for the option quote") + + +class PutCallParity(BaseModel, frozen=True): + """A matched put-call parity at a single strike, + used for discount curve calibration.""" + + strike: Decimal = Field(description="Strike price") + call: Price = Field(description="Call option bid/ask prices") + put: Price = Field(description="Put option bid/ask prices") + inverse: bool = Field(default=False, description="Whether the option is inverse") + + @property + def bid(self) -> Decimal: + """Lower bound of the call-put price difference""" + return self.call.bid - self.put.ask + + @property + def ask(self) -> Decimal: + """Upper bound of the call-put price difference""" + return self.call.ask - self.put.bid + + @property + def mid(self) -> Decimal: + """Midpoint of the call-put price difference""" + return (self.bid + self.ask) / 2 + + @property + def spread(self) -> Decimal: + """Bid-ask spread of the call-put price difference""" + return self.ask - self.bid + + +class PutCallParities(BaseModel, frozen=True): + """A collection of put-call parities for a given maturity""" + + parities: list[PutCallParity] = Field(description="List of put-call parities") + spot: Decimal = Field(description="Spot price of the underlying asset") + inverse: bool = Field(default=False, description="Whether the options are inverse") + + @classmethod + def from_parities(cls, parities: list[PutCallParity], spot: Decimal) -> Self: + inverse = any(p.inverse for p in parities) + return cls(parities=parities, spot=spot, inverse=inverse) + + def regressand(self) -> FloatArray: + """Calculate the regressand for put-call parity regression. + + For direct options, the regressand is (C - P) / S, while for inverse + options it is simply c - p. + """ + scale = self.spot if not self.inverse else Decimal(1) + return np.asarray([float(p.mid / scale) for p in self.parities]) + + def regressor(self) -> FloatArray: + """Calculate the regressor for put-call parity regression, + which is the strike price divided by the spot price. + """ + return np.asarray([float(p.strike / self.spot) for p in self.parities]) + + def fit_discounts( + self, + dq: float | None = None, + da: float | None = None, + constrained: bool = False, + ) -> DiscountPair | None: + """Return the fitted discount factors, or None if the result is invalid. + + Both direct and inverse options satisfy the same normalized equation + y = Da - (Dq/S) * K, where y = mid/S for direct and y = mid for inverse. + + When both known values are None a full OLS is run. + When one is provided the other is solved analytically as the mean over pairs. + When constrained is True, discount factors are bounded to (0, 1]. + """ + if not self.parities: + return None + ys = self.regressand() + xs = self.regressor() + if dq is not None: + if da is not None: + return DiscountPair(asset_discount=da, quote_discount=dq) + da = float(np.mean(ys + dq * xs)) + elif da is not None: + dq = float(np.mean((da - ys) / xs)) + elif constrained: + from scipy.optimize import lsq_linear + + A = np.column_stack([np.ones(len(xs)), xs]) + # alpha = Da in (0, 1], beta = -Dq in [-1, 0) + result = lsq_linear(A, ys, bounds=([0, -1], [1, 0])) + da, dq = float(result.x[0]), -float(result.x[1]) + else: + beta, alpha = np.polyfit(xs, ys, 1) + da, dq = float(alpha), -float(beta) + upper = 1.0 if constrained else float("inf") + if not (0 < dq <= upper and 0 < da <= upper): + return None + return DiscountPair(asset_discount=da, quote_discount=dq) + + def plot( + self, + dq: float | None = None, + da: float | None = None, + constrained: bool = False, + ) -> Any: + """Plot the normalized put-call parity data and the fitted regression line.""" + from quantflow.utils.plot import check_plotly + + check_plotly() + import plotly.graph_objects as go + + xs = self.regressor() + ys = self.regressand() + discounts = self.fit_discounts(dq=dq, da=da, constrained=constrained) + fig = go.Figure() + fig.add_trace( + go.Scatter(x=xs, y=ys, mode="markers", name="market", marker_size=10) + ) + if discounts is not None: + x_range = np.linspace(xs.min(), xs.max(), 100) + y_fit = discounts.asset_discount - discounts.quote_discount * x_range + fig.add_trace(go.Scatter(x=x_range, y=y_fit, mode="lines", name="fit")) + y_label = "c - p" if self.inverse else "(C - P) / S" + return fig.update_layout(xaxis_title="K / S", yaxis_title=y_label) diff --git a/quantflow/options/surface.py b/quantflow/options/surface.py index c6f0293..937d032 100644 --- a/quantflow/options/surface.py +++ b/quantflow/options/surface.py @@ -14,9 +14,11 @@ from typing_extensions import Annotated, Doc from quantflow.rates.interest_rate import Rate +from quantflow.rates.yield_curve import NoDiscount, YieldCurve from quantflow.utils import plot from quantflow.utils.dates import utcnow from quantflow.utils.numbers import ( + ONE, ZERO, DecimalNumber, Number, @@ -27,6 +29,7 @@ to_decimal, to_decimal_or_none, ) +from quantflow.utils.price import Price from quantflow.utils.types import FloatArray from .bs import black_price, implied_black_volatility @@ -42,6 +45,7 @@ VolSurfaceInputs, VolSurfaceSecurity, ) +from .parity import PutCallParities, PutCallParity from .svi import SVI INITIAL_VOL = 0.5 @@ -71,44 +75,26 @@ class OptionSelection(enum.Enum): """Select all options regardless of their moneyness""" -class Price(BaseModel, Generic[S]): +class SecurityPrice(Price, Generic[S]): """Represents the bid/ask price of a security, which can be a spot price, forward price or option price """ security: S = Field(description="The underlying security of the price") - bid: DecimalNumber = Field(description="Bid price") - ask: DecimalNumber = Field(description="Ask price") - - @property - def mid(self) -> Decimal: - """Calculate the mid price by averaging the bid and ask prices""" - return (self.bid + self.ask) / 2 - - @property - def spread(self) -> Decimal: - """Calculate the bid-ask spread""" - return self.ask - self.bid - - @property - def bp_spread(self) -> Decimal: - """Bid-ask spread in basis points, calculated as spread divided by mid - price and multiplied by 10000""" - mid = self.mid - if mid > ZERO: - return round(10000 * self.spread / mid, 2) - else: - return Decimal("inf") - - -class SpotPrice(Price[S]): - """Represents the spot bid/ask price of an underlying asset""" - open_interest: DecimalNumber = Field( default=ZERO, description="Open interest of the spot price" ) volume: DecimalNumber = Field(default=ZERO, description="Total volume traded") + def is_valid(self) -> bool: + """Check if the forward price is valid, which means that the bid and ask + are positive and the bid is less than or equal to the ask""" + return self.bid > ZERO and self.ask > ZERO and super().is_valid() + + +class SpotPrice(SecurityPrice[S]): + """Represents the spot bid/ask price of an underlying asset""" + def inputs(self) -> SpotInput: return SpotInput( bid=self.bid, @@ -118,15 +104,11 @@ def inputs(self) -> SpotInput: ) -class FwdPrice(Price[S]): +class FwdPrice(SecurityPrice[S]): """Represents the forward bid/ask price of an underlying asset at a specific maturity""" maturity: datetime = Field(description="Maturity date of the forward price") - open_interest: DecimalNumber = Field( - default=ZERO, description="Open interest of the forward price" - ) - volume: DecimalNumber = Field(default=ZERO, description="Total volume traded") def inputs(self) -> ForwardInput: return ForwardInput( @@ -137,11 +119,6 @@ def inputs(self) -> ForwardInput: volume=self.volume, ) - def is_valid(self) -> bool: - """Check if the forward price is valid, which means that the bid and ask - are positive and the bid is less than or equal to the ask""" - return self.bid > ZERO and self.ask > ZERO and self.bid <= self.ask - class ImpliedFwdPrice(FwdPrice[S]): """Represents the implied forward price of an underlying asset at a specific @@ -513,6 +490,10 @@ def spread(self) -> Decimal: """Calculate the bid-ask spread""" return self.ask.price - self.bid.price + def price(self) -> Price: + """Convert the option prices to a Price object""" + return Price(bid=self.bid.price, ask=self.ask.price) + def iv_bid_ask_spread(self) -> float: """Calculate the bid-ask spread of the implied volatility""" return self.ask.implied_vol - self.bid.implied_vol @@ -583,11 +564,31 @@ class Strike(BaseModel, Generic[S]): default=None, description="Put option prices for the strike" ) + def put_call_parity(self) -> PutCallParity | None: + """Return a [PutCallParity][quantflow.rates.calibrator.PutCallParity] for this + strike, or None if either the call or the put are not available.""" + if self.call is None or self.put is None: + return None + return PutCallParity( + strike=self.strike, + call=self.call.price(), + put=self.put.price(), + inverse=self.call.meta.inverse, + ) + def implied_forward( self, tick_size: Annotated[ Decimal | None, Doc("Tick size for rounding the implied forward bid/ask") ] = None, + df: Annotated[ + Decimal, + Doc( + "Discount factor at this maturity. " + "Defaults to 1 (no discounting). When set, option prices are " + "deflated by $D$ before applying put-call parity." + ), + ] = ONE, ) -> ImpliedFwdPrice[S] | None: r"""Extract the implied forward price from put-call parity. @@ -599,15 +600,17 @@ def implied_forward( put-call parity reads \begin{equation} - F = \frac{K}{1 - c + p} + F = \frac{K}{1 - (c - p) / D} \end{equation} For non-inverse options (prices quoted in the quote currency) \begin{equation} - F = K + C - P + F = K + \frac{C - P}{D} \end{equation} + where $D$ is the discount factor (default 1, i.e. no discounting). + Returns None when the strike does not have both a call and a put, or when the denominator is non-positive (arbitrage condition violated). """ @@ -616,15 +619,15 @@ def implied_forward( cp_bid = self.call.bid.price - self.put.ask.price cp_ask = self.call.ask.price - self.put.bid.price if self.call.meta.inverse: - d_bid = 1 - cp_bid - d_ask = 1 - cp_ask + d_bid = ONE - cp_bid / df + d_ask = ONE - cp_ask / df if d_bid <= ZERO or d_ask <= ZERO: return None bid = self.strike / d_bid ask = self.strike / d_ask else: - bid = self.strike + cp_bid - ask = self.strike + cp_ask + bid = self.strike + cp_bid / df + ask = self.strike + cp_ask / df if bid <= ZERO or ask <= ZERO: return None if bid > ask: @@ -1388,14 +1391,25 @@ def cross_section( Decimal | None, Doc("Tick size for rounding implied forward bid/ask prices"), ] = None, + yield_curve: Annotated[ + YieldCurve | None, + Doc( + "Yield curve used to compute the per-maturity discount factor " + "applied in put-call parity. When None, no discounting is applied." + ), + ] = None, ) -> VolCrossSection[S] | None: strikes = [] implied_forwards = [] + ref = ref_date or utcnow() + ttm = self.day_counter.dcf(ref, self.maturity) + yield_curve = yield_curve or NoDiscount() + df = yield_curve.discount_factor(ttm) for strike in sorted(self.strikes): sk = self.strikes[strike] if sk.call is None and sk.put is None: continue - if implied_forward := sk.implied_forward(tick_size=tick_size): + if implied_forward := sk.implied_forward(tick_size=tick_size, df=df): implied_forwards.append(implied_forward) strikes.append(sk) forward = self.forward @@ -1421,6 +1435,30 @@ def cross_section( else None ) + def put_call_parities( + self, + spot: Annotated[Decimal, Doc("Spot price of the underlying asset")], + *, + max_pairs: Annotated[ + int, Doc("Maximum number of put-call pairs to consider") + ] = 10, + ) -> PutCallParities: + """Return a list of the most liquid + [PutCallParity][quantflow.options.parity.PutCallParities] + from a cross-section loader. + + Liquidity is determined by the bid-ask spread of the put-call parity price. + """ + parities = sorted( + ( + p + for sk in self.strikes.values() + if (p := sk.put_call_parity()) is not None + ), + key=lambda p: p.spread, + )[:max_pairs] + return PutCallParities.from_parities(parities, spot) + class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=True): """Helper class to build a volatility surface from a list of securities @@ -1461,9 +1499,35 @@ class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=Tru exclude_volume: DecimalNumber | None = Field( default=None, description="Exclude options with volume at or below this value" ) + quote_curve: YieldCurve = Field( + default_factory=NoDiscount, + description=( + "Discount curve for the quote currency $D_q$. " + "Set by calling calibrate_curves()." + ), + ) + asset_curve: YieldCurve = Field( + default_factory=NoDiscount, + description=( + "Discount curve for the asset $D_a$. " "Set by calling calibrate_curves()." + ), + ) + + def ref_date( + self, + ref_date: Annotated[ + datetime | None, Doc("Reference date for the volatility surface") + ] = None, + ) -> datetime: + """Reference date for the volatility surface, taken as the earliest maturity + or the provided ref_date if it's earlier""" + if ref_date is not None: + return ref_date + return utcnow() def get_or_create_maturity( - self, maturity: Annotated[datetime, Doc("Maturity date for the options")] + self, + maturity: Annotated[datetime, Doc("Maturity date for the options")], ) -> VolCrossSectionLoader[S]: """Get or create a [VolCrossSectionLoader][quantflow.options.surface.VolCrossSectionLoader] @@ -1565,6 +1629,7 @@ def surface( ref_date=ref_date, previous_forward=previous_forward, tick_size=self.tick_size_forwards, + yield_curve=self.quote_curve, ): previous_forward = section.forward.mid maturities.append(section) @@ -1578,6 +1643,101 @@ def surface( tick_size_options=self.tick_size_options, ) + def calibrate_curves( + self, + *, + quote_curve: Annotated[ + type[YieldCurve] | None, + Doc( + "YieldCurve type to fit the quote currency discount curve $D_q$ " + "from option prices. When None the current quote_curve is unchanged." + ), + ] = None, + asset_curve: Annotated[ + type[YieldCurve] | None, + Doc( + "YieldCurve type to fit the asset discount curve $D_a$ " + "from option prices. When None the current asset_curve is unchanged." + ), + ] = None, + ref_date: Annotated[ + datetime | None, Doc("Reference date for time to maturity calculations") + ] = None, + max_pairs: Annotated[ + int, Doc("Maximum number of put-call pairs to use per maturity") + ] = 10, + ) -> None: + """Calibrate the quote and/or asset discount curves from option prices. + + Three modes are supported: + + Both curves: pass a type for both `quote_curve` and `asset_curve`. + A single OLS regression per maturity identifies $D_q$ and $D_a$ simultaneously. + + Asset only: pass a type for `asset_curve`, leave `quote_curve` as None. + The existing `quote_curve` is treated as known and $D_a$ is solved analytically. + + Quote only: pass a type for `quote_curve`, leave `asset_curve` as None. + The existing `asset_curve` is treated as known and $D_q$ is solved analytically. + """ + ttms, rates_q, rates_a = self.collect_rates( + fit_quote_curve=quote_curve is not None, + fit_asset_curve=asset_curve is not None, + ref_date=ref_date, + max_pairs=max_pairs, + ) + if quote_curve is not None: + self.quote_curve = quote_curve.calibrate(ttms, rates_q) + if asset_curve is not None: + self.asset_curve = asset_curve.calibrate(ttms, rates_a) + + def collect_rates( + self, + *, + fit_quote_curve: Annotated[ + bool, Doc("Whether to fit the quote discount curve $D_q$") + ] = True, + fit_asset_curve: Annotated[ + bool, Doc("Whether to fit the asset discount curve $D_a$") + ] = True, + ref_date: Annotated[ + datetime | None, Doc("Reference date for time to maturity calculations") + ] = None, + max_pairs: Annotated[ + int, Doc("Maximum number of put-call pairs to use per maturity") + ] = 10, + ) -> tuple[list[float], list[float], list[float]]: + """Collect per-maturity continuously compounded rates from put-call parity.""" + if not self.spot or self.spot.mid == ZERO: + raise ValueError("No spot price provided") + spot = self.spot.mid + ttms: list[float] = [] + rates_q: list[float] = [] + rates_a: list[float] = [] + ref_date = self.ref_date(ref_date=ref_date) + for maturity, section in sorted(self.maturities.items()): + ttm = self.day_counter.dcf(ref_date, maturity) + if ttm <= 0: + continue + parities = section.put_call_parities(spot, max_pairs=max_pairs) + dq = ( + None + if fit_quote_curve + else float(self.quote_curve.discount_factor(ttm)) + ) + da = ( + None + if fit_asset_curve + else float(self.asset_curve.discount_factor(ttm)) + ) + d = parities.fit_discounts(dq=dq, da=da) + if d is None: + continue + ttms.append(ttm) + rates_q.append(-math.log(d.quote_discount) / ttm) + rates_a.append(-math.log(d.asset_discount) / ttm) + return ttms, rates_q, rates_a + class VolSurfaceLoader(GenericVolSurfaceLoader[DefaultVolSecurity]): """Helper class to build a volatility surface from a list of securities diff --git a/quantflow/rates/interest_rate.py b/quantflow/rates/interest_rate.py index c083a28..e588869 100644 --- a/quantflow/rates/interest_rate.py +++ b/quantflow/rates/interest_rate.py @@ -24,6 +24,15 @@ class Rate(BaseModel, arbitrary_types_allowed=True): default=DayCounter.ACTACT, description="Day count convention to use when calculating time to maturity", ) + ttm: float = Field( + default=0.0, + ge=0.0, + description=( + "Time to maturity for the rate, used to calculate discount factors. " + "Expressed in years. when 0 it is a spot rate, when > 0 " + "it is a forward rate." + ), + ) frequency: Period | None = Field( default=None, description=( diff --git a/quantflow/rates/nelson_siegel.py b/quantflow/rates/nelson_siegel.py index fceb18b..a44a0ce 100644 --- a/quantflow/rates/nelson_siegel.py +++ b/quantflow/rates/nelson_siegel.py @@ -6,7 +6,7 @@ from numpy.typing import ArrayLike from pydantic import Field from scipy.optimize import minimize_scalar -from typing_extensions import Annotated, Doc +from typing_extensions import Annotated, Doc, Self from quantflow.utils.numbers import ONE, Number, to_decimal @@ -68,21 +68,21 @@ def discount_factor(self, ttm: Number) -> Decimal: return (-zero_coupon_rate * ttmd).exp() @classmethod - def fit( + def calibrate( cls, ttm: Annotated[ ArrayLike, Doc("times to maturity in years (1-D, length >= 3)"), ], rates: Annotated[ - ArrayLike, Doc("observed zero-coupon rates, same length as ttm") + ArrayLike, Doc("observed continuously compounded rates, same length as ttm") ], lambda_bounds: Annotated[ tuple[float, float], Doc("search bounds for the decay parameter $\\lambda$"), ] = (0.01, 10.0), - ) -> NelsonSiegel: - r"""Fit a Nelson-Siegel curve to observed zero-coupon rates. + ) -> Self: + r"""Fit a Nelson-Siegel curve to observed continuously compounded rates. Uses a profile OLS approach: for each candidate $\lambda$ the betas are solved exactly via least squares, so only a 1-D scalar minimisation over diff --git a/quantflow/rates/yield_curve.py b/quantflow/rates/yield_curve.py index 29d292e..4aa1476 100644 --- a/quantflow/rates/yield_curve.py +++ b/quantflow/rates/yield_curve.py @@ -1,7 +1,15 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from decimal import Decimal +from typing import Any +from numpy.typing import ArrayLike from pydantic import BaseModel +from typing_extensions import Annotated, Doc, Self + +from quantflow.utils import plot +from quantflow.utils.numbers import ONE, ZERO class YieldCurve(BaseModel, ABC, extra="forbid"): @@ -35,3 +43,59 @@ def discount_factor(self, ttm: float) -> Decimal: where $f(\tau)$ is the instantaneous forward rate for a given time to maturity $\tau$. """ + + @classmethod + @abstractmethod + def calibrate(cls, ttm: ArrayLike, rates: ArrayLike) -> Self: + """Fit the yield curve to continuously compounded rates. + + Parameters + ---------- + ttm: + Times to maturity in years. + rates: + Continuously compounded rates, same length as ttm (e.g. 0.05 for 5%). + """ + + def continuously_compounded_rate(self, ttm: float) -> Decimal: + r"""Calculate the continuously compounded rate for a given time to maturity + + The continuously compounded rate is related to the discount factor + by the following formula: + + \begin{equation} + r(\tau) = -\frac{\ln D(\tau)}{\tau} + \end{equation} + + where $D(\tau)$ is the discount factor for a given time to maturity $\tau$. + """ + if ttm <= 0: + return self.instanteous_forward_rate(0) + else: + return -self.discount_factor(float(ttm)).ln() / Decimal(ttm) + + def plot( + self, + ttm_max: Annotated[float, Doc("Maximum time to maturity in years")] = 10.0, + n: Annotated[int, Doc("Number of points to evaluate")] = 200, + **kwargs: Any, + ) -> Any: + """Plot the continuously compounded rate vs time to maturity. + + Requires plotly to be installed. + """ + return plot.plot_yield_curve(self, ttm_max=ttm_max, n=n, **kwargs) + + +class NoDiscount(YieldCurve): + """Flat yield curve with zero rates (discount factor is always 1).""" + + def instanteous_forward_rate(self, ttm: float) -> Decimal: + return ZERO + + def discount_factor(self, ttm: float) -> Decimal: + return ONE + + @classmethod + def calibrate(cls, ttm: ArrayLike, rates: ArrayLike) -> Self: + return cls() diff --git a/quantflow/utils/plot.py b/quantflow/utils/plot.py index 8e8eb88..e99aa2a 100644 --- a/quantflow/utils/plot.py +++ b/quantflow/utils/plot.py @@ -1,12 +1,16 @@ import os -from typing import Any +from typing import TYPE_CHECKING, Any +import numpy as np import pandas as pd from scipy.stats import norm from .marginal import Marginal1D from .types import FloatArray +if TYPE_CHECKING: + from quantflow.rates.yield_curve import YieldCurve + PLOTLY_THEME = os.environ.get("PLOTLY_THEME", "plotly_dark") try: @@ -214,6 +218,28 @@ def plot3d( return fig +def plot_yield_curve( + curve: YieldCurve, + ttm_max: float = 10.0, + n: int = 200, + **kwargs: Any, +) -> Any: + check_plotly() + ttms = np.linspace(0.01, ttm_max, n) + rates = [float(curve.continuously_compounded_rate(t)) for t in ttms] + df = pd.DataFrame({"ttm": ttms, "rate": rates}) + return px.line( + df, + x="ttm", + y="rate", + labels={ + "ttm": "time to maturity (years)", + "rate": "continuously compounded rate", + }, + **kwargs, + ) + + def candlestick_plot(df: pd.DataFrame, slider: bool = True) -> Any: fig = go.Figure( data=go.Candlestick( diff --git a/quantflow/utils/price.py b/quantflow/utils/price.py new file mode 100644 index 0000000..d7d02b4 --- /dev/null +++ b/quantflow/utils/price.py @@ -0,0 +1,37 @@ +from pydantic import BaseModel, Field + +from .numbers import ZERO, Decimal, DecimalNumber + + +class Price(BaseModel): + """Represents the bid/ask price of a security, + which can be a spot price, forward price or option price + """ + + bid: DecimalNumber = Field(description="Bid price") + ask: DecimalNumber = Field(description="Ask price") + + @property + def mid(self) -> Decimal: + """Calculate the mid price by averaging the bid and ask prices""" + return (self.bid + self.ask) / 2 + + @property + def spread(self) -> Decimal: + """Calculate the bid-ask spread""" + return self.ask - self.bid + + @property + def bp_spread(self) -> Decimal: + """Bid-ask spread in basis points, calculated as spread divided by mid + price and multiplied by 10000""" + mid = self.mid + if mid > ZERO: + return round(10000 * self.spread / mid, 2) + else: + return Decimal("inf") + + def is_valid(self) -> bool: + """Check if the price is valid, which means the bid is less than + or equal to the ask""" + return self.bid <= self.ask diff --git a/quantflow_tests/test_rates.py b/quantflow_tests/test_rates.py index 0b947bb..d24fee5 100644 --- a/quantflow_tests/test_rates.py +++ b/quantflow_tests/test_rates.py @@ -186,7 +186,7 @@ def test_nelson_siegel_fit_recovers_parameters() -> None: rates = np.array([float(ns_true.discount_factor(t)) for t in ttm]) # convert discount factors back to zero rates for fitting zero_rates = -np.log(rates) / ttm - ns_fit = NelsonSiegel.fit(ttm, zero_rates) + ns_fit = NelsonSiegel.calibrate(ttm, zero_rates) for t in [1.0, 2.0, 5.0]: assert float(ns_fit.discount_factor(t)) == pytest.approx( float(ns_true.discount_factor(t)), rel=1e-4 @@ -196,7 +196,7 @@ def test_nelson_siegel_fit_recovers_parameters() -> None: def test_nelson_siegel_fit_flat_curve() -> None: ttm = np.array([0.5, 1.0, 2.0, 5.0, 10.0]) rates = np.full_like(ttm, 0.05) - ns = NelsonSiegel.fit(ttm, rates) + ns = NelsonSiegel.calibrate(ttm, rates) for t in ttm: assert float(ns.discount_factor(t)) == pytest.approx( math.exp(-0.05 * t), rel=1e-4 From b10c5b292390d2c6a3fffcddab2611d81e14d9b5 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 17 May 2026 18:39:58 +0100 Subject: [PATCH 2/8] Add constraints --- app/volatility_surface.py | 6 +++++ quantflow/options/parity.py | 45 +++++++++++++++++++++++------------- quantflow/options/surface.py | 10 ++++++-- quantflow/utils/plot.py | 2 +- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/app/volatility_surface.py b/app/volatility_surface.py index 5a133b9..efda531 100644 --- a/app/volatility_surface.py +++ b/app/volatility_surface.py @@ -150,6 +150,12 @@ def _(da, p): return +@app.cell +def _(loader): + loader.collect_rates() + return + + @app.cell def _(): return diff --git a/quantflow/options/parity.py b/quantflow/options/parity.py index da7b64f..2baf68e 100644 --- a/quantflow/options/parity.py +++ b/quantflow/options/parity.py @@ -6,6 +6,7 @@ import numpy as np from pydantic import BaseModel, Field +from quantflow.utils.numbers import ZERO, Number, to_decimal from quantflow.utils.price import Price from quantflow.utils.types import FloatArray @@ -51,13 +52,21 @@ class PutCallParities(BaseModel, frozen=True): """A collection of put-call parities for a given maturity""" parities: list[PutCallParity] = Field(description="List of put-call parities") - spot: Decimal = Field(description="Spot price of the underlying asset") + spot: Decimal = Field(ge=ZERO, description="Spot price of the underlying asset") + ttm: Decimal = Field(gt=ZERO, description="Time to maturity in years") inverse: bool = Field(default=False, description="Whether the options are inverse") @classmethod - def from_parities(cls, parities: list[PutCallParity], spot: Decimal) -> Self: + def from_parities( + cls, parities: list[PutCallParity], spot: Number, ttm: Number + ) -> Self: inverse = any(p.inverse for p in parities) - return cls(parities=parities, spot=spot, inverse=inverse) + return cls( + parities=parities, + spot=to_decimal(spot), + ttm=to_decimal(ttm), + inverse=inverse, + ) def regressand(self) -> FloatArray: """Calculate the regressand for put-call parity regression. @@ -78,39 +87,40 @@ def fit_discounts( self, dq: float | None = None, da: float | None = None, - constrained: bool = False, + min_rate_q: float = 0.0, + min_rate_a: float = 0.0, ) -> DiscountPair | None: """Return the fitted discount factors, or None if the result is invalid. Both direct and inverse options satisfy the same normalized equation y = Da - (Dq/S) * K, where y = mid/S for direct and y = mid for inverse. - When both known values are None a full OLS is run. + When both known values are None a full OLS is run via constrained least squares. When one is provided the other is solved analytically as the mean over pairs. - When constrained is True, discount factors are bounded to (0, 1]. + Discount factors are bounded by D <= exp(-min_rate * ttm), so min_rate=0 + enforces D <= 1 (non-negative rates). """ if not self.parities: return None ys = self.regressand() xs = self.regressor() + ttm = float(self.ttm) + max_dq = float(np.exp(-min_rate_q * ttm)) + max_da = float(np.exp(-min_rate_a * ttm)) if dq is not None: if da is not None: return DiscountPair(asset_discount=da, quote_discount=dq) da = float(np.mean(ys + dq * xs)) elif da is not None: dq = float(np.mean((da - ys) / xs)) - elif constrained: + else: from scipy.optimize import lsq_linear A = np.column_stack([np.ones(len(xs)), xs]) - # alpha = Da in (0, 1], beta = -Dq in [-1, 0) - result = lsq_linear(A, ys, bounds=([0, -1], [1, 0])) + # alpha = Da in (0, max_da], beta = -Dq in [-max_dq, 0) + result = lsq_linear(A, ys, bounds=([0, -max_dq], [max_da, 0])) da, dq = float(result.x[0]), -float(result.x[1]) - else: - beta, alpha = np.polyfit(xs, ys, 1) - da, dq = float(alpha), -float(beta) - upper = 1.0 if constrained else float("inf") - if not (0 < dq <= upper and 0 < da <= upper): + if not (0 < dq <= max_dq and 0 < da <= max_da): return None return DiscountPair(asset_discount=da, quote_discount=dq) @@ -118,7 +128,8 @@ def plot( self, dq: float | None = None, da: float | None = None, - constrained: bool = False, + min_rate_q: float = 0.0, + min_rate_a: float = 0.0, ) -> Any: """Plot the normalized put-call parity data and the fitted regression line.""" from quantflow.utils.plot import check_plotly @@ -128,7 +139,9 @@ def plot( xs = self.regressor() ys = self.regressand() - discounts = self.fit_discounts(dq=dq, da=da, constrained=constrained) + discounts = self.fit_discounts( + dq=dq, da=da, min_rate_q=min_rate_q, min_rate_a=min_rate_a + ) fig = go.Figure() fig.add_trace( go.Scatter(x=xs, y=ys, mode="markers", name="market", marker_size=10) diff --git a/quantflow/options/surface.py b/quantflow/options/surface.py index 937d032..70a1db1 100644 --- a/quantflow/options/surface.py +++ b/quantflow/options/surface.py @@ -1439,6 +1439,9 @@ def put_call_parities( self, spot: Annotated[Decimal, Doc("Spot price of the underlying asset")], *, + ref_date: Annotated[ + datetime | None, Doc("Reference date for time to maturity calculation") + ] = None, max_pairs: Annotated[ int, Doc("Maximum number of put-call pairs to consider") ] = 10, @@ -1449,6 +1452,7 @@ def put_call_parities( Liquidity is determined by the bid-ask spread of the put-call parity price. """ + ttm = self.day_counter.dcf(ref_date or utcnow(), self.maturity) parities = sorted( ( p @@ -1457,7 +1461,7 @@ def put_call_parities( ), key=lambda p: p.spread, )[:max_pairs] - return PutCallParities.from_parities(parities, spot) + return PutCallParities.from_parities(parities, spot, ttm) class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=True): @@ -1719,7 +1723,9 @@ def collect_rates( ttm = self.day_counter.dcf(ref_date, maturity) if ttm <= 0: continue - parities = section.put_call_parities(spot, max_pairs=max_pairs) + parities = section.put_call_parities( + spot, ref_date=ref_date, max_pairs=max_pairs + ) dq = ( None if fit_quote_curve diff --git a/quantflow/utils/plot.py b/quantflow/utils/plot.py index e99aa2a..acaacdd 100644 --- a/quantflow/utils/plot.py +++ b/quantflow/utils/plot.py @@ -219,7 +219,7 @@ def plot3d( def plot_yield_curve( - curve: YieldCurve, + curve: "YieldCurve", ttm_max: float = 10.0, n: int = 200, **kwargs: Any, From 855c9d09d7e43e855b01a4944651140547e633f4 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 17 May 2026 20:24:13 +0100 Subject: [PATCH 3/8] Fit with bounds --- app/volatility_surface.py | 8 ++-- quantflow/options/parity.py | 10 ++--- quantflow/options/surface.py | 71 ++++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/app/volatility_surface.py b/app/volatility_surface.py index efda531..f77ccad 100644 --- a/app/volatility_surface.py +++ b/app/volatility_surface.py @@ -65,7 +65,7 @@ async def _(asset, inverse, mo): use_perp=not inverse.value ) - loader.calibrate_curves(quote_curve=NelsonSiegel, asset_curve=NelsonSiegel) + loader.calibrate_curves(quote_curve=NelsonSiegel) # build the volatility surface surface = loader.surface() # calculate black implied volatilities @@ -132,9 +132,9 @@ def _(loader): @app.cell def _(loader): - cross = loader.maturities[sorted(loader.maturities)[-2]] + cross = loader.maturities[sorted(loader.maturities)[6]] p = cross.put_call_parities(loader.spot.mid, max_pairs=100) - da=None + da=1 return da, p @@ -152,7 +152,7 @@ def _(da, p): @app.cell def _(loader): - loader.collect_rates() + loader.collect_rates(fit_asset_curve=False).model_dump() return diff --git a/quantflow/options/parity.py b/quantflow/options/parity.py index 2baf68e..ebdad7e 100644 --- a/quantflow/options/parity.py +++ b/quantflow/options/parity.py @@ -5,6 +5,7 @@ import numpy as np from pydantic import BaseModel, Field +from scipy.optimize import lsq_linear from quantflow.utils.numbers import ZERO, Number, to_decimal from quantflow.utils.price import Price @@ -114,12 +115,9 @@ def fit_discounts( elif da is not None: dq = float(np.mean((da - ys) / xs)) else: - from scipy.optimize import lsq_linear - - A = np.column_stack([np.ones(len(xs)), xs]) - # alpha = Da in (0, max_da], beta = -Dq in [-max_dq, 0) - result = lsq_linear(A, ys, bounds=([0, -max_dq], [max_da, 0])) - da, dq = float(result.x[0]), -float(result.x[1]) + A = np.column_stack([np.ones(len(xs)), -xs]) + result = lsq_linear(A, ys, bounds=([0, 0], [max_da, max_dq])) + da, dq = float(result.x[0]), float(result.x[1]) if not (0 < dq <= max_dq and 0 < da <= max_da): return None return DiscountPair(asset_discount=da, quote_discount=dq) diff --git a/quantflow/options/surface.py b/quantflow/options/surface.py index 70a1db1..6c3e4c6 100644 --- a/quantflow/options/surface.py +++ b/quantflow/options/surface.py @@ -1464,6 +1464,14 @@ def put_call_parities( return PutCallParities.from_parities(parities, spot, ttm) +class VolRates(BaseModel, frozen=True): + """Per-maturity continuously compounded rates fitted from put-call parity.""" + + ttms: list[float] = Field(description="Times to maturity in years") + quote_rates: list[float] = Field(description="Quote continuously compounded rates") + asset_rates: list[float] = Field(description="Asset continuously compounded rates") + + class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=True): """Helper class to build a volatility surface from a list of securities @@ -1664,6 +1672,20 @@ def calibrate_curves( "from option prices. When None the current asset_curve is unchanged." ), ] = None, + min_rate_q: Annotated[ + float, + Doc( + "Minimum continuously compounded quote rate." + " Default 0 enforces non-negative quote rates." + ), + ] = 0.0, + min_rate_a: Annotated[ + float, + Doc( + "Minimum continuously compounded asset rate." + " Set negative to allow positive asset carry." + ), + ] = 0.0, ref_date: Annotated[ datetime | None, Doc("Reference date for time to maturity calculations") ] = None, @@ -1684,16 +1706,22 @@ def calibrate_curves( Quote only: pass a type for `quote_curve`, leave `asset_curve` as None. The existing `asset_curve` is treated as known and $D_q$ is solved analytically. """ - ttms, rates_q, rates_a = self.collect_rates( + vol_rates = self.collect_rates( fit_quote_curve=quote_curve is not None, fit_asset_curve=asset_curve is not None, + min_rate_q=min_rate_q, + min_rate_a=min_rate_a, ref_date=ref_date, max_pairs=max_pairs, ) if quote_curve is not None: - self.quote_curve = quote_curve.calibrate(ttms, rates_q) + self.quote_curve = quote_curve.calibrate( + vol_rates.ttms, vol_rates.quote_rates + ) if asset_curve is not None: - self.asset_curve = asset_curve.calibrate(ttms, rates_a) + self.asset_curve = asset_curve.calibrate( + vol_rates.ttms, vol_rates.asset_rates + ) def collect_rates( self, @@ -1704,27 +1732,43 @@ def collect_rates( fit_asset_curve: Annotated[ bool, Doc("Whether to fit the asset discount curve $D_a$") ] = True, + min_rate_q: Annotated[ + float, + Doc( + "Minimum continuously compounded quote rate." + " Default 0 enforces non-negative quote rates." + ), + ] = 0.0, + min_rate_a: Annotated[ + float, + Doc( + "Minimum continuously compounded asset rate." + " Set negative to allow positive asset carry." + ), + ] = 0.0, ref_date: Annotated[ datetime | None, Doc("Reference date for time to maturity calculations") ] = None, max_pairs: Annotated[ int, Doc("Maximum number of put-call pairs to use per maturity") ] = 10, - ) -> tuple[list[float], list[float], list[float]]: + ) -> VolRates: """Collect per-maturity continuously compounded rates from put-call parity.""" if not self.spot or self.spot.mid == ZERO: raise ValueError("No spot price provided") spot = self.spot.mid ttms: list[float] = [] - rates_q: list[float] = [] - rates_a: list[float] = [] + quote_rates: list[float] = [] + asset_rates: list[float] = [] ref_date = self.ref_date(ref_date=ref_date) for maturity, section in sorted(self.maturities.items()): ttm = self.day_counter.dcf(ref_date, maturity) if ttm <= 0: continue parities = section.put_call_parities( - spot, ref_date=ref_date, max_pairs=max_pairs + spot, + ref_date=ref_date, + max_pairs=max_pairs, ) dq = ( None @@ -1736,13 +1780,18 @@ def collect_rates( if fit_asset_curve else float(self.asset_curve.discount_factor(ttm)) ) - d = parities.fit_discounts(dq=dq, da=da) + d = parities.fit_discounts( + dq=dq, + da=da, + min_rate_q=min_rate_q, + min_rate_a=min_rate_a, + ) if d is None: continue ttms.append(ttm) - rates_q.append(-math.log(d.quote_discount) / ttm) - rates_a.append(-math.log(d.asset_discount) / ttm) - return ttms, rates_q, rates_a + quote_rates.append(-math.log(d.quote_discount) / ttm) + asset_rates.append(-math.log(d.asset_discount) / ttm) + return VolRates(ttms=ttms, quote_rates=quote_rates, asset_rates=asset_rates) class VolSurfaceLoader(GenericVolSurfaceLoader[DefaultVolSecurity]): From 29619cc0e0002cadc0202dca91e5fee8699165f3 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 17 May 2026 21:07:20 +0100 Subject: [PATCH 4/8] Add examples index --- docs/examples/index.md | 32 ++++++++++++++++++++++++++++++++ mkdocs.yml | 2 ++ 2 files changed, 34 insertions(+) create mode 100644 docs/examples/index.md diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..6e881bc --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,32 @@ +# Interactive Examples + +Interactive notebooks for exploring quantflow tools and models. + +Each example is a [marimo](https://marimo.io) notebook served as a live application. +You can run the code, adjust parameters, and see results update in real time. + +!!! warning "Work in progress" + These notebooks are not always stable and may fail to load or produce unexpected results. + We are actively working on improving their reliability. + If you have experience with marimo and would like to help, contributions are very welcome via [GitHub](https://github.com/quantmind/quantflow). + +## Stochastic Processes + +| Example | Description | +|---|---| +| [Gaussian Sampling](gaussian-sampling) | Sample the Gaussian Ornstein-Uhlenbeck (Vasicek) process for different mean-reversion speeds and path counts | +| [Poisson Sampling](poisson-sampling) | Compare Monte Carlo simulation of the Poisson process against the analytical PDF | +| [Double Exponential Sampling](double-exponential-sampling) | Explore the Asymmetric Laplace distribution with adjustable asymmetry parameter | + +## Time Series Analysis + +| Example | Description | +|---|---| +| [Hurst Exponent](hurst) | Estimate the Hurst exponent to classify a time series as trending, mean-reverting, or random | +| [Supersmoother](supersmoother) | Apply the Supersmoother and EWMA filters to financial time series | + +## Volatility and Options + +| Example | Description | +|---|---| +| [Volatility Surface](volatility-surface) | Build and visualise an implied volatility surface from live Deribit ETH/USD options data | diff --git a/mkdocs.yml b/mkdocs.yml index 6fa5c46..30f9622 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -127,6 +127,7 @@ nav: - Lévy Process: theory/levy.md - Option Pricing: theory/option_pricing.md - Examples: + - examples/index.md - Gaussian Sampling: examples/gaussian-sampling - Poisson Sampling: examples/poisson-sampling - Double Exponential Sampling: examples/double-exponential-sampling @@ -139,6 +140,7 @@ nav: - Bibliography: bibliography.md - Release Notes: release-notes.md markdown_extensions: + - admonition - attr_list - tables - pymdownx.arithmatex: From 784720f31fe80b1cb8f0a90a2a84556dee95dddc Mon Sep 17 00:00:00 2001 From: Luca Date: Mon, 18 May 2026 10:51:57 +0100 Subject: [PATCH 5/8] Yield curve dump --- app/volatility_surface.py | 56 +-- docs/api/options/calibration.md | 2 + docs/api/options/vol_surface.md | 2 - docs/api/rates/yield_curve.md | 2 - quantflow/data/deribit.py | 9 + quantflow/data/yahoo.py | 17 +- quantflow/options/inputs.py | 7 +- quantflow/options/surface.py | 466 +++++--------------- quantflow/rates/__init__.py | 22 + quantflow/rates/nelson_siegel.py | 2 + quantflow/rates/vasicek.py | 40 ++ quantflow/rates/yield_curve.py | 13 +- quantflow/utils/plot.py | 2 +- quantflow_tests/test_data_deribit.py | 12 +- quantflow_tests/test_data_yahoo.py | 1 + quantflow_tests/test_implied_fwd.py | 175 -------- quantflow_tests/test_non_inverse_surface.py | 17 +- 17 files changed, 255 insertions(+), 590 deletions(-) create mode 100644 quantflow/rates/vasicek.py delete mode 100644 quantflow_tests/test_implied_fwd.py diff --git a/app/volatility_surface.py b/app/volatility_surface.py index f77ccad..593ea10 100644 --- a/app/volatility_surface.py +++ b/app/volatility_surface.py @@ -85,21 +85,13 @@ def int_or_none(v): label="Maturity" ) maturity_dropdown - return int_or_none, loader, maturity_dropdown, pd, surface + return int_or_none, maturity_dropdown, surface @app.cell def _(int_or_none, maturity_dropdown, surface): index = int_or_none(maturity_dropdown.value) surface.plot3d(index=index) - return (index,) - - -@app.cell -def _(index, pd, surface): - # display inputs - only options with converged implied volatility - surface_inputs = surface.inputs(converged=True, index=index) - pd.DataFrame([i.model_dump() for i in surface_inputs.inputs]) return @@ -111,48 +103,18 @@ def _(surface): @app.cell -def _(ts): - from quantflow.utils import plot - - plot.plot_lines(ts, x="ttm", y="rate_percent") - return - - -@app.cell -def _(loader): - loader.quote_curve.plot(ttm_max=2) - return - - -@app.cell -def _(loader): - loader.asset_curve.plot(ttm_max=2) +def _(surface, ts): + import math + ttm_max = 0.1*math.ceil(10*surface.maturities[-1].ttm(surface.ref_date)) + fig = surface.quote_curve.plot(ttm_max=ttm_max) + fig.add_scatter(x=ts["ttm"], y=ts["rate"], mode="markers", name="Cross Sections", marker=dict(size=8, color="orange")) + fig return @app.cell -def _(loader): - cross = loader.maturities[sorted(loader.maturities)[6]] - p = cross.put_call_parities(loader.spot.mid, max_pairs=100) - da=1 - return da, p - - -@app.cell -def _(da, p): - p.fit_discounts(da=da) - return - - -@app.cell -def _(da, p): - p.plot(da=da) - return - - -@app.cell -def _(loader): - loader.collect_rates(fit_asset_curve=False).model_dump() +def _(surface): + surface.asset_curve.plot(ttm_max=2) return diff --git a/docs/api/options/calibration.md b/docs/api/options/calibration.md index b2eadd8..022ebd4 100644 --- a/docs/api/options/calibration.md +++ b/docs/api/options/calibration.md @@ -1,5 +1,7 @@ # Vol Model Calibration +::: quantflow.options.calibration.base.ResidualKind + ::: quantflow.options.calibration.base.OptionEntry ::: quantflow.options.calibration.base.VolModelCalibration diff --git a/docs/api/options/vol_surface.md b/docs/api/options/vol_surface.md index 666a753..cc52999 100644 --- a/docs/api/options/vol_surface.md +++ b/docs/api/options/vol_surface.md @@ -19,8 +19,6 @@ ::: quantflow.options.surface.FwdPrice -::: quantflow.options.surface.ImpliedFwdPrice - ::: quantflow.options.surface.Strike ::: quantflow.options.surface.OptionArrays diff --git a/docs/api/rates/yield_curve.md b/docs/api/rates/yield_curve.md index f2f41c8..77e74ce 100644 --- a/docs/api/rates/yield_curve.md +++ b/docs/api/rates/yield_curve.md @@ -2,5 +2,3 @@ ::: quantflow.rates.yield_curve.YieldCurve - -::: quantflow.rates.nelson_siegel.NelsonSiegel diff --git a/quantflow/data/deribit.py b/quantflow/data/deribit.py index 40f98c5..536e641 100644 --- a/quantflow/data/deribit.py +++ b/quantflow/data/deribit.py @@ -14,6 +14,8 @@ from quantflow.options.inputs import DefaultVolSecurity, OptionType from quantflow.options.surface import VolSurfaceLoader +from quantflow.rates.yield_curve import NoDiscount +from quantflow.utils.dates import utcnow from quantflow.utils.numbers import ( Number, round_to_step, @@ -125,6 +127,10 @@ async def volatility_surface_loader( self, currency: Annotated[str, Doc("Currency")], *, + ref_date: Annotated[ + datetime | None, + Doc("Reference date for the yield curves; defaults to now"), + ] = None, inverse: Annotated[ bool, Doc( @@ -145,10 +151,13 @@ async def volatility_surface_loader( ) -> VolSurfaceLoader: """Create a [VolSurfaceLoader][quantflow.options.surface.VolSurfaceLoader] for a given crypto-currency""" + ref = ref_date or utcnow() loader = VolSurfaceLoader( asset=currency, exclude_open_interest=to_decimal_or_none(exclude_open_interest), exclude_volume=to_decimal_or_none(exclude_volume), + quote_curve=NoDiscount(ref_date=ref), + asset_curve=NoDiscount(ref_date=ref), ) if inverse: futures = await self.get_book_summary_by_currency( diff --git a/quantflow/data/yahoo.py b/quantflow/data/yahoo.py index bfe9dfa..adf50e3 100644 --- a/quantflow/data/yahoo.py +++ b/quantflow/data/yahoo.py @@ -3,7 +3,7 @@ import gzip import json from dataclasses import dataclass, field -from datetime import date, timezone +from datetime import date, datetime, timezone from enum import StrEnum from pathlib import Path @@ -13,7 +13,8 @@ from quantflow.options.inputs import DefaultVolSecurity, OptionType from quantflow.options.surface import VolSurfaceLoader -from quantflow.utils.dates import as_utc +from quantflow.rates.yield_curve import NoDiscount +from quantflow.utils.dates import as_utc, utcnow from quantflow.utils.numbers import to_decimal @@ -86,6 +87,10 @@ async def volatility_surface_loader( self, symbol: Annotated[str, Doc("Underlying ticker symbol")], *, + ref_date: Annotated[ + datetime | None, + Doc("Reference date for the yield curves; defaults to now"), + ] = None, exclude_volume: Annotated[ int | None, Doc("Drop contracts with volume at or below this threshold") ] = None, @@ -99,6 +104,7 @@ async def volatility_surface_loader( [loader_from_chain][quantflow.data.yahoo.Yahoo.loader_from_chain].""" return self.loader_from_chain( await self.option_chain(symbol), + ref_date=ref_date, exclude_volume=exclude_volume, exclude_open_interest=exclude_open_interest, ) @@ -108,6 +114,10 @@ def loader_from_chain( cls, chain: Annotated[dict, Doc("Yahoo option chain payload")], *, + ref_date: Annotated[ + datetime | None, + Doc("Reference date for the yield curves; defaults to now"), + ] = None, exclude_volume: Annotated[ int | None, Doc("Drop contracts with volume at or below this threshold") ] = None, @@ -124,12 +134,15 @@ def loader_from_chain( by Yahoo, so they are recovered from put-call parity by the loader. """ symbol = chain.get("underlyingSymbol", "") + ref = ref_date or utcnow() loader = VolSurfaceLoader( asset=symbol, exclude_volume=to_decimal(exclude_volume) if exclude_volume else None, exclude_open_interest=( to_decimal(exclude_open_interest) if exclude_open_interest else None ), + quote_curve=NoDiscount(ref_date=ref), + asset_curve=NoDiscount(ref_date=ref), ) quote = chain.get("quote") or {} bid = quote.get("bid") or quote.get("regularMarketPrice") diff --git a/quantflow/options/inputs.py b/quantflow/options/inputs.py index 9c4bb91..fbaf2da 100644 --- a/quantflow/options/inputs.py +++ b/quantflow/options/inputs.py @@ -6,6 +6,7 @@ from pydantic import BaseModel, Field +from quantflow.rates import AnyYieldCurve from quantflow.utils.numbers import ZERO, DecimalNumber P = TypeVar("P") @@ -44,11 +45,14 @@ class VolSecurityType(enum.StrEnum): class VolSurfaceSecurity(BaseModel): + """Base class for Volatility Surface Securities""" + def vol_surface_type(self) -> VolSecurityType: raise NotImplementedError("vol_surface_type must be implemented by subclasses") @classmethod def forward(cls) -> Self: + """Create a forward security for the volatility surface""" raise NotImplementedError("forward_input must be implemented by subclasses") @@ -141,7 +145,8 @@ class VolSurfaceInputs(BaseModel): """Class representing the inputs for a volatility surface""" asset: str = Field(description="Underlying asset of the volatility surface") - ref_date: datetime = Field(description="Reference date for the volatility surface") + asset_curve: AnyYieldCurve = Field(description="Asset yield curve") + quote_curve: AnyYieldCurve = Field(description="Quote yield curve") inputs: list[ForwardInput | SpotInput | OptionInput] = Field( description="List of inputs for the volatility surface" ) diff --git a/quantflow/options/surface.py b/quantflow/options/surface.py index 6c3e4c6..c8d52fb 100644 --- a/quantflow/options/surface.py +++ b/quantflow/options/surface.py @@ -5,7 +5,7 @@ import warnings from datetime import datetime, timedelta from decimal import Decimal -from typing import Any, Generic, Iterator, NamedTuple, Self, TypeVar +from typing import Any, Generic, Iterator, NamedTuple, Self, TypeVar, cast import numpy as np import pandas as pd @@ -13,16 +13,13 @@ from pydantic import BaseModel, Field from typing_extensions import Annotated, Doc -from quantflow.rates.interest_rate import Rate -from quantflow.rates.yield_curve import NoDiscount, YieldCurve +from quantflow.rates import AnyYieldCurve, NoDiscount, Rate, YieldCurve from quantflow.utils import plot from quantflow.utils.dates import utcnow from quantflow.utils.numbers import ( - ONE, ZERO, DecimalNumber, Number, - Rounding, normalize_decimal, round_to_step, sigfig, @@ -103,6 +100,14 @@ def inputs(self) -> SpotInput: volume=self.volume, ) + def _implied_forward(self, maturity: datetime, price: Decimal) -> FwdPrice[S]: + return FwdPrice( + security=self.security.forward(), + maturity=maturity, + bid=price, + ask=price, + ) + class FwdPrice(SecurityPrice[S]): """Represents the forward bid/ask price of an underlying asset @@ -120,113 +125,6 @@ def inputs(self) -> ForwardInput: ) -class ImpliedFwdPrice(FwdPrice[S]): - """Represents the implied forward price of an underlying asset at a specific - maturity, extracted from option prices via put-call parity""" - - strike: DecimalNumber = Field( - description="Strike price of the options used to extract the forward price" - ) - - def moneyness(self, ttm: float) -> float: - """Moneyness of the implied forward""" - return math.log(float(self.strike / self.mid)) / math.sqrt(ttm) - - @classmethod - def aggregate( - cls, - implied_forwards: Annotated[ - list[Self], Doc("Implied forward prices from put-call parity") - ], - ttm: Annotated[float, Doc("Time to maturity in years")], - default: Annotated[ - FwdPrice[S] | None, - Doc("Market forward (e.g. from futures) used as fallback or for blending"), - ] = None, - previous_forward: Annotated[ - Decimal | None, - Doc( - "Anchor forward for proximity weighting, " - "typically the previous maturity" - ), - ] = None, - tick_size: Annotated[ - Decimal | None, Doc("Tick size for rounding the implied forward bid/ask") - ] = None, - ) -> FwdPrice[S] | None: - r"""Aggregate implied forward prices extracted from put-call parity into a - single best-estimate forward price. - - **Selection**: valid implied forwards are sorted by bid-ask spread in basis - points and the tightest 5 are retained as candidates. Let $c$ denote the - tightest bp spread among the candidates. - - **Default priority**: if a default forward is provided and its bp spread is - tighter than $c$, it is returned immediately as the most reliable price. - - **Default inclusion**: if the default's bp spread is wider than $c$ but - narrower than the worst candidate, it is appended to the candidate pool - and weighted on equal footing with the implied forwards. - - **Weighting**: each candidate $i$ receives weight - - \begin{equation} - w_i = w^{\text{spread}}_i \cdot w^{\text{proximity}}_i - \end{equation} - - where the spread weight is a Gaussian on the normalised distance from the - best spread $c$ and the proximity weight, applied only when - `previous_forward` is provided. - The result is the weighted average of the candidate mid prices, with the - bid/ask spread computed as the weighted average of candidate spreads. - When `tick_size` is provided the output bid is rounded down and the ask - is rounded up to the nearest tick. - """ - forwards: list[FwdPrice[S]] = [f for f in implied_forwards if f.is_valid()] - if not forwards: - return default - forwards = sorted(forwards, key=lambda f: f.bp_spread)[:5] - best_bp_spread = forwards[0].bp_spread - if ( - default is not None - and default.is_valid() - and default.bp_spread < best_bp_spread - ): - return default - weights = 0.0 - values = 0.0 - spreads = 0.0 - worse_bp_spread = forwards[-1].bp_spread - if ( - default is not None - and default.is_valid() - and default.bp_spread < worse_bp_spread - ): - forwards.append(default) - for forward in forwards: - s = (forward.bp_spread - best_bp_spread) / best_bp_spread - weight = math.exp(-s * s) - if previous_forward is not None: - d = (forward.mid - previous_forward) / previous_forward - weight *= math.exp(-d * d) - weights += weight - values += weight * float(forward.mid) - spreads += weight * float(forward.spread) - mid = to_decimal(values / weights) - spread = to_decimal(spreads / weights) - bid = mid - spread / 2 - ask = mid + spread / 2 - if tick_size is not None: - bid = round_to_step(bid, tick_size, Rounding.DOWN) - ask = round_to_step(ask, tick_size, Rounding.UP) - return FwdPrice( - security=forwards[0].security.forward(), - bid=bid, - ask=ask, - maturity=forwards[0].maturity, - ) - - class OptionMetadata(BaseModel): """Represents the metadata of an option, including its strike, type, maturity, and other relevant information.""" @@ -576,73 +474,6 @@ def put_call_parity(self) -> PutCallParity | None: inverse=self.call.meta.inverse, ) - def implied_forward( - self, - tick_size: Annotated[ - Decimal | None, Doc("Tick size for rounding the implied forward bid/ask") - ] = None, - df: Annotated[ - Decimal, - Doc( - "Discount factor at this maturity. " - "Defaults to 1 (no discounting). When set, option prices are " - "deflated by $D$ before applying put-call parity." - ), - ] = ONE, - ) -> ImpliedFwdPrice[S] | None: - r"""Extract the implied forward price from put-call parity. - - Requires both a call and a put at this strike. Uses bid/ask prices - to construct the bid/ask of the implied forward. When `tick_size` is - provided, bid is rounded down and ask is rounded up to the nearest tick. - - For inverse options (prices quoted in the underlying currency) - put-call parity reads - - \begin{equation} - F = \frac{K}{1 - (c - p) / D} - \end{equation} - - For non-inverse options (prices quoted in the quote currency) - - \begin{equation} - F = K + \frac{C - P}{D} - \end{equation} - - where $D$ is the discount factor (default 1, i.e. no discounting). - - Returns None when the strike does not have both a call and a put, - or when the denominator is non-positive (arbitrage condition violated). - """ - if self.call is None or self.put is None: - return None - cp_bid = self.call.bid.price - self.put.ask.price - cp_ask = self.call.ask.price - self.put.bid.price - if self.call.meta.inverse: - d_bid = ONE - cp_bid / df - d_ask = ONE - cp_ask / df - if d_bid <= ZERO or d_ask <= ZERO: - return None - bid = self.strike / d_bid - ask = self.strike / d_ask - else: - bid = self.strike + cp_bid / df - ask = self.strike + cp_ask / df - if bid <= ZERO or ask <= ZERO: - return None - if bid > ask: - return None - if tick_size is not None: - bid = round_to_step(bid, tick_size, Rounding.DOWN) - ask = round_to_step(ask, tick_size, Rounding.UP) - return ImpliedFwdPrice( - security=self.call.security.forward(), - strike=self.strike, - maturity=self.call.meta.maturity, - bid=bid, - ask=ask, - ) - def options_iter( self, forward: Annotated[Decimal, Doc("Forward price of the underlying asset")], @@ -748,33 +579,23 @@ def ttm(self, ref_date: datetime) -> float: """Time to maturity in years""" return self.day_counter.dcf(ref_date, self.maturity) - def forward_rate(self, ref_date: datetime, spot: SpotPrice[S]) -> Rate: - """Compute the implied continuous rate from spot and forward mid""" - return Rate.from_spot_and_forward( - spot.mid, - self.forward.mid, - ref_date, - self.maturity, - day_counter=self.day_counter, - ) - - def forward_spread_fraction(self) -> Decimal: - """Bid-ask spread of the forward as a fraction of its mid price""" - mid = self.forward.mid - if mid <= ZERO: - return Decimal("Inf") - return (self.forward.ask - self.forward.bid) / mid - - def info_dict(self, ref_date: datetime, spot: SpotPrice[S]) -> dict: + def info_dict( + self, + ref_date: datetime, + spot: Decimal, + implied_forward: Decimal, + ) -> dict: """Return a dictionary with information about the cross section""" + ttm = self.ttm(ref_date) return dict( maturity=self.maturity, - ttm=self.ttm(ref_date), + ttm=ttm, forward=self.forward.mid, + implied_forward=implied_forward, + forward_basis=implied_forward - self.forward.mid, + rate=Rate.from_number(float((implied_forward / spot).ln()) / ttm).rate, bid_ask_spread=self.forward.spread, - basis=self.forward.mid - spot.mid, - rate_percent=self.forward_rate(ref_date, spot).percent, - fwd_spread_pct=round(100 * self.forward_spread_fraction(), 4), + basis=implied_forward - spot, open_interest=self.forward.open_interest, volume=self.forward.volume, ) @@ -784,6 +605,7 @@ def option_prices( ref_date: Annotated[ datetime, Doc("Reference date for time to maturity calculation") ], + forward: Annotated[Decimal, Doc("Forward price of the underlying asset")], *, select: Annotated[ OptionSelection, Doc("Option selection method") @@ -798,7 +620,7 @@ def option_prices( """Iterator over option prices in the cross section""" for s in self.strikes: yield from s.option_prices( - self.forward.mid, + forward, self.ttm(ref_date), select=select, initial_vol=initial_vol, @@ -921,7 +743,66 @@ def disable_outliers( break -class VolSurface(BaseModel, Generic[S]): +class ForwardPricer(BaseModel, Generic[S]): + """Base class for forward/discount factor pricers""" + + asset: str = Field( + default="", + description="Name of the underlying asset", + ) + spot: SpotPrice[S] | None = Field( + default=None, + description="Spot price of the underlying asset", + ) + quote_curve: AnyYieldCurve = Field( + default_factory=NoDiscount, + description="Discount curve for the quote", + ) + asset_curve: AnyYieldCurve = Field( + default_factory=NoDiscount, + description="Discount curve for the asset", + ) + tick_size_forwards: DecimalNumber | None = Field( + default=None, + description="Tick size for rounding forward and spot prices - optional", + ) + tick_size_options: DecimalNumber | None = Field( + default=None, description="Tick size for rounding option prices - optional" + ) + day_counter: DayCounter = Field( + default=default_day_counter, + description=( + "Day counter for time to maturity calculations, " + "by default it uses Act/Act" + ), + ) + + @property + def ref_date(self) -> datetime: + """Reference date for the volatility surface, taken as the earliest maturity + or the provided ref_date if it's earlier""" + return min(self.quote_curve.ref_date, self.asset_curve.ref_date) + + def spot_price(self) -> Decimal: + """Get the spot price if it exists""" + if self.spot is None: + raise ValueError("No spot price provided") + return self.spot.mid + + def forward(self, maturity: datetime) -> Decimal: + """Calculate the implied forward for a given maturity""" + ttm = self.day_counter.dcf(self.ref_date, maturity) + df_quote = self.quote_curve.discount_factor(ttm) + df_asset = self.asset_curve.discount_factor(ttm) + forward_rate = self.spot_price() * df_asset / df_quote + return ( + round_to_step(forward_rate, self.tick_size_forwards) + if self.tick_size_forwards + else forward_rate + ) + + +class VolSurface(ForwardPricer[S]): """Represents a volatility surface, which captures the implied volatility of an option for different strikes and maturities. @@ -947,31 +828,14 @@ class VolSurface(BaseModel, Generic[S]): future volatility. """ - ref_date: datetime = Field(description="Reference date for the volatility surface") - asset: str = Field(description="Underlying asset of the volatility surface") - spot: SpotPrice[S] = Field(description="Spot price of the underlying asset") maturities: tuple[VolCrossSection[S], ...] = Field( + default=(), description=( "Sorted tuple of " "[VolCrossSection][quantflow.options.surface.VolCrossSection], " "each containing the forward price and option prices for that maturity" - ) - ) - day_counter: DayCounter = Field( - default=default_day_counter, - description=( - "Day counter for time to maturity calculations, " - "by default it uses Act/Act" ), ) - tick_size_forwards: DecimalNumber | None = Field( - default=None, - description="Tick size for rounding forward and spot prices - optional", - ) - tick_size_options: DecimalNumber | None = Field( - default=None, - description="Tick size for rounding option prices - optional", - ) def securities( self, @@ -992,7 +856,8 @@ def securities( ] = False, ) -> Iterator[SpotPrice[S] | FwdPrice[S] | OptionPrices[S]]: """Iterator over securities in the volatility surface""" - yield self.spot + if self.spot is not None: + yield self.spot if index is not None: yield from self.maturities[index].securities( select=select, converged=converged @@ -1023,7 +888,8 @@ def inputs( [VolSurfaceInputs][quantflow.options.inputs.VolSurfaceInputs] instance""" return VolSurfaceInputs( asset=self.asset, - ref_date=self.ref_date, + asset_curve=self.asset_curve, + quote_curve=self.quote_curve, inputs=list( s.inputs() for s in self.securities( @@ -1034,8 +900,10 @@ def inputs( def term_structure(self) -> pd.DataFrame: """Return the term structure of the volatility surface as a DataFrame""" + spot = self.spot_price() return pd.DataFrame( - cross.info_dict(self.ref_date, self.spot) for cross in self.maturities + cross.info_dict(self.ref_date, spot, self.forward(cross.maturity)) + for cross in self.maturities ) def trim(self, num_maturities: int) -> Self: @@ -1067,16 +935,19 @@ def option_prices( ) -> Iterator[OptionPrice]: """Iterator over selected option prices in the surface""" if index is not None: - yield from self.maturities[index].option_prices( + cross = self.maturities[index] + yield from cross.option_prices( self.ref_date, + self.forward(cross.maturity), select=select, initial_vol=initial_vol, converged=converged, ) else: - for maturity in self.maturities: - yield from maturity.option_prices( + for cross in self.maturities: + yield from cross.option_prices( self.ref_date, + self.forward(cross.maturity), select=select, initial_vol=initial_vol, converged=converged, @@ -1374,56 +1245,13 @@ def add_option( else: self.strikes[strike].put = option - def cross_section( - self, - ref_date: Annotated[ - datetime | None, Doc("Reference date for the volatility surface") - ] = None, - previous_forward: Annotated[ - Decimal | None, - Doc( - "Previous forward price for the volatility surface " - "Usaed by the implied forward calculation to replace missing " - "or unreliable forwards" - ), - ] = None, - tick_size: Annotated[ - Decimal | None, - Doc("Tick size for rounding implied forward bid/ask prices"), - ] = None, - yield_curve: Annotated[ - YieldCurve | None, - Doc( - "Yield curve used to compute the per-maturity discount factor " - "applied in put-call parity. When None, no discounting is applied." - ), - ] = None, - ) -> VolCrossSection[S] | None: + def _cross_section(self, forward: FwdPrice[S]) -> VolCrossSection[S] | None: strikes = [] - implied_forwards = [] - ref = ref_date or utcnow() - ttm = self.day_counter.dcf(ref, self.maturity) - yield_curve = yield_curve or NoDiscount() - df = yield_curve.discount_factor(ttm) for strike in sorted(self.strikes): sk = self.strikes[strike] if sk.call is None and sk.put is None: continue - if implied_forward := sk.implied_forward(tick_size=tick_size, df=df): - implied_forwards.append(implied_forward) strikes.append(sk) - forward = self.forward - if implied_forwards: - ttm = self.day_counter.dcf(ref_date or utcnow(), self.maturity) - forward = ImpliedFwdPrice.aggregate( - implied_forwards, - ttm, - default=self.forward, - previous_forward=previous_forward, - tick_size=tick_size, - ) - if forward is None or not forward.is_valid(): - return None return ( VolCrossSection( maturity=self.maturity, @@ -1472,7 +1300,7 @@ class VolRates(BaseModel, frozen=True): asset_rates: list[float] = Field(description="Asset continuously compounded rates") -class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=True): +class GenericVolSurfaceLoader(ForwardPricer[S], arbitrary_types_allowed=True): """Helper class to build a volatility surface from a list of securities Use this class to add spot, forward and option securities with their prices @@ -1480,30 +1308,12 @@ class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=Tru from the provided data. """ - asset: str = Field(default="", description="Name of the underlying asset") - spot: SpotPrice[S] | None = Field( - default=None, description="Spot price of the underlying asset" - ) maturities: dict[datetime, VolCrossSectionLoader[S]] = Field( default_factory=dict, description=( "Dictionary of maturities and their corresponding cross section loaders" ), ) - day_counter: DayCounter = Field( - default=default_day_counter, - description=( - "Day counter for time to maturity calculations " - "by default it uses Act/Act" - ), - ) - tick_size_forwards: DecimalNumber | None = Field( - default=None, - description="Tick size for rounding forward and spot prices - optional", - ) - tick_size_options: DecimalNumber | None = Field( - default=None, description="Tick size for rounding option prices - optional" - ) exclude_open_interest: DecimalNumber | None = Field( default=None, description="Exclude options with open interest at or below this value", @@ -1511,31 +1321,6 @@ class GenericVolSurfaceLoader(BaseModel, Generic[S], arbitrary_types_allowed=Tru exclude_volume: DecimalNumber | None = Field( default=None, description="Exclude options with volume at or below this value" ) - quote_curve: YieldCurve = Field( - default_factory=NoDiscount, - description=( - "Discount curve for the quote currency $D_q$. " - "Set by calling calibrate_curves()." - ), - ) - asset_curve: YieldCurve = Field( - default_factory=NoDiscount, - description=( - "Discount curve for the asset $D_a$. " "Set by calling calibrate_curves()." - ), - ) - - def ref_date( - self, - ref_date: Annotated[ - datetime | None, Doc("Reference date for the volatility surface") - ] = None, - ) -> datetime: - """Reference date for the volatility surface, taken as the earliest maturity - or the provided ref_date if it's earlier""" - if ref_date is not None: - return ref_date - return utcnow() def get_or_create_maturity( self, @@ -1624,33 +1409,27 @@ def add_option( inverse=inverse, ) - def surface( - self, - ref_date: Annotated[ - datetime | None, Doc("Reference date for the volatility surface") - ] = None, - ) -> VolSurface[S]: + def surface(self) -> VolSurface[S]: """Build a volatility surface from the provided data""" - if not self.spot or self.spot.mid == ZERO: - raise ValueError("No spot price provided") maturities = [] - ref_date = ref_date or utcnow() - previous_forward = self.spot.mid + spot = self.spot + if spot is None: + raise ValueError("No spot price provided") for maturity in sorted(self.maturities): - if section := self.maturities[maturity].cross_section( - ref_date=ref_date, - previous_forward=previous_forward, - tick_size=self.tick_size_forwards, - yield_curve=self.quote_curve, - ): - previous_forward = section.forward.mid + loader = self.maturities[maturity] + forward = loader.forward + if forward is None: + implied_forward_price = self.forward(maturity) + forward = spot._implied_forward(maturity, implied_forward_price) + if section := loader._cross_section(forward): maturities.append(section) return VolSurface( asset=self.asset, - ref_date=ref_date, spot=self.spot, maturities=tuple(maturities), day_counter=self.day_counter, + quote_curve=self.quote_curve.model_copy(), + asset_curve=self.asset_curve.model_copy(), tick_size_forwards=self.tick_size_forwards, tick_size_options=self.tick_size_options, ) @@ -1686,9 +1465,6 @@ def calibrate_curves( " Set negative to allow positive asset carry." ), ] = 0.0, - ref_date: Annotated[ - datetime | None, Doc("Reference date for time to maturity calculations") - ] = None, max_pairs: Annotated[ int, Doc("Maximum number of put-call pairs to use per maturity") ] = 10, @@ -1711,16 +1487,17 @@ def calibrate_curves( fit_asset_curve=asset_curve is not None, min_rate_q=min_rate_q, min_rate_a=min_rate_a, - ref_date=ref_date, max_pairs=max_pairs, ) if quote_curve is not None: - self.quote_curve = quote_curve.calibrate( - vol_rates.ttms, vol_rates.quote_rates + self.quote_curve = cast( + AnyYieldCurve, + quote_curve.calibrate(vol_rates.ttms, vol_rates.quote_rates), ) if asset_curve is not None: - self.asset_curve = asset_curve.calibrate( - vol_rates.ttms, vol_rates.asset_rates + self.asset_curve = cast( + AnyYieldCurve, + asset_curve.calibrate(vol_rates.ttms, vol_rates.asset_rates), ) def collect_rates( @@ -1746,9 +1523,6 @@ def collect_rates( " Set negative to allow positive asset carry." ), ] = 0.0, - ref_date: Annotated[ - datetime | None, Doc("Reference date for time to maturity calculations") - ] = None, max_pairs: Annotated[ int, Doc("Maximum number of put-call pairs to use per maturity") ] = 10, @@ -1760,7 +1534,7 @@ def collect_rates( ttms: list[float] = [] quote_rates: list[float] = [] asset_rates: list[float] = [] - ref_date = self.ref_date(ref_date=ref_date) + ref_date = self.ref_date for maturity, section in sorted(self.maturities.items()): ttm = self.day_counter.dcf(ref_date, maturity) if ttm <= 0: @@ -1848,4 +1622,4 @@ def surface_from_inputs( loader = VolSurfaceLoader() for input in inputs.inputs: loader.add(input) - return loader.surface(ref_date=inputs.ref_date) + return loader.surface() diff --git a/quantflow/rates/__init__.py b/quantflow/rates/__init__.py index e69de29..6458328 100644 --- a/quantflow/rates/__init__.py +++ b/quantflow/rates/__init__.py @@ -0,0 +1,22 @@ +from typing import Annotated, Union + +from pydantic import Field + +from .interest_rate import Rate +from .nelson_siegel import NelsonSiegel +from .vasicek import VasicekCurve +from .yield_curve import NoDiscount, YieldCurve + +__all__ = [ + "YieldCurve", + "NoDiscount", + "NelsonSiegel", + "VasicekCurve", + "AnyYieldCurve", + "Rate", +] + +AnyYieldCurve = Annotated[ + Union[NoDiscount, NelsonSiegel, VasicekCurve], + Field(discriminator="curve_type"), +] diff --git a/quantflow/rates/nelson_siegel.py b/quantflow/rates/nelson_siegel.py index a44a0ce..9a76fcc 100644 --- a/quantflow/rates/nelson_siegel.py +++ b/quantflow/rates/nelson_siegel.py @@ -1,6 +1,7 @@ from __future__ import annotations from decimal import Decimal +from typing import Literal import numpy as np from numpy.typing import ArrayLike @@ -34,6 +35,7 @@ class NelsonSiegel(YieldCurve): beta2: Decimal = Field(..., description="Slope parameter") beta3: Decimal = Field(..., description="Curvature parameter") lambda_: Decimal = Field(..., description="Decay factor") + curve_type: Literal["nelson_siegel"] = "nelson_siegel" def instanteous_forward_rate(self, ttm: Number) -> Decimal: ttmd = to_decimal(ttm) diff --git a/quantflow/rates/vasicek.py b/quantflow/rates/vasicek.py new file mode 100644 index 0000000..07c62b2 --- /dev/null +++ b/quantflow/rates/vasicek.py @@ -0,0 +1,40 @@ +from decimal import Decimal +from typing import Literal + +from pydantic import Field + +from quantflow.sp.ou import Vasicek +from quantflow.sp.wiener import WienerProcess +from quantflow.utils.numbers import ONE, ZERO, DecimalNumber, Number, to_decimal + +from .yield_curve import YieldCurve + + +class VasicekCurve(YieldCurve): + """Class representing a Vasicek yield curve""" + + rate: DecimalNumber = Field(description=r"Initial value $x_0$") + kappa: DecimalNumber = Field(gt=ZERO, description=r"Mean reversion speed $\kappa$") + theta: DecimalNumber = Field(description=r"Mean level $\theta$") + sigma: DecimalNumber = Field(ge=ZERO, description=r"Volatility $\sigma$") + curve_type: Literal["vasicek"] = "vasicek" + + def process(self) -> Vasicek: + return Vasicek( + rate=float(self.rate), + kappa=float(self.kappa), + theta=float(self.theta), + bdlp=WienerProcess(sigma=float(self.sigma)), + ) + + def discount_factor(self, ttm: Number) -> Decimal: + r"""Calculate the discount factor for a given time to maturity.""" + ttmd = to_decimal(ttm) + if ttmd <= ZERO: + return ONE + b = (ONE - (-self.kappa * ttmd).exp()) / self.kappa + s2 = self.sigma * self.sigma + a = (self.theta - s2 / (2 * self.kappa * self.kappa)) * ( + b - ttmd + ) + s2 * b * b / (4 * self.kappa) + return (a - self.rate * b).exp() diff --git a/quantflow/rates/yield_curve.py b/quantflow/rates/yield_curve.py index 4aa1476..760b0fb 100644 --- a/quantflow/rates/yield_curve.py +++ b/quantflow/rates/yield_curve.py @@ -1,20 +1,27 @@ from __future__ import annotations from abc import ABC, abstractmethod +from datetime import datetime from decimal import Decimal -from typing import Any +from typing import Any, Literal from numpy.typing import ArrayLike -from pydantic import BaseModel +from pydantic import BaseModel, Field from typing_extensions import Annotated, Doc, Self from quantflow.utils import plot +from quantflow.utils.dates import utcnow from quantflow.utils.numbers import ONE, ZERO class YieldCurve(BaseModel, ABC, extra="forbid"): """Abstract base class for yield curves""" + ref_date: datetime = Field( + default_factory=utcnow, + description="Reference date for the yield curve", + ) + @abstractmethod def instanteous_forward_rate(self, ttm: float) -> Decimal: r"""Calculate the instantaneous forward rate for a given time to maturity @@ -90,6 +97,8 @@ def plot( class NoDiscount(YieldCurve): """Flat yield curve with zero rates (discount factor is always 1).""" + curve_type: Literal["no_discount"] = "no_discount" + def instanteous_forward_rate(self, ttm: float) -> Decimal: return ZERO diff --git a/quantflow/utils/plot.py b/quantflow/utils/plot.py index acaacdd..17357db 100644 --- a/quantflow/utils/plot.py +++ b/quantflow/utils/plot.py @@ -225,7 +225,7 @@ def plot_yield_curve( **kwargs: Any, ) -> Any: check_plotly() - ttms = np.linspace(0.01, ttm_max, n) + ttms = np.linspace(0.0, ttm_max, n) rates = [float(curve.continuously_compounded_rate(t)) for t in ttms] df = pd.DataFrame({"ttm": ttms, "rate": rates}) return px.line( diff --git a/quantflow_tests/test_data_deribit.py b/quantflow_tests/test_data_deribit.py index 01c8a99..0509e0d 100644 --- a/quantflow_tests/test_data_deribit.py +++ b/quantflow_tests/test_data_deribit.py @@ -54,8 +54,8 @@ async def test_loader_loads_known_options( deribit_cli: Deribit, ref_date: datetime ) -> None: """Options present in both book summary and instruments are loaded.""" - loader = await deribit_cli.volatility_surface_loader("btc") - surface = loader.surface(ref_date=ref_date) + loader = await deribit_cli.volatility_surface_loader("btc", ref_date=ref_date) + surface = loader.surface() # fixture has 2 strikes (70000 C+P, 75000 C) all on one maturity total_strikes = sum(len(m.strikes) for m in surface.maturities) assert total_strikes == 2 @@ -68,8 +68,8 @@ async def test_loader_skips_option_missing_from_instruments( ghost = "BTC-10APR26-67500-P" assert any(o["instrument_name"] == ghost for o in options) - loader = await deribit_cli.volatility_surface_loader("btc") - surface = loader.surface(ref_date=ref_date) + loader = await deribit_cli.volatility_surface_loader("btc", ref_date=ref_date) + surface = loader.surface() all_strikes = { strike.strike for mat in surface.maturities for strike in mat.strikes } @@ -82,6 +82,6 @@ async def test_loader_skips_future_missing_from_instruments( """Futures absent from the instruments list are silently skipped.""" assert any(f["instrument_name"] == "BTC-GHOST-26" for f in futures) - loader = await deribit_cli.volatility_surface_loader("btc") - surface = loader.surface(ref_date=ref_date) + loader = await deribit_cli.volatility_surface_loader("btc", ref_date=ref_date) + surface = loader.surface() assert surface is not None diff --git a/quantflow_tests/test_data_yahoo.py b/quantflow_tests/test_data_yahoo.py index d3ee18a..c958fe0 100644 --- a/quantflow_tests/test_data_yahoo.py +++ b/quantflow_tests/test_data_yahoo.py @@ -32,6 +32,7 @@ async def test_loader_builds_surface(yahoo_cli: Yahoo, spx_chain: dict) -> None: surface = loader.surface() assert surface.asset == "^SPX" assert len(surface.maturities) == len(spx_chain["options"]) + assert surface.spot is not None assert surface.spot.mid > 0 diff --git a/quantflow_tests/test_implied_fwd.py b/quantflow_tests/test_implied_fwd.py deleted file mode 100644 index fbb71cd..0000000 --- a/quantflow_tests/test_implied_fwd.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Tests for ImpliedFwdPrice.aggregate""" - -from __future__ import annotations - -from datetime import datetime, timezone -from decimal import Decimal - -import pytest -from hypothesis import given -from hypothesis import strategies as st - -from quantflow.options.inputs import DefaultVolSecurity -from quantflow.options.surface import FwdPrice, ImpliedFwdPrice - -MATURITY = datetime(2026, 12, 31, tzinfo=timezone.utc) - - -def make_implied( - mid: float, spread_bp: float, strike: float | None = None -) -> ImpliedFwdPrice: - mid_d = Decimal(str(round(mid, 6))) - half_spread = Decimal(str(round(mid * spread_bp / 20000, 8))) - return ImpliedFwdPrice( - security=DefaultVolSecurity.forward(), - bid=mid_d - half_spread, - ask=mid_d + half_spread, - strike=Decimal(str(round(strike if strike is not None else mid, 6))), - maturity=MATURITY, - ) - - -def make_fwd(mid: float, spread_bp: float) -> FwdPrice: - mid_d = Decimal(str(round(mid, 6))) - half_spread = Decimal(str(round(mid * spread_bp / 20000, 8))) - return FwdPrice( - security=DefaultVolSecurity.forward(), - bid=mid_d - half_spread, - ask=mid_d + half_spread, - maturity=MATURITY, - ) - - -def test_aggregate_empty_no_default_returns_none() -> None: - assert ImpliedFwdPrice.aggregate([], ttm=1.0) is None - - -def test_aggregate_empty_with_default_returns_default() -> None: - default = make_fwd(100, 20) - assert ImpliedFwdPrice.aggregate([], ttm=1.0, default=default) is default - - -def make_implied_market( - theoretical_forward: float, - mid_fraction: float, - spread_multiplier: float, - strike_fraction: float, -) -> ImpliedFwdPrice: - """Implied forward whose spread scales with distance from theoretical_forward.""" - mid = theoretical_forward * mid_fraction - spread_bp = max( - 1.0, - abs(mid - theoretical_forward) - / theoretical_forward - * 10000 - * spread_multiplier, - ) - return make_implied(mid, spread_bp, strike=theoretical_forward * strike_fraction) - - -def test_aggregate_all_invalid_returns_default() -> None: - invalid = ImpliedFwdPrice( - security=DefaultVolSecurity.forward(), - bid=Decimal("101"), - ask=Decimal("99"), # bid > ask - strike=Decimal("100"), - maturity=MATURITY, - ) - default = make_fwd(100, 20) - assert ImpliedFwdPrice.aggregate([invalid], ttm=1.0, default=default) is default - - -@given( - theoretical_forward=st.floats( - min_value=100.0, max_value=100_000.0, allow_nan=False, allow_infinity=False - ), - anchor=st.floats( - min_value=0.9, max_value=0.99, allow_nan=False, allow_infinity=False - ), - mid_fractions=st.lists( - st.floats( - min_value=0.95, max_value=1.05, allow_nan=False, allow_infinity=False - ), - min_size=2, - max_size=8, - ), - spread_multipliers=st.lists( - st.floats(min_value=0.5, max_value=3.0, allow_nan=False, allow_infinity=False), - min_size=2, - max_size=8, - ), - strike_fractions=st.lists( - st.floats(min_value=0.8, max_value=1.2, allow_nan=False, allow_infinity=False), - min_size=2, - max_size=8, - ), - ttm=st.floats(min_value=0.1, max_value=2.0, allow_nan=False, allow_infinity=False), -) -def test_aggregate_with_previous_forward( - theoretical_forward: float, - anchor: float, - mid_fractions: list[float], - spread_multipliers: list[float], - strike_fractions: list[float], - ttm: float, -) -> None: - previous_forward = Decimal(str(round(theoretical_forward * anchor, 4))) - n = min(len(mid_fractions), len(spread_multipliers), len(strike_fractions)) - forwards = [ - make_implied_market( - theoretical_forward, - mid_fractions[i], - spread_multipliers[i], - strike_fractions[i], - ) - for i in range(n) - ] - result = ImpliedFwdPrice.aggregate( - forwards, ttm=ttm, previous_forward=previous_forward - ) - assert result is not None - assert result.is_valid() - mids = [float(f.mid) for f in forwards if f.is_valid()] - assert min(mids) - 1e-4 <= float(result.mid) <= max(mids) + 1e-4 - - -def test_aggregate_implied_tighter_than_default_uses_implied() -> None: - # implied forwards (5 bp) are tighter than the default (20 bp) - # the default is not included in the candidate pool, result comes from implied - forwards = [make_implied(100, 5), make_implied(100, 5)] - default = make_fwd(100, 20) - result = ImpliedFwdPrice.aggregate(forwards, ttm=1.0, default=default) - assert result is not None - assert result is not default - assert float(result.mid) == pytest.approx(100.0, rel=1e-3) - - -def test_aggregate_previous_forward_pulls_result_toward_anchor() -> None: - # two forwards: one at 90 (the anchor), one at 110, same tight spread - # without previous_forward: result ≈ 100 (equal weights) - # with previous_forward=90: forward at 90 gets proximity_weight=1, - # forward at 110 is penalised → result pulled below 100 - fwd_at_anchor = make_implied(90, 5) - fwd_above = make_implied(110, 5) - previous_forward = Decimal("90") - - result_without = ImpliedFwdPrice.aggregate([fwd_at_anchor, fwd_above], ttm=1.0) - result_with = ImpliedFwdPrice.aggregate( - [fwd_at_anchor, fwd_above], ttm=1.0, previous_forward=previous_forward - ) - assert result_without is not None - assert result_with is not None - assert float(result_with.mid) < float(result_without.mid) - - -def test_aggregate_outlier_with_wide_spread_does_not_move_result() -> None: - # three tight forwards near 100, one outlier far away with enormous spread - tight = [make_implied(100, 5) for _ in range(3)] - outlier = make_implied(200, 500) - result_without_outlier = ImpliedFwdPrice.aggregate(tight, ttm=1.0) - result_with_outlier = ImpliedFwdPrice.aggregate(tight + [outlier], ttm=1.0) - assert result_without_outlier is not None - assert result_with_outlier is not None - assert ( - abs(float(result_with_outlier.mid) - float(result_without_outlier.mid)) < 0.01 - ) diff --git a/quantflow_tests/test_non_inverse_surface.py b/quantflow_tests/test_non_inverse_surface.py index 309ba98..6689b09 100644 --- a/quantflow_tests/test_non_inverse_surface.py +++ b/quantflow_tests/test_non_inverse_surface.py @@ -17,6 +17,7 @@ from quantflow.options.bs import black_price from quantflow.options.inputs import DefaultVolSecurity, OptionType from quantflow.options.surface import VolSurfaceLoader +from quantflow.rates.yield_curve import NoDiscount REF_DATE = datetime(2026, 1, 1, tzinfo=timezone.utc) MATURITY = datetime(2026, 7, 2, tzinfo=timezone.utc) # roughly 0.5y @@ -34,7 +35,11 @@ def _black_mid_usd(strike: float, call_put: int, ttm: float) -> Decimal: def _build_loader(ttm: float) -> VolSurfaceLoader: - loader = VolSurfaceLoader(asset="TEST") + loader = VolSurfaceLoader( + asset="TEST", + quote_curve=NoDiscount(ref_date=REF_DATE), + asset_curve=NoDiscount(ref_date=REF_DATE), + ) loader.add_spot( DefaultVolSecurity.spot(), bid=Decimal(str(FORWARD)), @@ -61,7 +66,7 @@ def _build_loader(ttm: float) -> VolSurfaceLoader: def test_loader_recovers_forward_via_parity() -> None: """With matched call/put prices the implied forward equals the true forward.""" loader = _build_loader(ttm=0.5) - surface = loader.surface(ref_date=REF_DATE) + surface = loader.surface() cross = surface.maturities[0] assert float(cross.forward.mid) == pytest.approx(FORWARD, rel=1e-6) @@ -69,12 +74,12 @@ def test_loader_recovers_forward_via_parity() -> None: def test_bs_recovers_input_volatility() -> None: """`bs()` inverts the synthetic non-inverse prices back to the input sigma.""" loader = _build_loader(ttm=0.5) - surface = loader.surface(ref_date=REF_DATE) + surface = loader.surface() ttm = surface.maturities[0].ttm(surface.ref_date) # rebuild prices at the actual ttm so the inversion is not biased by the # slight day-count drift from our nominal 0.5y target. loader = _build_loader(ttm=ttm) - surface = loader.surface(ref_date=REF_DATE) + surface = loader.surface() surface.bs() options = list(surface.option_prices(converged=True)) assert options, "expected converged options on the synthetic surface" @@ -85,10 +90,10 @@ def test_bs_recovers_input_volatility() -> None: def test_non_inverse_price_in_forward_space_matches_black() -> None: """`price_in_forward_space` is the Black forward-space price.""" loader = _build_loader(ttm=0.5) - surface = loader.surface(ref_date=REF_DATE) + surface = loader.surface() ttm = surface.maturities[0].ttm(surface.ref_date) loader = _build_loader(ttm=ttm) - surface = loader.surface(ref_date=REF_DATE) + surface = loader.surface() for option in surface.option_prices(): log_strike = float(option.log_strike) call_put = 1 if option.option_type.is_call() else -1 From a484628d1243274074f61be94b251451d230af07 Mon Sep 17 00:00:00 2001 From: Luca Date: Thu, 21 May 2026 11:16:26 +0100 Subject: [PATCH 6/8] vol surface in examples --- .github/copilot-instructions.md | 24 +- .gitignore | 3 + Makefile | 12 +- app/__main__.py | 60 +- app/api/__init__.py | 0 app/api/deps.py | 54 + app/api/rates.py | 54 + app/api/status.py | 13 + app/api/volatility.py | 74 + app/frontend/observablehq.config.js | 22 + app/frontend/package-lock.json | 5062 +++++++++++++++ app/frontend/package.json | 18 + app/frontend/src/index.md | 13 + app/frontend/src/lib/api.ts | 15 + app/frontend/src/style.css | 74 + app/frontend/src/volatility-surface.md | 184 + app/frontend/src/yield-curve.md | 142 + app/volatility_surface.py | 127 - dev/api-serve | 15 + dev/docs-serve | 16 + dev/frontend-serve | 8 + dev/mkdocs-serve | 12 + dev/serve-info | 10 + docs/api/rates/index.md | 2 + docs/api/rates/options.md | 5 + docs/examples/fixtures/volsurface_btc.json | 5505 ++++++++-------- docs/examples/fixtures/volsurface_eth.json | 6681 +++++++++----------- mkdocs.yml | 1 + pyproject.toml | 7 + quantflow/data/deribit.py | 18 - quantflow/options/pricer.py | 2 +- quantflow/options/surface.py | 206 +- quantflow/rates/__init__.py | 4 + quantflow/rates/nelson_siegel.py | 169 +- quantflow/rates/options.py | 183 + quantflow/rates/vasicek.py | 71 +- quantflow/rates/yield_curve.py | 116 +- quantflow/utils/text.py | 23 + quantflow/utils/types.py | 4 + quantflow_tests/test_nelson_siegel.py | 198 + quantflow_tests/test_options.py | 5 +- quantflow_tests/test_rates.py | 131 - 42 files changed, 12480 insertions(+), 6863 deletions(-) create mode 100644 app/api/__init__.py create mode 100644 app/api/deps.py create mode 100644 app/api/rates.py create mode 100644 app/api/status.py create mode 100644 app/api/volatility.py create mode 100644 app/frontend/observablehq.config.js create mode 100644 app/frontend/package-lock.json create mode 100644 app/frontend/package.json create mode 100644 app/frontend/src/index.md create mode 100644 app/frontend/src/lib/api.ts create mode 100644 app/frontend/src/style.css create mode 100644 app/frontend/src/volatility-surface.md create mode 100644 app/frontend/src/yield-curve.md delete mode 100644 app/volatility_surface.py create mode 100755 dev/api-serve create mode 100755 dev/docs-serve create mode 100755 dev/frontend-serve create mode 100755 dev/mkdocs-serve create mode 100644 dev/serve-info create mode 100644 docs/api/rates/options.md create mode 100644 quantflow/rates/options.py create mode 100644 quantflow/utils/text.py create mode 100644 quantflow_tests/test_nelson_siegel.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 704f9fb..1a46fb3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -12,7 +12,17 @@ applyTo: '/**' * Always run `make lint` after code changes — runs taplo, isort, black, ruff, and mypy * Never edit `readme.md` directly — it is generated from `docs/index.md` via `make docs` -* To install all dependencies (including all optional extras) run `make install-dev` — runs `uv sync --all-extras` +* To install all dependencies (including all optional extras) run `make install-dev` +* Do not modify code unless the developer explicitly asks for a code change. +* Never change code that works unless you have been asked by the developer to do so, + or you have a good reason to believe the code is wrong. +* Concentrate on fixing the problem, not on making the code look nice unless you are + extremely confident that your code style is better than the original and that the original code style + is not serving a purpose (e.g. readability, consistency with other code, etc.). + If you are unsure, ask the developer or leave the code style as it is. + +## Run Tests + * To run all tests use `make test` — runs all tests in the `tests/` directory using pytest * To run a specific test file, use `uv run pytest tests/path/to/test_file.py` @@ -29,16 +39,26 @@ applyTo: '/**' * Documentation is built using [mkdocs](https://www.mkdocs.org/) and stored in the `docs/` directory. The documentation source files are written in markdown format. * Split prose into short paragraphs (one idea per paragraph) separated by blank lines. Never write a wall-of-text paragraph that strings together mechanism, rationale, caveats and usage advice. This applies to mkdocs tutorials, theory pages and long docstrings. * Do not use dashes (em dashes, en dashes, or hyphens used as dashes) in documentation files or docstrings. Use colons, parentheses, or restructure the sentence instead. +* Always use `Annotated(..., Doc("..."))` for docstrings in code, never use triple-quoted strings below the definition of a function or class. For example: + ```python + from typing_extensions import Annotated, Doc + + def foo(x: Annotated[int, Doc("This is the docstring for x")]) -> float: + """This is the docstring for foo""" + return float(x) + ``` +* Do not use Docstrings with markdown text that may genereate headings (e.g. `# Heading`, `## Heading`, etc.) * Math in documentation and docstrings: always use `\begin{equation}...\end{equation}` for any formula or equation. Use `$...$` only for brief inline references to variables (e.g. $F$, $K$). Do not use `$$...$$`, `` `...` ``, or RST syntax (`.. math::`, `:math:`). * Math notation convention: use $\Phi$ for the characteristic function and $\phi$ for the characteristic exponent, where $\Phi = e^{-\phi}$. * Glossary entries in `docs/glossary.md` must be kept in alphabetical order. * Do not repeat concept definitions inline in tutorials or docstrings, link to the glossary instead using a relative markdown link (e.g. `[moneyness](../glossary.md#moneyness)`). +* Use relative links for all mkdocs page links (e.g. `[Option Pricing](../theory/option_pricing.md)`) — prefer relative over absolute URLs to keep links shorter and portable. * Prefer mkdocstrings relative cross-references whenever the target is visible from the current scope: write `[label][.member]` (same class) or `[label][..Sibling]` (same module) instead of repeating the fully-qualified path. Use the full path only when the target lives in a different module than the current docstring. * To rebuild doc examples run `uv run ./dev/build-examples` — runs all scripts in `docs/examples/` and writes their output to `docs/examples_output/` ## Pydantic models -* Always document Pydantic fields with `Field(description=...)` — never use a docstring below a field assignment +* Always document Pydantic fields with `Field(description=...)`, never use a docstring below a field assignment * Split long description strings across lines using implicit string concatenation rather than shortening the text * When a docstring line exceeds the line length limit, split it across multiple lines rather than shortening the text diff --git a/.gitignore b/.gitignore index cd81b87..b6d9e48 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,8 @@ _build # builds app/docs +app/examples +app/frontend/node_modules +app/frontend/src/.observablehq docs/assets/examples docs/examples/output diff --git a/Makefile b/Makefile index 269b988..18d7f9b 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,16 @@ docs-examples: ## Regenerate docs examples @uv run ./dev/build-examples .PHONY: docs-serve -docs-serve: ## serve documentation - @uv run mkdocs serve --livereload --watch quantflow --watch docs +docs-serve: ## serve docs, examples, and API with auto-reload + @bash ./dev/docs-serve + +.PHONY: frontend-build +frontend-build: ## build Observable frontend examples + @npm --prefix app/frontend run build + +.PHONY: frontend-serve +frontend-serve: ## serve Observable frontend with auto-reload + @bash ./dev/frontend-serve .PHONY: install-dev install-dev: ## Install development dependencies diff --git a/app/__main__.py b/app/__main__.py index 46ff3c5..9e32f8a 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -1,14 +1,22 @@ import os -from pathlib import Path import marimo from fastapi import APIRouter, FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.openapi.docs import get_redoc_html +from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles +from fluid.utils import log from app.utils.paths import APP_PATH, head_snippet +from quantflow import __version__ + +from .api.deps import instrument_app +from .api.rates import rates_router +from .api.status import status_router +from .api.volatility import volatility_router PORT = int(os.environ.get("MICRO_SERVICE_PORT", "8001")) -status_router = APIRouter() def crate_app() -> FastAPI: @@ -20,26 +28,46 @@ def crate_app() -> FastAPI: continue dashed = path.stem.replace("_", "-") server = server.with_app(path=f"/{dashed}", root=f"./app/{path.name}") - # Create a FastAPI app - app = FastAPI() - app.include_router(status_router) - app.mount("/examples", server.build()) - app.mount("/", StaticFiles(directory=APP_PATH / "docs", html=True), name="static") - return app - + app = FastAPI( + version=__version__, + title="Quantflow API", + description="API for Quantflow", + ) + instrument_app(app) + cors_origins = [ + origin.strip() + for origin in os.environ.get("QUANTFLOW_CORS_ORIGINS", "").split(",") + if origin.strip() + ] + if cors_origins: + app.add_middleware( + CORSMiddleware, + allow_origins=cors_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) -@status_router.get("/status") -async def service_status() -> dict: - return {"status": "ok"} + api = APIRouter(prefix="/.api") + @api.get("/redoc", include_in_schema=False) + async def api_redoc() -> HTMLResponse: + return get_redoc_html( + openapi_url="/openapi.json", + title="Quantflow API", + ) -@status_router.get("/ready") -async def service_ready() -> dict: - return {"status": "ok"} + api.include_router(rates_router) + api.include_router(volatility_router) + app.include_router(api) + app.include_router(status_router, include_in_schema=False) + app.mount("/examples", server.build()) + app.mount("/", StaticFiles(directory=APP_PATH / "docs", html=True), name="static") + return app # Run the server if __name__ == "__main__": import uvicorn - + log.config() uvicorn.run(crate_app(), host="0.0.0.0", port=PORT) diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/deps.py b/app/api/deps.py new file mode 100644 index 0000000..52c219d --- /dev/null +++ b/app/api/deps.py @@ -0,0 +1,54 @@ +import json +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Annotated, Generic, TypeVar, cast +from fluid.utils import log +from fastapi import Depends, FastAPI, Request +from fluid.utils.redis import FluidRedis +from pydantic import BaseModel +from redis import Redis + +M = TypeVar("M", bound=BaseModel) +logger = log.get_logger(__name__) + + +def instrument_app(app: FastAPI) -> None: + """Instrument the app with the necessary dependencies""" + redis = FluidRedis.create() + app.state.redis = redis.redis_cli + app.router.on_shutdown.append(redis.close) + + +def get_redis(request: Request) -> Redis: + """Get the redis client from the app state""" + if not hasattr(request.app.state, "redis"): + raise RuntimeError("Redis client not found in app state") + return cast(Redis, request.app.state.redis) + + +@dataclass +class RedisCache(Generic[M]): + redis: Redis + Model: type[M] + key: str + ttl: int = 60 + + async def from_cache(self, loader: Callable[[], Awaitable[M]]) -> M: + """Get a value from the cache""" + value = await self.redis.get(self.key) + if value is None: + return await self.set_cache(await loader()) + try: + return self.Model.model_validate_json(value) + except json.JSONDecodeError: + logger.exception(f"Failed to decode cache value for key {self.key}") + return await self.set_cache(await loader()) + + async def set_cache(self, value: M) -> M: + """Set a value in the cache""" + payload = value.model_dump_json() + await self.redis.set(self.key, payload, ex=self.ttl) + return value + + +RedisDep = Annotated[Redis, Depends(get_redis)] diff --git a/app/api/rates.py b/app/api/rates.py new file mode 100644 index 0000000..6fb2345 --- /dev/null +++ b/app/api/rates.py @@ -0,0 +1,54 @@ +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field +import numpy as np +from quantflow.rates import AnyYieldCurve, YieldCurve + +rates_router = APIRouter() + + +class YieldCurveResponse(BaseModel): + curve: AnyYieldCurve = Field(description="The fitted yield curve") + ttm: list[float] = Field( + description="List of time to maturities for the fitted curve" + ) + rates: list[float] = Field(description="List of rates for the fitted curve") + + +@rates_router.get("/yield-curve") +async def yield_curve( + ttm: list[float] = Query( + ..., description="List of time to maturities corresponding to the rates" + ), + rates: list[float] = Query( + ..., description="List of rates to fit the Nelson-Siegel model" + ), + curve_type: str = Query( + "nelson_siegel", + description="Type of curve to fit", + enum=list(YieldCurve.curve_types()), + ), + max_ttm: float | None = Query( + None, + description=( + "Maximum time to maturity to consider for returning" + " the fitted curve. If not provided, the curve will be returned for all " + "time to maturities." + ), + ), + num_points: int = Query( + 100, + description=( + "Number of points to return for the fitted curve. Only used if max_ttm is " + "provided. Otherwise, the curve will be returned for all time " + "to maturities." + ), + ), +) -> YieldCurveResponse: + curve_class = YieldCurve.get_curve_class(curve_type) + if curve_class is None: + raise ValueError(f"Unsupported curve type: {curve_type}") + curve = curve_class.calibrate(ttm, rates) + if max_ttm is not None: + ttm = list(np.geomspace(1 / 365, max_ttm, num_points)) + rates = list(curve.continuously_compounded_rate(ttm)) + return YieldCurveResponse(curve=curve, ttm=ttm, rates=rates) diff --git a/app/api/status.py b/app/api/status.py new file mode 100644 index 0000000..6df04ed --- /dev/null +++ b/app/api/status.py @@ -0,0 +1,13 @@ +from fastapi import APIRouter + +status_router = APIRouter() + + +@status_router.get("/status") +async def service_status() -> dict: + return {"status": "ok"} + + +@status_router.get("/ready") +async def service_ready() -> dict: + return {"status": "ok"} diff --git a/app/api/volatility.py b/app/api/volatility.py new file mode 100644 index 0000000..619fd67 --- /dev/null +++ b/app/api/volatility.py @@ -0,0 +1,74 @@ +from functools import partial + +import numpy as np +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.data.deribit import Deribit +from quantflow.options.inputs import VolSurfaceInputs +from quantflow.options.surface import OptionInfo +from quantflow.rates.nelson_siegel import NelsonSiegel + +from .deps import RedisCache, RedisDep +from .rates import YieldCurveResponse + +volatility_router = APIRouter() + + +class VolSurfaceResponse(BaseModel): + inputs: VolSurfaceInputs = Field(description="Volatility surface inputs") + options: list[OptionInfo] = Field( + description="List of option info with implied volatilities" + ) + quote_curve: YieldCurveResponse = Field( + description="Quote discount curve with rates" + ) + asset_curve: YieldCurveResponse = Field( + description="Asset discount curve with rates" + ) + + +@volatility_router.get("/volatility-surface") +async def volatility_surface( + redis: RedisDep, + asset: str = Query( + "btc", + description="Crypto asset", + enum=["btc", "eth"], + ), +) -> VolSurfaceResponse: + cache = RedisCache( + redis=redis, + Model=VolSurfaceResponse, + key=f"vol_surface:{asset}", + ) + return await cache.from_cache(partial(_volatility_surface, asset)) + + +def _curve_response(curve, max_ttm: float) -> YieldCurveResponse: + ttm = list(np.linspace(1 / 365, max_ttm, 50)) + rates = list(curve.continuously_compounded_rate(ttm)) + return YieldCurveResponse(curve=curve, ttm=ttm, rates=rates) + + +async def _volatility_surface(asset: str) -> VolSurfaceResponse: + inverse = asset != "sol" + async with Deribit() as cli: + loader = await cli.volatility_surface_loader(asset, inverse=inverse) + + loader.calibrate_curves(quote_curve=NelsonSiegel) + surface = loader.surface() + surface.bs() + surface.disable_outliers() + + inputs = surface.inputs(converged=True) + options = [op.info() for op in surface.option_prices(converged=True)] + + max_ttm = max(float(op.ttm) for op in options) if options else 1.0 + + return VolSurfaceResponse( + inputs=inputs, + options=options, + quote_curve=_curve_response(surface.quote_curve, max_ttm), + asset_curve=_curve_response(surface.asset_curve, max_ttm), + ) diff --git a/app/frontend/observablehq.config.js b/app/frontend/observablehq.config.js new file mode 100644 index 0000000..bb2182a --- /dev/null +++ b/app/frontend/observablehq.config.js @@ -0,0 +1,22 @@ +const apiOrigin = process.env.QUANTFLOW_API_ORIGIN || ""; + +export default { + title: "Quantflow Examples", + root: "src", + output: "../examples", + base: "/examples", + head: ``, + style: "style.css", + pages: [{name: "Volatility Surface", path: "/volatility-surface"}, {name: "Yield Curve", path: "/yield-curve"}], + header: ` + + `, + footer: "Quantflow live examples" +}; diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json new file mode 100644 index 0000000..3e3a46d --- /dev/null +++ b/app/frontend/package-lock.json @@ -0,0 +1,5062 @@ +{ + "name": "quantflow-examples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "quantflow-examples", + "dependencies": { + "@observablehq/framework": "^1.13.4", + "@observablehq/plot": "^0.6.17", + "d3": "^7.9.0" + }, + "devDependencies": { + "concurrently": "^9.2.1", + "typescript": "^5.9.3" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.2.tgz", + "integrity": "sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^2.3.1", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "node_modules/@clack/core": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", + "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", + "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "license": "MIT", + "dependencies": { + "@clack/core": "^0.3.3", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@observablehq/framework": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@observablehq/framework/-/framework-1.13.4.tgz", + "integrity": "sha512-/qSEsd1R0n7wgVQHBpF8slShQhB2TDAWma1AllddYb3e1T72mm6dMSL3PaVUNx3Tx2lM3wXjPxcAH431yvdTHw==", + "license": "ISC", + "dependencies": { + "@clack/prompts": "^0.7.0", + "@observablehq/inputs": "^0.12.0", + "@observablehq/inspector": "^5.0.1", + "@observablehq/runtime": "^6.0.0", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-virtual": "^3.0.2", + "@sindresorhus/slugify": "^2.2.1", + "acorn": "^8.11.2", + "acorn-walk": "^8.3.0", + "ci-info": "^4.0.0", + "cross-spawn": "^7.0.3", + "d3-array": "^3.2.4", + "d3-hierarchy": "^3.1.2", + "esbuild": "^0.27.3", + "fast-array-diff": "^1.1.0", + "fast-deep-equal": "^3.1.3", + "glob": "^10.3.10", + "gray-matter": "^4.0.3", + "he": "^1.2.0", + "highlight.js": "^11.8.0", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "jsdom": "^23.2.0", + "jszip": "^3.10.1", + "markdown-it": "^14.0.0", + "markdown-it-anchor": "^8.6.7", + "mime": "^4.0.0", + "minisearch": "^6.3.0", + "open": "^10.1.0", + "picocolors": "^1.1.1", + "pkg-dir": "^8.0.0", + "resolve.exports": "^2.0.2", + "rollup": "^4.6.0", + "rollup-plugin-esbuild": "^6.1.0", + "semver": "^7.5.4", + "send": "^0.19.0", + "tar": "^7.5.9", + "tar-stream": "^3.1.6", + "tsx": "^4.7.1", + "untildify": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.14.2" + }, + "bin": { + "observable": "dist/bin/observable.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@observablehq/inputs": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@observablehq/inputs/-/inputs-0.12.0.tgz", + "integrity": "sha512-1ln7+PYe31cMx00K9awVbiCscQM0THnXRJ/AEzd+FfTA25Gu3KRWknAGECxU49QzHyKqiXpLl5LCg3XtYm70eQ==", + "license": "ISC", + "dependencies": { + "htl": "^0.3.1", + "isoformat": "^0.2.0" + }, + "engines": { + "node": ">=14.5.0" + } + }, + "node_modules/@observablehq/inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@observablehq/inspector/-/inspector-5.0.1.tgz", + "integrity": "sha512-euwWxwDa6KccU4G3D2JBD7GI/2McJh/z7HHEzJKbj2TDa7zhI37eTbTxiwE9rgTWBagvVBel+hAmnJRYBYOv2Q==", + "license": "ISC", + "dependencies": { + "isoformat": "^0.2.0" + } + }, + "node_modules/@observablehq/plot": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz", + "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", + "license": "ISC", + "dependencies": { + "d3": "^7.9.0", + "interval-tree-1d": "^1.0.0", + "isoformat": "^0.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@observablehq/runtime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@observablehq/runtime/-/runtime-6.0.0.tgz", + "integrity": "sha512-t3UXP69O0JK20HY/neF4/DDDSDorwo92As806Y1pNTgTmj1NtoPyVpesYzfH31gTFOFrXC2cArV+wLpebMk9eA==", + "license": "ISC" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", + "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-search-bounds": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-array-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-array-diff/-/fast-array-diff-1.1.0.tgz", + "integrity": "sha512-muSPyZa/yHCoDQhah9th57AmLENB1nekbrUoLAqOpQXdl1Kw8VbH24Syl5XLscaQJlx7KRU95bfTDPvVB5BJvw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/htl": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/htl/-/htl-0.3.1.tgz", + "integrity": "sha512-1LBtd+XhSc+++jpOOt0lCcEycXs/zTQSupOISnVAUmvGBpV7DH+C2M6hwV7zWYfpTMMg9ch4NO0lHiOTAMHdVA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/interval-tree-1d": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", + "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", + "license": "MIT", + "dependencies": { + "binary-search-bounds": "^2.0.0" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isoformat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", + "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", + "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^2.0.1", + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "license": "MIT" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-8.0.0.tgz", + "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-esbuild": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.2.1.tgz", + "integrity": "sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "get-tsconfig": "^4.10.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14.18.0" + }, + "peerDependencies": { + "esbuild": ">=0.18.0", + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tar": { + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz", + "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/untildify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-5.0.0.tgz", + "integrity": "sha512-bOgQLUnd2G5rhzaTvh1VCI9Fo6bC5cLTpH17T5aFfamyXFYDbbdzN6IXdeoc3jBS7T9hNTmJtYUzJCJ2Xlc9gA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/app/frontend/package.json b/app/frontend/package.json new file mode 100644 index 0000000..856def3 --- /dev/null +++ b/app/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "quantflow-examples", + "private": true, + "type": "module", + "scripts": { + "build": "observable build", + "dev": "observable preview --host 0.0.0.0" + }, + "dependencies": { + "@observablehq/framework": "^1.13.4", + "@observablehq/plot": "^0.6.17", + "d3": "^7.9.0" + }, + "devDependencies": { + "concurrently": "^9.2.1", + "typescript": "^5.9.3" + } +} diff --git a/app/frontend/src/index.md b/app/frontend/src/index.md new file mode 100644 index 0000000..62e1d76 --- /dev/null +++ b/app/frontend/src/index.md @@ -0,0 +1,13 @@ +--- +title: Quantflow examples +--- + +# Quantflow examples + +Interactive examples are built as static Observable pages and use the Quantflow +FastAPI service for Python computations and market data. + + diff --git a/app/frontend/src/lib/api.ts b/app/frontend/src/lib/api.ts new file mode 100644 index 0000000..8ff581f --- /dev/null +++ b/app/frontend/src/lib/api.ts @@ -0,0 +1,15 @@ +function apiOrigin(): string { + return ( + document + .querySelector('meta[name="quantflow-api-origin"]') + ?.getAttribute("content") || "" + ); +} + +export async function fetchJson(path: string): Promise { + const response = await fetch(`${apiOrigin()}${path}`); + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText}: ${path}`); + } + return response.json() as Promise; +} diff --git a/app/frontend/src/style.css b/app/frontend/src/style.css new file mode 100644 index 0000000..1ef07c7 --- /dev/null +++ b/app/frontend/src/style.css @@ -0,0 +1,74 @@ +@import url("observablehq:default.css"); +@import url("observablehq:theme-slate.css"); + +:root { + --observablehq-header-height: 3rem; + --theme-background-b: #1E2129; + --theme-foreground: #ffffffd1; + --theme-foreground-focus: #00ccb8; + --qf-primary: #009688; + --qf-accent: #ffc107; + font-family: Roboto, Helvetica, Arial, sans-serif; +} + +#observablehq-header { + background: var(--qf-primary); + padding: 0; + margin: 0; + left: 0 !important; + right: 0 !important; + border-bottom: none; +} + +.qf-header-inner { + display: flex; + align-items: center; + padding: 0.4rem 0.8rem; +} + +.qf-header-logo { + display: flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + color: #fff; +} + +.qf-header-logo img { + height: 1.5rem; + width: auto; +} + +.qf-header-title { + font-size: 0.9rem; + font-weight: 700; + white-space: nowrap; +} + +.qf-header-spacer { + flex: 1; +} + +.qf-header-link { + color: #ffffffcc; + font-size: 0.8rem; + text-decoration: none; +} + +.qf-header-link:hover { + color: #fff; +} + +.card { + border: 1px solid var(--theme-foreground-faintest); + border-radius: 0.4rem; + color: var(--theme-foreground); + display: block; + font-weight: 600; + padding: 1rem; + text-decoration: none; +} + +.card:hover { + border-color: var(--qf-primary); +} diff --git a/app/frontend/src/volatility-surface.md b/app/frontend/src/volatility-surface.md new file mode 100644 index 0000000..c7f31d2 --- /dev/null +++ b/app/frontend/src/volatility-surface.md @@ -0,0 +1,184 @@ +--- +title: Volatility Surface +--- + +# Volatility Surface + +Live implied volatility surface from [Deribit](https://www.deribit.com/) options market data. Select an asset to load the current vol surface. + +```js +import {fetchJson} from "./lib/api.js"; +import * as Plot from "npm:@observablehq/plot"; +import * as d3 from "npm:d3"; +``` + +```js +const assetInput = Inputs.select(["btc", "eth"], {label: "Asset", value: "btc"}); +const asset = Generators.input(assetInput); +``` + +```js +display(assetInput); +``` + +```js +const data = await fetchJson(`/.api/volatility-surface?asset=${asset}`); +``` + +```js +// Options come pre-computed from the API with all fields +const options = data.options; + +// Unique maturities sorted by date +const maturities = [...new Set(options.map(d => d.maturity))].sort(); + +// Parse numeric fields (API returns Decimals as strings) +const enriched = options.map(d => ({ + ...d, + strike: parseFloat(d.strike), + forward: parseFloat(d.forward), + log_strike: parseFloat(d.log_strike), + moneyness: parseFloat(d.moneyness), + ttm: parseFloat(d.ttm), + implied_vol: parseFloat(d.implied_vol), + price_bp: parseFloat(d.price_bp), + open_interest: parseFloat(d.open_interest), + volume: parseFloat(d.volume), +})); + +// Get spot from inputs +const spotInput = data.inputs.inputs.find(d => d.security_type === "spot"); +const spotMid = spotInput ? (parseFloat(spotInput.bid) + parseFloat(spotInput.ask)) / 2 : null; +``` + +```js +const refDate = new Date(data.inputs.quote_curve.ref_date); +const formatDate = d3.utcFormat("%d %b %Y %H:%M:%S UTC"); +display(html`

${formatDate(refDate)} · Spot: ${spotMid ? d3.format(",.0f")(spotMid) : "N/A"} USD · ${enriched.length} options across ${maturities.length} maturities

`); +``` + +```js +const downloadInputs = () => { + const blob = new Blob([JSON.stringify(data.inputs, null, 2)], {type: "application/json"}); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `volsurface_${asset}_${d3.utcFormat("%Y%m%d_%H%M%S")(refDate)}.json`; + a.click(); + URL.revokeObjectURL(url); +}; + +display(html``); +``` + +```js +const maturityInput = Inputs.select( + [null, ...maturities], + {label: "Maturity", value: null, format: d => d === null ? "All" : d.slice(0, 10)} +); +const selectedMaturity = Generators.input(maturityInput); + +const xAxisInput = Inputs.select( + ["moneyness", "log_strike", "strike"], + {label: "X-Axis", value: "moneyness", format: d => ({moneyness: "Moneyness", log_strike: "Log Strike", strike: "Strike"}[d])} +); +const xAxis = Generators.input(xAxisInput); +``` + +```js +display(html`
${maturityInput}${xAxisInput}
`); +``` + +## Volatility Smile + +```js +const smileData = selectedMaturity === null + ? enriched + : enriched.filter(d => d.maturity === selectedMaturity); + +const xLabel = {moneyness: "Moneyness (log(K/F) / √T)", log_strike: "Log Strike (log K/F)", strike: "Strike"}[xAxis]; + +display(Plot.plot({ + width: 800, + height: 450, + marginLeft: 60, + marginBottom: 50, + style: {background: "transparent"}, + x: {label: xLabel}, + y: {label: "Implied Volatility", percent: true}, + color: { + type: "ordinal", + domain: maturities, + scheme: "turbo", + legend: selectedMaturity === null, + label: "Maturity", + tickFormat: d => d.slice(5, 10) + }, + marks: [ + Plot.dot(smileData, { + x: xAxis, + y: "implied_vol", + fill: "maturity", + r: 3, + opacity: 0.8, + tip: true + }), + Plot.ruleY([0]), + ...(xAxis === "moneyness" ? [Plot.ruleX([0], {stroke: "var(--theme-foreground-muted)", strokeDasharray: "4,4"})] : []) + ] +})); +``` + +## Volatility Term Structure + +```js +// ATM vol per maturity (closest to moneyness = 0) +const atmByMaturity = maturities.map(m => { + const slice = enriched.filter(d => d.maturity === m); + const atm = slice.reduce((best, d) => Math.abs(d.moneyness) < Math.abs(best.moneyness) ? d : best); + return {maturity: m, implied_vol: atm.implied_vol}; +}); + +display(Plot.plot({ + width: 800, + height: 350, + marginLeft: 60, + marginBottom: 50, + style: {background: "transparent"}, + x: {label: "Maturity", type: "point"}, + y: {label: "ATM Implied Volatility", percent: true}, + marks: [ + Plot.line(atmByMaturity, {x: "maturity", y: "implied_vol", stroke: "var(--theme-foreground-focus)", strokeWidth: 2}), + Plot.dot(atmByMaturity, { + x: "maturity", + y: "implied_vol", + fill: "var(--theme-foreground-focus)", + r: 5, + tip: true + }) + ] +})); +``` + +## Discount Curves + +```js +const quoteCurve = data.quote_curve.ttm.map((t, i) => ({ttm: t, rate: data.quote_curve.rates[i], curve: "Quote"})); +const assetCurve = data.asset_curve.ttm.map((t, i) => ({ttm: t, rate: data.asset_curve.rates[i], curve: "Asset"})); +const curveData = [...quoteCurve, ...assetCurve]; + +display(Plot.plot({ + width: 800, + height: 350, + marginLeft: 60, + marginBottom: 50, + style: {background: "transparent"}, + x: {label: "Time to Maturity (years)"}, + y: {label: "Rate", percent: true}, + color: {legend: true, label: "Curve"}, + marks: [ + Plot.line(curveData, {x: "ttm", y: "rate", stroke: "curve", strokeWidth: 2}), + Plot.ruleY([0], {stroke: "var(--theme-foreground-muted)", strokeDasharray: "4,4"}) + ] +})); +``` diff --git a/app/frontend/src/yield-curve.md b/app/frontend/src/yield-curve.md new file mode 100644 index 0000000..e849836 --- /dev/null +++ b/app/frontend/src/yield-curve.md @@ -0,0 +1,142 @@ +--- +title: Yield Curve +--- + +# Yield Curve + +Fit a yield curve to a set of interest rates. Drag the points to change the input rates. + +```js +import {fetchJson} from "./lib/api.js"; +import * as Plot from "npm:@observablehq/plot"; +import * as d3 from "npm:d3"; +``` + +```js +const curveType = view(Inputs.select(["nelson_siegel", "vasicek_curve"], {label: "Curve type"})); +``` + +```js +const defaultRates = [ + {ttm: 1/365, rate: 0.043}, + {ttm: 7/365, rate: 0.043}, + {ttm: 1/12, rate: 0.044}, + {ttm: 0.25, rate: 0.045}, + {ttm: 0.5, rate: 0.046}, + {ttm: 1, rate: 0.047}, + {ttm: 2, rate: 0.043}, + {ttm: 3, rate: 0.041}, + {ttm: 5, rate: 0.040}, + {ttm: 7, rate: 0.041}, + {ttm: 10, rate: 0.043}, + {ttm: 20, rate: 0.048}, + {ttm: 30, rate: 0.049} +]; + +const inputRates = Mutable(defaultRates); +const setInputRates = (v) => inputRates.value = v; +``` + +```js +const params = new URLSearchParams(); +for (const {ttm, rate} of inputRates) { + params.append("ttm", ttm); + params.append("rates", rate); +} +params.set("curve_type", curveType); +params.set("max_ttm", "30"); +params.set("num_points", "200"); + +const result = await fetchJson(`/.api/yield-curve?${params}`); +``` + +```js +const fittedData = result.ttm + .map((t, i) => ({ttm: t, rate: result.rates[i]})) + .filter(d => d.ttm >= 1/365); +``` + +```js +const width = 640; +const height = 400; +const marginTop = 30; +const marginRight = 20; +const marginBottom = 40; +const marginLeft = 50; + +const x = d3.scaleLog() + .domain([1/365, 32]) + .range([marginLeft, width - marginRight]); + +const y = d3.scaleLinear() + .domain([0.02, 0.06]) + .range([height - marginBottom, marginTop]); + +const svg = d3.create("svg") + .attr("width", width) + .attr("height", height) + .attr("viewBox", [0, 0, width, height]) + .attr("style", "max-width: 100%; height: auto;"); + +svg.append("g") + .attr("transform", `translate(0,${height - marginBottom})`) + .call(d3.axisBottom(x) + .tickValues([1/365, 1/52, 1/12, 0.25, 0.5, 1, 2, 5, 10, 30]) + .tickFormat(d => d < 1/12 ? `${Math.round(d*365)}d` : d < 1 ? `${Math.round(d*12)}m` : `${d}y`) + ) + .append("text") + .attr("x", width / 2) + .attr("y", 35) + .attr("fill", "currentColor") + .attr("text-anchor", "middle") + .text("Time to Maturity (years)"); + +svg.append("g") + .attr("transform", `translate(${marginLeft},0)`) + .call(d3.axisLeft(y).tickFormat(d3.format(".1%"))) + .append("text") + .attr("x", -marginLeft + 10) + .attr("y", marginTop - 15) + .attr("fill", "currentColor") + .attr("text-anchor", "start") + .text("Rate"); + +const line = d3.line() + .x(d => x(d.ttm)) + .y(d => y(d.rate)); + +const curvePath = svg.append("path") + .datum(fittedData) + .attr("fill", "none") + .attr("stroke", "var(--qf-primary)") + .attr("stroke-width", 2) + .attr("d", line); + +const points = [...inputRates]; + +svg.selectAll("circle") + .data(points) + .join("circle") + .attr("cx", d => x(d.ttm)) + .attr("cy", d => y(d.rate)) + .attr("r", 7) + .attr("fill", "var(--qf-accent)") + .attr("cursor", "ns-resize") + .call(d3.drag() + .on("drag", function(event, d) { + const newRate = y.invert(event.y); + const clamped = Math.max(0.001, Math.min(0.1, newRate)); + d.rate = clamped; + d3.select(this).attr("cy", y(clamped)); + }) + .on("end", function() { + setInputRates(points.map(p => ({ttm: p.ttm, rate: p.rate}))); + }) + ); + +display(svg.node()); +``` + +```js +display(result.curve); +``` \ No newline at end of file diff --git a/app/volatility_surface.py b/app/volatility_surface.py deleted file mode 100644 index 593ea10..0000000 --- a/app/volatility_surface.py +++ /dev/null @@ -1,127 +0,0 @@ -import marimo - -__generated_with = "0.23.5" -app = marimo.App(width="medium") - - -@app.cell -def _(): - import marimo as mo - from app.utils import nav_menu - nav_menu() - return (mo,) - - -@app.cell(hide_code=True) -def _(mo): - mo.md(r""" - # Volatility Surface - - In this notebook we illustrate the use of the Volatility Surface tool in the library. We use [deribit](https://docs.deribit.com/) options on ETHUSD as example. - - The library provide a [VolSurfaceLoader](api/options/vol_surface/#quantflow.options.surface.VolSurfaceLoader) for Deribit: - - ```python - import pandas as pd - from quantflow.data.deribit import Deribit - - async with Deribit() as cli: - loader = await cli.volatility_surface_loader("eth", exclude_open_interest=0) - - # calibrate discount curve for the quoting asset (usd) - loader.calibrate_curves(quote_curve=NelsonSiegel) - # build the volatility surface - surface = loader.surface() - # calculate black implied volatilities - surface.bs() - # disable outliers - surface.disable_outliers() - # display inputs - only options with converged implied volatility - surface_inputs = surface.inputs(converged=True) - pd.DataFrame([i.model_dump() for i in surface_inputs.inputs]) - ``` - """) - return - - -@app.cell -def _(mo): - asset = mo.ui.dropdown(["btc", "eth", "sol"], value="btc", label="asset") - inverse = mo.ui.checkbox(value=True, label="Inverse options") - mo.hstack([asset, inverse]) - return asset, inverse - - -@app.cell -async def _(asset, inverse, mo): - import pandas as pd - from quantflow.data.deribit import Deribit - from quantflow.rates.nelson_siegel import NelsonSiegel - - async with Deribit() as cli: - loader = await cli.volatility_surface_loader( - asset.value, - inverse=inverse.value, - use_perp=not inverse.value - ) - - loader.calibrate_curves(quote_curve=NelsonSiegel) - # build the volatility surface - surface = loader.surface() - # calculate black implied volatilities - surface.bs() - # disable outliers - surface.disable_outliers() - # - def int_or_none(v): - try: - return int(v) - except TypeError: - return None - - maturites = [c.maturity for c in surface.maturities] - maturity_dropdown = mo.ui.dropdown( - options={m.strftime("%Y-%m-%d"): i for i, m in enumerate(maturites)}, - label="Maturity" - ) - maturity_dropdown - return int_or_none, maturity_dropdown, surface - - -@app.cell -def _(int_or_none, maturity_dropdown, surface): - index = int_or_none(maturity_dropdown.value) - surface.plot3d(index=index) - return - - -@app.cell -def _(surface): - ts = surface.term_structure() - ts - return (ts,) - - -@app.cell -def _(surface, ts): - import math - ttm_max = 0.1*math.ceil(10*surface.maturities[-1].ttm(surface.ref_date)) - fig = surface.quote_curve.plot(ttm_max=ttm_max) - fig.add_scatter(x=ts["ttm"], y=ts["rate"], mode="markers", name="Cross Sections", marker=dict(size=8, color="orange")) - fig - return - - -@app.cell -def _(surface): - surface.asset_curve.plot(ttm_max=2) - return - - -@app.cell -def _(): - return - - -if __name__ == "__main__": - app.run() diff --git a/dev/api-serve b/dev/api-serve new file mode 100755 index 0000000..c14a558 --- /dev/null +++ b/dev/api-serve @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -e + +API_PORT=${API_PORT:-8001} + +echo "API: http://127.0.0.1:${API_PORT}" + +uv run uvicorn \ + app.__main__:crate_app \ + --factory \ + --host 127.0.0.1 \ + --port ${API_PORT} \ + --reload \ + --reload-dir app \ + --reload-dir quantflow diff --git a/dev/docs-serve b/dev/docs-serve new file mode 100755 index 0000000..4e44b39 --- /dev/null +++ b/dev/docs-serve @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e + +OBSERVABLE_PORT=${OBSERVABLE_PORT:-3001} +DOCS_PORT=${DOCS_PORT:-8000} +API_PORT=${API_PORT:-8001} +FRONTEND_ORIGIN=${FRONTEND_ORIGIN:-http://127.0.0.1:${OBSERVABLE_PORT}} +QUANTFLOW_API_ORIGIN=${QUANTFLOW_API_ORIGIN:-http://127.0.0.1:${API_PORT}} + +npm --prefix app/frontend exec concurrently -- \ + --kill-others \ + --names docs,examples,api \ + --prefix "[{name}]" \ + "DOCS_PORT=${DOCS_PORT} bash ./dev/mkdocs-serve" \ + "OBSERVABLE_PORT=${OBSERVABLE_PORT} QUANTFLOW_API_ORIGIN=${QUANTFLOW_API_ORIGIN} bash ./dev/frontend-serve" \ + "API_PORT=${API_PORT} QUANTFLOW_CORS_ORIGINS=${FRONTEND_ORIGIN} bash ./dev/api-serve" diff --git a/dev/frontend-serve b/dev/frontend-serve new file mode 100755 index 0000000..a18a367 --- /dev/null +++ b/dev/frontend-serve @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e + +OBSERVABLE_PORT=${OBSERVABLE_PORT:-3001} + +echo "Examples: http://127.0.0.1:${OBSERVABLE_PORT}" + +npm --prefix app/frontend run dev -- --host 127.0.0.1 --port ${OBSERVABLE_PORT} diff --git a/dev/mkdocs-serve b/dev/mkdocs-serve new file mode 100755 index 0000000..a620938 --- /dev/null +++ b/dev/mkdocs-serve @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +DOCS_PORT=${DOCS_PORT:-8000} + +echo "Documentation: http://127.0.0.1:${DOCS_PORT}" + +uv run mkdocs serve \ + --dev-addr 127.0.0.1:${DOCS_PORT} \ + --livereload \ + --watch quantflow \ + --watch docs diff --git a/dev/serve-info b/dev/serve-info new file mode 100644 index 0000000..82461c8 --- /dev/null +++ b/dev/serve-info @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e + +OBSERVABLE_PORT=${OBSERVABLE_PORT:-3001} +DOCS_PORT=${DOCS_PORT:-8000} +API_PORT=${API_PORT:-8001} + +echo "Documentation: http://127.0.0.1:${DOCS_PORT}" +echo "Examples: http://127.0.0.1:${OBSERVABLE_PORT}" +echo "API: http://127.0.0.1:${API_PORT}" diff --git a/docs/api/rates/index.md b/docs/api/rates/index.md index 82514be..bea75bc 100644 --- a/docs/api/rates/index.md +++ b/docs/api/rates/index.md @@ -13,3 +13,5 @@ The central concept is the [discount factor](../../glossary.md#discount-factor) \end{equation} **[NelsonSiegel](nelson_siegel.md)** is a concrete `YieldCurve` implementation that fits a smooth parametric curve to observed zero-coupon rates using the Nelson-Siegel functional form. + +**[Options Discounting](options.md)** provides `YieldCurveCalibration`, the base class for fitting a yield curve to discount factors, and `OptionsDiscountingCalibration`, which bootstraps asset and quote curves from put-call parity observations. diff --git a/docs/api/rates/options.md b/docs/api/rates/options.md new file mode 100644 index 0000000..9e403bd --- /dev/null +++ b/docs/api/rates/options.md @@ -0,0 +1,5 @@ +# Options Discounting + +::: quantflow.rates.options.YieldCurveCalibration + +::: quantflow.rates.options.OptionsDiscountingCalibration diff --git a/docs/examples/fixtures/volsurface_btc.json b/docs/examples/fixtures/volsurface_btc.json index 35d4d68..e045ae0 100644 --- a/docs/examples/fixtures/volsurface_btc.json +++ b/docs/examples/fixtures/volsurface_btc.json @@ -1,5347 +1,5150 @@ { - "asset": "BTC", - "ref_date": "2026-04-27T08:54:57.694519Z", + "asset": "btc", + "asset_curve": { + "ref_date": "2026-05-21T10:04:10.269985Z", + "curve_type": "no_discount" + }, + "quote_curve": { + "ref_date": "2026-05-21T10:04:10.269985Z", + "curve_type": "nelson_siegel", + "beta1": "-1.3242608183", + "beta2": "1.3324170039", + "beta3": "1.6559934427", + "lambda_": "0.3139300004" + }, "inputs": [ { - "bid": "77775.5", - "ask": "77776", - "open_interest": "987675860", - "volume": "434177180", + "bid": "77596.5", + "ask": "77597", + "open_interest": "1010106310", + "volume": "310861580", "security_type": "spot" }, { - "bid": "77688.0", - "ask": "77847.5", - "open_interest": "0", - "volume": "0", - "maturity": "2026-04-28T08:00:00Z", + "bid": "77567.5", + "ask": "77570", + "open_interest": "6770230", + "volume": "2446790", + "maturity": "2026-05-22T08:00:00Z", "security_type": "forward" }, { "bid": "0.0001", "ask": "0.0002", - "open_interest": "112.9", - "volume": "1727.33", - "strike": "73000", - "maturity": "2026-04-28T08:00:00Z", + "open_interest": "1003", + "volume": "27274.34", + "strike": "74000", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5375622", - "iv_ask": "0.5919214", + "iv_bid": "0.429617", + "iv_ask": "0.4756913", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "250.8", - "volume": "1424.59", + "bid": "0.0003", + "ask": "0.0004", + "open_interest": "428.6", + "volume": "3943.98", "strike": "75000", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4188391", - "iv_ask": "0.4554825", + "iv_bid": "0.3867369", + "iv_ask": "0.408779", "inverse": true }, { - "bid": "0.0006", - "ask": "0.0008", - "open_interest": "49.3", - "volume": "3460.73", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "76.4", + "volume": "4231.52", "strike": "75500", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3889021", - "iv_ask": "0.4165291", + "iv_bid": "0.3437617", + "iv_ask": "0.3761377", "inverse": true }, { - "bid": "0.001", - "ask": "0.0012", - "open_interest": "100.2", - "volume": "8113.88", + "bid": "0.0008", + "ask": "0.0011", + "open_interest": "656.6", + "volume": "10576.95", "strike": "76000", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3668268", - "iv_ask": "0.3868547", + "iv_bid": "0.3290538", + "iv_ask": "0.360526", "inverse": true }, { - "bid": "0.0016", + "bid": "0.0015", "ask": "0.0019", - "open_interest": "66.7", - "volume": "8226.51", + "open_interest": "86.5", + "volume": "10478.47", "strike": "76500", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3392663", - "iv_ask": "0.3616924", + "iv_bid": "0.3105365", + "iv_ask": "0.3401717", "inverse": true }, { - "bid": "0.0028", - "ask": "0.0032", - "open_interest": "98.1", - "volume": "26045.32", + "bid": "0.0029", + "ask": "0.0033", + "open_interest": "432.3", + "volume": "20894.33", "strike": "77000", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3235727", - "iv_ask": "0.3467574", + "iv_bid": "0.3008514", + "iv_ask": "0.3236012", "inverse": true }, { - "bid": "0.0048", + "bid": "0.005", "ask": "0.0055", - "open_interest": "63.7", - "volume": "24109.03", + "open_interest": "70.9", + "volume": "34549.57", "strike": "77500", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3117853", - "iv_ask": "0.3467488", + "iv_bid": "0.2813215", + "iv_ask": "0.3064808", "inverse": true }, { - "bid": "0.005", - "ask": "0.0055", - "open_interest": "45.5", - "volume": "20438.49", + "bid": "0.0032", + "ask": "0.0036", + "open_interest": "460.9", + "volume": "46727.7", "strike": "78000", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3111888", - "iv_ask": "0.3359649", + "iv_bid": "0.2696848", + "iv_ask": "0.291074", "inverse": true }, { - "bid": "0.0032", - "ask": "0.0035", - "open_interest": "38.8", - "volume": "16645.2", + "bid": "0.0015", + "ask": "0.0019", + "open_interest": "200.2", + "volume": "10400.16", "strike": "78500", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.335916", - "iv_ask": "0.3526992", + "iv_bid": "0.2713719", + "iv_ask": "0.2990356", "inverse": true }, { - "bid": "0.0017", - "ask": "0.0021", - "open_interest": "70.5", - "volume": "12791.91", + "bid": "0.0008", + "ask": "0.0009", + "open_interest": "517.3", + "volume": "23993.21", "strike": "79000", - "maturity": "2026-04-28T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3353014", - "iv_ask": "0.3637628", - "inverse": true - }, - { - "bid": "0.001", - "ask": "0.0012", - "open_interest": "33.7", - "volume": "9985.45", - "strike": "79500", - "maturity": "2026-04-28T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.353494", - "iv_ask": "0.3729134", - "inverse": true - }, - { - "bid": "0.0005", - "ask": "0.0007", - "open_interest": "304.8", - "volume": "11838.5", - "strike": "80000", - "maturity": "2026-04-28T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3582481", - "iv_ask": "0.3870258", - "inverse": true - }, - { - "bid": "0.0003", - "ask": "0.0004", - "open_interest": "78", - "volume": "8399.42", - "strike": "80500", - "maturity": "2026-04-28T08:00:00Z", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3787766", - "iv_ask": "0.4001093", + "iv_bid": "0.2935461", + "iv_ask": "0.3037228", "inverse": true }, { "bid": "0.0002", "ask": "0.0003", - "open_interest": "67.7", - "volume": "2909.5", - "strike": "81000", - "maturity": "2026-04-28T08:00:00Z", + "open_interest": "1156.4", + "volume": "33764.55", + "strike": "80000", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4046507", - "iv_ask": "0.4333609", + "iv_bid": "0.3264002", + "iv_ask": "0.3512333", "inverse": true }, { "bid": "0.0001", "ask": "0.0002", - "open_interest": "101.3", - "volume": "2441.79", - "strike": "82000", - "maturity": "2026-04-28T08:00:00Z", + "open_interest": "994.5", + "volume": "1787.07", + "strike": "81000", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4573552", - "iv_ask": "0.5047645", + "iv_bid": "0.3911805", + "iv_ask": "0.4336455", "inverse": true }, { - "bid": "77700.0", - "ask": "77815.0", - "open_interest": "0", - "volume": "0", - "maturity": "2026-04-29T08:00:00Z", + "bid": "77255", + "ask": "77627.5", + "open_interest": "54290", + "volume": "6260", + "maturity": "2026-05-23T08:00:00Z", "security_type": "forward" }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "78.1", - "volume": "949.38", - "strike": "70000", - "maturity": "2026-04-29T08:00:00Z", + "ask": "0.0002", + "open_interest": "28.2", + "volume": "69.95", + "strike": "72000", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5886027", - "iv_ask": "0.6803194", + "iv_bid": "0.4418664", + "iv_ask": "0.485088", "inverse": true }, { "bid": "0.0002", "ask": "0.0004", - "open_interest": "8.8", - "volume": "187.38", - "strike": "71000", - "maturity": "2026-04-29T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.5664797", - "iv_ask": "0.6280995", - "inverse": true - }, - { - "bid": "0.0002", - "ask": "0.0005", - "open_interest": "3.6", - "volume": "60.16", - "strike": "72000", - "maturity": "2026-04-29T08:00:00Z", + "open_interest": "84.1", + "volume": "1258.29", + "strike": "73000", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4902668", - "iv_ask": "0.5665876", + "iv_bid": "0.407351", + "iv_ask": "0.4556122", "inverse": true }, { "bid": "0.0004", "ask": "0.0007", - "open_interest": "46", - "volume": "1682.65", - "strike": "73000", - "maturity": "2026-04-29T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.4623826", - "iv_ask": "0.5127515", - "inverse": true - }, - { - "bid": "0.0007", - "ask": "0.001", - "open_interest": "32.2", - "volume": "1348.04", + "open_interest": "11.5", + "volume": "371.77", "strike": "74000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4220138", - "iv_ask": "0.4563569", + "iv_bid": "0.3700709", + "iv_ask": "0.4133213", "inverse": true }, { - "bid": "0.001", - "ask": "0.0012", - "open_interest": "20.1", - "volume": "929.25", + "bid": "0.0007", + "ask": "0.0009", + "open_interest": "27.8", + "volume": "200.7", "strike": "74500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.407718", - "iv_ask": "0.4263311", + "iv_bid": "0.3663527", + "iv_ask": "0.3878956", "inverse": true }, { - "bid": "0.0013", - "ask": "0.0016", - "open_interest": "52.2", - "volume": "2394.27", + "bid": "0.001", + "ask": "0.0013", + "open_interest": "26.7", + "volume": "4071.74", "strike": "75000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3835137", - "iv_ask": "0.4062553", + "iv_bid": "0.3473141", + "iv_ask": "0.3721851", "inverse": true }, { - "bid": "0.0019", - "ask": "0.0023", - "open_interest": "10.9", - "volume": "2131.48", + "bid": "0.0016", + "ask": "0.0019", + "open_interest": "32.7", + "volume": "6591.67", "strike": "75500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3709432", - "iv_ask": "0.3950226", + "iv_bid": "0.3387223", + "iv_ask": "0.3577577", "inverse": true }, { - "bid": "0.0028", - "ask": "0.003", - "open_interest": "30.5", - "volume": "7010.16", + "bid": "0.0025", + "ask": "0.0028", + "open_interest": "7.8", + "volume": "1423.38", "strike": "76000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3606688", - "iv_ask": "0.3706205", + "iv_bid": "0.3300149", + "iv_ask": "0.3451084", "inverse": true }, { - "bid": "0.0041", - "ask": "0.0044", - "open_interest": "165.7", - "volume": "6172.95", + "bid": "0.0038", + "ask": "0.0041", + "open_interest": "25.6", + "volume": "9566.44", "strike": "76500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.352067", - "iv_ask": "0.3646061", + "iv_bid": "0.3208607", + "iv_ask": "0.333405", "inverse": true }, { - "bid": "0.006", - "ask": "0.0065", - "open_interest": "44.6", - "volume": "17287.94", + "bid": "0.0055", + "ask": "0.006", + "open_interest": "3.9", + "volume": "1481.76", "strike": "77000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.34822", - "iv_ask": "0.3666432", + "iv_bid": "0.3069603", + "iv_ask": "0.3253663", "inverse": true }, { "bid": "0.008", "ask": "0.009", - "open_interest": "19.8", - "volume": "8876.2", + "open_interest": "18.3", + "volume": "13806.5", "strike": "77500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3275722", - "iv_ask": "0.362119", + "iv_bid": "0.2989107", + "iv_ask": "0.3336076", "inverse": true }, { - "bid": "0.0085", - "ask": "0.009", - "open_interest": "13.9", - "volume": "9131.59", + "bid": "0.006", + "ask": "0.007", + "open_interest": "329.4", + "volume": "147856.15", "strike": "78000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3407638", - "iv_ask": "0.3579606", + "iv_bid": "0.2874394", + "iv_ask": "0.3229204", "inverse": true }, { - "bid": "0.006", - "ask": "0.0065", - "open_interest": "6.2", - "volume": "5492.23", + "bid": "0.0039", + "ask": "0.0043", + "open_interest": "20.5", + "volume": "7080.72", "strike": "78500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3423951", - "iv_ask": "0.3606088", + "iv_bid": "0.2913542", + "iv_ask": "0.3072174", "inverse": true }, { - "bid": "0.0041", - "ask": "0.0044", - "open_interest": "5", - "volume": "4461.19", + "bid": "0.0023", + "ask": "0.0027", + "open_interest": "37.4", + "volume": "5866.37", "strike": "79000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3443416", - "iv_ask": "0.3566525", + "iv_bid": "0.28883", + "iv_ask": "0.3081595", "inverse": true }, { - "bid": "0.0027", - "ask": "0.0029", - "open_interest": "21.4", - "volume": "15740.49", + "bid": "0.0013", + "ask": "0.0017", + "open_interest": "15.1", + "volume": "612.35", "strike": "79500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3458753", - "iv_ask": "0.3556803", + "iv_bid": "0.2890175", + "iv_ask": "0.3142938", "inverse": true }, { - "bid": "0.0018", - "ask": "0.002", - "open_interest": "136.9", - "volume": "46707.64", + "bid": "0.0009", + "ask": "0.001", + "open_interest": "41.3", + "volume": "1458", "strike": "80000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.352621", - "iv_ask": "0.3647188", + "iv_bid": "0.3084363", + "iv_ask": "0.3168659", "inverse": true }, { - "bid": "0.0012", - "ask": "0.0015", - "open_interest": "57", - "volume": "21009.19", + "bid": "0.0005", + "ask": "0.0007", + "open_interest": "4", + "volume": "293.59", "strike": "80500", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3609259", - "iv_ask": "0.3835217", + "iv_bid": "0.3113354", + "iv_ask": "0.3346143", "inverse": true }, { - "bid": "0.0008", - "ask": "0.001", - "open_interest": "31.3", - "volume": "7861.02", + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "63.4", + "volume": "587.34", "strike": "81000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3699856", - "iv_ask": "0.3896471", + "iv_bid": "0.320528", + "iv_ask": "0.3524184", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "3.6", - "volume": "428.85", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "33", + "volume": "681.71", "strike": "82000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3969482", - "iv_ask": "0.4280958", + "iv_bid": "0.33562", + "iv_ask": "0.3945901", "inverse": true }, { - "bid": "0.0002", - "ask": "0.0004", - "open_interest": "22.9", - "volume": "905.57", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "2.6", + "volume": "48.27", "strike": "83000", - "maturity": "2026-04-29T08:00:00Z", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4214744", - "iv_ask": "0.4699667", + "iv_bid": "0.3986911", + "iv_ask": "0.4379765", "inverse": true }, { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "10.1", - "volume": "523.75", - "strike": "84000", - "maturity": "2026-04-29T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.4439978", - "iv_ask": "0.5163625", - "inverse": true + "bid": "77270", + "ask": "77835", + "open_interest": "10410", + "volume": "10150", + "maturity": "2026-05-24T08:00:00Z", + "security_type": "forward" }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "13.4", - "volume": "171.28", - "strike": "85000", - "maturity": "2026-04-29T08:00:00Z", - "option_type": "call", + "ask": "0.0002", + "open_interest": "1.5", + "volume": "30.39", + "strike": "70000", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.5028555", - "iv_ask": "0.5823179", + "iv_bid": "0.4748059", + "iv_ask": "0.5185349", "inverse": true }, { - "bid": "77709.5", - "ask": "77821.5", - "open_interest": "0", - "volume": "0", - "maturity": "2026-04-30T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "56.5", - "volume": "2535.67", - "strike": "69000", - "maturity": "2026-04-30T08:00:00Z", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "10.6", + "volume": "2.33", + "strike": "71000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6190282", - "iv_ask": "0.6685198", + "iv_bid": "0.416506", + "iv_ask": "0.4559338", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "0.1", - "volume": "3.9", - "strike": "70000", - "maturity": "2026-04-30T08:00:00Z", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "8.8", + "volume": "25.56", + "strike": "72000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5787191", - "iv_ask": "0.6181027", + "iv_bid": "0.3932479", + "iv_ask": "0.4378054", "inverse": true }, { - "bid": "0.0006", - "ask": "0.0008", - "open_interest": "0.4", - "volume": "21.79", - "strike": "71000", - "maturity": "2026-04-30T08:00:00Z", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "27.4", + "volume": "518.23", + "strike": "73000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5476061", - "iv_ask": "0.5768897", + "iv_bid": "0.3693727", + "iv_ask": "0.397771", "inverse": true }, { "bid": "0.0008", "ask": "0.001", - "open_interest": "0.7", - "volume": "45.13", - "strike": "72000", - "maturity": "2026-04-30T08:00:00Z", + "open_interest": "112.5", + "volume": "9228", + "strike": "74000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5033645", - "iv_ask": "0.5263468", + "iv_bid": "0.3448846", + "iv_ask": "0.3626844", "inverse": true }, { "bid": "0.0011", "ask": "0.0014", - "open_interest": "127.2", - "volume": "13425.15", - "strike": "73000", - "maturity": "2026-04-30T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.4592521", - "iv_ask": "0.4852733", - "inverse": true - }, - { - "bid": "0.0018", - "ask": "0.002", - "open_interest": "26", - "volume": "4001.11", - "strike": "74000", - "maturity": "2026-04-30T08:00:00Z", + "open_interest": "9.3", + "volume": "501.87", + "strike": "74500", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4306846", - "iv_ask": "0.4433194", + "iv_bid": "0.3301382", + "iv_ask": "0.3512342", "inverse": true }, { - "bid": "0.0029", - "ask": "0.0033", - "open_interest": "32.2", - "volume": "8385.22", + "bid": "0.0016", + "ask": "0.0019", + "open_interest": "59.9", + "volume": "7354.21", "strike": "75000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3990423", - "iv_ask": "0.4174519", + "iv_bid": "0.3199972", + "iv_ask": "0.3368257", "inverse": true }, { - "bid": "0.0038", - "ask": "0.0042", - "open_interest": "19.5", - "volume": "8779.89", + "bid": "0.0022", + "ask": "0.0026", + "open_interest": "29.2", + "volume": "7246.7", "strike": "75500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3873955", - "iv_ask": "0.4033429", + "iv_bid": "0.3046823", + "iv_ask": "0.3230649", "inverse": true }, { - "bid": "0.005", - "ask": "0.0055", - "open_interest": "18.1", - "volume": "6707.94", + "bid": "0.0033", + "ask": "0.0036", + "open_interest": "5.9", + "volume": "2617.8", "strike": "76000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3772585", - "iv_ask": "0.3947663", + "iv_bid": "0.2995157", + "iv_ask": "0.3109128", "inverse": true }, { - "bid": "0.0065", - "ask": "0.0075", - "open_interest": "3.5", - "volume": "2969.98", + "bid": "0.0047", + "ask": "0.0055", + "open_interest": "4.3", + "volume": "3662.7", "strike": "76500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.366274", - "iv_ask": "0.3977172", + "iv_bid": "0.290402", + "iv_ask": "0.3163913", "inverse": true }, { - "bid": "0.0085", - "ask": "0.009", - "open_interest": "17.1", - "volume": "13690.5", + "bid": "0.0065", + "ask": "0.0075", + "open_interest": "10.3", + "volume": "6978.72", "strike": "77000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3586642", - "iv_ask": "0.3732924", + "iv_bid": "0.2788022", + "iv_ask": "0.3082419", "inverse": true }, { - "bid": "0.011", - "ask": "0.012", - "open_interest": "26.7", - "volume": "22941.75", + "bid": "0.009", + "ask": "0.01", + "open_interest": "25.3", + "volume": "25783.65", "strike": "77500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3521661", - "iv_ask": "0.3801943", + "iv_bid": "0.2707164", + "iv_ask": "0.2988297", "inverse": true }, { - "bid": "0.0115", - "ask": "0.012", - "open_interest": "114.8", - "volume": "125803.96", + "bid": "0.007", + "ask": "0.008", + "open_interest": "12.6", + "volume": "12412.58", "strike": "78000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3598777", - "iv_ask": "0.3738297", + "iv_bid": "0.2614019", + "iv_ask": "0.289999", "inverse": true }, { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "18", - "volume": "21300.07", + "bid": "0.0048", + "ask": "0.0055", + "open_interest": "3.4", + "volume": "1501.51", "strike": "78500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3508133", - "iv_ask": "0.3796688", + "iv_bid": "0.2645518", + "iv_ask": "0.2863374", "inverse": true }, { - "bid": "0.0065", - "ask": "0.007", - "open_interest": "112", - "volume": "142139.84", + "bid": "0.0031", + "ask": "0.0035", + "open_interest": "14.2", + "volume": "5268.42", "strike": "79000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3565683", - "iv_ask": "0.37206", + "iv_bid": "0.2645833", + "iv_ask": "0.2791584", "inverse": true }, { - "bid": "0.0048", - "ask": "0.0055", - "open_interest": "82.4", - "volume": "56724.13", + "bid": "0.002", + "ask": "0.0022", + "open_interest": "25.4", + "volume": "2653.6", "strike": "79500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3584929", - "iv_ask": "0.3824691", + "iv_bid": "0.2687246", + "iv_ask": "0.2777732", "inverse": true }, { - "bid": "0.0034", - "ask": "0.0038", - "open_interest": "24", - "volume": "11431.76", + "bid": "0.0013", + "ask": "0.0015", + "open_interest": "25.8", + "volume": "3810.16", "strike": "80000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3573297", - "iv_ask": "0.3732309", + "iv_bid": "0.2754615", + "iv_ask": "0.2869724", "inverse": true }, { - "bid": "0.0024", - "ask": "0.0028", - "open_interest": "160", - "volume": "72073.42", + "bid": "0.0008", + "ask": "0.001", + "open_interest": "0.9", + "volume": "76.81", "strike": "80500", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3589487", - "iv_ask": "0.3777727", + "iv_bid": "0.2794076", + "iv_ask": "0.2947166", "inverse": true }, { - "bid": "0.0017", - "ask": "0.0019", - "open_interest": "14.6", - "volume": "5405.8", - "strike": "81000", - "maturity": "2026-04-30T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3629189", - "iv_ask": "0.3744968", - "inverse": true - }, - { - "bid": "0.0012", - "ask": "0.0014", - "open_interest": "1.9", - "volume": "195.91", - "strike": "81500", - "maturity": "2026-04-30T08:00:00Z", + "bid": "0.0005", + "ask": "0.0007", + "open_interest": "63.7", + "volume": "2239.71", + "strike": "81000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3677125", - "iv_ask": "0.3819824", + "iv_bid": "0.2854745", + "iv_ask": "0.3059472", "inverse": true }, { - "bid": "0.0008", - "ask": "0.001", - "open_interest": "42.8", - "volume": "6840.17", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "0.4", + "volume": "12.39", "strike": "82000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3687651", - "iv_ask": "0.3870011", + "iv_bid": "0.2998333", + "iv_ask": "0.335726", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "33.7", - "volume": "3504.41", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "0.1", + "volume": "1.56", "strike": "83000", - "maturity": "2026-04-30T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3819902", - "iv_ask": "0.4104745", - "inverse": true - }, - { - "bid": "0.0002", - "ask": "0.0004", - "open_interest": "50.6", - "volume": "3508.83", - "strike": "84000", - "maturity": "2026-04-30T08:00:00Z", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3955028", - "iv_ask": "0.4392871", + "iv_bid": "0.3230049", + "iv_ask": "0.3772798", "inverse": true }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "46.2", - "volume": "2409.81", - "strike": "85000", - "maturity": "2026-04-30T08:00:00Z", + "ask": "0.0002", + "open_interest": "2.3", + "volume": "42.78", + "strike": "84000", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4088416", - "iv_ask": "0.4734637", + "iv_bid": "0.3725421", + "iv_ask": "0.4080264", "inverse": true }, { - "bid": "77730", - "ask": "77732.5", - "open_interest": "30198620", - "volume": "5038760", - "maturity": "2026-05-01T08:00:00Z", + "bid": "77275", + "ask": "77840", + "open_interest": "0", + "volume": "0", + "maturity": "2026-05-25T08:00:00Z", "security_type": "forward" }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "996.5", - "volume": "1651.85", - "strike": "65000", - "maturity": "2026-05-01T08:00:00Z", + "ask": "0.0002", + "open_interest": "20", + "volume": "155.41", + "strike": "69000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6662974", - "iv_ask": "0.7605", + "iv_bid": "0.4602006", + "iv_ask": "0.5015937", "inverse": true }, { "bid": "0.0001", "ask": "0.0002", - "open_interest": "774.4", - "volume": "460.43", - "strike": "66000", - "maturity": "2026-05-01T08:00:00Z", + "open_interest": "0", + "volume": "0", + "strike": "70000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.614914", - "iv_ask": "0.6671515", + "iv_bid": "0.4097721", + "iv_ask": "0.4475098", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "551.5", - "volume": "13.36", - "strike": "68000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "0", + "volume": "0", + "strike": "71000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5898027", - "iv_ask": "0.6359126", + "iv_bid": "0.3934965", + "iv_ask": "0.4173482", "inverse": true }, { "bid": "0.0004", - "ask": "0.0006", - "open_interest": "213.5", - "volume": "1598.06", - "strike": "69000", - "maturity": "2026-05-01T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.5565995", - "iv_ask": "0.5934968", - "inverse": true - }, - { - "bid": "0.0005", - "ask": "0.0007", - "open_interest": "1573.6", - "volume": "1174.28", - "strike": "70000", - "maturity": "2026-05-01T08:00:00Z", + "ask": "0.0005", + "open_interest": "0", + "volume": "0", + "strike": "72000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5166318", - "iv_ask": "0.5469371", + "iv_bid": "0.3778649", + "iv_ask": "0.3925531", "inverse": true }, { - "bid": "0.0008", - "ask": "0.0011", - "open_interest": "447.3", - "volume": "1654.46", - "strike": "71000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0007", + "ask": "0.0009", + "open_interest": "1.2", + "volume": "74.52", + "strike": "73000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4968149", - "iv_ask": "0.5286282", + "iv_bid": "0.3538191", + "iv_ask": "0.3725539", "inverse": true }, { - "bid": "0.0012", - "ask": "0.0014", - "open_interest": "1153.7", - "volume": "20262.68", - "strike": "72000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0014", + "ask": "0.0015", + "open_interest": "5.8", + "volume": "672.77", + "strike": "74000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4707589", - "iv_ask": "0.487009", + "iv_bid": "0.3397653", + "iv_ask": "0.3458473", "inverse": true }, { - "bid": "0.0017", - "ask": "0.002", - "open_interest": "679", - "volume": "3836.82", - "strike": "73000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0026", + "ask": "0.0029", + "open_interest": "0", + "volume": "0", + "strike": "75000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4374705", - "iv_ask": "0.4559868", + "iv_bid": "0.3215794", + "iv_ask": "0.3338328", "inverse": true }, { - "bid": "0.0027", - "ask": "0.0031", - "open_interest": "431.6", - "volume": "6673.85", - "strike": "74000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0035", + "ask": "0.0039", + "open_interest": "0.2", + "volume": "49.79", + "strike": "75500", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.415717", - "iv_ask": "0.4340148", + "iv_bid": "0.3120235", + "iv_ask": "0.3259059", "inverse": true }, { - "bid": "0.0044", - "ask": "0.0047", - "open_interest": "584.5", - "volume": "64541.48", - "strike": "75000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0048", + "ask": "0.0055", + "open_interest": "53.9", + "volume": "19215.72", + "strike": "76000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3987878", - "iv_ask": "0.4093333", + "iv_bid": "0.3060527", + "iv_ask": "0.3270361", "inverse": true }, { - "bid": "0.007", - "ask": "0.0075", - "open_interest": "1073.9", - "volume": "30847.2", - "strike": "76000", - "maturity": "2026-05-01T08:00:00Z", + "bid": "0.0065", + "ask": "0.007", + "open_interest": "0.2", + "volume": "93.26", + "strike": "76500", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3816549", - "iv_ask": "0.395859", + "iv_bid": "0.3006115", + "iv_ask": "0.3141044", "inverse": true }, { - "bid": "0.011", - "ask": "0.0115", - "open_interest": "565.5", - "volume": "89851.21", + "bid": "0.0085", + "ask": "0.0095", + "open_interest": "0", + "volume": "0", "strike": "77000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3685241", - "iv_ask": "0.3809728", + "iv_bid": "0.2914305", + "iv_ask": "0.316497", "inverse": true }, { - "bid": "0.0135", - "ask": "0.0145", - "open_interest": "6.3", - "volume": "7191.7", + "bid": "0.011", + "ask": "0.012", + "open_interest": "0", + "volume": "0", "strike": "77500", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3600164", - "iv_ask": "0.3841877", + "iv_bid": "0.2823985", + "iv_ask": "0.3066479", "inverse": true }, { - "bid": "0.0135", - "ask": "0.014", - "open_interest": "520.4", - "volume": "84779.86", + "bid": "0.009", + "ask": "0.01", + "open_interest": "0.9", + "volume": "767.01", "strike": "78000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3642812", - "iv_ask": "0.3763406", + "iv_bid": "0.2745331", + "iv_ask": "0.2990391", "inverse": true }, { - "bid": "0.0105", - "ask": "0.011", - "open_interest": "0.2", - "volume": "155.35", + "bid": "0.0065", + "ask": "0.0075", + "open_interest": "0.1", + "volume": "50.42", "strike": "78500", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3573817", - "iv_ask": "0.3697657", + "iv_bid": "0.2732062", + "iv_ask": "0.2991565", "inverse": true }, { - "bid": "0.008", - "ask": "0.009", - "open_interest": "471.1", - "volume": "209444.25", + "bid": "0.0049", + "ask": "0.0055", + "open_interest": "0.5", + "volume": "216.24", "strike": "79000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3519466", - "iv_ask": "0.3780838", + "iv_bid": "0.282447", + "iv_ask": "0.2995996", "inverse": true }, { - "bid": "0.0047", - "ask": "0.005", - "open_interest": "1442.7", - "volume": "137543.46", + "bid": "0.0034", + "ask": "0.0037", + "open_interest": "1.8", + "volume": "553.88", + "strike": "79500", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.2823709", + "iv_ask": "0.2923432", + "inverse": true + }, + { + "bid": "0.0023", + "ask": "0.0027", + "open_interest": "4.2", + "volume": "933.71", "strike": "80000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3553669", - "iv_ask": "0.3649151", + "iv_bid": "0.2827487", + "iv_ask": "0.2986393", "inverse": true }, { - "bid": "0.0025", - "ask": "0.0028", - "open_interest": "491.3", - "volume": "110407.93", + "bid": "0.0016", + "ask": "0.0019", + "open_interest": "0.1", + "volume": "15.58", + "strike": "80500", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.2876692", + "iv_ask": "0.3023128", + "inverse": true + }, + { + "bid": "0.0011", + "ask": "0.0013", + "open_interest": "1.6", + "volume": "161.97", "strike": "81000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3540833", - "iv_ask": "0.3669875", + "iv_bid": "0.2925095", + "iv_ask": "0.3048741", "inverse": true }, { - "bid": "0.0014", - "ask": "0.0016", - "open_interest": "825.1", - "volume": "69654.05", + "bid": "0.0006", + "ask": "0.0007", + "open_interest": "2.1", + "volume": "114.46", "strike": "82000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.364147", - "iv_ask": "0.3763902", + "iv_bid": "0.3120796", + "iv_ask": "0.3217238", "inverse": true }, { - "bid": "0.0008", - "ask": "0.0009", - "open_interest": "667.4", - "volume": "8398.29", + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "0", + "volume": "0", "strike": "83000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3771286", - "iv_ask": "0.3862035", + "iv_bid": "0.3254275", + "iv_ask": "0.354409", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "129.8", - "volume": "3924.17", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "0", + "volume": "0", "strike": "84000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3816245", - "iv_ask": "0.408895", + "iv_bid": "0.3519634", + "iv_ask": "0.3906923", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "954.6", - "volume": "415.26", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "0", + "volume": "0", "strike": "85000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4110852", - "iv_ask": "0.4453186", + "iv_bid": "0.3629208", + "iv_ask": "0.4200007", "inverse": true }, { - "bid": "0.0002", + "bid": "0.0001", "ask": "0.0003", - "open_interest": "39.2", - "volume": "306.28", + "open_interest": "0", + "volume": "0", "strike": "86000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4312223", - "iv_ask": "0.4561907", + "iv_bid": "0.4034472", + "iv_ask": "0.4652873", "inverse": true }, { "bid": "0.0001", "ask": "0.0003", - "open_interest": "205.4", - "volume": "155.2", + "open_interest": "0", + "volume": "0", "strike": "87000", - "maturity": "2026-05-01T08:00:00Z", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4347883", - "iv_ask": "0.5001501", + "iv_bid": "0.4430337", + "iv_ask": "0.5094337", "inverse": true }, { - "bid": "77755", - "ask": "77757.5", - "open_interest": "2718240", - "volume": "2864240", - "maturity": "2026-05-08T08:00:00Z", + "bid": "77615", + "ask": "77617.5", + "open_interest": "96001750", + "volume": "6722440", + "maturity": "2026-05-29T08:00:00Z", "security_type": "forward" }, { - "bid": "0.001", - "ask": "0.0012", - "open_interest": "217.3", - "volume": "1119.53", - "strike": "65000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "293.6", + "volume": "125.36", + "strike": "58000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5487305", - "iv_ask": "0.5664115", + "iv_bid": "0.733615", + "iv_ask": "0.7905761", "inverse": true }, { - "bid": "0.0012", - "ask": "0.0014", - "open_interest": "370.2", - "volume": "5839.88", - "strike": "66000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "338.8", + "volume": "1.56", + "strike": "59000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5257564", - "iv_ask": "0.5409071", + "iv_bid": "0.6942328", + "iv_ask": "0.7486132", "inverse": true }, { - "bid": "0.0018", - "ask": "0.002", - "open_interest": "1041.1", - "volume": "14507.73", - "strike": "68000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "1979.6", + "volume": "926.56", + "strike": "60000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4824045", - "iv_ask": "0.493292", + "iv_bid": "0.65537", + "iv_ask": "0.7071899", "inverse": true }, { - "bid": "0.0029", - "ask": "0.0032", - "open_interest": "108.4", - "volume": "3075.54", - "strike": "70000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "303.5", + "volume": "0", + "strike": "61000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4446109", - "iv_ask": "0.455945", + "iv_bid": "0.6169972", + "iv_ask": "0.6662741", "inverse": true }, { - "bid": "0.0038", - "ask": "0.0041", - "open_interest": "137", - "volume": "11800.49", - "strike": "71000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "303.5", + "volume": "209.54", + "strike": "62000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4289334", - "iv_ask": "0.4383776", + "iv_bid": "0.579085", + "iv_ask": "0.6578869", "inverse": true }, { - "bid": "0.005", - "ask": "0.0055", - "open_interest": "170.8", - "volume": "22088.81", - "strike": "72000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "424.9", + "volume": "3107.24", + "strike": "63000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4139109", - "iv_ask": "0.4270395", + "iv_bid": "0.5858339", + "iv_ask": "0.6162078", "inverse": true }, { - "bid": "0.0065", - "ask": "0.007", - "open_interest": "192.5", - "volume": "6559.66", - "strike": "73000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "363.1", + "volume": "3.12", + "strike": "64000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3974257", - "iv_ask": "0.4086612", + "iv_bid": "0.5462422", + "iv_ask": "0.5976688", "inverse": true }, { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "621.5", - "volume": "42889.31", - "strike": "74000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "2450.1", + "volume": "1217.8", + "strike": "65000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3820314", - "iv_ask": "0.4013904", + "iv_bid": "0.5340454", + "iv_ask": "0.5735944", "inverse": true }, { - "bid": "0.012", - "ask": "0.0125", - "open_interest": "255.4", - "volume": "107071.38", - "strike": "75000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "1059.4", + "volume": "2275.24", + "strike": "66000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3824536", - "iv_ask": "0.3909715", + "iv_bid": "0.5136121", + "iv_ask": "0.5456127", "inverse": true }, { - "bid": "0.0155", - "ask": "0.0165", - "open_interest": "87.3", - "volume": "28202.41", - "strike": "76000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0006", + "ask": "0.0007", + "open_interest": "1112.1", + "volume": "66.84", + "strike": "67000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3687289", - "iv_ask": "0.3842928", + "iv_bid": "0.5020094", + "iv_ask": "0.514634", "inverse": true }, { - "bid": "0.0205", - "ask": "0.021", - "open_interest": "293.6", - "volume": "56530.03", - "strike": "77000", - "maturity": "2026-05-08T08:00:00Z", + "bid": "0.0008", + "ask": "0.0009", + "open_interest": "1307.3", + "volume": "1371.15", + "strike": "68000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3643546", - "iv_ask": "0.3717117", + "iv_bid": "0.4812067", + "iv_ask": "0.491222", "inverse": true }, { - "bid": "0.0235", - "ask": "0.0245", - "open_interest": "441.7", - "volume": "106843.94", - "strike": "78000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3616212", - "iv_ask": "0.3760875", - "inverse": true - }, - { - "bid": "0.0175", - "ask": "0.0185", - "open_interest": "271.8", - "volume": "250080.7", - "strike": "79000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.354168", - "iv_ask": "0.368993", - "inverse": true - }, - { - "bid": "0.013", - "ask": "0.0135", - "open_interest": "574.4", - "volume": "90683.65", - "strike": "80000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3537403", - "iv_ask": "0.3616659", - "inverse": true - }, - { - "bid": "0.0095", - "ask": "0.01", - "open_interest": "438.6", - "volume": "44904.99", - "strike": "81000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.354583", - "iv_ask": "0.36338", - "inverse": true - }, - { - "bid": "0.0065", - "ask": "0.007", - "open_interest": "913.3", - "volume": "154210.98", - "strike": "82000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3493293", - "iv_ask": "0.3595734", - "inverse": true - }, - { - "bid": "0.0031", - "ask": "0.0034", - "open_interest": "939.8", - "volume": "93358.52", - "strike": "84000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3520139", - "iv_ask": "0.3611368", - "inverse": true - }, - { - "bid": "0.0021", - "ask": "0.0024", - "open_interest": "1483.1", - "volume": "57029.06", - "strike": "85000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", + "bid": "0.001", + "ask": "0.0013", + "open_interest": "843.4", + "volume": "2506.83", + "strike": "69000", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3542017", - "iv_ask": "0.3657113", + "iv_bid": "0.4543972", + "iv_ask": "0.477926", "inverse": true }, { - "bid": "0.0015", + "bid": "0.0014", "ask": "0.0017", - "open_interest": "302.6", - "volume": "10072.55", - "strike": "86000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3614836", - "iv_ask": "0.3711836", - "inverse": true - }, - { - "bid": "0.0004", - "ask": "0.0006", - "open_interest": "479.2", - "volume": "8258.29", - "strike": "90000", - "maturity": "2026-05-08T08:00:00Z", - "option_type": "call", + "open_interest": "3544.9", + "volume": "28177.55", + "strike": "70000", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3908739", - "iv_ask": "0.4149628", + "iv_bid": "0.4366525", + "iv_ask": "0.4551727", "inverse": true }, { - "bid": "77701.5", - "ask": "77862.0", - "open_interest": "0", - "volume": "0", - "maturity": "2026-05-15T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.0023", - "ask": "0.0026", - "open_interest": "124.9", - "volume": "813.5", - "strike": "65000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.002", + "ask": "0.0023", + "open_interest": "1810", + "volume": "15470.38", + "strike": "71000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5021365", - "iv_ask": "0.5152999", + "iv_bid": "0.4207728", + "iv_ask": "0.4352691", "inverse": true }, { - "bid": "0.006", - "ask": "0.0065", - "open_interest": "70.3", - "volume": "18603.75", - "strike": "70000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.0029", + "ask": "0.0032", + "open_interest": "1169.4", + "volume": "1468.49", + "strike": "72000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4268486", - "iv_ask": "0.4377581", + "iv_bid": "0.4068536", + "iv_ask": "0.4182367", "inverse": true }, { - "bid": "0.0095", - "ask": "0.0105", - "open_interest": "124.6", - "volume": "9119.02", - "strike": "72000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.0042", + "ask": "0.0045", + "open_interest": "980", + "volume": "5561.06", + "strike": "73000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4077988", - "iv_ask": "0.4244853", + "iv_bid": "0.3937874", + "iv_ask": "0.4028787", "inverse": true }, { - "bid": "0.015", - "ask": "0.016", - "open_interest": "129.5", - "volume": "39340.5", + "bid": "0.0055", + "ask": "0.0065", + "open_interest": "2478.8", + "volume": "44786.16", "strike": "74000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3929067", - "iv_ask": "0.4064881", + "iv_bid": "0.3676371", + "iv_ask": "0.3927052", "inverse": true }, { - "bid": "0.019", - "ask": "0.0195", - "open_interest": "50.7", - "volume": "41847.1", + "bid": "0.008", + "ask": "0.009", + "open_interest": "5082.6", + "volume": "91033.07", "strike": "75000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.390482", - "iv_ask": "0.3967645", + "iv_bid": "0.3568048", + "iv_ask": "0.3779735", "inverse": true }, { - "bid": "0.023", - "ask": "0.024", - "open_interest": "32", - "volume": "49361.37", + "bid": "0.0115", + "ask": "0.0125", + "open_interest": "1627.4", + "volume": "103099.78", "strike": "76000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3796642", - "iv_ask": "0.3915336", + "iv_bid": "0.3477398", + "iv_ask": "0.366378", "inverse": true }, { - "bid": "0.0285", - "ask": "0.0295", - "open_interest": "108.7", - "volume": "56828.82", + "bid": "0.016", + "ask": "0.017", + "open_interest": "826", + "volume": "41062.04", "strike": "77000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.3781084", - "iv_ask": "0.3895557", + "iv_bid": "0.3366352", + "iv_ask": "0.3539406", "inverse": true }, { - "bid": "0.032", - "ask": "0.0325", - "open_interest": "176.4", - "volume": "141630.22", + "bid": "0.017", + "ask": "0.018", + "open_interest": "918.2", + "volume": "387262.01", "strike": "78000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3768078", - "iv_ask": "0.3824578", + "iv_bid": "0.3293645", + "iv_ask": "0.3464361", "inverse": true }, { - "bid": "0.0255", - "ask": "0.0265", - "open_interest": "43.4", - "volume": "48689", + "bid": "0.0115", + "ask": "0.0125", + "open_interest": "752.3", + "volume": "42604.93", "strike": "79000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3671444", - "iv_ask": "0.378566", + "iv_bid": "0.322708", + "iv_ask": "0.340735", "inverse": true }, { - "bid": "0.0205", - "ask": "0.0215", - "open_interest": "85", - "volume": "25163.88", + "bid": "0.0075", + "ask": "0.008", + "open_interest": "6866.8", + "volume": "90507.57", "strike": "80000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3656068", - "iv_ask": "0.3774266", + "iv_bid": "0.3196261", + "iv_ask": "0.3298768", "inverse": true }, { - "bid": "0.016", - "ask": "0.017", - "open_interest": "42.2", - "volume": "7730.4", + "bid": "0.0048", + "ask": "0.005", + "open_interest": "1913.4", + "volume": "72252.34", "strike": "81000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3609931", - "iv_ask": "0.3735362", + "iv_bid": "0.3206313", + "iv_ask": "0.3256201", "inverse": true }, { - "bid": "0.0125", - "ask": "0.0135", - "open_interest": "85.7", - "volume": "9669.77", + "bid": "0.003", + "ask": "0.0032", + "open_interest": "2466", + "volume": "404269.51", "strike": "82000", - "maturity": "2026-05-15T08:00:00Z", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3600389", - "iv_ask": "0.3736357", + "iv_bid": "0.3230775", + "iv_ask": "0.329457", "inverse": true }, { - "bid": "0.0075", - "ask": "0.008", - "open_interest": "460.4", - "volume": "13417.51", - "strike": "84000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.0019", + "ask": "0.002", + "open_interest": "1402.3", + "volume": "13095.04", + "strike": "83000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3615357", - "iv_ask": "0.3700621", + "iv_bid": "0.3291435", + "iv_ask": "0.3333866", "inverse": true }, { - "bid": "0.0043", - "ask": "0.0046", - "open_interest": "88.4", - "volume": "7720.93", - "strike": "86000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.0011", + "ask": "0.0014", + "open_interest": "1957", + "volume": "17424.15", + "strike": "84000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3628801", - "iv_ask": "0.3698054", + "iv_bid": "0.3299835", + "iv_ask": "0.3472624", "inverse": true }, { - "bid": "0.0015", - "ask": "0.0018", - "open_interest": "129.4", - "volume": "2242.32", - "strike": "90000", - "maturity": "2026-05-15T08:00:00Z", + "bid": "0.0008", + "ask": "0.0011", + "open_interest": "2822.4", + "volume": "20253.4", + "strike": "85000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3782631", - "iv_ask": "0.3918298", + "iv_bid": "0.346729", + "iv_ask": "0.3684132", "inverse": true }, { "bid": "0.0005", - "ask": "0.0007", - "open_interest": "6.1", - "volume": "22.62", - "strike": "95000", - "maturity": "2026-05-15T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.4108303", - "iv_ask": "0.4310875", - "inverse": true - }, - { - "bid": "77812.5", - "ask": "77815", - "open_interest": "67350560", - "volume": "9387130", - "maturity": "2026-05-29T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.0007", - "ask": "0.0009", - "open_interest": "739.1", - "volume": "249.12", - "strike": "48000", + "ask": "0.0008", + "open_interest": "1091.7", + "volume": "3790", + "strike": "86000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.7241261", - "iv_ask": "0.749391", + "iv_bid": "0.3532887", + "iv_ask": "0.38247", "inverse": true }, { - "bid": "0.0008", - "ask": "0.001", - "open_interest": "1250.9", - "volume": "2674.89", - "strike": "50000", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "729", + "volume": "693.45", + "strike": "87000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.6812462", - "iv_ask": "0.7031067", + "iv_bid": "0.3732087", + "iv_ask": "0.3975479", "inverse": true }, { - "bid": "0.0011", - "ask": "0.0013", - "open_interest": "496.1", - "volume": "47.67", - "strike": "52000", + "bid": "0.0004", + "ask": "0.0005", + "open_interest": "931.8", + "volume": "194.38", + "strike": "88000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.6567867", - "iv_ask": "0.6737252", + "iv_bid": "0.4044815", + "iv_ask": "0.4182131", "inverse": true }, { - "bid": "0.0014", - "ask": "0.0016", - "open_interest": "472.3", - "volume": "991.8", - "strike": "54000", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "414", + "volume": "216.27", + "strike": "89000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.6255369", - "iv_ask": "0.6392689", + "iv_bid": "0.3963949", + "iv_ask": "0.4350493", "inverse": true }, { - "bid": "0.0018", - "ask": "0.002", - "open_interest": "525.2", - "volume": "142", - "strike": "56000", + "bid": "0.0003", + "ask": "0.0004", + "open_interest": "1902.4", + "volume": "3275.9", + "strike": "90000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.5957779", - "iv_ask": "0.6068514", + "iv_bid": "0.4469673", + "iv_ask": "0.4649646", "inverse": true }, { - "bid": "0.002", - "ask": "0.0023", - "open_interest": "527.5", - "volume": "1373.33", - "strike": "57000", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "1034.3", + "volume": "342.53", + "strike": "92000", "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.5789308", - "iv_ask": "0.593825", + "iv_bid": "0.4422465", + "iv_ask": "0.5033281", "inverse": true }, { - "bid": "0.0023", - "ask": "0.0026", - "open_interest": "282.7", - "volume": "148.52", - "strike": "58000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.5657187", - "iv_ask": "0.579027", - "inverse": true + "bid": "77622.5", + "ask": "77625", + "open_interest": "8424810", + "volume": "2094940", + "maturity": "2026-06-05T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.0026", - "ask": "0.0029", - "open_interest": "346", - "volume": "84.41", - "strike": "59000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0014", + "ask": "0.0017", + "open_interest": "257.5", + "volume": "4812.81", + "strike": "65000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5507657", - "iv_ask": "0.5627731", + "iv_bid": "0.4955249", + "iv_ask": "0.5140271", "inverse": true }, { - "bid": "0.003", - "ask": "0.0033", - "open_interest": "852", - "volume": "10889.66", - "strike": "60000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0018", + "ask": "0.0019", + "open_interest": "0.9", + "volume": "133.58", + "strike": "66000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5380795", - "iv_ask": "0.5488048", + "iv_bid": "0.4828669", + "iv_ask": "0.4881512", "inverse": true }, { - "bid": "0.0034", - "ask": "0.0038", - "open_interest": "290.6", - "volume": "262.53", - "strike": "61000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0028", + "ask": "0.0031", + "open_interest": "520.6", + "volume": "150947.3", + "strike": "68000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5234718", - "iv_ask": "0.5362842", + "iv_bid": "0.4517302", + "iv_ask": "0.462836", "inverse": true }, { - "bid": "0.0039", - "ask": "0.0043", - "open_interest": "347.7", - "volume": "304.55", - "strike": "62000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0046", + "ask": "0.0048", + "open_interest": "268.5", + "volume": "11455.2", + "strike": "70000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5102348", - "iv_ask": "0.5217509", + "iv_bid": "0.4267441", + "iv_ask": "0.4320624", "inverse": true }, { - "bid": "0.0045", - "ask": "0.0049", - "open_interest": "275.5", - "volume": "3685.61", - "strike": "63000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "109.1", + "volume": "15346.65", + "strike": "72000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4977308", - "iv_ask": "0.5080668", + "iv_bid": "0.3923341", + "iv_ask": "0.4023312", "inverse": true }, { - "bid": "0.005", - "ask": "0.0055", - "open_interest": "344.9", - "volume": "17058.37", - "strike": "64000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.009", + "ask": "0.01", + "open_interest": "2.3", + "volume": "1259.06", + "strike": "73000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4807026", - "iv_ask": "0.4924656", + "iv_bid": "0.380996", + "iv_ask": "0.3983532", "inverse": true }, { - "bid": "0.006", - "ask": "0.0065", - "open_interest": "967.7", - "volume": "20444.21", - "strike": "65000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.012", + "ask": "0.0125", + "open_interest": "125.3", + "volume": "24915.61", + "strike": "74000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.473088", - "iv_ask": "0.4835113", + "iv_bid": "0.377332", + "iv_ask": "0.3850406", "inverse": true }, { - "bid": "0.007", - "ask": "0.0075", - "open_interest": "767.4", - "volume": "38202.78", - "strike": "66000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.015", + "ask": "0.016", + "open_interest": "180.5", + "volume": "6412.82", + "strike": "75000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4622161", - "iv_ask": "0.4715945", + "iv_bid": "0.3637011", + "iv_ask": "0.3777339", "inverse": true }, { - "bid": "0.008", - "ask": "0.009", - "open_interest": "381.4", - "volume": "19427.36", - "strike": "67000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0195", + "ask": "0.02", + "open_interest": "632.3", + "volume": "13857.69", + "strike": "76000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4486493", - "iv_ask": "0.4655219", + "iv_bid": "0.3608082", + "iv_ask": "0.3673447", "inverse": true }, { - "bid": "0.0095", - "ask": "0.01", - "open_interest": "695.2", - "volume": "2533.12", - "strike": "68000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0245", + "ask": "0.025", + "open_interest": "169", + "volume": "44354.62", + "strike": "77000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4405757", - "iv_ask": "0.4482546", + "iv_bid": "0.35328", + "iv_ask": "0.3595495", "inverse": true }, { - "bid": "0.011", - "ask": "0.012", - "open_interest": "660.7", - "volume": "8364.57", - "strike": "69000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0255", + "ask": "0.026", + "open_interest": "154.4", + "volume": "76667.56", + "strike": "78000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.429087", - "iv_ask": "0.4430004", + "iv_bid": "0.3442852", + "iv_ask": "0.3504889", "inverse": true }, { - "bid": "0.013", - "ask": "0.014", - "open_interest": "3084.5", - "volume": "36353.17", - "strike": "70000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0195", + "ask": "0.0205", + "open_interest": "184.7", + "volume": "16347.49", + "strike": "79000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.4210971", - "iv_ask": "0.4337965", + "iv_bid": "0.3372516", + "iv_ask": "0.3499487", "inverse": true }, { - "bid": "0.0155", - "ask": "0.0165", - "open_interest": "360.6", - "volume": "36300.47", - "strike": "71000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0145", + "ask": "0.0155", + "open_interest": "519.7", + "volume": "86265.15", + "strike": "80000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.4153419", - "iv_ask": "0.4269854", + "iv_bid": "0.3307813", + "iv_ask": "0.3442524", "inverse": true }, { - "bid": "0.0185", - "ask": "0.019", - "open_interest": "1096.9", - "volume": "20156", - "strike": "72000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0105", + "ask": "0.0115", + "open_interest": "343.2", + "volume": "20160.47", + "strike": "81000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.4109078", - "iv_ask": "0.4163026", + "iv_bid": "0.3253839", + "iv_ask": "0.3402158", "inverse": true }, { - "bid": "0.0215", - "ask": "0.0225", - "open_interest": "479.9", - "volume": "673656.53", - "strike": "73000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0075", + "ask": "0.008", + "open_interest": "433", + "volume": "60267.48", + "strike": "82000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.4021122", - "iv_ask": "0.412195", + "iv_bid": "0.3225841", + "iv_ask": "0.3310965", "inverse": true }, { - "bid": "0.0255", - "ask": "0.0265", - "open_interest": "1841.2", - "volume": "36817.48", - "strike": "74000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0055", + "ask": "0.006", + "open_interest": "72.6", + "volume": "5481.62", + "strike": "83000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.3989167", - "iv_ask": "0.4084237", + "iv_bid": "0.3257491", + "iv_ask": "0.3356494", "inverse": true }, { - "bid": "0.03", - "ask": "0.0305", - "open_interest": "991.1", - "volume": "84666.73", - "strike": "75000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0039", + "ask": "0.0043", + "open_interest": "99", + "volume": "1220.48", + "strike": "84000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.3956157", - "iv_ask": "0.400154", + "iv_bid": "0.3267716", + "iv_ask": "0.3363094", "inverse": true }, { - "bid": "0.035", - "ask": "0.0355", - "open_interest": "981.3", - "volume": "90930.8", - "strike": "76000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.0028", + "ask": "0.0032", + "open_interest": "595.7", + "volume": "158700.42", + "strike": "85000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.3920165", - "iv_ask": "0.396398", + "iv_bid": "0.3302033", + "iv_ask": "0.3417768", "inverse": true }, { - "bid": "0.0405", - "ask": "0.041", - "open_interest": "241.5", - "volume": "303035.32", - "strike": "77000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "put", + "bid": "0.002", + "ask": "0.0023", + "open_interest": "177.4", + "volume": "1256.25", + "strike": "86000", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", "security_type": "option", - "iv_bid": "0.3879827", - "iv_ask": "0.3922652", + "iv_bid": "0.3338807", + "iv_ask": "0.3446652", "inverse": true }, { - "bid": "0.044", - "ask": "0.045", - "open_interest": "1029.4", - "volume": "413684.52", - "strike": "78000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0011", + "ask": "0.0014", + "open_interest": "603.4", + "volume": "2413.16", + "strike": "88000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3825106", - "iv_ask": "0.390987", + "iv_bid": "0.3470625", + "iv_ask": "0.3631434", "inverse": true }, { - "bid": "0.038", - "ask": "0.039", - "open_interest": "330.9", - "volume": "93354.3", - "strike": "79000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0007", + "ask": "0.0008", + "open_interest": "54.3", + "volume": "177.63", + "strike": "90000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3803055", - "iv_ask": "0.3888008", + "iv_bid": "0.3678199", + "iv_ask": "0.3758029", "inverse": true }, { - "bid": "0.0325", - "ask": "0.0335", - "open_interest": "7198.7", - "volume": "14159.05", - "strike": "80000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "276.8", + "volume": "0", + "strike": "95000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3774939", - "iv_ask": "0.3861169", + "iv_bid": "0.4047617", + "iv_ask": "0.4250875", "inverse": true }, { - "bid": "0.0275", - "ask": "0.0285", - "open_interest": "1002.5", - "volume": "58284.14", - "strike": "81000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.374137", - "iv_ask": "0.3830036", - "inverse": true + "bid": "77572.5", + "ask": "77670", + "open_interest": "3160", + "volume": "3170", + "maturity": "2026-06-12T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.023", - "ask": "0.024", - "open_interest": "4595.2", - "volume": "260884.82", - "strike": "82000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.0029", + "ask": "0.0033", + "open_interest": "1", + "volume": "489.73", + "strike": "65000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3703197", - "iv_ask": "0.3795593", + "iv_bid": "0.4740863", + "iv_ask": "0.488063", "inverse": true }, { - "bid": "0.0195", - "ask": "0.02", - "open_interest": "864.3", - "volume": "129161.75", - "strike": "83000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.0075", + "ask": "0.008", + "open_interest": "0", + "volume": "0", + "strike": "70000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3710584", - "iv_ask": "0.3759287", + "iv_bid": "0.4106372", + "iv_ask": "0.4197066", "inverse": true }, { - "bid": "0.016", - "ask": "0.017", - "open_interest": "2237.3", - "volume": "207661.75", - "strike": "84000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.0115", + "ask": "0.012", + "open_interest": "1.8", + "volume": "1570.52", + "strike": "72000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.367117", - "iv_ask": "0.3775085", + "iv_bid": "0.3937229", + "iv_ask": "0.4009085", "inverse": true }, { - "bid": "0.013", - "ask": "0.014", - "open_interest": "1754", - "volume": "20347.15", - "strike": "85000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.017", + "ask": "0.018", + "open_interest": "0.2", + "volume": "264.25", + "strike": "74000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3634411", - "iv_ask": "0.3746992", + "iv_bid": "0.3739904", + "iv_ask": "0.3859807", "inverse": true }, { - "bid": "0.011", - "ask": "0.0115", - "open_interest": "708.9", - "volume": "27863.79", - "strike": "86000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.021", + "ask": "0.022", + "open_interest": "0", + "volume": "0", + "strike": "75000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3667496", - "iv_ask": "0.3728768", + "iv_bid": "0.3693841", + "iv_ask": "0.3805748", "inverse": true }, { - "bid": "0.009", - "ask": "0.0095", - "open_interest": "215.5", - "volume": "116228.33", - "strike": "87000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.0255", + "ask": "0.0265", + "open_interest": "0", + "volume": "0", + "strike": "76000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3660756", - "iv_ask": "0.3728294", + "iv_bid": "0.3630418", + "iv_ask": "0.3736895", "inverse": true }, { - "bid": "0.007", - "ask": "0.008", - "open_interest": "812.7", - "volume": "11422.71", - "strike": "88000", - "maturity": "2026-05-29T08:00:00Z", - "option_type": "call", + "bid": "0.0305", + "ask": "0.0315", + "open_interest": "0", + "volume": "0", + "strike": "77000", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", "security_type": "option", - "iv_bid": "0.3605964", - "iv_ask": "0.3756909", + "iv_bid": "0.3546863", + "iv_ask": "0.365016", "inverse": true }, { - "bid": "0.0048", - "ask": "0.0055", - "open_interest": "1147.9", - "volume": "152285.26", - "strike": "90000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.032", + "ask": "0.033", + "open_interest": "0", + "volume": "0", + "strike": "78000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3654597", - "iv_ask": "0.3786821", + "iv_bid": "0.3493457", + "iv_ask": "0.3595761", "inverse": true }, { - "bid": "0.0032", - "ask": "0.0035", - "open_interest": "533.7", - "volume": "7006.13", - "strike": "92000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.026", + "ask": "0.027", + "open_interest": "0", + "volume": "0", + "strike": "79000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3685609", - "iv_ask": "0.3759941", + "iv_bid": "0.3448915", + "iv_ask": "0.3552505", "inverse": true }, { - "bid": "0.0022", - "ask": "0.0024", - "open_interest": "1158.7", - "volume": "2448.17", - "strike": "94000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0205", + "ask": "0.0215", + "open_interest": "0", + "volume": "0", + "strike": "80000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3746106", - "iv_ask": "0.3810515", + "iv_bid": "0.3375731", + "iv_ask": "0.348317", "inverse": true }, { - "bid": "0.0018", - "ask": "0.0021", - "open_interest": "1463", - "volume": "1559.6", - "strike": "95000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0165", + "ask": "0.017", + "open_interest": "0", + "volume": "0", + "strike": "81000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3767028", - "iv_ask": "0.3876312", + "iv_bid": "0.3384761", + "iv_ask": "0.344171", "inverse": true }, { - "bid": "0.0014", - "ask": "0.0015", - "open_interest": "205.8", - "volume": "5771.38", - "strike": "96000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0125", + "ask": "0.0135", + "open_interest": "0", + "volume": "0", + "strike": "82000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3755968", - "iv_ask": "0.3800562", + "iv_bid": "0.3313625", + "iv_ask": "0.3437451", "inverse": true }, { - "bid": "0.0011", - "ask": "0.0012", - "open_interest": "1441.5", - "volume": "220.47", - "strike": "98000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1.3", + "volume": "801.68", + "strike": "84000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3898627", - "iv_ask": "0.3952896", + "iv_bid": "0.3318968", + "iv_ask": "0.347283", "inverse": true }, { - "bid": "0.0008", - "ask": "0.0009", - "open_interest": "283.4", - "volume": "204.77", - "strike": "100000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0044", + "ask": "0.0048", + "open_interest": "0", + "volume": "0", + "strike": "86000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3984549", - "iv_ask": "0.4053762", + "iv_bid": "0.3346273", + "iv_ask": "0.3429262", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "871.6", - "volume": "7905.91", - "strike": "105000", - "maturity": "2026-05-29T08:00:00Z", + "bid": "0.0015", + "ask": "0.0019", + "open_interest": "15.6", + "volume": "2045.73", + "strike": "90000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4084979", - "iv_ask": "0.4345937", + "iv_bid": "0.345667", + "iv_ask": "0.3618859", "inverse": true }, { - "bid": "77920", - "ask": "77922.5", - "open_interest": "588089150", - "volume": "6932390", + "bid": "77697.5", + "ask": "77700", + "open_interest": "587314120", + "volume": "20900300", "maturity": "2026-06-26T08:00:00Z", "security_type": "forward" }, { - "bid": "0.0013", - "ask": "0.0016", - "open_interest": "1700.5", - "volume": "5327.23", - "strike": "40000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option", - "iv_bid": "0.7705258", - "iv_ask": "0.7944322", - "inverse": true - }, - { - "bid": "0.002", - "ask": "0.0023", - "open_interest": "1859.9", - "volume": "0", + "bid": "0.0005", + "ask": "0.0006", + "open_interest": "1872.9", + "volume": "15.56", "strike": "45000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6913868", - "iv_ask": "0.7074736", + "iv_bid": "0.7312121", + "iv_ask": "0.7480836", "inverse": true }, { - "bid": "0.0032", - "ask": "0.0035", - "open_interest": "2511.2", - "volume": "868.37", + "bid": "0.001", + "ask": "0.0011", + "open_interest": "2406.4", + "volume": "41.88", "strike": "50000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6231686", - "iv_ask": "0.6338104", + "iv_bid": "0.6612595", + "iv_ask": "0.6705402", "inverse": true }, { - "bid": "0.005", - "ask": "0.0055", - "open_interest": "1334.6", - "volume": "14343.48", + "bid": "0.0018", + "ask": "0.002", + "open_interest": "1285.8", + "volume": "178.72", "strike": "55000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5567148", - "iv_ask": "0.568612", + "iv_bid": "0.5861374", + "iv_ask": "0.5969319", "inverse": true }, { - "bid": "0.007", - "ask": "0.0075", - "open_interest": "562.1", - "volume": "14669.57", + "bid": "0.0026", + "ask": "0.0028", + "open_interest": "812", + "volume": "1412.77", "strike": "58000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5270521", - "iv_ask": "0.5362689", + "iv_bid": "0.5438646", + "iv_ask": "0.5517577", "inverse": true }, { - "bid": "0.009", - "ask": "0.0095", - "open_interest": "4488.5", - "volume": "891477.33", + "bid": "0.0034", + "ask": "0.0036", + "open_interest": "4113.1", + "volume": "4933.32", "strike": "60000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5120157", - "iv_ask": "0.5197543", + "iv_bid": "0.5184896", + "iv_ask": "0.5248414", "inverse": true }, { - "bid": "0.0105", - "ask": "0.0115", - "open_interest": "975.9", - "volume": "46965.67", + "bid": "0.0044", + "ask": "0.0046", + "open_interest": "997.5", + "volume": "609.94", "strike": "62000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4839094", - "iv_ask": "0.4973974", + "iv_bid": "0.4921997", + "iv_ask": "0.4973638", "inverse": true }, { - "bid": "0.0135", - "ask": "0.014", - "open_interest": "351.5", - "volume": "13461.97", + "bid": "0.0055", + "ask": "0.006", + "open_interest": "767.6", + "volume": "193.87", "strike": "64000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.47048", - "iv_ask": "0.476281", + "iv_bid": "0.4617876", + "iv_ask": "0.4723854", "inverse": true }, { - "bid": "0.015", - "ask": "0.016", - "open_interest": "1857.7", - "volume": "376167.1", + "bid": "0.0065", + "ask": "0.007", + "open_interest": "1848.6", + "volume": "58465.03", "strike": "65000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4610231", - "iv_ask": "0.4717971", + "iv_bid": "0.453227", + "iv_ask": "0.4626974", "inverse": true }, { - "bid": "0.017", - "ask": "0.018", - "open_interest": "984.4", - "volume": "14762.43", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1157.7", + "volume": "837.76", "strike": "66000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.455103", - "iv_ask": "0.4651352", + "iv_bid": "0.441873", + "iv_ask": "0.4588076", "inverse": true }, { - "bid": "0.0215", - "ask": "0.022", - "open_interest": "2331.5", - "volume": "15414.79", + "bid": "0.0105", + "ask": "0.011", + "open_interest": "1804.1", + "volume": "261786.55", "strike": "68000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4418524", - "iv_ask": "0.4462776", + "iv_bid": "0.426679", + "iv_ask": "0.4336515", "inverse": true }, { - "bid": "0.027", - "ask": "0.0275", - "open_interest": "2642.7", - "volume": "25180.56", + "bid": "0.0145", + "ask": "0.015", + "open_interest": "2693.4", + "volume": "156262.54", "strike": "70000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4289799", - "iv_ask": "0.4329336", + "iv_bid": "0.4114612", + "iv_ask": "0.4172817", "inverse": true }, { - "bid": "0.034", - "ask": "0.035", - "open_interest": "1834.5", - "volume": "54826.62", + "bid": "0.0195", + "ask": "0.0205", + "open_interest": "2788.3", + "volume": "46272.92", "strike": "72000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4189497", - "iv_ask": "0.4261325", + "iv_bid": "0.3936792", + "iv_ask": "0.4036897", "inverse": true }, { - "bid": "0.0425", - "ask": "0.0435", - "open_interest": "364.5", - "volume": "20003.28", + "bid": "0.0265", + "ask": "0.027", + "open_interest": "1539.6", + "volume": "31915.53", "strike": "74000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4101679", - "iv_ask": "0.4168459", + "iv_bid": "0.3805915", + "iv_ask": "0.385045", "inverse": true }, { - "bid": "0.0475", - "ask": "0.0485", - "open_interest": "2108.6", - "volume": "223992.04", + "bid": "0.031", + "ask": "0.0315", + "open_interest": "2072.4", + "volume": "8294.88", "strike": "75000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4071464", - "iv_ask": "0.4136411", + "iv_bid": "0.376697", + "iv_ask": "0.3809547", "inverse": true }, { - "bid": "0.0525", - "ask": "0.0535", - "open_interest": "687.1", - "volume": "327012.65", + "bid": "0.0355", + "ask": "0.0365", + "open_interest": "1697.8", + "volume": "38046.47", "strike": "76000", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4016744", - "iv_ask": "0.4080308", + "iv_bid": "0.3684674", + "iv_ask": "0.3767064", "inverse": true }, { - "bid": "0.0635", - "ask": "0.0645", - "open_interest": "1441.2", - "volume": "34976.14", + "bid": "0.043", + "ask": "0.044", + "open_interest": "1809.7", + "volume": "328917.75", "strike": "78000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3960528", - "iv_ask": "0.4022545", + "iv_bid": "0.3585696", + "iv_ask": "0.3665627", "inverse": true }, { - "bid": "0.0515", - "ask": "0.0525", - "open_interest": "3569.8", - "volume": "2513250", + "bid": "0.0315", + "ask": "0.032", + "open_interest": "3958.1", + "volume": "477882.47", "strike": "80000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3907505", - "iv_ask": "0.3969573", + "iv_bid": "0.352804", + "iv_ask": "0.3568861", "inverse": true }, { - "bid": "0.0415", - "ask": "0.0425", - "open_interest": "628.9", - "volume": "54016.85", + "bid": "0.022", + "ask": "0.023", + "open_interest": "3064.7", + "volume": "248114.98", "strike": "82000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3880683", - "iv_ask": "0.3944372", + "iv_bid": "0.3447232", + "iv_ask": "0.3535177", "inverse": true }, { - "bid": "0.033", - "ask": "0.0335", - "open_interest": "621.1", - "volume": "54173.91", + "bid": "0.015", + "ask": "0.016", + "open_interest": "1095.7", + "volume": "220482.13", "strike": "84000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3854263", - "iv_ask": "0.3887765", + "iv_bid": "0.3399865", + "iv_ask": "0.3499685", "inverse": true }, { - "bid": "0.029", - "ask": "0.03", - "open_interest": "4113.3", - "volume": "793351.7", + "bid": "0.0125", + "ask": "0.0135", + "open_interest": "4598.5", + "volume": "126055.1", "strike": "85000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3821867", - "iv_ask": "0.3891201", + "iv_bid": "0.3408542", + "iv_ask": "0.351629", "inverse": true }, { - "bid": "0.0255", - "ask": "0.0265", - "open_interest": "380.4", - "volume": "6595.8", + "bid": "0.01", + "ask": "0.011", + "open_interest": "1109.5", + "volume": "7148.1", "strike": "86000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3800176", - "iv_ask": "0.3872386", + "iv_bid": "0.3373432", + "iv_ask": "0.3492113", "inverse": true }, { - "bid": "0.02", - "ask": "0.0205", - "open_interest": "1700.4", - "volume": "1400183.63", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "1974.1", + "volume": "16982.56", "strike": "88000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3800168", - "iv_ask": "0.3839904", - "inverse": true - }, - { - "bid": "0.015", - "ask": "0.016", - "open_interest": "6366.2", - "volume": "54063.27", - "strike": "90000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3751772", - "iv_ask": "0.384126", - "inverse": true - }, - { - "bid": "0.0115", - "ask": "0.0125", - "open_interest": "165.3", - "volume": "18050.06", - "strike": "92000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3752646", - "iv_ask": "0.3854642", - "inverse": true - }, - { - "bid": "0.0075", - "ask": "0.008", - "open_interest": "3273.8", - "volume": "48362.74", - "strike": "95000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3741708", - "iv_ask": "0.3806715", - "inverse": true - }, - { - "bid": "0.004", - "ask": "0.0042", - "open_interest": "3712.3", - "volume": "21691.86", - "strike": "100000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.382784", - "iv_ask": "0.3867213", - "inverse": true - }, - { - "bid": "0.0021", - "ask": "0.0023", - "open_interest": "1825.8", - "volume": "8512.31", - "strike": "105000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.3902033", - "iv_ask": "0.3963578", + "iv_bid": "0.3430513", + "iv_ask": "0.3502962", "inverse": true }, - { - "bid": "0.0011", - "ask": "0.0014", - "open_interest": "1453.8", - "volume": "3311.61", - "strike": "110000", + { + "bid": "0.0047", + "ask": "0.0049", + "open_interest": "5606.5", + "volume": "516356.96", + "strike": "90000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3972395", - "iv_ask": "0.4115515", + "iv_bid": "0.345534", + "iv_ask": "0.3492293", "inverse": true }, { - "bid": "0.0006", - "ask": "0.0008", - "open_interest": "2391.3", - "volume": "515.9", - "strike": "115000", + "bid": "0.0031", + "ask": "0.0033", + "open_interest": "651", + "volume": "7902.37", + "strike": "92000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4058538", - "iv_ask": "0.4210114", + "iv_bid": "0.3476826", + "iv_ask": "0.3524896", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0007", - "open_interest": "2121.6", - "volume": "3.89", - "strike": "120000", + "bid": "0.0021", + "ask": "0.0024", + "open_interest": "128", + "volume": "157.03", + "strike": "94000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4228396", - "iv_ask": "0.4519006", + "iv_bid": "0.3524808", + "iv_ask": "0.3617506", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0006", - "open_interest": "1877.2", - "volume": "0", - "strike": "125000", + "bid": "0.0019", + "ask": "0.002", + "open_interest": "3914.1", + "volume": "58056.38", + "strike": "95000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4431012", - "iv_ask": "0.4789481", + "iv_bid": "0.361102", + "iv_ask": "0.3645497", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0004", - "open_interest": "1532.4", - "volume": "64.54", - "strike": "130000", + "bid": "0.0009", + "ask": "0.0011", + "open_interest": "3792.4", + "volume": "242.72", + "strike": "100000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4749204", - "iv_ask": "0.4897225", + "iv_bid": "0.3843633", + "iv_ask": "0.3961793", "inverse": true }, { - "bid": "0.0002", - "ask": "0.0003", - "open_interest": "819.1", - "volume": "2.33", - "strike": "135000", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "1486.9", + "volume": "123.9", + "strike": "105000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4852468", - "iv_ask": "0.5052415", + "iv_bid": "0.4005138", + "iv_ask": "0.4215658", "inverse": true }, { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "742.6", - "volume": "128.7", - "strike": "140000", + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "1335", + "volume": "0", + "strike": "110000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4820369", - "iv_ask": "0.5342025", + "iv_bid": "0.4384558", + "iv_ask": "0.4656032", "inverse": true }, { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "595.4", - "volume": "14.21", - "strike": "145000", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "2510.2", + "volume": "0", + "strike": "115000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5076161", - "iv_ask": "0.5401968", + "iv_bid": "0.4662348", + "iv_ask": "0.5027862", "inverse": true }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "2595.8", + "ask": "0.0004", + "open_interest": "2689.7", "volume": "0", - "strike": "150000", + "strike": "120000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5321674", - "iv_ask": "0.588503", + "iv_bid": "0.4775807", + "iv_ask": "0.5495393", "inverse": true }, { "bid": "0.0001", - "ask": "0.0003", - "open_interest": "254.9", + "ask": "0.0004", + "open_interest": "1875.4", "volume": "0", - "strike": "155000", + "strike": "125000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5557704", - "iv_ask": "0.6140355", + "iv_bid": "0.5172833", + "iv_ask": "0.5938204", "inverse": true }, { "bid": "0.0001", - "ask": "0.0002", - "open_interest": "1221.2", + "ask": "0.0003", + "open_interest": "1493.3", "volume": "0", - "strike": "160000", + "strike": "130000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5784956", - "iv_ask": "0.6145991", + "iv_bid": "0.5550711", + "iv_ask": "0.6166916", "inverse": true }, { "bid": "0.0001", - "ask": "0.0002", - "open_interest": "177", + "ask": "0.0003", + "open_interest": "881.7", "volume": "0", - "strike": "165000", + "strike": "135000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.6004055", - "iv_ask": "0.6375765", + "iv_bid": "0.5911258", + "iv_ask": "0.6558426", "inverse": true }, { "bid": "0.0001", "ask": "0.0002", - "open_interest": "753.2", + "open_interest": "732", "volume": "0", - "strike": "170000", + "strike": "140000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.6215564", - "iv_ask": "0.6597485", + "iv_bid": "0.6256028", + "iv_ask": "0.6661626", "inverse": true }, { "bid": "0.0001", - "ask": "0.0002", - "open_interest": "1178.8", - "volume": "0", - "strike": "180000", + "ask": "0.0003", + "open_interest": "589.1", + "volume": "15.45", + "strike": "145000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.661777", - "iv_ask": "0.7018874", + "iv_bid": "0.658636", + "iv_ask": "0.7290331", "inverse": true }, { - "bid": "78012.0", - "ask": "78185.5", - "open_interest": "0", - "volume": "0", + "bid": "77830", + "ask": "77847.5", + "open_interest": "5813390", + "volume": "845200", "maturity": "2026-07-31T08:00:00Z", "security_type": "forward" }, { - "bid": "0.0125", - "ask": "0.0135", - "open_interest": "43.1", - "volume": "1405.02", + "bid": "0.006", + "ask": "0.007", + "open_interest": "88.9", + "volume": "256.12", + "strike": "56000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5097194", + "iv_ask": "0.5287411", + "inverse": true + }, + { + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "169.6", + "volume": "9347.53", "strike": "58000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4928138", - "iv_ask": "0.5042139", + "iv_bid": "0.4919384", + "iv_ask": "0.5081114", "inverse": true }, { - "bid": "0.0155", - "ask": "0.0165", - "open_interest": "38.6", - "volume": "13565.91", + "bid": "0.0095", + "ask": "0.01", + "open_interest": "120.9", + "volume": "409.89", "strike": "60000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4816157", - "iv_ask": "0.4915485", + "iv_bid": "0.4766844", + "iv_ask": "0.4836297", "inverse": true }, { - "bid": "0.019", - "ask": "0.0195", - "open_interest": "67.2", - "volume": "53391.07", + "bid": "0.012", + "ask": "0.0125", + "open_interest": "431.8", + "volume": "19340.44", "strike": "62000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4699701", - "iv_ask": "0.4743675", + "iv_bid": "0.4620901", + "iv_ask": "0.468025", "inverse": true }, { - "bid": "0.023", - "ask": "0.0235", - "open_interest": "17.3", - "volume": "1552.84", + "bid": "0.015", + "ask": "0.0155", + "open_interest": "195", + "volume": "61326.6", "strike": "64000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.457463", - "iv_ask": "0.4613839", + "iv_bid": "0.4470167", + "iv_ask": "0.4521543", "inverse": true }, { - "bid": "0.028", - "ask": "0.0285", - "open_interest": "8.8", - "volume": "640.92", + "bid": "0.0185", + "ask": "0.0195", + "open_interest": "98.1", + "volume": "6788.44", "strike": "66000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4473491", - "iv_ask": "0.4508734", + "iv_bid": "0.4307497", + "iv_ask": "0.4397374", "inverse": true }, { - "bid": "0.0305", - "ask": "0.0315", - "open_interest": "49.5", - "volume": "1903.78", + "bid": "0.021", + "ask": "0.022", + "open_interest": "99.9", + "volume": "7997.5", "strike": "67000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4402805", - "iv_ask": "0.4469974", + "iv_bid": "0.4267865", + "iv_ask": "0.4352005", "inverse": true }, { - "bid": "0.0335", - "ask": "0.0345", - "open_interest": "26.5", - "volume": "238.29", + "bid": "0.0235", + "ask": "0.0245", + "open_interest": "257.6", + "volume": "3209.66", "strike": "68000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4352386", - "iv_ask": "0.441658", + "iv_bid": "0.420802", + "iv_ask": "0.4287368", "inverse": true }, { - "bid": "0.037", - "ask": "0.038", - "open_interest": "26.9", - "volume": "262.04", + "bid": "0.026", + "ask": "0.027", + "open_interest": "124.1", + "volume": "0", "strike": "69000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4318989", - "iv_ask": "0.4380467", + "iv_bid": "0.4130091", + "iv_ask": "0.4205365", "inverse": true }, { - "bid": "0.0405", - "ask": "0.0415", - "open_interest": "20.1", - "volume": "16806.44", + "bid": "0.0295", + "ask": "0.03", + "open_interest": "733.5", + "volume": "30409.52", "strike": "70000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4270392", - "iv_ask": "0.4329541", + "iv_bid": "0.4107304", + "iv_ask": "0.4143027", "inverse": true }, { - "bid": "0.0445", - "ask": "0.0455", - "open_interest": "0.2", - "volume": "666.55", + "bid": "0.0325", + "ask": "0.0335", + "open_interest": "89.1", + "volume": "780.56", "strike": "71000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4236284", - "iv_ask": "0.4293353", + "iv_bid": "0.4028269", + "iv_ask": "0.4096516", "inverse": true }, { - "bid": "0.049", - "ask": "0.05", - "open_interest": "3.2", - "volume": "361.22", + "bid": "0.0365", + "ask": "0.0375", + "open_interest": "383.4", + "volume": "58255.75", "strike": "72000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4214726", - "iv_ask": "0.4269974", + "iv_bid": "0.3997415", + "iv_ask": "0.4062813", "inverse": true }, { - "bid": "0.0535", - "ask": "0.0545", - "open_interest": "0.3", - "volume": "1240.9", + "bid": "0.0405", + "ask": "0.0415", + "open_interest": "53.5", + "volume": "21449.97", "strike": "73000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4177374", - "iv_ask": "0.4231095", + "iv_bid": "0.3945086", + "iv_ask": "0.4008148", "inverse": true }, { - "bid": "0.0585", - "ask": "0.0595", - "open_interest": "8.5", - "volume": "0", + "bid": "0.045", + "ask": "0.046", + "open_interest": "130.4", + "volume": "371.83", "strike": "74000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4151357", - "iv_ask": "0.4203781", + "iv_bid": "0.3903181", + "iv_ask": "0.3964291", "inverse": true }, { - "bid": "0.0635", - "ask": "0.0645", - "open_interest": "2.2", - "volume": "471.15", + "bid": "0.05", + "ask": "0.0505", + "open_interest": "337.8", + "volume": "381447.32", "strike": "75000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4109944", - "iv_ask": "0.416131", + "iv_bid": "0.3870014", + "iv_ask": "0.3899788", "inverse": true }, { - "bid": "0.069", - "ask": "0.0705", - "open_interest": "0.3", - "volume": "1030.72", + "bid": "0.055", + "ask": "0.056", + "open_interest": "400.1", + "volume": "2242.39", "strike": "76000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4078978", - "iv_ask": "0.4154751", + "iv_bid": "0.3815221", + "iv_ask": "0.3873571", "inverse": true }, { - "bid": "0.075", - "ask": "0.0765", - "open_interest": "8.2", - "volume": "46759.25", + "bid": "0.061", + "ask": "0.062", + "open_interest": "53.5", + "volume": "15760.08", "strike": "77000", "maturity": "2026-07-31T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4057753", - "iv_ask": "0.413257", + "iv_bid": "0.3796833", + "iv_ask": "0.385434", "inverse": true }, { - "bid": "0.0815", - "ask": "0.0825", - "open_interest": "24", - "volume": "0", + "bid": "0.065", + "ask": "0.066", + "open_interest": "255", + "volume": "161998.55", "strike": "78000", "maturity": "2026-07-31T08:00:00Z", - "option_type": "put", + "option_type": "call", "security_type": "option", - "iv_bid": "0.404587", - "iv_ask": "0.4095311", + "iv_bid": "0.37513", + "iv_ask": "0.3808316", "inverse": true }, { - "bid": "0.0765", - "ask": "0.0775", - "open_interest": "0", - "volume": "0", + "bid": "0.0585", + "ask": "0.0595", + "open_interest": "145.7", + "volume": "7476.58", "strike": "79000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4020575", - "iv_ask": "0.4069774", + "iv_bid": "0.3708304", + "iv_ask": "0.3765173", "inverse": true }, { - "bid": "0.0705", - "ask": "0.0715", - "open_interest": "138.9", - "volume": "267403.7", + "bid": "0.0525", + "ask": "0.0535", + "open_interest": "944.7", + "volume": "462038.01", "strike": "80000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3993124", - "iv_ask": "0.4042273", + "iv_bid": "0.3671482", + "iv_ask": "0.3728559", "inverse": true }, { - "bid": "0.065", - "ask": "0.066", - "open_interest": "0.1", - "volume": "0", + "bid": "0.0475", + "ask": "0.0485", + "open_interest": "818.7", + "volume": "51556.24", "strike": "81000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3975185", - "iv_ask": "0.4024473", + "iv_bid": "0.3669694", + "iv_ask": "0.3727311", "inverse": true }, { - "bid": "0.06", - "ask": "0.061", - "open_interest": "13.9", - "volume": "0", + "bid": "0.0425", + "ask": "0.0435", + "open_interest": "974.1", + "volume": "42233.75", "strike": "82000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3967051", - "iv_ask": "0.4016662", + "iv_bid": "0.3646151", + "iv_ask": "0.3704674", "inverse": true }, { - "bid": "0.055", - "ask": "0.056", - "open_interest": "2", - "volume": "4581.03", + "bid": "0.038", + "ask": "0.039", + "open_interest": "382.5", + "volume": "1542.4", "strike": "83000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3944221", - "iv_ask": "0.3994351", + "iv_bid": "0.3630191", + "iv_ask": "0.3689977", "inverse": true }, { - "bid": "0.0505", - "ask": "0.0515", - "open_interest": "1.1", + "bid": "0.034", + "ask": "0.0345", + "open_interest": "87.9", "volume": "0", "strike": "84000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3931913", - "iv_ask": "0.3982745", + "iv_bid": "0.3623045", + "iv_ask": "0.3653772", "inverse": true }, { - "bid": "0.046", - "ask": "0.0475", - "open_interest": "3.5", - "volume": "0", + "bid": "0.03", + "ask": "0.031", + "open_interest": "619.2", + "volume": "221575.57", "strike": "85000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3905105", - "iv_ask": "0.3982701", + "iv_bid": "0.3594643", + "iv_ask": "0.3658145", "inverse": true }, { - "bid": "0.042", - "ask": "0.0435", - "open_interest": "3.5", - "volume": "12678.25", + "bid": "0.0265", + "ask": "0.0275", + "open_interest": "48.8", + "volume": "641.27", "strike": "86000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3889773", - "iv_ask": "0.3969027", + "iv_bid": "0.3576603", + "iv_ask": "0.3642618", "inverse": true }, { - "bid": "0.0385", - "ask": "0.0395", - "open_interest": "5", - "volume": "16185.96", + "bid": "0.0235", + "ask": "0.024", + "open_interest": "294.2", + "volume": "5736.86", "strike": "87000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3887166", - "iv_ask": "0.3941323", + "iv_bid": "0.3571286", + "iv_ask": "0.3605819", "inverse": true }, { - "bid": "0.035", - "ask": "0.036", - "open_interest": "0.5", - "volume": "841.8", + "bid": "0.0205", + "ask": "0.0215", + "open_interest": "232.6", + "volume": "156792.29", "strike": "88000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3870983", - "iv_ask": "0.3926691", + "iv_bid": "0.3545421", + "iv_ask": "0.361794", "inverse": true }, { - "bid": "0.032", - "ask": "0.033", - "open_interest": "1.2", + "bid": "0.018", + "ask": "0.019", + "open_interest": "120.3", "volume": "0", "strike": "89000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3869391", - "iv_ask": "0.3926805", + "iv_bid": "0.3535409", + "iv_ask": "0.3611957", "inverse": true }, { - "bid": "0.029", - "ask": "0.03", - "open_interest": "334.3", - "volume": "702111.4", + "bid": "0.016", + "ask": "0.0165", + "open_interest": "988.5", + "volume": "99813.86", "strike": "90000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3854712", - "iv_ask": "0.3914152", + "iv_bid": "0.3545589", + "iv_ask": "0.358619", "inverse": true }, { - "bid": "0.0265", - "ask": "0.0275", - "open_interest": "5", - "volume": "0", + "bid": "0.014", + "ask": "0.0145", + "open_interest": "266.6", + "volume": "2532.19", "strike": "91000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3857126", - "iv_ask": "0.391871", + "iv_bid": "0.3537972", + "iv_ask": "0.3581236", "inverse": true }, { - "bid": "0.024", - "ask": "0.025", - "open_interest": "5.2", - "volume": "0", + "bid": "0.0125", + "ask": "0.013", + "open_interest": "272.6", + "volume": "197.69", "strike": "92000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3847205", - "iv_ask": "0.3911306", + "iv_bid": "0.3556985", + "iv_ask": "0.3602919", "inverse": true }, { - "bid": "0.0215", - "ask": "0.0225", - "open_interest": "0", - "volume": "0", + "bid": "0.0105", + "ask": "0.0115", + "open_interest": "96.7", + "volume": "9799.23", "strike": "93000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3824065", - "iv_ask": "0.3891159", + "iv_bid": "0.3510342", + "iv_ask": "0.3609526", "inverse": true }, { - "bid": "0.0195", - "ask": "0.0205", - "open_interest": "68.4", - "volume": "81762.95", + "bid": "0.0095", + "ask": "0.01", + "open_interest": "836.2", + "volume": "6385.51", "strike": "94000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3821924", - "iv_ask": "0.3892072", + "iv_bid": "0.3546241", + "iv_ask": "0.3599375", "inverse": true }, { - "bid": "0.016", - "ask": "0.0165", - "open_interest": "19.9", - "volume": "133149.86", + "bid": "0.008", + "ask": "0.009", + "open_interest": "45", + "volume": "66.25", + "strike": "95000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3511667", + "iv_ask": "0.3626681", + "inverse": true + }, + { + "bid": "0.007", + "ask": "0.008", + "open_interest": "183.5", + "volume": "0", "strike": "96000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3819147", - "iv_ask": "0.3857958", + "iv_bid": "0.3517152", + "iv_ask": "0.3641281", "inverse": true }, { - "bid": "0.013", - "ask": "0.014", - "open_interest": "1018.5", - "volume": "15984.97", + "bid": "0.0055", + "ask": "0.006", + "open_interest": "1102.9", + "volume": "368.18", "strike": "98000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3810492", - "iv_ask": "0.3896375", + "iv_bid": "0.3552503", + "iv_ask": "0.3625767", "inverse": true }, { - "bid": "0.0105", - "ask": "0.011", - "open_interest": "33.2", - "volume": "17857.78", + "bid": "0.0043", + "ask": "0.0047", + "open_interest": "152.6", + "volume": "3528.56", "strike": "100000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3801337", - "iv_ask": "0.3850011", + "iv_bid": "0.3583671", + "iv_ask": "0.3652718", "inverse": true }, { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "9.2", - "volume": "4041.41", + "bid": "0.0034", + "ask": "0.0038", + "open_interest": "109.9", + "volume": "347.78", "strike": "102000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.379947", - "iv_ask": "0.3908171", + "iv_bid": "0.3622637", + "iv_ask": "0.3703492", "inverse": true }, { - "bid": "0.007", - "ask": "0.008", - "open_interest": "11.4", - "volume": "96898.84", + "bid": "0.0027", + "ask": "0.003", + "open_interest": "337.4", + "volume": "76946.24", "strike": "104000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3816213", - "iv_ask": "0.3938358", + "iv_bid": "0.3662936", + "iv_ask": "0.3734663", + "inverse": true + }, + { + "bid": "0.0025", + "ask": "0.0028", + "open_interest": "362.8", + "volume": "1302.71", + "strike": "105000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3707317", + "iv_ask": "0.3783452", + "inverse": true + }, + { + "bid": "0.0022", + "ask": "0.0025", + "open_interest": "39.7", + "volume": "18.68", + "strike": "106000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3717582", + "iv_ask": "0.3800736", "inverse": true }, { - "bid": "0.0055", - "ask": "0.0065", - "open_interest": "12.9", - "volume": "1866.75", - "strike": "106000", + "bid": "0.0017", + "ask": "0.0021", + "open_interest": "244.1", + "volume": "1089.58", + "strike": "108000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3795041", - "iv_ask": "0.3935809", + "iv_bid": "0.3737024", + "iv_ask": "0.3867538", "inverse": true }, { - "bid": "0.004", - "ask": "0.0043", - "open_interest": "34", - "volume": "490.85", + "bid": "0.0014", + "ask": "0.0017", + "open_interest": "137.6", + "volume": "0", "strike": "110000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3885566", - "iv_ask": "0.3940005", + "iv_bid": "0.3792555", + "iv_ask": "0.3907244", "inverse": true }, { - "bid": "0.0024", - "ask": "0.0029", - "open_interest": "11.1", - "volume": "497.37", + "bid": "0.001", + "ask": "0.0011", + "open_interest": "23.8", + "volume": "7.69", "strike": "115000", "maturity": "2026-07-31T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3910344", - "iv_ask": "0.4036299", + "iv_bid": "0.3996393", + "iv_ask": "0.4049242", + "inverse": true + }, + { + "bid": "0.0005", + "ask": "0.0007", + "open_interest": "163.1", + "volume": "4.67", + "strike": "120000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3996656", + "iv_ask": "0.416279", "inverse": true }, { - "bid": "78407.5", - "ask": "78410", - "open_interest": "276427820", - "volume": "7071210", + "bid": "78167.5", + "ask": "78170", + "open_interest": "289191460", + "volume": "3088870", "maturity": "2026-09-25T08:00:00Z", "security_type": "forward" }, { - "bid": "0.0023", - "ask": "0.0027", - "open_interest": "1576.9", - "volume": "103.03", + "bid": "0.0015", + "ask": "0.0018", + "open_interest": "1748.7", + "volume": "5732.93", "strike": "30000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.7387981", - "iv_ask": "0.7578277", + "iv_bid": "0.7540254", + "iv_ask": "0.7742621", "inverse": true }, { - "bid": "0.0036", - "ask": "0.004", - "open_interest": "543.3", - "volume": "544.4", + "bid": "0.0024", + "ask": "0.0027", + "open_interest": "546.3", + "volume": "20.9", "strike": "35000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6780532", - "iv_ask": "0.6908654", + "iv_bid": "0.6887831", + "iv_ask": "0.7020779", "inverse": true }, { - "bid": "0.0055", - "ask": "0.006", - "open_interest": "1844.5", - "volume": "357.77", + "bid": "0.0037", + "ask": "0.0041", + "open_interest": "2023.9", + "volume": "5282.55", "strike": "40000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6249601", - "iv_ask": "0.6360055", + "iv_bid": "0.6299359", + "iv_ask": "0.6418935", "inverse": true }, { - "bid": "0.008", - "ask": "0.0085", - "open_interest": "1676.4", - "volume": "444.41", + "bid": "0.006", + "ask": "0.0065", + "open_interest": "1629.2", + "volume": "186.8", "strike": "45000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5737061", - "iv_ask": "0.5817025", + "iv_bid": "0.5851877", + "iv_ask": "0.595219", "inverse": true }, { - "bid": "0.012", - "ask": "0.013", - "open_interest": "3174", - "volume": "14910.53", + "bid": "0.009", + "ask": "0.0095", + "open_interest": "3168.2", + "volume": "6377.66", "strike": "50000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5328755", - "iv_ask": "0.5443926", + "iv_bid": "0.5376121", + "iv_ask": "0.5447974", "inverse": true }, { - "bid": "0.0185", - "ask": "0.0195", - "open_interest": "1520.4", - "volume": "12538.34", + "bid": "0.014", + "ask": "0.0145", + "open_interest": "1833.4", + "volume": "445004.22", "strike": "55000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5023818", - "iv_ask": "0.5109083", + "iv_bid": "0.5004397", + "iv_ask": "0.5056182", "inverse": true }, { - "bid": "0.02", - "ask": "0.021", - "open_interest": "102.9", - "volume": "713.74", + "bid": "0.0155", + "ask": "0.016", + "open_interest": "106.6", + "volume": "120.8", "strike": "56000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4957369", - "iv_ask": "0.5038211", + "iv_bid": "0.4957336", + "iv_ask": "0.5005767", "inverse": true }, { - "bid": "0.0235", - "ask": "0.0245", - "open_interest": "551.9", - "volume": "174.49", + "bid": "0.0185", + "ask": "0.019", + "open_interest": "602.6", + "volume": "9110.88", "strike": "58000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4840656", - "iv_ask": "0.4913477", + "iv_bid": "0.4831756", + "iv_ask": "0.4874751", "inverse": true }, { - "bid": "0.0275", - "ask": "0.0285", - "open_interest": "3852.1", - "volume": "27987.2", + "bid": "0.0215", + "ask": "0.0225", + "open_interest": "3349.6", + "volume": "440069", "strike": "60000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4727332", - "iv_ask": "0.4793387", + "iv_bid": "0.4671972", + "iv_ask": "0.4749172", "inverse": true }, { - "bid": "0.0325", - "ask": "0.0335", - "open_interest": "719.5", - "volume": "11432.55", + "bid": "0.026", + "ask": "0.0265", + "open_interest": "827.2", + "volume": "18861.5", "strike": "62000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4644218", - "iv_ask": "0.470436", + "iv_bid": "0.45894", + "iv_ask": "0.46241", "inverse": true }, { - "bid": "0.038", - "ask": "0.039", - "open_interest": "212.7", - "volume": "1163.89", + "bid": "0.0305", + "ask": "0.032", + "open_interest": "184.5", + "volume": "1500.8", "strike": "64000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4553685", - "iv_ask": "0.4608988", + "iv_bid": "0.4464424", + "iv_ask": "0.4558838", "inverse": true }, { - "bid": "0.0415", - "ask": "0.0425", - "open_interest": "460.2", - "volume": "7438.92", + "bid": "0.0335", + "ask": "0.0345", + "open_interest": "627.8", + "volume": "40437.02", "strike": "65000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.453539", - "iv_ask": "0.4588471", + "iv_bid": "0.4433767", + "iv_ask": "0.4493944", "inverse": true }, { - "bid": "0.0445", - "ask": "0.0455", - "open_interest": "114.2", - "volume": "656.41", + "bid": "0.0365", + "ask": "0.0375", + "open_interest": "177.5", + "volume": "0", "strike": "66000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.448075", - "iv_ask": "0.4531978", + "iv_bid": "0.4391212", + "iv_ask": "0.4448873", "inverse": true }, { - "bid": "0.052", - "ask": "0.053", - "open_interest": "576.2", - "volume": "2371.39", + "bid": "0.0435", + "ask": "0.0445", + "open_interest": "561.7", + "volume": "21198.3", "strike": "68000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.441974", - "iv_ask": "0.4467614", + "iv_bid": "0.4327811", + "iv_ask": "0.4381104", "inverse": true }, { - "bid": "0.0605", - "ask": "0.0615", - "open_interest": "1487.2", - "volume": "64675.18", + "bid": "0.051", + "ask": "0.052", + "open_interest": "1446.8", + "volume": "118968.71", "strike": "70000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4366797", - "iv_ask": "0.4411965", + "iv_bid": "0.4244617", + "iv_ask": "0.42945", "inverse": true }, { - "bid": "0.0695", - "ask": "0.071", - "open_interest": "261.1", - "volume": "31282.09", + "bid": "0.06", + "ask": "0.061", + "open_interest": "277.9", + "volume": "24486.2", "strike": "72000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4297844", - "iv_ask": "0.4362407", + "iv_bid": "0.4189874", + "iv_ask": "0.423705", "inverse": true }, { - "bid": "0.08", - "ask": "0.081", - "open_interest": "68.6", - "volume": "6700.79", + "bid": "0.0695", + "ask": "0.0705", + "open_interest": "88.8", + "volume": "0", "strike": "74000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4255159", - "iv_ask": "0.4296568", + "iv_bid": "0.4112388", + "iv_ask": "0.4157551", "inverse": true }, { - "bid": "0.0855", - "ask": "0.0865", - "open_interest": "3022.8", - "volume": "670604", + "bid": "0.075", + "ask": "0.076", + "open_interest": "3024.3", + "volume": "12412.49", "strike": "75000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4229795", - "iv_ask": "0.4270557", + "iv_bid": "0.4090425", + "iv_ask": "0.4134802", "inverse": true }, { - "bid": "0.091", - "ask": "0.0925", - "open_interest": "369.5", - "volume": "0", + "bid": "0.08", + "ask": "0.0815", + "open_interest": "567.6", + "volume": "12494.14", "strike": "76000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4195139", - "iv_ask": "0.425547", + "iv_bid": "0.4034978", + "iv_ask": "0.4100583", "inverse": true }, { - "bid": "0.1035", - "ask": "0.1045", - "open_interest": "252.9", - "volume": "368973.54", + "bid": "0.092", + "ask": "0.0935", + "open_interest": "623.1", + "volume": "212534.55", "strike": "78000", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4158037", - "iv_ask": "0.4197477", + "iv_bid": "0.3977517", + "iv_ask": "0.4041812", "inverse": true }, { - "bid": "0.0965", - "ask": "0.0975", - "open_interest": "1788", - "volume": "303998.18", + "bid": "0.0825", + "ask": "0.0835", + "open_interest": "2270.4", + "volume": "412339.64", "strike": "80000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4115207", - "iv_ask": "0.4154247", + "iv_bid": "0.3942133", + "iv_ask": "0.398465", "inverse": true }, { - "bid": "0.086", - "ask": "0.087", - "open_interest": "985.2", - "volume": "0", + "bid": "0.0715", + "ask": "0.0725", + "open_interest": "1428.7", + "volume": "14743.49", "strike": "82000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4102988", - "iv_ask": "0.414199", + "iv_bid": "0.3903293", + "iv_ask": "0.3945973", "inverse": true }, { - "bid": "0.076", - "ask": "0.077", - "open_interest": "570.9", - "volume": "12082.44", + "bid": "0.062", + "ask": "0.063", + "open_interest": "624.4", + "volume": "37259.74", "strike": "84000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4075023", - "iv_ask": "0.411433", + "iv_bid": "0.3884229", + "iv_ask": "0.3927561", "inverse": true }, { - "bid": "0.071", - "ask": "0.0725", - "open_interest": "589.4", - "volume": "17080.56", + "bid": "0.0575", + "ask": "0.0585", + "open_interest": "775.1", + "volume": "1366.21", "strike": "85000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4047999", - "iv_ask": "0.4107383", + "iv_bid": "0.3869298", + "iv_ask": "0.3913145", "inverse": true }, { - "bid": "0.067", - "ask": "0.068", - "open_interest": "448", + "bid": "0.053", + "ask": "0.054", + "open_interest": "464.4", "volume": "0", "strike": "86000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4052159", - "iv_ask": "0.4092107", + "iv_bid": "0.3843524", + "iv_ask": "0.3888036", "inverse": true }, { - "bid": "0.059", - "ask": "0.06", - "open_interest": "306.5", + "bid": "0.0455", + "ask": "0.0465", + "open_interest": "323", "volume": "0", "strike": "88000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4036245", - "iv_ask": "0.4077164", + "iv_bid": "0.3827502", + "iv_ask": "0.3873678", "inverse": true }, { - "bid": "0.0515", - "ask": "0.0525", - "open_interest": "1433.9", - "volume": "4157939.27", + "bid": "0.0385", + "ask": "0.0395", + "open_interest": "1627.1", + "volume": "8932.45", "strike": "90000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4008612", - "iv_ask": "0.4050876", + "iv_bid": "0.3793618", + "iv_ask": "0.3842081", "inverse": true }, { - "bid": "0.045", - "ask": "0.046", - "open_interest": "307.8", - "volume": "43549.7", + "bid": "0.033", + "ask": "0.034", + "open_interest": "530.1", + "volume": "2938.7", "strike": "92000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3991974", - "iv_ask": "0.4035926", + "iv_bid": "0.3793488", + "iv_ask": "0.3844671", "inverse": true }, { - "bid": "0.0395", - "ask": "0.04", - "open_interest": "89.7", - "volume": "60443.34", + "bid": "0.028", + "ask": "0.029", + "open_interest": "133.4", + "volume": "441.68", "strike": "94000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3989996", - "iv_ask": "0.4013002", + "iv_bid": "0.3782503", + "iv_ask": "0.3837088", "inverse": true }, { - "bid": "0.0365", - "ask": "0.0375", - "open_interest": "482.3", - "volume": "54697.29", + "bid": "0.0255", + "ask": "0.0265", + "open_interest": "1228.3", + "volume": "393.25", "strike": "95000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3966385", - "iv_ask": "0.4013613", + "iv_bid": "0.3762776", + "iv_ask": "0.3819479", "inverse": true }, { - "bid": "0.034", - "ask": "0.035", - "open_interest": "114.4", - "volume": "1963.53", + "bid": "0.0235", + "ask": "0.0245", + "open_interest": "194", + "volume": "0", "strike": "96000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3958875", - "iv_ask": "0.4007404", + "iv_bid": "0.3762327", + "iv_ask": "0.3821139", "inverse": true }, { - "bid": "0.0295", - "ask": "0.0305", - "open_interest": "82.9", - "volume": "37404.99", + "bid": "0.02", + "ask": "0.021", + "open_interest": "159", + "volume": "0", "strike": "98000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3947589", - "iv_ask": "0.3999035", + "iv_bid": "0.3766939", + "iv_ask": "0.3830441", "inverse": true }, { - "bid": "0.0255", - "ask": "0.0265", - "open_interest": "3607.7", - "volume": "25051.74", + "bid": "0.017", + "ask": "0.018", + "open_interest": "3887.7", + "volume": "8998.15", "strike": "100000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3935173", - "iv_ask": "0.3990043", + "iv_bid": "0.3772592", + "iv_ask": "0.3841538", "inverse": true }, { - "bid": "0.018", - "ask": "0.0185", - "open_interest": "1003.3", - "volume": "41824.99", + "bid": "0.0145", + "ask": "0.0155", + "open_interest": "44.1", + "volume": "0", + "strike": "102000", + "maturity": "2026-09-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3784069", + "iv_ask": "0.3859159", + "inverse": true + }, + { + "bid": "0.011", + "ask": "0.012", + "open_interest": "1354", + "volume": "0", "strike": "105000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3938507", - "iv_ask": "0.3971326", + "iv_bid": "0.3765661", + "iv_ask": "0.385299", "inverse": true }, { - "bid": "0.0125", - "ask": "0.013", - "open_interest": "1752.4", - "volume": "24687.25", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1496.2", + "volume": "1051.45", "strike": "110000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3933668", - "iv_ask": "0.3973912", + "iv_bid": "0.3816648", + "iv_ask": "0.3926917", "inverse": true }, { - "bid": "0.0085", - "ask": "0.009", - "open_interest": "2039.5", - "volume": "14708.22", + "bid": "0.0055", + "ask": "0.006", + "open_interest": "2420.3", + "volume": "1027.39", "strike": "115000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.3918607", - "iv_ask": "0.3969331", + "iv_bid": "0.3919664", + "iv_ask": "0.398899", "inverse": true }, { - "bid": "0.0065", - "ask": "0.007", - "open_interest": "922.8", - "volume": "2026.33", + "bid": "0.0039", + "ask": "0.0042", + "open_interest": "984.4", + "volume": "126.73", "strike": "120000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4004925", - "iv_ask": "0.4065865", + "iv_bid": "0.3984314", + "iv_ask": "0.4037731", "inverse": true }, { - "bid": "0.0047", - "ask": "0.0055", - "open_interest": "2218.4", - "volume": "203.19", + "bid": "0.0029", + "ask": "0.0033", + "open_interest": "2266.7", + "volume": "495.74", "strike": "125000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4038175", - "iv_ask": "0.4157425", + "iv_bid": "0.4072225", + "iv_ask": "0.4159936", "inverse": true }, { - "bid": "0.0036", - "ask": "0.0038", - "open_interest": "2226.1", - "volume": "351.74", + "bid": "0.0022", + "ask": "0.0025", + "open_interest": "1425.3", + "volume": "74.41", "strike": "130000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4107317", - "iv_ask": "0.4144832", + "iv_bid": "0.4161483", + "iv_ask": "0.4243203", "inverse": true }, { - "bid": "0.0027", - "ask": "0.0031", - "open_interest": "146.6", - "volume": "2608.55", + "bid": "0.0017", + "ask": "0.002", + "open_interest": "159.9", + "volume": "0", "strike": "135000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4154483", - "iv_ask": "0.4245169", + "iv_bid": "0.4250954", + "iv_ask": "0.4350339", "inverse": true }, { - "bid": "0.0021", - "ask": "0.0025", - "open_interest": "1134.5", - "volume": "4327.41", + "bid": "0.0013", + "ask": "0.0016", + "open_interest": "1137.2", + "volume": "0", "strike": "140000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4218051", - "iv_ask": "0.432708", + "iv_bid": "0.4324387", + "iv_ask": "0.4445829", "inverse": true }, { - "bid": "0.0017", - "ask": "0.002", - "open_interest": "475.8", + "bid": "0.0011", + "ask": "0.0014", + "open_interest": "477.2", "volume": "0", "strike": "145000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4297655", - "iv_ask": "0.4395167", + "iv_bid": "0.444507", + "iv_ask": "0.4584254", "inverse": true }, { - "bid": "0.0013", - "ask": "0.0015", - "open_interest": "542.4", - "volume": "35.08", + "bid": "0.0009", + "ask": "0.0011", + "open_interest": "758", + "volume": "0", "strike": "150000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4337369", - "iv_ask": "0.4418277", + "iv_bid": "0.4536976", + "iv_ask": "0.4648723", "inverse": true }, { - "bid": "0.0011", - "ask": "0.0013", - "open_interest": "316.7", - "volume": "10.28", + "bid": "0.0007", + "ask": "0.001", + "open_interest": "318.8", + "volume": "0", "strike": "155000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4426635", - "iv_ask": "0.4519309", + "iv_bid": "0.4593963", + "iv_ask": "0.4788539", "inverse": true }, { - "bid": "0.0009", - "ask": "0.0012", - "open_interest": "830.5", - "volume": "7.84", + "bid": "0.0006", + "ask": "0.0009", + "open_interest": "827.4", + "volume": "0", "strike": "160000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4490589", - "iv_ask": "0.4647861", + "iv_bid": "0.4694182", + "iv_ask": "0.491344", "inverse": true }, { - "bid": "0.0008", - "ask": "0.0012", - "open_interest": "127.7", + "bid": "0.0005", + "ask": "0.0008", + "open_interest": "131.4", "volume": "0", "strike": "165000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4591254", - "iv_ask": "0.4814855", + "iv_bid": "0.4772616", + "iv_ask": "0.5022968", "inverse": true }, { - "bid": "0.0006", - "ask": "0.0009", - "open_interest": "368.9", + "bid": "0.0004", + "ask": "0.0007", + "open_interest": "352.5", "volume": "0", "strike": "170000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4599512", - "iv_ask": "0.4810538", + "iv_bid": "0.482482", + "iv_ask": "0.5116173", "inverse": true }, { - "bid": "0.0005", - "ask": "0.0007", - "open_interest": "1412", - "volume": "18.81", + "bid": "0.0003", + "ask": "0.0006", + "open_interest": "1448.7", + "volume": "0", "strike": "175000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4656563", - "iv_ask": "0.4825423", + "iv_bid": "0.4842356", + "iv_ask": "0.5191386", "inverse": true }, { - "bid": "0.0004", - "ask": "0.0008", - "open_interest": "751.7", - "volume": "3.91", + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "751.2", + "volume": "0", "strike": "180000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.468996", - "iv_ask": "0.5041863", + "iv_bid": "0.4989006", + "iv_ask": "0.524579", "inverse": true }, { - "bid": "0.0003", - "ask": "0.0007", - "open_interest": "824.3", + "bid": "0.0002", + "ask": "0.0006", + "open_interest": "824.6", "volume": "0", "strike": "190000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4817765", - "iv_ask": "0.524214", + "iv_bid": "0.507848", + "iv_ask": "0.564019", "inverse": true }, { - "bid": "0.0002", - "ask": "0.0006", - "open_interest": "1128.2", + "bid": "0.0001", + "ask": "0.0004", + "open_interest": "1131.6", "volume": "0", "strike": "200000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.487836", - "iv_ask": "0.5411607", + "iv_bid": "0.503566", + "iv_ask": "0.5683326", "inverse": true }, { "bid": "0.0001", "ask": "0.0003", - "open_interest": "272.8", + "open_interest": "279.7", "volume": "0", "strike": "220000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5017463", - "iv_ask": "0.5501913", - "inverse": true - }, - { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "249.5", - "volume": "3.11", - "strike": "240000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option", - "iv_bid": "0.5389329", - "iv_ask": "0.5697779", + "iv_bid": "0.548504", + "iv_ask": "0.6014345", "inverse": true }, { - "bid": "79082.5", - "ask": "79092.5", - "open_interest": "121192440", - "volume": "2532540", + "bid": "78887.5", + "ask": "78892.5", + "open_interest": "125882130", + "volume": "1281370", "maturity": "2026-12-25T08:00:00Z", "security_type": "forward" }, { - "bid": "0.005", - "ask": "0.006", - "open_interest": "1404.7", - "volume": "0", + "bid": "0.0047", + "ask": "0.0049", + "open_interest": "1603.5", + "volume": "1929.79", "strike": "30000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6715134", - "iv_ask": "0.6952157", + "iv_bid": "0.6979647", + "iv_ask": "0.7033441", "inverse": true }, { - "bid": "0.008", - "ask": "0.009", - "open_interest": "2067.1", - "volume": "2809.05", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "2086.4", + "volume": "1155.98", "strike": "35000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.6310269", - "iv_ask": "0.6473544", + "iv_bid": "0.6448642", + "iv_ask": "0.6541921", "inverse": true }, { - "bid": "0.012", - "ask": "0.013", - "open_interest": "2801.6", - "volume": "3785.67", + "bid": "0.0105", + "ask": "0.011", + "open_interest": "3079.6", + "volume": "14963.8", "strike": "40000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5928292", - "iv_ask": "0.6046837", + "iv_bid": "0.6031506", + "iv_ask": "0.6098451", "inverse": true }, { - "bid": "0.017", - "ask": "0.018", - "open_interest": "1982.1", - "volume": "0", + "bid": "0.0145", + "ask": "0.0155", + "open_interest": "2102.6", + "volume": "233.87", "strike": "45000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5548153", - "iv_ask": "0.5638314", + "iv_bid": "0.557817", + "iv_ask": "0.5679378", "inverse": true }, { - "bid": "0.024", - "ask": "0.025", - "open_interest": "1403.5", - "volume": "13076.74", + "bid": "0.021", + "ask": "0.022", + "open_interest": "1406.2", + "volume": "173.42", "strike": "50000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5232647", - "iv_ask": "0.5302812", + "iv_bid": "0.5263559", + "iv_ask": "0.5340901", "inverse": true }, { - "bid": "0.0315", - "ask": "0.033", - "open_interest": "1708.2", - "volume": "477.09", + "bid": "0.028", + "ask": "0.0295", + "open_interest": "1710.7", + "volume": "0", "strike": "54000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5021622", - "iv_ask": "0.5109207", + "iv_bid": "0.5047503", + "iv_ask": "0.5142862", "inverse": true }, { - "bid": "0.0335", - "ask": "0.035", - "open_interest": "1004.5", - "volume": "17069.39", + "bid": "0.03", + "ask": "0.0315", + "open_interest": "911.7", + "volume": "14736.48", "strike": "55000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4963488", - "iv_ask": "0.5047596", + "iv_bid": "0.4995092", + "iv_ask": "0.5086321", "inverse": true }, { - "bid": "0.036", - "ask": "0.0375", - "open_interest": "70.9", - "volume": "5834.36", + "bid": "0.0325", + "ask": "0.034", + "open_interest": "72.2", + "volume": "0", "strike": "56000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4927808", - "iv_ask": "0.500843", + "iv_bid": "0.4966248", + "iv_ask": "0.5053355", "inverse": true }, { - "bid": "0.0415", - "ask": "0.043", - "open_interest": "59.9", - "volume": "315.08", + "bid": "0.0375", + "ask": "0.0385", + "open_interest": "127", + "volume": "13031.62", "strike": "58000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4863054", - "iv_ask": "0.4937464", + "iv_bid": "0.488647", + "iv_ask": "0.4939975", "inverse": true }, { - "bid": "0.047", - "ask": "0.0485", - "open_interest": "6301.9", - "volume": "5915.37", + "bid": "0.043", + "ask": "0.044", + "open_interest": "6493.6", + "volume": "230934.51", "strike": "60000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4773844", - "iv_ask": "0.4843161", + "iv_bid": "0.4806593", + "iv_ask": "0.485613", "inverse": true }, { - "bid": "0.0535", - "ask": "0.0555", - "open_interest": "601.8", + "bid": "0.049", + "ask": "0.0505", + "open_interest": "624.9", "volume": "0", "strike": "62000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4707383", - "iv_ask": "0.4793757", + "iv_bid": "0.4725478", + "iv_ask": "0.4794678", "inverse": true }, { - "bid": "0.061", - "ask": "0.0625", - "open_interest": "50.7", - "volume": "1417.49", + "bid": "0.056", + "ask": "0.0575", + "open_interest": "69.1", + "volume": "0", "strike": "64000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4658683", - "iv_ask": "0.4719648", + "iv_bid": "0.4663949", + "iv_ask": "0.4728811", "inverse": true }, { - "bid": "0.065", - "ask": "0.0665", - "open_interest": "409.3", - "volume": "0", + "bid": "0.0595", + "ask": "0.061", + "open_interest": "470.5", + "volume": "958527.77", "strike": "65000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4634874", - "iv_ask": "0.4694142", + "iv_bid": "0.4623341", + "iv_ask": "0.4686335", "inverse": true }, { - "bid": "0.069", - "ask": "0.071", - "open_interest": "37.4", - "volume": "15240", + "bid": "0.0635", + "ask": "0.065", + "open_interest": "126.6", + "volume": "0", "strike": "66000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4604877", - "iv_ask": "0.4681817", + "iv_bid": "0.4597206", + "iv_ask": "0.465844", "inverse": true }, { - "bid": "0.078", - "ask": "0.08", - "open_interest": "241.9", - "volume": "0", + "bid": "0.0725", + "ask": "0.0735", + "open_interest": "264.5", + "volume": "19352.04", "strike": "68000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4564555", - "iv_ask": "0.4637856", + "iv_bid": "0.4564068", + "iv_ask": "0.4602832", "inverse": true }, { - "bid": "0.0885", - "ask": "0.0895", - "open_interest": "1173.4", - "volume": "43093.73", + "bid": "0.0815", + "ask": "0.083", + "open_interest": "1124.6", + "volume": "6505.06", "strike": "70000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4553156", - "iv_ask": "0.4588291", + "iv_bid": "0.4503714", + "iv_ask": "0.4559327", "inverse": true }, { - "bid": "0.098", - "ask": "0.0995", - "open_interest": "210.6", - "volume": "59325.12", + "bid": "0.0915", + "ask": "0.093", + "open_interest": "366.4", + "volume": "29717.34", "strike": "72000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4482641", - "iv_ask": "0.45335", + "iv_bid": "0.445472", + "iv_ask": "0.4508264", "inverse": true }, { - "bid": "0.109", - "ask": "0.1115", - "open_interest": "55.9", - "volume": "0", + "bid": "0.102", + "ask": "0.104", + "open_interest": "57.3", + "volume": "796.91", "strike": "74000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4440836", - "iv_ask": "0.4523094", + "iv_bid": "0.4398255", + "iv_ask": "0.4467439", "inverse": true }, { - "bid": "0.115", - "ask": "0.117", - "open_interest": "729.7", - "volume": "4512.24", + "bid": "0.108", + "ask": "0.1095", + "open_interest": "794.6", + "volume": "37634.74", "strike": "75000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4427834", - "iv_ask": "0.4492804", + "iv_bid": "0.4386538", + "iv_ask": "0.4437741", "inverse": true }, { - "bid": "0.121", - "ask": "0.1235", - "open_interest": "127.2", + "bid": "0.1135", + "ask": "0.1155", + "open_interest": "132.3", "volume": "0", "strike": "76000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4409156", - "iv_ask": "0.4489453", + "iv_bid": "0.4351538", + "iv_ask": "0.4419011", "inverse": true }, { - "bid": "0.134", - "ask": "0.1365", - "open_interest": "216", - "volume": "2082.33", + "bid": "0.126", + "ask": "0.128", + "open_interest": "248.2", + "volume": "0", "strike": "78000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4387076", - "iv_ask": "0.4465904", + "iv_bid": "0.4313785", + "iv_ask": "0.4380005", "inverse": true }, { - "bid": "0.1365", - "ask": "0.1385", - "open_interest": "3942.4", - "volume": "325204.46", + "bid": "0.127", + "ask": "0.1275", + "open_interest": "4855.9", + "volume": "512397.91", "strike": "80000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4375664", - "iv_ask": "0.443791", + "iv_bid": "0.4333078", + "iv_ask": "0.4349429", "inverse": true }, { - "bid": "0.125", - "ask": "0.1275", - "open_interest": "77.3", + "bid": "0.115", + "ask": "0.116", + "open_interest": "185.2", "volume": "0", "strike": "82000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4335411", - "iv_ask": "0.4412604", + "iv_bid": "0.4275045", + "iv_ask": "0.4307526", "inverse": true }, { - "bid": "0.115", - "ask": "0.117", - "open_interest": "527.5", - "volume": "19189.3", + "bid": "0.1045", + "ask": "0.1055", + "open_interest": "591.4", + "volume": "807.59", "strike": "84000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.432112", - "iv_ask": "0.4382695", + "iv_bid": "0.4242835", + "iv_ask": "0.4275287", "inverse": true }, { - "bid": "0.1105", - "ask": "0.112", - "open_interest": "1380.3", - "volume": "17323.71", + "bid": "0.1", + "ask": "0.1005", + "open_interest": "1539.7", + "volume": "80021.74", "strike": "85000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4321908", - "iv_ask": "0.4368105", + "iv_bid": "0.424262", + "iv_ask": "0.4258873", "inverse": true }, { - "bid": "0.105", - "ask": "0.1075", - "open_interest": "112.1", - "volume": "16586.39", + "bid": "0.0945", + "ask": "0.096", + "open_interest": "120.7", + "volume": "1477.19", "strike": "86000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4287009", - "iv_ask": "0.4364123", + "iv_bid": "0.4204317", + "iv_ask": "0.4253225", "inverse": true }, { - "bid": "0.0965", - "ask": "0.0975", - "open_interest": "1586.6", - "volume": "34774.46", + "bid": "0.086", + "ask": "0.0865", + "open_interest": "1712.6", + "volume": "68319.83", "strike": "88000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.428003", - "iv_ask": "0.4311079", + "iv_bid": "0.4192873", + "iv_ask": "0.4209341", "inverse": true }, { - "bid": "0.0885", - "ask": "0.09", - "open_interest": "2240.3", - "volume": "27861.5", + "bid": "0.0775", + "ask": "0.079", + "open_interest": "2227.3", + "volume": "2463.37", "strike": "90000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4270223", - "iv_ask": "0.4317289", + "iv_bid": "0.4160191", + "iv_ask": "0.4210341", "inverse": true }, { - "bid": "0.0805", - "ask": "0.0825", - "open_interest": "138.1", - "volume": "13318.92", + "bid": "0.07", + "ask": "0.0715", + "open_interest": "217.5", + "volume": "0", "strike": "92000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.424245", - "iv_ask": "0.430615", + "iv_bid": "0.414019", + "iv_ask": "0.4191349", "inverse": true }, { - "bid": "0.0735", - "ask": "0.0755", - "open_interest": "52.7", + "bid": "0.0635", + "ask": "0.0645", + "open_interest": "65.3", "volume": "0", "strike": "94000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4229086", - "iv_ask": "0.4293985", + "iv_bid": "0.4134683", + "iv_ask": "0.4169626", "inverse": true }, { - "bid": "0.071", - "ask": "0.072", - "open_interest": "878.5", - "volume": "40877.5", + "bid": "0.06", + "ask": "0.0615", + "open_interest": "1168.3", + "volume": "39554.5", "strike": "95000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4248858", - "iv_ask": "0.4281638", + "iv_bid": "0.4115896", + "iv_ask": "0.416905", "inverse": true }, { - "bid": "0.067", - "ask": "0.069", - "open_interest": "49", + "bid": "0.057", + "ask": "0.0585", + "open_interest": "65.6", "volume": "0", "strike": "96000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4215262", - "iv_ask": "0.4281631", + "iv_bid": "0.4109999", + "iv_ask": "0.4163942", "inverse": true }, { - "bid": "0.061", - "ask": "0.063", - "open_interest": "37.6", + "bid": "0.0515", + "ask": "0.0535", + "open_interest": "70", "volume": "0", "strike": "98000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4201873", - "iv_ask": "0.4269985", + "iv_bid": "0.4102709", + "iv_ask": "0.4176904", "inverse": true }, { - "bid": "0.056", - "ask": "0.0575", - "open_interest": "2382", - "volume": "1773.15", + "bid": "0.0465", + "ask": "0.048", + "open_interest": "2356.2", + "volume": "37380.71", "strike": "100000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4207486", - "iv_ask": "0.4260033", + "iv_bid": "0.4096577", + "iv_ask": "0.4154293", "inverse": true }, { - "bid": "0.051", - "ask": "0.0525", - "open_interest": "39.9", - "volume": "78938.4", + "bid": "0.042", + "ask": "0.0435", + "open_interest": "57.7", + "volume": "0", "strike": "102000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4198637", - "iv_ask": "0.4252884", + "iv_bid": "0.4093035", + "iv_ask": "0.4153022", "inverse": true }, { - "bid": "0.0465", - "ask": "0.048", - "open_interest": "35.2", - "volume": "107766.3", + "bid": "0.038", + "ask": "0.0395", + "open_interest": "64", + "volume": "0", "strike": "104000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4193658", - "iv_ask": "0.4249792", + "iv_bid": "0.4093728", + "iv_ask": "0.4156223", "inverse": true }, { - "bid": "0.0445", - "ask": "0.0455", - "open_interest": "755.1", - "volume": "0", + "bid": "0.036", + "ask": "0.0375", + "open_interest": "708", + "volume": "153314.78", "strike": "105000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4195455", - "iv_ask": "0.4233592", + "iv_bid": "0.4088213", + "iv_ask": "0.4152136", "inverse": true }, { - "bid": "0.035", - "ask": "0.0365", - "open_interest": "1905", - "volume": "2288.72", + "bid": "0.0345", + "ask": "0.0355", + "open_interest": "74.7", + "volume": "0", + "strike": "106000", + "maturity": "2026-12-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4100557", + "iv_ask": "0.414412", + "inverse": true + }, + { + "bid": "0.031", + "ask": "0.0325", + "open_interest": "76", + "volume": "0", + "strike": "108000", + "maturity": "2026-12-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4092779", + "iv_ask": "0.4161181", + "inverse": true + }, + { + "bid": "0.0285", + "ask": "0.0295", + "open_interest": "2910.1", + "volume": "59348.43", "strike": "110000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4175218", - "iv_ask": "0.423841", + "iv_bid": "0.4117678", + "iv_ask": "0.4165397", "inverse": true }, { - "bid": "0.028", - "ask": "0.029", - "open_interest": "2688.7", - "volume": "23290.69", + "bid": "0.022", + "ask": "0.0235", + "open_interest": "2501.3", + "volume": "5485.34", "strike": "115000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4185056", - "iv_ask": "0.4232116", + "iv_bid": "0.4114016", + "iv_ask": "0.4195322", "inverse": true }, { - "bid": "0.0225", - "ask": "0.0235", - "open_interest": "6583.5", - "volume": "27714.15", + "bid": "0.018", + "ask": "0.0185", + "open_interest": "7058.2", + "volume": "5733.95", "strike": "120000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.420054", - "iv_ask": "0.425338", + "iv_bid": "0.4177896", + "iv_ask": "0.420869", "inverse": true }, { - "bid": "0.018", - "ask": "0.019", - "open_interest": "501.9", - "volume": "0", + "bid": "0.014", + "ask": "0.015", + "open_interest": "820.3", + "volume": "7301.8", "strike": "125000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4209927", - "iv_ask": "0.4269805", + "iv_bid": "0.4181325", + "iv_ask": "0.4252136", "inverse": true }, { - "bid": "0.014", - "ask": "0.0155", - "open_interest": "1730.3", - "volume": "19458.27", + "bid": "0.011", + "ask": "0.012", + "open_interest": "1508.9", + "volume": "170.4", "strike": "130000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4190828", - "iv_ask": "0.4293767", + "iv_bid": "0.4194931", + "iv_ask": "0.4276967", "inverse": true }, { - "bid": "0.0115", - "ask": "0.013", - "open_interest": "699.1", - "volume": "13730.46", + "bid": "0.009", + "ask": "0.01", + "open_interest": "901.7", + "volume": "0", "strike": "135000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4225318", - "iv_ask": "0.4341579", + "iv_bid": "0.4242077", + "iv_ask": "0.4335599", "inverse": true }, { - "bid": "0.0095", - "ask": "0.0105", - "open_interest": "1103.5", - "volume": "389.72", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1400.6", + "volume": "58.21", "strike": "140000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4260323", - "iv_ask": "0.4348952", + "iv_bid": "0.4298431", + "iv_ask": "0.4404236", "inverse": true }, { - "bid": "0.0065", - "ask": "0.008", - "open_interest": "1508.9", - "volume": "0", + "bid": "0.005", + "ask": "0.006", + "open_interest": "1559.8", + "volume": "38.79", "strike": "150000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4319949", - "iv_ask": "0.4487617", + "iv_bid": "0.4362192", + "iv_ask": "0.4501", "inverse": true }, { - "bid": "0.005", - "ask": "0.006", - "open_interest": "1029.6", - "volume": "567.37", + "bid": "0.0037", + "ask": "0.0042", + "open_interest": "1172.5", + "volume": "2323.3", "strike": "160000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4449599", - "iv_ask": "0.4587394", + "iv_bid": "0.4482195", + "iv_ask": "0.4571405", "inverse": true }, { - "bid": "0.0039", - "ask": "0.0044", - "open_interest": "1979.8", - "volume": "1368.16", + "bid": "0.0031", + "ask": "0.0034", + "open_interest": "2143.3", + "volume": "492.14", "strike": "170000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4566378", - "iv_ask": "0.4651832", + "iv_bid": "0.4662594", + "iv_ask": "0.4725881", "inverse": true }, { - "bid": "0.0028", - "ask": "0.0035", - "open_interest": "831.5", + "bid": "0.0022", + "ask": "0.0027", + "open_interest": "919.2", "volume": "0", "strike": "180000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4610361", - "iv_ask": "0.4759005", + "iv_bid": "0.471281", + "iv_ask": "0.4845143", "inverse": true }, { - "bid": "0.0021", - "ask": "0.0028", - "open_interest": "963.2", - "volume": "35.07", + "bid": "0.0017", + "ask": "0.0023", + "open_interest": "989.2", + "volume": "0", "strike": "190000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4671526", - "iv_ask": "0.4853673", + "iv_bid": "0.4803256", + "iv_ask": "0.499248", "inverse": true }, { - "bid": "0.0016", + "bid": "0.0013", "ask": "0.002", - "open_interest": "922.6", - "volume": "128.87", + "open_interest": "942.4", + "volume": "0", "strike": "200000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4731253", - "iv_ask": "0.4863711", + "iv_bid": "0.4874298", + "iv_ask": "0.5135969", "inverse": true }, { - "bid": "0.0014", - "ask": "0.0018", - "open_interest": "134.2", + "bid": "0.0011", + "ask": "0.0017", + "open_interest": "149.2", "volume": "0", "strike": "210000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4858514", - "iv_ask": "0.5006689", + "iv_bid": "0.4990451", + "iv_ask": "0.524983", "inverse": true }, { - "bid": "0.0014", - "ask": "0.0017", - "open_interest": "222.2", + "bid": "0.0011", + "ask": "0.0015", + "open_interest": "221.8", "volume": "0", "strike": "220000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5049758", - "iv_ask": "0.5166325", + "iv_bid": "0.5188023", + "iv_ask": "0.5374411", "inverse": true }, { - "bid": "0.0009", - "ask": "0.0014", - "open_interest": "186.3", - "volume": "7.78", + "bid": "0.0008", + "ask": "0.0013", + "open_interest": "186.8", + "volume": "0", "strike": "230000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4982523", - "iv_ask": "0.5231024", + "iv_bid": "0.5195513", + "iv_ask": "0.5476157", "inverse": true }, { - "bid": "0.0009", - "ask": "0.0015", - "open_interest": "877.9", + "bid": "0.0007", + "ask": "0.0012", + "open_interest": "879.2", "volume": "0", "strike": "240000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5148978", - "iv_ask": "0.5445898", + "iv_bid": "0.5296618", + "iv_ask": "0.5606509", "inverse": true }, { "bid": "0.0007", - "ask": "0.0012", - "open_interest": "1135.2", - "volume": "255.77", + "ask": "0.0011", + "open_interest": "1186.4", + "volume": "0", "strike": "250000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.5172986", - "iv_ask": "0.5473116", + "iv_bid": "0.5460613", + "iv_ask": "0.5723", "inverse": true }, { - "bid": "79800", - "ask": "79802.5", - "open_interest": "15964110", - "volume": "3373920", + "bid": "79542.5", + "ask": "79560", + "open_interest": "16356610", + "volume": "1525090", "maturity": "2027-03-26T08:00:00Z", "security_type": "forward" }, { - "bid": "0.018", - "ask": "0.0195", - "open_interest": "430.9", - "volume": "22395.24", + "bid": "0.016", + "ask": "0.017", + "open_interest": "740", + "volume": "34382.22", "strike": "40000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.5675747", - "iv_ask": "0.5803427", + "iv_bid": "0.5692864", + "iv_ask": "0.5786", "inverse": true }, { - "bid": "0.0355", - "ask": "0.037", - "open_interest": "1288.2", - "volume": "1942.15", + "bid": "0.0325", + "ask": "0.0335", + "open_interest": "1916.2", + "volume": "1307554.9", "strike": "50000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.517943", - "iv_ask": "0.5259073", + "iv_bid": "0.5190345", + "iv_ask": "0.5246949", "inverse": true }, { - "bid": "0.0485", - "ask": "0.05", - "open_interest": "629.3", - "volume": "376.13", + "bid": "0.045", + "ask": "0.0465", + "open_interest": "1213.6", + "volume": "0", "strike": "55000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.499579", - "iv_ask": "0.506169", + "iv_bid": "0.5004849", + "iv_ask": "0.5074307", "inverse": true }, { - "bid": "0.0515", - "ask": "0.053", - "open_interest": "34.8", + "bid": "0.048", + "ask": "0.0495", + "open_interest": "53.4", "volume": "0", "strike": "56000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4963148", - "iv_ask": "0.5026841", + "iv_bid": "0.4976177", + "iv_ask": "0.504317", "inverse": true }, { - "bid": "0.058", - "ask": "0.06", - "open_interest": "23.9", + "bid": "0.054", + "ask": "0.0555", + "open_interest": "49.6", "volume": "0", "strike": "58000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4904471", - "iv_ask": "0.4984018", + "iv_bid": "0.4903554", + "iv_ask": "0.4966245", "inverse": true }, { - "bid": "0.065", - "ask": "0.067", - "open_interest": "122.7", - "volume": "1491.49", + "bid": "0.0605", + "ask": "0.0625", + "open_interest": "144.5", + "volume": "4714.29", "strike": "60000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4846734", - "iv_ask": "0.4921719", + "iv_bid": "0.4832935", + "iv_ask": "0.4911506", "inverse": true }, { - "bid": "0.0725", - "ask": "0.075", - "open_interest": "60.1", + "bid": "0.068", + "ask": "0.07", + "open_interest": "75.1", "volume": "0", "strike": "62000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4789458", - "iv_ask": "0.4878228", + "iv_bid": "0.4782111", + "iv_ask": "0.4856328", "inverse": true }, { - "bid": "0.081", - "ask": "0.0835", - "open_interest": "22.7", + "bid": "0.076", + "ask": "0.078", + "open_interest": "73", "volume": "0", "strike": "64000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4749216", - "iv_ask": "0.4833696", + "iv_bid": "0.4729972", + "iv_ask": "0.4800463", "inverse": true }, { - "bid": "0.0855", - "ask": "0.088", - "open_interest": "25.4", + "bid": "0.0805", + "ask": "0.0825", + "open_interest": "265.1", "volume": "0", "strike": "65000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4730537", - "iv_ask": "0.4813104", + "iv_bid": "0.4714147", + "iv_ask": "0.4782955", "inverse": true }, { - "bid": "0.09", - "ask": "0.0925", - "open_interest": "29.4", + "bid": "0.0845", + "ask": "0.087", + "open_interest": "57.7", "volume": "0", "strike": "66000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4707328", - "iv_ask": "0.4788136", + "iv_bid": "0.4676429", + "iv_ask": "0.4760536", "inverse": true }, { - "bid": "0.0995", - "ask": "0.102", - "open_interest": "34.1", - "volume": "0", + "bid": "0.094", + "ask": "0.096", + "open_interest": "40.2", + "volume": "743.62", "strike": "68000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4663906", - "iv_ask": "0.4741575", + "iv_bid": "0.4637569", + "iv_ask": "0.4702126", "inverse": true }, { - "bid": "0.11", - "ask": "0.1125", - "open_interest": "72.7", - "volume": "84054.42", + "bid": "0.104", + "ask": "0.106", + "open_interest": "73.6", + "volume": "3276.79", "strike": "70000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4634057", - "iv_ask": "0.4709042", + "iv_bid": "0.4596019", + "iv_ask": "0.4658256", "inverse": true }, { - "bid": "0.121", - "ask": "0.124", - "open_interest": "104.3", + "bid": "0.115", + "ask": "0.117", + "open_interest": "107.1", "volume": "0", "strike": "72000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4601959", - "iv_ask": "0.4689229", + "iv_bid": "0.4567083", + "iv_ask": "0.462737", "inverse": true }, { - "bid": "0.1325", - "ask": "0.1355", + "bid": "0.126", + "ask": "0.1285", "open_interest": "23.1", "volume": "0", "strike": "74000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4567922", - "iv_ask": "0.4652929", + "iv_bid": "0.4520399", + "iv_ask": "0.4593739", "inverse": true }, { - "bid": "0.145", - "ask": "0.148", - "open_interest": "5.2", + "bid": "0.138", + "ask": "0.141", + "open_interest": "7.7", "volume": "0", "strike": "76000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4546068", - "iv_ask": "0.462922", + "iv_bid": "0.4486009", + "iv_ask": "0.4572049", "inverse": true }, { - "bid": "0.1575", - "ask": "0.1595", - "open_interest": "114.5", + "bid": "0.151", + "ask": "0.1535", + "open_interest": "176", "volume": "0", "strike": "78000", "maturity": "2027-03-26T08:00:00Z", "option_type": "put", "security_type": "option", - "iv_bid": "0.4508679", - "iv_ask": "0.4563106", + "iv_bid": "0.4463247", + "iv_ask": "0.4533647", "inverse": true }, { - "bid": "0.1685", - "ask": "0.1715", - "open_interest": "306.6", + "bid": "0.159", + "ask": "0.1615", + "open_interest": "525.8", "volume": "0", "strike": "80000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4483291", - "iv_ask": "0.4563785", + "iv_bid": "0.4415988", + "iv_ask": "0.4485389", "inverse": true }, { - "bid": "0.158", - "ask": "0.1595", - "open_interest": "100.9", - "volume": "1312.3", + "bid": "0.148", + "ask": "0.15", + "open_interest": "149", + "volume": "44978.86", "strike": "82000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4471844", - "iv_ask": "0.4511663", + "iv_bid": "0.4391986", + "iv_ask": "0.4446949", "inverse": true }, { - "bid": "0.1475", - "ask": "0.15", - "open_interest": "27", - "volume": "1190.08", + "bid": "0.1375", + "ask": "0.1395", + "open_interest": "73.2", + "volume": "0", "strike": "84000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4446112", - "iv_ask": "0.4512014", + "iv_bid": "0.4366111", + "iv_ask": "0.4420737", "inverse": true }, { - "bid": "0.1375", - "ask": "0.1405", - "open_interest": "30", + "bid": "0.1275", + "ask": "0.13", + "open_interest": "30.8", "volume": "0", "strike": "86000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4419852", - "iv_ask": "0.4498641", + "iv_bid": "0.4338765", + "iv_ask": "0.4406886", "inverse": true }, { - "bid": "0.1285", - "ask": "0.1315", - "open_interest": "19.6", + "bid": "0.1185", + "ask": "0.1205", + "open_interest": "19.7", "volume": "0", "strike": "88000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4406535", - "iv_ask": "0.4485282", + "iv_bid": "0.432398", + "iv_ask": "0.4378542", "inverse": true }, { - "bid": "0.12", - "ask": "0.123", - "open_interest": "58.4", - "volume": "993.16", + "bid": "0.11", + "ask": "0.112", + "open_interest": "390.9", + "volume": "0", "strike": "90000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4393464", - "iv_ask": "0.4472404", + "iv_bid": "0.4308631", + "iv_ask": "0.4363441", "inverse": true }, { - "bid": "0.1115", - "ask": "0.1145", - "open_interest": "63.3", - "volume": "9113.44", + "bid": "0.102", + "ask": "0.104", + "open_interest": "64", + "volume": "0", "strike": "92000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4367892", - "iv_ask": "0.4447267", + "iv_bid": "0.4293254", + "iv_ask": "0.4348491", "inverse": true }, { - "bid": "0.104", - "ask": "0.107", - "open_interest": "28.2", + "bid": "0.0945", + "ask": "0.0965", + "open_interest": "35.5", "volume": "0", "strike": "94000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4356695", - "iv_ask": "0.4436715", + "iv_bid": "0.4278404", + "iv_ask": "0.4334243", "inverse": true }, { - "bid": "0.1005", - "ask": "0.1035", - "open_interest": "65", - "volume": "16048.48", + "bid": "0.091", + "ask": "0.093", + "open_interest": "69.1", + "volume": "0", "strike": "95000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4353399", - "iv_ask": "0.4433815", + "iv_bid": "0.4273117", + "iv_ask": "0.4329318", "inverse": true }, { - "bid": "0.097", - "ask": "0.1", - "open_interest": "51.4", - "volume": "8945.05", + "bid": "0.0875", + "ask": "0.0895", + "open_interest": "59.4", + "volume": "676.51", "strike": "96000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.434726", - "iv_ask": "0.4428132", + "iv_bid": "0.4264671", + "iv_ask": "0.4321282", "inverse": true }, { - "bid": "0.0905", - "ask": "0.0935", - "open_interest": "19", + "bid": "0.081", + "ask": "0.083", + "open_interest": "18.6", "volume": "0", "strike": "98000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4340182", - "iv_ask": "0.4422103", + "iv_bid": "0.4252696", + "iv_ask": "0.4310243", "inverse": true }, { - "bid": "0.084", - "ask": "0.087", - "open_interest": "128", - "volume": "6961.12", + "bid": "0.075", + "ask": "0.077", + "open_interest": "137.5", + "volume": "5963.63", "strike": "100000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4322207", - "iv_ask": "0.4405429", + "iv_bid": "0.4243178", + "iv_ask": "0.4301823", "inverse": true }, { - "bid": "0.0785", - "ask": "0.0815", - "open_interest": "22.5", - "volume": "6478.32", + "bid": "0.0695", + "ask": "0.0715", + "open_interest": "34.4", + "volume": "0", "strike": "102000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4321595", - "iv_ask": "0.4406239", + "iv_bid": "0.4236896", + "iv_ask": "0.4296788", "inverse": true }, { - "bid": "0.073", - "ask": "0.076", - "open_interest": "1", + "bid": "0.0645", + "ask": "0.0665", + "open_interest": "9.6", "volume": "0", "strike": "104000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.431101", - "iv_ask": "0.4397331", + "iv_bid": "0.4234716", + "iv_ask": "0.4295994", "inverse": true }, { - "bid": "0.0705", - "ask": "0.0735", - "open_interest": "51.9", + "bid": "0.062", + "ask": "0.064", + "open_interest": "97.4", "volume": "0", "strike": "105000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4309251", - "iv_ask": "0.4396462", + "iv_bid": "0.4229626", + "iv_ask": "0.4291682", "inverse": true }, { - "bid": "0.068", - "ask": "0.071", - "open_interest": "0", + "bid": "0.06", + "ask": "0.0615", + "open_interest": "15.4", "volume": "0", "strike": "106000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.430512", - "iv_ask": "0.4393289", + "iv_bid": "0.4237602", + "iv_ask": "0.4284732", "inverse": true }, { - "bid": "0.063", - "ask": "0.066", - "open_interest": "5", - "volume": "27513.66", + "bid": "0.0555", + "ask": "0.057", + "open_interest": "32.9", + "volume": "0", "strike": "108000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4289668", - "iv_ask": "0.4379983", + "iv_bid": "0.4230437", + "iv_ask": "0.4278879", "inverse": true }, { - "bid": "0.059", - "ask": "0.062", - "open_interest": "35.2", + "bid": "0.0515", + "ask": "0.053", + "open_interest": "90.6", "volume": "0", "strike": "110000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4295475", - "iv_ask": "0.4387921", + "iv_bid": "0.422967", + "iv_ask": "0.427951", "inverse": true }, { - "bid": "0.049", - "ask": "0.052", - "open_interest": "393.3", + "bid": "0.0475", + "ask": "0.0495", + "open_interest": "18.2", "volume": "0", - "strike": "115000", + "strike": "112000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4273018", - "iv_ask": "0.4372142", + "iv_bid": "0.4219323", + "iv_ask": "0.4287816", "inverse": true }, { - "bid": "0.0415", + "bid": "0.0425", "ask": "0.044", - "open_interest": "80.5", + "open_interest": "527.2", "volume": "0", - "strike": "120000", + "strike": "115000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4284489", - "iv_ask": "0.4373427", + "iv_bid": "0.4221283", + "iv_ask": "0.4275233", "inverse": true }, { - "bid": "0.035", - "ask": "0.0375", - "open_interest": "22.6", + "bid": "0.0355", + "ask": "0.037", + "open_interest": "185.2", "volume": "0", - "strike": "125000", + "strike": "120000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.428861", - "iv_ask": "0.4384941", + "iv_bid": "0.423243", + "iv_ask": "0.4291091", "inverse": true }, { "bid": "0.0295", - "ask": "0.0315", - "open_interest": "592.1", - "volume": "253.38", + "ask": "0.031", + "open_interest": "48.1", + "volume": "14508.61", + "strike": "125000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4235706", + "iv_ask": "0.4300026", + "inverse": true + }, + { + "bid": "0.025", + "ask": "0.026", + "open_interest": "638.4", + "volume": "3190.44", "strike": "130000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4291369", - "iv_ask": "0.4375563", + "iv_bid": "0.426195", + "iv_ask": "0.430905", "inverse": true }, { - "bid": "0.0215", - "ask": "0.0235", - "open_interest": "80.5", - "volume": "2002.98", + "bid": "0.018", + "ask": "0.019", + "open_interest": "110.7", + "volume": "1481.29", "strike": "140000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4323137", - "iv_ask": "0.4423314", + "iv_bid": "0.4306551", + "iv_ask": "0.4363593", "inverse": true }, { - "bid": "0.0155", - "ask": "0.017", - "open_interest": "83", - "volume": "271.41", + "bid": "0.013", + "ask": "0.0145", + "open_interest": "1260.7", + "volume": "551576.81", "strike": "150000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4335328", - "iv_ask": "0.4426856", + "iv_bid": "0.4342409", + "iv_ask": "0.4446091", "inverse": true }, { - "bid": "0.012", - "ask": "0.013", - "open_interest": "604", - "volume": "467.16", + "bid": "0.01", + "ask": "0.011", + "open_interest": "814.3", + "volume": "155.27", "strike": "160000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4405418", - "iv_ask": "0.4478131", + "iv_bid": "0.4422051", + "iv_ask": "0.4505239", "inverse": true }, { - "bid": "0.009", - "ask": "0.0105", - "open_interest": "63.3", - "volume": "82.66", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "145.5", + "volume": "5960.34", "strike": "170000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4434883", - "iv_ask": "0.4564694", + "iv_bid": "0.4465725", + "iv_ask": "0.4566574", "inverse": true }, { - "bid": "0.007", - "ask": "0.0085", - "open_interest": "198.7", - "volume": "7845.12", + "bid": "0.006", + "ask": "0.007", + "open_interest": "299", + "volume": "251.79", "strike": "180000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.4484281", - "iv_ask": "0.4637703", + "iv_bid": "0.4547126", + "iv_ask": "0.4665266", "inverse": true }, { - "bid": "0.0055", - "ask": "0.0075", - "open_interest": "64.9", - "volume": "6366.53", + "bid": "0.005", + "ask": "0.006", + "open_interest": "161.9", + "volume": "0", "strike": "190000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.453116", - "iv_ask": "0.476623", + "iv_bid": "0.4644076", + "iv_ask": "0.4779217", "inverse": true }, { - "bid": "0.0048", - "ask": "0.0055", - "open_interest": "57.8", - "volume": "218.23", + "bid": "0.0041", + "ask": "0.0049", + "open_interest": "259.5", + "volume": "300.11", "strike": "200000", "maturity": "2027-03-26T08:00:00Z", "option_type": "call", "security_type": "option", - "iv_bid": "0.464132", - "iv_ask": "0.4738818", + "iv_bid": "0.4716403", + "iv_ask": "0.4842735", "inverse": true } ] diff --git a/docs/examples/fixtures/volsurface_eth.json b/docs/examples/fixtures/volsurface_eth.json index 1e23240..e67b375 100644 --- a/docs/examples/fixtures/volsurface_eth.json +++ b/docs/examples/fixtures/volsurface_eth.json @@ -1,4359 +1,3916 @@ { "asset": "eth", - "ref_date": "2026-01-31T12:25:19.499850Z", + "asset_curve": { + "ref_date": "2026-05-21T10:08:42.397057Z", + "curve_type": "no_discount" + }, + "quote_curve": { + "ref_date": "2026-05-21T10:08:42.397057Z", + "curve_type": "nelson_siegel", + "beta1": "0.0430852145", + "beta2": "-0.0189909065", + "beta3": "-0.0679762918", + "lambda_": "9.9928466198" + }, "inputs": [ { - "bid": "2635.25", - "ask": "2635.30", - "open_interest": "397478495", - "volume": "233177071.0", + "bid": "2125.7", + "ask": "2125.75", + "open_interest": "311539747", + "volume": "73039627", "security_type": "spot" }, { - "bid": "2637.00", - "ask": "2637.75", - "open_interest": "8615529", - "volume": "7197233.0", - "maturity": "2026-02-06T08:00:00Z", + "bid": "2125.5", + "ask": "2125.75", + "open_interest": "5430815", + "volume": "1488159", + "maturity": "2026-05-22T08:00:00Z", "security_type": "forward" }, { - "bid": "0.0970", - "ask": "0.1000", - "open_interest": "65.0", - "volume": "17808.85", - "strike": "2400.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0080", - "ask": "0.0085", - "open_interest": "8334.0", - "volume": "39295.43", - "strike": "2400.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "7621", + "volume": "2631.85", + "strike": "2000", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5898448", + "iv_ask": "0.6597484", + "inverse": true }, { - "bid": "0.0100", - "ask": "0.0110", - "open_interest": "790.0", - "volume": "20308.16", - "strike": "2450.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0004", + "ask": "0.0007", + "open_interest": "430", + "volume": "363.26", + "strike": "2025", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5466591", + "iv_ask": "0.6101786", + "inverse": true }, { - "bid": "0.0135", - "ask": "0.0145", - "open_interest": "336.0", - "volume": "12741.23", - "strike": "2500.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0008", + "ask": "0.0011", + "open_interest": "1062", + "volume": "2065", + "strike": "2050", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5005718", + "iv_ask": "0.5412402", + "inverse": true }, { - "bid": "0.0500", - "ask": "0.0535", - "open_interest": "1.0", - "volume": "172.2", - "strike": "2550.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0180", - "ask": "0.0190", - "open_interest": "993.0", - "volume": "142363.24", - "strike": "2550.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0016", + "ask": "0.002", + "open_interest": "445", + "volume": "864.04", + "strike": "2075", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4505546", + "iv_ask": "0.4853175", + "inverse": true }, { - "bid": "0.0385", - "ask": "0.0395", - "open_interest": "72.0", - "volume": "5403.44", - "strike": "2600.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0240", - "ask": "0.0255", - "open_interest": "5175.0", - "volume": "97966.61", - "strike": "2600.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0036", + "ask": "0.0041", + "open_interest": "8411", + "volume": "7425.66", + "strike": "2100", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4191678", + "iv_ask": "0.4487797", + "inverse": true }, { - "bid": "0.0275", - "ask": "0.0290", - "open_interest": "237.0", - "volume": "23482.45", - "strike": "2650.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0325", - "ask": "0.0335", - "open_interest": "788.0", - "volume": "61913.3", - "strike": "2650.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "7358", + "volume": "103676.78", + "strike": "2125", + "maturity": "2026-05-22T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3864152", + "iv_ask": "0.4366218", + "inverse": true }, { - "bid": "0.0195", - "ask": "0.0200", - "open_interest": "511.0", - "volume": "33138.18", - "strike": "2700.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0033", + "ask": "0.0039", + "open_interest": "3840", + "volume": "36964.72", + "strike": "2150", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0415", - "ask": "0.0450", - "open_interest": "6388.0", - "volume": "57465.12", - "strike": "2700.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3832703", + "iv_ask": "0.4183887", + "inverse": true }, { - "bid": "0.0130", - "ask": "0.0140", - "open_interest": "776.0", - "volume": "32574.29", - "strike": "2750.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0014", + "ask": "0.0017", + "open_interest": "2707", + "volume": "2814.05", + "strike": "2175", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0540", - "ask": "0.0575", - "open_interest": "2691.0", - "volume": "93993.49", - "strike": "2750.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4121547", + "iv_ask": "0.4387093", + "inverse": true }, { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "2046.0", - "volume": "39680.28", - "strike": "2800.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0006", + "ask": "0.0009", + "open_interest": "3623", + "volume": "3658.14", + "strike": "2200", + "maturity": "2026-05-22T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.444837", + "iv_ask": "0.4885229", + "inverse": true }, { - "bid": "0.0680", - "ask": "0.0715", - "open_interest": "4290.0", - "volume": "26795.96", - "strike": "2800.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "2714", + "volume": "2443.65", + "strike": "2250", + "maturity": "2026-05-22T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5497289", + "iv_ask": "0.6150819", + "inverse": true }, { - "bid": "0.0055", - "ask": "0.0060", - "open_interest": "1699.0", - "volume": "6380.46", - "strike": "2850.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0001", + "ask": "0.0002", + "open_interest": "6578", + "volume": "432.18", + "strike": "2300", + "maturity": "2026-05-22T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.6628309", + "iv_ask": "0.7260448", + "inverse": true + }, + { + "bid": "2116", + "ask": "2132.5", + "open_interest": "17067", + "volume": "10710", + "maturity": "2026-05-23T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.0830", - "ask": "0.0890", - "open_interest": "2040.0", - "volume": "58465.05", - "strike": "2850.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0002", + "ask": "0.0004", + "open_interest": "695", + "volume": "3.41", + "strike": "1900", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6904371", + "iv_ask": "0.7623777", + "inverse": true }, { - "bid": "0.0035", - "ask": "0.0040", - "open_interest": "1675.0", - "volume": "4139.29", - "strike": "2900.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "103", + "volume": "90.34", + "strike": "1950", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6096839", + "iv_ask": "0.6529888", + "inverse": true }, { - "bid": "0.1000", - "ask": "0.1060", - "open_interest": "3555.0", - "volume": "7128.68", - "strike": "2900.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0006", + "ask": "0.0008", + "open_interest": "294", + "volume": "1187.98", + "strike": "1975", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5723022", + "iv_ask": "0.6044527", + "inverse": true }, { - "bid": "0.0028", - "ask": "0.0033", - "open_interest": "1218.0", - "volume": "9791.21", - "strike": "2925.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.001", + "ask": "0.0012", + "open_interest": "1740", + "volume": "48.85", + "strike": "2000", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5449622", + "iv_ask": "0.5675813", + "inverse": true }, { - "bid": "0.1090", - "ask": "0.1145", - "open_interest": "1317.0", - "volume": "9570.87", - "strike": "2925.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0015", + "ask": "0.0017", + "open_interest": "274", + "volume": "99.64", + "strike": "2025", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5036662", + "iv_ask": "0.5204981", + "inverse": true }, { "bid": "0.0023", - "ask": "0.0026", - "open_interest": "1937.0", - "volume": "1391.37", - "strike": "2950.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1180", - "ask": "0.1235", - "open_interest": "1125.0", - "volume": "1996.38", - "strike": "2950.0000", - "maturity": "2026-02-06T08:00:00Z", + "ask": "0.0027", + "open_interest": "103", + "volume": "118.69", + "strike": "2050", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0015", - "ask": "0.0019", - "open_interest": "4544.0", - "volume": "9774.06", - "strike": "3000.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4619188", + "iv_ask": "0.4867005", + "inverse": true }, { - "bid": "0.1360", - "ask": "0.1415", - "open_interest": "13493.0", - "volume": "8342.81", - "strike": "3000.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0039", + "ask": "0.0044", + "open_interest": "120", + "volume": "165.75", + "strike": "2075", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4336451", + "iv_ask": "0.4569453", + "inverse": true }, { - "bid": "0.0010", - "ask": "0.0013", - "open_interest": "2036.0", - "volume": "1212.68", - "strike": "3050.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0065", + "ask": "0.0075", + "open_interest": "220", + "volume": "820.52", + "strike": "2100", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4045199", + "iv_ask": "0.4423182", + "inverse": true }, { - "bid": "0.1545", - "ask": "0.1600", - "open_interest": "559.0", - "volume": "629.96", - "strike": "3050.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.011", + "ask": "0.012", + "open_interest": "361", + "volume": "10714.51", + "strike": "2125", + "maturity": "2026-05-23T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3893008", + "iv_ask": "0.4239628", + "inverse": true }, { - "bid": "0.0007", - "ask": "0.0010", - "open_interest": "3019.0", - "volume": "8077.18", - "strike": "3100.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.006", + "ask": "0.007", + "open_interest": "673", + "volume": "9786.5", + "strike": "2150", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3690337", + "iv_ask": "0.406368", + "inverse": true }, { - "bid": "0.1730", - "ask": "0.1785", - "open_interest": "1698.0", - "volume": "0.0", - "strike": "3100.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0033", + "ask": "0.0037", + "open_interest": "251", + "volume": "2262.53", + "strike": "2175", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3859476", + "iv_ask": "0.4047696", + "inverse": true }, { - "bid": "0.0006", - "ask": "0.0009", - "open_interest": "3461.0", - "volume": "1003.42", - "strike": "3150.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0016", + "ask": "0.002", + "open_interest": "246", + "volume": "449.82", + "strike": "2200", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3921861", + "iv_ask": "0.4190799", + "inverse": true }, { - "bid": "0.1920", - "ask": "0.1975", - "open_interest": "150.0", - "volume": "0.0", - "strike": "3150.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0009", + "ask": "0.0012", + "open_interest": "1255", + "volume": "3275.3", + "strike": "2225", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4183812", + "iv_ask": "0.4475877", + "inverse": true }, { - "bid": "0.0004", + "bid": "0.0006", "ask": "0.0007", - "open_interest": "2895.0", - "volume": "29.82", - "strike": "3200.0000", - "maturity": "2026-02-06T08:00:00Z", + "open_interest": "555", + "volume": "74.51", + "strike": "2250", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2110", - "ask": "0.2165", - "open_interest": "853.0", - "volume": "49751.34", - "strike": "3200.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4569462", + "iv_ask": "0.4709649", + "inverse": true }, { "bid": "0.0003", - "ask": "0.0006", - "open_interest": "1694.0", - "volume": "16.95", - "strike": "3250.0000", - "maturity": "2026-02-06T08:00:00Z", + "ask": "0.0005", + "open_interest": "203", + "volume": "232.32", + "strike": "2275", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2295", - "ask": "0.2350", - "open_interest": "246.0", - "volume": "0.0", - "strike": "3250.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4687542", + "iv_ask": "0.5104235", + "inverse": true }, { "bid": "0.0002", - "ask": "0.0005", - "open_interest": "1916.0", - "volume": "15.8", - "strike": "3300.0000", - "maturity": "2026-02-06T08:00:00Z", + "ask": "0.0004", + "open_interest": "24", + "volume": "29.8", + "strike": "2300", + "maturity": "2026-05-23T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5008554", + "iv_ask": "0.5560522", + "inverse": true }, { - "bid": "0.2485", - "ask": "0.2540", - "open_interest": "306.0", - "volume": "0.0", - "strike": "3300.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "3", + "volume": "1.28", + "strike": "2350", + "maturity": "2026-05-23T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5648167", + "iv_ask": "0.6518551", + "inverse": true + }, + { + "bid": "2116", + "ask": "2133.5", + "open_interest": "0", + "volume": "0", + "maturity": "2026-05-24T08:00:00Z", + "security_type": "forward" }, { "bid": "0.0001", - "ask": "0.0004", - "open_interest": "1848.0", - "volume": "7.01", - "strike": "3350.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "ask": "0.0003", + "open_interest": "235", + "volume": "214.04", + "strike": "1800", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7288311", + "iv_ask": "0.8331671", + "inverse": true }, { - "bid": "0.2675", - "ask": "0.2730", - "open_interest": "42.0", - "volume": "0.0", - "strike": "3350.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "612", + "volume": "21.36", + "strike": "1900", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6179026", + "iv_ask": "0.6593642", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0004", - "open_interest": "666.0", - "volume": "103.36", - "strike": "3400.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0007", + "ask": "0.001", + "open_interest": "1501", + "volume": "2014.98", + "strike": "1950", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5442104", + "iv_ask": "0.5827092", + "inverse": true }, { - "bid": "0.2860", - "ask": "0.2920", - "open_interest": "103.0", - "volume": "0.0", - "strike": "3400.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0011", + "ask": "0.0012", + "open_interest": "41", + "volume": "156.84", + "strike": "1975", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5229804", + "iv_ask": "0.5328746", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "1528.0", - "volume": "18.54", - "strike": "3450.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0015", + "ask": "0.0017", + "open_interest": "232", + "volume": "792.17", + "strike": "2000", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4849481", + "iv_ask": "0.5001637", + "inverse": true }, { - "bid": "0.3050", - "ask": "0.3110", - "open_interest": "65.0", - "volume": "0.0", - "strike": "3450.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0021", + "ask": "0.0025", + "open_interest": "2", + "volume": "6.43", + "strike": "2025", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4473162", + "iv_ask": "0.4706452", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "1005.0", - "volume": "13.37", - "strike": "3500.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0033", + "ask": "0.0037", + "open_interest": "75", + "volume": "52.65", + "strike": "2050", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4229207", + "iv_ask": "0.4407995", + "inverse": true }, { - "bid": "0.3240", - "ask": "0.3300", - "open_interest": "6.0", - "volume": "0.0", - "strike": "3500.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.005", + "ask": "0.006", + "open_interest": "62", + "volume": "1021.13", + "strike": "2075", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3926257", + "iv_ask": "0.4280636", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "741.0", - "volume": "0.27", - "strike": "3550.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.008", + "ask": "0.009", + "open_interest": "1050", + "volume": "22757.79", + "strike": "2100", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.3740384", + "iv_ask": "0.4041321", + "inverse": true }, { - "bid": "0.3430", - "ask": "0.3490", - "open_interest": "3.0", - "volume": "0.0", - "strike": "3550.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.0125", + "ask": "0.014", + "open_interest": "751", + "volume": "18830.38", + "strike": "2125", + "maturity": "2026-05-24T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3581984", + "iv_ask": "0.4003254", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "863.0", - "volume": "9.02", - "strike": "3600.0000", - "maturity": "2026-02-06T08:00:00Z", + "bid": "0.008", + "ask": "0.0085", + "open_interest": "373", + "volume": "10475.49", + "strike": "2150", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3585822", + "iv_ask": "0.3733821", + "inverse": true }, { - "bid": "0.3620", - "ask": "0.3680", - "open_interest": "18.0", - "volume": "1776.78", - "strike": "3600.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0046", + "ask": "0.005", + "open_interest": "268", + "volume": "4417.63", + "strike": "2175", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3604301", + "iv_ask": "0.3745463", + "inverse": true }, { - "bid": "0.3995", - "ask": "0.4060", - "open_interest": "10.0", - "volume": "0.0", - "strike": "3700.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0027", + "ask": "0.003", + "open_interest": "886", + "volume": "7582.74", + "strike": "2200", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3736216", + "iv_ask": "0.387351", + "inverse": true }, { - "bid": "0.5130", - "ask": "0.5205", - "open_interest": "5.0", - "volume": "6236.57", - "strike": "4000.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0015", + "ask": "0.0018", + "open_interest": "203", + "volume": "14.35", + "strike": "2225", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.383282", + "iv_ask": "0.4023574", + "inverse": true }, { - "bid": "0.5885", - "ask": "0.5965", - "open_interest": "142.0", - "volume": "4490.75", - "strike": "4200.0000", - "maturity": "2026-02-06T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0009", + "ask": "0.0011", + "open_interest": "14", + "volume": "14.97", + "strike": "2250", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4016103", + "iv_ask": "0.4196097", + "inverse": true }, { - "bid": "2638.50", - "ask": "2639.00", - "open_interest": "1793521", - "volume": "3591089.0", - "maturity": "2026-02-13T08:00:00Z", - "security_type": "forward" + "bid": "0.0005", + "ask": "0.0008", + "open_interest": "266", + "volume": "277.33", + "strike": "2275", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4133124", + "iv_ask": "0.4512002", + "inverse": true }, { - "bid": "0.1720", - "ask": "0.1750", - "open_interest": "36.0", - "volume": "0.0", - "strike": "2200.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0003", + "ask": "0.0004", + "open_interest": "114", + "volume": "235.88", + "strike": "2300", + "maturity": "2026-05-24T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4303763", + "iv_ask": "0.4502918", + "inverse": true }, { - "bid": "0.0060", - "ask": "0.0070", - "open_interest": "2557.0", - "volume": "7628.56", - "strike": "2200.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "1", + "volume": "0.64", + "strike": "2350", + "maturity": "2026-05-24T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4574285", + "iv_ask": "0.5279253", + "inverse": true + }, + { + "bid": "2117", + "ask": "2133.5", + "open_interest": "0", + "volume": "0", + "maturity": "2026-05-25T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.1045", - "ask": "0.1085", - "open_interest": "26.0", - "volume": "1311.26", - "strike": "2400.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "90", + "volume": "86.55", + "strike": "1800", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7189681", + "iv_ask": "0.7728417", + "inverse": true }, { - "bid": "0.0150", - "ask": "0.0160", - "open_interest": "2531.0", - "volume": "9805.64", - "strike": "2400.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0007", + "ask": "0.0008", + "open_interest": "0", + "volume": "0", + "strike": "1900", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5841816", + "iv_ask": "0.598068", + "inverse": true }, { - "bid": "0.0190", - "ask": "0.0200", - "open_interest": "137.0", - "volume": "6730.16", - "strike": "2450.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0012", + "ask": "0.0014", + "open_interest": "25", + "volume": "48.03", + "strike": "1950", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5220594", + "iv_ask": "0.5395355", + "inverse": true }, { - "bid": "0.0755", - "ask": "0.0790", - "open_interest": "25.0", - "volume": "5100.76", - "strike": "2500.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0016", + "ask": "0.0019", + "open_interest": "1", + "volume": "2.99", + "strike": "1975", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4908857", + "iv_ask": "0.5116076", + "inverse": true }, { - "bid": "0.0240", - "ask": "0.0250", - "open_interest": "735.0", - "volume": "16615.35", - "strike": "2500.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0022", + "ask": "0.0026", + "open_interest": "0", + "volume": "0", + "strike": "2000", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.461736", + "iv_ask": "0.4835807", + "inverse": true }, { - "bid": "0.0300", - "ask": "0.0310", - "open_interest": "10.0", - "volume": "958.44", - "strike": "2550.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0032", + "ask": "0.0036", + "open_interest": "0", + "volume": "0", + "strike": "2025", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4386838", + "iv_ask": "0.4559745", + "inverse": true }, { - "bid": "0.0520", - "ask": "0.0535", - "open_interest": "62.0", - "volume": "6790.02", - "strike": "2600.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0047", + "ask": "0.0055", + "open_interest": "5", + "volume": "41.65", + "strike": "2050", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4170839", + "iv_ask": "0.4446746", + "inverse": true }, { - "bid": "0.0370", - "ask": "0.0380", - "open_interest": "1803.0", - "volume": "54753.08", - "strike": "2600.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.007", + "ask": "0.008", + "open_interest": "2", + "volume": "28.7", + "strike": "2075", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3992538", + "iv_ask": "0.4280909", + "inverse": true }, { - "bid": "0.0420", - "ask": "0.0430", - "open_interest": "94.0", - "volume": "15978.18", - "strike": "2650.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0105", + "ask": "0.0115", + "open_interest": "1", + "volume": "18.15", + "strike": "2100", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.3876481", + "iv_ask": "0.4131372", + "inverse": true }, { - "bid": "0.0455", - "ask": "0.0470", - "open_interest": "399.0", - "volume": "32858.32", - "strike": "2650.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.015", + "ask": "0.0165", + "open_interest": "57", + "volume": "1876.57", + "strike": "2125", + "maturity": "2026-05-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.37017", + "iv_ask": "0.4065161", + "inverse": true }, { - "bid": "0.0330", - "ask": "0.0340", - "open_interest": "202.0", - "volume": "17763.67", - "strike": "2700.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0105", + "ask": "0.0115", + "open_interest": "45", + "volume": "1172.42", + "strike": "2150", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3722401", + "iv_ask": "0.3972876", + "inverse": true }, { - "bid": "0.0560", - "ask": "0.0570", - "open_interest": "1627.0", - "volume": "69889.92", - "strike": "2700.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0065", + "ask": "0.0075", + "open_interest": "110", + "volume": "1780.44", + "strike": "2175", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.366776", + "iv_ask": "0.3950436", + "inverse": true }, { - "bid": "0.0260", - "ask": "0.0270", - "open_interest": "364.0", - "volume": "38301.64", - "strike": "2750.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0042", + "ask": "0.0046", + "open_interest": "63", + "volume": "655.13", + "strike": "2200", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.3778136", + "iv_ask": "0.3915648", + "inverse": true }, { - "bid": "0.0660", - "ask": "0.0705", - "open_interest": "660.0", - "volume": "61385.41", - "strike": "2750.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0027", + "ask": "0.0029", + "open_interest": "0", + "volume": "0", + "strike": "2225", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.3902128", + "iv_ask": "0.3990012", + "inverse": true }, { - "bid": "0.0200", - "ask": "0.0210", - "open_interest": "469.0", - "volume": "20120.55", - "strike": "2800.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0017", + "ask": "0.002", + "open_interest": "0", + "volume": "0", + "strike": "2250", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4012579", + "iv_ask": "0.4184585", + "inverse": true }, { - "bid": "0.0790", - "ask": "0.0835", - "open_interest": "563.0", - "volume": "43479.87", - "strike": "2800.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0012", + "ask": "0.0014", + "open_interest": "5", + "volume": "12.39", + "strike": "2275", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4232369", + "iv_ask": "0.4380503", + "inverse": true }, { - "bid": "0.0150", - "ask": "0.0160", - "open_interest": "843.0", - "volume": "30665.25", - "strike": "2850.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0008", + "ask": "0.001", + "open_interest": "0", + "volume": "0", + "strike": "2300", + "maturity": "2026-05-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4385199", + "iv_ask": "0.4580562", + "inverse": true }, { - "bid": "0.0930", - "ask": "0.0975", - "open_interest": "597.0", - "volume": "40759.3", - "strike": "2850.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0005", + "ask": "0.0006", + "open_interest": "0", + "volume": "0", + "strike": "2350", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4922109", + "iv_ask": "0.5071885", + "inverse": true }, { - "bid": "0.0115", - "ask": "0.0125", - "open_interest": "1061.0", - "volume": "33224.32", - "strike": "2900.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0003", + "ask": "0.0004", + "open_interest": "30", + "volume": "27.78", + "strike": "2400", + "maturity": "2026-05-25T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5355226", + "iv_ask": "0.5580377", + "inverse": true + }, + { + "bid": "2126.5", + "ask": "2127", + "open_interest": "28999132", + "volume": "3685069", + "maturity": "2026-05-29T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.1070", - "ask": "0.1130", - "open_interest": "1412.0", - "volume": "77553.06", - "strike": "2900.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0006", + "ask": "0.0008", + "open_interest": "11306", + "volume": "149.68", + "strike": "1700", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.7215095", + "iv_ask": "0.7534892", + "inverse": true }, { - "bid": "0.0090", - "ask": "0.0095", - "open_interest": "1967.0", - "volume": "22104.43", - "strike": "2950.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0008", + "ask": "0.0011", + "open_interest": "4431", + "volume": "21.34", + "strike": "1750", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6688583", + "iv_ask": "0.7049865", + "inverse": true }, { - "bid": "0.1230", - "ask": "0.1295", - "open_interest": "1107.0", - "volume": "0.0", - "strike": "2950.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0012", + "ask": "0.0015", + "open_interest": "10666", + "volume": "46", + "strike": "1800", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6278276", + "iv_ask": "0.6543077", + "inverse": true }, { - "bid": "0.0065", - "ask": "0.0075", - "open_interest": "2947.0", - "volume": "14510.47", - "strike": "3000.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0017", + "ask": "0.0021", + "open_interest": "3578", + "volume": "213.7", + "strike": "1850", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5790283", + "iv_ask": "0.6052631", + "inverse": true }, { - "bid": "0.1405", - "ask": "0.1465", - "open_interest": "497.0", - "volume": "1942.36", - "strike": "3000.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0027", + "ask": "0.003", + "open_interest": "11680", + "volume": "2639.34", + "strike": "1900", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.542507", + "iv_ask": "0.5567728", + "inverse": true }, { - "bid": "0.0050", - "ask": "0.0060", - "open_interest": "770.0", - "volume": "2741.81", - "strike": "3050.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0043", + "ask": "0.0047", + "open_interest": "6105", + "volume": "6805.47", + "strike": "1950", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5058849", + "iv_ask": "0.5196545", + "inverse": true }, { - "bid": "0.1580", - "ask": "0.1640", - "open_interest": "398.0", - "volume": "666.19", - "strike": "3050.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "23914", + "volume": "82869.67", + "strike": "2000", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4727668", + "iv_ask": "0.485597", + "inverse": true }, { - "bid": "0.0041", - "ask": "0.0045", - "open_interest": "1841.0", - "volume": "784.72", - "strike": "3100.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0115", + "ask": "0.0125", + "open_interest": "7256", + "volume": "7639.54", + "strike": "2050", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4437291", + "iv_ask": "0.4639094", + "inverse": true }, { - "bid": "0.1750", - "ask": "0.1820", - "open_interest": "1195.0", - "volume": "364.96", - "strike": "3100.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0195", + "ask": "0.0205", + "open_interest": "75411", + "volume": "119342.67", + "strike": "2100", + "maturity": "2026-05-29T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4331064", + "iv_ask": "0.4505752", + "inverse": true }, { - "bid": "0.0032", - "ask": "0.0035", - "open_interest": "1111.0", - "volume": "394.09", - "strike": "3150.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0195", + "ask": "0.0205", + "open_interest": "14600", + "volume": "27307.93", + "strike": "2150", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4166556", + "iv_ask": "0.4338577", + "inverse": true }, { - "bid": "0.1930", - "ask": "0.2000", - "open_interest": "8.0", - "volume": "1726.42", - "strike": "3150.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.011", + "ask": "0.012", + "open_interest": "13418", + "volume": "69364.08", + "strike": "2200", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4097906", + "iv_ask": "0.429265", + "inverse": true }, { - "bid": "0.0025", - "ask": "0.0029", - "open_interest": "5029.0", - "volume": "4107.27", - "strike": "3200.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.006", + "ask": "0.007", + "open_interest": "14425", + "volume": "22633.29", + "strike": "2250", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4137438", + "iv_ask": "0.4385522", + "inverse": true }, { - "bid": "0.2115", - "ask": "0.2185", - "open_interest": "27.0", - "volume": "0.0", - "strike": "3200.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0033", + "ask": "0.0038", + "open_interest": "14902", + "volume": "2378.88", + "strike": "2300", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4259386", + "iv_ask": "0.4432802", + "inverse": true }, { - "bid": "0.0016", - "ask": "0.0020", - "open_interest": "1966.0", - "volume": "2844.13", - "strike": "3300.0000", - "maturity": "2026-02-13T08:00:00Z", + "bid": "0.0019", + "ask": "0.0022", + "open_interest": "10564", + "volume": "56.38", + "strike": "2350", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2490", - "ask": "0.2555", - "open_interest": "6.0", - "volume": "0.0", - "strike": "3300.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0011", - "ask": "0.0014", - "open_interest": "2460.0", - "volume": "1870.7", - "strike": "3400.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0005", - "ask": "0.0008", - "open_interest": "1537.0", - "volume": "12.75", - "strike": "3600.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0003", - "ask": "0.0006", - "open_interest": "298.0", - "volume": "14.52", - "strike": "3800.0000", - "maturity": "2026-02-13T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "2641.75", - "ask": "2642.50", - "open_interest": "39806132", - "volume": "2917427.0", - "maturity": "2026-02-27T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.4325", - "ask": "0.4350", - "open_interest": "37.0", - "volume": "22493.2", - "strike": "1500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0010", - "ask": "0.0012", - "open_interest": "8160.0", - "volume": "1027.3", - "strike": "1500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2490", - "ask": "0.2520", - "open_interest": "90.0", - "volume": "384784.09", - "strike": "2000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0065", - "ask": "0.0070", - "open_interest": "13386.0", - "volume": "8713.36", - "strike": "2000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1180", - "ask": "0.1215", - "open_interest": "2916.0", - "volume": "742.53", - "strike": "2400.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0275", - "ask": "0.0280", - "open_interest": "10615.0", - "volume": "90062.38", - "strike": "2400.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0915", - "ask": "0.0950", - "open_interest": "28.0", - "volume": "5822.63", - "strike": "2500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0390", - "ask": "0.0395", - "open_interest": "16411.0", - "volume": "497716.42", - "strike": "2500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0700", - "ask": "0.0715", - "open_interest": "262.0", - "volume": "38654.38", - "strike": "2600.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0535", - "ask": "0.0550", - "open_interest": "13574.0", - "volume": "217258.45", - "strike": "2600.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0515", - "ask": "0.0525", - "open_interest": "243.0", - "volume": "14246.82", - "strike": "2700.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0725", - "ask": "0.0740", - "open_interest": "6346.0", - "volume": "230336.81", - "strike": "2700.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0370", - "ask": "0.0375", - "open_interest": "2284.0", - "volume": "40517.06", - "strike": "2800.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0955", - "ask": "0.0985", - "open_interest": "7559.0", - "volume": "117513.15", - "strike": "2800.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0310", - "ask": "0.0320", - "open_interest": "1095.0", - "volume": "30306.07", - "strike": "2850.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1075", - "ask": "0.1120", - "open_interest": "890.0", - "volume": "5128.61", - "strike": "2850.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0260", - "ask": "0.0265", - "open_interest": "4056.0", - "volume": "46909.13", - "strike": "2900.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1220", - "ask": "0.1260", - "open_interest": "5364.0", - "volume": "20429.94", - "strike": "2900.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0215", - "ask": "0.0225", - "open_interest": "2190.0", - "volume": "18903.96", - "strike": "2950.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1355", - "ask": "0.1400", - "open_interest": "844.0", - "volume": "3805.06", - "strike": "2950.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0180", - "ask": "0.0190", - "open_interest": "11071.0", - "volume": "29625.59", - "strike": "3000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1495", - "ask": "0.1550", - "open_interest": "6092.0", - "volume": "68573.33", - "strike": "3000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0150", - "ask": "0.0155", - "open_interest": "847.0", - "volume": "11219.97", - "strike": "3050.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1650", - "ask": "0.1730", - "open_interest": "286.0", - "volume": "3958.45", - "strike": "3050.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0125", - "ask": "0.0130", - "open_interest": "5952.0", - "volume": "40623.98", - "strike": "3100.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1810", - "ask": "0.1895", - "open_interest": "4122.0", - "volume": "43080.5", - "strike": "3100.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "6936.0", - "volume": "14280.86", - "strike": "3200.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2150", - "ask": "0.2235", - "open_interest": "2626.0", - "volume": "2013.75", - "strike": "3200.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0060", - "ask": "0.0070", - "open_interest": "14920.0", - "volume": "19381.75", - "strike": "3300.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2505", - "ask": "0.2590", - "open_interest": "2863.0", - "volume": "0.0", - "strike": "3300.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0045", - "ask": "0.0049", - "open_interest": "8132.0", - "volume": "1229.63", - "strike": "3400.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2865", - "ask": "0.2950", - "open_interest": "1123.0", - "volume": "0.0", - "strike": "3400.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0034", - "ask": "0.0038", - "open_interest": "6440.0", - "volume": "3217.49", - "strike": "3500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3230", - "ask": "0.3320", - "open_interest": "640.0", - "volume": "4505.66", - "strike": "3500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0025", - "ask": "0.0029", - "open_interest": "6370.0", - "volume": "83.28", - "strike": "3600.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3600", - "ask": "0.3690", - "open_interest": "150.0", - "volume": "0.0", - "strike": "3600.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0020", - "ask": "0.0023", - "open_interest": "2072.0", - "volume": "24.99", - "strike": "3700.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3970", - "ask": "0.4065", - "open_interest": "103.0", - "volume": "0.0", - "strike": "3700.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0015", - "ask": "0.0019", - "open_interest": "6066.0", - "volume": "116.68", - "strike": "3800.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.4345", - "ask": "0.4440", - "open_interest": "30.0", - "volume": "0.0", - "strike": "3800.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4445969", + "iv_ask": "0.4595853", + "inverse": true }, { "bid": "0.0012", - "ask": "0.0016", - "open_interest": "3005.0", - "volume": "170.27", - "strike": "3900.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.4720", - "ask": "0.4815", - "open_interest": "5.0", - "volume": "0.0", - "strike": "3900.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0011", - "ask": "0.0013", - "open_interest": "3809.0", - "volume": "53.85", - "strike": "4000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.5095", - "ask": "0.5200", - "open_interest": "132.0", - "volume": "0.0", - "strike": "4000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0007", - "ask": "0.0010", - "open_interest": "3139.0", - "volume": "19.64", - "strike": "4200.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0004", - "ask": "0.0007", - "open_interest": "4886.0", - "volume": "8.72", - "strike": "4500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.6970", - "ask": "0.7090", - "open_interest": "1.0", - "volume": "0.0", - "strike": "4500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0004", - "open_interest": "3663.0", - "volume": "162.61", - "strike": "5000.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "5584.0", - "volume": "5.39", - "strike": "5500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.0735", - "ask": "1.0885", - "open_interest": "862.0", - "volume": "0.0", - "strike": "5500.0000", - "maturity": "2026-02-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "2649.25", - "ask": "2649.50", - "open_interest": "127513022", - "volume": "5914666.0", - "maturity": "2026-03-27T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.8095", - "ask": "0.8120", - "open_interest": "1229.0", - "volume": "0.0", - "strike": "500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.6215", - "ask": "0.6245", - "open_interest": "191.0", - "volume": "0.0", - "strike": "1000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "15884.0", - "volume": "112.92", - "strike": "1000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.5470", - "ask": "0.5495", - "open_interest": "108.0", - "volume": "0.0", - "strike": "1200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0009", "ask": "0.0014", - "open_interest": "4155.0", - "volume": "5.35", - "strike": "1200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.5095", - "ask": "0.5125", - "open_interest": "81.0", - "volume": "0.0", - "strike": "1300.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0015", - "ask": "0.0019", - "open_interest": "2009.0", - "volume": "0.0", - "strike": "1300.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.4725", - "ask": "0.4755", - "open_interest": "167.0", - "volume": "0.0", - "strike": "1400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0022", - "ask": "0.0026", - "open_interest": "1424.0", - "volume": "0.0", - "strike": "1400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.4355", - "ask": "0.4390", - "open_interest": "469.0", - "volume": "0.0", - "strike": "1500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0031", - "ask": "0.0035", - "open_interest": "3118.0", - "volume": "31.65", - "strike": "1500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.3990", - "ask": "0.4025", - "open_interest": "760.0", - "volume": "0.0", - "strike": "1600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0044", - "ask": "0.0048", - "open_interest": "6396.0", - "volume": "11.89", - "strike": "1600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.3275", - "ask": "0.3310", - "open_interest": "752.0", - "volume": "0.0", - "strike": "1800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0080", - "ask": "0.0090", - "open_interest": "9932.0", - "volume": "629.52", - "strike": "1800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2590", - "ask": "0.2625", - "open_interest": "2242.0", - "volume": "2273.85", - "strike": "2000.0000", - "maturity": "2026-03-27T08:00:00Z", + "open_interest": "20999", + "volume": "3.62", + "strike": "2400", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4703419", + "iv_ask": "0.4844348", + "inverse": true }, - { - "bid": "0.0150", - "ask": "0.0160", - "open_interest": "14029.0", - "volume": "46084.53", - "strike": "2000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1955", - "ask": "0.1995", - "open_interest": "9808.0", - "volume": "0.0", - "strike": "2200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0270", - "ask": "0.0285", - "open_interest": "6140.0", - "volume": "74443.2", - "strike": "2200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1395", - "ask": "0.1435", - "open_interest": "12137.0", - "volume": "0.0", - "strike": "2400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0475", - "ask": "0.0490", - "open_interest": "9087.0", - "volume": "158492.99", - "strike": "2400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1175", - "ask": "0.1200", - "open_interest": "1685.0", - "volume": "9273.65", - "strike": "2500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0620", - "ask": "0.0630", - "open_interest": "24803.0", - "volume": "360220.03", - "strike": "2500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0970", - "ask": "0.0985", - "open_interest": "2659.0", - "volume": "461328.1", - "strike": "2600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0785", - "ask": "0.0800", - "open_interest": "13629.0", - "volume": "271734.24", - "strike": "2600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0790", - "ask": "0.0805", - "open_interest": "537.0", - "volume": "120536.37", - "strike": "2700.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0980", - "ask": "0.1000", - "open_interest": "2002.0", - "volume": "133210.99", - "strike": "2700.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0640", - "ask": "0.0655", - "open_interest": "3107.0", - "volume": "3179.84", - "strike": "2800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1205", - "ask": "0.1225", - "open_interest": "9219.0", - "volume": "152274.58", - "strike": "2800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0510", - "ask": "0.0525", - "open_interest": "1363.0", - "volume": "55582.05", - "strike": "2900.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1445", - "ask": "0.1485", - "open_interest": "2288.0", - "volume": "36418.75", - "strike": "2900.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0410", - "ask": "0.0420", - "open_interest": "7879.0", - "volume": "140805.33", - "strike": "3000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1720", - "ask": "0.1765", - "open_interest": "12988.0", - "volume": "92927.36", - "strike": "3000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0325", - "ask": "0.0335", - "open_interest": "2637.0", - "volume": "6257.55", - "strike": "3100.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1985", - "ask": "0.2080", - "open_interest": "17328.0", - "volume": "27748.62", - "strike": "3100.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0255", - "ask": "0.0270", - "open_interest": "19963.0", - "volume": "84839.34", - "strike": "3200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2285", - "ask": "0.2395", - "open_interest": "3824.0", - "volume": "153278.2", - "strike": "3200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0205", - "ask": "0.0215", - "open_interest": "3186.0", - "volume": "16683.15", - "strike": "3300.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2605", - "ask": "0.2715", - "open_interest": "5430.0", - "volume": "0.0", - "strike": "3300.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0160", - "ask": "0.0175", - "open_interest": "13410.0", - "volume": "4837.48", - "strike": "3400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.2940", - "ask": "0.3060", - "open_interest": "3811.0", - "volume": "36043.8", - "strike": "3400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0130", - "ask": "0.0140", - "open_interest": "22490.0", - "volume": "3884.36", - "strike": "3500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3285", - "ask": "0.3410", - "open_interest": "8707.0", - "volume": "1699.81", - "strike": "3500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0105", - "ask": "0.0115", - "open_interest": "7719.0", - "volume": "6366.67", - "strike": "3600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3630", - "ask": "0.3760", - "open_interest": "2118.0", - "volume": "925.35", - "strike": "3600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0085", - "ask": "0.0095", - "open_interest": "5006.0", - "volume": "4160.62", - "strike": "3700.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.3990", - "ask": "0.4115", - "open_interest": "47.0", - "volume": "0.0", - "strike": "3700.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0070", - "ask": "0.0080", - "open_interest": "8235.0", - "volume": "9314.26", - "strike": "3800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.4340", - "ask": "0.4480", - "open_interest": "2026.0", - "volume": "0.0", - "strike": "3800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0055", - "ask": "0.0065", - "open_interest": "1601.0", - "volume": "2112.02", - "strike": "3900.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0049", - "ask": "0.0055", - "open_interest": "16718.0", - "volume": "29327.33", - "strike": "4000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.5070", - "ask": "0.5225", - "open_interest": "8666.0", - "volume": "0.0", - "strike": "4000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0041", - "ask": "0.0044", - "open_interest": "858.0", - "volume": "548.96", - "strike": "4100.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0035", - "ask": "0.0040", - "open_interest": "6496.0", - "volume": "264.82", - "strike": "4200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.5810", - "ask": "0.5965", - "open_interest": "757.0", - "volume": "0.0", - "strike": "4200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0026", - "ask": "0.0030", - "open_interest": "5749.0", - "volume": "54.69", - "strike": "4400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.6550", - "ask": "0.6725", - "open_interest": "3228.0", - "volume": "0.0", - "strike": "4400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0023", - "ask": "0.0026", - "open_interest": "15123.0", - "volume": "118.03", - "strike": "4500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.6925", - "ask": "0.7095", - "open_interest": "2600.0", - "volume": "0.0", - "strike": "4500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0020", - "ask": "0.0023", - "open_interest": "4231.0", - "volume": "65.96", - "strike": "4600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.7295", - "ask": "0.7470", - "open_interest": "242.0", - "volume": "0.0", - "strike": "4600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0015", - "ask": "0.0019", - "open_interest": "4199.0", - "volume": "69.62", - "strike": "4800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.8045", - "ask": "0.8220", - "open_interest": "220.0", - "volume": "0.0", - "strike": "4800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0011", - "ask": "0.0015", - "open_interest": "8050.0", - "volume": "172.95", - "strike": "5000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.8795", - "ask": "0.8985", - "open_interest": "992.0", - "volume": "0.0", - "strike": "5000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0008", - "ask": "0.0013", - "open_interest": "8078.0", - "volume": "296.12", - "strike": "5200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.9540", - "ask": "0.9735", - "open_interest": "284.0", - "volume": "0.0", - "strike": "5200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0006", - "ask": "0.0011", - "open_interest": "3452.0", - "volume": "90.01", - "strike": "5400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.0280", - "ask": "1.0495", - "open_interest": "81.0", - "volume": "0.0", - "strike": "5400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0006", - "ask": "0.0010", - "open_interest": "49199.0", - "volume": "75.47", - "strike": "5500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.0655", - "ask": "1.0870", - "open_interest": "156.0", - "volume": "0.0", - "strike": "5500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0005", - "ask": "0.0009", - "open_interest": "1908.0", - "volume": "20.12", - "strike": "5600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.1030", - "ask": "1.1260", - "open_interest": "44.0", - "volume": "0.0", - "strike": "5600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0004", - "ask": "0.0007", - "open_interest": "4698.0", - "volume": "49.7", - "strike": "5800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.1780", - "ask": "1.2005", - "open_interest": "16.0", - "volume": "0.0", - "strike": "5800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0003", - "ask": "0.0007", - "open_interest": "9987.0", - "volume": "1.91", - "strike": "6000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.2535", - "ask": "1.2775", - "open_interest": "2.0", - "volume": "0.0", - "strike": "6000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0006", - "open_interest": "2260.0", - "volume": "2.18", - "strike": "6200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.3280", - "ask": "1.3520", - "open_interest": "4.0", - "volume": "0.0", - "strike": "6200.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0005", - "open_interest": "3778.0", - "volume": "10.76", - "strike": "6400.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0003", - "ask": "0.0005", - "open_interest": "53376.0", - "volume": "1.08", - "strike": "6500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0005", - "open_interest": "1457.0", - "volume": "4.07", - "strike": "6600.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0005", - "open_interest": "1969.0", - "volume": "0.0", - "strike": "6800.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0004", - "open_interest": "14068.0", - "volume": "0.0", - "strike": "7000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "3116.0", - "volume": "0.0", - "strike": "7500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "6360.0", - "volume": "0.0", - "strike": "8000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "2.0010", - "ask": "2.0365", - "open_interest": "506.0", - "volume": "0.0", - "strike": "8000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0001", - "ask": "0.0003", - "open_interest": "2367.0", - "volume": "0.0", - "strike": "8500.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0001", - "ask": "0.0002", - "open_interest": "3739.0", - "volume": "0.0", - "strike": "9000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "2.7485", - "ask": "2.7965", - "open_interest": "11.0", - "volume": "0.0", - "strike": "10000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "3.5000", - "ask": "3.5565", - "open_interest": "21.0", - "volume": "0.0", - "strike": "12000.0000", - "maturity": "2026-03-27T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "2672.25", - "ask": "2672.75", - "open_interest": "53720583", - "volume": "2902678.0", - "maturity": "2026-06-26T08:00:00Z", - "security_type": "forward" - }, - { - "bid": "0.8120", - "ask": "0.8140", - "open_interest": "334.0", - "volume": "0.0", - "strike": "500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0002", - "ask": "0.0003", - "open_interest": "26674.0", - "volume": "0.0", - "strike": "500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.6275", - "ask": "0.6310", - "open_interest": "494.0", - "volume": "0.0", - "strike": "1000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0034", - "ask": "0.0041", - "open_interest": "7717.0", - "volume": "769.28", - "strike": "1000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.4520", - "ask": "0.4565", - "open_interest": "18.0", - "volume": "0.0", - "strike": "1500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0155", - "ask": "0.0170", - "open_interest": "7398.0", - "volume": "4300.45", - "strike": "1500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0315", - "ask": "0.0335", - "open_interest": "52.0", - "volume": "540.84", - "strike": "1800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2975", - "ask": "0.3030", - "open_interest": "492.0", - "volume": "883.07", - "strike": "2000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0485", - "ask": "0.0505", - "open_interest": "18245.0", - "volume": "3418.78", - "strike": "2000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2465", - "ask": "0.2515", - "open_interest": "82.0", - "volume": "0.0", - "strike": "2200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0715", - "ask": "0.0740", - "open_interest": "16099.0", - "volume": "55438.45", - "strike": "2200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2010", - "ask": "0.2065", - "open_interest": "7.0", - "volume": "542.17", - "strike": "2400.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1010", - "ask": "0.1035", - "open_interest": "2949.0", - "volume": "49608.68", - "strike": "2400.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1830", - "ask": "0.1860", - "open_interest": "625.0", - "volume": "0.0", - "strike": "2500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1185", - "ask": "0.1210", - "open_interest": "3375.0", - "volume": "1502.29", - "strike": "2500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1645", - "ask": "0.1675", - "open_interest": "1357.0", - "volume": "2947.94", - "strike": "2600.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1375", - "ask": "0.1400", - "open_interest": "18710.0", - "volume": "15566.93", - "strike": "2600.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1325", - "ask": "0.1350", - "open_interest": "4936.0", - "volume": "553375.49", - "strike": "2800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1800", - "ask": "0.1830", - "open_interest": "4566.0", - "volume": "21016.1", - "strike": "2800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + { + "bid": "0.0008", + "ask": "0.001", + "open_interest": "9009", + "volume": "790.95", + "strike": "2450", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4977246", + "iv_ask": "0.5168521", + "inverse": true }, { - "bid": "0.1060", - "ask": "0.1085", - "open_interest": "4995.0", - "volume": "530361.8", - "strike": "3000.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0005", + "ask": "0.0008", + "open_interest": "25244", + "volume": "13.5", + "strike": "2500", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5173543", + "iv_ask": "0.5556877", + "inverse": true }, { - "bid": "0.2275", - "ask": "0.2350", - "open_interest": "13780.0", - "volume": "7194.75", - "strike": "3000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0004", + "ask": "0.0006", + "open_interest": "16576", + "volume": "143.37", + "strike": "2550", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5530704", + "iv_ask": "0.5855428", + "inverse": true }, { - "bid": "0.0845", - "ask": "0.0865", - "open_interest": "2511.0", - "volume": "149706.39", - "strike": "3200.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0003", + "ask": "0.0004", + "open_interest": "26179", + "volume": "165.23", + "strike": "2600", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5812452", + "iv_ask": "0.6031905", + "inverse": true }, { - "bid": "0.2810", - "ask": "0.2840", - "open_interest": "2558.0", - "volume": "0.0", - "strike": "3200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "8480", + "volume": "0.64", + "strike": "2650", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5989191", + "iv_ask": "0.6284835", + "inverse": true }, { - "bid": "0.0675", - "ask": "0.0695", - "open_interest": "2228.0", - "volume": "3211.07", - "strike": "3400.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "18325", + "volume": "4.26", + "strike": "2700", + "maturity": "2026-05-29T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6431273", + "iv_ask": "0.6743626", + "inverse": true }, { - "bid": "0.3325", - "ask": "0.3465", - "open_interest": "6002.0", - "volume": "0.0", - "strike": "3400.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "22411", + "volume": "0", + "strike": "2800", + "maturity": "2026-05-29T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.6774029", + "iv_ask": "0.7624207", + "inverse": true + }, + { + "bid": "2125.75", + "ask": "2126.5", + "open_interest": "5729802", + "volume": "722790", + "maturity": "2026-06-05T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.0600", - "ask": "0.0625", - "open_interest": "2832.0", - "volume": "71023.55", - "strike": "3500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0039", + "ask": "0.0044", + "open_interest": "5138", + "volume": "111.16", + "strike": "1800", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5859417", + "iv_ask": "0.6036698", + "inverse": true }, { - "bid": "0.3620", - "ask": "0.3775", - "open_interest": "4075.0", - "volume": "0.0", - "strike": "3500.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1219", + "volume": "3877.84", + "strike": "1900", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5279543", + "iv_ask": "0.5504442", + "inverse": true }, { - "bid": "0.0535", - "ask": "0.0560", - "open_interest": "3765.0", - "volume": "60049.47", - "strike": "3600.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.011", + "ask": "0.0115", + "open_interest": "2194", + "volume": "368.46", + "strike": "1950", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5106228", + "iv_ask": "0.5198153", + "inverse": true }, { - "bid": "0.3930", - "ask": "0.4085", - "open_interest": "2592.0", - "volume": "0.0", - "strike": "3600.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0155", + "ask": "0.0165", + "open_interest": "982", + "volume": "22065.1", + "strike": "2000", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4884372", + "iv_ask": "0.5039086", + "inverse": true }, { - "bid": "0.0430", - "ask": "0.0450", - "open_interest": "1032.0", - "volume": "6235.32", - "strike": "3800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.022", + "ask": "0.023", + "open_interest": "1104", + "volume": "31861.72", + "strike": "2050", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.4723366", + "iv_ask": "0.4859407", + "inverse": true }, { - "bid": "0.4565", - "ask": "0.4730", - "open_interest": "1190.0", - "volume": "0.0", - "strike": "3800.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.031", + "ask": "0.032", + "open_interest": "835", + "volume": "20899.95", + "strike": "2100", + "maturity": "2026-06-05T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4625308", + "iv_ask": "0.4751426", + "inverse": true }, { - "bid": "0.0345", - "ask": "0.0365", - "open_interest": "8019.0", - "volume": "27453.46", - "strike": "4000.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0315", + "ask": "0.0325", + "open_interest": "997", + "volume": "24201.29", + "strike": "2150", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4517714", + "iv_ask": "0.4642028", + "inverse": true }, { - "bid": "0.5225", - "ask": "0.5415", - "open_interest": "3990.0", - "volume": "0.0", - "strike": "4000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.022", + "ask": "0.0225", + "open_interest": "1014", + "volume": "14615.68", + "strike": "2200", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4463917", + "iv_ask": "0.4529286", + "inverse": true }, { - "bid": "0.0280", - "ask": "0.0300", - "open_interest": "5251.0", - "volume": "2763.51", - "strike": "4200.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.015", + "ask": "0.0155", + "open_interest": "2451", + "volume": "6805.97", + "strike": "2250", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4450852", + "iv_ask": "0.4523944", + "inverse": true }, { - "bid": "0.5900", - "ask": "0.6085", - "open_interest": "2111.0", - "volume": "0.0", - "strike": "4200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0095", + "ask": "0.0105", + "open_interest": "3641", + "volume": "7643.52", + "strike": "2300", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4372256", + "iv_ask": "0.4546071", + "inverse": true }, { - "bid": "0.0230", - "ask": "0.0250", - "open_interest": "4234.0", - "volume": "119.77", - "strike": "4400.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0065", + "ask": "0.007", + "open_interest": "1170", + "volume": "4265.42", + "strike": "2350", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4474492", + "iv_ask": "0.4581288", + "inverse": true }, { - "bid": "0.6590", - "ask": "0.6785", - "open_interest": "4317.0", - "volume": "0.0", - "strike": "4400.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0043", + "ask": "0.0047", + "open_interest": "1284", + "volume": "1391.98", + "strike": "2400", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4541381", + "iv_ask": "0.4650089", + "inverse": true }, { - "bid": "0.0210", - "ask": "0.0225", - "open_interest": "3921.0", - "volume": "8394.82", - "strike": "4500.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0029", + "ask": "0.0033", + "open_interest": "752", + "volume": "0", + "strike": "2450", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4638575", + "iv_ask": "0.4777899", + "inverse": true }, { - "bid": "0.6940", - "ask": "0.7155", - "open_interest": "411.0", - "volume": "0.0", - "strike": "4500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0019", + "ask": "0.0023", + "open_interest": "1318", + "volume": "163.36", + "strike": "2500", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.470869", + "iv_ask": "0.4891746", + "inverse": true }, { - "bid": "0.0190", - "ask": "0.0205", - "open_interest": "4309.0", - "volume": "0.0", - "strike": "4600.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.001", + "ask": "0.0013", + "open_interest": "1874", + "volume": "3", + "strike": "2600", + "maturity": "2026-06-05T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5013057", + "iv_ask": "0.5233498", + "inverse": true }, { - "bid": "0.7295", - "ask": "0.7495", - "open_interest": "3460.0", - "volume": "0.0", - "strike": "4600.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0003", + "ask": "0.0005", + "open_interest": "581", + "volume": "105.24", + "strike": "2800", + "maturity": "2026-06-05T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.554879", + "iv_ask": "0.5909788", + "inverse": true + }, + { + "bid": "2122.25", + "ask": "2128.25", + "open_interest": "6", + "volume": "6", + "maturity": "2026-06-12T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.0160", - "ask": "0.0175", - "open_interest": "745.0", - "volume": "0.0", - "strike": "4800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0024", + "ask": "0.0028", + "open_interest": "0", + "volume": "0", + "strike": "1600", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6712824", + "iv_ask": "0.6916138", + "inverse": true }, { - "bid": "0.8005", - "ask": "0.8215", - "open_interest": "340.0", - "volume": "0.0", - "strike": "4800.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "50", + "volume": "694.51", + "strike": "1800", + "maturity": "2026-06-12T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5646614", + "iv_ask": "0.5760703", + "inverse": true }, { - "bid": "0.0135", - "ask": "0.0150", - "open_interest": "16134.0", - "volume": "380964.1", - "strike": "5000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.013", + "ask": "0.0135", + "open_interest": "0", + "volume": "0", + "strike": "1900", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5305516", + "iv_ask": "0.5384319", + "inverse": true }, { - "bid": "0.8720", - "ask": "0.8965", - "open_interest": "433.0", - "volume": "11543.83", - "strike": "5000.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.023", + "ask": "0.024", + "open_interest": "52", + "volume": "2389.51", + "strike": "2000", + "maturity": "2026-06-12T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4965638", + "iv_ask": "0.5085588", + "inverse": true }, { - "bid": "0.0110", - "ask": "0.0125", - "open_interest": "2305.0", - "volume": "0.0", - "strike": "5200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "0.0305", + "ask": "0.0315", + "open_interest": "1", + "volume": "60.91", + "strike": "2050", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.484771", + "iv_ask": "0.4957249", + "inverse": true }, { - "bid": "0.9440", - "ask": "0.9675", - "open_interest": "360.0", - "volume": "0.0", - "strike": "5200.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.04", + "ask": "0.041", + "open_interest": "0", + "volume": "0", + "strike": "2100", + "maturity": "2026-06-12T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4761048", + "iv_ask": "0.4864843", + "inverse": true }, { - "bid": "0.0095", - "ask": "0.0110", - "open_interest": "767.0", - "volume": "27.31", - "strike": "5400.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0405", + "ask": "0.0415", + "open_interest": "0", + "volume": "0", + "strike": "2150", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4639298", + "iv_ask": "0.4741663", + "inverse": true }, { - "bid": "1.0165", - "ask": "1.0435", - "open_interest": "15.0", - "volume": "0.0", - "strike": "5400.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0305", + "ask": "0.0315", + "open_interest": "0", + "volume": "0", + "strike": "2200", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4579469", + "iv_ask": "0.4684714", + "inverse": true }, { - "bid": "0.0090", - "ask": "0.0105", - "open_interest": "45069.0", - "volume": "728.82", - "strike": "5500.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0225", + "ask": "0.0235", + "open_interest": "0", + "volume": "0", + "strike": "2250", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4538406", + "iv_ask": "0.4651133", + "inverse": true }, { - "bid": "1.0530", - "ask": "1.0805", - "open_interest": "20.0", - "volume": "0.0", - "strike": "5500.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0165", + "ask": "0.0175", + "open_interest": "32", + "volume": "1203.1", + "strike": "2300", + "maturity": "2026-06-12T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.4539639", + "iv_ask": "0.4664757", + "inverse": true }, { - "bid": "0.0080", + "bid": "0.0085", "ask": "0.0095", - "open_interest": "1322.0", - "volume": "256.16", - "strike": "5600.0000", - "maturity": "2026-06-26T08:00:00Z", + "open_interest": "23", + "volume": "458.42", + "strike": "2400", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4561324", + "iv_ask": "0.4730239", + "inverse": true }, { - "bid": "0.0070", - "ask": "0.0085", - "open_interest": "665.0", - "volume": "20.48", - "strike": "5800.0000", - "maturity": "2026-06-26T08:00:00Z", + "bid": "0.0024", + "ask": "0.0029", + "open_interest": "0", + "volume": "0", + "strike": "2600", + "maturity": "2026-06-12T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4821009", + "iv_ask": "0.5006523", + "inverse": true }, { - "bid": "1.1625", - "ask": "1.1920", - "open_interest": "44.0", - "volume": "0.0", - "strike": "5800.0000", + "bid": "2127.25", + "ask": "2127.5", + "open_interest": "91532882", + "volume": "5153214", "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "forward" }, { - "bid": "0.0060", - "ask": "0.0075", - "open_interest": "6485.0", - "volume": "19.18", - "strike": "6000.0000", + "bid": "0.0023", + "ask": "0.0027", + "open_interest": "6295", + "volume": "2102.35", + "strike": "1400", "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7220753", + "iv_ask": "0.7429887", + "inverse": true }, { - "bid": "1.2360", - "ask": "1.2665", - "open_interest": "5.0", - "volume": "0.0", - "strike": "6000.0000", + "bid": "0.0036", + "ask": "0.0037", + "open_interest": "12591", + "volume": "324.91", + "strike": "1500", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.673963", + "iv_ask": "0.6777027", + "inverse": true }, { "bid": "0.0055", "ask": "0.0065", - "open_interest": "1062.0", - "volume": "0.0", - "strike": "6200.0000", + "open_interest": "9705", + "volume": "6052.95", + "strike": "1600", "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6254", + "iv_ask": "0.6510014", + "inverse": true }, { - "bid": "1.3095", - "ask": "1.3410", - "open_interest": "18.0", - "volume": "0.0", - "strike": "6200.0000", + "bid": "0.009", + "ask": "0.0095", + "open_interest": "5315", + "volume": "11796.83", + "strike": "1700", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5904337", + "iv_ask": "0.5997074", + "inverse": true }, { - "bid": "0.0050", - "ask": "0.0060", - "open_interest": "689.0", - "volume": "0.0", - "strike": "6400.0000", + "bid": "0.0145", + "ask": "0.015", + "open_interest": "7948", + "volume": "17773.91", + "strike": "1800", "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5581504", + "iv_ask": "0.5650107", + "inverse": true }, { - "bid": "1.3830", - "ask": "1.4130", - "open_interest": "31.0", - "volume": "0.0", - "strike": "6400.0000", + "bid": "0.023", + "ask": "0.024", + "open_interest": "6559", + "volume": "124208.23", + "strike": "1900", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5295156", + "iv_ask": "0.5401895", + "inverse": true }, { - "bid": "0.0047", - "ask": "0.0055", - "open_interest": "37688.0", - "volume": "0.0", - "strike": "6500.0000", + "bid": "0.036", + "ask": "0.037", + "open_interest": "19680", + "volume": "67094.79", + "strike": "2000", "maturity": "2026-06-26T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5077279", + "iv_ask": "0.5166439", + "inverse": true }, { - "bid": "1.4195", - "ask": "1.4505", - "open_interest": "60.0", - "volume": "0.0", - "strike": "6500.0000", + "bid": "0.055", + "ask": "0.056", + "open_interest": "13981", + "volume": "42220.67", + "strike": "2100", "maturity": "2026-06-26T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4949915", + "iv_ask": "0.5030916", + "inverse": true }, { - "bid": "0.0044", - "ask": "0.0055", - "open_interest": "1218.0", - "volume": "4259.41", - "strike": "6600.0000", + "bid": "0.0455", + "ask": "0.0465", + "open_interest": "7800", + "volume": "10768.03", + "strike": "2200", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4785464", + "iv_ask": "0.4866195", + "inverse": true }, { - "bid": "0.0040", - "ask": "0.0047", - "open_interest": "933.0", - "volume": "0.0", - "strike": "6800.0000", + "bid": "0.0295", + "ask": "0.0305", + "open_interest": "9912", + "volume": "160055.7", + "strike": "2300", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4737501", + "iv_ask": "0.4825604", + "inverse": true }, { - "bid": "1.5300", - "ask": "1.5660", - "open_interest": "1.0", - "volume": "0.0", - "strike": "6800.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.0036", - "ask": "0.0043", - "open_interest": "6490.0", - "volume": "0.0", - "strike": "7000.0000", + "bid": "0.0185", + "ask": "0.019", + "open_interest": "14883", + "volume": "12829.27", + "strike": "2400", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4719197", + "iv_ask": "0.4771436", + "inverse": true }, { - "bid": "0.0032", - "ask": "0.0039", - "open_interest": "1068.0", - "volume": "19.95", - "strike": "7200.0000", + "bid": "0.011", + "ask": "0.012", + "open_interest": "68572", + "volume": "76676.51", + "strike": "2500", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "1.6775", - "ask": "1.7160", - "open_interest": "76.0", - "volume": "0.0", - "strike": "7200.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4681885", + "iv_ask": "0.4814408", + "inverse": true }, { - "bid": "0.0029", - "ask": "0.0036", - "open_interest": "2242.0", - "volume": "0.0", - "strike": "7400.0000", + "bid": "0.007", + "ask": "0.0075", + "open_interest": "15892", + "volume": "31.9", + "strike": "2600", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4781266", + "iv_ask": "0.4868166", + "inverse": true }, { - "bid": "0.0027", - "ask": "0.0034", - "open_interest": "1289.0", - "volume": "3921.85", - "strike": "7500.0000", + "bid": "0.0045", + "ask": "0.0049", + "open_interest": "16511", + "volume": "2951.61", + "strike": "2700", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4890707", + "iv_ask": "0.4983296", + "inverse": true }, { - "bid": "0.0021", - "ask": "0.0028", - "open_interest": "1908.0", - "volume": "51.21", - "strike": "8000.0000", + "bid": "0.0029", + "ask": "0.0033", + "open_interest": "17794", + "volume": "1263.99", + "strike": "2800", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.4995412", + "iv_ask": "0.5119932", + "inverse": true }, { - "bid": "0.0017", + "bid": "0.002", "ask": "0.0023", - "open_interest": "4017.0", - "volume": "7283.62", - "strike": "8500.0000", + "open_interest": "15071", + "volume": "486.94", + "strike": "2900", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.514955", + "iv_ask": "0.5272995", + "inverse": true }, { - "bid": "0.0014", - "ask": "0.0019", - "open_interest": "9538.0", - "volume": "0.0", - "strike": "9000.0000", + "bid": "0.0013", + "ask": "0.0016", + "open_interest": "21551", + "volume": "408.6", + "strike": "3000", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5239483", + "iv_ask": "0.540699", + "inverse": true }, { "bid": "0.0009", - "ask": "0.0014", - "open_interest": "2018.0", - "volume": "0.0", - "strike": "10000.0000", + "ask": "0.0013", + "open_interest": "6617", + "volume": "7.26", + "strike": "3100", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5367198", + "iv_ask": "0.5650581", + "inverse": true }, { - "bid": "0.0006", - "ask": "0.0011", - "open_interest": "1832.0", - "volume": "0.0", - "strike": "11000.0000", + "bid": "0.0007", + "ask": "0.001", + "open_interest": "15035", + "volume": "2.35", + "strike": "3200", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5562015", + "iv_ask": "0.5827433", + "inverse": true }, { "bid": "0.0004", - "ask": "0.0009", - "open_interest": "3617.0", - "volume": "0.0", - "strike": "12000.0000", + "ask": "0.0007", + "open_interest": "12008", + "volume": "3.2", + "strike": "3400", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "3.4510", - "ask": "3.5215", - "open_interest": "55.0", - "volume": "0.0", - "strike": "12000.0000", - "maturity": "2026-06-26T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5861117", + "iv_ask": "0.6256834", + "inverse": true }, { "bid": "0.0002", - "ask": "0.0007", - "open_interest": "3262.0", - "volume": "0.0", - "strike": "13000.0000", + "ask": "0.0005", + "open_interest": "10563", + "volume": "257.14", + "strike": "3500", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.574405", + "iv_ask": "0.6329249", + "inverse": true }, { "bid": "0.0001", - "ask": "0.0006", - "open_interest": "3571.0", - "volume": "0.0", - "strike": "14000.0000", + "ask": "0.0005", + "open_interest": "7085", + "volume": "0", + "strike": "3600", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5654173", + "iv_ask": "0.6635841", + "inverse": true }, { - "bid": "0.0001", - "ask": "0.0004", - "open_interest": "2736.0", - "volume": "0.0", - "strike": "15000.0000", + "bid": "0.0002", + "ask": "0.0003", + "open_interest": "7017", + "volume": "26.12", + "strike": "3800", "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "2698.00", - "ask": "2699.75", - "open_interest": "23582530", - "volume": "3549252.0", - "maturity": "2026-09-25T08:00:00Z", - "security_type": "forward" + "security_type": "option", + "iv_bid": "0.6569811", + "iv_ask": "0.6837684", + "inverse": true }, { - "bid": "0.6355", - "ask": "0.6415", - "open_interest": "55.0", - "volume": "0.0", - "strike": "1000.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0001", + "ask": "0.0003", + "open_interest": "13122", + "volume": "0", + "strike": "4000", + "maturity": "2026-06-26T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.0085", - "ask": "0.0100", - "open_interest": "1564.0", - "volume": "1981.77", - "strike": "1000.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6651664", + "iv_ask": "0.7361061", + "inverse": true }, { - "bid": "0.4730", - "ask": "0.4790", - "open_interest": "4.0", - "volume": "0.0", - "strike": "1500.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "bid": "2131.75", + "ask": "2132.25", + "open_interest": "2446074", + "volume": "263778", + "maturity": "2026-07-31T08:00:00Z", + "security_type": "forward" }, { - "bid": "0.0305", - "ask": "0.0330", - "open_interest": "6296.0", - "volume": "1536.16", - "strike": "1500.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.017", + "ask": "0.018", + "open_interest": "3560", + "volume": "3715.65", + "strike": "1600", + "maturity": "2026-07-31T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6105739", + "iv_ask": "0.6221458", + "inverse": true }, { - "bid": "0.0555", - "ask": "0.0580", - "open_interest": "19.0", - "volume": "0.0", - "strike": "1800.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0335", + "ask": "0.0345", + "open_interest": "7870", + "volume": "0", + "strike": "1800", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5632748", + "iv_ask": "0.5711162", + "inverse": true + }, + { + "bid": "0.0465", + "ask": "0.0475", + "open_interest": "1778", + "volume": "191.4", + "strike": "1900", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5472005", + "iv_ask": "0.5539907", + "inverse": true + }, + { + "bid": "0.054", + "ask": "0.0555", + "open_interest": "95", + "volume": "2443.4", + "strike": "1950", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5379451", + "iv_ask": "0.5475732", + "inverse": true + }, + { + "bid": "0.063", + "ask": "0.064", + "open_interest": "2063", + "volume": "0", + "strike": "2000", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5328136", + "iv_ask": "0.5389454", + "inverse": true + }, + { + "bid": "0.073", + "ask": "0.074", + "open_interest": "336", + "volume": "153.58", + "strike": "2050", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5281861", + "iv_ask": "0.5341071", + "inverse": true + }, + { + "bid": "0.084", + "ask": "0.085", + "open_interest": "582", + "volume": "19869.82", + "strike": "2100", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5239524", + "iv_ask": "0.5297329", + "inverse": true + }, + { + "bid": "0.087", + "ask": "0.0885", + "open_interest": "199", + "volume": "0", + "strike": "2150", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5177179", + "iv_ask": "0.5262742", + "inverse": true + }, + { + "bid": "0.0765", + "ask": "0.0775", + "open_interest": "613", + "volume": "0", + "strike": "2200", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5140201", + "iv_ask": "0.5197087", + "inverse": true + }, + { + "bid": "0.067", + "ask": "0.068", + "open_interest": "303", + "volume": "0", + "strike": "2250", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5108267", + "iv_ask": "0.5165586", + "inverse": true + }, + { + "bid": "0.0585", + "ask": "0.0595", + "open_interest": "1883", + "volume": "0", + "strike": "2300", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5083247", + "iv_ask": "0.5141569", + "inverse": true + }, + { + "bid": "0.051", + "ask": "0.052", + "open_interest": "2982", + "volume": "112.56", + "strike": "2350", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5067894", + "iv_ask": "0.5127782", + "inverse": true + }, + { + "bid": "0.044", + "ask": "0.045", + "open_interest": "3953", + "volume": "3990.34", + "strike": "2400", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5034967", + "iv_ask": "0.5097061", + "inverse": true + }, + { + "bid": "0.038", + "ask": "0.039", + "open_interest": "554", + "volume": "0", + "strike": "2450", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5017914", + "iv_ask": "0.5082811", + "inverse": true + }, + { + "bid": "0.033", + "ask": "0.034", + "open_interest": "435", + "volume": "0", + "strike": "2500", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5022651", + "iv_ask": "0.5090885", + "inverse": true }, { - "bid": "0.3365", - "ask": "0.3420", - "open_interest": "243.0", - "volume": "0.0", - "strike": "2000.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0285", + "ask": "0.0295", + "open_interest": "902", + "volume": "0", + "strike": "2550", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.502076", + "iv_ask": "0.5093036", + "inverse": true }, { - "bid": "0.0780", - "ask": "0.0810", - "open_interest": "9845.0", - "volume": "2708.42", - "strike": "2000.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.2900", - "ask": "0.2965", - "open_interest": "105.0", - "volume": "0.0", - "strike": "2200.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0245", + "ask": "0.0255", + "open_interest": "1681", + "volume": "4164.52", + "strike": "2600", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1060", - "ask": "0.1095", - "open_interest": "3198.0", - "volume": "549.96", - "strike": "2200.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.501528", + "iv_ask": "0.5092379", + "inverse": true }, { - "bid": "0.2505", - "ask": "0.2550", - "open_interest": "36.0", - "volume": "0.0", - "strike": "2400.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.021", + "ask": "0.022", + "open_interest": "706", + "volume": "47.9", + "strike": "2650", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1395", - "ask": "0.1435", - "open_interest": "1947.0", - "volume": "0.0", - "strike": "2400.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5009859", + "iv_ask": "0.5092619", + "inverse": true }, { - "bid": "0.2320", - "ask": "0.2365", - "open_interest": "23.0", - "volume": "669.88", - "strike": "2500.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.018", + "ask": "0.019", + "open_interest": "2300", + "volume": "587.71", + "strike": "2700", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" - }, - { - "bid": "0.1580", - "ask": "0.1625", - "open_interest": "2767.0", - "volume": "836.69", - "strike": "2500.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5009078", + "iv_ask": "0.5098347", + "inverse": true }, { - "bid": "0.2145", - "ask": "0.2190", - "open_interest": "61.0", - "volume": "31550.33", - "strike": "2600.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0155", + "ask": "0.0165", + "open_interest": "138", + "volume": "0", + "strike": "2750", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5018813", + "iv_ask": "0.5115344", + "inverse": true }, { - "bid": "0.1780", - "ask": "0.1825", - "open_interest": "8624.0", - "volume": "0.0", - "strike": "2600.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" - }, - { - "bid": "0.1840", - "ask": "0.1880", - "open_interest": "562.0", - "volume": "90868.82", - "strike": "2800.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0135", + "ask": "0.0145", + "open_interest": "5776", + "volume": "0", + "strike": "2800", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5046547", + "iv_ask": "0.5150816", + "inverse": true }, { - "bid": "0.2210", - "ask": "0.2255", - "open_interest": "1295.0", - "volume": "0.0", - "strike": "2800.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.01", + "ask": "0.011", + "open_interest": "1386", + "volume": "24.52", + "strike": "2900", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5069915", + "iv_ask": "0.519377", + "inverse": true }, { - "bid": "0.1575", - "ask": "0.1615", - "open_interest": "397.0", - "volume": "0.0", - "strike": "3000.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "1407", + "volume": "512.56", + "strike": "3000", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5110555", + "iv_ask": "0.5258152", + "inverse": true }, - { - "bid": "0.2665", - "ask": "0.2760", - "open_interest": "6484.0", - "volume": "0.0", - "strike": "3000.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + { + "bid": "0.006", + "ask": "0.0065", + "open_interest": "700", + "volume": "51.24", + "strike": "3100", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5218068", + "iv_ask": "0.5305488", + "inverse": true }, { - "bid": "0.1345", - "ask": "0.1385", - "open_interest": "1116.0", - "volume": "0.0", - "strike": "3200.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0046", + "ask": "0.0049", + "open_interest": "3875", + "volume": "0", + "strike": "3200", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5271592", + "iv_ask": "0.5335063", + "inverse": true }, { - "bid": "0.3165", - "ask": "0.3295", - "open_interest": "1436.0", - "volume": "0.0", - "strike": "3200.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.003", + "ask": "0.0033", + "open_interest": "1585", + "volume": "13.25", + "strike": "3400", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.545704", + "iv_ask": "0.5543935", + "inverse": true }, { - "bid": "0.1155", - "ask": "0.1190", - "open_interest": "3707.0", - "volume": "0.0", - "strike": "3400.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.002", + "ask": "0.0022", + "open_interest": "521", + "volume": "0", + "strike": "3600", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5627004", + "iv_ask": "0.5706662", + "inverse": true }, { - "bid": "0.3645", - "ask": "0.3915", - "open_interest": "1084.0", - "volume": "1983.02", - "strike": "3400.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0014", + "ask": "0.0016", + "open_interest": "668", + "volume": "0", + "strike": "3800", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.580564", + "iv_ask": "0.591091", + "inverse": true }, { - "bid": "0.1070", - "ask": "0.1100", - "open_interest": "10484.0", - "volume": "1623.79", - "strike": "3500.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0009", + "ask": "0.0012", + "open_interest": "8641", + "volume": "3.62", + "strike": "4000", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5895746", + "iv_ask": "0.6108455", + "inverse": true }, { - "bid": "0.3925", - "ask": "0.4200", - "open_interest": "908.0", - "volume": "0.0", - "strike": "3500.0000", - "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "bid": "0.0005", + "ask": "0.0009", + "open_interest": "1130", + "volume": "0", + "strike": "4200", + "maturity": "2026-07-31T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5878241", + "iv_ask": "0.6279168", + "inverse": true }, { - "bid": "0.0990", - "ask": "0.1025", - "open_interest": "952.0", - "volume": "5604.54", - "strike": "3600.0000", - "maturity": "2026-09-25T08:00:00Z", + "bid": "0.0006", + "ask": "0.0007", + "open_interest": "761", + "volume": "0", + "strike": "4400", + "maturity": "2026-07-31T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6345173", + "iv_ask": "0.6453587", + "inverse": true }, { - "bid": "0.4215", - "ask": "0.4500", - "open_interest": "608.0", - "volume": "5830.71", - "strike": "3600.0000", + "bid": "2141.25", + "ask": "2141.5", + "open_interest": "61466233", + "volume": "1675130", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "security_type": "forward" }, { - "bid": "0.0850", - "ask": "0.0880", - "open_interest": "774.0", - "volume": "1302.33", - "strike": "3800.0000", + "bid": "0.006", + "ask": "0.007", + "open_interest": "6792", + "volume": "13.84", + "strike": "1000", "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7729307", + "iv_ask": "0.7976245", + "inverse": true }, { - "bid": "0.4825", - "ask": "0.5110", - "open_interest": "1001.0", - "volume": "0.0", - "strike": "3800.0000", + "bid": "0.0115", + "ask": "0.0125", + "open_interest": "11195", + "volume": "128.97", + "strike": "1200", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.7063138", + "iv_ask": "0.7212723", + "inverse": true }, { - "bid": "0.0735", - "ask": "0.0765", - "open_interest": "2737.0", - "volume": "2039.32", - "strike": "4000.0000", + "bid": "0.0205", + "ask": "0.0215", + "open_interest": "2247", + "volume": "0", + "strike": "1400", "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.648459", + "iv_ask": "0.6582983", + "inverse": true }, { - "bid": "0.5425", - "ask": "0.5740", - "open_interest": "173.0", - "volume": "0.0", - "strike": "4000.0000", + "bid": "0.0275", + "ask": "0.0285", + "open_interest": "9374", + "volume": "3244.24", + "strike": "1500", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6280565", + "iv_ask": "0.6362094", + "inverse": true }, { - "bid": "0.0635", - "ask": "0.0665", - "open_interest": "808.0", - "volume": "0.0", - "strike": "4200.0000", + "bid": "0.036", + "ask": "0.0375", + "open_interest": "3440", + "volume": "767.76", + "strike": "1600", "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6074609", + "iv_ask": "0.6178414", + "inverse": true }, { - "bid": "0.6065", - "ask": "0.6385", - "open_interest": "527.0", - "volume": "0.0", - "strike": "4200.0000", + "bid": "0.047", + "ask": "0.0485", + "open_interest": "1639", + "volume": "0", + "strike": "1700", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5917157", + "iv_ask": "0.6007305", + "inverse": true }, { - "bid": "0.0550", - "ask": "0.0580", - "open_interest": "3169.0", - "volume": "290.12", - "strike": "4400.0000", + "bid": "0.061", + "ask": "0.062", + "open_interest": "3679", + "volume": "13865.65", + "strike": "1800", "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5808918", + "iv_ask": "0.586236", + "inverse": true }, { - "bid": "0.6720", - "ask": "0.7055", - "open_interest": "281.0", - "volume": "0.0", - "strike": "4400.0000", + "bid": "0.077", + "ask": "0.0785", + "open_interest": "3609", + "volume": "334.43", + "strike": "1900", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5680346", + "iv_ask": "0.5753462", + "inverse": true }, { - "bid": "0.0515", - "ask": "0.0540", - "open_interest": "7978.0", - "volume": "411.36", - "strike": "4500.0000", + "bid": "0.0965", + "ask": "0.098", + "open_interest": "13628", + "volume": "0", + "strike": "2000", "maturity": "2026-09-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.5599858", + "iv_ask": "0.5668179", + "inverse": true }, { - "bid": "0.7050", - "ask": "0.7395", - "open_interest": "6.0", - "volume": "0.0", - "strike": "4500.0000", + "bid": "0.119", + "ask": "0.1205", + "open_interest": "3247", + "volume": "2010.72", + "strike": "2100", "maturity": "2026-09-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5536079", + "iv_ask": "0.5601455", + "inverse": true }, { - "bid": "0.0480", - "ask": "0.0505", - "open_interest": "2103.0", - "volume": "0.0", - "strike": "4600.0000", + "bid": "0.1165", + "ask": "0.118", + "open_interest": "3464", + "volume": "0", + "strike": "2200", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5465174", + "iv_ask": "0.5529136", + "inverse": true }, { - "bid": "0.7390", - "ask": "0.7690", - "open_interest": "39.0", - "volume": "0.0", - "strike": "4600.0000", + "bid": "0.098", + "ask": "0.0995", + "open_interest": "2400", + "volume": "0", + "strike": "2300", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5426376", + "iv_ask": "0.5490261", + "inverse": true }, { - "bid": "0.0420", - "ask": "0.0445", - "open_interest": "838.0", - "volume": "0.0", - "strike": "4800.0000", + "bid": "0.082", + "ask": "0.083", + "open_interest": "3795", + "volume": "2139.24", + "strike": "2400", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5393378", + "iv_ask": "0.5436726", + "inverse": true }, { - "bid": "0.8055", - "ask": "0.8425", - "open_interest": "50.0", - "volume": "0.0", - "strike": "4800.0000", + "bid": "0.0685", + "ask": "0.0695", + "open_interest": "3610", + "volume": "0", + "strike": "2500", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5375009", + "iv_ask": "0.5419861", + "inverse": true }, { - "bid": "0.0370", - "ask": "0.0390", - "open_interest": "3704.0", - "volume": "618.41", - "strike": "5000.0000", + "bid": "0.0565", + "ask": "0.0575", + "open_interest": "901", + "volume": "0", + "strike": "2600", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.533579", + "iv_ask": "0.5382963", + "inverse": true }, { - "bid": "0.8740", - "ask": "0.9075", - "open_interest": "1.0", - "volume": "0.0", - "strike": "5000.0000", + "bid": "0.047", + "ask": "0.048", + "open_interest": "4378", + "volume": "0", + "strike": "2700", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5332688", + "iv_ask": "0.5382867", + "inverse": true }, { - "bid": "0.0325", - "ask": "0.0345", - "open_interest": "1227.0", - "volume": "89.67", - "strike": "5200.0000", + "bid": "0.0385", + "ask": "0.04", + "open_interest": "4658", + "volume": "709.17", + "strike": "2800", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5303505", + "iv_ask": "0.5384613", + "inverse": true }, { - "bid": "0.9435", - "ask": "0.9775", - "open_interest": "3.0", - "volume": "0.0", - "strike": "5200.0000", + "bid": "0.032", + "ask": "0.0335", + "open_interest": "847", + "volume": "436.44", + "strike": "2900", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5312178", + "iv_ask": "0.5400166", + "inverse": true }, { - "bid": "0.0285", - "ask": "0.0310", - "open_interest": "654.0", - "volume": "0.0", - "strike": "5400.0000", + "bid": "0.0265", + "ask": "0.028", + "open_interest": "12969", + "volume": "19240.46", + "strike": "3000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5316631", + "iv_ask": "0.5412916", + "inverse": true }, { - "bid": "1.0135", - "ask": "1.0485", - "open_interest": "4.0", - "volume": "0.0", - "strike": "5400.0000", + "bid": "0.022", + "ask": "0.0235", + "open_interest": "1514", + "volume": "1175.31", + "strike": "3100", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.532715", + "iv_ask": "0.54331", + "inverse": true }, { - "bid": "0.0270", - "ask": "0.0290", - "open_interest": "1582.0", - "volume": "0.0", - "strike": "5500.0000", + "bid": "0.0185", + "ask": "0.0195", + "open_interest": "7332", + "volume": "44.96", + "strike": "3200", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5357009", + "iv_ask": "0.5435163", + "inverse": true }, { - "bid": "1.0485", - "ask": "1.0840", - "open_interest": "1.0", - "volume": "0.0", - "strike": "5500.0000", + "bid": "0.0155", + "ask": "0.0165", + "open_interest": "16784", + "volume": "74.88", + "strike": "3300", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5379423", + "iv_ask": "0.5466011", + "inverse": true }, { - "bid": "0.0255", - "ask": "0.0275", - "open_interest": "1433.0", - "volume": "0.0", - "strike": "5600.0000", + "bid": "0.013", + "ask": "0.014", + "open_interest": "6600", + "volume": "220.32", + "strike": "3400", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5401509", + "iv_ask": "0.5497736", + "inverse": true }, { - "bid": "0.0225", - "ask": "0.0245", - "open_interest": "4908.0", - "volume": "0.0", - "strike": "5800.0000", + "bid": "0.011", + "ask": "0.012", + "open_interest": "13304", + "volume": "24.6", + "strike": "3500", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5432431", + "iv_ask": "0.5539222", + "inverse": true }, { - "bid": "0.0205", - "ask": "0.0220", - "open_interest": "2308.0", - "volume": "0.0", - "strike": "6000.0000", + "bid": "0.009", + "ask": "0.0105", + "open_interest": "2399", + "volume": "44.82", + "strike": "3600", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5422601", + "iv_ask": "0.5601402", + "inverse": true }, { - "bid": "0.0180", - "ask": "0.0200", - "open_interest": "1754.0", - "volume": "0.0", - "strike": "6200.0000", + "bid": "0.0065", + "ask": "0.0075", + "open_interest": "1604", + "volume": "28.72", + "strike": "3800", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5483802", + "iv_ask": "0.5633361", + "inverse": true }, { - "bid": "0.0165", - "ask": "0.0180", - "open_interest": "1129.0", - "volume": "0.0", - "strike": "6400.0000", + "bid": "0.005", + "ask": "0.006", + "open_interest": "12096", + "volume": "12.84", + "strike": "4000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5593478", + "iv_ask": "0.5772925", + "inverse": true }, { - "bid": "0.0155", - "ask": "0.0175", - "open_interest": "1057.0", - "volume": "0.0", - "strike": "6500.0000", + "bid": "0.0037", + "ask": "0.0043", + "open_interest": "2339", + "volume": "8.51", + "strike": "4200", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5651866", + "iv_ask": "0.5787662", + "inverse": true }, { - "bid": "0.0150", - "ask": "0.0165", - "open_interest": "828.0", - "volume": "0.0", - "strike": "6600.0000", + "bid": "0.0029", + "ask": "0.0034", + "open_interest": "4414", + "volume": "0", + "strike": "4400", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5748154", + "iv_ask": "0.5884599", + "inverse": true }, { - "bid": "0.0135", - "ask": "0.0150", - "open_interest": "756.0", - "volume": "0.0", - "strike": "6800.0000", + "bid": "0.0025", + "ask": "0.003", + "open_interest": "8560", + "volume": "32.08", + "strike": "4500", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.576961", + "iv_ask": "0.5921213", + "inverse": true }, { - "bid": "0.0120", - "ask": "0.0140", - "open_interest": "1165.0", - "volume": "0.0", - "strike": "7000.0000", + "bid": "0.0022", + "ask": "0.0027", + "open_interest": "3051", + "volume": "0", + "strike": "4600", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5805351", + "iv_ask": "0.5971678", + "inverse": true }, { - "bid": "0.0110", - "ask": "0.0130", - "open_interest": "594.0", - "volume": "0.0", - "strike": "7200.0000", + "bid": "0.0017", + "ask": "0.0022", + "open_interest": "1809", + "volume": "0", + "strike": "4800", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5867671", + "iv_ask": "0.6067807", + "inverse": true }, { - "bid": "0.0105", - "ask": "0.0120", - "open_interest": "735.0", - "volume": "155.06", - "strike": "7400.0000", + "bid": "0.0015", + "ask": "0.0018", + "open_interest": "3853", + "volume": "0", + "strike": "5000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6017274", + "iv_ask": "0.6155559", + "inverse": true }, { - "bid": "0.0100", - "ask": "0.0115", - "open_interest": "788.0", - "volume": "0.0", - "strike": "7500.0000", + "bid": "0.0011", + "ask": "0.0016", + "open_interest": "1598", + "volume": "0", + "strike": "5200", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6022607", + "iv_ask": "0.629629", + "inverse": true }, { - "bid": "0.0080", - "ask": "0.0095", - "open_interest": "1160.0", - "volume": "1745.22", - "strike": "8000.0000", + "bid": "0.0008", + "ask": "0.0013", + "open_interest": "1174", + "volume": "0", + "strike": "5400", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6020513", + "iv_ask": "0.6357591", + "inverse": true }, { - "bid": "0.0065", - "ask": "0.0080", - "open_interest": "1256.0", - "volume": "0.0", - "strike": "8500.0000", + "bid": "0.0007", + "ask": "0.0012", + "open_interest": "1487", + "volume": "0", + "strike": "5500", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6035237", + "iv_ask": "0.6402875", + "inverse": true }, { - "bid": "0.0055", - "ask": "0.0070", - "open_interest": "1533.0", - "volume": "0.0", - "strike": "9000.0000", + "bid": "0.0006", + "ask": "0.0012", + "open_interest": "1878", + "volume": "11.73", + "strike": "5600", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6036121", + "iv_ask": "0.6504761", + "inverse": true }, { - "bid": "0.0041", - "ask": "0.0050", - "open_interest": "1583.0", - "volume": "979.95", - "strike": "10000.0000", + "bid": "0.0006", + "ask": "0.001", + "open_interest": "4884", + "volume": "0", + "strike": "5800", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6223255", + "iv_ask": "0.6567333", + "inverse": true }, { - "bid": "0.0035", - "ask": "0.0039", - "open_interest": "2666.0", - "volume": "0.0", - "strike": "11000.0000", + "bid": "0.0005", + "ask": "0.0009", + "open_interest": "3264", + "volume": "0", + "strike": "6000", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.628857", + "iv_ask": "0.6677812", + "inverse": true }, { - "bid": "0.0024", - "ask": "0.0031", - "open_interest": "1348.0", - "volume": "0.0", - "strike": "12000.0000", + "bid": "0.0004", + "ask": "0.0008", + "open_interest": "2025", + "volume": "0", + "strike": "6200", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.632339", + "iv_ask": "0.6771633", + "inverse": true }, { - "bid": "0.0019", - "ask": "0.0025", - "open_interest": "1664.0", - "volume": "0.0", - "strike": "13000.0000", + "bid": "0.0004", + "ask": "0.0007", + "open_interest": "1915", + "volume": "0", + "strike": "6400", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6485274", + "iv_ask": "0.6847523", + "inverse": true }, { - "bid": "0.0014", - "ask": "0.0020", - "open_interest": "1168.0", - "volume": "0.0", - "strike": "14000.0000", + "bid": "0.0003", + "ask": "0.0007", + "open_interest": "1334", + "volume": "0", + "strike": "6500", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6394638", + "iv_ask": "0.6929517", + "inverse": true }, { - "bid": "0.0011", - "ask": "0.0017", - "open_interest": "831.0", - "volume": "0.0", - "strike": "15000.0000", + "bid": "0.0003", + "ask": "0.0006", + "open_interest": "1420", + "volume": "0", + "strike": "6600", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6470546", + "iv_ask": "0.6903349", + "inverse": true }, { - "bid": "0.0009", - "ask": "0.0012", - "open_interest": "3210.0", - "volume": "0.0", - "strike": "16000.0000", + "bid": "0.0002", + "ask": "0.0006", + "open_interest": "1467", + "volume": "0", + "strike": "6800", "maturity": "2026-09-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6392119", + "iv_ask": "0.7058581", + "inverse": true }, { - "bid": "4.8520", - "ask": "5.0155", - "open_interest": "30.0", - "volume": "0.0", - "strike": "16000.0000", + "bid": "0.0002", + "ask": "0.0005", + "open_interest": "2039", + "volume": "0", + "strike": "7000", "maturity": "2026-09-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.6531505", + "iv_ask": "0.7083764", + "inverse": true }, { - "bid": "2725.25", - "ask": "2727.25", - "open_interest": "6370628", - "volume": "1321880.0", + "bid": "2160", + "ask": "2160.5", + "open_interest": "29083389", + "volume": "754779", "maturity": "2026-12-25T08:00:00Z", "security_type": "forward" }, { - "bid": "0.5195", - "ask": "0.5315", - "open_interest": "10.0", - "volume": "14001.87", - "strike": "1400.0000", + "bid": "0.008", + "ask": "0.009", + "open_interest": "1361", + "volume": "804.5", + "strike": "800", "maturity": "2026-12-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7945755", + "iv_ask": "0.8146921", + "inverse": true }, { - "bid": "0.0370", - "ask": "0.0405", - "open_interest": "1067.0", - "volume": "488.15", - "strike": "1400.0000", + "bid": "0.015", + "ask": "0.016", + "open_interest": "18980", + "volume": "130.15", + "strike": "1000", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.7338959", + "iv_ask": "0.7462375", + "inverse": true }, { - "bid": "0.0545", - "ask": "0.0580", - "open_interest": "87.0", - "volume": "0.0", - "strike": "1600.0000", + "bid": "0.0255", + "ask": "0.0265", + "open_interest": "2596", + "volume": "383.78", + "strike": "1200", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6826406", + "iv_ask": "0.6909914", + "inverse": true }, { - "bid": "0.0760", - "ask": "0.0800", - "open_interest": "271.0", - "volume": "0.0", - "strike": "1800.0000", + "bid": "0.042", + "ask": "0.043", + "open_interest": "10731", + "volume": "79690.2", + "strike": "1400", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6476807", + "iv_ask": "0.6537289", + "inverse": true }, { - "bid": "0.3645", - "ask": "0.3790", - "open_interest": "84.0", - "volume": "0.0", - "strike": "2000.0000", + "bid": "0.0525", + "ask": "0.054", + "open_interest": "3131", + "volume": "1090.83", + "strike": "1500", "maturity": "2026-12-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6318968", + "iv_ask": "0.639842", + "inverse": true }, { - "bid": "0.1025", - "ask": "0.1065", - "open_interest": "1337.0", - "volume": "11678.26", - "strike": "2000.0000", + "bid": "0.0655", + "ask": "0.067", + "open_interest": "4606", + "volume": "14030.87", + "strike": "1600", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.62109", + "iv_ask": "0.6281672", + "inverse": true }, { - "bid": "0.1335", - "ask": "0.1385", - "open_interest": "96.0", - "volume": "6584.22", - "strike": "2200.0000", + "bid": "0.08", + "ask": "0.082", + "open_interest": "16450", + "volume": "37730.74", + "strike": "1700", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6093218", + "iv_ask": "0.6178828", + "inverse": true }, { - "bid": "0.2835", - "ask": "0.2995", - "open_interest": "538.0", - "volume": "0.0", - "strike": "2400.0000", + "bid": "0.097", + "ask": "0.099", + "open_interest": "3857", + "volume": "0", + "strike": "1800", "maturity": "2026-12-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6006469", + "iv_ask": "0.6085412", + "inverse": true }, { - "bid": "0.1695", - "ask": "0.1745", - "open_interest": "939.0", - "volume": "107315.86", - "strike": "2400.0000", + "bid": "0.117", + "ask": "0.1185", + "open_interest": "2673", + "volume": "0", + "strike": "1900", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5962189", + "iv_ask": "0.6017638", + "inverse": true }, { - "bid": "0.2670", - "ask": "0.2835", - "open_interest": "3.0", - "volume": "783.61", - "strike": "2500.0000", + "bid": "0.138", + "ask": "0.1405", + "open_interest": "6813", + "volume": "0", + "strike": "2000", "maturity": "2026-12-25T08:00:00Z", - "option_type": "call", - "security_type": "option" + "option_type": "put", + "security_type": "option", + "iv_bid": "0.588404", + "iv_ask": "0.5971883", + "inverse": true }, { - "bid": "0.1890", - "ask": "0.1945", - "open_interest": "260.0", - "volume": "0.0", - "strike": "2500.0000", + "bid": "0.1625", + "ask": "0.1645", + "open_interest": "1926", + "volume": "0", + "strike": "2100", "maturity": "2026-12-25T08:00:00Z", "option_type": "put", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5862439", + "iv_ask": "0.5930151", + "inverse": true }, { - "bid": "0.2560", - "ask": "0.2625", - "open_interest": "64.0", - "volume": "45237.74", - "strike": "2600.0000", + "bid": "0.17", + "ask": "0.1725", + "open_interest": "50826", + "volume": "0", + "strike": "2200", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5790226", + "iv_ask": "0.5872761", + "inverse": true }, { - "bid": "0.2095", - "ask": "0.2150", - "open_interest": "615.0", - "volume": "5599.48", - "strike": "2600.0000", + "bid": "0.1515", + "ask": "0.154", + "open_interest": "1964", + "volume": "1646.38", + "strike": "2300", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5756262", + "iv_ask": "0.5837668", + "inverse": true }, { - "bid": "0.2265", - "ask": "0.2320", - "open_interest": "332.0", - "volume": "162390.14", - "strike": "2800.0000", + "bid": "0.135", + "ask": "0.1375", + "open_interest": "12377", + "volume": "35878.39", + "strike": "2400", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5732938", + "iv_ask": "0.5814046", + "inverse": true }, { - "bid": "0.2530", - "ask": "0.2590", - "open_interest": "1932.0", - "volume": "6361.06", - "strike": "2800.0000", + "bid": "0.12", + "ask": "0.1225", + "open_interest": "21353", + "volume": "0", + "strike": "2500", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5707483", + "iv_ask": "0.578903", + "inverse": true }, { - "bid": "0.2005", - "ask": "0.2055", - "open_interest": "605.0", - "volume": "1179.5", - "strike": "3000.0000", + "bid": "0.107", + "ask": "0.109", + "open_interest": "1458", + "volume": "0", + "strike": "2600", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5700075", + "iv_ask": "0.5766196", + "inverse": true }, { - "bid": "0.2915", - "ask": "0.3105", - "open_interest": "696.0", - "volume": "765.67", - "strike": "3000.0000", + "bid": "0.095", + "ask": "0.097", + "open_interest": "1566", + "volume": "35906.25", + "strike": "2700", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5681783", + "iv_ask": "0.5749294", + "inverse": true }, { - "bid": "0.1770", - "ask": "0.1825", - "open_interest": "673.0", - "volume": "51633.27", - "strike": "3200.0000", + "bid": "0.0845", + "ask": "0.0865", + "open_interest": "3779", + "volume": "0", + "strike": "2800", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5673101", + "iv_ask": "0.574246", + "inverse": true }, { - "bid": "0.3410", - "ask": "0.3640", - "open_interest": "209.0", - "volume": "10301.53", - "strike": "3200.0000", + "bid": "0.075", + "ask": "0.077", + "open_interest": "347", + "volume": "0", + "strike": "2900", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5660836", + "iv_ask": "0.5732523", + "inverse": true }, { - "bid": "0.1570", - "ask": "0.1620", - "open_interest": "751.0", - "volume": "14029.57", - "strike": "3400.0000", + "bid": "0.067", + "ask": "0.0685", + "open_interest": "3033", + "volume": "1308.65", + "strike": "3000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5667078", + "iv_ask": "0.5722898", + "inverse": true }, { - "bid": "0.3935", - "ask": "0.4175", - "open_interest": "34.0", - "volume": "28891.61", - "strike": "3400.0000", + "bid": "0.0595", + "ask": "0.061", + "open_interest": "1250", + "volume": "0", + "strike": "3100", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5659009", + "iv_ask": "0.5717257", + "inverse": true }, { - "bid": "0.1390", - "ask": "0.1445", - "open_interest": "687.0", - "volume": "0.0", - "strike": "3600.0000", + "bid": "0.053", + "ask": "0.0545", + "open_interest": "67659", + "volume": "5617.39", + "strike": "3200", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5658704", + "iv_ask": "0.571968", + "inverse": true }, { - "bid": "0.4480", - "ask": "0.4735", - "open_interest": "92.0", - "volume": "29370.19", - "strike": "3600.0000", + "bid": "0.0475", + "ask": "0.049", + "open_interest": "1315", + "volume": "0", + "strike": "3300", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.567085", + "iv_ask": "0.5734785", + "inverse": true }, { - "bid": "0.1235", - "ask": "0.1290", - "open_interest": "321.0", - "volume": "0.0", - "strike": "3800.0000", + "bid": "0.0425", + "ask": "0.044", + "open_interest": "11271", + "volume": "0", + "strike": "3400", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5678254", + "iv_ask": "0.5745518", + "inverse": true }, { - "bid": "0.5050", - "ask": "0.5325", - "open_interest": "36.0", - "volume": "0.0", - "strike": "3800.0000", + "bid": "0.038", + "ask": "0.0395", + "open_interest": "23299", + "volume": "0", + "strike": "3500", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5683342", + "iv_ask": "0.5754312", + "inverse": true }, { - "bid": "0.1120", - "ask": "0.1140", - "open_interest": "4003.0", - "volume": "353734.29", - "strike": "4000.0000", + "bid": "0.034", + "ask": "0.0355", + "open_interest": "1734", + "volume": "0", + "strike": "3600", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5688766", + "iv_ask": "0.5763805", + "inverse": true }, { - "bid": "0.5640", - "ask": "0.5930", - "open_interest": "226.0", - "volume": "0.0", - "strike": "4000.0000", + "bid": "0.0275", + "ask": "0.029", + "open_interest": "2264", + "volume": "0", + "strike": "3800", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5712907", + "iv_ask": "0.5796956", + "inverse": true }, { - "bid": "0.0990", - "ask": "0.1035", - "open_interest": "435.0", - "volume": "65657.7", - "strike": "4200.0000", + "bid": "0.0225", + "ask": "0.0235", + "open_interest": "16570", + "volume": "1008.95", + "strike": "4000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5747596", + "iv_ask": "0.5810737", + "inverse": true }, { - "bid": "0.6250", - "ask": "0.6550", - "open_interest": "60.0", - "volume": "0.0", - "strike": "4200.0000", + "bid": "0.018", + "ask": "0.0195", + "open_interest": "1747", + "volume": "0", + "strike": "4200", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5745246", + "iv_ask": "0.5852541", + "inverse": true }, { - "bid": "0.0885", - "ask": "0.0930", - "open_interest": "277.0", - "volume": "58929.81", - "strike": "4400.0000", + "bid": "0.015", + "ask": "0.016", + "open_interest": "1880", + "volume": "65.95", + "strike": "4400", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5791185", + "iv_ask": "0.587201", + "inverse": true }, { - "bid": "0.0840", - "ask": "0.0885", - "open_interest": "37.0", - "volume": "7340.47", - "strike": "4500.0000", + "bid": "0.0135", + "ask": "0.015", + "open_interest": "1487", + "volume": "59.57", + "strike": "4500", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.579414", + "iv_ask": "0.5922468", + "inverse": true }, { - "bid": "0.0795", - "ask": "0.0840", - "open_interest": "186.0", - "volume": "0.0", - "strike": "4600.0000", + "bid": "0.0125", + "ask": "0.0135", + "open_interest": "8781", + "volume": "171.74", + "strike": "4600", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5829142", + "iv_ask": "0.5920095", + "inverse": true }, { - "bid": "0.0720", - "ask": "0.0760", - "open_interest": "175.0", - "volume": "0.0", - "strike": "4800.0000", + "bid": "0.0105", + "ask": "0.0115", + "open_interest": "1974", + "volume": "48.02", + "strike": "4800", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5869198", + "iv_ask": "0.597131", + "inverse": true }, { - "bid": "0.8155", - "ask": "0.8500", - "open_interest": "25.0", - "volume": "0.0", - "strike": "4800.0000", + "bid": "0.0085", + "ask": "0.0095", + "open_interest": "3576", + "volume": "38.35", + "strike": "5000", "maturity": "2026-12-25T08:00:00Z", - "option_type": "put", - "security_type": "option" + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5864419", + "iv_ask": "0.5981591", + "inverse": true }, { - "bid": "0.0650", - "ask": "0.0690", - "open_interest": "2453.0", - "volume": "43834.3", - "strike": "5000.0000", + "bid": "0.0075", + "ask": "0.0085", + "open_interest": "8537", + "volume": "17.02", + "strike": "5200", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5942999", + "iv_ask": "0.6071286", + "inverse": true }, { - "bid": "0.0590", - "ask": "0.0625", - "open_interest": "132.0", - "volume": "0.0", - "strike": "5200.0000", + "bid": "0.006", + "ask": "0.0065", + "open_interest": "1536", + "volume": "0", + "strike": "5500", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6011605", + "iv_ask": "0.6088364", + "inverse": true }, { - "bid": "0.0510", - "ask": "0.0545", - "open_interest": "906.0", - "volume": "7477.65", - "strike": "5500.0000", + "bid": "0.0041", + "ask": "0.0048", + "open_interest": "10719", + "volume": "0", + "strike": "6000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6091383", + "iv_ask": "0.623092", + "inverse": true }, { - "bid": "0.0405", - "ask": "0.0440", - "open_interest": "3130.0", - "volume": "483.5", - "strike": "6000.0000", + "bid": "0.0032", + "ask": "0.0036", + "open_interest": "1972", + "volume": "0", + "strike": "6500", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6254197", + "iv_ask": "0.6353199", + "inverse": true }, { - "bid": "0.0330", - "ask": "0.0360", - "open_interest": "677.0", - "volume": "386.84", - "strike": "6500.0000", + "bid": "0.0022", + "ask": "0.0027", + "open_interest": "2889", + "volume": "24.5", + "strike": "7000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6287991", + "iv_ask": "0.6448487", + "inverse": true }, { - "bid": "0.0270", - "ask": "0.0300", - "open_interest": "1606.0", - "volume": "0.0", - "strike": "7000.0000", + "bid": "0.0015", + "ask": "0.0018", + "open_interest": "2476", + "volume": "0", + "strike": "8000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6571531", + "iv_ask": "0.6706568", + "inverse": true }, { - "bid": "0.0185", - "ask": "0.0210", - "open_interest": "1134.0", - "volume": "279.34", - "strike": "8000.0000", + "bid": "0.001", + "ask": "0.0013", + "open_interest": "1916", + "volume": "0", + "strike": "9000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6763852", + "iv_ask": "0.6948657", + "inverse": true }, { - "bid": "0.0135", - "ask": "0.0160", - "open_interest": "1369.0", - "volume": "18148.43", - "strike": "9000.0000", + "bid": "0.0005", + "ask": "0.0007", + "open_interest": "1688", + "volume": "0", + "strike": "10000", "maturity": "2026-12-25T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.6725556", + "iv_ask": "0.6935471", + "inverse": true }, { - "bid": "0.0100", - "ask": "0.0120", - "open_interest": "1205.0", - "volume": "1085.31", - "strike": "10000.0000", - "maturity": "2026-12-25T08:00:00Z", + "bid": "2181.75", + "ask": "2182.75", + "open_interest": "8510750", + "volume": "568965", + "maturity": "2027-03-26T08:00:00Z", + "security_type": "forward" + }, + { + "bid": "0.0245", + "ask": "0.0255", + "open_interest": "4438", + "volume": "52.52", + "strike": "1000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.7138243", + "iv_ask": "0.7223943", + "inverse": true + }, + { + "bid": "0.061", + "ask": "0.063", + "open_interest": "2206", + "volume": "1459.39", + "strike": "1400", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.646589", + "iv_ask": "0.655977", + "inverse": true + }, + { + "bid": "0.0745", + "ask": "0.0765", + "open_interest": "1287", + "volume": "162.48", + "strike": "1500", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.636517", + "iv_ask": "0.6449409", + "inverse": true + }, + { + "bid": "0.09", + "ask": "0.092", + "open_interest": "902", + "volume": "0", + "strike": "1600", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6285263", + "iv_ask": "0.6361975", + "inverse": true + }, + { + "bid": "0.107", + "ask": "0.1095", + "open_interest": "1155", + "volume": "0", + "strike": "1700", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.620378", + "iv_ask": "0.6292343", + "inverse": true + }, + { + "bid": "0.127", + "ask": "0.129", + "open_interest": "1279", + "volume": "0", + "strike": "1800", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6171413", + "iv_ask": "0.623768", + "inverse": true + }, + { + "bid": "0.148", + "ask": "0.1505", + "open_interest": "698", + "volume": "0", + "strike": "1900", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6118249", + "iv_ask": "0.6196657", + "inverse": true + }, + { + "bid": "0.1705", + "ask": "0.1735", + "open_interest": "973", + "volume": "0", + "strike": "2000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6063969", + "iv_ask": "0.6153975", + "inverse": true + }, + { + "bid": "0.195", + "ask": "0.1985", + "open_interest": "340", + "volume": "414.24", + "strike": "2100", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "put", + "security_type": "option", + "iv_bid": "0.6024715", + "iv_ask": "0.6126137", + "inverse": true + }, + { + "bid": "0.2135", + "ask": "0.2165", + "open_interest": "1447", + "volume": "1845.09", + "strike": "2200", + "maturity": "2027-03-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5961066", + "iv_ask": "0.6045711", + "inverse": true }, { - "bid": "0.0075", - "ask": "0.0095", - "open_interest": "346.0", - "volume": "0.0", - "strike": "11000.0000", - "maturity": "2026-12-25T08:00:00Z", + "bid": "0.196", + "ask": "0.1985", + "open_interest": "167", + "volume": "0", + "strike": "2300", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5947617", + "iv_ask": "0.6016878", + "inverse": true + }, + { + "bid": "0.1795", + "ask": "0.182", + "open_interest": "211", + "volume": "0", + "strike": "2400", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5924796", + "iv_ask": "0.5993279", + "inverse": true + }, + { + "bid": "0.1645", + "ask": "0.167", + "open_interest": "457", + "volume": "49467.79", + "strike": "2500", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.590875", + "iv_ask": "0.5976893", + "inverse": true + }, + { + "bid": "0.1505", + "ask": "0.153", + "open_interest": "804", + "volume": "0", + "strike": "2600", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.588801", + "iv_ask": "0.5956201", + "inverse": true + }, + { + "bid": "0.138", + "ask": "0.1405", + "open_interest": "650", + "volume": "589.36", + "strike": "2700", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5878419", + "iv_ask": "0.5947001", + "inverse": true + }, + { + "bid": "0.127", + "ask": "0.1285", + "open_interest": "448", + "volume": "275.55", + "strike": "2800", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5882395", + "iv_ask": "0.5923968", + "inverse": true + }, + { + "bid": "0.1165", + "ask": "0.118", + "open_interest": "903", + "volume": "0", + "strike": "2900", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5874531", + "iv_ask": "0.5916702", + "inverse": true + }, + { + "bid": "0.1065", + "ask": "0.109", + "open_interest": "608", + "volume": "11743.45", + "strike": "3000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5856363", + "iv_ask": "0.5927907", + "inverse": true + }, + { + "bid": "0.098", + "ask": "0.1", + "open_interest": "355", + "volume": "21056.11", + "strike": "3100", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5858469", + "iv_ask": "0.5916921", + "inverse": true + }, + { + "bid": "0.09", + "ask": "0.092", + "open_interest": "299", + "volume": "0", + "strike": "3200", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5854157", + "iv_ask": "0.5914024", + "inverse": true + }, + { + "bid": "0.083", + "ask": "0.085", + "open_interest": "406", + "volume": "0", + "strike": "3300", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5860171", + "iv_ask": "0.5921597", + "inverse": true + }, + { + "bid": "0.0765", + "ask": "0.078", + "open_interest": "42", + "volume": "166.61", + "strike": "3400", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5863307", + "iv_ask": "0.5910724", + "inverse": true + }, + { + "bid": "0.0705", + "ask": "0.072", + "open_interest": "408", + "volume": "1218.95", + "strike": "3500", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5864985", + "iv_ask": "0.5913863", + "inverse": true + }, + { + "bid": "0.065", + "ask": "0.0665", + "open_interest": "786", + "volume": "140.59", + "strike": "3600", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5866652", + "iv_ask": "0.5917126", + "inverse": true + }, + { + "bid": "0.06", + "ask": "0.062", + "open_interest": "490", + "volume": "130.06", + "strike": "3700", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5869811", + "iv_ask": "0.5939316", + "inverse": true + }, + { + "bid": "0.0555", + "ask": "0.057", + "open_interest": "759", + "volume": "0", + "strike": "3800", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.587605", + "iv_ask": "0.5930073", + "inverse": true + }, + { + "bid": "0.0475", + "ask": "0.049", + "open_interest": "1290", + "volume": "0", + "strike": "4000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5885202", + "iv_ask": "0.5943306", + "inverse": true + }, + { + "bid": "0.041", + "ask": "0.0425", + "open_interest": "1643", + "volume": "88.77", + "strike": "4200", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5904635", + "iv_ask": "0.5967217", + "inverse": true + }, + { + "bid": "0.0355", + "ask": "0.0365", + "open_interest": "848", + "volume": "78.05", + "strike": "4400", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5923637", + "iv_ask": "0.5968813", + "inverse": true + }, + { + "bid": "0.033", + "ask": "0.0345", + "open_interest": "1452", + "volume": "0", + "strike": "4500", + "maturity": "2027-03-26T08:00:00Z", "option_type": "call", - "security_type": "option" + "security_type": "option", + "iv_bid": "0.5929603", + "iv_ask": "0.599992", + "inverse": true + }, + { + "bid": "0.0305", + "ask": "0.032", + "open_interest": "506", + "volume": "0", + "strike": "4600", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5925813", + "iv_ask": "0.5999234", + "inverse": true + }, + { + "bid": "0.027", + "ask": "0.028", + "open_interest": "581", + "volume": "0", + "strike": "4800", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.5967907", + "iv_ask": "0.6020745", + "inverse": true + }, + { + "bid": "0.0235", + "ask": "0.025", + "open_interest": "803", + "volume": "0", + "strike": "5000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.598008", + "iv_ask": "0.6065738", + "inverse": true + }, + { + "bid": "0.017", + "ask": "0.0185", + "open_interest": "1124", + "volume": "230.97", + "strike": "5500", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.6024288", + "iv_ask": "0.6128944", + "inverse": true + }, + { + "bid": "0.0125", + "ask": "0.0135", + "open_interest": "1779", + "volume": "27.71", + "strike": "6000", + "maturity": "2027-03-26T08:00:00Z", + "option_type": "call", + "security_type": "option", + "iv_bid": "0.6065562", + "iv_ask": "0.6151545", + "inverse": true } ] } \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 30f9622..ac3ec2b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -101,6 +101,7 @@ nav: - api/rates/index.md - Interest Rate: api/rates/interest_rate.md - Nelson Siegel Curve: api/rates/nelson_siegel.md + - Options Discounting: api/rates/options.md - Yield Curve: api/rates/yield_curve.md - Utilities: - api/utils/index.md diff --git a/pyproject.toml b/pyproject.toml index 3ad9c49..4147c1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,8 +122,13 @@ exclude_also = [ [tool.isort] profile = "black" +skip_glob = [ "app/frontend/*" ] + +[tool.black] +extend-exclude = "/app/frontend/" [tool.ruff] +exclude = [ "app/frontend" ] lint.select = [ "E", "F" ] line-length = 88 @@ -134,7 +139,9 @@ path = "quantflow/__init__.py" # strict = true disallow_untyped_calls = true disallow_untyped_defs = true +exclude = [ "^app/frontend/" ] warn_no_return = true +disable_error_code = [ "prop-decorator" ] [[tool.mypy.overrides]] module = [ diff --git a/quantflow/data/deribit.py b/quantflow/data/deribit.py index 536e641..b358797 100644 --- a/quantflow/data/deribit.py +++ b/quantflow/data/deribit.py @@ -145,9 +145,6 @@ async def volatility_surface_loader( exclude_volume: Annotated[ Number | None, Doc("Exclude options with volume below this threshold") ] = None, - use_perp: Annotated[ - bool, Doc("Whether to use perpetual as futures proxies") - ] = False, ) -> VolSurfaceLoader: """Create a [VolSurfaceLoader][quantflow.options.surface.VolSurfaceLoader] for a given crypto-currency""" @@ -177,24 +174,9 @@ async def volatility_surface_loader( instruments = await self.get_instruments(currency="usdc", base=currency) instrument_map = {i["instrument_name"]: i for i in instruments} min_tick_size = Decimal("inf") - perp_bid_ask: tuple[Any, Any] | None = None - for entry in futures: - name = entry["instrument_name"] - if (meta := instrument_map.get(name)) is None: - continue - if ( - meta["settlement_period"] == "perpetual" - and (bid_ := entry["bid_price"]) - and (ask_ := entry["ask_price"]) - ): - perp_bid_ask = (bid_, ask_) - break - for entry in futures: bid_ = entry["bid_price"] ask_ = entry["ask_price"] - if not (bid_ and ask_) and use_perp and perp_bid_ask is not None: - bid_, ask_ = perp_bid_ask if bid_ and ask_: name = entry["instrument_name"] if (meta := instrument_map.get(name)) is None: diff --git a/quantflow/options/pricer.py b/quantflow/options/pricer.py index d36e818..31ae237 100644 --- a/quantflow/options/pricer.py +++ b/quantflow/options/pricer.py @@ -77,7 +77,7 @@ def parity(self) -> float: and put price for the same strike and maturity""" return 1.0 - float(np.exp(self.log_strike)) - @computed_field # type: ignore [prop-decorator] + @computed_field @property def black(self) -> BlackSensitivities: """Calculate the Black price for the option using the price as time value and diff --git a/quantflow/options/surface.py b/quantflow/options/surface.py index c8d52fb..82e581d 100644 --- a/quantflow/options/surface.py +++ b/quantflow/options/surface.py @@ -1,7 +1,6 @@ from __future__ import annotations import enum -import math import warnings from datetime import datetime, timedelta from decimal import Decimal @@ -13,7 +12,14 @@ from pydantic import BaseModel, Field from typing_extensions import Annotated, Doc -from quantflow.rates import AnyYieldCurve, NoDiscount, Rate, YieldCurve +from quantflow.rates import ( + AnyYieldCurve, + NoDiscount, + Rate, + YieldCurve, + YieldCurveCalibration, +) +from quantflow.rates.options import OptionsDiscountingCalibration from quantflow.utils import plot from quantflow.utils.dates import utcnow from quantflow.utils.numbers import ( @@ -342,6 +348,53 @@ def info_dict(self) -> dict[str, Any]: volume=float(self.volume), ) + def info(self) -> OptionInfo: + """Return a structured [OptionInfo][quantflow.options.surface.OptionInfo] + representation of this option price""" + return OptionInfo( + strike=self.strike, + forward=self.forward, + maturity=self.maturity, + log_strike=to_decimal(self.log_strike), + moneyness=to_decimal(self.log_strike / np.sqrt(self.ttm)), + ttm=to_decimal(self.ttm), + implied_vol=to_decimal(self.implied_vol), + price=self.price_in_forward_space, + price_bp=self.price_bp, + price_quote=self.price_in_quote, + option_type=self.option_type, + side=self.side, + open_interest=self.open_interest, + volume=self.volume, + ) + + +class OptionInfo(BaseModel): + """Structured representation of an option price with all computed fields""" + + strike: DecimalNumber = Field(description="Strike price of the option") + forward: DecimalNumber = Field( + description="Forward price of the underlying at maturity" + ) + maturity: datetime = Field(description="Maturity date of the option") + log_strike: DecimalNumber = Field( + description="Log strike, calculated as log(strike/forward)" + ) + moneyness: DecimalNumber = Field( + description="Standardised moneyness, log(K/F) / sqrt(T)" + ) + ttm: DecimalNumber = Field(description="Time to maturity in years") + implied_vol: DecimalNumber = Field(description="Black implied volatility") + price: DecimalNumber = Field( + description="Option price as a fraction of the forward price" + ) + price_bp: DecimalNumber = Field(description="Option price in basis points") + price_quote: DecimalNumber = Field(description="Option price in quote currency") + option_type: OptionType = Field(description="Option type (call or put)") + side: Side = Field(description="Market side (bid or ask)") + open_interest: DecimalNumber = Field(description="Open interest") + volume: DecimalNumber = Field(description="Volume traded") + class OptionArrays(NamedTuple): """Represents the option data in array form for efficient calculations @@ -792,8 +845,8 @@ def spot_price(self) -> Decimal: def forward(self, maturity: datetime) -> Decimal: """Calculate the implied forward for a given maturity""" ttm = self.day_counter.dcf(self.ref_date, maturity) - df_quote = self.quote_curve.discount_factor(ttm) - df_asset = self.asset_curve.discount_factor(ttm) + df_quote = to_decimal(float(self.quote_curve.discount_factor(ttm))) + df_asset = to_decimal(float(self.asset_curve.discount_factor(ttm))) forward_rate = self.spot_price() * df_asset / df_quote return ( round_to_step(forward_rate, self.tick_size_forwards) @@ -1292,14 +1345,6 @@ def put_call_parities( return PutCallParities.from_parities(parities, spot, ttm) -class VolRates(BaseModel, frozen=True): - """Per-maturity continuously compounded rates fitted from put-call parity.""" - - ttms: list[float] = Field(description="Times to maturity in years") - quote_rates: list[float] = Field(description="Quote continuously compounded rates") - asset_rates: list[float] = Field(description="Asset continuously compounded rates") - - class GenericVolSurfaceLoader(ForwardPricer[S], arbitrary_types_allowed=True): """Helper class to build a volatility surface from a list of securities @@ -1438,17 +1483,19 @@ def calibrate_curves( self, *, quote_curve: Annotated[ - type[YieldCurve] | None, + type[YieldCurve] | YieldCurve | None, Doc( - "YieldCurve type to fit the quote currency discount curve $D_q$ " - "from option prices. When None the current quote_curve is unchanged." + "YieldCurve type or instance to fit the quote currency discount " + "curve $D_q$ from option prices. " + "When None the current quote_curve is unchanged." ), ] = None, asset_curve: Annotated[ - type[YieldCurve] | None, + type[YieldCurve] | YieldCurve | None, Doc( - "YieldCurve type to fit the asset discount curve $D_a$ " - "from option prices. When None the current asset_curve is unchanged." + "YieldCurve type or instance to fit the asset discount curve $D_a$ " + "from option prices. " + "When None the current asset_curve is unchanged." ), ] = None, min_rate_q: Annotated[ @@ -1473,67 +1520,63 @@ def calibrate_curves( Three modes are supported: - Both curves: pass a type for both `quote_curve` and `asset_curve`. + Both curves: pass a curve type or instance for both curves. A single OLS regression per maturity identifies $D_q$ and $D_a$ simultaneously. - Asset only: pass a type for `asset_curve`, leave `quote_curve` as None. + Asset only: pass a curve type or instance for `asset_curve`, leave + `quote_curve` as None. The existing `quote_curve` is treated as known and $D_a$ is solved analytically. - Quote only: pass a type for `quote_curve`, leave `asset_curve` as None. + Quote only: pass a curve type or instance for `quote_curve`, leave + `asset_curve` as None. The existing `asset_curve` is treated as known and $D_q$ is solved analytically. """ - vol_rates = self.collect_rates( - fit_quote_curve=quote_curve is not None, - fit_asset_curve=asset_curve is not None, - min_rate_q=min_rate_q, - min_rate_a=min_rate_a, - max_pairs=max_pairs, + ttm, cp, strikes = self.collect_put_call_parities(max_pairs=max_pairs) + asset_curve_input = ( + self._curve_calibrator(asset_curve) if asset_curve else self.asset_curve ) - if quote_curve is not None: - self.quote_curve = cast( - AnyYieldCurve, - quote_curve.calibrate(vol_rates.ttms, vol_rates.quote_rates), - ) - if asset_curve is not None: - self.asset_curve = cast( - AnyYieldCurve, - asset_curve.calibrate(vol_rates.ttms, vol_rates.asset_rates), - ) + quote_curve_input = ( + self._curve_calibrator(quote_curve) if quote_curve else self.quote_curve + ) + calibration = OptionsDiscountingCalibration( + asset_curve=asset_curve_input, + quote_curve=quote_curve_input, + ttm=ttm, + cp=cp, + strikes=strikes, + ) + calibrated_asset_curve, calibrated_quote_curve = calibration.calibrate() + self.asset_curve = cast(AnyYieldCurve, calibrated_asset_curve) + self.quote_curve = cast(AnyYieldCurve, calibrated_quote_curve) - def collect_rates( + def _curve_calibrator( + self, + curve_type: type[YieldCurve] | YieldCurve, + ) -> YieldCurveCalibration: + curve = ( + curve_type(ref_date=self.ref_date) + if isinstance(curve_type, type) + else curve_type + ) + calibrator = curve.calibrator() + if calibrator is None: + raise ValueError(f"{type(curve).__name__} does not support calibration") + return calibrator + + def collect_put_call_parities( self, *, - fit_quote_curve: Annotated[ - bool, Doc("Whether to fit the quote discount curve $D_q$") - ] = True, - fit_asset_curve: Annotated[ - bool, Doc("Whether to fit the asset discount curve $D_a$") - ] = True, - min_rate_q: Annotated[ - float, - Doc( - "Minimum continuously compounded quote rate." - " Default 0 enforces non-negative quote rates." - ), - ] = 0.0, - min_rate_a: Annotated[ - float, - Doc( - "Minimum continuously compounded asset rate." - " Set negative to allow positive asset carry." - ), - ] = 0.0, max_pairs: Annotated[ int, Doc("Maximum number of put-call pairs to use per maturity") ] = 10, - ) -> VolRates: + ) -> tuple[FloatArray, FloatArray, FloatArray]: """Collect per-maturity continuously compounded rates from put-call parity.""" if not self.spot or self.spot.mid == ZERO: raise ValueError("No spot price provided") spot = self.spot.mid - ttms: list[float] = [] - quote_rates: list[float] = [] - asset_rates: list[float] = [] + ttms: list[FloatArray] = [] + cp: list[FloatArray] = [] + strikes: list[FloatArray] = [] ref_date = self.ref_date for maturity, section in sorted(self.maturities.items()): ttm = self.day_counter.dcf(ref_date, maturity) @@ -1544,28 +1587,19 @@ def collect_rates( ref_date=ref_date, max_pairs=max_pairs, ) - dq = ( - None - if fit_quote_curve - else float(self.quote_curve.discount_factor(ttm)) - ) - da = ( - None - if fit_asset_curve - else float(self.asset_curve.discount_factor(ttm)) - ) - d = parities.fit_discounts( - dq=dq, - da=da, - min_rate_q=min_rate_q, - min_rate_a=min_rate_a, - ) - if d is None: + regressand = parities.regressand() + if not regressand.size: continue - ttms.append(ttm) - quote_rates.append(-math.log(d.quote_discount) / ttm) - asset_rates.append(-math.log(d.asset_discount) / ttm) - return VolRates(ttms=ttms, quote_rates=quote_rates, asset_rates=asset_rates) + ttms.append(np.full(regressand.shape, ttm, dtype=float)) + cp.append(regressand) + strikes.append(parities.regressor()) + if not cp: + raise ValueError("No put-call parity pairs available") + return ( + np.concatenate(ttms), + np.concatenate(cp), + np.concatenate(strikes), + ) class VolSurfaceLoader(GenericVolSurfaceLoader[DefaultVolSecurity]): @@ -1619,7 +1653,11 @@ def surface_from_inputs( """Helper function to build a volatility surface from a [VolSurfaceInputs][quantflow.options.inputs.VolSurfaceInputs] instance """ - loader = VolSurfaceLoader() + loader = VolSurfaceLoader( + asset=inputs.asset, + quote_curve=inputs.quote_curve, + asset_curve=inputs.asset_curve, + ) for input in inputs.inputs: loader.add(input) return loader.surface() diff --git a/quantflow/rates/__init__.py b/quantflow/rates/__init__.py index 6458328..5d83b57 100644 --- a/quantflow/rates/__init__.py +++ b/quantflow/rates/__init__.py @@ -4,11 +4,13 @@ from .interest_rate import Rate from .nelson_siegel import NelsonSiegel +from .options import YieldCurveCalibration from .vasicek import VasicekCurve from .yield_curve import NoDiscount, YieldCurve __all__ = [ "YieldCurve", + "YieldCurveCalibration", "NoDiscount", "NelsonSiegel", "VasicekCurve", @@ -20,3 +22,5 @@ Union[NoDiscount, NelsonSiegel, VasicekCurve], Field(discriminator="curve_type"), ] + +YieldCurve.register_curve_types(NoDiscount, NelsonSiegel, VasicekCurve) diff --git a/quantflow/rates/nelson_siegel.py b/quantflow/rates/nelson_siegel.py index 9a76fcc..f32d564 100644 --- a/quantflow/rates/nelson_siegel.py +++ b/quantflow/rates/nelson_siegel.py @@ -6,11 +6,12 @@ import numpy as np from numpy.typing import ArrayLike from pydantic import Field -from scipy.optimize import minimize_scalar +from scipy.optimize import Bounds, minimize_scalar from typing_extensions import Annotated, Doc, Self -from quantflow.utils.numbers import ONE, Number, to_decimal +from quantflow.utils.types import FloatArray, FloatArrayLike, maybe_float +from .options import YieldCurveCalibration from .yield_curve import YieldCurve @@ -31,22 +32,30 @@ class NelsonSiegel(YieldCurve): $\beta_3$ is the curvature parameter and $\lambda$ is the decay factor. """ - beta1: Decimal = Field(..., description="Level parameter") - beta2: Decimal = Field(..., description="Slope parameter") - beta3: Decimal = Field(..., description="Curvature parameter") - lambda_: Decimal = Field(..., description="Decay factor") curve_type: Literal["nelson_siegel"] = "nelson_siegel" + beta1: Decimal = Field(default=Decimal(0), description="Level parameter") + beta2: Decimal = Field(default=Decimal(0), description="Slope parameter") + beta3: Decimal = Field(default=Decimal(0), description="Curvature parameter") + lambda_: Decimal = Field(default=Decimal(1), description="Decay factor") + + def calibrator(self) -> NelsonSiegelCalibration: + """Return a [NelsonSiegelCalibration][..NelsonSiegelCalibration] wrapping + this curve.""" + return NelsonSiegelCalibration(yield_curve=self) + + def instanteous_forward_rate(self, ttm: FloatArrayLike) -> FloatArrayLike: + b1, b2, b3, lam = ( + float(self.beta1), + float(self.beta2), + float(self.beta3), + float(self.lambda_), + ) + ttm_ = np.maximum(np.asarray(ttm, dtype=float), 0.0) + lt = lam * ttm_ + et = np.exp(-lt) + return maybe_float(b1 + b2 * et + b3 * lt * et) - def instanteous_forward_rate(self, ttm: Number) -> Decimal: - ttmd = to_decimal(ttm) - if ttmd <= 0: - return self.beta1 + self.beta2 - else: - tt = ttmd * self.lambda_ - et = (-tt).exp() - return self.beta1 + self.beta2 * et + self.beta3 * tt * et - - def discount_factor(self, ttm: Number) -> Decimal: + def discount_factor(self, ttm: FloatArrayLike) -> FloatArrayLike: r"""Calculate the discount factor for a given time to maturity. The discount factor is calculated using the formula: @@ -59,15 +68,43 @@ def discount_factor(self, ttm: Number) -> Decimal: - e^{-\lambda \tau}\right) \end{align*} """ - ttmd = to_decimal(ttm) - if ttmd <= 0: - return ONE - else: - tt = ttmd * self.lambda_ - et = (-tt).exp() - ett = (1 - et) / tt - zero_coupon_rate = self.beta1 + self.beta2 * ett + self.beta3 * (ett - et) - return (-zero_coupon_rate * ttmd).exp() + ttma = np.maximum(np.asarray(ttm, dtype=float), 0.0) + tt = ttma * float(self.lambda_) + et = np.exp(-tt) + with np.errstate(divide="ignore", invalid="ignore"): + ett = np.where(tt > 1e-10, (1 - et) / tt, 1.0) + zero_coupon_rate = ( + float(self.beta1) + float(self.beta2) * ett + float(self.beta3) * (ett - et) + ) + df = np.exp(-zero_coupon_rate * ttma) + return maybe_float(df) + + def jacobian(self, ttm: FloatArrayLike) -> FloatArray: + r"""Analytical Jacobian of discount factors w.r.t. params. + + Params order: $[\beta_1, \beta_2, \beta_3, \lambda]$. Shape: (len(ttm), 4). + """ + b2, b3, lam = float(self.beta2), float(self.beta3), float(self.lambda_) + ttma = np.maximum(np.asarray(ttm, dtype=float), 0.0) + lt = lam * ttma + et = np.exp(-lt) + with np.errstate(divide="ignore", invalid="ignore"): + ett = np.where(lt > 1e-10, (1.0 - et) / lt, 1.0 - lt / 2.0) + a_term = ttma * ett + b_term = a_term - ttma * et + zero_rate = float(self.beta1) + b2 * ett + b3 * (ett - et) + df = np.exp(-zero_rate * ttma) + with np.errstate(divide="ignore", invalid="ignore"): + da_dlam = np.where(lam > 1e-10, ttma * et / lam - a_term / lam, 0.0) + db_dlam = da_dlam + ttma**2 * et + return np.column_stack( + [ + -ttma * df, + -a_term * df, + -b_term * df, + -df * (b2 * da_dlam + b3 * db_dlam), + ] + ) @classmethod def calibrate( @@ -89,17 +126,32 @@ def calibrate( Uses a profile OLS approach: for each candidate $\lambda$ the betas are solved exactly via least squares, so only a 1-D scalar minimisation over $\lambda$ is needed. + + Observations whose rates deviate by more than 3 robust standard deviations + (MAD-scaled) from the median are excluded before fitting, making the result + robust to a small number of bad parity observations. """ ttm_arr = np.asarray(ttm, dtype=float) rates_arr = np.asarray(rates, dtype=float) + mask = _mad_filter(rates_arr) + fit_ttm = ttm_arr[mask] if mask.sum() >= 3 else ttm_arr + fit_rates = rates_arr[mask] if mask.sum() >= 3 else rates_arr + # Grid search to avoid local minima, then refine with bounded minimisation + lo, hi = lambda_bounds + grid = np.linspace(lo, hi, 50) + rss_values = [_rss(lam, fit_ttm, fit_rates) for lam in grid] + best_idx = int(np.argmin(rss_values)) + # Refine around the best grid point + refine_lo = grid[max(best_idx - 1, 0)] + refine_hi = grid[min(best_idx + 1, len(grid) - 1)] result = minimize_scalar( _rss, - bounds=lambda_bounds, + bounds=(refine_lo, refine_hi), method="bounded", - args=(ttm_arr, rates_arr), + args=(fit_ttm, fit_rates), ) lam: float = result.x - b1, b2, b3 = _ols_betas(ttm_arr, rates_arr, lam) + b1, b2, b3 = _ols_betas(fit_ttm, fit_rates, lam) return cls( beta1=Decimal(str(round(b1, 10))), beta2=Decimal(str(round(b2, 10))), @@ -108,6 +160,59 @@ def calibrate( ) +class NelsonSiegelCalibration(YieldCurveCalibration[NelsonSiegel]): + """Calibration wrapper for a Nelson-Siegel yield curve.""" + + beta_bounds: tuple[float, float] = Field( + default=(-0.5, 0.5), + description="Lower and upper bounds for beta parameters", + ) + lambda_bounds: tuple[float, float] = Field( + default=(0.01, 10.0), + description="Lower and upper bounds for the decay parameter", + ) + + def get_params(self) -> FloatArray: + ns = self.yield_curve + return np.array( + [float(ns.beta1), float(ns.beta2), float(ns.beta3), float(ns.lambda_)] + ) + + def set_params(self, params: FloatArray) -> None: + b1, b2, b3, lam = params + self.yield_curve.beta1 = Decimal(str(round(float(b1), 10))) + self.yield_curve.beta2 = Decimal(str(round(float(b2), 10))) + self.yield_curve.beta3 = Decimal(str(round(float(b3), 10))) + self.yield_curve.lambda_ = Decimal(str(round(float(lam), 10))) + + def get_bounds(self) -> Bounds: + lo, hi = self.beta_bounds + lam_lo, lam_hi = self.lambda_bounds + return Bounds([lo, lo, lo, lam_lo], [hi, hi, hi, lam_hi]) + + def calibrate( + self, + ttm: Annotated[ArrayLike, Doc("Times to maturity in years.")], + target: Annotated[ + ArrayLike, Doc("Target discount factors, same length as ttm.") + ], + ) -> NelsonSiegel: + """Fit the curve using the fast profile-OLS solver. + + Drop times to maturity <= 1 day (if any) before fitting, as these are often + dominated by noise and can cause instability in the fit. + """ + ttm_ = np.asarray(ttm, dtype=float) + mask = ttm_ >= 1 / 365 + rates = -np.log(np.asarray(target, dtype=float)[mask]) / ttm_[mask] + ns = NelsonSiegel.calibrate(ttm_[mask], rates, lambda_bounds=self.lambda_bounds) + self.yield_curve.beta1 = ns.beta1 + self.yield_curve.beta2 = ns.beta2 + self.yield_curve.beta3 = ns.beta3 + self.yield_curve.lambda_ = ns.lambda_ + return self.yield_curve + + def _design_matrix(ttm: np.ndarray, lam: float) -> np.ndarray: lt = lam * ttm with np.errstate(divide="ignore", invalid="ignore"): @@ -123,3 +228,11 @@ def _ols_betas(ttm: np.ndarray, rates: np.ndarray, lam: float) -> np.ndarray: def _rss(lam: float, ttm: np.ndarray, rates: np.ndarray) -> float: residuals = rates - _design_matrix(ttm, lam) @ _ols_betas(ttm, rates, lam) return float(np.dot(residuals, residuals)) + + +def _mad_filter(rates: np.ndarray, k: float = 3.0) -> np.ndarray: + """Boolean mask: True for observations within k standard deviations (MAD-scaled).""" + med = np.median(rates) + mad = np.median(np.abs(rates - med)) + scale = max(mad / 0.6745, 1e-12) + return np.abs(rates - med) <= k * scale diff --git a/quantflow/rates/options.py b/quantflow/rates/options.py new file mode 100644 index 0000000..c7f5079 --- /dev/null +++ b/quantflow/rates/options.py @@ -0,0 +1,183 @@ +from __future__ import annotations + +from abc import abstractmethod +from dataclasses import dataclass +from typing import TYPE_CHECKING, Generic, TypeVar + +import numpy as np +from numpy.typing import ArrayLike +from pydantic import BaseModel, Field +from scipy.optimize import Bounds, least_squares +from typing_extensions import Annotated, Doc + +from quantflow.utils.types import FloatArray + +if TYPE_CHECKING: + from .yield_curve import YieldCurve + + +Y = TypeVar("Y", bound="YieldCurve") + + +class YieldCurveCalibration(BaseModel, Generic[Y]): + yield_curve: Y = Field(..., description="Yield curve to be calibrated") + + @abstractmethod + def get_params(self) -> FloatArray: + """Current model parameters as a flat array (starting point for fit)""" + + @abstractmethod + def set_params(self, params: FloatArray) -> None: + """Update the yield curve from a flat parameter array""" + + @abstractmethod + def get_bounds(self) -> Bounds: + """Parameter bounds for the optimiser""" + + def __call__(self, ttm: FloatArray) -> FloatArray: + """Discount factors for the given TTMs evaluated at current params. + + Called inside the optimiser hot loop: must not construct a new + yield curve instance on every call. + """ + return np.asarray(self.yield_curve.discount_factor(ttm), dtype=float) + + def calibrate( + self, + ttm: Annotated[ArrayLike, Doc("Times to maturity in years.")], + target: Annotated[ + ArrayLike, Doc("Target discount factors, same length as ttm.") + ], + ) -> Y: + """Fit the yield curve to target discount factors via least squares.""" + ttm_ = np.asarray(ttm, dtype=float) + target_ = np.asarray(target, dtype=float) + has_jacobian = self.yield_curve.jacobian(ttm_) is not None + + def residuals(params: np.ndarray) -> np.ndarray: + self.set_params(params) + return self(ttm_) - target_ + + def jac(params: np.ndarray) -> FloatArray: + self.set_params(params) + return self.yield_curve.jacobian(ttm_) # type: ignore[return-value] + + result = least_squares( + residuals, + self.get_params(), + jac=jac if has_jacobian else "2-point", + bounds=self.get_bounds(), + method="trf", + ) + self.set_params(result.x) + return self.yield_curve + + +@dataclass +class OptionsDiscountingCalibration: + """Calibrate yield curves from option price parity data. + + The input data consists of arrays of call-put parity values, strikes, and times + to maturity for a set of options on the same underlying. The calibration can be + done jointly for both the asset and quote curves, or separately for one curve with + the other fixed. + """ + + asset_curve: Annotated[ + YieldCurve | YieldCurveCalibration, + Doc( + "Yield curve for the underlying asset. An instance is treated as fixed; " + "a YieldCurveCalibration will be calibrated from the parity data." + ), + ] + quote_curve: Annotated[ + YieldCurve | YieldCurveCalibration, + Doc( + "Yield curve for the quote asset. An instance is treated as fixed; " + "a YieldCurveCalibration will be calibrated from the parity data." + ), + ] + cp: Annotated[FloatArray, Doc("(Call - Put) / Spot for each option pair")] + strikes: Annotated[ + FloatArray, Doc("Strike / Spot for each option pair, same length as cp") + ] + ttm: Annotated[ + FloatArray, + Doc("Time to maturity in years for each option pair, same length as cp"), + ] + + def calibrate(self) -> tuple[YieldCurve, YieldCurve]: + if isinstance(self.asset_curve, YieldCurveCalibration): + if isinstance(self.quote_curve, YieldCurveCalibration): + return self.joint_calibration(self.asset_curve, self.quote_curve) + else: + return self.asset_calibration(self.asset_curve, self.quote_curve) + elif isinstance(self.quote_curve, YieldCurveCalibration): + return self.quote_calibration(self.asset_curve, self.quote_curve) + else: + return self.asset_curve, self.quote_curve + + def joint_calibration( + self, + asset_cal: YieldCurveCalibration, + quote_cal: YieldCurveCalibration, + ) -> tuple[YieldCurve, YieldCurve]: + """Calibrate both curves jointly from all parity observations.""" + pa = asset_cal.get_params() + pq = quote_cal.get_params() + has_jacobian = ( + asset_cal.yield_curve.jacobian(self.ttm) is not None + and quote_cal.yield_curve.jacobian(self.ttm) is not None + ) + n_a = len(pa) + bounds = Bounds( + np.concatenate([asset_cal.get_bounds().lb, quote_cal.get_bounds().lb]), + np.concatenate([asset_cal.get_bounds().ub, quote_cal.get_bounds().ub]), + ) + + def residuals(params: np.ndarray) -> np.ndarray: + asset_cal.set_params(params[:n_a]) + quote_cal.set_params(params[n_a:]) + da = asset_cal(self.ttm) + dq = quote_cal(self.ttm) + return self.cp - da + dq * self.strikes + + def jac(params: np.ndarray) -> FloatArray: + asset_cal.set_params(params[:n_a]) + quote_cal.set_params(params[n_a:]) + ja = asset_cal.yield_curve.jacobian(self.ttm) + jq = quote_cal.yield_curve.jacobian(self.ttm) + if ja is None or jq is None: # pragma: no cover + raise TypeError("jacobian must not return None in joint calibration") + return np.hstack([-ja, jq * self.strikes[:, None]]) + + result = least_squares( + residuals, + np.concatenate([pa, pq]), + jac=jac if has_jacobian else "2-point", + bounds=bounds, + method="trf", + ) + asset_cal.set_params(result.x[:n_a]) + quote_cal.set_params(result.x[n_a:]) + return asset_cal.yield_curve, quote_cal.yield_curve + + def asset_calibration( + self, + asset_cal: YieldCurveCalibration, + fixed_quote: YieldCurve, + ) -> tuple[YieldCurve, YieldCurve]: + """Calibrate only the asset curve; quote curve is fixed.""" + dq = np.asarray(fixed_quote.discount_factor(self.ttm), dtype=float) + target_da = self.cp + dq * self.strikes + return asset_cal.calibrate(self.ttm, target_da), fixed_quote + + def quote_calibration( + self, + fixed_asset: YieldCurve, + quote_cal: YieldCurveCalibration, + ) -> tuple[YieldCurve, YieldCurve]: + """Calibrate only the quote curve; asset curve is fixed.""" + da = np.asarray(fixed_asset.discount_factor(self.ttm), dtype=float) + target_dq = (da - self.cp) / self.strikes + return fixed_asset, quote_cal.calibrate(self.ttm, target_dq) diff --git a/quantflow/rates/vasicek.py b/quantflow/rates/vasicek.py index 07c62b2..676406e 100644 --- a/quantflow/rates/vasicek.py +++ b/quantflow/rates/vasicek.py @@ -1,11 +1,17 @@ -from decimal import Decimal +from __future__ import annotations + from typing import Literal +import numpy as np +from numpy.typing import ArrayLike from pydantic import Field +from scipy.optimize import least_squares +from typing_extensions import Self from quantflow.sp.ou import Vasicek from quantflow.sp.wiener import WienerProcess -from quantflow.utils.numbers import ONE, ZERO, DecimalNumber, Number, to_decimal +from quantflow.utils.numbers import ZERO, DecimalNumber +from quantflow.utils.types import FloatArrayLike from .yield_curve import YieldCurve @@ -13,11 +19,11 @@ class VasicekCurve(YieldCurve): """Class representing a Vasicek yield curve""" + curve_type: Literal["vasicek_curve"] = "vasicek_curve" rate: DecimalNumber = Field(description=r"Initial value $x_0$") kappa: DecimalNumber = Field(gt=ZERO, description=r"Mean reversion speed $\kappa$") theta: DecimalNumber = Field(description=r"Mean level $\theta$") sigma: DecimalNumber = Field(ge=ZERO, description=r"Volatility $\sigma$") - curve_type: Literal["vasicek"] = "vasicek" def process(self) -> Vasicek: return Vasicek( @@ -27,14 +33,53 @@ def process(self) -> Vasicek: bdlp=WienerProcess(sigma=float(self.sigma)), ) - def discount_factor(self, ttm: Number) -> Decimal: + def instanteous_forward_rate(self, ttm: FloatArrayLike) -> FloatArrayLike: + r"""Calculate the instantaneous forward rate.""" + arr = np.asarray(ttm, dtype=float) + ttma = np.maximum(arr, 0.0) + kappa = float(self.kappa) + theta = float(self.theta) + sigma = float(self.sigma) + rate = float(self.rate) + s2 = sigma * sigma + et = np.exp(-kappa * ttma) + b = (1.0 - et) / kappa + fwd = rate * et + theta * (1.0 - et) - s2 / (2.0 * kappa) * b * et + return fwd if fwd.ndim > 0 else float(fwd) + + def discount_factor(self, ttm: FloatArrayLike) -> FloatArrayLike: r"""Calculate the discount factor for a given time to maturity.""" - ttmd = to_decimal(ttm) - if ttmd <= ZERO: - return ONE - b = (ONE - (-self.kappa * ttmd).exp()) / self.kappa - s2 = self.sigma * self.sigma - a = (self.theta - s2 / (2 * self.kappa * self.kappa)) * ( - b - ttmd - ) + s2 * b * b / (4 * self.kappa) - return (a - self.rate * b).exp() + arr = np.asarray(ttm, dtype=float) + ttma = np.maximum(arr, 0.0) + kappa = float(self.kappa) + theta = float(self.theta) + sigma = float(self.sigma) + rate = float(self.rate) + s2 = sigma * sigma + b = (1.0 - np.exp(-kappa * ttma)) / kappa + a = (theta - s2 / (2.0 * kappa * kappa)) * (b - ttma) + s2 * b * b / ( + 4.0 * kappa + ) + df = np.exp(a - rate * b) + return df if df.ndim > 0 else float(df) + + @classmethod + def calibrate(cls, ttm: ArrayLike, rates: ArrayLike) -> Self: + """Fit the Vasicek curve to continuously compounded rates via least squares.""" + ttm_arr = np.asarray(ttm, dtype=float) + rates_arr = np.asarray(rates, dtype=float) + + def residuals(params: np.ndarray) -> np.ndarray: + curve = cls( + rate=params[0], kappa=params[1], theta=params[2], sigma=params[3] + ) + df = np.asarray(curve.discount_factor(ttm_arr), dtype=float) + fitted = -np.log(df) / ttm_arr + return fitted - rates_arr + + x0 = np.array([rates_arr[0], 1.0, rates_arr[-1], 0.01]) + result = least_squares( + residuals, x0, bounds=([-1.0, 1e-4, -1.0, 0.0], [1.0, 50.0, 1.0, 1.0]) + ) + r, k, th, s = result.x + return cls(rate=r, kappa=k, theta=th, sigma=s) diff --git a/quantflow/rates/yield_curve.py b/quantflow/rates/yield_curve.py index 760b0fb..e0a8328 100644 --- a/quantflow/rates/yield_curve.py +++ b/quantflow/rates/yield_curve.py @@ -2,16 +2,24 @@ from abc import ABC, abstractmethod from datetime import datetime -from decimal import Decimal -from typing import Any, Literal +from typing import TYPE_CHECKING, Any, Literal +import numpy as np from numpy.typing import ArrayLike from pydantic import BaseModel, Field from typing_extensions import Annotated, Doc, Self from quantflow.utils import plot from quantflow.utils.dates import utcnow -from quantflow.utils.numbers import ONE, ZERO +from quantflow.utils.text import snake_case +from quantflow.utils.types import FloatArray, FloatArrayLike, maybe_float + +if TYPE_CHECKING: + from .options import YieldCurveCalibration + + +_CURVE_TYPES: dict[str, type[YieldCurve]] = {} +_TYPES_TO_NAMES: dict[type[YieldCurve], str] = {} class YieldCurve(BaseModel, ABC, extra="forbid"): @@ -21,10 +29,16 @@ class YieldCurve(BaseModel, ABC, extra="forbid"): default_factory=utcnow, description="Reference date for the yield curve", ) + curve_type: str = Field( + default="unknown", + description=( + "Type of the yield curve, used for serialization" " and discrimination" + ), + ) @abstractmethod - def instanteous_forward_rate(self, ttm: float) -> Decimal: - r"""Calculate the instantaneous forward rate for a given time to maturity + def instanteous_forward_rate(self, ttm: FloatArrayLike) -> FloatArrayLike: + r"""Calculate the instantaneous forward rate for a given time to maturity. The instantaneous forward rate is related to discount factor by the following formula: @@ -34,11 +48,14 @@ def instanteous_forward_rate(self, ttm: float) -> Decimal: \end{equation} where $D(\tau)$ is the discount factor for a given time to maturity $\tau$. + + Accepts a scalar float or a float array. Returns a scalar float for scalar + input and a numpy float array for array input. """ @abstractmethod - def discount_factor(self, ttm: float) -> Decimal: - r"""Calculate the discount factor for a given time to maturity + def discount_factor(self, ttm: FloatArrayLike) -> FloatArrayLike: + r"""Calculate the discount factor for a given time to maturity. The discount factor is related to the instantaneous forward rate by the following formula: @@ -49,23 +66,43 @@ def discount_factor(self, ttm: float) -> Decimal: where $f(\tau)$ is the instantaneous forward rate for a given time to maturity $\tau$. + + Accepts a scalar float or a float array. Returns a scalar float for scalar + input and a numpy float array for array input. """ @classmethod @abstractmethod - def calibrate(cls, ttm: ArrayLike, rates: ArrayLike) -> Self: - """Fit the yield curve to continuously compounded rates. - - Parameters - ---------- - ttm: - Times to maturity in years. - rates: - Continuously compounded rates, same length as ttm (e.g. 0.05 for 5%). + def calibrate( + cls, + ttm: Annotated[ArrayLike, Doc("Times to maturity in years.")], + rates: Annotated[ + ArrayLike, + Doc( + "Continuously compounded rates, same length as ttm (e.g. 0.05 for 5%)." + ), + ], + ) -> Self: + """Fit the yield curve to continuously compounded rates.""" + + def calibrator(self) -> YieldCurveCalibration | None: + """Return a calibration wrapper for this curve, or None if not available.""" + return None + + def jacobian( + self, ttm: Annotated[FloatArrayLike, Doc("Times to maturity in years.")] + ) -> FloatArray | None: + """Analytical Jacobian of discount factors w.r.t. model parameters. + + Returns None if no analytical Jacobian is available (default). + Shape when not None: (len(ttm), n_params). """ + return None - def continuously_compounded_rate(self, ttm: float) -> Decimal: - r"""Calculate the continuously compounded rate for a given time to maturity + def continuously_compounded_rate( + self, ttm: Annotated[ArrayLike, Doc("Time to maturity in years")] + ) -> FloatArrayLike: + r"""Calculate the continuously compounded rate for a given time to maturity. The continuously compounded rate is related to the discount factor by the following formula: @@ -75,11 +112,16 @@ def continuously_compounded_rate(self, ttm: float) -> Decimal: \end{equation} where $D(\tau)$ is the discount factor for a given time to maturity $\tau$. + + Accepts a scalar float or a float array. Returns a scalar float for scalar + input and a numpy float array for array input. """ - if ttm <= 0: - return self.instanteous_forward_rate(0) - else: - return -self.discount_factor(float(ttm)).ln() / Decimal(ttm) + ttm_ = np.asarray(ttm, dtype=float) + df = np.asarray(self.discount_factor(ttm_), dtype=float) + result = np.where( + ttm_ <= 0, self.instanteous_forward_rate(0.0), -np.log(df) / ttm_ + ) + return maybe_float(result) def plot( self, @@ -93,17 +135,39 @@ def plot( """ return plot.plot_yield_curve(self, ttm_max=ttm_max, n=n, **kwargs) + @classmethod + def register_curve_types(cls, *curve_classes: type[YieldCurve]) -> None: + """Register a yield curve subclass for deserialization.""" + for curve_cls in curve_classes: + name = snake_case(curve_cls.__name__) + if current_type := _CURVE_TYPES.pop(name, None): + _TYPES_TO_NAMES.pop(current_type, None) + _CURVE_TYPES[name] = curve_cls + _TYPES_TO_NAMES[curve_cls] = name + + @classmethod + def curve_types(cls) -> tuple[str, ...]: + """Return the registered curve types.""" + return tuple(sorted(_CURVE_TYPES)) + + @classmethod + def get_curve_class(cls, curve_type: str) -> type[YieldCurve] | None: + """Get the yield curve class for a given curve type.""" + return _CURVE_TYPES.get(curve_type) + class NoDiscount(YieldCurve): """Flat yield curve with zero rates (discount factor is always 1).""" curve_type: Literal["no_discount"] = "no_discount" - def instanteous_forward_rate(self, ttm: float) -> Decimal: - return ZERO + def instanteous_forward_rate(self, ttm: FloatArrayLike) -> FloatArrayLike: + arr = np.asarray(ttm, dtype=float) + return np.zeros_like(arr) if arr.ndim > 0 else 0.0 - def discount_factor(self, ttm: float) -> Decimal: - return ONE + def discount_factor(self, ttm: FloatArrayLike) -> FloatArrayLike: + arr = np.asarray(ttm, dtype=float) + return np.ones_like(arr) if arr.ndim > 0 else 1.0 @classmethod def calibrate(cls, ttm: ArrayLike, rates: ArrayLike) -> Self: diff --git a/quantflow/utils/text.py b/quantflow/utils/text.py new file mode 100644 index 0000000..7c16c90 --- /dev/null +++ b/quantflow/utils/text.py @@ -0,0 +1,23 @@ +""" +Adapted from humps + +https://github.com/nficano/humps +""" + +import re + +ACRONYM_RE = re.compile(r"([A-Z\d]+)(?=[A-Z\d]|$)") +SPLIT_RE = re.compile(r"([\-_]*(?<=[^0-9])(?=[A-Z])[^A-Z]*[\-_]*)") + + +def snake_case(string: str) -> str: + """Convert a string into snake case.""" + return _separate_words(_fix_abbreviations(string)).lower() + + +def _fix_abbreviations(string: str) -> str: + return ACRONYM_RE.sub(lambda m: m.group(0).title(), string) + + +def _separate_words(string: str, separator: str = "_") -> str: + return separator.join(s for s in SPLIT_RE.split(string) if s) diff --git a/quantflow/utils/types.py b/quantflow/utils/types.py index 2e1ac95..076a193 100644 --- a/quantflow/utils/types.py +++ b/quantflow/utils/types.py @@ -32,6 +32,10 @@ def as_float(num: NumberType | None = None) -> float: return float(0 if num is None else num) +def maybe_float(array: FloatArray) -> FloatArrayLike: + return array if array.ndim > 0 else float(array) + + def as_array(n: Vector) -> np.ndarray: """Convert an input into an array""" if isinstance(n, int): diff --git a/quantflow_tests/test_nelson_siegel.py b/quantflow_tests/test_nelson_siegel.py new file mode 100644 index 0000000..014a32f --- /dev/null +++ b/quantflow_tests/test_nelson_siegel.py @@ -0,0 +1,198 @@ +from __future__ import annotations + +import math +from decimal import Decimal + +import numpy as np +import pytest + +from quantflow.rates.nelson_siegel import NelsonSiegel + + +def _flat_curve(level: float = 0.05) -> NelsonSiegel: + return NelsonSiegel( + beta1=Decimal(str(level)), + beta2=Decimal("0"), + beta3=Decimal("0"), + lambda_=Decimal("1"), + ) + + +def _true_curve() -> NelsonSiegel: + return NelsonSiegel( + beta1=Decimal("0.04"), + beta2=Decimal("-0.02"), + beta3=Decimal("0.03"), + lambda_=Decimal("1.5"), + ) + + +# --------------------------------------------------------------------------- +# Discount factor and forward rate +# --------------------------------------------------------------------------- + + +def test_flat_curve_discount_factor_one_year() -> None: + ns = _flat_curve(0.05) + assert float(ns.discount_factor(1.0)) == pytest.approx(math.exp(-0.05), rel=1e-5) + + +def test_flat_curve_discount_factor_two_year() -> None: + ns = _flat_curve(0.04) + assert float(ns.discount_factor(2.0)) == pytest.approx(math.exp(-0.08), rel=1e-5) + + +def test_discount_factor_zero_ttm() -> None: + assert _flat_curve(0.05).discount_factor(0) == Decimal("1") + + +def test_discount_factor_negative_ttm() -> None: + assert _flat_curve(0.05).discount_factor(-1) == Decimal("1") + + +def test_discount_factor_increases_with_lower_rate() -> None: + ttm = 1.0 + assert float(_flat_curve(0.02).discount_factor(ttm)) > float( + _flat_curve(0.08).discount_factor(ttm) + ) + + +def test_discount_factor_decreases_with_ttm() -> None: + ns = _flat_curve(0.05) + assert float(ns.discount_factor(1.0)) > float(ns.discount_factor(5.0)) + + +def test_instantaneous_forward_rate_at_zero() -> None: + ns = NelsonSiegel( + beta1=Decimal("0.04"), + beta2=Decimal("0.02"), + beta3=Decimal("0.01"), + lambda_=Decimal("1"), + ) + assert float(ns.instanteous_forward_rate(0)) == pytest.approx(0.06, rel=1e-6) + + +def test_instantaneous_forward_rate_large_ttm() -> None: + ns = NelsonSiegel( + beta1=Decimal("0.04"), + beta2=Decimal("0.02"), + beta3=Decimal("0.01"), + lambda_=Decimal("1"), + ) + assert float(ns.instanteous_forward_rate(100)) == pytest.approx(0.04, abs=1e-5) + + +def test_consistency_forward_and_discount() -> None: + ns = NelsonSiegel( + beta1=Decimal("0.04"), + beta2=Decimal("0.015"), + beta3=Decimal("0.008"), + lambda_=Decimal("2"), + ) + ttm, h = 1.5, 1e-5 + numerical = -( + math.log(float(ns.discount_factor(ttm + h))) + - math.log(float(ns.discount_factor(ttm - h))) + ) / (2 * h) + assert numerical == pytest.approx(float(ns.instanteous_forward_rate(ttm)), rel=1e-4) + + +# --------------------------------------------------------------------------- +# Jacobian +# --------------------------------------------------------------------------- + + +def test_jacobian_shape() -> None: + ns = _true_curve() + ttm = np.array([0.5, 1.0, 2.0, 5.0, 10.0]) + J = ns.jacobian(ttm) + assert J.shape == (len(ttm), 4) + + +def test_jacobian_matches_finite_differences() -> None: + ns = _true_curve() + ttm = np.array([0.25, 0.5, 1.0, 2.0, 5.0, 10.0]) + h = 1e-5 + params = [ns.beta1, ns.beta2, ns.beta3, ns.lambda_] + fields = ["beta1", "beta2", "beta3", "lambda_"] + J_analytical = ns.jacobian(ttm) + J_numerical = np.zeros_like(J_analytical) + for i, (field, p) in enumerate(zip(fields, params)): + ns_fwd = ns.model_copy(update={field: Decimal(str(float(p) + h))}) + ns_bwd = ns.model_copy(update={field: Decimal(str(float(p) - h))}) + df_fwd = np.array([float(ns_fwd.discount_factor(t)) for t in ttm]) + df_bwd = np.array([float(ns_bwd.discount_factor(t)) for t in ttm]) + J_numerical[:, i] = (df_fwd - df_bwd) / (2 * h) + np.testing.assert_allclose(J_analytical, J_numerical, rtol=1e-4) + + +# --------------------------------------------------------------------------- +# calibrate — clean data +# --------------------------------------------------------------------------- + + +def test_calibrate_recovers_curve_noiseless() -> None: + ns_true = _true_curve() + ttm = np.linspace(0.25, 10.0, 20) + rates = -np.log([float(ns_true.discount_factor(t)) for t in ttm]) / ttm + ns_fit = NelsonSiegel.calibrate(ttm, rates) + for t in [1.0, 2.0, 5.0]: + assert float(ns_fit.discount_factor(t)) == pytest.approx( + float(ns_true.discount_factor(t)), rel=1e-4 + ) + + +def test_calibrate_flat_curve() -> None: + ttm = np.array([0.5, 1.0, 2.0, 5.0, 10.0]) + rates = np.full_like(ttm, 0.05) + ns = NelsonSiegel.calibrate(ttm, rates) + for t in ttm: + assert float(ns.discount_factor(t)) == pytest.approx( + math.exp(-0.05 * t), rel=1e-4 + ) + + +# --------------------------------------------------------------------------- +# calibrate — robustness +# --------------------------------------------------------------------------- + +# Crypto-realistic TTM grid: short maturities out to ~2 years +_CRYPTO_TTMS = np.array([1 / 52, 2 / 52, 1 / 12, 2 / 12, 3 / 12, 6 / 12, 1.0, 2.0]) + + +def _true_rates(ns: NelsonSiegel, ttm: np.ndarray) -> np.ndarray: + return -np.log([float(ns.discount_factor(t)) for t in ttm]) / ttm + + +def _df_rmse(ns_fit: NelsonSiegel, ns_true: NelsonSiegel, ttm: np.ndarray) -> float: + fitted = np.array([float(ns_fit.discount_factor(t)) for t in ttm]) + true = np.array([float(ns_true.discount_factor(t)) for t in ttm]) + return float(np.sqrt(np.mean((fitted - true) ** 2))) + + +def test_calibrate_crypto_ttms_noiseless() -> None: + ns_true = _true_curve() + rates = _true_rates(ns_true, _CRYPTO_TTMS) + ns_fit = NelsonSiegel.calibrate(_CRYPTO_TTMS, rates) + assert _df_rmse(ns_fit, ns_true, _CRYPTO_TTMS) < 1e-4 + + +def test_calibrate_with_gaussian_noise() -> None: + rng = np.random.default_rng(42) + ns_true = _true_curve() + rates = _true_rates(ns_true, _CRYPTO_TTMS) + noisy = rates + rng.normal(0, 0.002, size=len(rates)) + ns_fit = NelsonSiegel.calibrate(_CRYPTO_TTMS, noisy) + assert _df_rmse(ns_fit, ns_true, _CRYPTO_TTMS) < 0.005 + + +def test_calibrate_robust_to_outliers() -> None: + """Two extreme outlier rates must not corrupt the fit materially.""" + rng = np.random.default_rng(0) + ns_true = _true_curve() + rates = _true_rates(ns_true, _CRYPTO_TTMS).copy() + # inject two bad observations — 5× the true rate + outlier_idx = rng.choice(len(rates), size=2, replace=False) + rates[outlier_idx] *= 5.0 + ns_fit = NelsonSiegel.calibrate(_CRYPTO_TTMS, rates) + assert _df_rmse(ns_fit, ns_true, _CRYPTO_TTMS) < 0.01 diff --git a/quantflow_tests/test_options.py b/quantflow_tests/test_options.py index f685cfc..400b148 100644 --- a/quantflow_tests/test_options.py +++ b/quantflow_tests/test_options.py @@ -95,10 +95,11 @@ def test_term_structure(vol_surface: VolSurface) -> None: "maturity", "ttm", "forward", + "implied_forward", + "forward_basis", + "rate", "bid_ask_spread", "basis", - "rate_percent", - "fwd_spread_pct", "open_interest", "volume", ] diff --git a/quantflow_tests/test_rates.py b/quantflow_tests/test_rates.py index d24fee5..652c1ce 100644 --- a/quantflow_tests/test_rates.py +++ b/quantflow_tests/test_rates.py @@ -4,22 +4,15 @@ from datetime import datetime, timezone from decimal import Decimal -import numpy as np import pytest from quantflow.rates.interest_rate import Rate -from quantflow.rates.nelson_siegel import NelsonSiegel REF_DATE = datetime(2024, 1, 1, tzinfo=timezone.utc) ONE_YEAR = datetime(2025, 1, 1, tzinfo=timezone.utc) TWO_YEARS = datetime(2026, 1, 1, tzinfo=timezone.utc) -# --------------------------------------------------------------------------- -# Rate -# --------------------------------------------------------------------------- - - def test_rate_from_number_stores_rate() -> None: r = Rate.from_number(0.05) assert float(r.rate) == pytest.approx(0.05, rel=1e-6) @@ -94,127 +87,3 @@ def test_from_spot_and_forward_expired_returns_zero_rate() -> None: forward = Decimal("105") r = Rate.from_spot_and_forward(spot, forward, ONE_YEAR, REF_DATE) assert r.rate == Decimal("0") - - -# --------------------------------------------------------------------------- -# NelsonSiegel -# --------------------------------------------------------------------------- - - -def _flat_curve(level: float = 0.05) -> NelsonSiegel: - """Flat curve: beta2=beta3=0, so yield = beta1 for all maturities.""" - return NelsonSiegel( - beta1=Decimal(str(level)), - beta2=Decimal("0"), - beta3=Decimal("0"), - lambda_=Decimal("1"), - ) - - -def test_nelson_siegel_flat_curve_discount_factor() -> None: - ns = _flat_curve(0.05) - ttm = 1.0 - df = ns.discount_factor(ttm) - expected = math.exp(-0.05 * ttm) - assert float(df) == pytest.approx(expected, rel=1e-5) - - -def test_nelson_siegel_flat_curve_two_year() -> None: - ns = _flat_curve(0.04) - df = ns.discount_factor(2.0) - expected = math.exp(-0.04 * 2.0) - assert float(df) == pytest.approx(expected, rel=1e-5) - - -def test_nelson_siegel_discount_factor_zero_ttm() -> None: - ns = _flat_curve(0.05) - assert ns.discount_factor(0) == Decimal("1") - - -def test_nelson_siegel_discount_factor_negative_ttm() -> None: - ns = _flat_curve(0.05) - assert ns.discount_factor(-1) == Decimal("1") - - -def test_nelson_siegel_instantaneous_forward_rate_at_zero() -> None: - ns = NelsonSiegel( - beta1=Decimal("0.04"), - beta2=Decimal("0.02"), - beta3=Decimal("0.01"), - lambda_=Decimal("1"), - ) - # at ttm=0: f(0) = beta1 + beta2 - fr = ns.instanteous_forward_rate(0) - assert float(fr) == pytest.approx(0.06, rel=1e-6) - - -def test_nelson_siegel_instantaneous_forward_rate_large_ttm() -> None: - ns = NelsonSiegel( - beta1=Decimal("0.04"), - beta2=Decimal("0.02"), - beta3=Decimal("0.01"), - lambda_=Decimal("1"), - ) - # as ttm -> inf, e^{-ttm/lambda} -> 0, so f -> beta1 - fr = ns.instanteous_forward_rate(100) - assert float(fr) == pytest.approx(0.04, abs=1e-5) - - -def test_nelson_siegel_discount_factor_increases_with_rate() -> None: - # higher rate => smaller discount factor - ns_low = _flat_curve(0.02) - ns_high = _flat_curve(0.08) - ttm = 1.0 - assert float(ns_low.discount_factor(ttm)) > float(ns_high.discount_factor(ttm)) - - -def test_nelson_siegel_discount_factor_decreases_with_ttm() -> None: - ns = _flat_curve(0.05) - df1 = float(ns.discount_factor(1.0)) - df5 = float(ns.discount_factor(5.0)) - assert df1 > df5 - - -def test_nelson_siegel_fit_recovers_parameters() -> None: - ns_true = NelsonSiegel( - beta1=Decimal("0.04"), - beta2=Decimal("-0.02"), - beta3=Decimal("0.03"), - lambda_=Decimal("1.5"), - ) - ttm = np.linspace(0.25, 10.0, 20) - rates = np.array([float(ns_true.discount_factor(t)) for t in ttm]) - # convert discount factors back to zero rates for fitting - zero_rates = -np.log(rates) / ttm - ns_fit = NelsonSiegel.calibrate(ttm, zero_rates) - for t in [1.0, 2.0, 5.0]: - assert float(ns_fit.discount_factor(t)) == pytest.approx( - float(ns_true.discount_factor(t)), rel=1e-4 - ) - - -def test_nelson_siegel_fit_flat_curve() -> None: - ttm = np.array([0.5, 1.0, 2.0, 5.0, 10.0]) - rates = np.full_like(ttm, 0.05) - ns = NelsonSiegel.calibrate(ttm, rates) - for t in ttm: - assert float(ns.discount_factor(t)) == pytest.approx( - math.exp(-0.05 * t), rel=1e-4 - ) - - -def test_nelson_siegel_consistency_forward_and_discount() -> None: - """Numerical check: f(tau) ≈ -d ln D / d tau.""" - ns = NelsonSiegel( - beta1=Decimal("0.04"), - beta2=Decimal("0.015"), - beta3=Decimal("0.008"), - lambda_=Decimal("2"), - ) - ttm = 1.5 - h = 1e-5 - df_plus = float(ns.discount_factor(ttm + h)) - df_minus = float(ns.discount_factor(ttm - h)) - numerical_fwd = -(math.log(df_plus) - math.log(df_minus)) / (2 * h) - analytic_fwd = float(ns.instanteous_forward_rate(ttm)) - assert numerical_fwd == pytest.approx(analytic_fwd, rel=1e-4) From 16906c3d271227e6ff77c749f539320567021b7e Mon Sep 17 00:00:00 2001 From: Luca Date: Thu, 21 May 2026 15:16:04 +0100 Subject: [PATCH 7/8] convert all to observable --- app/__init__.py | 0 app/__main__.py | 29 ++- app/api/cointegration.py | 61 +++++ app/api/deps.py | 3 +- app/api/heston.py | 80 ++++++ app/api/hurst.py | 157 ++++++++++++ app/api/rates.py | 9 +- app/api/sampling.py | 74 ++++++ app/api/smoother.py | 50 ++++ app/api/volatility.py | 39 ++- app/cointegration.py | 164 ------------- app/double_exponential_sampling.py | 81 ------ app/frontend/observablehq.config.js | 2 +- app/frontend/src/cointegration.md | 64 +++++ app/frontend/src/heston-vol-surface.md | 102 ++++++++ app/frontend/src/hurst.md | 111 +++++++++ app/frontend/src/index.md | 5 + app/frontend/src/sampling.md | 138 +++++++++++ app/frontend/src/style.css | 8 +- app/frontend/src/supersmoother.md | 48 ++++ app/frontend/src/volatility-surface.md | 4 +- app/gaussian_sampling.py | 91 ------- app/heston_vol_surface.py | 165 ------------- app/hurst.py | 327 ------------------------- app/poisson_sampling.py | 72 ------ app/supersmoother.py | 110 --------- app/utils/__init__.py | 12 - app/utils/paths.py | 3 +- dev/lint | 2 +- mkdocs.yml | 9 +- {app => notebooks}/heston_divfm_fit.py | 0 pyproject.toml | 1 + 32 files changed, 954 insertions(+), 1067 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/api/cointegration.py create mode 100644 app/api/heston.py create mode 100644 app/api/hurst.py create mode 100644 app/api/sampling.py create mode 100644 app/api/smoother.py delete mode 100644 app/cointegration.py delete mode 100644 app/double_exponential_sampling.py create mode 100644 app/frontend/src/cointegration.md create mode 100644 app/frontend/src/heston-vol-surface.md create mode 100644 app/frontend/src/hurst.md create mode 100644 app/frontend/src/sampling.md create mode 100644 app/frontend/src/supersmoother.md delete mode 100644 app/gaussian_sampling.py delete mode 100644 app/heston_vol_surface.py delete mode 100644 app/hurst.py delete mode 100644 app/poisson_sampling.py delete mode 100644 app/supersmoother.py rename {app => notebooks}/heston_divfm_fit.py (100%) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__main__.py b/app/__main__.py index 9e32f8a..51a706a 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -1,18 +1,23 @@ import os -import marimo +from dotenv import load_dotenv from fastapi import APIRouter, FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.docs import get_redoc_html from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles - from fluid.utils import log -from app.utils.paths import APP_PATH, head_snippet + +from app.utils.paths import APP_PATH from quantflow import __version__ +from .api.cointegration import cointegration_router from .api.deps import instrument_app +from .api.heston import heston_router +from .api.hurst import hurst_router from .api.rates import rates_router +from .api.sampling import sampling_router +from .api.smoother import smoother_router from .api.status import status_router from .api.volatility import volatility_router @@ -20,14 +25,8 @@ def crate_app() -> FastAPI: - # Create a marimo asgi app - html_head = head_snippet(APP_PATH / "docs") - server = marimo.create_asgi_app(include_code=True, html_head=html_head) - for path in APP_PATH.glob("*.py"): - if path.name.startswith("_"): - continue - dashed = path.stem.replace("_", "-") - server = server.with_app(path=f"/{dashed}", root=f"./app/{path.name}") + load_dotenv() + log.config() app = FastAPI( version=__version__, title="Quantflow API", @@ -57,11 +56,15 @@ async def api_redoc() -> HTMLResponse: title="Quantflow API", ) + api.include_router(cointegration_router) + api.include_router(heston_router) + api.include_router(hurst_router) api.include_router(rates_router) + api.include_router(sampling_router) + api.include_router(smoother_router) api.include_router(volatility_router) app.include_router(api) app.include_router(status_router, include_in_schema=False) - app.mount("/examples", server.build()) app.mount("/", StaticFiles(directory=APP_PATH / "docs", html=True), name="static") return app @@ -69,5 +72,5 @@ async def api_redoc() -> HTMLResponse: # Run the server if __name__ == "__main__": import uvicorn - log.config() + uvicorn.run(crate_app(), host="0.0.0.0", port=PORT) diff --git a/app/api/cointegration.py b/app/api/cointegration.py new file mode 100644 index 0000000..6e2778b --- /dev/null +++ b/app/api/cointegration.py @@ -0,0 +1,61 @@ +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.data.fmp import FMP + +cointegration_router = APIRouter() + + +class CointegrationResponse(BaseModel): + dates: list[str] = Field(description="Date strings") + residuals: list[float] = Field(description="Cointegration residual values") + deltas: list[float] = Field( + description="Cointegrating vector (eigenvector for largest eigenvalue)" + ) + + +@cointegration_router.get("/cointegration") +async def cointegration( + frequency: str = Query( + "daily", + description="Price frequency", + enum=["1min", "5min", "15min", "30min", "1hour", "4hour", "daily"], + ), +) -> CointegrationResponse: + import numpy as np + from statsmodels.tsa.vector_ar.vecm import coint_johansen + + # daily uses the EOD endpoint (frequency=None) + freq = None if frequency == "daily" else frequency + + async with FMP() as cli: + btc = await cli.prices("BTCUSD", convert_to_date=True, frequency=freq) + eth = await cli.prices("ETHUSD", convert_to_date=True, frequency=freq) + sol = await cli.prices("SOLUSD", convert_to_date=True, frequency=freq) + + btc = btc.set_index("date") + eth = eth.set_index("date") + sol = sol.set_index("date") + + prices_3 = ( + btc[["close"]] + .join(eth[["close"]], lsuffix="_btc", rsuffix="_eth") + .join(sol[["close"]]) + ) + prices_3.columns = ["btc_close", "eth_close", "sol_close"] + prices_3 = prices_3.dropna() + + log_prices_3 = np.log(prices_3) + johansen_result = coint_johansen(log_prices_3, det_order=0, k_ar_diff=1) + deltas = johansen_result.evec[:, 0] + + residuals = log_prices_3.dot(deltas) + residual_mean = residuals.mean() + residuals = residuals - residual_mean + + dates = [str(d)[:10] for d in residuals.index] + return CointegrationResponse( + dates=dates, + residuals=[float(v) for v in residuals.values], + deltas=[float(v) for v in deltas], + ) diff --git a/app/api/deps.py b/app/api/deps.py index 52c219d..31a215a 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -2,8 +2,9 @@ from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import Annotated, Generic, TypeVar, cast -from fluid.utils import log + from fastapi import Depends, FastAPI, Request +from fluid.utils import log from fluid.utils.redis import FluidRedis from pydantic import BaseModel from redis import Redis diff --git a/app/api/heston.py b/app/api/heston.py new file mode 100644 index 0000000..62bb08c --- /dev/null +++ b/app/api/heston.py @@ -0,0 +1,80 @@ +import numpy as np +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.options.pricer import OptionPricer +from quantflow.sp.heston import HestonJ +from quantflow.sp.jump_diffusion import JumpDiffusion +from quantflow.utils.distributions import DoubleExponential + +heston_router = APIRouter() + + +class VolSurfaceGridResponse(BaseModel): + moneyness: list[float] = Field(description="Moneyness grid values") + ttm: list[float] = Field(description="Time to maturity grid values") + implied_vol: list[list[float]] = Field( + description="Implied vol grid (rows=ttm, cols=moneyness)" + ) + + +@heston_router.get("/heston-vol-surface") +async def heston_vol_surface( + model: str = Query( + "jd", + description="Model type", + enum=["jd", "hj"], + ), + vol: float = Query(0.4, description="Long term volatility", ge=0.1, le=0.8), + sigma: float = Query(0.5, description="Vol of vol", ge=0.1, le=2.0), + kappa: float = Query(0.5, description="Variance mean reversion", ge=0.1, le=2.0), + rho: float = Query(0.0, description="Correlation", ge=-0.6, le=0.6), + r: float = Query(1.0, description="Initial vol ratio", ge=0.6, le=1.6), + jump_fraction: float = Query(0.5, description="Jump fraction", ge=0.1, le=0.9), + jump_intensity: float = Query( + 10.0, description="Jump intensity", ge=10.0, le=100.0 + ), + jump_asymmetry: float = Query( + 0.0, description="Jump asymmetry (log kappa)", ge=-2.0, le=2.0 + ), +) -> VolSurfaceGridResponse: + pricer: OptionPricer + if model == "jd": + vm_jd = JumpDiffusion.create( + DoubleExponential, + vol=vol, + jump_fraction=jump_fraction, + jump_intensity=jump_intensity, + jump_asymmetry=jump_asymmetry, + ) + pricer = OptionPricer(model=vm_jd) + else: + st = sigma / vol + k = max(kappa, 0.5 * st * st) + vm_hj = HestonJ.create( + DoubleExponential, + rate=r, + vol=vol, + sigma=sigma, + kappa=k, + rho=rho, + jump_fraction=jump_fraction, + jump_intensity=jump_intensity, + jump_asymmetry=jump_asymmetry, + ) + pricer = OptionPricer(model=vm_hj) + ttm_arr = np.linspace(0.1, 1.0, 10) + moneyness_arr = np.linspace(-0.5, 0.5, 51) + implied = np.zeros((len(ttm_arr), len(moneyness_arr))) + for i, t in enumerate(ttm_arr): + maturity = pricer.maturity(float(t)) + vols = maturity.prices(moneyness_arr * np.sqrt(t))["implied_vol"].values + # replace NaN/Inf/negative with 0 + vols = np.where(np.isfinite(vols) & (vols > 0), vols, 0.0) + implied[i, :] = vols + + return VolSurfaceGridResponse( + moneyness=[float(m) for m in moneyness_arr], + ttm=[float(t) for t in ttm_arr], + implied_vol=[[float(v) for v in row] for row in implied], + ) diff --git a/app/api/hurst.py b/app/api/hurst.py new file mode 100644 index 0000000..2082bb2 --- /dev/null +++ b/app/api/hurst.py @@ -0,0 +1,157 @@ +import math +from collections import defaultdict +from typing import Any + +import numpy as np +import pandas as pd +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.sp.ou import Vasicek +from quantflow.sp.wiener import WienerProcess +from quantflow.ta.ohlc import OHLC + +hurst_router = APIRouter() + +DEFAULT_PERIODS = ("10s", "20s", "30s", "1m", "2m", "3m", "5m", "10m", "30m") +VASICEK_PERIODS = ("10m", "20m", "30m", "1h") + + +class HurstWienerResponse(BaseModel): + dates: list[str] = Field(description="Datetime strings for the path") + values: list[float] = Field(description="Path values") + hurst_exponent: float = Field(description="Realized variance Hurst exponent") + realized_std: float = Field(description="Realized standard deviation (scaled)") + estimator_periods: list[str] = Field( + description="Sampling periods for range-based estimators" + ) + estimator_pk: list[float] = Field(description="Parkinson volatility estimates") + estimator_gk: list[float] = Field(description="Garman-Klass volatility estimates") + estimator_rs: list[float] = Field( + description="Rogers-Satchell volatility estimates" + ) + ohlc_hurst_pk: float = Field(description="OHLC Hurst exponent (Parkinson)") + ohlc_hurst_gk: float = Field(description="OHLC Hurst exponent (Garman-Klass)") + ohlc_hurst_rs: float = Field(description="OHLC Hurst exponent (Rogers-Satchell)") + + +class HurstVasicekResponse(BaseModel): + dates: list[str] = Field(description="Datetime strings for the path") + values: list[float] = Field(description="Path values") + hurst_realized: float = Field(description="Hurst exponent from realized variance") + hurst_pk: float = Field(description="Hurst exponent (Parkinson)") + hurst_gk: float = Field(description="Hurst exponent (Garman-Klass)") + hurst_rs: float = Field(description="Hurst exponent (Rogers-Satchell)") + + +def _range_std(pdf: Any, range_seconds: float, seconds_in_day: int) -> float: + variance = pdf.mean() + variance = seconds_in_day * variance / range_seconds + return math.sqrt(variance) + + +def _ohlc_hurst(df: pd.DataFrame, serie: str, periods: tuple) -> dict[str, float]: + template = OHLC( + serie=serie, + period="10m", + rogers_satchell_variance=True, + parkinson_variance=True, + garman_klass_variance=True, + ) + estimator_names = ("pk", "gk", "rs") + time_range = [] + estimators: dict[str, list] = defaultdict(list) + for period in periods: + ohlc = template.model_copy(update=dict(period=period)) + rf = ohlc(df) + ts = pd.to_timedelta(period).to_pytimedelta().total_seconds() + time_range.append(ts) + for name in estimator_names: + estimators[name].append(rf[f"{serie}_{name}"].mean()) + result = {} + for name in estimator_names: + result[name] = ( + float(np.polyfit(np.log(time_range), np.log(estimators[name]), 1)[0]) / 2.0 + ) + return result + + +@hurst_router.get("/hurst-wiener") +async def hurst_wiener( + sigma: float = Query(2.0, description="Wiener process volatility", ge=0.1, le=10), +) -> HurstWienerResponse: + from quantflow.utils.dates import start_of_day + + seconds_in_day = 24 * 60 * 60 + wiener = WienerProcess(sigma=sigma) + paths = wiener.sample(n=1, time_horizon=1, time_steps=seconds_in_day) + wiener_df = paths.as_datetime_df(start=start_of_day(), unit="d").reset_index() + + dates = [str(d) for d in wiener_df.iloc[:, 0]] + values = [float(v) for v in wiener_df.iloc[:, 1]] + hurst_exp = float(paths.hurst_exponent()) + realized_std = float(paths.paths_std(scaled=True)[0]) + + periods = list(DEFAULT_PERIODS) + pk_vals = [] + gk_vals = [] + rs_vals = [] + template = OHLC( + serie="0", + period="10m", + rogers_satchell_variance=True, + parkinson_variance=True, + garman_klass_variance=True, + ) + for period in periods: + ohlc = template.model_copy(update=dict(period=period)) + rf = ohlc(wiener_df) + ts = pd.to_timedelta(period).to_pytimedelta().total_seconds() + pk_vals.append(_range_std(rf["0_pk"], ts, seconds_in_day)) + gk_vals.append(_range_std(rf["0_gk"], ts, seconds_in_day)) + rs_vals.append(_range_std(rf["0_rs"], ts, seconds_in_day)) + + ohlc_hurst = _ohlc_hurst(wiener_df, "0", DEFAULT_PERIODS) + + return HurstWienerResponse( + dates=dates, + values=values, + hurst_exponent=hurst_exp, + realized_std=realized_std, + estimator_periods=periods, + estimator_pk=pk_vals, + estimator_gk=gk_vals, + estimator_rs=rs_vals, + ohlc_hurst_pk=ohlc_hurst["pk"], + ohlc_hurst_gk=ohlc_hurst["gk"], + ohlc_hurst_rs=ohlc_hurst["rs"], + ) + + +@hurst_router.get("/hurst-vasicek") +async def hurst_vasicek( + kappa: float = Query(10.0, description="Mean reversion speed", ge=1.0, le=500.0), +) -> HurstVasicekResponse: + from quantflow.utils.dates import start_of_day + + p = Vasicek(kappa=kappa) + paths = p.sample(n=1, time_horizon=1, time_steps=24 * 60 * 6) + hurst_real = float(paths.hurst_exponent()) + + pdf = pd.DataFrame( + {"0": paths.path(0)}, index=paths.dates(start=start_of_day()) + ).reset_index() + + ohlc_h = _ohlc_hurst(pdf, "0", VASICEK_PERIODS) + + dates = [str(d) for d in pdf.iloc[:, 0]] + values = [float(v) for v in pdf.iloc[:, 1]] + + return HurstVasicekResponse( + dates=dates, + values=values, + hurst_realized=hurst_real, + hurst_pk=ohlc_h["pk"], + hurst_gk=ohlc_h["gk"], + hurst_rs=ohlc_h["rs"], + ) diff --git a/app/api/rates.py b/app/api/rates.py index 6fb2345..b4549b0 100644 --- a/app/api/rates.py +++ b/app/api/rates.py @@ -1,6 +1,9 @@ +from typing import cast + +import numpy as np from fastapi import APIRouter, Query from pydantic import BaseModel, Field -import numpy as np + from quantflow.rates import AnyYieldCurve, YieldCurve rates_router = APIRouter() @@ -47,8 +50,8 @@ async def yield_curve( curve_class = YieldCurve.get_curve_class(curve_type) if curve_class is None: raise ValueError(f"Unsupported curve type: {curve_type}") - curve = curve_class.calibrate(ttm, rates) + curve = cast(AnyYieldCurve, curve_class.calibrate(ttm, rates)) if max_ttm is not None: ttm = list(np.geomspace(1 / 365, max_ttm, num_points)) - rates = list(curve.continuously_compounded_rate(ttm)) + rates = [float(r) for r in np.atleast_1d(curve.continuously_compounded_rate(ttm))] return YieldCurveResponse(curve=curve, ttm=ttm, rates=rates) diff --git a/app/api/sampling.py b/app/api/sampling.py new file mode 100644 index 0000000..f805057 --- /dev/null +++ b/app/api/sampling.py @@ -0,0 +1,74 @@ +import numpy as np +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.sp.ou import Vasicek +from quantflow.sp.poisson import PoissonProcess +from quantflow.utils import bins +from quantflow.utils.distributions import DoubleExponential + +sampling_router = APIRouter() + + +class SamplingResponse(BaseModel): + x: list[float] = Field(description="Bin centers") + simulation: list[float] = Field(description="Simulated PDF values") + analytical: list[float] = Field(description="Analytical PDF values") + + +class DoubleExponentialResponse(SamplingResponse): + char_x: list[float] = Field(description="X values from characteristic function") + char_y: list[float] = Field(description="Y values from characteristic function") + + +@sampling_router.get("/gaussian-sampling") +async def gaussian_sampling( + kappa: float = Query(1.0, description="Mean reversion speed", ge=0.1, le=5.0), + samples: int = Query(1000, description="Number of sample paths", ge=100, le=10000), +) -> SamplingResponse: + pr = Vasicek(rate=0.5, kappa=kappa) + paths = pr.sample(samples, 1, 1000) + pdf = paths.pdf(num_bins=50) + x = [float(v) for v in pdf.index] + simulation = [float(v) for v in pdf["pdf"]] + analytical = [float(v) for v in np.atleast_1d(pr.marginal(1).pdf(pdf.index))] + return SamplingResponse(x=x, simulation=simulation, analytical=analytical) + + +@sampling_router.get("/poisson-sampling") +async def poisson_sampling( + intensity: float = Query(2.0, description="Poisson intensity", ge=2.0, le=5.0), + samples: int = Query(1000, description="Number of sample paths", ge=100, le=10000), +) -> SamplingResponse: + pr = PoissonProcess(intensity=intensity) + paths = pr.sample(samples, 1, 1000) + pdf = paths.pdf(delta=1) + x = [float(v) for v in pdf.index] + simulation = [float(v) for v in pdf["pdf"]] + analytical = [float(v) for v in np.atleast_1d(pr.marginal(1).pdf(pdf.index))] + return SamplingResponse(x=x, simulation=simulation, analytical=analytical) + + +@sampling_router.get("/double-exponential-sampling") +async def double_exponential_sampling( + log_kappa: float = Query( + 0.1, description="Log of asymmetry parameter", ge=-2.0, le=2.0 + ), + samples: int = Query(1000, description="Number of samples", ge=100, le=10000), +) -> DoubleExponentialResponse: + pr = DoubleExponential.from_moments(kappa=np.exp(log_kappa)) + data = pr.sample(samples) + pdf = bins.pdf(data, num_bins=50, symmetric=0) + x = [float(v) for v in pdf.index] + simulation = [float(v) for v in pdf["pdf"]] + analytical = [float(v) for v in np.atleast_1d(pr.pdf(pdf.index))] + cha = pr.pdf_from_characteristic() + char_x = [float(v) for v in cha.x] + char_y = [float(v) for v in cha.y] + return DoubleExponentialResponse( + x=x, + simulation=simulation, + analytical=analytical, + char_x=char_x, + char_y=char_y, + ) diff --git a/app/api/smoother.py b/app/api/smoother.py new file mode 100644 index 0000000..c499376 --- /dev/null +++ b/app/api/smoother.py @@ -0,0 +1,50 @@ +from datetime import date + +from fastapi import APIRouter, Query +from pydantic import BaseModel, Field + +from quantflow.data.fmp import FMP +from quantflow.ta.ewma import EWMA +from quantflow.ta.supersmoother import SuperSmoother + +smoother_router = APIRouter() + + +class SmootherPoint(BaseModel): + date: str = Field(description="Date string") + close: float = Field(description="Close price") + supersmoother: float = Field(description="SuperSmoother filtered value") + ewma: float = Field(description="EWMA filtered value") + + +class SmootherResponse(BaseModel): + data: list[SmootherPoint] = Field(description="Time series with smoothed values") + + +@smoother_router.get("/supersmoother") +async def supersmoother( + period: int = Query(10, description="Filter period", ge=2, le=100), + symbol: str = Query("BTCUSD", description="Ticker symbol"), +) -> SmootherResponse: + async with FMP() as fmp: + prices = await fmp.prices(symbol, from_date=date(2024, 1, 1)) + sm = ( + prices[["date", "close"]] + .copy() + .sort_values("date", ascending=True) + .reset_index(drop=True) + ) + smoother = SuperSmoother(period=period) + ewma = EWMA(period=period) + sm["supersmoother"] = sm["close"].apply(smoother.update) + sm["ewma"] = sm["close"].apply(ewma.update) + data = [ + SmootherPoint( + date=str(row["date"]), + close=float(row["close"]), + supersmoother=float(row["supersmoother"]), + ewma=float(row["ewma"]), + ) + for _, row in sm.iterrows() + ] + return SmootherResponse(data=data) diff --git a/app/api/volatility.py b/app/api/volatility.py index 619fd67..f2bda01 100644 --- a/app/api/volatility.py +++ b/app/api/volatility.py @@ -1,12 +1,14 @@ from functools import partial +from typing import Any import numpy as np from fastapi import APIRouter, Query from pydantic import BaseModel, Field from quantflow.data.deribit import Deribit +from quantflow.data.yahoo import Yahoo from quantflow.options.inputs import VolSurfaceInputs -from quantflow.options.surface import OptionInfo +from quantflow.options.surface import OptionInfo, VolSurfaceLoader from quantflow.rates.nelson_siegel import NelsonSiegel from .deps import RedisCache, RedisDep @@ -14,6 +16,10 @@ volatility_router = APIRouter() +DERIBIT_ASSETS = {"BTC", "ETH"} +YAHOO_ASSETS = {"SPY", "AAPL", "NVDA"} +ALL_ASSETS = sorted(DERIBIT_ASSETS) + sorted(YAHOO_ASSETS) + class VolSurfaceResponse(BaseModel): inputs: VolSurfaceInputs = Field(description="Volatility surface inputs") @@ -32,9 +38,9 @@ class VolSurfaceResponse(BaseModel): async def volatility_surface( redis: RedisDep, asset: str = Query( - "btc", - description="Crypto asset", - enum=["btc", "eth"], + "BTC", + description="Asset symbol", + enum=ALL_ASSETS, ), ) -> VolSurfaceResponse: cache = RedisCache( @@ -45,18 +51,27 @@ async def volatility_surface( return await cache.from_cache(partial(_volatility_surface, asset)) -def _curve_response(curve, max_ttm: float) -> YieldCurveResponse: +def _curve_response(curve: Any, max_ttm: float) -> YieldCurveResponse: ttm = list(np.linspace(1 / 365, max_ttm, 50)) - rates = list(curve.continuously_compounded_rate(ttm)) - return YieldCurveResponse(curve=curve, ttm=ttm, rates=rates) + rates = [float(r) for r in np.atleast_1d(curve.continuously_compounded_rate(ttm))] + return YieldCurveResponse(curve=curve, ttm=ttm, rates=rates) # type: ignore[arg-type] -async def _volatility_surface(asset: str) -> VolSurfaceResponse: - inverse = asset != "sol" - async with Deribit() as cli: - loader = await cli.volatility_surface_loader(asset, inverse=inverse) +async def _load_surface(asset: str) -> VolSurfaceLoader: + if asset in DERIBIT_ASSETS: + async with Deribit() as cli: + loader = await cli.volatility_surface_loader(asset.lower(), inverse=True) + loader.calibrate_curves(quote_curve=NelsonSiegel) + return loader + else: + async with Yahoo() as cli: + loader = await cli.volatility_surface_loader(asset) + loader.calibrate_curves(quote_curve=NelsonSiegel) + return loader - loader.calibrate_curves(quote_curve=NelsonSiegel) + +async def _volatility_surface(asset: str) -> VolSurfaceResponse: + loader = await _load_surface(asset) surface = loader.surface() surface.bs() surface.disable_outliers() diff --git a/app/cointegration.py b/app/cointegration.py deleted file mode 100644 index b6e9c9b..0000000 --- a/app/cointegration.py +++ /dev/null @@ -1,164 +0,0 @@ -import marimo - -__generated_with = "0.19.7" -app = marimo.App(width="medium") - - -@app.cell -def _(): - import marimo as mo - from app.utils import nav_menu - nav_menu() - return (mo,) - - -@app.cell -def _(mo): - mo.md(r""" - # Cointegration Analysis of Cryptocurrencies - """) - return - - -@app.cell -def _(mo): - from quantflow.data.fmp import FMP - - frequency = mo.ui.dropdown( - options={x.name.replace("_", " "): x.value for x in FMP.freq}, - value="daily", - label="Frequency", - ) - frequency - return FMP, frequency - - -@app.cell -async def _(FMP, frequency): - async with FMP() as cli: - btc = await cli.prices("BTCUSD", convert_to_date=True, frequency=frequency.value) - eth = await cli.prices("ETHUSD", convert_to_date=True, frequency=frequency.value) - sol = await cli.prices("SOLUSD", convert_to_date=True, frequency=frequency.value) - - btc = btc.set_index("date") - eth = eth.set_index("date") - sol = sol.set_index("date") - return btc, eth, sol - - -@app.cell -def _(btc, eth, sol): - # Merge the three price series on the date - prices_3 = btc[['close']].join(eth[['close']], lsuffix='_btc', rsuffix='_eth').join(sol[['close']]) - prices_3.columns = ['btc_close', 'eth_close', 'sol_close'] - prices_3 = prices_3.dropna() - prices_3 - return (prices_3,) - - -@app.cell -def _(prices_3): - import numpy as np - log_prices_3 = np.log(prices_3) - return (log_prices_3,) - - -@app.cell -def _(log_prices_3): - from statsmodels.tsa.vector_ar.vecm import coint_johansen - - # Perform the Johansen cointegration test - # We choose det_order=0 for a constant term in the cointegrating relation - # and k_ar_diff=1 for the number of lags in the VAR model. - johansen_result = coint_johansen(log_prices_3, det_order=0, k_ar_diff=1) - deltas = johansen_result.evec[:, 0] - deltas - - return (deltas,) - - -@app.cell -def _(deltas, log_prices_3): - residuals = log_prices_3.dot(deltas) - residual_mean = residuals.mean() - residuals = residuals - residual_mean - return (residuals,) - - -@app.cell -def _(residuals): - import pandas as pd - import altair as alt - - - # Create a DataFrame for plotting - residuals_df = pd.DataFrame({ - "date": residuals.index, - "residual": residuals.values - }) - - # Create the base chart - base = alt.Chart(residuals_df).encode( - x=alt.X('date:T', title='Date') - ).properties( - title='First Cointegration Residual of BTC, ETH, and SOL', - width='container' - ) - - # Create the line chart for the residuals - line = base.mark_line(color='steelblue').encode( - y=alt.Y('residual:Q', title='Residual (Spread)'), - tooltip=[ - alt.Tooltip('date:T', title='Date'), - alt.Tooltip('residual:Q', title='Residual', format='.4f') - ] - ) - - line.interactive() - return - - -@app.cell -def _(): - return - - -@app.cell -def _(mo): - mo.md(r""" - ### Why Pick the Largest Eigenvalue? - - In the Johansen cointegration test, the eigenvalues ($\lambda$) are sorted in descending order, and each one corresponds to a different potential cointegrating vector. - - **The magnitude of the eigenvalue represents the strength and stability of the corresponding cointegrating relationship.** - - 1. **Strongest Relationship:** The largest eigenvalue corresponds to the linear combination of the time series that is "most stationary." This means the resulting spread (the residuals) has the strongest tendency to revert to its mean over time. - 2. **Statistical Significance:** The test statistics used in the Johansen test (the Trace test and the Maximum Eigenvalue test) are functions of these eigenvalues. These tests help us determine how many statistically significant cointegrating relationships exist, starting from the one associated with the largest eigenvalue. - 3. **Practical Application:** For applications like pairs trading, we want to find the most reliable and predictable long-run equilibrium relationship. By choosing the cointegrating vector associated with the largest eigenvalue, we are selecting the portfolio of assets whose value is most likely to be mean-reverting, making it the best candidate for a statistical arbitrage strategy. - - In short, picking the largest eigenvalue is equivalent to picking the **most significant and stable cointegrating vector** found by the test. - """) - return - - -@app.cell -def _(mo): - mo.md(r""" - ### Should You Use Log Prices? - - **Yes, using log prices is generally recommended** for cointegration analysis in finance. Here is why: - - 1. **Percentage vs. Absolute Changes:** Log-prices allow the model to work with **relative percentage changes** rather than absolute dollar amounts. This is crucial when assets trade at vastly different scales (e.g., BTC at \$60k vs. SOL at \$150). - 2. **Variance Stabilization:** Financial time series often exhibit heteroscedasticity, meaning volatility increases as the price level rises. Log transformation helps stabilize this variance, making the data more suitable for linear statistical models. - 3. **Linearization:** Standard cointegration tests (like Johansen or Engle-Granger) look for linear combinations. Real-world economic relationships between assets are often multiplicative (based on ratios); taking logarithms converts these into linear additive relationships. - """) - return - - -@app.cell -def _(): - return - - -if __name__ == "__main__": - app.run() diff --git a/app/double_exponential_sampling.py b/app/double_exponential_sampling.py deleted file mode 100644 index 4c95adf..0000000 --- a/app/double_exponential_sampling.py +++ /dev/null @@ -1,81 +0,0 @@ -import marimo - -__generated_with = "0.19.7" -app = marimo.App(width="medium") - - -@app.cell -def _(): - import marimo as mo - from app.utils import nav_menu - nav_menu() - return (mo,) - - -@app.cell -def _(mo): - mo.md(r""" - # Double Exponential Sampling - - Here we sample the Asymmetric Laplace distribution, a.k.a double exponential - We will set the mean to 0 and the variance to 1 so that the distribution is fully determined by the asymmetric parameter $\kappa$. - - ```python - from quantflow.utils.distributions import DoubleExponential - ``` - """) - return - - -@app.cell -def _(): - from quantflow.utils.distributions import DoubleExponential - from quantflow.utils import bins - import numpy as np - - def simulate_double_exponential(log_kappa: float, samples: int): - pr = DoubleExponential.from_moments(kappa=np.exp(log_kappa)) - data = pr.sample(samples) - pdf = bins.pdf(data, num_bins=50, symmetric=0) - pdf["simulation"] = pdf["pdf"] - pdf["analytical"] = pr.pdf(pdf.index) - cha = pr.pdf_from_characteristic() - return pdf, cha - return (simulate_double_exponential,) - - -@app.cell -def _(mo): - samples = mo.ui.slider(start=100, stop=10000, step=100, value=1000, debounce=True, label="Samples") - log_kappa = mo.ui.slider(start=-2, stop=2, step=0.1, value=0.1, debounce=True, label="Asymmetry - $\log \kappa$") - - controls = mo.hstack([samples, log_kappa], justify="start") - controls - return log_kappa, samples - - -@app.cell -def _(log_kappa, samples, simulate_double_exponential): - df, cha = simulate_double_exponential(log_kappa.value, samples.value) - return cha, df - - -@app.cell -def _(cha, df): - import plotly.graph_objects as go - - simulation = go.Bar(x=df.index, y=df["simulation"], name="simulation") - analytical = go.Scatter(x=df.index, y=df["analytical"], name="analytical") - characteristic = go.Scatter(x=cha.x, y=cha.y, name="from characteristic", mode="markers") - fig = go.Figure(data=[simulation, characteristic, analytical]) - fig - return - - -@app.cell -def _(): - return - - -if __name__ == "__main__": - app.run() diff --git a/app/frontend/observablehq.config.js b/app/frontend/observablehq.config.js index bb2182a..1dacccd 100644 --- a/app/frontend/observablehq.config.js +++ b/app/frontend/observablehq.config.js @@ -7,7 +7,7 @@ export default { base: "/examples", head: ``, style: "style.css", - pages: [{name: "Volatility Surface", path: "/volatility-surface"}, {name: "Yield Curve", path: "/yield-curve"}], + pages: [{name: "Volatility Surface", path: "/volatility-surface"}, {name: "Yield Curve", path: "/yield-curve"}, {name: "Sampling", path: "/sampling"}, {name: "SuperSmoother", path: "/supersmoother"}, {name: "Cointegration", path: "/cointegration"}, {name: "Hurst Exponent", path: "/hurst"}, {name: "Heston Vol Surface", path: "/heston-vol-surface"}], header: `