A high-performance quantitative finance library for derivatives pricing, yield curve construction, and stochastic simulation. Built with Rust for maximum performance and exposed to Python via PyO3 bindings. Designed for production use with modern Python tooling (uv).
📦 This project uses uv for dependency management - a fast, modern Python package manager. See GETTING_STARTED.md for detailed setup instructions.
- Analytical Pricing: Closed-form Black-Scholes formulas for European calls and puts
- Dividend Yield Support: Full Merton model implementation for dividend-paying assets
- Complete Greeks Suite: Delta, Gamma, Vega, Theta, and Rho calculations
- Type-Safe API: Dedicated
EuroCallOptionandEuroPutOptionclasses with full type hints - High-Performance Batch Operations: SIMD-optimized vectorized pricing via static methods
- AVX2 intrinsics for 4x throughput per core
- Rayon work-stealing parallelism across all cores
- 10-30x speedup on large portfolios (100K+ options)
- Early Exercise Premium: Accurate valuation of American-style early exercise rights
- Cox-Ross-Rubinstein Model: Industry-standard binomial tree implementation
- Longstaff-Schwartz Method: Monte Carlo pricing for path-dependent American options
- Dividend Support: Continuous dividend yield incorporation
- Greeks Calculation: Delta via finite difference methods
- Configurable Accuracy: Adjustable tree steps for precision/performance trade-off
- Market-Standard Construction: Bootstrap yield curves from market bond prices
- Flexible Instruments: Support for zero-coupon and coupon-bearing bonds with configurable payment frequencies
- Multiple Interpolation Schemes:
- Log-linear (default): Industry-standard method producing piecewise constant forward rates
- Linear: Simple linear interpolation of discount factors
- Cubic Spline: Hermite cubic interpolation for C¹-continuous smooth curves
- Bootstrap Algorithm: Iterative solver for extracting zero rates from coupon bonds
- Efficient Discount Factors: O(log n) binary search with automatic interpolation/extrapolation
- Forward Rate Analytics: Dedicated
ForwardCurveclass for:- Standard forward rates f(t₁, t₂) between any two maturities
- Instantaneous forward rates f(t) at specific time points
- Forward rate term structures over custom time grids
- Forward discount factors and forward bond pricing
- Bond Valuation: Present value calculations for arbitrary cash flow streams
- Parallel Batch Operations: Rayon-powered vectorization for 100+ simultaneous calculations
- Brownian Motion (Wiener Process):
- Parallel path generation via Rayon thread pool
- Antithetic variates for 50% variance reduction
- Configurable time discretization (Δt)
- Multi-dimensional correlated paths
- Geometric Brownian Motion (GBM):
- Risk-neutral asset price simulation (μ = r - q)
- Exact and Euler discretization schemes
- Batch terminal price generation for large portfolios
- Monte Carlo European option pricing
- Heston Stochastic Volatility Model:
- Two-factor model: S(t) and v(t) processes
- Mean-reverting variance with κ (speed), θ (long-term), σᵥ (vol-of-vol)
- Correlated Brownian motions (leverage effect: ρ)
- Captures volatility smile and skew
- Euler-Maruyama discretization with reflection at zero
- Monte Carlo Pricing Engine:
- European options via path simulation
- American options via Longstaff-Schwartz regression
- Variance reduction techniques (antithetic variates, control variates)
- Convergence diagnostics (standard error, confidence intervals)
- Massively parallel (100K+ paths across all cores)
- Production-Grade RNG:
- Xoshiro256++ algorithm (high-quality, fast PRNG)
- Optional seeding for reproducible simulations
- Thread-local storage for lock-free parallel execution
- Statistical quality validated (χ² tests, serial correlation)
- Zero-Cost Rust Core: Memory-safe, high-performance implementation without garbage collection overhead
- Seamless Python Integration: PyO3 bindings with zero-copy data transfer where possible
- Complete Type Safety: Full
.pyistub files for perfect IDE autocomplete, type checking, and inline documentation - Modern Build System:
uvfor dependency management,maturinfor Rust/Python packaging - Immutable Functional API: Thread-safe
with_*methods for scenario analysis without mutation - Production Ready: Comprehensive test suite, benchmarks, and real-world examples
rust-quant/
├── src/ # Rust source code
│ ├── lib.rs # Module exports and Python bindings
│ ├── types.rs # OptionGreeks, shared traits
│ ├── simd.rs # SIMD-optimized implementations
│ ├── vectorized.rs # Vectorized pricing functions
│ ├── european/ # European options module
│ │ ├── mod.rs # Module exports
│ │ ├── option.rs # EuroOption (simple class)
│ │ ├── call.rs # EuroCallOption (optimized)
│ │ └── put.rs # EuroPutOption (optimized)
│ ├── american/ # American options module
│ │ ├── mod.rs # Module exports
│ │ ├── option.rs # AmericanOption (simple class)
│ │ ├── call.rs # AmericanCallOption
│ │ ├── put.rs # AmericanPutOption
│ │ └── pricing.rs # Binomial tree implementation
│ ├── zero_coupon/ # Zero coupon yield curves
│ │ ├── mod.rs # Module exports
│ │ ├── curve.rs # ZeroCouponCurve, Security, InterpolationMethod
│ │ └── forward_curve.rs # ForwardCurve for forward rate calculations
│ └── stochastic/ # Stochastic processes module
│ ├── mod.rs # Module exports
│ ├── rng.rs # Thread-safe random number generation
│ ├── brownian.rs # Brownian motion paths
│ ├── gbm.rs # Geometric Brownian Motion
│ ├── heston.rs # Heston stochastic volatility model
│ └── monte_carlo.rs # Monte Carlo pricing engine
├── python/ # Python-related files
│ └── rust_quant/ # Python package directory
│ ├── __init__.py # Package initialization and exports
│ └── rust_quant.pyi # Type stubs for IDE support
├── examples/ # Usage examples (numbered for learning)
│ ├── 01_basic_single_option.py # Single option basics
│ ├── 02_multiple_options_vectorized.py # Vectorized operations
│ ├── 03_performance_benchmark.py # Performance comparison
│ ├── 04_dividend_yield_example.py # Dividend yield examples
│ ├── 05_american_options_example.py # American options
│ ├── 07_zero_coupon_curve.py # Zero coupon yield curves
│ └── 08_monte_carlo_paths.py # Stochastic processes & Monte Carlo
├── tests/ # Python test suite
│ ├── test_european_options.py # European options tests
│ ├── test_american_options.py # American options tests
│ ├── test_zero_coupon_curve.py # Zero coupon curve tests
│ ├── test_interpolation_methods.py # Interpolation method tests
│ └── test_stochastic.py # Stochastic processes tests
├── Cargo.toml # Rust dependencies
├── pyproject.toml # Python project config (uv-compatible)
└── README.md
You need two tools installed:
-
uv - Fast Python package manager (10-100x faster than pip)
# macOS/Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
-
Rust - For building the high-performance Rust backend
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
⚠️ Note: This project is designed foruv. While it may work withpip, all documentation, workflows, and testing assumeuv.
# Clone the repository
git clone https://github.com/aedan-chiari/rust-quant.git
cd rust-quant
# Install everything: create venv, install dependencies, build Rust extension
uv syncThat's it! uv sync automatically:
- Creates a Python virtual environment in
.venv/ - Installs all Python dependencies from
pyproject.toml - Compiles the Rust extension with
maturin - Installs the compiled library into the virtual environment
# Activate virtual environment (optional with uv run)
source .venv/bin/activate # macOS/Linux
# or
.venv\Scripts\activate # Windows
# Quick test
python -c "from rust_quant import EuroCallOption; print(EuroCallOption(100,100,1,0.05,0.2).price())"
# Should print: 10.450583572185565from rust_quant import EuroCallOption
# Create a European call option (without dividends)
call = EuroCallOption(
spot=100.0,
strike=105.0,
time_to_expiry=1.0,
risk_free_rate=0.05,
volatility=0.2
)
# Create a European call with dividend yield
call_with_div = EuroCallOption(
spot=100.0,
strike=105.0,
time_to_expiry=1.0,
risk_free_rate=0.05,
volatility=0.2,
dividend_yield=0.02 # 2% continuous dividend yield
)
# Calculate price and individual Greeks
price = call.price() # 8.0214
delta = call.delta() # 0.5422
gamma = call.gamma() # 0.0198
# Or get all Greeks at once (recommended - more efficient)
greeks = call.greeks()
print(f"Price: ${greeks.price:.4f}")
print(f"Delta: {greeks.delta:.4f}")
print(f"Gamma: {greeks.gamma:.4f}")
print(f"Vega: {greeks.vega:.4f}")
print(f"Theta: {greeks.theta:.4f}")
print(f"Rho: {greeks.rho:.4f}")
# Create new option with modified parameters (immutable updates)
higher_vol = call.with_volatility(0.3)
different_spot = call.with_spot(110.0)from rust_quant import AmericanPutOption, EuroPutOption
# Create an American put option (most common use case)
american_put = AmericanPutOption(
spot=100.0,
strike=105.0,
time_to_expiry=1.0,
risk_free_rate=0.05,
volatility=0.2,
steps=100, # Number of binomial tree steps (higher = more accurate)
)
# American options can be exercised early
price = american_put.price() # Includes early exercise premium
delta = american_put.delta()
# Compare with European option to see early exercise value
europen_put = EuroPutOption(
spot=100.0, strike=105.0, time_to_expiry=1.0,
risk_free_rate=0.05, volatility=0.2
)
early_exercise_premium = american_put.price() - europen_put.price()
print(f"Early exercise premium: ${early_exercise_premium:.4f}")Build yield curves from market bond prices and calculate discount factors, zero rates, and forward rates with multiple interpolation methods. Forward rate calculations are now handled by a separate ForwardCurve class for better separation of concerns.
from rust_quant import Security, ZeroCouponCurve, ForwardCurve
# Create securities representing market bond prices
# Mix of zero-coupon bonds and coupon-bearing bonds
securities = [
Security(maturity=0.5, price=97.50), # 6-month zero-coupon
Security(maturity=1.0, price=95.00), # 1-year zero-coupon
Security(maturity=2.0, price=90.00), # 2-year zero-coupon
# Coupon-bearing bond: 5% coupon, semi-annual payments
Security(maturity=5.0, price=98.0, coupon_rate=0.05, frequency=2),
]
# Create zero-coupon curve with default log-linear interpolation
curve = ZeroCouponCurve(securities)
# Calculate discount factors with automatic interpolation
df_1y = curve.discount_factor(1.0) # Exact maturity
df_18m = curve.discount_factor(1.5) # Interpolated
df_15y = curve.discount_factor(15.0) # Extrapolated
# Calculate zero rates (continuously compounded)
zero_rate_1y = curve.zero_rate(1.0)
zero_rate_5y = curve.zero_rate(5.0)
print(f"1Y rate: {zero_rate_1y * 100:.4f}%")
print(f"5Y rate: {zero_rate_5y * 100:.4f}%")
# Create ForwardCurve for forward rate calculations
forward_curve = ForwardCurve(curve)
# Calculate forward rates
fwd_1y1y = forward_curve.forward_rate(1.0, 2.0) # 1-year rate in 1 year
fwd_5y5y = forward_curve.forward_rate(5.0, 10.0) # 5-year rate in 5 years
print(f"1y1y forward: {fwd_1y1y * 100:.4f}%")
# Instantaneous forward rate
inst_fwd_2y = forward_curve.instantaneous_forward_rate(2.0)
print(f"Instantaneous forward at 2Y: {inst_fwd_2y * 100:.4f}%")
# Forward rate term structure
times, rates = forward_curve.term_structure(0.0, 10.0, 1.0)
print(f"0y1y forward: {rates[0] * 100:.4f}%")
# Value a bond or cash flow stream
cash_flows = [50.0, 50.0, 1050.0] # Annual coupons + principal
maturities = [1.0, 2.0, 3.0]
bond_value = curve.present_value_many(cash_flows, maturities)
print(f"Bond value: ${bond_value:.2f}")The library provides three interpolation methods:
# Log-linear (default, industry standard)
# → Piecewise constant forward rates
curve_log = ZeroCouponCurve(securities, interpolation="log_linear")
# Linear interpolation of discount factors
# → Simple and fast
curve_linear = ZeroCouponCurve(securities, interpolation="linear")
# Cubic spline interpolation
# → Smooth curves, C¹ continuous
curve_cubic = ZeroCouponCurve(securities, interpolation="cubic")
# Check which method is being used
print(curve_log.get_interpolation_method()) # "log_linear"
# Compare interpolated values
t = 1.5
df_log = curve_log.discount_factor(t)
df_linear = curve_linear.discount_factor(t)
df_cubic = curve_cubic.discount_factor(t)Which method to use?
- Log-linear (default): Industry standard. Produces piecewise constant forward rates, which is theoretically sound and commonly used in practice.
- Linear: Simple and fast. Good for general purposes when you don't need the theoretical properties of log-linear.
- Cubic: Smooth curves with C¹ continuity. Use when smoothness is critical (e.g., risk management, hedging).
# Verify log-linear produces constant forward rates
fwd_1_15 = curve_log.forward_rate(1.0, 1.5)
fwd_15_2 = curve_log.forward_rate(1.5, 2.0)
# These will be nearly identical (piecewise constant)
print(f"Difference: {abs(fwd_1_15 - fwd_15_2) * 10000:.2f} bps") # ~0.00 bpsCalculate multiple values efficiently using batch methods:
# Batch discount factors
maturities = [0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 7.0, 10.0]
discount_factors = ZeroCouponCurve.discount_factors_many(curve, maturities)
# Batch zero rates
zero_rates = ZeroCouponCurve.zero_rates_many(curve, maturities)
# Batch forward rates
start_times = [1.0, 2.0, 3.0, 5.0]
end_times = [2.0, 3.0, 5.0, 10.0]
forward_rates = ZeroCouponCurve.forward_rates_many(curve, start_times, end_times)
# All batch methods automatically use Rayon parallelism for >100 itemsYou can also create curves from vectors:
# Create from separate vectors
maturities = [1.0, 2.0, 3.0, 5.0, 10.0]
prices = [95.0, 90.0, 85.5, 78.35, 61.39]
curve = ZeroCouponCurve.from_vectors(
maturities,
prices,
interpolation="log_linear" # Optional, defaults to log_linear
)Both European and American options support dividend-adjusted pricing:
from rust_quant import EuroCallOption, EuroPutOption, AmericanCallOption
# European options with dividends (Merton model)
call = EuroCallOption(
spot=100.0,
strike=100.0,
time_to_expiry=1.0,
risk_free_rate=0.05,
volatility=0.2,
dividend_yield=0.02 # 2% continuous dividend yield
)
put = EuroPutOption(
spot=100.0,
strike=100.0,
time_to_expiry=1.0,
risk_free_rate=0.05,
volatility=0.2,
dividend_yield=0.02
)
# American options with dividends
american_call = AmericanCallOption(
spot=100.0, strike=100.0, time_to_expiry=1.0,
risk_free_rate=0.05, volatility=0.2,
dividend_yield=0.02, steps=100
)
# Dividends reduce call prices and increase put prices
print(f"Call price: ${call.price():.4f}") # Lower than without dividends
print(f"Put price: ${put.price():.4f}") # Higher than without dividends
# Put-call parity with dividends: C - P = S*e^(-qT) - K*e^(-rT)
# where q is the dividend yieldWhen to use dividend_yield:
- Dividend-paying stocks (e.g., most large-cap stocks)
- Index options (e.g., S&P 500 has ~1.5-2% yield)
- Currency options (foreign interest rate)
- Commodity options with storage costs
Default value is 0.0 (no dividends) for backward compatibility.
from rust_quant import EuroCallOption
# Price multiple European options at once using static methods
spots = [100.0, 101.0, 102.0, 103.0, 104.0]
strikes = [105.0] * 5
times = [1.0] * 5
rates = [0.05] * 5
vols = [0.2] * 5
# Vectorized pricing with SIMD + parallel processing
prices = EuroCallOption.price_many(spots, strikes, times, rates, vols)
# Vectorized Greeks calculation (returns tuple of lists)
prices, deltas, gammas, vegas, thetas, rhos = EuroCallOption.greeks_many(
spots, strikes, times, rates, vols
)For large portfolios (100+ options), static class methods automatically use SIMD + parallel processing:
from rust_quant import EuroCallOption, EuroPutOption
# Create large dataset
n = 100_000
spots = [100.0 + i * 0.01 for i in range(n)]
strikes = [105.0] * n
times = [1.0] * n
rates = [0.05] * n
vols = [0.2] * n
# SIMD + parallel processing (10-30x faster on large datasets)
prices = EuroCallOption.price_many(spots, strikes, times, rates, vols)
# All Greeks with SIMD + parallel
prices, deltas, gammas, vegas, thetas, rhos = EuroCallOption.greeks_many(
spots, strikes, times, rates, vols
)
# Same for puts
put_prices = EuroPutOption.price_many(spots, strikes, times, rates, vols)
put_greeks = EuroPutOption.greeks_many(spots, strikes, times, rates, vols)Architecture Note: All static class methods (EuroCallOption.price_many, EuroCallOption.greeks_many, EuroPutOption.price_many, EuroPutOption.greeks_many) automatically use SIMD intrinsics (AVX) and Rayon parallelism for maximum performance. The EuroOption and AmericanOption classes are designed for simple one-off calculations.
Run the numbered examples to learn different usage patterns:
# Basic single option usage
uv run examples/01_basic_single_option.py
# Vectorized operations and batch processing
uv run examples/02_multiple_options_vectorized.py
# Performance benchmarks at different scales
uv run examples/03_performance_benchmark.py
# Dividend yield examples
uv run examples/04_dividend_yield_example.py
# American options with early exercise
uv run examples/05_american_options_example.py
# Zero coupon yield curves
uv run examples/07_zero_coupon_curve.py
# Monte Carlo simulations and stochastic processes
uv run examples/08_monte_carlo_paths.pySee examples/README.md for detailed documentation.
European call option with Black-Scholes pricing (Merton model with dividend support).
Constructor:
EuroCallOption(spot, strike, time_to_expiry, risk_free_rate, volatility, dividend_yield=0.0)Parameters:
spot(float): Current price of the underlying assetstrike(float): Strike price of the optiontime_to_expiry(float): Time to expiration in yearsrisk_free_rate(float): Risk-free interest rate (as decimal, e.g., 0.05 for 5%)volatility(float): Volatility of the underlying asset (as decimal, e.g., 0.2 for 20%)dividend_yield(float, optional): Continuous dividend yield (default 0.0)
Instance Methods:
price()→ float: Calculate call option price (Black-Scholes analytical)delta()→ float: Sensitivity to underlying pricegamma()→ float: Rate of change of deltavega()→ float: Sensitivity to volatilitytheta()→ float: Time decay (per day)rho()→ float: Sensitivity to interest rategreeks()→ OptionGreeks: All Greeks and price at onceprice_monte_carlo(num_paths, num_steps)→ float: Monte Carlo pricingprice_monte_carlo_antithetic(num_paths, num_steps)→ float: MC with variance reductionprice_heston(initial_variance, kappa, theta, vol_of_vol, correlation, num_paths, num_steps)→ float: Heston stochastic volatility pricingwith_spot(new_spot)→ EuroCallOption: Create new option with different spotwith_strike(new_strike)→ EuroCallOption: Create new option with different strikewith_time(new_time)→ EuroCallOption: Create new option with different timewith_volatility(new_volatility)→ EuroCallOption: Create new option with different volatility
Static Methods (SIMD + Parallel Optimized):
EuroCallOption.price_many(spots, strikes, times, rates, vols)→ List[float]EuroCallOption.greeks_many(spots, strikes, times, rates, vols)→ Tuple[List[float], ...]
These methods automatically use SIMD intrinsics (AVX) and Rayon parallelism for maximum performance.
Returns from greeks_many: (prices, deltas, gammas, vegas, thetas, rhos)
European put option with Black-Scholes pricing (Merton model with dividend support). Has the same methods and parameters as EuroCallOption.
Constructor:
EuroPutOption(spot, strike, time_to_expiry, risk_free_rate, volatility, dividend_yield=0.0)Generic European option class for simple one-off calculations. Can represent either calls or puts via an is_call parameter. This class is optimized for single option calculations without SIMD/parallelism overhead.
Constructor:
EuroOption(spot, strike, time_to_expiry, risk_free_rate, volatility, is_call=True, dividend_yield=0.0)Note: For batch operations, use EuroCallOption or EuroPutOption classes which have optimized static methods.
American call option with binomial tree pricing (CRR method).
Constructor:
AmericanCallOption(spot, strike, time_to_expiry, risk_free_rate, volatility, dividend_yield=0.0, steps=100)Parameters:
spot(float): Current price of the underlying assetstrike(float): Strike price of the optiontime_to_expiry(float): Time to expiration in yearsrisk_free_rate(float): Risk-free interest rate (as decimal)volatility(float): Volatility of the underlying asset (as decimal)dividend_yield(float, optional): Continuous dividend yield (default 0.0)steps(int, optional): Number of binomial tree steps (default 100)
Instance Methods:
price()→ float: Calculate option price using binomial tree (CRR method)delta()→ float: Sensitivity to underlying price (finite difference)price_lsm(num_paths, num_steps)→ float: Longstaff-Schwartz Monte Carlo pricing
Note: More steps = more accuracy but slower computation. 100-200 steps typically sufficient for binomial trees. For LSM, 50k paths with 50 steps provides ~0.3% accuracy.
American put option with binomial tree pricing. Has the same constructor and methods as AmericanCallOption.
Generic American option class for simple calculations. Can represent either calls or puts via an is_call parameter.
Constructor:
AmericanOption(spot, strike, time_to_expiry, risk_free_rate, volatility, is_call=True, dividend_yield=0.0, steps=100)Container for option price and all Greeks.
Attributes:
price(float): Option pricedelta(float): Sensitivity to underlying pricegamma(float): Rate of change of deltavega(float): Sensitivity to volatilitytheta(float): Time decay per dayrho(float): Sensitivity to interest rate
Calculate standard error of Monte Carlo simulations to assess convergence and precision.
Parameters:
values(List[float]): Vector of simulated values (e.g., option prices from multiple MC runs)
Returns:
float: Standard error of the mean (SE = σ / √N)
Example:
from rust_quant import EuroCallOption, monte_carlo_standard_error
call = EuroCallOption(100, 100, 1.0, 0.05, 0.2)
# Run MC 100 times to assess convergence
prices = [call.price_monte_carlo(10000) for _ in range(100)]
se = monte_carlo_standard_error(prices)
mean_price = sum(prices) / len(prices)
print(f"Price: ${mean_price:.4f} ± ${1.96*se:.4f} (95% CI)")Represents a bond security (zero-coupon or coupon-bearing) for yield curve construction.
Constructor:
Security(maturity, price, face_value=100.0, coupon_rate=0.0, frequency=0)Parameters:
maturity(float): Time to maturity in yearsprice(float): Market price of the bondface_value(float, optional): Face value at maturity (default 100.0)coupon_rate(float, optional): Annual coupon rate as decimal (default 0.0 for zero-coupon)frequency(int, optional): Coupon payments per year (0 for zero-coupon, 2 for semi-annual, etc.)
Examples:
# Zero-coupon bond
zero = Security(maturity=2.0, price=90.0)
# Coupon-bearing bond: 5% coupon, semi-annual payments
coupon_bond = Security(
maturity=5.0,
price=98.0,
coupon_rate=0.05,
frequency=2
)Zero-coupon yield curve for discount factor and rate calculations.
Constructor:
ZeroCouponCurve(securities, interpolation=None)Parameters:
securities(List[Security]): List of Security objectsinterpolation(str, optional): Interpolation method - "linear", "log_linear", or "cubic". Defaults to "log_linear" (industry standard)
Alternative Constructor:
ZeroCouponCurve.from_vectors(maturities, prices, face_values=None, interpolation=None)Instance Methods:
discount_factor(maturity)→ float: Calculate discount factor at given maturityzero_rate(maturity)→ float: Calculate continuously compounded zero rateforward_rate(t1, t2)→ float: Calculate forward rate between t1 and t2present_value(cash_flow, maturity)→ float: Present value of single cash flowpresent_value_many(cash_flows, maturities)→ float: Present value of multiple cash flowsadd_security(security): Add a new security to the curvesize()→ int: Number of securities in the curvematurities()→ List[float]: List of all maturitiesget_interpolation_method()→ str: Get current interpolation method ("linear", "log_linear", or "cubic")
Static Methods (Batch Operations):
ZeroCouponCurve.discount_factors_many(curve, maturities)→ List[float]ZeroCouponCurve.zero_rates_many(curve, maturities)→ List[float]ZeroCouponCurve.forward_rates_many(curve, start_maturities, end_maturities)→ List[float]
All batch methods automatically use Rayon parallelism for datasets >100 items.
Examples:
# Basic usage
securities = [Security(maturity=1.0, price=95.0), Security(maturity=2.0, price=90.0)]
curve = ZeroCouponCurve(securities, interpolation="log_linear")
# Single calculations
df = curve.discount_factor(1.5)
rate = curve.zero_rate(1.5)
fwd = curve.forward_rate(1.0, 2.0)
# Batch calculations
maturities = [0.5, 1.0, 1.5, 2.0, 3.0]
dfs = ZeroCouponCurve.discount_factors_many(curve, maturities)
rates = ZeroCouponCurve.zero_rates_many(curve, maturities)Enum representing interpolation methods (mainly for type checking).
Values:
InterpolationMethod.Linear: Linear interpolation of discount factorsInterpolationMethod.LogLinear: Log-linear interpolation (piecewise constant forwards)InterpolationMethod.CubicSpline: Hermite cubic spline interpolation
Note: In practice, you'll use string values ("linear", "log_linear", "cubic") when constructing curves.
The library implements the Black-Scholes-Merton model with dividend yield support:
Call Option:
C = S * e^(-qT) * N(d1) - K * e^(-rT) * N(d2)
Put Option:
P = K * e^(-rT) * N(-d2) - S * e^(-qT) * N(-d1)
Where:
d1 = [ln(S/K) + (r - q + σ²/2)T] / (σ√T)
d2 = d1 - σ√T
Parameters:
S: Current spot priceK: Strike priceT: Time to expiration (years)r: Risk-free interest rateσ: Volatilityq: Continuous dividend yieldN(·): Cumulative standard normal distribution
Delta:
- Call:
e^(-qT) * N(d1) - Put:
e^(-qT) * (N(d1) - 1)
Gamma (same for calls and puts):
Γ = e^(-qT) * φ(d1) / (S * σ * √T)
Vega (same for calls and puts):
ν = S * e^(-qT) * φ(d1) * √T / 100
Theta:
- Call:
[-(S * e^(-qT) * φ(d1) * σ)/(2√T) - rKe^(-rT)N(d2) + qSe^(-qT)N(d1)] / 365 - Put:
[-(S * e^(-qT) * φ(d1) * σ)/(2√T) + rKe^(-rT)N(-d2) - qSe^(-qT)N(-d1)] / 365
Rho:
- Call:
K * T * e^(-rT) * N(d2) / 100 - Put:
-K * T * e^(-rT) * N(-d2) / 100
Where φ(·) is the standard normal probability density function.
With dividends:
C - P = S * e^(-qT) - K * e^(-rT)
Without dividends (q=0):
C - P = S - K * e^(-rT)
American options use the Cox-Ross-Rubinstein (CRR) binomial tree method:
dt = T / N (time step)
u = e^(σ√dt) (up factor)
d = 1/u (down factor)
p = (e^((r-q)dt) - d) / (u - d) (risk-neutral probability)
Where N is the number of steps.
- Build tree forward: Calculate all possible stock prices at each node
- Calculate terminal payoffs:
max(S - K, 0)for calls,max(K - S, 0)for puts - Work backward: At each node, option value is:
V = max(intrinsic_value, e^(-r*dt) * (p*V_up + (1-p)*V_down))
The max operation captures the early exercise feature.
American options ≥ European options, with equality only when early exercise is never optimal:
- American calls on non-dividend stocks: typically same as European
- American calls with dividends: may exercise early before ex-dividend dates
- American puts: often have significant early exercise premium, especially ITM
The library implements zero-coupon yield curve construction from market bond prices with bootstrapping and multiple interpolation methods.
Relationship:
DF(T) = exp(-r(T) * T)
r(T) = -ln(DF(T)) / T
Where:
DF(T): Discount factor at maturity Tr(T): Continuously compounded zero rate at maturity TT: Time to maturity in years
Forward rate between times t₁ and t₂:
f(t₁, t₂) = [ln(DF(t₁)) - ln(DF(t₂))] / (t₂ - t₁)
Or equivalently:
f(t₁, t₂) = [r(t₂) * t₂ - r(t₁) * t₁] / (t₂ - t₁)
The library implements three interpolation methods for discount factors between known maturities:
1. Linear Interpolation
Linear interpolation of discount factors:
DF(t) = DF(t₁) + [DF(t₂) - DF(t₁)] * (t - t₁) / (t₂ - t₁)
Properties:
- Simple and fast: O(log n) binary search + O(1) interpolation
- Monotonic: Preserves decreasing discount factors
- Not arbitrage-free: Can produce non-constant forward rates within intervals
2. Log-linear Interpolation (Industry Standard)
Linear interpolation of log discount factors:
ln(DF(t)) = ln(DF(t₁)) + [ln(DF(t₂)) - ln(DF(t₁))] * (t - t₁) / (t₂ - t₁)
Equivalently:
DF(t) = exp[ln(DF(t₁)) + [ln(DF(t₂)) - ln(DF(t₁))] * (t - t₁) / (t₂ - t₁)]
Properties:
- Piecewise constant forward rates: Forward rates are constant within each interval
- Arbitrage-free: Theoretically sound for rate markets
- Industry standard: Most widely used in fixed income
- Monotonic: Preserves decreasing discount factors
Verification that forward rates are constant:
For t ∈ [t₁, t₂]: f(t₁, t) = f(t, t₂) = constant
3. Cubic Spline Interpolation
Hermite cubic interpolation with finite difference derivatives:
DF(t) = h₀₀(u) * DF₁ + h₁₀(u) * m₁ * Δt + h₀₁(u) * DF₂ + h₁₁(u) * m₂ * Δt
Where:
u = (t - t₁) / (t₂ - t₁)(normalized position)Δt = t₂ - t₁h₀₀(u) = 2u³ - 3u² + 1(Hermite basis function)h₁₀(u) = u³ - 2u² + uh₀₁(u) = -2u³ + 3u²h₁₁(u) = u³ - u²m₁, m₂: Derivatives at endpoints (computed using finite differences)
Properties:
- C¹ continuous: Smooth first derivative
- May oscillate: Can produce non-monotonic curves with sparse data
- Best for smooth curves: Risk management, hedging applications
- More computation: O(log n) search + O(1) cubic evaluation
For coupon-bearing bonds, the library uses bootstrapping to extract discount factors:
Zero-coupon bond:
DF(T) = Price / FaceValue
Coupon-bearing bond:
Price = Σ(coupon * DF(tᵢ)) + FaceValue * DF(T)
Solve iteratively for DF(T):
DF(T) = [Price - Σ(coupon * DF(tᵢ))] / FaceValue
Where the sum is over all coupon payment dates before maturity.
Beyond the last maturity, the library uses flat rate extrapolation:
For t > T_max:
r(t) = r(T_max) (constant rate)
DF(t) = exp(-r(T_max) * t)
This prevents arbitrage opportunities from negative or increasing rates.
The library provides a clean, class-based architecture with automatic optimization:
-
EuroOption / AmericanOption Classes: Simple, lightweight for one-off calculations
- No SIMD or parallelism overhead
- Perfect for single option pricing or quick calculations
-
EuroCallOption / EuroPutOption Classes: Specialized for European options
- Instance methods for single options (Black-Scholes analytical formulas)
- Static methods (
price_many,greeks_many) automatically use:- SIMD (AVX): Process 4 options simultaneously with vectorized instructions
- Rayon Parallelism: Distribute work across all CPU cores
- Chunked Processing: Efficient memory access patterns
-
AmericanCallOption / AmericanPutOption Classes: American options with early exercise
- Binomial tree pricing (CRR method)
- Configurable steps for accuracy/performance trade-off
- Delta via finite differences
Single Option:
- Use instance methods:
option.price(),option.greeks() - Microsecond-level latency
Batch Operations (100+ options):
- Use static methods:
EuroCallOption.price_many(),EuroCallOption.greeks_many() - 10-30x speedup vs sequential processing
- Scales with number of CPU cores
- SIMD provides ~4x boost, parallelism adds core-count multiplier
Benchmark Results (1M options on 8-core machine):
- Standard sequential: ~150ms
- Static methods (SIMD + Parallel): ~80ms
- Speedup: 1.9x
Benchmark on your machine:
uv run python examples/03_performance_benchmark.py# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Setup everything
uv sync
# Activate virtual environment
source .venv/bin/activate # On Windows: .venv\Scripts\activate# Run Python tests
pytest tests/
# Run Rust tests
cargo test
# Run all tests with verbose output
pytest tests/ -v && cargo test# Format Rust code
cargo fmt
# Lint Rust code
cargo clippy
# Format Python code
ruff format
# Check formatting
cargo fmt -- --check
ruff format --check# Build optimized release wheel
maturin build --release
# The wheel will be in target/wheels/
# Install with: uv pip install target/wheels/rust_quant-*.whl- Rust Core: High-performance Black-Scholes implementation with SIMD intrinsics
- PyO3 Bindings: Zero-cost abstractions for Python integration
- Type Safety: Full
.pyistubs for IDE support and type checking - Immutability: All option modifications create new instances
- Performance: Release builds with LTO and aggressive optimizations
MIT
Contributions are welcome! This project follows strict quality standards to maintain production-grade code.
Before submitting, ensure:
- ✅ Use
uvfor all Python dependency management (nopip) - ✅ Code formatting:
cargo fmt(Rust) andruff format(Python) - ✅ All tests pass:
pytest tests/ -v && cargo test - ✅ No linter warnings:
cargo clippy -- -D warnings - ✅ Type stubs updated: If adding new classes/methods, update
.pyifiles - ✅ Examples provided: New features should include example usage
- ✅ Benchmarks: Performance-critical code should include benchmarks
- ✅ Documentation: Update relevant
.mdfiles
# 1. Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/rust-quant.git
cd rust-quant
# 2. Set up development environment
uv sync
source .venv/bin/activate # macOS/Linux
# 3. Create a feature branch
git checkout -b feature/your-feature-name
# 4. Make your changes
# - Edit Rust code in src/
# - Edit Python code in python/
# - Add/update tests in tests/
# - Add examples to examples/
# 5. Rebuild Rust extension after changes
maturin develop --release
# 6. Format code
cargo fmt
ruff format .
# 7. Run linters
cargo clippy -- -D warnings
# 8. Run all tests
pytest tests/ -v
cargo test
# 9. Run examples to verify
uv run python examples/01_basic_single_option.py
# 10. Commit and push
git add .
git commit -m "feat: your descriptive commit message"
git push origin feature/your-feature-name
# 11. Open a pull request on GitHubWe welcome contributions in these areas:
- New option types: Barrier, Asian, Lookback options
- More Greeks: Vanna, Volga, charm, color
- Implied volatility: Newton-Raphson solver
- Interest rate models: Vasicek, Cox-Ingersoll-Ross, Hull-White
- Performance optimizations: SIMD improvements, better parallelism
- Documentation: Tutorials, examples, API docs
- Testing: Edge cases, numerical accuracy validation
- Benchmarking: More comprehensive performance tests
Rust:
- Follow standard Rust naming conventions
- Use
rustfmtdefaults - Document public APIs with
///doc comments - Keep functions small and focused
- Prefer immutability where possible
Python:
- Follow PEP 8 via
ruff - Type hints for all public APIs
- Docstrings in Google style
- Keep examples self-contained and runnable
- Bugs: Open an issue with a minimal reproducible example
- Feature requests: Open an issue describing the use case
- Questions: Start a discussion on GitHub Discussions