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
malloclatency - 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 referenceGET /api/v1/market/expiries?symbol=SPY- listed option expirationsGET /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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b07623d4d3f361f7e7ac5f25e8e93f7eb97426074fe36d65e1aa9714f43e96c
|
|
| MD5 |
51130cda26b0c71dff73e6453b3f68c2
|
|
| BLAKE2b-256 |
936694eae031eaa3aeef1540b0f2a1fa527e8d8a76760f44651a698aa7c17f63
|
File details
Details for the file nabla_quant-0.1.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: nabla_quant-0.1.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 549.1 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c54b96da8cb351f1e51d8d94e630da91bee49f0d4a6454ff1e08fcfeef4db35c
|
|
| MD5 |
54a6cc1a161b14e689811639db7bbb28
|
|
| BLAKE2b-256 |
a6e585e7d79d7ed86e059e58ec69c8cb99e786905aa4c6f1c6db2889ede4bae4
|