Quantitative finance relies heavily on Monte Carlo simulation engines to value derivatives, measure exposures, and compute xVA metrics like CVA, DVA, or MVA.
While many libraries exist for basic option pricing, very few open-source tools provide a full multi-model, multi-factor risk engine that can simulate:
- interest rates
- equity prices
- stochastic default intensities
- hybrid correlated factors
- exposure profiles (EE, EPE, ENE, EEPE, PFE)
- xVA metrics (CVA implemented, extendable to FVA/MVA/KVA)
- adjoint algorithmic differentiation (AAD)
- regression for American/Bermudan options
This article introduces the Monte Carlo Risk Engine, a PyTorch-powered framework for financial modeling, exposure simulation, and xVA.
All code is open source:
👉 https://github.com/konstantineder/montecarlo-risk-engine
Why Another Monte Carlo Engine?
Banks rely on complex XVA engines. Academia uses theory-heavy models. Python has many pricing libraries — but they:
- rarely support multi-factor correlated risk
- almost never include credit models (CIR++)
- don’t compute exposures or xVA
- don’t allow AAD
- don’t offer American option valuation
- lack wrong-way risk (WWR) analysis
I wanted a framework that:
- is modular
- supports any number of risk models
- allows correlation between factors
- runs fast thanks to PyTorch vectorization
- supports AAD, regression, and early exercise
- works as a true risk engine, not just a toy pricer
So I built one.
Architecture Overview
The engine consists of several core components:
SimulationController
Runs Monte Carlo paths, orchestrates model evolution, handles time stepping.
ModelConfig
Combines multiple stochastic models into a joint hybrid model with a correlation matrix (e.g., Vasicek interest rates + CIR++ intensity to simulate CVA).
Metrics
A pluggable API that includes:
- PV
- EE, EPE, ENE
- EEPE
- CE
- PFE
- CVA
Each metric returns **both the estimate and its Monte Carlo standard error.
Products
Supports a wide range of instruments:
- European, Bermudan, American options
- Bond options
- Bermudan swaptions
- Interest rate swaps
- Zero, coupon, and floating-rate bonds
- Basket options
- Barrier and binary options
Regression-based continuation value estimation (Longstaff–Schwartz) is built in.
Stochastic Models
- Black–Scholes (single-asset & multi-asset)
- Vasicek
- Hull–White
- CIR++
- Bootstrapped hazard rates from CDS data
All models are implemented in vectorized PyTorch.
Example: Exposure Profile of a Bermudan Swaption
This figure shows Expected Exposure (EE) and Potential Future Exposure (PFE) for a Bermudan swaption computed across 100,000 paths.
The pull-to-par behavior, early exercise shaping, and long-tail exposure are all captured correctly.
Wrong-Way Risk Example: CVA vs Correlation for a Zero-Coupon Bond
One of the most powerful features of a hybrid engine is analyzing xVA and WWR (wrong-way risk).
Consider a 10Y payer swap. CVA depends on:
- exposure (increase when interest rates rise).
- default intensity (CIR++)
- correlation between short rate and intensity
If interest rates and intensity are positively correlated, we get wrong-way risk → CVA increases. If negatively correlated → right-way risk → CVA increases.
Result:
This reflects exactly the expected economic behavior!
Example: CVA under Wrong-Way Risk for a 10Y Payer IRS
The following code computes the CVA of a 10-year payer interest rate swap using:
- Vasicek for stochastic interest rates
- CIR++ for stochastic credit intensities
- strong positive correlation (high WWR)
- A baseline CVA from an uncorrelated simulation
- A statistical significance test comparing the two (3-sigma rule)
import numpy as np
from common.packages import *
from common.enums import SimulationScheme
from controller.controller import SimulationController
from models.vasicek import VasicekModel
from models.cirpp import CIRPPModel
from models.model_config import ModelConfig
from products.bond import Bond
from products.swap import InterestRateSwap, IRSType
from metrics.cva_metric import CVAMetric
from metrics.risk_metrics import RiskMetrics
# ----------------------------
# Interest Rate Model (Vasicek)
# ----------------------------
interest_rate_model = VasicekModel(
calibration_date=0.0,
rate=0.03,
mean=0.05,
mean_reversion_speed=0.02,
volatility=0.2,
asset_id="irs"
)
# ----------------------------
# Bootstrapped Hazard Rates
# ----------------------------
hazard_rates: dict[float, float] = {
0.5: 0.006402303360855854,
1.0: 0.01553038972325307,
2.0: 0.009729741230773657,
3.0: 0.015552544648116201,
4.0: 0.021196186202801115,
5.0: 0.02284319986706472,
7.0: 0.010111423894480876,
10.0: 0.00613267811172937,
15.0: 0.0036969930706003337,
20.0: 0.003791311459217732
}
counterparty_id = "General Motors Co"
intensity_model = CIRPPModel(
calibration_date=0.0,
y0=0.0001,
theta=0.01,
kappa=0.1,
volatility=0.02,
hazard_rates=hazard_rates,
asset_id=counterparty_id
)
# -------------------------------------------
# Hybrid Model Configuration (with WWR: ρ≈1)
# -------------------------------------------
inter_correlation_matrix = np.array([0.99999])
model_config = ModelConfig(
models=[interest_rate_model, intensity_model],
inter_asset_correlation_matrix=inter_correlation_matrix,
)
# ----------------------------
# Payer Interest Rate Swap
# ----------------------------
maturity = 10.0
irs = InterestRateSwap(
startdate=0.0,
enddate=maturity,
notional=1.0,
fixed_rate=0.03,
tenor_fixed=0.25,
tenor_float=0.25,
irs_type=IRSType.PAYER,
asset_id="irs"
)
portfolio = [irs]
# ----------------------------
# CVA Metric Setup
# ----------------------------
exposure_timeline = np.linspace(0.0, maturity, 100)
cva_metric = CVAMetric(counterparty_id=counterparty_id, recovery_rate=0.4)
risk_metrics = RiskMetrics(
metrics=[cva_metric],
exposure_timeline=exposure_timeline
)
# ----------------------------
# Monte Carlo Simulation
# ----------------------------
num_paths_mainsim = 100000
num_paths_presim = 100000
num_steps = 10
sc = SimulationController(
portfolio=portfolio,
model=model_config,
risk_metrics=risk_metrics,
num_paths_mainsim=num_paths_mainsim,
num_paths_presim=num_paths_presim,
num_steps=num_steps,
simulation_scheme=SimulationScheme.EULER,
differentiate=False
)
sim_results = sc.run_simulation()
# ----------------------------
# CVA Estimate + MC Error
# ----------------------------
cva_irs = sim_results.get_results(0, 0)[0]
cva_irs_error = sim_results.get_mc_error(0, 0)[0]
# --------------------------------------------------------------
# Baseline CVA (uncorrelated case), from reference simulation
# --------------------------------------------------------------
cva_uncorr = 1.114576156484541
cva_uncorr_error = 0.0024446898428056294
# --------------------------------------------------------------
# Statistical Test: Is WWR CVA > Uncorrelated CVA?
# Uses 3-sigma significance test on the difference.
# --------------------------------------------------------------
diff = cva_irs - cva_uncorr
se_diff = (cva_irs_error**2 + cva_uncorr_error**2) ** 0.5
assert diff > 3 * se_diff, "WWR CVA is not significantly higher than baseline"
AAD: Sensitivities With PyTorch Autodiff
The engine supports adjoint algorithmic differentiation, made possible by PyTorch’s computation graph.
You can compute Greeks (sensitivities) by enabling differentiation:
sc = SimulationController(..., differentiate=True)
This allows:
- pathwise sensitivities
- efficient Reverse-Mode differentiation
- differentiable regression layers for Bermudan options
- differentiable risk metrics
These are advanced capabilities typical of bank-level xVA systems.
Example:
As an example, the following plots demonstrate pathwise sensitivities (Greeks) computed via AAD.
Each line shows the derivative of the option value with respect to a chosen model parameter (e.g., spot, volatility, interest rate). These sensitivities were obtained in a single backward pass thanks to reverse-mode autodiff — something that would require multiple full re-valuations in traditional bump-and-revalue Monte Carlo.
This highlights three important points:
- Efficiency Reverse-mode AAD computes all Greeks at roughly the cost of a single simulation.
- Stability Sensitivities are smooth and differentiable, avoiding the noise of bump-and-revalue methods.
-
Generality
The same mechanism works for:
- Bermudan/early exercise products (via differentiable regression),
- CVA and exposure metrics,
- Multi-factor hybrid SDE models,
- Discontinuous payoffs when smoothing is enabled.
Below is an example sensitivity plot produced by the engine:
🐳 Docker Support
A full environment is available via Docker:
docker build -t mcengine .
docker run -it mcengine python tests/pv_tests/pv_european_option.py
No dependencies, no headaches — runs out of the box.
Source Code
👉 https://github.com/konstantineder/montecarlo-risk-engine
If you like the engine, consider ⭐ starring the repo or opening issues/suggestions!
Final Thoughts
This project aims to provide a research-grade, production-style Monte Carlo engine that:
- is transparent
- is extendable
- supports hybrid multi-factor models
- runs on PyTorch for speed & autodiff
- handles real-world xVA phenomena like WWR
- produces correct, validated numerical results
If you're a quant, quant dev, fintech engineer, or researcher, I hope you'll find it useful.
If you'd like a deep dive into:
- American Option pricing (American Monte Carlo via Longstaff-Schwartz; new approaches using Machine Learning techniques (reinforcement learning))
- AAD functionalities and implementation details
- Exposure and/or CVA analytics
- or any other fascinating topic covered by the engine
let me know in the comments!



Top comments (0)