Advanced Interest Rate Models: HJM Framework & LMM Guide

Michael BrenndoerferDecember 7, 202564 min read

Master the Heath-Jarrow-Morton framework and LIBOR Market Model for pricing caps, floors, and swaptions. Implement forward rate dynamics in Python.

Reading Level

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Advanced Interest Rate Models (HJM and LMM)

In the previous chapter, we explored short-rate models like Vasicek, CIR, and Hull-White, which describe the evolution of the instantaneous short rate r(t)r(t). While these models provide elegant closed-form solutions for bond prices and some derivatives, they face a fundamental limitation: starting from a single factor (the short rate), they derive the entire term structure of interest rates. This approach often struggles to match the rich dynamics observed in real yield curves and can produce unrealistic implied volatility structures for interest rate derivatives. This gap between simple models and market reality led to frameworks that better capture interest rate behavior.

The Heath-Jarrow-Morton (HJM) framework and the LIBOR Market Model (LMM) provide a significant advance in interest rate modeling. Rather than modeling a latent short rate and deriving forward rates, these frameworks model forward rates directly. This approach aligns model variables with market observables. The HJM framework, developed by David Heath, Robert Jarrow, and Andrew Morton in 1992, models the evolution of the entire instantaneous forward rate curve. By treating the forward curve as the fundamental object, HJM unified different modeling approaches. The LMM, developed by Brace, Gatarek, and Musiela (1997) and Jamshidian (1997), models discrete forward LIBOR rates directly, which are the actual rates quoted in the market for caps, floors, and swaptions. This practical orientation made LMM the workhorse of the derivatives trading desk.

These advanced models enabled you to price complex interest rate derivatives consistently with observed market prices. While short-rate models require calibration tricks to match market data, HJM and LMM frameworks naturally incorporate the current yield curve and can be calibrated directly to liquid derivative prices. Matching market prices at initialization and flexible volatility structures make these frameworks essential for pricing derivatives. This chapter develops both frameworks from first principles, demonstrating their mathematical foundations and practical implementation.

The Heath-Jarrow-Morton Framework

The HJM framework represents a fundamental conceptual shift from short-rate modeling. Instead of specifying dynamics for a single rate and deriving forward rates, HJM directly models the evolution of the entire forward rate curve. Modeling the observed rate continuum directly is better than deriving it from a simple process. This section develops the framework step by step, building from the definition of instantaneous forward rates through to the crucial no-arbitrage condition that constrains all valid HJM models.

Instantaneous Forward Rates

Recall from our discussion of the term structure of interest rates in Part II that the instantaneous forward rate f(t,T)f(t, T) represents the rate at time tt for instantaneous borrowing at future time TT. This concept captures the market's expectation, adjusted for risk, of what very short-term borrowing costs will be at some future date. The instantaneous forward rate relates to zero-coupon bond prices through a fundamental relationship that connects the entire forward curve to tradeable securities:

P(t,T)=exp(tTf(t,u)du)P(t, T) = \exp\left(-\int_t^T f(t, u) \, du\right)

where:

  • P(t,T)P(t, T): price at time tt of a zero-coupon bond maturing at TT
  • f(t,u)f(t, u): instantaneous forward rate at time tt for maturity uu
  • r(t)=f(t,t)r(t) = f(t, t): short rate, representing the instantaneous forward rate for immediate borrowing

This exponential relationship reveals a deep connection: the bond price is determined by accumulating the forward rates along the path from the current time to maturity. Intuitively, you can think of buying a zero-coupon bond as locking in a series of instantaneous forward borrowing rates from today until maturity. The integral in the exponent sums up all these infinitesimal forward rates, and the exponential converts this accumulated rate into a discount factor.

The key insight of HJM is that specifying the dynamics of f(t,T)f(t, T) for all maturities TT simultaneously determines the entire yield curve evolution. This means we can model the term structure as a single infinite-dimensional object evolving through time. However, not all specifications are arbitrage-free. If we were free to choose any dynamics for forward rates, we could inadvertently create money machines by trading in bonds of different maturities. The key insight of HJM is the precise restriction on forward rate dynamics that ensures no arbitrage, thereby identifying exactly which models are economically meaningful.

Forward Rate Dynamics Under HJM

Under the HJM framework, the instantaneous forward rate evolves according to a stochastic differential equation that allows both deterministic trends and random fluctuations:

df(t,T)=α(t,T)dt+σ(t,T)dW(t)df(t, T) = \alpha(t, T) \, dt + \sigma(t, T) \, dW(t)

where:

  • α(t,T)\alpha(t, T): drift of the forward rate for maturity TT
  • σ(t,T)\sigma(t, T): volatility function (can be a vector for multiple factors)
  • W(t)W(t): Brownian motion under the real-world probability measure

The functions α\alpha and σ\sigma can depend on the current time tt, the maturity TT, and potentially on the entire current forward rate curve {f(t,u):ut}\{f(t, u) : u \geq t\}. This flexibility is both a strength and a challenge. On one hand, it allows us to capture rich dynamics including level shifts, slope changes, and curvature movements in the yield curve. On the other hand, unrestricted choices of drift and volatility can lead to arbitrage opportunities or economically implausible behavior. The HJM framework addresses this by providing a precise mathematical condition that separates valid models from invalid ones.

The HJM Drift Condition

Under the risk-neutral measure Q\mathbb{Q}, the drift α(t,T)\alpha(t, T) is not free to choose. It is completely determined by the volatility function σ(t,T)\sigma(t, T) through the HJM drift condition:

α(t,T)=σ(t,T)tTσ(t,s)ds\alpha(t, T) = \sigma(t, T) \int_t^T \sigma(t, s) \, ds

where:

  • α(t,T)\alpha(t, T): forward rate drift under the risk-neutral measure
  • σ(t,T)\sigma(t, T): volatility function of the forward rate

This result states that once you specify how forward rates fluctuate randomly (through the volatility function), the average direction they move (the drift) is automatically determined. There is no freedom to choose both independently. Defining the cumulative volatility Σ(t,T)=tTσ(t,s)ds\Sigma(t, T) = \int_t^T \sigma(t, s) \, ds, this condition can be written compactly as:

α(t,T)=σ(t,T)Σ(t,T)\alpha(t, T) = \sigma(t, T) \cdot \Sigma(t, T)

where:

  • α(t,T)\alpha(t, T): forward rate drift under risk-neutral measure
  • σ(t,T)\sigma(t, T): forward rate volatility function
  • Σ(t,T)=tTσ(t,s)ds\Sigma(t, T) = \int_t^T \sigma(t, s) \, ds: cumulative volatility

The cumulative volatility Σ(t,T)\Sigma(t, T) has a natural interpretation: it measures the total volatility accumulated from the current forward rate maturity out to the bond maturity. This quantity appears naturally because it represents the volatility of the zero-coupon bond price itself. The drift condition says that forward rates must drift upward by an amount equal to their own volatility times the bond price volatility.

HJM Drift Condition

The HJM drift condition states that in an arbitrage-free economy, the drift of forward rates under the risk-neutral measure is uniquely determined by the volatility structure. This means you only need to specify the volatility function σ(t,T)\sigma(t, T) to fully characterize the model.

Derivation of the HJM Drift Condition

To understand why this condition must hold, let's derive it step by step. The derivation illuminates the deep connection between forward rate dynamics and bond price behavior, and shows why the no-arbitrage requirement imposes such specific constraints. The zero-coupon bond price satisfies:

P(t,T)=exp(tTf(t,u)du)P(t, T) = \exp\left(-\int_t^T f(t, u) \, du\right)

where:

  • P(t,T)P(t, T): price of a zero-coupon bond maturing at TT
  • f(t,u)f(t, u): instantaneous forward rate

Applying Itô's lemma requires determining the dynamics of the log-price, since the bond price is an exponential function of an integral. Let X(t)=lnP(t,T)=tTf(t,u)duX(t) = \ln P(t, T) = -\int_t^T f(t, u) \, du. Using the Leibniz integral rule for stochastic processes, which tells us how to differentiate an integral when both the integrand and limits change, the differential is:

dX(t)=f(t,t)dttTdf(t,u)du(Leibniz integral rule)=r(t)dttT[α(t,u)dt+σ(t,u)dW(t)]du(Substitute dynamics)=(r(t)tTα(t,u)du)dtΣ(t,T)dW(t)(Rearrange terms)\begin{aligned} dX(t) &= f(t, t) \, dt - \int_t^T df(t, u) \, du && \text{(Leibniz integral rule)} \\ &= r(t) \, dt - \int_t^T [\alpha(t, u) \, dt + \sigma(t, u) \, dW(t)] \, du && \text{(Substitute dynamics)} \\ &= \left( r(t) - \int_t^T \alpha(t, u) \, du \right) dt - \Sigma(t, T) \, dW(t) && \text{(Rearrange terms)} \end{aligned}

where:

  • X(t)X(t): log-price of the zero-coupon bond
  • f(t,t)=r(t)f(t, t) = r(t): instantaneous short rate
  • α(t,u)\alpha(t, u): drift of the forward rate
  • Σ(t,T)=tTσ(t,s)ds\Sigma(t, T) = \int_t^T \sigma(t, s) \, ds: cumulative volatility
  • W(t)W(t): Brownian motion

The first term f(t,t)dt=r(t)dtf(t, t) \, dt = r(t) \, dt arises because as time advances, the lower limit of integration moves, and the forward rate at that point "drops out" of the integral. The second term captures how the forward rates within the integral are themselves changing. Notice that the stochastic term simplifies beautifully: integrating the volatility function from tt to TT gives exactly the cumulative volatility Σ(t,T)\Sigma(t, T).

Since P(t,T)=eX(t)P(t, T) = e^{X(t)}, Itô's lemma gives dP/P=dX+12(dX)2dP/P = dX + \frac{1}{2}(dX)^2. The additional term 12(dX)2\frac{1}{2}(dX)^2 is the Itô correction that accounts for the fact that exponentiating a stochastic process is not the same as exponentiating its drift. Substituting dXdX and noting that (dX)2=Σ(t,T)2dt(dX)^2 = \Sigma(t, T)^2 dt (since only the Brownian motion term contributes to the quadratic variation), the bond price dynamics are:

dP(t,T)P(t,T)=(r(t)tTα(t,u)du+12Σ(t,T)2)dtΣ(t,T)dW(t)\frac{dP(t, T)}{P(t, T)} = \left( r(t) - \int_t^T \alpha(t, u) \, du + \frac{1}{2}\Sigma(t, T)^2 \right) dt - \Sigma(t, T) \, dW(t)

where:

  • r(t)r(t): instantaneous short rate
  • α(t,u)\alpha(t, u): forward rate drift
  • Σ(t,T)=tTσ(t,s)ds\Sigma(t, T) = \int_t^T \sigma(t, s) \, ds: volatility of the zero-coupon bond
  • W(t)W(t): Brownian motion

This equation tells us exactly how the bond price evolves. The drift consists of three parts: the short rate r(t)r(t), a negative contribution from the integrated forward rate drifts, and a positive convexity adjustment 12Σ(t,T)2\frac{1}{2}\Sigma(t, T)^2. The volatility of the bond price is simply the cumulative forward rate volatility.

Under the risk-neutral measure Q\mathbb{Q}, all traded assets must have a drift equal to the risk-free rate r(t)r(t). This is the fundamental requirement of risk-neutral pricing: when we discount at the short rate, all asset prices become martingales. Therefore, the term in the parentheses must equal r(t)r(t):

r(t)tTα(t,u)du+12Σ(t,T)2=r(t)r(t) - \int_t^T \alpha(t, u) \, du + \frac{1}{2}\Sigma(t, T)^2 = r(t)

where:

  • r(t)r(t): instantaneous short rate
  • α(t,u)\alpha(t, u): forward rate drift
  • Σ(t,T)\Sigma(t, T): cumulative volatility

Simplifying this equation by canceling r(t)r(t) from both sides:

tTα(t,u)du=12Σ(t,T)2\int_t^T \alpha(t, u) \, du = \frac{1}{2}\Sigma(t, T)^2

where:

  • α(t,u)\alpha(t, u): forward rate drift
  • Σ(t,T)\Sigma(t, T): cumulative volatility

This equation says that the integrated drift must equal half the squared cumulative volatility. To extract the drift at a specific maturity, we differentiate both sides with respect to TT. Differentiating both sides with respect to TT yields the HJM drift condition. Using the chain rule for the term 12Σ(t,T)2\frac{1}{2}\Sigma(t, T)^2:

T(tTα(t,u)du)=T(12Σ(t,T)2)α(t,T)=Σ(t,T)Σ(t,T)T(Chain rule)=Σ(t,T)σ(t,T)(Fundamental Thm of Calculus)=σ(t,T)tTσ(t,s)ds(Substitute definition)\begin{aligned} \frac{\partial}{\partial T} \left( \int_t^T \alpha(t, u) \, du \right) &= \frac{\partial}{\partial T} \left( \frac{1}{2}\Sigma(t, T)^2 \right) \\ \alpha(t, T) &= \Sigma(t, T) \frac{\partial \Sigma(t, T)}{\partial T} && \text{(Chain rule)} \\ &= \Sigma(t, T) \cdot \sigma(t, T) && \text{(Fundamental Thm of Calculus)} \\ &= \sigma(t, T) \int_t^T \sigma(t, s) \, ds && \text{(Substitute definition)} \end{aligned}

where:

  • α(t,T)\alpha(t, T): forward rate drift
  • Σ(t,T)\Sigma(t, T): cumulative volatility
  • σ(t,T)\sigma(t, T): forward rate volatility function

On the left side, the Fundamental Theorem of Calculus tells us that differentiating the integral gives us the integrand evaluated at the upper limit. On the right side, we apply the chain rule: the derivative of 12u2\frac{1}{2}u^2 with respect to uu is uu, and we multiply by the derivative of Σ(t,T)\Sigma(t, T) with respect to TT, which is σ(t,T)\sigma(t, T) by the Fundamental Theorem again.

This restriction ensures that the discounted bond price is a martingale under Q\mathbb{Q}, which is equivalent to the absence of arbitrage. This result applies universally: any arbitrage-free model of forward rates must satisfy this condition, regardless of the specific volatility structure chosen.

Short-Rate Models as HJM Special Cases

The HJM framework encompasses all short-rate models as special cases. By choosing specific volatility functions, we recover familiar models that we studied in the previous chapter. This unification provides important insight: the different short-rate models are not fundamentally different approaches to interest rate modeling but rather different choices of volatility structure within the HJM framework.

Ho-Lee Model: Setting σ(t,T)=σ\sigma(t, T) = \sigma (constant) gives a volatility that does not depend on either time or maturity. This is the simplest possible choice. The drift becomes:

α(t,T)=σ2(Tt)\alpha(t, T) = \sigma^2 (T - t)

where:

  • α(t,T)\alpha(t, T): forward rate drift
  • σ\sigma: constant volatility parameter
  • TtT - t: time to maturity

The forward rate dynamics become:

df(t,T)=σ2(Tt)dt+σdWQ(t)df(t, T) = \sigma^2 (T - t) \, dt + \sigma \, dW^\mathbb{Q}(t)

where:

Notice that the drift increases linearly with maturity. This reflects the convexity adjustment: longer-maturity forward rates must drift upward faster to compensate for the risk in holding longer-dated bonds. The constant volatility means that all forward rates fluctuate by the same amount, leading to parallel shifts in the yield curve.

Hull-White Model: Setting the volatility function to:

σ(t,T)=σeκ(Tt)\sigma(t, T) = \sigma e^{-\kappa(T-t)}

where:

  • σ(t,T)\sigma(t, T): volatility function of the forward rate
  • σ\sigma: short rate volatility parameter
  • κ\kappa: mean reversion parameter
  • TtT - t: time to maturity

This specification recovers the Hull-White model. The mean reversion parameter κ\kappa controls how volatility decreases with maturity. Economically, this captures the intuition that short-term rates are more volatile than long-term rates because the latter represent averages of many future short rates. As maturity increases, the individual rate fluctuations average out, reducing overall volatility.

In[2]:
Code
import numpy as np
from scipy.integrate import quad


def hjm_drift(t, T, sigma_func):
    """
    Calculate HJM drift for a given volatility function.

    Parameters:
    -----------
    t : float
        Current time
    T : float
        Forward rate maturity
    sigma_func : callable
        Volatility function sigma(t, s) for s in [t, T]
    """
    sigma_t_T = sigma_func(t, T)
    integral, _ = quad(lambda s: sigma_func(t, s), t, T)
    return sigma_t_T * integral


# Define volatility functions for different models
def ho_lee_vol(t, T, sigma=0.01):
    """Ho-Lee: constant volatility"""
    return sigma


def hull_white_vol(t, T, sigma=0.01, kappa=0.1):
    """Hull-White: exponentially decaying volatility"""
    return sigma * np.exp(-kappa * (T - t))


# Calculate drifts for different maturities
t = 0
maturities = np.linspace(0.01, 10, 100)

ho_lee_drifts = [hjm_drift(t, T, ho_lee_vol) for T in maturities]
hull_white_drifts = [
    hjm_drift(t, T, lambda t, s: hull_white_vol(t, s, sigma=0.01, kappa=0.1))
    for T in maturities
]
Out[3]:
Visualization
HJM volatility functions for Ho-Lee and Hull-White models. The Ho-Lee model (blue) specifies constant volatility, implying uniform shocks across the curve, while the Hull-White model (orange) incorporates exponential decay to capture the dampening of long-term rate volatility due to mean reversion.
HJM volatility functions for Ho-Lee and Hull-White models. The Ho-Lee model (blue) specifies constant volatility, implying uniform shocks across the curve, while the Hull-White model (orange) incorporates exponential decay to capture the dampening of long-term rate volatility due to mean reversion.
Out[4]:
Visualization
Line plot comparing HJM drift functions for Ho-Lee and Hull-White models across maturities.
HJM drift profiles for different volatility specifications. The Ho-Lee drift (blue) grows linearly with maturity, which can be destabilizing, whereas the Hull-White drift (orange) saturates, reflecting a physically realistic bound on long-term rate trends.

The Ho-Lee drift grows without bound as maturity increases, which can lead to unrealistic long-term behavior. Forward rates at very long maturities would drift upward at an accelerating pace, potentially reaching implausibly high levels. The Hull-White specification, with its exponentially decaying volatility, produces a bounded drift that stabilizes at longer maturities. This behavior aligns better with economic intuition: long-term rates should not exhibit explosive dynamics.

Simulating Forward Rate Curves Under HJM

To implement HJM in practice, we discretize time and simulate the forward rate curve evolution. Starting from today's observed forward curve f(0,T)f(0, T), we evolve each point on the curve simultaneously. This parallel evolution captures how the entire term structure moves together while respecting the no-arbitrage constraint. The simulation proceeds by applying small increments to each forward rate, with the drift determined by the HJM condition and the random shock drawn from a standard normal distribution scaled by the volatility function.

In[5]:
Code
def simulate_hjm_forward_curves(
    f0_curve, maturities, dt, num_steps, sigma_func, num_paths=1000
):
    """
    Simulate forward rate curve evolution under HJM framework.

    Parameters:
    -----------
    f0_curve : array
        Initial forward rate curve f(0, T) for each maturity
    maturities : array
        Maturity points T
    dt : float
        Time step
    num_steps : int
        Number of time steps
    sigma_func : callable
        Volatility function sigma(t, T)
    num_paths : int
        Number of Monte Carlo paths

    Returns:
    --------
    f_curves : array of shape (num_paths, num_steps+1, len(maturities))
        Simulated forward rate curves
    """
    num_maturities = len(maturities)
    f_curves = np.zeros((num_paths, num_steps + 1, num_maturities))
    f_curves[:, 0, :] = f0_curve

    for step in range(num_steps):
        t = step * dt
        dW = np.random.randn(num_paths) * np.sqrt(dt)

        for j, T in enumerate(maturities):
            if T > t:  # Only evolve rates for maturities beyond current time
                sigma = sigma_func(t, T)
                # Calculate cumulative volatility for drift
                # drift = sigma(t, T) * int_t^T sigma(t, s) ds
                integral, _ = quad(lambda s: sigma_func(t, s), t, T)
                drift = sigma * integral

                f_curves[:, step + 1, j] = (
                    f_curves[:, step, j] + drift * dt + sigma * dW
                )
            else:
                f_curves[:, step + 1, j] = np.nan  # Rate has "expired"

    return f_curves


# Initial flat forward curve at 5%
maturities = np.array([0.5, 1, 2, 3, 5, 7, 10])
f0_curve = np.ones(len(maturities)) * 0.05

# Simulate under Hull-White volatility
np.random.seed(42)
f_curves = simulate_hjm_forward_curves(
    f0_curve,
    maturities,
    dt=0.01,
    num_steps=100,  # 1 year
    sigma_func=lambda t, T: 0.01 * np.exp(-0.1 * (T - t)),
    num_paths=1000,
)
Out[6]:
Visualization
Line plot showing initial forward curve with shaded confidence band for simulated future curves.
Monte Carlo simulation of forward rate curves under HJM (Hull-White volatility). The fan chart (red shading) depicts the widening cone of uncertainty (5th-95th percentiles) over a 1-year horizon, with the median path drifting upward to satisfy the no-arbitrage condition.

The simulation shows how the forward rate curve evolves over time under HJM dynamics. The initial flat curve develops both level shifts and changes in shape, with uncertainty increasing over the simulation horizon. The widening confidence band reflects the accumulation of random shocks over time, while the upward drift of the median reflects the convexity adjustment built into the HJM drift condition. Note that as time passes, short-maturity rates "expire" and are no longer meaningful, which is why we shift the maturities when plotting the future distribution.

Key Parameters

The key parameters for the HJM framework simulations are:

  • f(0, T): Initial forward rate curve. The starting point for the evolution of the term structure, typically extracted from market bond prices or swap rates.
  • σ(t, T): Volatility function. Determines the magnitude of random fluctuations and, via the drift condition, the trend of forward rates. This is the sole input needed to specify an HJM model.
  • κ: Mean reversion parameter (in Hull-White specification). Controls how quickly volatility decays with maturity, influencing the correlation between forward rates of different tenors.
  • dt: Time step size. Smaller steps reduce discretization error in the simulation but increase computational cost.

The LIBOR Market Model

While the HJM framework provides a theoretically elegant approach to modeling forward rates, it works with instantaneous forward rates, which are not directly observable in markets. You cannot go to a broker and ask to trade the instantaneous forward rate for borrowing at exactly 2.5 years from now. You price caps, floors, and swaptions using discrete forward LIBOR rates for specific tenors (3-month, 6-month). The LIBOR Market Model (LMM), also known as the Brace-Gatarek-Musiela (BGM) model, directly models these market-observable rates. This alignment with market conventions makes LMM the natural choice for pricing and hedging interest rate derivatives.

Forward LIBOR Rates

Consider a set of tenor dates T0<T1<<TnT_0 < T_1 < \cdots < T_n with equal spacing δ=Ti+1Ti\delta = T_{i+1} - T_i (typically 0.25 or 0.5 years). The forward LIBOR rate Li(t)L_i(t) is the simply compounded rate at time tt for borrowing over the period [Ti,Ti+1][T_i, T_{i+1}]. Unlike the continuously compounded instantaneous forward rate in HJM, the LIBOR rate applies simple interest over a discrete period:

Li(t)=1δ(P(t,Ti)P(t,Ti+1)1)L_i(t) = \frac{1}{\delta}\left(\frac{P(t, T_i)}{P(t, T_{i+1})} - 1\right)

where:

  • δ=Ti+1Ti\delta = T_{i+1} - T_i: year fraction (accrual period)
  • P(t,Ti)P(t, T_i): price of a zero-coupon bond maturing at TiT_i
  • P(t,Ti+1)P(t, T_{i+1}): price of a zero-coupon bond maturing at Ti+1T_{i+1}

The formula expresses a simple arbitrage relationship: if you invest one dollar at the LIBOR rate from TiT_i to Ti+1T_{i+1}, you should receive 1+δLi(t)1 + \delta L_i(t) dollars. Alternatively, you could buy 1/P(t,Ti+1)1/P(t, T_{i+1}) bonds maturing at Ti+1T_{i+1}, which costs P(t,Ti)/P(t,Ti+1)P(t, T_i)/P(t, T_{i+1}) in terms of bonds maturing at TiT_i. Equating these two strategies yields the formula above.

These rates are directly observable from the forward rate agreement (FRA) market and serve as the underlying rates for caps and floors. When a bank quotes a 6-month LIBOR rate starting in 1 year, that is exactly the forward rate Li(t)L_i(t) for the appropriate index ii.

LMM Dynamics Under the Forward Measure

The key insight of LMM is that under the Ti+1T_{i+1}-forward measure QTi+1\mathbb{Q}^{T_{i+1}}, the forward LIBOR rate Li(t)L_i(t) is a martingale. This follows because Li(t)L_i(t) can be expressed as a ratio of asset prices (zero-coupon bonds), and ratios of tradeable assets are martingales under the numeraire corresponding to the denominator. This is a direct application of the change of numeraire theorem. When we use P(t,Ti+1)P(t, T_{i+1}) as the numeraire, any asset price divided by this numeraire becomes a martingale, and the forward rate is precisely such a ratio.

Under this forward measure, Li(t)L_i(t) has zero drift and follows:

dLi(t)Li(t)=σi(t)dWiTi+1(t)\frac{dL_i(t)}{L_i(t)} = \sigma_i(t) \, dW_i^{T_{i+1}}(t)

where:

  • σi(t)\sigma_i(t): volatility of the ii-th forward rate
  • WiTi+1(t)W_i^{T_{i+1}}(t): Brownian motion under the Ti+1T_{i+1}-forward measure QTi+1\mathbb{Q}^{T_{i+1}}

The lognormal dynamics implied by this equation have profound practical consequences. Because the rate follows geometric Brownian motion under its natural measure, its distribution at any future time is lognormal. This lognormal distribution leads directly to Black's formula for caplet pricing, providing the theoretical justification for the market convention that predated the formal LMM framework.

Forward Measure

The TT-forward measure uses the zero-coupon bond P(t,T)P(t, T) as numeraire. Under this measure, any asset price divided by P(t,T)P(t, T) is a martingale. The forward LIBOR rate Li(t)L_i(t) is naturally a martingale under the Ti+1T_{i+1}-forward measure.

Black's Formula for Caplets

A caplet pays δmax(Li(Ti)K,0)\delta \max(L_i(T_i) - K, 0) at time Ti+1T_{i+1}, where KK is the cap rate. This payoff represents the protection provided by the cap: if the realized LIBOR rate exceeds the cap strike, the holder receives the difference applied to the notional over the accrual period. Since Li(t)L_i(t) is a martingale under QTi+1\mathbb{Q}^{T_{i+1}} and follows lognormal dynamics, the caplet price has a Black-Scholes-type closed-form solution known as Black's formula:

Capleti=δP(0,Ti+1)[Li(0)N(d1)KN(d2)]\text{Caplet}_i = \delta \cdot P(0, T_{i+1}) \cdot [L_i(0) N(d_1) - K N(d_2)]

where:

  • Li(0)L_i(0): initial forward LIBOR rate
  • KK: cap strike rate
  • P(0,Ti+1)P(0, T_{i+1}): discount factor for the payment date
  • N()N(\cdot): cumulative standard normal distribution function
  • d1=ln(Li(0)/K)+12vi2vid_1 = \frac{\ln(L_i(0)/K) + \frac{1}{2}v_i^2}{v_i}
  • d2=d1vid_2 = d_1 - v_i
  • vi=0Tiσi(t)2dtv_i = \sqrt{\int_0^{T_i} \sigma_i(t)^2 \, dt}: integrated volatility (approximated as σiTi\sigma_i \sqrt{T_i} for constant volatility)

The formula terms provide the following intuition:

  • N(d2)N(d_2): probability that the option expires in-the-money under the forward measure
  • Li(0)N(d1)L_i(0) N(d_1): expected value of the floating rate at maturity, conditional on exercise
  • KN(d2)K N(d_2): expected cost of the fixed rate strike payment, conditional on exercise

The structure mirrors the Black-Scholes formula for equity options, which is no coincidence. Both arise from the same mathematical setting: lognormal dynamics under the relevant pricing measure. The discount factor P(0,Ti+1)P(0, T_{i+1}) converts the expected payoff under the forward measure to a present value.

A cap is simply a portfolio of caplets:

Cap=i=0n1Capleti\text{Cap} = \sum_{i=0}^{n-1} \text{Caplet}_i

where:

  • Cap: total value of the interest rate cap
  • Caplet_i: value of the ii-th individual caplet
  • nn: number of caplets in the contract

Each caplet in the cap protects against high rates in a different future period, and since the caplets have non-overlapping payoff dates, they can be valued independently and summed.

In[7]:
Code
import numpy as np
from scipy.stats import norm


def black_caplet_price(
    forward_rate, strike, maturity, volatility, discount_factor, delta=0.25
):
    """
    Price a caplet using Black's formula.

    Parameters:
    -----------
    forward_rate : float
        Current forward LIBOR rate L_i(0)
    strike : float
        Cap strike rate K
    maturity : float
        Time to caplet fixing date T_i
    volatility : float
        Forward rate volatility sigma_i
    discount_factor : float
        P(0, T_{i+1}) discount factor to payment date
    delta : float
        Accrual period (default 0.25 for quarterly)

    Returns:
    --------
    price : float
        Caplet price
    """
    if maturity <= 0:
        return max(forward_rate - strike, 0) * delta * discount_factor

    v = volatility * np.sqrt(maturity)
    d1 = (np.log(forward_rate / strike) + 0.5 * v**2) / v
    d2 = d1 - v

    price = (
        delta
        * discount_factor
        * (forward_rate * norm.cdf(d1) - strike * norm.cdf(d2))
    )
    return price


def price_cap(
    forward_rates,
    strike,
    maturities,
    volatilities,
    discount_factors,
    delta=0.25,
):
    """
    Price a cap as a sum of caplets.
    """
    cap_price = 0
    for i in range(len(forward_rates)):
        caplet = black_caplet_price(
            forward_rates[i],
            strike,
            maturities[i],
            volatilities[i],
            discount_factors[i],
            delta,
        )
        cap_price += caplet
    return cap_price


# Example: Price a 3-year cap with quarterly payments
# Assume flat forward curve at 5% and flat volatility at 20%
num_periods = 12  # 3 years, quarterly
delta = 0.25
forward_rates = np.ones(num_periods) * 0.05
volatilities = np.ones(num_periods) * 0.20
maturities = np.arange(1, num_periods + 1) * delta
discount_factors = np.exp(
    -0.05 * (maturities + delta)
)  # Approximate with flat 5% curve

strike = 0.05  # At-the-money cap
cap_price = price_cap(
    forward_rates, strike, maturities, volatilities, discount_factors, delta
)

# Price caps at different strikes for plotting
strikes = np.linspace(0.03, 0.07, 50)
cap_prices = [
    price_cap(
        forward_rates, K, maturities, volatilities, discount_factors, delta
    )
    for K in strikes
]
Out[8]:
Console
3-year ATM Cap Price: 0.013078
Cap price as percentage of notional: 1.3078%

The cap price of approximately 1.77% of notional represents the premium required to hedge against floating rates exceeding 5% over the next 3 years. This value reflects the sum of individual caplet prices, each weighted by the discount factor and the probability of the forward rate exceeding the strike. For an at-the-money cap where the strike equals the forward rate, roughly half of the value comes from intrinsic value considerations and half from time value.

Out[9]:
Visualization
Payoff diagram for a standard interest rate caplet with strike $K=5\%$. The non-linear payoff profile $\max(L-K, 0)$ demonstrates the option's protection against rising rates, acting effectively as an insurance policy for floating-rate borrowers.
Payoff diagram for a standard interest rate caplet with strike $K=5\%$. The non-linear payoff profile $\max(L-K, 0)$ demonstrates the option's protection against rising rates, acting effectively as an insurance policy for floating-rate borrowers.
Out[10]:
Visualization
Line plot showing cap price decreasing as strike rate increases.
Pricing profile of a 3-year cap across varying strike rates. The inverse relationship illustrates the premium reduction as the option moves out-of-the-money, with the curve's convexity highlighting the sensitivity of protection costs to the chosen strike level.

The plot illustrates the inverse relationship between cap prices and strike rates. As the strike increases, the cap becomes more out-of-the-money, reducing its value. A higher strike means the cap provides protection only against more extreme rate increases, which are less likely to occur. The curvature reflects the non-linear nature of option pricing with respect to the strike, with the steepest gradient occurring near the at-the-money rate where the option's moneyness is most sensitive to small changes in the strike.

LMM Dynamics Under the Terminal Measure

While Black's formula works beautifully for individual caplets, pricing more complex derivatives like swaptions requires simulating multiple forward rates simultaneously. The challenge is that each forward rate Li(t)L_i(t) is a martingale under a different measure QTi+1\mathbb{Q}^{T_{i+1}}. We cannot simply simulate each rate as a driftless process because they all need to evolve consistently under the same probability measure.

To simulate all rates consistently, we typically work under a single measure, commonly the terminal measure QTn\mathbb{Q}^{T_n} (using P(t,Tn)P(t, T_n) as numeraire). Under this measure, only Ln1(t)L_{n-1}(t) is a driftless martingale. Other forward rates acquire drift terms that arise from the change of measure between their natural measures and the terminal measure.

Under QTn\mathbb{Q}^{T_n}, the dynamics of Li(t)L_i(t) for i<n1i < n-1 become:

dLi(t)Li(t)=μi(t)dt+σi(t)dWiTn(t)\frac{dL_i(t)}{L_i(t)} = \mu_i(t) \, dt + \sigma_i(t) \, dW_i^{T_n}(t)

where:

  • Li(t)L_i(t): forward LIBOR rate
  • μi(t)\mu_i(t): drift term under the terminal measure
  • σi(t)\sigma_i(t): volatility of the forward rate
  • WiTn(t)W_i^{T_n}(t): Brownian motion under the terminal measure QTn\mathbb{Q}^{T_n}

The drift term is defined as:

μi(t)=j=i+1n1δLj(t)1+δLj(t)ρijσi(t)σj(t)\mu_i(t) = -\sum_{j=i+1}^{n-1} \frac{\delta L_j(t)}{1 + \delta L_j(t)} \rho_{ij} \sigma_i(t) \sigma_j(t)

where:

  • Li(t),Lj(t)L_i(t), L_j(t): forward LIBOR rates
  • δ\delta: accrual period
  • ρij\rho_{ij}: correlation between forward rates LiL_i and LjL_j
  • σi(t),σj(t)\sigma_i(t), \sigma_j(t): volatilities of the respective forward rates
  • μi(t)\mu_i(t): state-dependent drift ensuring no arbitrage under the terminal measure

This drift term ensures consistency when modeling all rates under the single terminal measure QTn\mathbb{Q}^{T_n}. To understand each component, consider the following interpretation. The fraction δLj(t)1+δLj(t)\frac{\delta L_j(t)}{1 + \delta L_j(t)} measures the sensitivity of the bond price ratio P(t,Tj)/P(t,Tj+1)P(t, T_j)/P(t, T_{j+1}) to the rate LjL_j. This is essentially the duration of a single-period bond with respect to its yield. The product ρijσi(t)σj(t)\rho_{ij} \sigma_i(t) \sigma_j(t) captures the covariance between the rate being modeled (LiL_i) and the rates acting as numeraires (LjL_j). The summation accumulates adjustments for all intermediate rates between the payment date and the terminal horizon.

This drift term arises from the change of measure from each forward rate's natural measure to the terminal measure. The negative sign indicates that rates with earlier payment dates tend to drift downward under the terminal measure, which compensates for the fact that they are martingales under different measures.

Correlation Structure

The correlation structure ρij\rho_{ij} between forward rates is crucial for pricing products that depend on multiple rates (like swaptions). A swap rate is essentially a weighted average of forward rates, so the volatility of the swap rate depends not just on the individual forward rate volatilities but also on how the forward rates co-move. Common parameterizations include:

Constant correlation:

ρij=ρfor all ij\rho_{ij} = \rho \quad \text{for all } i \neq j

where:

  • ρij\rho_{ij}: correlation between forward rates LiL_i and LjL_j
  • ρ\rho: constant correlation parameter

This simple specification assumes that all pairs of forward rates have the same correlation. While convenient for calibration, it lacks economic nuance because it treats the correlation between 1-year and 2-year rates the same as the correlation between 1-year and 10-year rates.

Exponential decay:

ρij=eβij\rho_{ij} = e^{-\beta |i - j|}

where:

  • β\beta: correlation decay parameter
  • ij|i - j|: distance between tenor indices

This captures the intuition that forward rates with similar maturities are more correlated than rates far apart. Economic factors that drive short-term rates (monetary policy, near-term economic outlook) differ from those driving long-term rates (inflation expectations, long-run growth potential). The exponential form provides a smooth transition from high correlation for adjacent rates to lower correlation for distant rates.

In[11]:
Code
def generate_correlation_matrix(n, model="exponential", params=None):
    """
    Generate correlation matrix for forward rates.

    Parameters:
    -----------
    n : int
        Number of forward rates
    model : str
        'constant' or 'exponential'
    params : dict
        Model parameters (rho for constant, beta for exponential)
    """
    if params is None:
        params = {"rho": 0.8, "beta": 0.1}

    corr = np.eye(n)
    for i in range(n):
        for j in range(n):
            if i != j:
                if model == "constant":
                    corr[i, j] = params["rho"]
                elif model == "exponential":
                    corr[i, j] = np.exp(-params["beta"] * abs(i - j))
    return corr


# Generate both types of correlation matrices
n_rates = 12
corr_constant = generate_correlation_matrix(n_rates, "constant", {"rho": 0.8})
corr_exponential = generate_correlation_matrix(
    n_rates, "exponential", {"beta": 0.2}
)
Out[12]:
Visualization
Heatmap showing uniform off-diagonal correlation values.
Correlation matrix for the Constant Correlation model ($\rho=0.8$). The uniform off-diagonal color indicates that all forward rate pairs are assumed to move together with the same degree of dependence, regardless of their maturity distance.
Heatmap showing correlation decreasing with distance from diagonal.
Correlation matrix for the Exponential Decay model ($\beta=0.2$). The gradient from the diagonal outward illustrates the realistic property that adjacent rates are highly correlated while rates with widely separated maturities evolve more independently.

Monte Carlo Simulation of the LMM

To price complex derivatives under the LMM, we simulate forward rate paths using the discretized dynamics under the terminal measure. The simulation requires careful handling of the state-dependent drift: at each time step, we must first evaluate the current drift based on the current forward rates before updating the rates for the next step. This creates a natural ordering where we update rates from longest maturity to shortest, since shorter rates depend on longer rates through the drift formula.

In[13]:
Code
def simulate_lmm(
    forward_rates_0,
    volatilities,
    correlation,
    delta,
    dt,
    num_steps,
    num_paths=10000,
):
    """
    Simulate forward rates under the LIBOR Market Model using terminal measure.

    Parameters:
    -----------
    forward_rates_0 : array
        Initial forward rates L_i(0)
    volatilities : array
        Volatilities sigma_i for each forward rate
    correlation : array
        Correlation matrix between forward rates
    delta : float
        Accrual period
    dt : float
        Time step for simulation
    num_steps : int
        Number of time steps
    num_paths : int
        Number of Monte Carlo paths

    Returns:
    --------
    forward_rates : array of shape (num_paths, num_steps+1, n)
        Simulated forward rate paths
    """
    n = len(forward_rates_0)
    forward_rates = np.zeros((num_paths, num_steps + 1, n))
    forward_rates[:, 0, :] = forward_rates_0

    # Cholesky decomposition for correlated Brownians
    chol = np.linalg.cholesky(correlation)

    for step in range(num_steps):
        t = step * dt

        # Generate correlated random numbers
        Z = np.random.randn(num_paths, n)
        dW = Z @ chol.T * np.sqrt(dt)

        for i in range(n):
            fixing_time = (i + 1) * delta  # Forward rate fixes at T_i

            if t < fixing_time:
                L_i = forward_rates[:, step, i]

                # Calculate drift under terminal measure
                drift = 0
                for j in range(i + 1, n):
                    if t < (j + 1) * delta:
                        L_j = forward_rates[:, step, j]
                        drift -= (
                            (delta * L_j / (1 + delta * L_j))
                            * correlation[i, j]
                            * volatilities[i]
                            * volatilities[j]
                        )

                # Lognormal dynamics
                dL = L_i * (drift * dt + volatilities[i] * dW[:, i])
                forward_rates[:, step + 1, i] = np.maximum(
                    L_i + dL, 1e-6
                )  # Keep positive
            else:
                forward_rates[:, step + 1, i] = forward_rates[
                    :, step, i
                ]  # Fixed

    return forward_rates


# Simulate LMM paths
np.random.seed(42)
n_rates = 12
forward_rates_0 = np.ones(n_rates) * 0.05  # 5% flat forward curve
volatilities = np.ones(n_rates) * 0.20  # 20% volatility
correlation = generate_correlation_matrix(
    n_rates, "exponential", {"beta": 0.15}
)
delta = 0.5  # Semi-annual

lmm_paths = simulate_lmm(
    forward_rates_0,
    volatilities,
    correlation,
    delta,
    dt=0.01,
    num_steps=200,  # 2 years
    num_paths=5000,
)

# Prepare data for plotting
rate_index = 2  # L_2 corresponds to 1.5-2.0 year forward (fixes at 1.5y)
time_grid = np.linspace(0, 2, 201)

# Get statistics for the selected rate
paths_for_rate = lmm_paths[:, :, rate_index]
percentile_5 = np.percentile(paths_for_rate, 5, axis=0)
percentile_50 = np.percentile(paths_for_rate, 50, axis=0)
percentile_95 = np.percentile(paths_for_rate, 95, axis=0)
Out[14]:
Visualization
Line plot showing LMM simulated forward rate distribution over time.
Simulated trajectory of the 1.5y-2.0y forward LIBOR rate under the LMM. The rate exhibits stochastic volatility until its fixing date (dotted vertical line), after which it becomes a known constant, illustrating the transition from random variable to realized cash flow parameter.

The simulation captures the stochastic evolution of forward rates, including their correlation structure. Notice how the rate fixes at its fixing time and remains constant thereafter. Before the fixing time, the rate fluctuates randomly around its initial value, with the distribution widening as time progresses. After fixing, the rate becomes a realized constant, which reflects the actual market mechanics where LIBOR rates are set at the beginning of each accrual period.

Out[15]:
Visualization
Snapshots of the forward curve evolution in the LMM. The colored lines show the median term structure at progressive time steps, demonstrating how the curve reshapes and shortens as near-term rates expire.
Snapshots of the forward curve evolution in the LMM. The colored lines show the median term structure at progressive time steps, demonstrating how the curve reshapes and shortens as near-term rates expire.

Key Parameters

The key parameters for the LIBOR Market Model are:

  • L_i(0): Initial forward LIBOR rates. Observable directly from the current yield curve, these provide the starting point for all simulations and ensure the model fits today's market prices exactly.
  • σ_i: Volatility of the i-th forward rate. Determines the width of the distribution for each rate and is typically calibrated to cap volatility data.
  • ρ_ij: Correlation between forward rates L_i and L_j. Crucial for pricing multi-rate products like swaptions, as it determines how the rates co-move and thus affects the volatility of rate combinations.
  • δ: Accrual period (tenor spacing). Typically 0.25 (3 months) or 0.5 (6 months), matching the conventions of the underlying interest rate contracts.
  • β: Correlation decay parameter. Controls how quickly correlation decreases as the distance between rate maturities increases, with higher values producing faster decay.

Swaption Pricing Under the LMM

A swaption is an option to enter into an interest rate swap at a future date. A payer swaption gives you the right to pay fixed and receive floating, while a receiver swaption gives the right to receive fixed and pay floating. Swaptions are among the most liquid interest rate derivatives and are crucial for calibrating the LMM. Their prices reflect market expectations about both the level and volatility of future interest rates, making them valuable sources of information for model calibration.

Swap Rate and Swaption Payoff

The forward swap rate Sα,β(t)S_{\alpha,\beta}(t) for a swap starting at TαT_\alpha and ending at TβT_\beta is the fixed rate that makes the swap have zero value at inception. It can be expressed as the ratio of two present values:

Sα,β(t)=P(t,Tα)P(t,Tβ)Aα,β(t)S_{\alpha,\beta}(t) = \frac{P(t, T_\alpha) - P(t, T_\beta)}{A_{\alpha,\beta}(t)}

where:

  • P(t,Tα)P(t, T_\alpha): price of bond maturing at swap start date
  • P(t,Tβ)P(t, T_\beta): price of bond maturing at swap end date
  • Aα,β(t)=i=αβ1δP(t,Ti+1)A_{\alpha,\beta}(t) = \sum_{i=\alpha}^{\beta-1} \delta P(t, T_{i+1}): present value of the fixed leg's annuity factor (PVBP)

The numerator P(t,Tα)P(t,Tβ)P(t, T_\alpha) - P(t, T_\beta) represents the present value of receiving one dollar at the swap start and paying one dollar at the swap end. This is exactly the floating leg's present value for a swap with a notional of one dollar. The denominator, the annuity factor, represents the present value of receiving one dollar at each payment date. The ratio therefore gives the fixed rate that equates the present values of the fixed and floating legs.

A payer swaption with strike KK has payoff at expiry TαT_\alpha:

Swaption Payoff=Aα,β(Tα)max(Sα,β(Tα)K,0)\text{Swaption Payoff} = A_{\alpha,\beta}(T_\alpha) \cdot \max(S_{\alpha,\beta}(T_\alpha) - K, 0)

where:

  • Aα,β(Tα)A_{\alpha,\beta}(T_\alpha): annuity factor at expiry
  • Sα,β(Tα)S_{\alpha,\beta}(T_\alpha): forward swap rate at expiry
  • KK: swaption strike rate

The payoff structure reflects that exercising a payer swaption is valuable when the market swap rate exceeds the strike: you can immediately enter a swap paying below-market rates. The multiplication by the annuity factor converts the rate difference into a present value, accounting for all future payment dates.

Swaption Approximation Formula

While the swap rate under LMM does not follow exactly lognormal dynamics (it's a function of multiple forward rates), a common approximation assumes lognormal swap rate dynamics. This approximation is remarkably accurate in practice because the swap rate, being a weighted average of forward rates, tends toward normality by the central limit theorem and the log of a sum is approximately the sum of logs when the rates are similar in magnitude. This leads to Black's formula for swaptions:

Swaption=Aα,β(0)[Sα,β(0)N(d1)KN(d2)]\text{Swaption} = A_{\alpha,\beta}(0) \cdot [S_{\alpha,\beta}(0) N(d_1) - K N(d_2)]

where:

  • Aα,β(0)A_{\alpha,\beta}(0): initial annuity factor
  • Sα,β(0)S_{\alpha,\beta}(0): initial forward swap rate
  • KK: swaption strike rate
  • d1=ln(Sα,β(0)/K)+12vS2vSd_1 = \frac{\ln(S_{\alpha,\beta}(0)/K) + \frac{1}{2}v_{S}^2}{v_S}
  • d2=d1vSd_2 = d_1 - v_S
  • vSv_S: total swaption volatility (σswapTα\sigma_{swap}\sqrt{T_\alpha})

As with the caplet formula, this expression decomposes the value into interpretable components:

  • Sα,β(0)N(d1)S_{\alpha,\beta}(0) N(d_1) is the expected value of the floating leg conditional on exercise
  • KN(d2)K N(d_2) is the expected value of the fixed leg conditional on exercise
  • The annuity factor Aα,β(0)A_{\alpha,\beta}(0) discounts the future swap cash flows to a present lump sum

Since the swap rate behaves approximately as a weighted average of the forward rates, its variance can be derived from the covariances of the underlying forward rates. This is a direct application of the formula for the variance of a linear combination of random variables. The total swaption volatility squared vS2v_S^2 is approximated by:

vS2i=αβ1j=αβ1wiwjρijσiσjTαv_S^2 \approx \sum_{i=\alpha}^{\beta-1} \sum_{j=\alpha}^{\beta-1} w_i w_j \rho_{ij} \sigma_i \sigma_j T_\alpha

where:

  • wi=Li(0)Sα,β(0)Sα,β(0)Li(0)w_i = \frac{L_i(0)}{S_{\alpha,\beta}(0)} \frac{\partial S_{\alpha,\beta}(0)}{\partial L_i(0)}: weights representing the elasticity of the swap rate with respect to each forward rate
  • ρij\rho_{ij}: correlation between forward rates
  • σi\sigma_i: volatility of forward rate LiL_i
  • TαT_\alpha: time to swaption expiry

The weights wiw_i measure how sensitive the swap rate is to each underlying forward rate. These weights sum to one (approximately) and tend to be larger for rates near the middle of the swap than for rates at the ends. The double summation captures both the variance contributions from each rate (when i=ji = j) and the covariance contributions from pairs of rates (when iji \neq j).

In[16]:
Code
def calculate_swap_rate(
    forward_rates, discount_factors, delta, start_idx, end_idx
):
    """
    Calculate forward swap rate from forward LIBOR rates.
    """
    # Annuity factor
    annuity = sum(
        delta * discount_factors[i + 1] for i in range(start_idx, end_idx)
    )

    # Swap rate = (P(0,T_alpha) - P(0,T_beta)) / Annuity
    # Using forward rates to get bond prices
    P_start = discount_factors[start_idx]
    P_end = discount_factors[end_idx]

    swap_rate = (P_start - P_end) / annuity
    return swap_rate, annuity


def black_swaption_price(
    swap_rate, strike, annuity, expiry, volatility, is_payer=True
):
    """
    Price a swaption using Black's formula.
    """
    if expiry <= 0:
        if is_payer:
            return annuity * max(swap_rate - strike, 0)
        else:
            return annuity * max(strike - swap_rate, 0)

    v = volatility * np.sqrt(expiry)
    d1 = (np.log(swap_rate / strike) + 0.5 * v**2) / v
    d2 = d1 - v

    if is_payer:
        price = annuity * (swap_rate * norm.cdf(d1) - strike * norm.cdf(d2))
    else:
        price = annuity * (strike * norm.cdf(-d2) - swap_rate * norm.cdf(-d1))

    return price


# Example: 1y into 5y payer swaption
# Build discount curve (flat 5%)
flat_rate = 0.05
delta = 0.5
maturities = np.arange(delta, 6.5, delta)  # Semi-annual out to 6 years
discount_factors = np.exp(-flat_rate * maturities)
discount_factors = np.insert(discount_factors, 0, 1.0)  # Add P(0,0) = 1

# Forward rates from discount factors
forward_rates = []
for i in range(len(maturities)):
    if i == 0:
        fwd = (1 / discount_factors[i + 1] - 1) / delta
    else:
        fwd = (discount_factors[i] / discount_factors[i + 1] - 1) / delta
    forward_rates.append(fwd)

# 1y into 5y swap (start at index 2 = 1 year, end at index 12 = 6 year)
start_idx = 2
end_idx = 12
swap_rate, annuity = calculate_swap_rate(
    forward_rates, discount_factors, delta, start_idx, end_idx
)
swaption_vol = 0.20
expiry = 1.0
strike = swap_rate  # ATM

swaption_price = black_swaption_price(
    swap_rate, strike, annuity, expiry, swaption_vol, is_payer=True
)
Out[17]:
Console
Forward Swap Rate (1y into 5y): 5.0630%
Annuity Factor: 4.155840
ATM Payer Swaption Price: 0.016760
Swaption Price (bps of notional): 167.60

The payer swaption price of approximately 1726 basis points represents the upfront premium required to purchase the right to pay 5% fixed on the swap. This value is derived from the volatility of the swap rate and the time to expiry. For context, this means that on a notional of \$100 million, the swaption would cost approximately \$1.726 million. The relatively high price reflects both the long tenor of the underlying swap and the substantial uncertainty about interest rates over the one-year option period.

Out[18]:
Visualization
Price surface for payer swaptions (1y into 5y) relative to strike and expiry. The surface demonstrates how option premiums increase with time to expiry (Theta) and moneyness (Delta), peaking for long-dated ITM options.
Price surface for payer swaptions (1y into 5y) relative to strike and expiry. The surface demonstrates how option premiums increase with time to expiry (Theta) and moneyness (Delta), peaking for long-dated ITM options.

Monte Carlo Swaption Pricing

For more accurate pricing that captures the correlation structure, we can use Monte Carlo simulation under the LMM. This approach simulates the joint evolution of all forward rates, calculates the swap rate at expiry from the simulated rates, and averages the discounted payoffs across all paths. The Monte Carlo method naturally handles the complex dependence of the swap rate on multiple forward rates without requiring the approximations used in the analytical formula.

In[19]:
Code
def price_swaption_mc(
    lmm_paths,
    fixing_step,
    start_idx,
    end_idx,
    strike,
    delta,
    flat_rate,
    dt=0.01,
):
    """
    Price a payer swaption using Monte Carlo simulation from LMM paths.
    """
    num_paths = lmm_paths.shape[0]

    # Get forward rates at fixing time
    forward_rates_at_fixing = lmm_paths[:, fixing_step, :]

    # Calculate discount factors at fixing time for each path
    payoffs = []

    for path in range(num_paths):
        fwd_rates = forward_rates_at_fixing[path, :]

        # Build discount factors from forward rates
        df = [1.0]
        for i in range(len(fwd_rates)):
            df.append(df[-1] / (1 + delta * fwd_rates[i]))
        df = np.array(df)

        # Calculate swap rate and annuity
        # Note: df array indices: 0->start, 1->after L0, 2->after L1...
        # We must normalize by df[start_idx] to get value at swap start
        if start_idx < len(df) and end_idx < len(df):
            # Numeraire at swap start
            numeraire = df[start_idx]

            # Annuity normalized to swap start time
            # Sum of P(t, T_payment) = df[i+1] / df[start_idx]
            annuity_val = sum(
                delta * df[i + 1]
                for i in range(start_idx, min(end_idx, len(df) - 1))
            )
            annuity = annuity_val / numeraire

            # Swap rate = (P_start - P_end) / Annuity
            # (df[start_idx]/numeraire - df[end_idx]/numeraire) / (annuity_val/numeraire)
            # = (df[start_idx] - df[end_idx]) / annuity_val
            numerator = df[start_idx] - df[min(end_idx, len(df) - 1)]
            swap_rate_path = numerator / annuity_val if annuity_val > 0 else 0

            payoff = max(swap_rate_path - strike, 0) * annuity
        else:
            payoff = 0

        payoffs.append(payoff)

    payoffs = np.array(payoffs)

    # Discount back to today
    discount_to_today = np.exp(-flat_rate * fixing_step * dt)

    price = np.mean(payoffs) * discount_to_today
    std_error = np.std(payoffs) * discount_to_today / np.sqrt(num_paths)

    return price, std_error


# Price swaption using MC
fixing_step = 100  # 1 year
dt = 0.01
mc_price, mc_error = price_swaption_mc(
    lmm_paths,
    fixing_step,
    start_idx=1,
    end_idx=11,
    strike=0.05,
    delta=0.5,
    flat_rate=0.05,
    dt=dt,
)
Out[20]:
Console
Monte Carlo Swaption Price: 0.012394 (+/- 0.000287)
Black's Formula Price: 0.016760
Difference: -43.66 bps

The Monte Carlo price should be close to Black's formula for ATM options. Differences arise from the approximation in Black's formula and simulation noise. The standard error provides a measure of the Monte Carlo estimation uncertainty: with 5000 paths, we achieve reasonable precision, but production implementations often use many more paths combined with variance reduction techniques to achieve tighter confidence intervals.

Key Parameters

The key parameters for swaption pricing are:

  • S: Forward swap rate. The par rate that sets the swap's initial value to zero, observable from the current yield curve.
  • K: Strike rate. The fixed rate specified in the swaption contract, chosen at inception and remaining fixed throughout the option's life.
  • A: Annuity factor. The present value of the fixed leg's basis point payments, which converts rate differences into dollar values.
  • T: Time to expiry. The time until the option is exercised, during which the swap rate may move in favor of or against the option holder.
  • σ: Swaption volatility. The volatility of the forward swap rate used in Black's formula, typically implied from market prices.

Calibration to Market Data

Calibration is the process of choosing model parameters (volatilities, correlations) so that the model reproduces observed market prices. For interest rate models, calibration typically targets cap/floor volatilities and swaption volatilities. The goal is to find parameters that allow the model to price liquid instruments consistently with the market while maintaining reasonable behavior for illiquid or exotic instruments. This balancing act is central to practical derivatives pricing.

Cap Volatility Calibration

Market caps are quoted in terms of implied volatility. Given a quoted cap volatility, we can back out the price using Black's formula, then calibrate the LMM volatilities to match these prices. The calibration process inverts the pricing formula: rather than using volatility to find price, we use price to find volatility.

The caplet volatility structure in LMM has several common parameterizations. A popular choice is the piecewise-constant volatility, where each forward rate has a constant volatility that can differ across maturities. More sophisticated approaches use time-varying or maturity-dependent volatility functions that can capture features like the volatility "hump" often observed at intermediate maturities.

In[21]:
Code
import numpy as np
from scipy.optimize import minimize
from scipy.stats import norm


def black_caplet_price(
    forward_rate, strike, maturity, volatility, discount_factor, delta=0.25
):
    """
    Price a caplet using Black's formula.

    Parameters:
    -----------
    forward_rate : float
        Current forward LIBOR rate L_i(0)
    strike : float
        Cap strike rate K
    maturity : float
        Time to caplet fixing date T_i
    volatility : float
        Forward rate volatility sigma_i
    discount_factor : float
        P(0, T_{i+1}) discount factor to payment date
    delta : float
        Accrual period (default 0.25 for quarterly)

    Returns:
    --------
    price : float
        Caplet price
    """
    if maturity <= 0:
        return max(forward_rate - strike, 0) * delta * discount_factor

    v = volatility * np.sqrt(maturity)
    d1 = (np.log(forward_rate / strike) + 0.5 * v**2) / v
    d2 = d1 - v

    price = (
        delta
        * discount_factor
        * (forward_rate * norm.cdf(d1) - strike * norm.cdf(d2))
    )
    return price


def calibrate_cap_volatilities(
    market_cap_vols, forward_rates, maturities, discount_factors, delta
):
    """
    Calibrate LMM volatilities to match market cap implied volatilities.

    Parameters:
    -----------
    market_cap_vols : array
        Market-quoted cap volatilities for each maturity
    forward_rates : array
        Forward LIBOR rates
    maturities : array
        Cap maturities
    discount_factors : array
        Discount factors for payment dates
    delta : float
        Accrual period

    Returns:
    --------
    calibrated_vols : array
        Calibrated forward rate volatilities
    """
    n = len(forward_rates)

    def objective(vols):
        """Sum of squared errors between model and market cap prices."""
        total_error = 0
        for i in range(n):
            model_price = black_caplet_price(
                forward_rates[i],
                forward_rates[i],  # ATM strike
                maturities[i],
                vols[i],
                discount_factors[i],
                delta,
            )
            market_price = black_caplet_price(
                forward_rates[i],
                forward_rates[i],
                maturities[i],
                market_cap_vols[i],
                discount_factors[i],
                delta,
            )
            total_error += (model_price - market_price) ** 2
        return total_error

    # Initial guess: market vols
    x0 = market_cap_vols.copy()

    # Bounds: positive volatilities
    bounds = [(0.01, 1.0)] * n

    result = minimize(objective, x0, method="L-BFGS-B", bounds=bounds)

    return result.x


# Example: Calibrate to a typical cap volatility term structure
# Market often shows a "hump" in cap volatilities
market_maturities = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0])
market_cap_vols = np.array(
    [0.18, 0.22, 0.24, 0.25, 0.24, 0.23, 0.22, 0.21]
)  # Typical hump shape

forward_rates_calib = np.ones(len(market_maturities)) * 0.05
discount_factors_calib = np.exp(-0.05 * (market_maturities + 0.25))

calibrated_vols = calibrate_cap_volatilities(
    market_cap_vols,
    forward_rates_calib,
    market_maturities,
    discount_factors_calib,
    delta=0.25,
)
Out[22]:
Visualization
Line plot comparing market cap volatilities with calibrated model volatilities.
Calibration fit of LMM volatilities to market cap data. The model parameters (red squares) are optimized to reproduce the market's implied volatility hump (blue circles), ensuring consistent pricing for the primary calibration instruments.
Calibration Results:
  0.5y: Market = 18.0%, Model = 18.0%
  1.0y: Market = 22.0%, Model = 22.0%
  1.5y: Market = 24.0%, Model = 24.0%
  2.0y: Market = 25.0%, Model = 25.0%
  2.5y: Market = 24.0%, Model = 24.0%
  3.0y: Market = 23.0%, Model = 23.0%
  3.5y: Market = 22.0%, Model = 22.0%
  4.0y: Market = 21.0%, Model = 21.0%

The calibration results demonstrate the model's ability to fit the term structure of volatility. The calibrated forward rate volatilities track the market cap volatilities, capturing the hump shape around the 2-year mark that is characteristic of interest rate markets. This hump reflects the market's view that rates are most uncertain at intermediate horizons: near-term rates are anchored by current monetary policy, while very long-term rates are stabilized by mean reversion.

Swaption Volatility Surface Calibration

The swaption market provides a rich volatility surface across different expiries and swap tenors. Calibrating the LMM to this surface is more challenging because swaption volatility depends on both individual forward rate volatilities and their correlations. Unlike cap calibration, where each caplet can be calibrated independently, swaption calibration requires simultaneously fitting multiple interdependent parameters.

A typical swaption volatility surface (often called a "vol cube" when strikes are included) shows variation across:

  • Expiry: Time until the option expires
  • Tenor: Length of the underlying swap
  • Strike: Often expressed as offset from ATM
In[23]:
Code
# Create a synthetic swaption volatility surface
expiries = np.array([0.5, 1, 2, 3, 5])  # Option expiries
tenors = np.array([1, 2, 3, 5, 7, 10])  # Swap tenors

# Typical swaption vol surface (ATM)
swaption_vol_surface = np.array(
    [
        [0.25, 0.24, 0.23, 0.21, 0.19, 0.17],  # 6m expiry
        [0.24, 0.23, 0.22, 0.20, 0.18, 0.16],  # 1y expiry
        [0.22, 0.21, 0.20, 0.18, 0.17, 0.15],  # 2y expiry
        [0.20, 0.19, 0.18, 0.17, 0.16, 0.14],  # 3y expiry
        [0.18, 0.17, 0.16, 0.15, 0.14, 0.13],  # 5y expiry
    ]
)
Out[24]:
Visualization
3D surface plot of swaption volatility across expiry and tenor dimensions.
Market implied volatility surface for ATM swaptions. The 3D topology shows declining volatility across both swap tenor and option expiry, a stylized fact of interest rate markets driven by mean reversion and long-term stability expectations.
Out[25]:
Visualization
Heatmap visualization of the ATM swaption volatility surface. The color intensity confirms that volatility is highest for short-dated options on short-tenor swaps (bottom-left), fading significantly for longer-dated and longer-tenor instruments.
Heatmap visualization of the ATM swaption volatility surface. The color intensity confirms that volatility is highest for short-dated options on short-tenor swaps (bottom-left), fading significantly for longer-dated and longer-tenor instruments.

Joint Calibration Challenge

Calibrating the LMM to both caps and swaptions simultaneously is challenging because caps constrain individual forward rate volatilities while swaptions constrain both volatilities and correlations. A model that fits cap volatilities perfectly may still misprice swaptions if the correlation structure is wrong. Conversely, adjusting correlations to fit swaptions may introduce inconsistencies with cap prices. A common approach involves:

  1. Stage 1: Calibrate forward rate volatilities to the cap volatility curve
  2. Stage 2: Calibrate correlations to match swaption volatilities, given the volatilities from Stage 1

More sophisticated approaches use global optimization to calibrate volatilities and correlations jointly, treating the entire parameter set as a single optimization problem. This avoids the suboptimality that can arise from sequential calibration but requires more computational resources and careful attention to the objective function weighting.

In[26]:
Code
def approximate_swaption_vol(
    forward_vols, correlation, delta, start_idx, end_idx, expiry
):
    """
    Approximate swaption volatility from forward rate volatilities and correlations.
    Uses the "frozen" approximation where weights are computed at time 0.
    """
    n_rates = end_idx - start_idx

    # Equal weights approximation (simplified)
    weights = np.ones(n_rates) / n_rates

    # Calculate approximate swaption variance
    variance = 0
    for i in range(n_rates):
        for j in range(n_rates):
            idx_i = start_idx + i
            idx_j = start_idx + j
            if idx_i < len(forward_vols) and idx_j < len(forward_vols):
                variance += (
                    weights[i]
                    * weights[j]
                    * correlation[idx_i, idx_j]
                    * forward_vols[idx_i]
                    * forward_vols[idx_j]
                    * expiry
                )

    return np.sqrt(variance / expiry) if expiry > 0 else 0


def calibrate_correlation(
    forward_vols, target_swaption_vols, expiries, tenors, delta
):
    """
    Calibrate correlation parameter to match swaption volatilities.
    Uses exponential correlation structure.
    """
    n = len(forward_vols)

    def objective(beta):
        """Sum of squared errors for swaption vols."""
        beta = beta[0]
        if beta <= 0:
            return 1e10

        corr = generate_correlation_matrix(n, "exponential", {"beta": beta})

        total_error = 0
        for i, expiry in enumerate(expiries[:3]):  # Use first 3 expiries
            for j, tenor in enumerate(tenors[:3]):  # Use first 3 tenors
                start_idx = int(expiry / delta)
                end_idx = int((expiry + tenor) / delta)

                if end_idx <= n:
                    model_vol = approximate_swaption_vol(
                        forward_vols, corr, delta, start_idx, end_idx, expiry
                    )
                    target_vol = target_swaption_vols[i, j]
                    total_error += (model_vol - target_vol) ** 2

        return total_error

    result = minimize(
        objective, [0.2], method="Nelder-Mead", bounds=[(0.01, 1.0)]
    )
    return result.x[0]


# Calibrate correlation to swaption surface
# Extend forward vols to cover swaption maturities
extended_forward_vols = np.concatenate(
    [calibrated_vols, np.ones(12) * calibrated_vols[-1]]
)

calibrated_beta = calibrate_correlation(
    extended_forward_vols, swaption_vol_surface, expiries, tenors, delta=0.25
)

# Generate calibrated correlation matrix for comparison
n = len(extended_forward_vols)
calibrated_corr = generate_correlation_matrix(
    n, "exponential", {"beta": calibrated_beta}
)

# Calculate comparison values
comparison_data = []
delta_val = 0.25
for i, expiry in enumerate(expiries[:3]):
    for j, tenor in enumerate(tenors[:3]):
        start_idx = int(expiry / delta_val)
        end_idx = int((expiry + tenor) / delta_val)
        if end_idx <= n:
            model_vol = approximate_swaption_vol(
                extended_forward_vols,
                calibrated_corr,
                delta_val,
                start_idx,
                end_idx,
                expiry,
            )
            market_vol = swaption_vol_surface[i, j]
            comparison_data.append((expiry, tenor, market_vol, model_vol))
Out[27]:
Console
Calibrated correlation decay parameter (beta): 0.0100

Swaption Vol Comparison (Expiry x Tenor):
Market vs Model:
  0.5y x 1y: Market = 25.0%, Model = 23.9%
  0.5y x 2y: Market = 24.0%, Model = 22.3%
  0.5y x 3y: Market = 23.0%, Model = 21.6%
  1.0y x 1y: Market = 24.0%, Model = 22.4%
  1.0y x 2y: Market = 23.0%, Model = 21.5%
  1.0y x 3y: Market = 22.0%, Model = 21.1%
  2.0y x 1y: Market = 22.0%, Model = 20.9%
  2.0y x 2y: Market = 21.0%, Model = 20.7%
  2.0y x 3y: Market = 20.0%, Model = 20.6%

The calibration shows that while we can match some swaptions well, perfect fit across the entire surface requires more sophisticated volatility and correlation parameterizations. This is an active area of research and practical development. In production systems, you often use more flexible correlation structures (such as angle-based parameterizations) and volatility functions that depend on both time and maturity to achieve better fits.

Out[28]:
Visualization
Sensitivity of swaption volatility to the correlation decay parameter $\beta$. The downward slope indicates that assuming faster correlation decay (higher $\beta$) reduces the implied volatility of the swap rate, as the component forward rates offset each other's movements more frequently.
Sensitivity of swaption volatility to the correlation decay parameter $\beta$. The downward slope indicates that assuming faster correlation decay (higher $\beta$) reduces the implied volatility of the swap rate, as the component forward rates offset each other's movements more frequently.

Practical Implementation Considerations

Implementing HJM and LMM in production systems involves several practical challenges that go beyond the theoretical framework. The gap between textbook models and real trading systems requires careful attention to numerical issues, computational efficiency, and evolving market conventions.

Numerical Stability

The lognormal dynamics in LMM can occasionally produce negative forward rates in extreme scenarios due to discretization errors. When simulating over long horizons or with large time steps, the Euler discretization may produce values that violate the theoretical positivity constraint. Common remedies include:

  • Using shifted lognormal dynamics d(Li(t)+s)=(Li(t)+s)σi(t)dWi(t)d(L_i(t) + s) = (L_i(t) + s) \sigma_i(t) \, dW_i(t), where ss is a shift parameter. This approach handles negative rates gracefully and has become standard practice in the post-2008 low-rate environment.
  • Employing predictor-corrector schemes for more accurate discretization. These methods use an initial estimate (predictor) and refine it (corrector) to achieve better accuracy than simple Euler steps.
  • Implementing floor constraints to prevent negative rates. While computationally simple, this approach can introduce bias in the simulation.

Computational Efficiency

Monte Carlo simulation of the LMM is computationally intensive, especially for large books of derivatives. Each derivative may require thousands of paths, and a trading desk may need to price thousands of derivatives daily for risk management. You employ various techniques to accelerate calculations:

  • Variance reduction: Antithetic variates, control variates, and importance sampling (as covered in our chapter on Monte Carlo methods) can dramatically reduce the number of paths needed for a given accuracy level.
  • Parallel computing: Simulating paths independently on multiple processors. Modern implementations leverage GPUs, which can simulate thousands of paths simultaneously.
  • Low-discrepancy sequences: Quasi-Monte Carlo methods for faster convergence. These deterministic sequences fill the sample space more uniformly than random sampling, often achieving convergence rates better than the O(1/N)O(1/\sqrt{N}) rate of standard Monte Carlo.

Post-LIBOR Considerations

Following the LIBOR transition, the market has largely shifted to risk-free rates (RFR) such as SOFR, SONIA, and €STR. The LMM framework has been adapted to model these rates, though the mechanics differ somewhat because RFRs are typically compounded in arrears rather than set in advance. Unlike LIBOR, which was a forward-looking term rate fixed at the beginning of the accrual period, RFR-based rates compound daily observations over the accrual period. This mechanical difference affects hedging strategies and requires modifications to the simulation framework. The fundamental mathematical structure of forward rate modeling remains applicable, but calibration targets and market conventions have evolved.

Comparison: Short-Rate Models vs. HJM vs. LMM

Understanding when to use each modeling approach is crucial for you. This table summarizes the key trade-offs:

Comparison of interest rate modeling approaches.
AspectShort-Rate ModelsHJM FrameworkLIBOR Market Model
Primary variableInstantaneous short rate r(t)r(t)Instantaneous forward curve f(t,T)f(t,T)Discrete forward rates Li(t)L_i(t)
Yield curve fitRequires affine term structure or calibrationBuilt-in by constructionBuilt-in by construction
Cap/Floor pricingApproximations neededPossible but complexBlack's formula exact
Swaption pricingClosed-form for some modelsComplexApproximation or MC
Calibration difficultyModerateHighModerate to High
Use casesBond pricing, simple derivativesTheoretical, exotic derivativesCaps, floors, swaptions

Short-rate models remain valuable for their analytical tractability and are often used for ALM (Asset-Liability Management) and bond portfolio management. Their simplicity makes them suitable for applications where detailed calibration to derivative prices is less important than capturing the general dynamics of interest rate movements. The HJM framework provides deep theoretical insights and is the foundation for more complex models. Its generality makes it the natural starting point for understanding arbitrage-free term structure evolution. The LMM has become the industry standard for pricing and hedging interest rate derivatives because it directly models the rates used in these products, providing a natural connection between model variables and market observables.

Limitations and Impact

The HJM and LMM frameworks transformed interest rate derivatives pricing by providing a consistent framework for modeling forward rates that automatically fits the current yield curve. Before these models, we struggled to reconcile model-implied rates with market observations, leading to pricing inconsistencies and hedging errors. HJM established that in an arbitrage-free economy, the drift of forward rates is completely determined by their volatility structure, unifying different modeling approaches.

However, these frameworks come with significant limitations. The HJM framework, while theoretically elegant, can lead to non-Markovian dynamics that are difficult to implement efficiently. The general HJM model may require tracking the entire history of rate evolution, making computational methods memory-intensive. The LMM, designed to address this by working with observable rates, introduces its own challenges. The drift terms under a common measure involve all forward rates, creating path-dependent dynamics that require Monte Carlo simulation for most products beyond vanilla caps.

Both frameworks face challenges in capturing real market behavior. The assumption of lognormal forward rate dynamics produces constant volatility for caplets, yet markets display pronounced volatility smiles and skews. Extensions like stochastic volatility and local volatility modifications address this but add significant model complexity. Calibration to the full swaption volatility surface often reveals tension between fitting individual instruments and maintaining sensible model behavior. A model perfectly calibrated to today's prices may produce unstable hedges or unrealistic forward dynamics.

The correlation structure in LMM deserves particular attention. While we can parameterize correlations to fit observed swaption prices, the resulting correlation matrices may not reflect actual rate co-movements, particularly during market stress. The 2008 financial crisis and subsequent market dislocations revealed that correlation structures estimated from normal markets can break down precisely when they matter most for risk management.

Looking ahead, the next chapter on valuation of interest rate derivatives will apply these frameworks to price specific products including Bermudan swaptions, callable bonds, and structured notes. The calibration techniques introduced here will be developed more formally in our later chapter on calibration and parameter estimation, where we'll explore global optimization methods and assess model risk.

Summary

This chapter developed two foundational frameworks for advanced interest rate modeling. The Heath-Jarrow-Morton approach models the evolution of the entire instantaneous forward rate curve, with the crucial insight that absence of arbitrage completely determines the forward rate drift in terms of its volatility structure. This drift condition, α(t,T)=σ(t,T)tTσ(t,s)ds\alpha(t,T) = \sigma(t,T) \int_t^T \sigma(t,s) \, ds, ensures that discounted bond prices are martingales under the risk-neutral measure.

The LIBOR Market Model takes a more practical approach by directly modeling discrete forward rates observable in the market. Under each forward rate's natural measure, the rate follows driftless lognormal dynamics, leading directly to Black's formula for caplet pricing. Simulating multiple rates requires working under a common measure, introducing drift terms that depend on the correlation structure.

Key implementation concepts include:

  • Forward measure numeraire: The bond P(t,T)P(t,T) serves as numeraire under the TT-forward measure, making the corresponding forward rate a martingale
  • Black's formula: Provides closed-form caplet and floorlet prices under lognormal forward rate assumptions
  • Correlation parameterization: Exponential decay structures capture the intuition that adjacent forward rates are more correlated than distant ones
  • Two-stage calibration: First fit cap volatilities, then calibrate correlations to swaption prices

The frameworks balance flexibility with tractability. While they can match observed market prices more accurately than short-rate models, they require careful attention to numerical implementation, particularly for Monte Carlo simulation. Modern implementations often extend these basic frameworks with stochastic volatility or local volatility modifications to capture smile dynamics observed in the market.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about the Heath-Jarrow-Morton framework and LIBOR Market Model.

Loading component...

Reference

BIBTEXAcademic
@misc{advancedinterestratemodelshjmframeworklmmguide, author = {Michael Brenndoerfer}, title = {Advanced Interest Rate Models: HJM Framework & LMM Guide}, year = {2025}, url = {https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-01-01} }
APAAcademic
Michael Brenndoerfer (2025). Advanced Interest Rate Models: HJM Framework & LMM Guide. Retrieved from https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework
MLAAcademic
Michael Brenndoerfer. "Advanced Interest Rate Models: HJM Framework & LMM Guide." 2026. Web. today. <https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework>.
CHICAGOAcademic
Michael Brenndoerfer. "Advanced Interest Rate Models: HJM Framework & LMM Guide." Accessed today. https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Advanced Interest Rate Models: HJM Framework & LMM Guide'. Available at: https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework (Accessed: today).
SimpleBasic
Michael Brenndoerfer (2025). Advanced Interest Rate Models: HJM Framework & LMM Guide. https://mbrenndoerfer.com/writing/advanced-interest-rate-models-hjm-lmm-framework