Skip to content
CONTENTS
Press a letter to jump to a section

One option, five pricing methods: a reproducible Python lab

Published Updated 6 min readOptions pricing:Part 1Part 2Part 3Part 4Part 5Part 6Part 7Part 8

A formula, a tree, a simulation, and a Fourier integral should not be compared by changing the contract every time.

In this Python option-pricing lab, we hold the European payoff and geometric Brownian motion (GBM) assumptions fixed. We compare Black–Scholes, a Cox–Ross–Rubinstein binomial tree, Monte Carlo, Fourier quadrature and FFT pricing. After that, we make one controlled contract change: allowing early exercise of a put.

The point is to distinguish numerical error, exercise value, and model assumptions. A smaller solver error does not establish a better market model, and a model value does not establish an executable trading opportunity.

Define the experiment

We price one call per underlying unit with spot and strike 100100, one year to expiry, continuously compounded rate 5%, continuous dividend yield 2%, and annual volatility 20%. The Monte Carlo seed is 20261007.

The downloadable folder contains pricing_lab.py, requirements.txt, a README, executed results.json, and eight generated figures. It uses synthetic data only. There is no historical option chain, calibration, backtest, or claim of investment profitability.

Run the project in an environment with NumPy, SciPy, and Matplotlib. The lab writes results relative to its own location, so extracting the reader bundle preserves the experiment's paths. The version record documents the environment used for these published results.

The implementations are intentionally readable. They cover a dividend-adjusted BSM formula, recombining CRR tree, exact-terminal GBM Monte Carlo, antithetic Monte Carlo, Carr–Madan quadrature, and a grid FFT. The last two share the same characteristic function.

Python option-pricing results: Black–Scholes, binomial, Monte Carlo and Fourier

MethodCall valueRelevant uncertainty or error
BSM formula$9.2270055082Benchmark under the stated model
CRR, 1,000 steps$9.2250617378Error of about −$0.001944
Plain MC, 250,000 evaluations$9.2368995790Estimated SE $0.027629
Antithetic MC, equal evaluation budget$9.2190912097Estimated SE $0.020456
Carr–Madan quadrature$9.2270055082Agreement to numerical precision in this case
FFT with strike interpolation$9.2333347211Error of about +$0.006329

The plain Monte Carlo approximate 95% interval is [9.182747,9.291052][9.182747,9.291052]. The antithetic interval is [9.178996,9.259186][9.178996,9.259186]. Both contain the benchmark for this fixed seed. That is a recorded outcome, not a requirement that every correctly implemented interval must contain the true value.

Antithetic Monte Carlo uses 125,000 independent pair means, requiring 250,000 terminal payoff evaluations. Its estimated standard error is about 26% lower than plain simulation in this experiment. We compare equal terminal evaluation budgets; runtime may still differ because implementations and hardware differ.

Numerical price differences against BSM
Numerical price differences against BSM
The Monte Carlo error bar is an approximate 95% sampling interval. The tree and Fourier differences are numerical approximation errors, not statistical confidence intervals.

Binomial tree convergence to Black–Scholes

The CRR construction tends toward the GBM diffusion as its time steps become small. Its vanilla payoff is discounted backwards using the same carry assumptions as BSM. We therefore expect convergence to the same European price.

The finite tree represents the payoff kink on a discrete terminal grid. Odd and even step counts can show different errors. The lab plots those errors and checks a 2,000-step value against a specified tolerance. Do not judge convergence by displaying two rounded values that happen to look identical.

For the American put, backward induction adds the exercise maximum. On the 1,000-step tree, the European put is 6.32813685726.3281368572 and the American put is 6.65975919946.6597591994. The difference is about 0.3316220.331622 under this grid and model.

That difference has a different meaning from the call tree's discrepancy against BSM. We changed the holder's rights. The American put is a different contract. Compare its price with the European put on the same grid to isolate the exercise feature as clearly as possible.

Monte Carlo estimates and uncertainty as the independent path count increases
Monte Carlo estimates and uncertainty as the independent path count increases
Only the sample count changes. The horizontal benchmark uses the identical contract and GBM assumptions.

Monte Carlo standard error and the martingale check

Exact GBM terminal simulation draws the correct terminal distribution, but the sample mean still fluctuates. Increasing independent observations shrinks its standard error at the usual square-root rate. It does not alter the model's assumed volatility.

Antithetics average each ZZ payoff with its −Z-Z payoff. The pair is one independent observation. Treating both members as independent would misstate the interval. The lab makes the observation count and terminal evaluation count explicit in the output.

The benchmark being inside an interval is only one diagnostic. Also verify expected stock carry, payoff construction, discounting, and independent draws. A single random seed can hide a mistake or reveal an unusually large fluctuation; it cannot prove estimator correctness by itself.

For the stock-carry diagnostic, the model target is EQ[e−rTST]=S0e−qT\mathbb E^{\mathbb Q}[e^{-rT}S_T]=S_0e^{-qT}. This follows from the dividend-adjusted martingale condition, not from fitting the option price. The Black–Scholes derivation explains how Itô's lemma produces the correct log drift; the Monte Carlo article separates sampling error from a wrong drift. This diagnostic is a suggested additional check, separate from the nine executed lab checks listed below.

Why quadrature and FFT differ

Both invert the same damped transform. Adaptive quadrature targets the requested strike directly. The FFT computes an evenly spaced log-strike grid, and our strike 100100 requires interpolation.

The current FFT grid uses 4,096 points, frequency spacing 0.05, damping 1.5, and log-strike spacing about 0.03068. Refinement affects truncation and interpolation together. A production solver needs explicit tolerances across the relevant region, not only at the benchmark strike.

Quadrature matches BSM here and remains stable when damping changes among 0.5, 1.0, and 2.0 and the integration limit is increased. The lab also checks five strikes. The routine's local error estimate does not quantify the economic uncertainty in the model.

Read the checks as evidence with a scope

The executed run passes nine checks: European parity, tree convergence, American put dominance, quadrature agreement, damping stability, a strike-grid comparison, FFT accuracy at the stated tolerance, recorded fixed-seed Monte Carlo coverage, and expiry/zero-volatility boundaries.

These are benchmark checks for a teaching implementation. They do not validate a production risk system across arbitrary parameter regimes. In particular, the lab does not implement local volatility, Heston simulation, jump calibration, continuous barrier corrections, or least-squares Monte Carlo. The earlier articles explain where those extensions belong.

Good validation combines independent identities with limiting cases and an external or analytically known benchmark. Merely duplicating the implementation in a test would give less useful evidence. A production extension should add the tests that its new contract and model demand.

Extend one axis at a time

First vary tree steps. Then vary Monte Carlo paths while keeping the terminal model exact. Then vary FFT grid parameters. Record the resulting errors separately.

Next change volatility while retaining the BSM formula. That targets input sensitivity. Finally change the contract's exercise rule on the tree. That targets economic rights. Keeping those experiments distinct makes the results interpretable.

To explore path dependence, add an arithmetic Asian payoff with a stated observation schedule and a control variate. To explore richer dynamics, begin with a separately validated characteristic function or simulation scheme. Compare limiting cases before attempting calibration.

Choosing a method is choosing a task

Closed-form BSM is an excellent vanilla benchmark when its assumptions apply. A tree is useful for low-dimensional exercise decisions. Monte Carlo is flexible for complex path and multi-asset payoffs, with sampling and discretization work to manage. Fourier methods are attractive for terminal payoffs when a tractable characteristic function is available, especially across many strikes. Finite-difference PDE methods are another important family for low-dimensional state problems and exercise boundaries; they sit beyond this lab's implemented scope.

The right method is the one that represents the contract, works with the chosen model, and achieves a controlled error within the available budget. No algorithm wins every pricing problem.

Pricing-task solver and validation comparison
Pricing-task solver and validation comparison
Candidate methods depend on the contract and model. PDE methods are included as a comparison but are not implemented in this lab.

Continue learning

Return to numerical sensitivities, Monte Carlo, and Fourier pricing. Reproduce the baseline, then design one change whose effect you can explain before running it.

Sources and reproducibility

The model and methods follow the original papers linked throughout Parts 1–7, including CRR (1979) and Carr–Madan (1999). All reported lab results come from the supplied seeded implementation. Prices are synthetic model values per underlying unit.

Go deeper with lessons, derivations and worked exercises behind this article.