Python & Numerical Computing · Sep 2026 · 13 min read

A Monte Carlo Option Pricer, Checked Against the Closed Form

European option pricing via Monte Carlo simulation of GBM, validated against Black-Scholes — real spot price, real historical volatility, and a measured convergence curve instead of an asserted one.

Checking a Simulation Against the Answer You Already Know

Most of the numerical-Python work in this portfolio is FinOps pipelines and cloud cost data — this one is different: a Monte Carlo European option pricer, checked against the closed-form Black-Scholes price it should converge to. The reason to build this alongside FinOps work isn't a pivot into derivatives trading; it's the same underlying skill — numerical simulation, variance reduction, and being honest about what a model's confidence interval actually claims — applied to a problem that happens to have an exact answer to check against, which makes correctness bugs impossible to hide.

Live repo: monte-carlo-option-pricer

Before Step 1, one term this walkthrough leans on:


Step 1 — Ground the inputs in a real ticker, not invented numbers

def fetch_spot_and_realized_vol(ticker: str, lookback_days: int = 252) -> dict:
    hist = yf.Ticker(ticker).history(period="1y")
    closes = hist["Close"].tail(lookback_days + 1)
    log_returns = np.log(closes / closes.shift(1)).dropna()

    spot = float(closes.iloc[-1])
    annualized_vol = float(log_returns.std() * np.sqrt(252))
    return {"spot": spot, "annualized_realized_vol": annualized_vol, ...}

Running this against AAPL on 2026-09-17: a real spot price of $335.63, and a real annualized realized volatility of 0.2509 computed from 251 actual trading days of log returns — not a textbook sigma = 0.2 picked because it's round.

The honest limitation worth stating up front: this is realized (backward-looking, historical) volatility, not implied volatility from a live options-chain feed. Real implied vol needs a paid market-data source this project doesn't have. Realized vol is a standard, defensible substitute for illustration — it is not a claim that this matches what the option would actually trade at on an exchange today.


Step 2 — Simulate terminal prices under GBM, with antithetic variates

def simulate_terminal_prices(S0, r, sigma, T, n_paths, seed=None, antithetic=True):
    rng = np.random.default_rng(seed)
    if antithetic:
        half = n_paths // 2
        z = rng.standard_normal(half)
        z = np.concatenate([z, -z])
    else:
        z = rng.standard_normal(n_paths)

    drift = (r - 0.5 * sigma**2) * T
    diffusion = sigma * np.sqrt(T) * z
    return S0 * np.exp(drift + diffusion)

This is the same log-normal price process Black-Scholes assumes analytically — which is exactly what makes Step 4's comparison meaningful. If this simulation used a different process (a jump-diffusion model, say), a mismatch against Black-Scholes would prove nothing about correctness. Using the same process means a mismatch can only mean a bug.


Step 3 — Price the option and report the real standard error

discounted = np.exp(-r * T) * payoffs
price = discounted.mean()
std_error = discounted.std(ddof=1) / np.sqrt(n_paths)

The standard error isn't decoration — it's the actual, honest uncertainty on the Monte Carlo estimate, computed from the discounted payoffs' own sample variance. A price without this number is an unfalsifiable claim; a price with it can be checked.


Step 4 — Verify convergence against Black-Scholes, don't just assert it

def black_scholes_price(S0, K, r, sigma, T, option_type="call"):
    d1 = (math.log(S0/K) + (r + 0.5*sigma**2)*T) / (sigma*math.sqrt(T))
    d2 = d1 - sigma*math.sqrt(T)
    return S0*norm.cdf(d1) - K*math.exp(-r*T)*norm.cdf(d2)

Running the full pricer against real AAPL data, a 5%-out-of-the-money call, 6 months to expiry:

Black-Scholes closed-form call price: $19.8300

  n_paths=    1,000  MC price=$18.5138  std_error=±$1.0700  error_vs_BS=$-1.3162
  n_paths=   10,000  MC price=$19.7065  std_error=±$0.3583  error_vs_BS=$-0.1235
  n_paths=  100,000  MC price=$19.8804  std_error=±$0.1134  error_vs_BS=$+0.0504
  n_paths=1,000,000  MC price=$19.8468  std_error=±$0.0357  error_vs_BS=$+0.0168
  n_paths=5,000,000  MC price=$19.8276  std_error=±$0.0160  error_vs_BS=$-0.0024

The error shrinks from -$1.32 at 1,000 paths to -$0.002 at 5,000,000 paths — a real, measured convergence, not a plot manufactured to look convincing. This is the actual claim the project makes, and it's checked, not asserted: the repo's test suite verifies the closed-form price falls inside the Monte Carlo estimate's own 99.7% confidence interval (3 standard errors), which is a real statistical check rather than a fixed tolerance picked to make a test pass.


Step 5 — Confirm variance reduction is real, not decorative

def test_antithetic_variates_reduce_standard_error():
    with_antithetic = monte_carlo_price(..., antithetic=True)
    without_antithetic = monte_carlo_price(..., antithetic=False)
    assert with_antithetic["std_error"] < without_antithetic["std_error"]

It would be easy to add antithetic variates to a pricer and never verify they're actually reducing variance — this test exists specifically so that claim is checked on every run, at the same path count, same seed, same everything except the technique itself.


Closing Thoughts

What real market data and a real closed-form check add here, that a from-scratch toy example wouldn't: an actual measured convergence curve against AAPL's actual volatility, not a synthetic example tuned to converge quickly; and a test suite that checks statistical claims (convergence-within-CI, variance actually reduced) instead of asserting them in a README. The honest limitations — realized vs. implied volatility, European-only, the GBM assumption both this simulation and Black-Scholes share — are stated directly rather than smoothed over, the same discipline used throughout this portfolio's other numerical and infrastructure work.

GitHub Repository: monte-carlo-option-pricer — the pricer, the Black-Scholes reference, the real-data fetch, and the five-test suite this article's convergence numbers came from.

Reviewed against AAPL market data as of 2026-09-17.

Python · Quantitative Finance · Monte Carlo Simulation · NumPy · SciPy