Prerequisites

Requires IR-03 (swaption pricing). No stochastic calculus background needed; the Wiener process is built from scratch.

By the end, you can justify stochasticity from observed option prices, write down the Vasicek SDE from scratch, and test a simulation engine against the two identities it must satisfy; Figure 1, just below, maps exactly what that takes.

What this chapter builds: one new primitive, and the two identities that check it An assembly map of the chapter, read top to bottom. Two navy boxes across the top hold what arrives: the discount curve P(0,T) and today's short rate r(0), both from IR-01, and the at-the-money swaption from the IR-03 surface, which the chapter treats as illustrative rather than as an observed quote. Nothing flows out of the swaption box; it is the motivation, and a neutral hairline hangs two grey lines under it: why, a curve gives the option no value, it needs a distribution of r. A navy rail leaves the IR-01 box, runs down the left edge past everything and enters the Test 1 box at the foot, where its label reads P(0,T): that curve is the target Test 1 has to reproduce. On the right a third navy box holds HullWhite1F, the engine that ships, with a equals 0.05 and sigma equals 0.70 percent, asserted rather than calibrated. It arrives from outside the chapter, so it is navy, and a navy rail runs from it down the right edge into the same Test 1 box, labelled Hull-White paths, exp of minus the integral from 0 to T of r sub s, ds. Between the two rails is the gold dashed chain, the only work the chapter does. First the one new primitive, the Wiener increment dW at time t, normally distributed with mean zero and variance dt. A gold arrow labelled dW at time t carries it into the Vasicek short rate equation, dr equals a times b minus r, dt, plus sigma dW, under the risk-neutral measure. A dotted aside, pinned to that equation by a short neutral hairline, states the price of those constants: a, b and sigma are constant and illustrative, so this Vasicek cannot refit the IR-01 curve, while the engine above it can and IR-05, a draft, teaches it. Two gold arrows, each labelled r at time t, carry the law that equation defines into two boxes that read it twice. On the left the closed form, P(t,T) equals A times e to the minus B r, priced at the simulated r sub s. On the right the Vasicek paths, exp of minus the integral from 0 to s of r sub u, du, started from IR-01's 1W deposit. Those two boxes feed the first crimson box by two gold arrows, one labelled P(s,T) and one labelled exp of minus the integral from 0 to s of r sub u, du. Note the limits, because that difference is the tower property: Test 1 accumulates the short rate all the way to T, Test 2 only as far as the intermediate date s and then holds the bond P(s,T) for the rest. Test 2 is drawn first because it is what closes the Vasicek chain. It reads: the expectation under Q of exp of minus the integral from 0 to s of r sub u, du, times P(s,T), equals P(0,T), the tower property, at every s less than or equal to T, where P(0,T) here is Vasicek's own closed form. A crimson arrow labelled the law of r leaves Test 2, because the law of r is Vasicek's and Test 2 is what closes it, and reaches the answer tile: the law of r, a distribution and not a forecast. Below that, Test 1 reads: the expectation under Q of exp of minus the integral from 0 to T of r sub s, ds equals P(0,T), the same identity on a different engine, where P(0,T) here is the IR-01 curve. A teal arrow labelled paths of r leaves Test 1, because the paths XVA-01 simulates are HullWhite1F's, and reaches XVA-01, where they become the expected exposure profile EE(t). WHAT ARRIVES P(0,T), r(0) IR-01 curve ATM swaption IR-03, illustrative why: a curve gives the option no value; it needs a distribution of r ALSO ARRIVES HullWhite1F the engine that ships a = 0.05, σ = 0.70%, asserted, not calibrated BUILT HERE dWt ~ N(0, dt) the one new primitive dWt drt = a(b − rt)dt + σ dWt Vasicek, under Q the price of constants a, b and σ are constant and illustrative, so this Vasicek cannot refit the IR-01 curve. The engine above can; IR-05 (Draft) teaches it. rt rt P(t,T) = A e−B r closed form, at the simulated rs Vasicek paths: exp(−∫0s ru du) from the 1W deposit P(s,T) exp(−∫0s ru du) TEST 2 EQ[ exp(−∫0s ru du) P(s,T) ] = P(0,T) the tower property, at every s ≤ T P(0,T) here is Vasicek's own closed form THE ARTEFACT the law of r the law of r not a forecast P(0,T) Hull-White paths: exp(−∫0T rs ds) TEST 1 EQ[ exp(−∫0T rs ds) ] = P(0,T) the same identity on a different engine P(0,T) here is the IR-01 curve paths of r XVA-01 · exposure EE(t) from these Hull-White paths
Figure 1 · IR-04 One new primitive turns a curve into a distribution, worth nothing until both identities hold. Navy carries what arrives, gold is built here; Test 2 closes the Vasicek chain, Test 1 checks the shipping engine. Structural map; not library output.

The problem with deterministic rates

Why a deterministic curve prices an at-the-money swaption at zero A payoff curve above a probability density, sharing one horizontal axis: the forward swap rate at expiry, from -0.5 to 9.5 percent. The payoff of the five-year into five-year at-the-money payer swaption is flat at zero until the strike of 4.358 percent, then rises with slope equal to the annuity 3.6398, and running off the top of the panel at 7.65 percent. The strike equals today’s forward, so the deterministic answer, marked by a solid dot sitting on the axis at 4.358 percent, is exactly zero. Below the axis, the distribution of the rate at expiry is shaded, centred on the same 4.358 percent with a standard deviation of 228 basis points. Dotted risers carry five equally spaced sample rates up to the payoff curve: the three at or below the strike all land on zero, the two above it land at 415 and 830 basis points. Averaging the payoff over the whole distribution gives 331 basis points, drawn as a crimson horizontal line above the axis, with a caliper measuring the gap between the two. The payoff of the average rate is zero; the average of the payoff is 331 basis points; the difference is the option. Payoff at expiry, bp of notional K = forward = 4.358% 0 400 800 331 bp every rate below K lands on zero 0 bp this gap = the price 0% 2% 4% 6% 8% Forward swap rate at expiry, in % Shaded: its distribution, sd = 228 bp payoff of the average rate 0 bp average of the payoff 331 bp The difference is the option.
Figure 2 · IR-04 A deterministic curve prices every at-the-money swaption at zero. Make the rate random and the same swaption is worth 331 bp of notional. That gap is the option. Read the two levels off the left edge: the dot is the payoff of the average, the crimson line is the average of the payoff. Source: forward swap rate 4.3581%, annuity 3.6398, xvafoundations on the Chapter 01 curve. The 102 bp normal vol is the illustrative 5Y x 5Y cell IR-03 labels as such; the rest follows in closed form.

Under the annuity measure $\mathbb{Q}^A$ of Chapter 03 the forward swap rate is a martingale, so $\mathbb{E}^{A}\bigl[S_{\alpha,\beta}(T_\alpha)\bigr] = S_{\alpha,\beta}(0)$, which at an at-the-money quote is the strike: plugging the mean in returns $\max(K - K,\,0) = 0$ for every cell on that surface. Convexity of $\max(\cdot,0)$ forces $\mathbb{E}^{A}\bigl[\max(S_{\alpha,\beta}(T_\alpha) - K,\,0)\bigr] > 0$, and no bootstrap produces the difference.

The Wiener process

The simplest object that captures unpredictable, continuous movement is the Wiener process $W_t$. Its increments are independent across non-overlapping intervals and normally distributed:

Wiener process increment $$dW_t \sim \mathcal{N}(0,\, dt)$$

The variance equals $dt$, not $dt^2$: shocks grow as the square root of time. This is why daily vol scales to annual vol by $\sqrt{252}$, and why longer-dated options are more expensive.

From noise to a short rate model

A Wiener process is just noise. A rate model adds a drift, where the rate is pulled, and a diffusion, how much noise surrounds that pull:

Generic SDE for the short rate $$dr_t = \underbrace{\mu(r_t,\, t)}_{\text{drift}} \, dt + \underbrace{\sigma(r_t,\, t)}_{\text{diffusion}} \, dW_t$$

The choice of $\mu$ and $\sigma$ is the model. Mean reversion toward a long-run level $b$ at speed $a$, with constant volatility $\sigma$, gives the Vasicek model ([1], Section 3.2) under the risk-neutral measure $\mathbb{Q}$:

Vasicek SDE (under $\mathbb{Q}$) $$dr_t = a\,(b - r_t)\,dt + \sigma\,dW_t$$

Above $b$ the drift pulls down, below $b$ it pulls up: Ornstein-Uhlenbeck applied to rates. Both constants have a unit you can feel. At $a = 0.15$ the pull closes half the gap to $b$ in $\ln 2 / a = 4.62$ years, and the fan stops widening once that pull balances the noise, at $\sigma/\sqrt{2a} = 1.46\%$; five years reaches $\sigma\sqrt{(1 - e^{-2aT})/2a} = 1.29\%$.

The Vasicek bond price formula

Vasicek's advantage is a closed form for the bond price. Risk-neutral pricing gives

Risk-neutral pricing of the ZCB $$P(t,T) = \mathbb{E}^{\mathbb{Q}}_t\!\left[\exp\!\Bigl(-\!\int_t^T r_s\,ds\Bigr)\right]$$

Under Vasicek $r$ is Markov under $\mathbb{Q}$ with constant $(a, b, \sigma)$ and $r_T \mid r_t$ is Gaussian, so $\int_t^T r_s\,ds$ is a limit of sums of Gaussians and Gaussian too. The expectation of a lognormal is exponential-affine:

Vasicek zero-coupon bond price $$P(t,T) = A(t,T)\,\exp\!\bigl(-B(t,T)\,r_t\bigr)$$ $$B(t,T) = \frac{1 - e^{-a(T-t)}}{a}, \qquad A(t,T) = \exp\!\left[\left(b - \frac{\sigma^2}{2a^2}\right)\!\bigl(B(t,T) - (T - t)\bigr) - \frac{\sigma^2\,B(t,T)^2}{4a}\right]$$

$B(t,T) = \int_0^{T-t} e^{-au}\,du$ and $-\partial \ln P(t,T)/\partial r_t = B(t,T)$: the bond's sensitivity to today's short rate, accumulated over the remaining life and damped by the mean reversion (derivation in [1], Section 3.2.1). It also lets the martingale test below reprice analytically at any simulated $r_s$, with no second Monte Carlo nested inside the first.

At $a = 0.15$, $b = 4\%$, $\sigma = 0.80\%$, $r_0 = 4.33\%$:

$B(0,5) = \frac{1 - e^{-0.15 \times 5}}{0.15} = 3.5176$, and $\ln A(0,5) = \bigl(b - \frac{\sigma^2}{2a^2}\bigr)(B - T) - \frac{\sigma^2}{4a}B^2 = -0.0585$, giving $P(0,5) = e^{-0.0585} \times e^{-3.5176 \times 0.0433} = 0.8099$.

Monte Carlo pricing

Start at $r_0$, draw increments, accumulate $\exp(-\int_0^T r_s\,ds)$ along the path, and average the discounted payoff:

Monte Carlo price $$V_0 = \mathbb{E}^{\mathbb{Q}}\!\left[\exp\!\Bigl(-\!\int_0^T r_s\,ds\Bigr) \cdot h(r_T)\right] \approx \frac{1}{N} \sum_{i=1}^{N} e^{-\sum_k \frac{1}{2}\left(r_{t_k}^{(i)} + r_{t_{k+1}}^{(i)}\right) \Delta t} \cdot h\!\left(r_T^{(i)}\right)$$

The exponential factor is the path-wise stochastic discount factor, kept in integral form throughout to avoid confusion with the deterministic curve $P(0,T)$. $h(r_T)$ is the payoff at maturity.

The fan of futures the model considers possible
Over ten years the distribution widens from a point at 4.33% to a 5-95% span of 1.73%-6.42%, while the mean falls only to 4.07%: the spread is the story, not the drift.
Vasicek transition law at $r_0 = 4.33\%$ (the Chapter 01 SOFR 1W deposit), $a = 0.15$, $b = 4.00\%$, $\sigma = 0.80\%$, all three illustrative rather than calibrated. Optionality prices off the band, not off one forward. Source: xvafoundations, closed form, no Monte Carlo.

The martingale test

Test 1: discount factor alone

Can the simulation reproduce today's $P(0,T)$ from the stochastic discount factor alone? Under $\mathbb{Q}$ the path-wise factor must average to the deterministic price:

Discount factor identity $$\mathbb{E}^{\mathbb{Q}}\!\left[\exp\!\Bigl(-\!\int_0^T r_s\,ds\Bigr)\right] = P(0,T)$$

Constant-parameter Vasicek cannot reproduce the Chapter 01 curve at all, which is why its $a$, $b$ and $\sigma$ above are illustrative rather than calibrated. Hull-White replaces the long-run level $b$ with a time-dependent $\theta(t)/a$, which forces today's bond prices to reprice exactly, and it is what the production engine and the XVA series simulate.

The identity does not care which short-rate model you feed it, so this runs on the engine that ships: xvafoundations.models.HullWhite1F on the Chapter 01 SOFR curve, every path starting at that curve's own $r(0) = 4.3282\%$. Its $a = 0.05$ and $\sigma = 0.70\%$ are asserted rather than calibrated, as every chapter downstream of this one discloses, pending IR-06 (Draft).

Then plot the difference, not the two curves. On a bond-price axis running 97.9% down to 66.7% a 10bp error is a tenth of a pixel: an overlay cannot fail visibly, so it is not a test.

Term structure test: simulated stochastic discount factor minus $P(0,T)$, in standard errors
No error reaches one Monte Carlo standard error, and the signs are mixed: the largest is 0.53 at one year, which is what sampling noise around zero looks like.
In basis points the errors run between -0.67 and +0.96, but one standard error runs 0.10 at six months to 5.05 at ten years, so each error is shown against its own rather than against a flat tolerance. Source: xvafoundations.models.HullWhite1F.simulate, $a = 0.05$, $\sigma = 0.70\%$, seed 137, 20,000 paths, 200 steps to 10y, on the Chapter 01 SOFR curve.

Why the accumulator is not a trapezoid on the short rate

Exact simulation buys an unbiased short rate. It does not buy an unbiased money-market account on a finite grid. Write $r_t = x_t + \alpha(t)$: the deterministic $\alpha$ carries $f(0,t)$, which log-linear interpolation of $\ln P(0,T)$ leaves piecewise constant with a jump at every curve pillar. Trapezoid $r$ itself and each panel straddling a jump misses $\tfrac{1}{2}\Delta t$ times it, so the rule drops to first order, and the straddling-panel count is the pillar count, which does not shrink with $\Delta t$.

Bias of a raw trapezoid on the short rate, in bp, exact, no Monte Carlo
Maturity200 steps400800
0.5Y0.330.160.08
1Y1.670.840.41
2Y1.560.810.38
3Y1.520.740.35
5Y1.040.540.29
7Y0.900.390.18
10Y0.790.400.17

The two pillars inside the first year drop $f$ by 13.7bp and 56.2bp, and $0.025 \times 69.8 = 1.75$bp predicts the 1.74bp of accumulation lost, or the 1.67bp in the first column once multiplied by $P(0,1) = 0.9596$. Halving the step halves each column rather than quartering it: first order, and no path count touches it.

So the library does not accumulate that way. $\int_0^t f(0,u)\,du = -\ln P(0,t)$ holds by definition of the instantaneous forward, under every interpolation and every pillar set, so $\int_0^t \alpha$ is closed form, that identity plus a Hull-White convexity term, and only the smooth $x$ is quadratured. No panel straddles a pillar. On the same seven maturities at 200 steps the residual bias is at most 0.0003bp, and it falls by exactly 4.000 at each of four halvings: second order, measured. The trapezoid survives under the name money_market_account_trapezoid, where the model-risk sweep still measures it; there the switch took the worst bias from 11.39bp to 0.012bp.

Test 2: the tower property

The stronger check: at any intermediate $s$, the path-wise discount factor times the conditional bond price must average back to today's $P(0,T)$. That is the tower property of conditional expectation, applied to pricing:

Tower property (martingale condition) $$\mathbb{E}^{\mathbb{Q}}\!\left[\exp\!\Bigl(-\!\int_0^s r_u\,du\Bigr)\cdot P(s,T)\right] = P(0,T), \qquad \forall\, 0 \leq s \leq T$$

Here $P(s,T)$ is the Vasicek analytical price at the simulated $r_s$. On a path where rates rose you discount harder, but the bond you still hold is cheaper by the offsetting amount, so the product does not move: the two effects are the same effect seen twice.

Tower property: stochastic discount factor times $P(s,5)$ vs $P(0,5)$ at six monitoring dates
Six running averages, spread over 60 basis points at ten paths, close to within 4.6bp of each other and 6.3bp of P(0,5) = 0.809920 by three thousand paths.
Six dates $s = 0.5$ to $4.0$Y, all targeting $T = 5$Y, same Vasicek parameters, 200 exact steps; the closed-form $P(0,5) = 0.809920$ is the dashed crimson line. Provenance: not library output. Vasicek is a teaching model and does not ship in xvafoundations, so the run is simulated in your browser at seed 271; only the target line is library.

Python implementation

The teaching version of both tests: exact Vasicek step, no library dependency, 50,000 paths. Its Test 1 targets the Vasicek analytical $P(0,T)$, for the reason given above; the chart ran the same identity on HullWhite1F.

Python · PyTorch
import math

import torch

torch.set_default_dtype(torch.float64)


class VasicekModel:
    """Vasicek short rate under Q:  dr_t = a (b - r_t) dt + sigma dW_t."""

    def __init__(self, r0=0.0433, a=0.15, b=0.04, sigma=0.008):
        self.r0, self.a, self.b, self.sigma = r0, a, b, sigma

    def bond_price(self, r_t, t, T):
        """Analytical Vasicek ZCB price P(t, T) given r_t."""
        tau = T - t
        B = (1.0 - math.exp(-self.a * tau)) / self.a
        ln_A = ((self.b - self.sigma ** 2 / (2 * self.a ** 2)) * (B - tau)
                - self.sigma ** 2 * B ** 2 / (4 * self.a))
        return torch.exp(ln_A - B * torch.as_tensor(r_t))

    def simulate(self, T, n_steps, n_paths, seed=137):
        """Exact Vasicek step. Returns (n_paths, n_steps + 1) short rates and
        the trapezoidal integral of r accumulated along each path, matching
        xvafoundations.pricing.money_market_account_trapezoid."""
        torch.manual_seed(seed)
        dt = T / n_steps
        decay = math.exp(-self.a * dt)
        shift = self.b * (1.0 - decay)
        vol = self.sigma * math.sqrt(
            (1.0 - math.exp(-2.0 * self.a * dt)) / (2.0 * self.a))

        rates = torch.zeros(n_paths, n_steps + 1)
        rates[:, 0] = self.r0
        for i in range(n_steps):
            rates[:, i + 1] = rates[:, i] * decay + shift + vol * torch.randn(n_paths)

        integrals = torch.zeros_like(rates)
        integrals[:, 1:] = torch.cumsum(
            0.5 * (rates[:, :-1] + rates[:, 1:]) * dt, dim=1)
        return rates, integrals


model = VasicekModel()
T_max, n_steps = 10.0, 400
rates, integrals = model.simulate(T=T_max, n_steps=n_steps, n_paths=50_000)

# Test 1: E^Q[exp(-int_0^T r ds)] = P(0,T).  Constant-parameter Vasicek has a
# smooth drift, so the trapezoid stays second order here and the residual at
# 400 steps is Monte Carlo noise, not bias.  Point the same rule at a
# bootstrapped curve and it drops to first order, because f(0,t) jumps at every
# pillar: that is the table above.  The library avoids it by integrating alpha
# in closed form and quadraturing only x.
for T in [0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0]:
    k = round(T * n_steps / T_max)
    sim = torch.exp(-integrals[:, k]).mean()
    err_bp = (sim - model.bond_price(model.r0, 0.0, T)).item() * 1e4
    print(f"T={T:4.1f}Y  sim={sim:.6f}  err={err_bp:+.2f}bp")

# Test 2: E^Q[exp(-int_0^s r du) * P(s,5)] = P(0,5)
P05 = model.bond_price(model.r0, 0.0, 5.0)
for s in [0.5, 1.0, 1.5, 2.0, 3.0, 4.0]:
    k = round(s * n_steps / T_max)
    avg = (torch.exp(-integrals[:, k]) * model.bond_price(rates[:, k], s, 5.0)).mean()
    print(f"s={s:4.1f}Y  E[disc*P]={avg:.6f}  err={(avg - P05).item() * 1e4:+.2f}bp")

Why this matters for XVA

Every exposure number in the XVA series averages over paths this machinery produces, so understating rate volatility understates exposure and therefore CVA. XVA-01 turns the paths into an exposure profile; XVA-03 derives the CVA integral from it.

The moral

The discipline comes from the SDE: it constrains which futures are consistent with market prices. The averaging is Monte Carlo. Together they give a price that cannot be arbitraged, provided the model is calibrated to instruments you can observe.

References

  1. D. Brigo and F. Mercurio, Interest Rate Models: Theory and Practice, 2nd edition, Springer, 2006. Sections 3.2 (the model) and 3.2.1 (the analytical bond price).
  2. S. E. Shreve, Stochastic Calculus for Finance II: Continuous-Time Models, Springer, 2004. Chapters 3-5: Brownian motion, SDEs, change of measure.
  3. O. A. Vasicek, "An Equilibrium Characterization of the Term Structure," Journal of Financial Economics, 5(2), 1977. The original paper.