C++20 | Multithreaded | Monte Carlo | Options | Numerical Methods
Analytical Black-Scholes price: 10.450584
Monte Carlo estimate: 10.447338
95% confidence interval: [10.438221, 10.456454]
Paths: 5000000
Threads: 8
Runtime: 0.047305 s
Throughput: 105.697121 M paths/s
This is a small derivatives pricing and risk engine, not a trading system. It prices European calls and puts, simulates arithmetic Asian calls, and calculates Delta, Gamma, and Vega. A separate historical GBM tool produces stock-price scenarios without mixing physical forecasts into risk-neutral pricing.
These numbers came from a Release build on an Intel Core i9-13900H laptop. The
test case is a one-year at-the-money call with S = K = 100, r = 5%,
sigma = 20%, and seed 42. Full details are in
results/final/.
| Experiment | Result |
|---|---|
| 5M-path price | 10.448002 vs 10.450584 Black–Scholes |
| Absolute error / standard error | 0.002582 / 0.006584 |
| Eight-thread throughput | 110.370M paths/s |
| Eight-thread speedup | 4.486x |
| Antithetic variance reduction | 2.002x |
| 1M-path Greek relative errors | 0.055%–0.129% |
| Peak RSS in documented large runs | 4,124 KiB |
flowchart LR
CLI[CLI + validation] --> BS[Black-Scholes]
CLI --> MC[Monte Carlo engines]
PAYOFF[European and Asian payoffs] --> MC
MODEL[Risk-neutral GBM] --> MC
MC --> WORKERS[Thread-local RNG + statistics]
WORKERS --> RESULT[Price, SE, CI, runtime]
MC --> GREEKS[Finite-difference Greeks]
European options use an exact terminal GBM draw. Asian options use a separate path engine that keeps only the current price and running average. Each worker owns its random generator and Welford statistics, so there is no lock in the hot loop and no need to store every payoff or path.
More detail: architecture and mathematics.
You need CMake 3.20+ and a C++20 compiler. Catch2 is downloaded on the first configure if it is not already installed.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failureThe normal build targets Linux and macOS. GitHub Actions builds and tests both.
European calls and puts default to analytical and Monte Carlo pricing together:
./build/mcprice price \
--type call \
--spot 100 --strike 100 \
--rate 0.05 --volatility 0.20 --maturity 1 \
--paths 5000000 --threads 8 --seed 42 \
--antitheticUse --method mc|analytical|both. Asian calls use Monte Carlo because there is
no analytical implementation:
./build/mcprice price \
--type asian-call \
--spot 100 --strike 100 \
--rate 0.05 --volatility 0.20 --maturity 1 \
--paths 1000000 --threads 8 --steps 252 \
--batch-size 50000 --seed 42 --antitheticRun ./build/mcprice --help for every option. Bad and non-finite inputs return a
clear error and a nonzero exit code.
Pass a chronological CSV containing ISO YYYY-MM-DD dates and an Adj Close
column:
./build/mcprice forecast --csv examples/sample_prices.csv --horizon-days 20For an exported Apple history, replace the path with your file. Use
--price-column Close when adjusted prices are unavailable. The command fits a
historical GBM model and reports a mean, median, 95% model interval, and chance
of finishing above the latest close. These are scenarios, not trading signals.
Expected returns are noisy, and the interval is only meaningful after
out-of-sample backtesting.
Use --volatility-model ewma --ewma-decay 0.94 to weight recent return
deviations more heavily. Sample volatility remains the default.
Run a rolling, no-lookahead evaluation against a latest-price baseline:
./build/mcprice backtest \
--csv examples/sample_prices.csv \
--lookback-days 10 --horizon-days 3 --step-days 1The report includes MAE, RMSE, MAPE, directional accuracy, and 95% interval coverage. A step shorter than the horizon creates overlapping targets; use a step equal to the horizon for a smaller non-overlapping evaluation. The same volatility-model options work here, making interval calibration directly comparable.
Under risk-neutral Black–Scholes dynamics,
For discounted payoffs X_i, the engine reports
Antithetic mode averages the payoffs from Z and -Z and treats that pair as
one independent observation. Asian monitoring uses jT/M, j = 1,...,M, so it
excludes today's spot and includes maturity. Greeks use central differences with
common random numbers. Vega is reported per one volatility percentage point.
python3 -m venv .venv
.venv/bin/python -m pip install -r python/requirements.txt
./build/mc_final_benchmarks results/final
.venv/bin/python python/plot_final_benchmarks.pyThe Python script checks the CSV calculations before plotting them. An
independent NumPy/SciPy check is available in python/validate_black_scholes.py.
- Zero dividends, flat continuous rates, constant volatility, and risk-neutral geometric Brownian motion.
- European calls and puts plus one discretely monitored arithmetic Asian call.
- No early exercise, stochastic volatility, jumps, calibration, portfolios, or execution.
- Confidence intervals cover Monte Carlo sampling error, not model risk.
- Forecast ranges assume future log returns resemble the supplied history.
- EWMA changes forecast uncertainty only; it does not change pricing volatility.
- Historical forecasting remains separate from risk-neutral option valuation.
- Greeks also contain finite-difference bump error.
- Fixed configurations reproduce on the same implementation and toolchain. Different thread counts or standard libraries may produce different random streams but should remain statistically consistent.
- Benchmarks describe one laptop without CPU pinning or thermal control.
Likely next steps are thread-count-independent streams, Greek confidence intervals, more variance-reduction methods, and broader CI coverage.
include/mc/ Public C++ interfaces
src/ Engine and CLI implementation
tests/ Catch2 tests
benchmarks/ C++ experiments
python/ Validation and plots
results/ Raw data and measured environments
docs/ Architecture and mathematics


