DEV Community

Konstantin E.
Konstantin E.

Posted on

Building a Monte Carlo Risk Engine in PyTorch: Pricing, xVA, Exposure Simulation & Wrong-Way Risk

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"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Efficiency Reverse-mode AAD computes all Greeks at roughly the cost of a single simulation.
  2. Stability Sensitivities are smooth and differentiable, avoiding the noise of bump-and-revalue methods.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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)