Skip to content

About

Interactive option pricing calculator with Black-Scholes & Monte Carlo simulation. Real-time pricing, Greeks, and convergence charts with C++ acceleration.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Vantage Point — Option Pricing Engine & Site

Python FastAPI C++ NumPy SciPy Docker Render License Status

A quantitative-trading-firm website with a real, working options pricing engine embedded as its centerpiece: a FastAPI backend that prices European options with the Black-Scholes closed-form model and a Monte Carlo simulation, backed by an optional compiled C++ execution path with an automatic Python fallback.

This README explains what the program does, the math behind it, and how every piece fits together — read it top to bottom and you should understand the whole project without opening the code.


Table of contents

  1. What this project is
  2. Topics covered
  3. The math
  4. How the code maps to the math
  5. Project structure
  6. Running it
  7. API reference
  8. Deployment
  9. License

What this project is

There are two things bundled together:

  • A pricing engine — implemented three times over (C++, Python/NumPy, and a JavaScript fallback in the browser), all computing the same math, so the result is identical no matter which one answers your request.
  • A website built around that engine — a "pricing terminal" widget where you type in option contract terms and get live prices, Greeks, and a convergence chart back from the API.

The point of the site is to make the math and the code that implements it directly inspectable and interactive, rather than a static writeup.

Topics covered

If you want to use this repo as a learning reference, here's what it touches:

Area Where
Options pricing theory (Black-Scholes) backend/services/bs_pricer.py
Monte Carlo methods / stochastic simulation backend/services/mc_pricer.py
Geometric Brownian Motion mc_pricer.py, options.cpp
Option Greeks (Delta, Gamma, Vega, Theta, Rho) bs_pricer.py::greeks()
Numerical vs. analytical convergence mc_pricer.py::convergence_series()
C++ for performance-critical numerics backend/cpp_src/options.cpp
Calling compiled binaries from Python (subprocess) services/cpp_wrapper.py
REST API design with FastAPI + Pydantic routers/pricing.py, schemas/pricing.py
Graceful engine fallback (C++ → Python → JS) cpp_wrapper.py, script.js
Multi-stage Docker builds Dockerfile
Cloud deployment (Render) render.yaml, build.sh, start.sh
Frontend data visualization (canvas charting) frontend/script.js

The math

1. Black-Scholes model (analytical)

The Black-Scholes model gives a closed-form price for a European option — no simulation needed, just plug numbers into a formula. It assumes the underlying asset price follows Geometric Brownian Motion under the risk-neutral measure:

dS_t = r · S_t dt + σ · S_t dW_t

Where:

  • S_t — the asset price at time t
  • r — the risk-free interest rate
  • σ (sigma) — volatility of the asset
  • W_t — a Wiener process (Brownian motion / random walk)

Given:

  • S — current spot price of the underlying
  • K — strike price
  • r — risk-free rate
  • σ — volatility
  • T — time to maturity, in years

the call and put prices are:

Call price:  C = S · N(d1) − K · e^(−rT) · N(d2)
Put price:   P = K · e^(−rT) · N(−d2) − S · N(−d1)

where N(x) is the standard normal cumulative distribution function (the probability a standard normal random variable is ≤ x), and:

d1 = [ ln(S/K) + (r + σ²/2) · T ] / (σ · √T)
d2 = d1 − σ · √T

Intuition: d1 and d2 measure how far in-the-money the option is expected to be, in units of volatility-adjusted standard deviations. N(d2) is (approximately) the risk-neutral probability the option finishes in-the-money; N(d1) weights that by the expected payoff.

Implemented in: backend/services/bs_pricer.py (using scipy.stats.norm) and backend/cpp_src/options.cpp (using erfc — the complementary error function, since N(x) = 0.5 · erfc(−x / √2)).

2. Monte Carlo simulation (numerical)

Instead of a formula, Monte Carlo simulates many possible future paths of the stock price and averages the payoff. It's slower and only approximate, but it generalizes to option types that don't have a clean closed-form solution (this project sticks to European options so both methods can be compared directly).

Step 1 — simulate the terminal stock price using the closed-form solution to the GBM equation:

S_T = S · exp[ (r − σ²/2) · T + σ · √T · Z ],   where Z ~ N(0, 1)

Z is a single draw from a standard normal distribution — this is the "random" part. Each simulated path draws a fresh Z and produces one possible future price S_T.

Step 2 — compute the payoff for each simulated path:

Call payoff = max(S_T − K, 0)
Put payoff  = max(K − S_T, 0)

Step 3 — average and discount back to present value:

Price ≈ e^(−rT) · (1/N) · Σ payoff_i     for i = 1..N

By the Law of Large Numbers, as the number of simulated paths N grows, this average converges to the true expected discounted payoff — which, for a European option, is exactly the Black-Scholes price. That convergence is what the site's chart visualizes: the Monte Carlo estimate wobbling around the closed-form Black-Scholes line as N gets larger.

Implemented in: backend/services/mc_pricer.py (vectorized with NumPy — all N paths generated and evaluated in one array operation, no Python loop) and backend/cpp_src/options.cpp (monte_carlo_option_price(), using <random>'s Mersenne Twister generator).

3. The Greeks (sensitivities)

The Greeks measure how the option's price changes as each input changes — i.e. they're partial derivatives of the pricing formula. They're what a trading desk actually watches to manage risk, since "the option is worth $4.30" matters less than "the option's value moves $0.60 for every $1 the stock moves."

Greek Measures sensitivity of price to... Formula (call)
Delta (Δ) the underlying price S N(d1)
Gamma (Γ) the rate of change of Delta itself φ(d1) / (S·σ·√T)
Vega (ν) volatility σ S·φ(d1)·√T
Theta (Θ) time decay, as T shrinks −(S·φ(d1)·σ)/(2√T) − r·K·e^(−rT)·N(d2)
Rho (ρ) the risk-free rate r K·T·e^(−rT)·N(d2)

where φ(x) is the standard normal probability density function (not the cumulative one — it's the "bell curve height" at x, versus N(x) which is the area under the curve up to x). Put Greeks use the mirrored formulas (see bs_pricer.py::greeks()).

These are computed analytically (not simulated) since they fall directly out of differentiating the Black-Scholes formula.

4. Why the two methods agree

Both models start from the same assumption — the underlying follows Geometric Brownian Motion — so they're computing the exact same expectation, just two different ways:

  • Black-Scholes solves that expectation's integral in closed form.
  • Monte Carlo approximates it by sampling and averaging.

That's why, in the pricing terminal, the Monte Carlo call/put prices land close to (but not exactly on) the Black-Scholes prices, and why the convergence chart's simulated line visibly tightens around the analytical line as the path count increases. Small persistent gaps at low path counts are simulation noise, not model disagreement.


How the code maps to the math

Request comes in (S, K, r, σ, T, path count)
        │
        ▼
┌───────────────────────────┐
│ services/cpp_wrapper.py   │  picks an engine: cpp → python fallback
└───────────┬───────────────┘
            │
   ┌────────┴─────────┐
   ▼                   ▼
cpp_src/options.cpp   services/bs_pricer.py  +  services/mc_pricer.py
(compiled binary,     (SciPy norm.cdf for Black-Scholes,
 called via            NumPy vectorized Monte Carlo,
 subprocess)           Greeks via analytical derivatives)
   │                   │
   └─────────┬─────────┘
             ▼
   routers/pricing.py assembles the response:
   BS price, MC price, Greeks, convergence series, engine used, timing
             ▼
   frontend/script.js renders the terminal output + convergence canvas

If neither the compiled binary nor a live backend is reachable at all (e.g. you open index.html directly as a file), frontend/script.js includes a third, self-contained implementation of the same formulas in plain JavaScript, so the page still works standalone.


Project structure

option-pricing-render/
├── frontend/
│   ├── index.html          Marketing site + interactive pricing terminal
│   ├── style.css            Design system (dark, terminal-inspired)
│   └── script.js             Terminal logic + offline JS fallback pricer
│
├── backend/
│   ├── app.py                    FastAPI app, mounts the frontend + API
│   ├── routers/pricing.py        /api/pricing/* endpoints
│   ├── services/
│   │   ├── bs_pricer.py          Black-Scholes + Greeks (SciPy)
│   │   ├── mc_pricer.py          Vectorized NumPy Monte Carlo + convergence
│   │   └── cpp_wrapper.py        Calls the compiled C++ binary, falls back to Python
│   ├── schemas/pricing.py        Pydantic request/response models
│   ├── cpp_src/options.cpp       C++ implementation (CLI-friendly)
│   ├── compile_cpp.py            Compiles options.cpp -> options_bin
│   └── requirements.txt
│
├── render.yaml               Render Blueprint (Docker-based web service)
├── Dockerfile                 Multi-stage build: compile C++, then Python runtime
├── docker-compose.yml         Local dev
├── build.sh / start.sh        Alternative native (non-Docker) Render deploy
├── .env.example
└── .dockerignore

Running it

Docker (recommended)

bash

cd option-pricing-render
docker compose up --build

PowerShell

cd option-pricing-render
docker compose up --build

Then open http://localhost:8000. Stop with Ctrl+C, or docker compose down to remove the container.

Native Python (no Docker)

bash

cd option-pricing-render/backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python compile_cpp.py          # optional — needs g++/clang++; falls back to Python if missing
uvicorn app:app --reload --port 8000

PowerShell

cd option-pricing-render\backend
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install -r requirements.txt
python compile_cpp.py          # optional — needs a C++ compiler on PATH; falls back to Python if missing
uvicorn app:app --reload --port 8000

Then open http://localhost:8000.


API reference

GET /api/pricing/health

Returns engine status:

{ "status": "ok", "cpp_engine_available": true }

POST /api/pricing/price

Request body:

{
  "spot": 100,
  "strike": 100,
  "rate": 0.05,
  "volatility": 0.2,
  "maturity": 1.0,
  "simulations": 100000,
  "engine": "auto"
}
  • engine: "auto" prefers the compiled C++ path and falls back to Python silently; "cpp" forces C++ and errors if it isn't compiled; "python" forces the NumPy/SciPy path.

Response: Black-Scholes call/put prices, Monte Carlo call/put prices, Greeks for both, a convergence series (MC estimate at increasing path counts), which engine actually executed the request, and compute time in milliseconds.


Deployment

Render, Docker (recommended, matches render.yaml):

  1. Push this repo to GitHub.
  2. In Render: New + → Blueprint → point at the repo.

Render, native Python:

  1. New + → Web Service
  2. Build command: bash build.sh
  3. Start command: bash start.sh

See Dockerfile for the exact multi-stage build (compiles the C++ kernel in one stage, copies the binary into a slim Python runtime in the next).


License

MIT — use it, learn from it, adapt it.

About

Interactive option pricing calculator with Black-Scholes & Monte Carlo simulation. Real-time pricing, Greeks, and convergence charts with C++ acceleration.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages