A high-performance Python library for pricing American and European equity options using Binomial Trees (CRR, Leisen-Reimer) and Analytical approximations (Black-Scholes-Merton, Barone-Adesi & Whaley).
-
Optimized Binomial Engines:
$\mathcal{O}(n)$ space complexity with vectorized backward induction for Cox-Ross-Rubinstein (CRR) and Leisen-Reimer (LR) trees. - Analytical Models: Unified Black-Scholes-Merton (BSM) for European options and Barone-Adesi & Whaley (BAW) quadratic approximation for American options.
- Volatility Calibration: Model-agnostic implied volatility solver and automated IV smile fitting using cubic splines with flat-wing extrapolation.
- Validation Pipeline: Integrated 3-stage pipeline to evaluate analytical convergence, in-sample calibration consistency, and out-of-sample predictive accuracy using live market data.
| Module | Description |
|---|---|
options/analytic.py |
Combined BSM (European) and BAW (American) analytical pricers. |
options/binomial.py |
Binomial Tree engines (CRR and Leisen-Reimer) with |
options/vol_surface.py |
Implied Volatility smile calibration and cubic spline interpolation. |
options/base.py |
Base interfaces, OptionContract frozen dataclass, and shared IV solvers. |
benchmark/runner.py |
Parallelized benchmarking engine using ThreadPoolExecutor. |
benchmark/market_data.py |
Real-time option chain fetching and cleaning via yfinance. |
main.py |
CLI entry point demonstrating the 3-stage validation pipeline. |
Theoretical Foundation: Both models provide instantaneous, continuous-time pricing. The Barone-Adesi & Whaley (BAW) quadratic approximation model is linked to the Black-Scholes-Merton (BSM) framework, utilizing the European BSM value as its baseline.
Core Distinctions: The standard BSM formula is strictly limited to European options, which can only be exercised at expiration
Code Structure: Both models reside in options/analytic.py. The BAWPricer delegates the base European valuation directly to the core BSM function. To determine the critical boundary scipy.optimize.brentq).
While analytical approximations like BAW are computationally efficient, their accuracy can degrade under extreme parameter regimes—such as deep-in-the-money options or exceptionally long maturities. Binomial trees bridge this theoretical gap by explicitly evaluating the optimal early-exercise constraint
Theoretical Foundation: Both methodologies construct a recombining lattice of asset prices mapping the underlying process. The core backward-induction mechanism—discounting the risk-neutral expected value and enforcing the early-exercise condition—is mathematically identical across both models.
Core Distinctions:
-
CRR (Cox-Ross-Rubinstein) parameterizes the tree using symmetric logarithmic jumps
$u = e^{\sigma \sqrt{\Delta t}}$ and$d = 1/u$ . However, because the terminal tree nodes rarely align with the option's strike price$K$ , CRR suffers from numerical oscillation (sawtooth convergence) as the step count$n$ changes. -
LR (Leisen-Reimer) resolves this by replacing standard binomial probabilities with the Peizer-Pratt inversion algorithm. This mathematically forces the terminal nodes to straddle the strike price
$K$ , aligning the tree with the continuous-time probabilities. The result is a dramatic elimination of oscillation and roughly 16x the computational efficiency of CRR for equivalent accuracy on standard contracts.
Code Structure: Both engines are unified within options/binomial.py. They inherit from the abstract BinomialPricer class and share a highly optimized, _backward_induction engine utilizing vectorized NumPy arrays. They diverge solely in their _get_params implementation, providing a rigorous application of the Template Method pattern.
The library enforces a strict three-stage validation pipeline:
- Analytical Validation: Ensures tree models converge to the exact BSM solution (European) and align closely with the BAW approximation (American).
- Calibration Self-Consistency: Verifies the IV solver and spline interpolation by calibrating a 1-D volatility smile to market data and repricing those same contracts to guarantee a near-100% hit rate within the bid-ask spread.
- Cross-Expiry Benchmarking: Tests out-of-sample predictive power by calibrating the smile on one expiry (~30 DTE) and evaluating contracts on a subsequent expiry (~35-45 DTE).
from options import BAWPricer, OptionContract, OptionType, ExerciseStyle
# Define an American Put using the immutable dataclass
contract = OptionContract(
spot=100.0,
strike=105.0,
time_to_expiry=0.5,
volatility=0.2,
option_type=OptionType.PUT,
exercise_style=ExerciseStyle.AMERICAN
)
pricer = BAWPricer()
price = pricer.price(contract)
print(f"BAW American Price: {price:.4f}")Run the full 3-stage validation pipeline across multiple tickers:
# Evaluate SPY and QQQ using the CRR model with 200 steps
python main.py --symbols SPY QQQ --steps 200 --model crrRun the comprehensive test suite with pytest:
pytest tests/