Skip to main content

High-performance PDE option pricing engine with AAD Greeks

Project description

NablaQuant

High-Performance PDE Option Pricing Engine

Caution: Educational/Experimental purposes only, no financial advice intended (Shouldn't be treated as a real-time/money production engine)

An institutional-grade, low-latency pricing engine for solving financial PDEs. Built with a bare-metal C++ core, exposed as a Python SDK, with a real-time WebGL visualization dashboard.


Installation

pip install nabla-quant

This installs the full package: SDK + FastAPI pricing server + market data (yfinance). For the SDK and numpy only:

pip install nabla-quant --no-deps && pip install numpy

Requirements: Python 3.9+, pre-built wheels available for Windows/macOS/Linux (no compiler needed).


Quick Start

Python SDK

import nabla_quant as nq

# European option (Crank-Nicolson PDE)
result = nq.european(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20)
print(result.price)   # 10.4506

# All Greeks via AAD — single backward sweep, same cost as one forward solve
g = nq.greeks(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20)
print(f"Δ={g.delta:.4f}  Γ={g.gamma:.5f}  Θ={g.theta:.4f}  ν={g.vega:.3f}  ρ={g.rho:.3f}")
# Δ=0.6368  Γ=0.01876  Θ=-6.4136  ν=37.524  ρ=53.232

# American option with early exercise (projection method)
result = nq.american(spot=80, strike=100, expiry=1.0, rate=0.05, vol=0.25, option_type="put")
print(result.price)   # 20.3633  (> European 18.26 — early-exercise premium ~$2.10)

# Barrier option — all 8 types: (down/up) x (in/out) x (call/put)
result = nq.barrier(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20,
                    level=80, direction="down", knock="out")
print(result.price)   # 10.3605

# Local volatility — CEV, skew, or custom σ(S, t) surface
result = nq.european(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20,
                     vol_model="cev", vol_params={"beta": 0.5})

# Tune the grid for more accuracy (default: 801 nodes × 800 steps)
result = nq.european(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20,
                     num_nodes=1601, num_time_steps=1600)

Running the API Server

cd python
uvicorn nabla_quant.api.app:app --host 0.0.0.0 --port 8000

REST endpoints:

Method Path Description
GET /api/v1/health Engine status and version
POST /api/v1/greeks Full Greeks via AAD
POST /api/v1/price/european European option pricing
POST /api/v1/price/american American option pricing
POST /api/v1/price/barrier Barrier option (all 8 types)
POST /api/v1/price/local_vol Local volatility pricing
POST /api/v1/price/batch Batch pricing (up to 100 requests)
GET /api/v1/market/quote?symbol=SPY Live spot + dividend yield
GET /api/v1/market/chain?symbol=SPY&expiration=YYYY-MM-DD Option chain with implied vols
WS /ws/pricing Real-time PDE pricing over WebSocket
curl -X POST http://localhost:8000/api/v1/greeks \
  -H "Content-Type: application/json" \
  -d '{"spot":100,"strike":100,"expiry":1.0,"rate":0.05,"volatility":0.20}'
# {"price":10.4506,"delta":0.6368,"gamma":0.01876,"theta":-6.414,"vega":37.524,"rho":53.232}

Running the Real-Time Dashboard

Requires the API server running (above).

# Terminal 1 — API backend
cd python && uvicorn nabla_quant.api.app:app --host 0.0.0.0 --port 8000

# Terminal 2 — Next.js dashboard
cd dashboard && npm install && npm run dev
# Open http://localhost:3000

The dashboard works in two modes:

  • Online: WebSocket to the API — displays C++ PDE prices + live Greeks
  • Offline: Browser-side Black-Scholes fallback — all panels still functional

Phase 1: Mathematical Foundation & Discretization

Phase 1 maps the continuous Black-Scholes PDE to a discrete computational domain, optimized for both accuracy and performance.

Non-Uniform Mesh Generation

Standard uniform grids waste compute: they over-sample the deep ITM/OTM tails where the option value is nearly linear, while under-sampling the critical region near the strike where curvature (Gamma) is highest.

We implement a hyperbolic sine coordinate transformation that solves this problem:

Forward transform (computational → physical):

S(ξ) = K + c · sinh(ξ)

Inverse transform:

ξ(S) = arcsinh((S − K) / c)

Jacobian:

dS/dξ = c · cosh(ξ)

Where:

  • K is the strike price (clustering center)
  • c is the concentration parameter (smaller c → tighter clustering)
  • ξ lives on a uniform computational grid

The Jacobian c · cosh(ξ) is minimized at ξ = 0 (i.e., S = K), so uniform ξ-spacing produces the smallest physical spacing precisely where we need the most resolution. The cosh function grows exponentially in the tails, naturally sparsifying the far-field grid.

Concentration parameter effect (N = 201, domain [0, 300], K = 100):

c Min Δs Max Δs Ratio
2 0.099 9.663 97.5
10 0.334 6.586 19.7
20 0.531 5.267 9.9
100 1.163 2.586 2.2
500 1.472 1.584 1.1

Boundary Conditions

The PDE domain requires three conditions:

Terminal condition (t = T):

Call: V(S, T) = max(S − K, 0)
Put:  V(S, T) = max(K − S, 0)

Lower spatial boundary (S → 0):

Call: V(0, t) = 0
Put:  V(0, t) = K · exp(−r · τ) − S_min · exp(−q · τ)

Upper spatial boundary (S → S_max):

Call: V(S_max, t) = S_max · exp(−q · τ) − K · exp(−r · τ)
Put:  V(S_max, t) = 0

Where τ = T − t is the time to maturity, r is the risk-free rate, and q is the continuous dividend yield.


Phase 2: Core Engine & Bare-Metal Optimization

The solver is built in pure, dependency-free C++17, shifting the bottleneck from algorithmic complexity to hardware execution limits.

Crank-Nicolson Scheme

The Black-Scholes PDE is discretized using the unconditionally stable θ-scheme (θ = 0.5):

∂V/∂τ = ½σ²S² ∂²V/∂S² + (r-q)S ∂V/∂S - rV

Second-order accurate finite differences on the non-uniform SinhMesh produce a tridiagonal system at each time step. Both first and second spatial derivatives use the full non-uniform stencil for O(h²) accuracy.

Thomas Algorithm - O(N)

A custom tridiagonal solver advances the grid backward in time without any matrix inversion libraries. Forward elimination followed by back substitution in O(N) with pre-allocated scratch workspace.

Memory Arena Allocator

All solver workspace (coefficient arrays, tridiagonal system, volatility vectors) is pre-allocated from a single 64-byte aligned contiguous memory block at construction time. The hot time-stepping loop performs zero heap allocations, guaranteeing:

  • Cache-local memory access patterns
  • No fragmentation or malloc latency
  • Deterministic execution time

SIMD Vectorization (AVX2)

The innermost loops, PDE coefficient computation and RHS assembly, are accelerated with AVX2 intrinsics, processing 4 doubles per cycle (256-bit registers) with FMA (fused multiply-add) support. Automatic scalar fallback on non-AVX2 platforms.

Local Volatility Surfaces

The engine supports σ(S, t) via the LocalVolSurface class:

  • Flat: constant volatility (reduces to standard BS)
  • CEV: σ(S) = σ₀(S/S₀)^(β−1) for leverage effects
  • Skew: σ(S,t) = σ_atm + skew·ln(S/K) + term·√t
  • Custom: arbitrary user-defined std::function

When local vol is active, PDE coefficients are recomputed at each time step at the midpoint time.

American Option Early Exercise

Early-exercise boundary conditions are handled via the projection method: after each Crank-Nicolson step, the solution is projected onto the constraint V(S) ≥ payoff(S). The payoff vector is pre-computed in the arena for zero-allocation enforcement.


Phase 3: Sensitivities & Adjoint Algorithmic Differentiation (AAD)

Calculating risk sensitivities (the Greeks) efficiently is often more critical than the price itself. Phase 3 implements three complementary methods.

Direct Spatial Greeks: O(1) Extra Cost

Delta (Δ) and Gamma (Γ) are computed directly from the solved grid using second-order non-uniform finite differences. No additional PDE solves required.

Delta (non-uniform central difference):

Δ_i = [h_m² V_{i+1} + (h_p² − h_m²) V_i − h_p² V_{i-1}] / [h_m h_p (h_m + h_p)]

Gamma (non-uniform second derivative):

Γ_i = 2/(h_m + h_p) · [(V_{i+1} − V_i)/h_p − (V_i − V_{i-1})/h_m]

Theta from the Black-Scholes PDE identity (exact, no extra computation):

Θ = rV − (r−q)SΔ − ½σ²S²Γ

Adjoint Algorithmic Differentiation (AAD)

Standard "bumping" (re-running the PDE with perturbed parameters) requires O(P) full PDE solves for P parameters. AAD replaces this with a single backward sweep over the Crank-Nicolson computational graph, computing all parametric sensitivities simultaneously in O(N_t · N_S), the same cost as one forward solve.

The adjoint equation at each backward time step:

μ = (I − θΔt L)⁻ᵀ λ           # Adjoint Thomas solve (swap lower ↔ upper)
λ ← (I + (1−θ)Δt L)ᵀ μ         # Propagate through explicit part
∂f/∂p += μᵀ · Δt · (∂L/∂p) · W  # Accumulate parameter sensitivity

where W = (1−θ)V^n + θV^{n+1} is the Crank-Nicolson weighted average, and ∂L/∂p is the sensitivity of the spatial operator to parameter p.

Computed via AAD:

  • Vega (ν) = ∂V/∂σ - sensitivity to volatility
  • Rho (ρ) = ∂V/∂r - sensitivity to interest rate (includes boundary value sensitivity)

Accuracy vs Black-Scholes Analytical (ATM Call, S=K=100, T=1, r=5%, σ=20%)

Greek Analytical AAD Engine Error
Price 10.4506 10.4506 2.4e-5
Delta 0.6368 0.6368 -8e-6
Gamma 0.01876 0.01876 -3e-7
Theta -6.4140 -6.4140 7.2e-5
Vega 37.524 37.523 -6.2e-4
Rho 53.232 53.232 -3.8e-4

Bump-and-Revalue Reference

A full bump-and-revalue implementation is provided for independent validation: central differences in σ and r (4 re-solves), grid interpolation for Δ/Γ, forward difference for Θ. The AAD and bump results agree to high precision across all test cases.


Phase 4: Barrier Options & Exotic Pricing

Barrier options enforce absorbing boundary conditions within the PDE time-stepping loop, dynamically zeroing (or rebating) the grid when the asset price breaches a predefined barrier.

Barrier Types

All 8 standard European barrier types are supported:

Direction Knock Description
Down Out Killed if S ever touches B (B < S)
Down In Activates only if S touches B
Up Out Killed if S ever touches B (B > S)
Up In Activates only if S touches B

Each type works with both calls and puts, giving 8 total combinations.

Knock-Out Implementation

After each Crank-Nicolson time step, the absorbing barrier is enforced:

for each node i:
    if direction == Down and S[i] <= B:  V[i] = rebate * e^{-rτ}
    if direction == Up   and S[i] >= B:  V[i] = rebate * e^{-rτ}

The implicit boundary contributions in the tridiagonal RHS are also corrected: domain boundaries beyond the barrier use the barrier value (rebate) instead of the vanilla Dirichlet conditions, preventing value leakage through the tridiagonal coupling.

Knock-In via In-Out Parity

Knock-in options are computed without a separate PDE solve:

V_knock_in = V_vanilla − V_knock_out

This identity is exact and verified to hold to 6+ decimal places in the PDE.

Analytical Validation

Rubinstein-Reiner (1991) closed-form formulas for DOC, UOC, DOP, and UOP under constant volatility are provided for test validation. The PDE converges to these analytical values with grid refinement:

Grid DOC Price Error vs Analytical
101×100 10.400 0.049
201×200 10.383 0.032
401×400 10.371 0.020
801×800 10.364 0.012
1601×1600 10.359 0.008

Features

  • Rebate support: optional cash rebate paid on knock-out
  • Local volatility: barriers work with CEV, skew, and custom σ(S, t) surfaces
  • Input validation: barrier level must be within mesh domain; up barriers must exceed spot; down barriers must be below spot

Phase 5: Python SDK & FastAPI Layer

The C++ engine is accessible from Python without sacrificing execution speed, enabling direct integration into ML/trading ecosystems.

Python SDK (nabla_quant)

The pybind11 bindings expose the full C++ API as a native Python module with zero-copy numpy array returns.

High-level convenience API:

import nabla_quant as nq

# European option
result = nq.european(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20)
print(result.price)   # 10.4506

# All Greeks via AAD - single backward sweep
g = nq.greeks(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20)
print(f"Δ={g.delta:.4f}  Γ={g.gamma:.5f}  Θ={g.theta:.4f}  ν={g.vega:.3f}  ρ={g.rho:.3f}")

# American option with early exercise
result = nq.american(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20, option_type="put")

# Barrier option (all 8 types supported)
result = nq.barrier(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20,
                    level=80, direction="down", knock="out")

# Local volatility (CEV model)
result = nq.european(spot=100, strike=100, expiry=1.0, rate=0.05, vol=0.20,
                     vol_model="cev", vol_params={"beta": 0.5})

Low-level access (direct C++ bindings):

from nabla_quant import SinhMesh, BoundaryConditions, CrankNicolsonSolver, LocalVolSurface

mesh = SinhMesh(strike=100, s_min=0, s_max=400, num_nodes=801, concentration=12.5)
# mesh.spot_nodes → numpy array, mesh.spacing_ratio, mesh.strike_index, etc.

FastAPI Pricing API

An async REST API dispatches pricing requests to the C++ engine via a thread pool, keeping the event loop free for I/O.

Endpoints:

Method Path Description
GET /api/v1/health Engine status and version
POST /api/v1/price/european European option pricing
POST /api/v1/price/american American option pricing
POST /api/v1/price/barrier Barrier option pricing
POST /api/v1/greeks Full Greeks via AAD
POST /api/v1/price/local_vol Local volatility pricing
POST /api/v1/price/batch Batch pricing (up to 100 requests)

Running the API server:

cd python && PYTHONPATH=. uvicorn nabla_quant.api.app:app --host 0.0.0.0 --port 8000

Example request:

curl -X POST http://localhost:8000/api/v1/greeks \
  -H "Content-Type: application/json" \
  -d '{"spot": 100, "strike": 100, "expiry": 1.0, "rate": 0.05, "volatility": 0.20}'

Response:

{"price": 10.4506, "delta": 0.6368, "gamma": 0.01876, "theta": -6.414, "vega": 37.524, "rho": 53.232}

All request parameters are validated via Pydantic: spot/strike/vol must be positive, rate in [-0.5, 2.0], grid size bounded [51, 10001], etc. Invalid requests return 422 with descriptive errors.


Building

Requirements: CMake ≥ 3.20, C++17 compiler with AVX2 support (GCC ≥ 7, Clang ≥ 5, or MSVC ≥ 2017), Python ≥ 3.8 with pybind11

cmake -B build -DCMAKE_BUILD_TYPE=Release -DNABLA_BUILD_PYTHON=ON
cmake --build build

To build without Python bindings:

cmake -B build -DCMAKE_BUILD_TYPE=Release -DNABLA_BUILD_PYTHON=OFF
cmake --build build

Running Tests

C++ tests:

cd build && ctest --output-on-failure

Python tests (SDK + API + market + real-world ≈ 119 tests):

cd python && PYTHONPATH=. python -m pytest tests/ -v

C++ test summary: 14 test suites:

  • MeshTests (22): construction, monotonicity, clustering, transform invertibility, Jacobian verification
  • BoundaryTests (21): payoff, terminal condition, Dirichlet boundaries, put-call parity
  • IntegrationTests (19): mesh+boundary combined, refinement, Greeks pipeline, convergence, edge cases
  • ThomasTests (13): tridiagonal solver correctness, residual checks, symmetry, M-matrix positivity
  • SolverTests (15): Crank-Nicolson vs Black-Scholes analytical (ATM/ITM/OTM, put-call parity, convergence)
  • ArenaTests (14): allocation, alignment, overflow, move semantics, PDE loop simulation
  • SIMDTests (9): AVX2 vs scalar exact match, dispatch, solver integration
  • LocalVolTests (13): flat/CEV/skew models, leverage effect, grid non-negativity
  • AmericanTests (11): early exercise premium, intrinsic floor, deep-ITM, BAW benchmark, CEV compatibility
  • GreeksTests (18): spatial Delta/Gamma/Theta vs BS analytical, surface properties, dividends
  • GreeksAADTests (18): AAD Vega/Rho vs BS analytical, AAD vs bump-and-revalue cross-validation
  • GreeksValidationTests (32): deep OTM/ITM limits, zero rate, high/low vol, dividends, near-expiry, convergence, constructor validation, AAD stability
  • BarrierTests (28): DOC/UOC/DOP/UOP vs Rubinstein-Reiner analytical, in-out parity (all 8 types), rebate, local vol, convergence, edge cases, input validation

Running the Demos

./build/examples/phase1_demo    # Mesh & boundary visualization
./build/examples/phase2_demo    # Full pricing engine demonstration
./build/examples/phase3_demo    # Greeks & AAD comparison vs analytical
./build/examples/phase4_demo    # Barrier options: all 8 types, parity, local vol, convergence

Project Structure

NablaQuant/
├── CMakeLists.txt
├── include/nabla/
│   ├── nabla.hpp              # Top-level include
│   └── core/
│       ├── types.hpp          # MarketParams, ContractParams, ExerciseType
│       ├── mesh.hpp           # SinhMesh: non-uniform grid generation
│       ├── boundary.hpp       # BoundaryConditions: terminal + Dirichlet
│       ├── thomas.hpp         # ThomasSolver: O(N) tridiagonal solver
│       ├── arena.hpp          # Arena: zero-allocation memory pool
│       ├── simd.hpp           # AVX2-vectorized PDE operations
│       ├── local_vol.hpp      # LocalVolSurface: σ(S, t) models
│       ├── solver.hpp         # CrankNicolsonSolver: PDE engine
│       ├── greeks.hpp         # GreeksCalculator: AAD + spatial Greeks
│       └── barrier.hpp        # BarrierPricer: knock-out/knock-in options
├── src/core/
│   ├── mesh.cpp, boundary.cpp, thomas.cpp, solver.cpp
│   ├── arena.cpp, simd.cpp, local_vol.cpp
│   ├── greeks.cpp             # AAD backward sweep + spatial Greeks
│   └── barrier.cpp            # Barrier option PDE + analytical formulas
├── tests/
│   ├── test_harness.hpp
│   ├── test_mesh.cpp, test_boundary.cpp, test_integration.cpp
│   ├── test_thomas.cpp, test_solver.cpp, test_arena.cpp
│   ├── test_simd.cpp, test_local_vol.cpp, test_american.cpp
│   ├── test_greeks.cpp        # Spatial Greeks vs BS analytical
│   ├── test_greeks_aad.cpp    # AAD vs BS + AAD vs bump-and-revalue
│   ├── test_greeks_validation.cpp  # 32 rigorous edge/exceptional case tests
│   └── test_barrier.cpp       # 34 barrier option tests + parity checks
├── examples/
│   ├── phase1_demo.cpp
│   ├── phase2_demo.cpp
│   ├── phase3_demo.cpp        # Greeks & AAD demonstration
│   └── phase4_demo.cpp        # Barrier options demonstration
├── python/
│   ├── CMakeLists.txt           # pybind11 module build
│   ├── bindings.cpp             # C++ ↔ Python bindings
│   ├── requirements.txt
│   ├── nabla_quant/
│   │   ├── __init__.py          # High-level Pythonic API
│   │   └── api/
│   │       ├── __init__.py
│   │       ├── app.py           # FastAPI + WebSocket endpoints
│   │       └── models.py        # Pydantic request/response models
│   └── tests/
│       ├── conftest.py
│       ├── test_sdk.py          # 76 SDK validation tests
│       ├── test_api.py          # 30 API endpoint tests
│       └── test_real_world.py   # 12 market-realistic validation tests
└── dashboard/
    ├── package.json               # Next.js 16 + react-plotly.js
    ├── src/
    │   ├── app/
    │   │   ├── layout.tsx         # Root layout with dark theme
    │   │   ├── page.tsx           # Main dashboard page
    │   │   └── globals.css        # Quant-terminal dark theme
    │   ├── components/
    │   │   ├── Header.tsx         # NablaQuant header bar
    │   │   ├── ParameterPanel.tsx # Interactive parameter sliders
    │   │   ├── PricingCard.tsx    # Live Greeks display (Δ, Γ, Θ, ν, ρ)
    │   │   ├── SurfacePlot.tsx    # 3D option value surface V(S, T)
    │   │   ├── VolSurfacePlot.tsx # 3D implied vol surface σ(S, T)
    │   │   ├── GreeksHeatmap.tsx  # Selectable Greeks heatmap
    │   │   ├── PlotlyChart.tsx    # Dynamic Plotly import (SSR-safe)
    │   │   └── StatusBar.tsx      # Connection + compute time status
    │   └── lib/
    │       ├── types.ts           # TypeScript interfaces
    │       └── useNablaSocket.ts  # WebSocket hook with auto-reconnect
    └── .env.local                 # WS_URL configuration

Phase 5 (continued): Real-Time Visualization Dashboard

Architecture

┌──────────────────┐       WebSocket        ┌─────────────────┐       pybind11      ┌──────────────┐
│  Next.js + Plotly │  ←─────────────────→  │  FastAPI + WS   │  ←─────────────→   │  C++ PDE Core │
│  (React frontend) │   /ws/pricing         │  (Python SDK)   │   _nabla_core      │  (CN + AAD)   │
└──────────────────┘                        └─────────────────┘                     └──────────────┘

Dashboard Features

Panel Description
Parameter Sliders S, K, T, r, σ, q: instant recalc on change (250ms debounce)
Greeks Card Live Δ, Γ, Θ, ν, ρ with color-coded sign indicators
Option Value Surface 3D Plotly surface V(S, T): blue colorscale, rotatable
Volatility Surface 3D implied vol σ(S, T) with skew + term structure: purple colorscale
Greeks Heatmap 2D heatmap of any Greek over (S, T) with selectable tabs
Status Bar WebSocket connection status, compute latency

Running the Dashboard

# 1. Start the FastAPI backend (requires built C++ extension)
cd python && uvicorn nabla_quant.api.app:app --host 0.0.0.0 --port 8000

# 2. Start the Next.js dashboard
cd dashboard && npm run dev
# Open http://localhost:3000

The dashboard works in two modes:

  • Online (WebSocket): Connects to the FastAPI backend for C++ PDE pricing
  • Offline (analytical): Falls back to Black-Scholes formulas computed in the browser

WebSocket Protocol

The /ws/pricing endpoint accepts JSON messages:

{
  "type": "single_price | surface | greeks_surface | vol_surface",
  "params": { "spot": 100, "strike": 100, "expiry": 1.0, "rate": 0.05, "volatility": 0.2 },
  "spot_range": [50, 150],
  "expiry_range": [0.05, 1.0],
  "spot_steps": 30,
  "expiry_steps": 20
}

Response includes compute time in milliseconds for performance monitoring. Optional request_id is echoed so the dashboard can ignore out-of-order replies.

Delayed market data (yfinance)

Optional endpoints for demos (not production trading data):

  • GET /api/v1/market/quote?symbol=SPY - last close as spot reference
  • GET /api/v1/market/expiries?symbol=SPY - listed option expirations
  • GET /api/v1/market/chain?symbol=SPY&expiration=YYYY-MM-DD - near-ATM calls/puts with implied vol

Requires pip install yfinance (listed in python/requirements.txt). The dashboard sidebar includes Real-World Options (Yahoo) to pull a quote, load a chain, and apply strike / IV / expiry to the sliders.

Copy dashboard/.env.example to dashboard/.env.local and set NEXT_PUBLIC_API_URL if the API is not on localhost:8000.


Pipeline Stress Test

Full end-to-end validation across 16 option contracts: standard, high-value/institutional, live-market-calibrated, exotic, and invalid inputs. Run with:

cd <repo root>
PYTHONPATH=python python python/tests/stress_pipeline.py

Test Matrix

# Contract Type S K T (yr) r% vol% q% PDE $ BS $ Err% Notes
1 ATM EUR call European 100 100 1.000 5.00 20.0 0.00 10.4505 10.4506 < 0.01% Benchmark case
2 OTM EUR put European 100 110 0.500 5.00 20.0 0.00 10.1905 10.1906 < 0.01% 10% OTM
3 ITM EUR call European 110 100 1.000 5.00 20.0 0.00 17.6629 17.6630 < 0.01% 10% ITM
4 1-week ATM call European 100 100 0.020 5.00 25.0 0.00 1.4602 1.4602 < 0.01% Short-dated
5 2yr div call European 100 100 2.000 5.00 20.0 4.00 11.2183 11.2183 < 0.01% High continuous div
6 Deep-ITM AM put American 80 100 1.000 5.00 25.0 0.00 20.3633 18.2645 (EU) +11.5% (AM) Early-exercise premium $2.10
7 SPX-scale ATM European 5300 5300 0.250 4.50 16.0 1.40 189.037 189.038 < 0.01% Institutional scale
8 SPY live ATM European 679.5 679 0.011 3.59 13.9 0.83 4.2808 4.2806 0.01% Live: mkt mid $4.62
9 AAPL live ATM European 260.5 260 0.016 3.59 24.0 0.40 3.5105 3.5106 < 0.01% Live: mkt mid $3.63
10 MSFT live ATM European 370.9 370 0.016 3.59 26.4 0.93 5.5278 5.5276 < 0.01% Live: mkt mid $5.83
11 DOC barrier Barrier (DOC) 100 100 1.000 5.00 20.0 0.00 10.3605 10.4506 (van) Barrier B=80, knock-out reduces value
12 UOC barrier Barrier (UOC) 100 100 1.000 5.00 20.0 0.00 3.3915 10.4506 (van) Barrier B=130, strong knock-out effect
13 CEV local vol LV European 100 100 1.000 5.00 20.0 0.00 10.4538 10.4506 0.03% β=0.5, leverage effect
14 Crisis vol European 100 100 0.500 2.00 80.0 0.00 22.6602 22.6603 < 0.01% σ=80% stress scenario
15 3-day near-expiry European 100 101 0.008 5.00 20.0 0.00 0.3460 0.3459 0.03% High gamma near expiry
16 INVALID: spot < 0 −10 100 1.000 API: HTTP 422

Dates and live-market prices are sourced from Yahoo Finance at the time of the test run (Apr 2026). Strikes and IVs reflect the nearest-ATM contract on the next available expiration.

Greeks: AAD vs Black-Scholes Analytical (Case #1 - ATM Call)

Greek Analytical PDE/AAD Abs Error
Price 10.4506 10.4505 1.0e-4
Delta (Δ) 0.6368 0.6368 < 1e-4
Gamma (Γ) 0.01876 0.01876 < 1e-5
Theta (Θ) −6.414 −6.414 < 1e-3
Vega (ν) 37.524 37.524 < 0.001

Live Market Calibration

PDE prices use constant-volatility Black-Scholes. Discrepancy vs mid-market arises from discrete dividends, volatility skew, bid-ask spread, and microstructure effects, all outside the constant-vol model by design.

Ticker Spot Strike T (days) IV r q PDE $ Mkt mid $ PDE/Mkt
SPY 679.46 679 4 13.9% 3.59% 0.83% 4.2808 4.6150 0.928
AAPL 260.48 260 6 24.0% 3.59% 0.40% 3.5105 3.6250 0.968
MSFT 370.87 370 6 26.4% 3.59% 0.93% 5.5278 5.8250 0.949

PDE/Market of 0.93–0.97 for short-dated ATM options is expected for a constant-vol model. The gap closes when local-vol or stochastic-vol surfaces are plugged in via the LocalVolSurface interface.

Barrier & Structural Checks

Check Result
In-out parity: DOC + DIC = vanilla 10.3605 + 0.0901 = 10.4506 error = 0.00e+00 ✓
American early-exercise premium (deep ITM put) AM $20.36 − EU $18.26 = $2.10 premium > 0 ✓
Put-call parity at SPX scale C − P = $40.77 ; Se^(−qT) − Ke^(−rT) = $40.77 error = 0.0000 ✓

API Input Validation (Pydantic / HTTP 422)

Invalid Input HTTP Status
vol > 500% 422 Rejected
spot < 0 422 Rejected
strike = 0 422 Rejected
expiry = 0 422 Rejected

Summary

Metric Result
Contracts tested 16
BS pricing error (European, avg) < 0.005%
Live PDE/Market ratio range 0.928 – 0.968
In-out barrier parity error 0.00e+00
Put-call parity error (SPX scale) 0.0000
Invalid inputs rejected (API) 4 / 4
Total C++ compute time (16 cases) < 750 ms

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nabla_quant-0.1.0.tar.gz (208.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nabla_quant-0.1.0-cp312-cp312-win_amd64.whl (549.1 kB view details)

Uploaded CPython 3.12Windows x86-64

File details

Details for the file nabla_quant-0.1.0.tar.gz.

File metadata

  • Download URL: nabla_quant-0.1.0.tar.gz
  • Upload date:
  • Size: 208.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for nabla_quant-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1b07623d4d3f361f7e7ac5f25e8e93f7eb97426074fe36d65e1aa9714f43e96c
MD5 51130cda26b0c71dff73e6453b3f68c2
BLAKE2b-256 936694eae031eaa3aeef1540b0f2a1fa527e8d8a76760f44651a698aa7c17f63

See more details on using hashes here.

File details

Details for the file nabla_quant-0.1.0-cp312-cp312-win_amd64.whl.

File metadata

File hashes

Hashes for nabla_quant-0.1.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 c54b96da8cb351f1e51d8d94e630da91bee49f0d4a6454ff1e08fcfeef4db35c
MD5 54a6cc1a161b14e689811639db7bbb28
BLAKE2b-256 a6e585e7d79d7ed86e059e58ec69c8cb99e786905aa4c6f1c6db2889ede4bae4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page