Metadata-Version: 2.2
Name: nabla-quant
Version: 0.1.0
Summary: High-performance PDE option pricing engine with AAD Greeks
Keywords: options,pricing,greeks,pde,quantitative-finance,aad,black-scholes,crank-nicolson,finite-difference
Author-Email: "Jai Ansh Singh Bindra and Anubhav Choudhery (JBAC EdTech)" <team@jbac.dev>
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Office/Business :: Financial
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Project-URL: Repository, https://github.com/JaiAnshSB26/NablaQuant
Project-URL: Documentation, https://github.com/JaiAnshSB26/NablaQuant#readme
Project-URL: Issues, https://github.com/JaiAnshSB26/NablaQuant/issues
Requires-Python: >=3.9
Requires-Dist: numpy>=1.24
Requires-Dist: fastapi>=0.100
Requires-Dist: uvicorn>=0.20
Requires-Dist: httpx>=0.24
Requires-Dist: yfinance>=0.2.40
Provides-Extra: sdk
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: httpx>=0.24; extra == "dev"
Description-Content-Type: text/markdown

# 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

```bash
pip install nabla-quant
```

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

```bash
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

```python
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

```bash
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 |

```bash
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).

```bash
# 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:**

```python
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):**

```python
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:**

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

**Example request:**

```bash
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:**

```json
{"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

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

To build without Python bindings:

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

### Running Tests

**C++ tests:**

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

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

```bash
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

```bash
./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

```bash
# 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:

```json
{
  "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:

```bash
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** |

---
