TL;DR – Interest rate models are essential for pricing bonds, swaps, and swaptions. This guide covers short-rate models (Vasicek, CIR, Hull-White), the HJM framework, and LIBOR market models with calibration to market data and production-ready implementations.
Interest rate models are fundamental for:
Challenge: Unlike stocks, interest rates are not directly observable. We observe bond prices and must infer the entire yield curve.
A zero-coupon bond pays at maturity . Its price at time is:
where is the instantaneous forward rate at time for maturity .
The spot rate (yield to maturity) satisfies:
Relationship to forward rates:
1import numpy as np
2from scipy.optimize import minimize_scalar
3from scipy.interpolate import CubicSpline
4
5class YieldCurve:
6 """Bootstrap and interpolate yield curves from bond prices."""
7
8 def __init__(self, maturities: np.ndarray, prices: np.ndarray):
9 """
10 Args:
11 maturities: Time to maturity in years
12 prices: Zero-coupon bond prices P(0, T)
13 """
14 self.maturities = maturities
15 self.prices = prices
16
17 # Compute spot rates
18 self.spot_rates = -np.log(prices) / maturities
19
20 # Cubic spline interpolation
21 self.spline = CubicSpline(maturities, self.spot_rates)
22
23 def zero_rate(self, T: float) -> float:
24 """Get zero rate for maturity T."""
25 return float(self.spline(T))
26
27 def discount_factor(self, T: float) -> float:
28 """Get discount factor P(0, T)."""
29 return np.exp(-self.zero_rate(T) * T)
30
31 def forward_rate(self, T1: float, T2: float) -> float:
32 """
33 Compute forward rate f(T1, T2).
34
35 f(T1, T2) = [R(T2)*T2 - R(T1)*T1] / (T2 - T1)
36 """
37 R1, R2 = self.zero_rate(T1), self.zero_rate(T2)
38 return (R2 * T2 - R1 * T1) / (T2 - T1)
39
40 def instantaneous_forward(self, T: float) -> float:
41 """
42 Compute instantaneous forward rate f(0, T).
43
44 f(0, T) = R(T) + T * dR/dT
45 """
46 R = self.zero_rate(T)
47 dR_dT = self.spline.derivative()(T)
48 return R + T * dR_dT
49
50# Example: Bootstrap from market data
51maturities = np.array([0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30])
52# Hypothetical zero-coupon bond prices
53prices = np.array([0.9975, 0.9945, 0.9880, 0.9720, 0.9540,
54 0.9150, 0.8750, 0.8200, 0.6800, 0.5500])
55
56curve = YieldCurve(maturities, prices)
57
58print(f"5Y zero rate: {curve.zero_rate(5.0):.4%}")
59print(f"10Y discount factor: {curve.discount_factor(10.0):.4f}")
60print(f"2Y-5Y forward rate: {curve.forward_rate(2.0, 5.0):.4%}")
61Short-rate models specify the dynamics of the instantaneous short rate .
SDE:
Where:
Properties:
Zero-Coupon Bond Price:
where:
1class VasicekModel:
2 """Vasicek short-rate model."""
3
4 def __init__(self, kappa: float, theta: float, sigma: float, r0: float):
5 self.kappa = kappa
6 self.theta = theta
7 self.sigma = sigma
8 self.r0 = r0
9
10 def B(self, t: float, T: float) -> float:
11 """B(t, T) function."""
12 tau = T - t
13 if self.kappa == 0:
14 return tau
15 return (1 - np.exp(-self.kappa * tau)) / self.kappa
16
17 def A(self, t: float, T: float) -> float:
18 """A(t, T) function."""
19 tau = T - t
20 B_val = self.B(t, T)
21
22 term1 = (self.theta - self.sigma**2 / (2 * self.kappa**2)) * (B_val - tau)
23 term2 = self.sigma**2 * B_val**2 / (4 * self.kappa)
24
25 return np.exp(term1 - term2)
26
27 def bond_price(self, t: float, T: float, r_t: float) -> float:
28 """Zero-coupon bond price P(t, T)."""
29 return self.A(t, T) * np.exp(-self.B(t, T) * r_t)
30
31 def simulate(self, T: float, n_steps: int, n_paths: int = 1) -> np.ndarray:
32 """Simulate short rate paths using Euler-Maruyama."""
33 dt = T / n_steps
34 sqrt_dt = np.sqrt(dt)
35
36 r = np.zeros((n_paths, n_steps + 1))
37 r[:, 0] = self.r0
38
39 for i in range(n_steps):
40 dW = np.random.normal(0, sqrt_dt, n_paths)
41 r[:, i+1] = r[:, i] + self.kappa * (self.theta - r[:, i]) * dt + self.sigma * dW
42
43 return r
44
45 def calibrate(self, maturities: np.ndarray, market_prices: np.ndarray) -> dict:
46 """
47 Calibrate model to market bond prices.
48
49 Minimize sum of squared pricing errors.
50 """
51 def objective(params):
52 kappa, theta, sigma = params
53 model = VasicekModel(kappa, theta, sigma, self.r0)
54
55 model_prices = np.array([
56 model.bond_price(0, T, self.r0) for T in maturities
57 ])
58
59 return np.sum((model_prices - market_prices)**2)
60
61 from scipy.optimize import minimize
62
63 # Initial guess
64 x0 = [0.1, 0.05, 0.01]
65 bounds = [(0.01, 1.0), (0.0, 0.2), (0.001, 0.1)]
66
67 result = minimize(objective, x0, bounds=bounds, method='L-BFGS-B')
68
69 return {
70 'kappa': result.x[0],
71 'theta': result.x[1],
72 'sigma': result.x[2],
73 'error': result.fun
74 }
75
76# Example usage
77model = VasicekModel(kappa=0.15, theta=0.05, sigma=0.01, r0=0.03)
78
79# Price a 5-year zero-coupon bond
80price_5y = model.bond_price(0, 5, 0.03)
81print(f"5Y bond price: {price_5y:.4f}")
82
83# Simulate rate paths
84paths = model.simulate(T=10, n_steps=1000, n_paths=100)
85
86import matplotlib.pyplot as plt
87t = np.linspace(0, 10, 1001)
88plt.figure(figsize=(12, 6))
89for i in range(10):
90 plt.plot(t, paths[i], alpha=0.5)
91plt.axhline(y=model.theta, color='r', linestyle='--', label=f'Long-term mean = {model.theta}')
92plt.xlabel('Time (years)')
93plt.ylabel('Short Rate')
94plt.title('Vasicek Model: Simulated Rate Paths')
95plt.legend()
96plt.grid(True, alpha=0.3)
97plt.show()
98SDE:
Key difference: Volatility is (state-dependent), ensuring if (Feller condition).
Bond Price:
where (more complex than Vasicek):
1class CIRModel:
2 """Cox-Ingersoll-Ross model."""
3
4 def __init__(self, kappa: float, theta: float, sigma: float, r0: float):
5 self.kappa = kappa
6 self.theta = theta
7 self.sigma = sigma
8 self.r0 = r0
9
10 # Check Feller condition
11 if 2 * kappa * theta < sigma**2:
12 print("Warning: Feller condition not satisfied. Rates may hit zero.")
13
14 def B(self, t: float, T: float) -> float:
15 """B(t, T) function."""
16 tau = T - t
17 gamma = np.sqrt(self.kappa**2 + 2 * self.sigma**2)
18
19 exp_gamma_tau = np.exp(gamma * tau)
20 numerator = 2 * (exp_gamma_tau - 1)
21 denominator = (gamma + self.kappa) * (exp_gamma_tau - 1) + 2 * gamma
22
23 return numerator / denominator
24
25 def A(self, t: float, T: float) -> float:
26 """A(t, T) function."""
27 tau = T - t
28 gamma = np.sqrt(self.kappa**2 + 2 * self.sigma**2)
29
30 exp_gamma_tau = np.exp(gamma * tau)
31 numerator = 2 * gamma * np.exp((self.kappa + gamma) * tau / 2)
32 denominator = (gamma + self.kappa) * (exp_gamma_tau - 1) + 2 * gamma
33
34 exponent = 2 * self.kappa * self.theta / (self.sigma**2)
35
36 return (numerator / denominator) ** exponent
37
38 def bond_price(self, t: float, T: float, r_t: float) -> float:
39 """Zero-coupon bond price."""
40 return self.A(t, T) * np.exp(-self.B(t, T) * r_t)
41
42 def simulate(self, T: float, n_steps: int, n_paths: int = 1) -> np.ndarray:
43 """
44 Simulate using full truncation scheme (Andersen, 2008).
45
46 Ensures non-negativity while maintaining accuracy.
47 """
48 dt = T / n_steps
49 sqrt_dt = np.sqrt(dt)
50
51 r = np.zeros((n_paths, n_steps + 1))
52 r[:, 0] = self.r0
53
54 for i in range(n_steps):
55 dW = np.random.normal(0, sqrt_dt, n_paths)
56
57 # Full truncation: use max(r, 0) in diffusion term
58 r_pos = np.maximum(r[:, i], 0)
59
60 r[:, i+1] = r[:, i] + self.kappa * (self.theta - r_pos) * dt + \
61 self.sigma * np.sqrt(r_pos) * dW
62
63 # Ensure non-negativity
64 r[:, i+1] = np.maximum(r[:, i+1], 0)
65
66 return r
67
68# Example
69cir = CIRModel(kappa=0.15, theta=0.05, sigma=0.02, r0=0.03)
70price_cir = cir.bond_price(0, 5, 0.03)
71print(f"CIR 5Y bond price: {price_cir:.4f}")
72Extension of Vasicek with time-dependent parameters to fit the initial yield curve exactly:
Calibration: Choose to match market bond prices:
where is the market instantaneous forward rate.
1class HullWhiteModel:
2 """Hull-White one-factor model."""
3
4 def __init__(self, kappa: float, sigma: float, yield_curve: YieldCurve):
5 self.kappa = kappa
6 self.sigma = sigma
7 self.curve = yield_curve
8
9 def theta(self, t: float) -> float:
10 """
11 Time-dependent drift calibrated to initial curve.
12
13 theta(t) = df/dt + kappa*f + sigma^2/(2*kappa)*(1 - exp(-2*kappa*t))
14 """
15 f_t = self.curve.instantaneous_forward(t)
16
17 # Numerical derivative of forward rate
18 dt = 0.0001
19 f_plus = self.curve.instantaneous_forward(t + dt)
20 df_dt = (f_plus - f_t) / dt
21
22 term3 = (self.sigma**2 / (2 * self.kappa)) * (1 - np.exp(-2 * self.kappa * t))
23
24 return df_dt + self.kappa * f_t + term3
25
26 def bond_price(self, t: float, T: float, r_t: float) -> float:
27 """Zero-coupon bond price."""
28 tau = T - t
29
30 # B(t, T)
31 B = (1 - np.exp(-self.kappa * tau)) / self.kappa
32
33 # A(t, T) - more complex due to time-dependent theta
34 P_market_T = self.curve.discount_factor(T)
35 P_market_t = self.curve.discount_factor(t)
36 f_t = self.curve.instantaneous_forward(t)
37
38 log_A = np.log(P_market_T / P_market_t) + B * f_t - \
39 (self.sigma**2 / (4 * self.kappa)) * B**2 * (1 - np.exp(-2 * self.kappa * t))
40
41 return np.exp(log_A - B * r_t)
42
43 def simulate(self, T: float, n_steps: int, n_paths: int = 1) -> np.ndarray:
44 """Simulate short rate paths."""
45 dt = T / n_steps
46 sqrt_dt = np.sqrt(dt)
47
48 r = np.zeros((n_paths, n_steps + 1))
49 r[:, 0] = self.curve.instantaneous_forward(0)
50
51 t = np.linspace(0, T, n_steps + 1)
52
53 for i in range(n_steps):
54 theta_t = self.theta(t[i])
55 dW = np.random.normal(0, sqrt_dt, n_paths)
56
57 r[:, i+1] = r[:, i] + (theta_t - self.kappa * r[:, i]) * dt + self.sigma * dW
58
59 return r
60
61# Example
62hw = HullWhiteModel(kappa=0.1, sigma=0.01, yield_curve=curve)
63price_hw = hw.bond_price(0, 5, curve.instantaneous_forward(0))
64print(f"Hull-White 5Y bond price: {price_hw:.4f}")
65print(f"Market 5Y bond price: {curve.discount_factor(5):.4f}")
66A swaption is an option to enter a swap. Key for hedging interest rate risk.
A payer swap exchanges fixed rate for floating rate (LIBOR) over tenor .
Swap value at time :
where is the forward LIBOR rate.
Payer swaption: Option to enter payer swap at time with strike .
Jamshidian's trick: Decompose swaption into portfolio of bond options.
1def swaption_price_hw(
2 model: HullWhiteModel,
3 T_option: float,
4 T_start: float,
5 T_end: float,
6 strike: float,
7 notional: float = 1.0,
8 n_periods: int = 4
9) -> float:
10 """
11 Price payer swaption using Jamshidian's decomposition.
12
13 Args:
14 T_option: Swaption expiry
15 T_start: Swap start
16 T_end: Swap end
17 strike: Fixed rate
18 n_periods: Number of payment periods per year
19 """
20 from scipy.stats import norm
21
22 # Payment dates
23 dt = 1 / n_periods
24 payment_dates = np.arange(T_start + dt, T_end + dt, dt)
25
26 # Compute critical rate r* such that swap value = 0
27 # This requires solving a nonlinear equation
28 def swap_value(r_star):
29 pv_fixed = sum(strike * dt * model.bond_price(T_option, T_i, r_star)
30 for T_i in payment_dates)
31 pv_float = model.bond_price(T_option, T_start, r_star) - \
32 model.bond_price(T_option, T_end, r_star)
33 return pv_float - pv_fixed
34
35 from scipy.optimize import brentq
36 r_star = brentq(swap_value, -0.1, 0.3)
37
38 # Price as sum of bond options using Black's formula
39 swaption_value = 0.0
40
41 for T_i in payment_dates:
42 # Strike price for bond option
43 K_i = model.bond_price(T_option, T_i, r_star)
44
45 # Volatility of bond price
46 B = (1 - np.exp(-model.kappa * (T_i - T_option))) / model.kappa
47 sigma_P = model.sigma * B * np.sqrt((1 - np.exp(-2 * model.kappa * T_option)) /
48 (2 * model.kappa))
49
50 # Current bond price
51 P_0_Ti = model.curve.discount_factor(T_i)
52
53 # Black's formula for bond option
54 d1 = (np.log(P_0_Ti / K_i) + 0.5 * sigma_P**2 * T_option) / \
55 (sigma_P * np.sqrt(T_option))
56 d2 = d1 - sigma_P * np.sqrt(T_option)
57
58 option_value = P_0_Ti * norm.cdf(d1) - K_i * norm.cdf(d2)
59
60 swaption_value += strike * dt * option_value
61
62 return notional * swaption_value
63
64# Example
65swaption_price = swaption_price_hw(hw, T_option=1.0, T_start=1.0,
66 T_end=6.0, strike=0.05)
67print(f"Payer swaption price: {swaption_price:.6f}")
68Key insight: Model the entire forward rate curve, not just the short rate.
Forward rate dynamics:
No-arbitrage condition (drift restriction):
This ensures consistency with bond price dynamics.
1class HJMModel:
2 """Heath-Jarrow-Morton framework."""
3
4 def __init__(self, sigma_func: callable, initial_curve: YieldCurve):
5 """
6 Args:
7 sigma_func: Volatility function sigma(t, T)
8 initial_curve: Initial forward rate curve
9 """
10 self.sigma = sigma_func
11 self.curve = initial_curve
12
13 def drift(self, t: float, T: float) -> float:
14 """
15 Compute drift alpha(t, T) from no-arbitrage condition.
16
17 alpha(t, T) = sigma(t, T) * integral_t^T sigma(t, s) ds
18 """
19 # Numerical integration
20 from scipy.integrate import quad
21
22 sigma_tT = self.sigma(t, T)
23 integral, _ = quad(lambda s: self.sigma(t, s), t, T)
24
25 return sigma_tT * integral
26
27 def simulate_forward_curve(
28 self,
29 T_max: float,
30 n_time_steps: int,
31 n_maturity_points: int
32 ) -> np.ndarray:
33 """
34 Simulate evolution of forward rate curve.
35
36 Returns:
37 Array of shape (n_time_steps + 1, n_maturity_points)
38 """
39 dt = T_max / n_time_steps
40 sqrt_dt = np.sqrt(dt)
41
42 # Maturity grid
43 maturities = np.linspace(0, T_max, n_maturity_points)
44
45 # Initialize forward curve
46 f = np.zeros((n_time_steps + 1, n_maturity_points))
47 for i, T in enumerate(maturities):
48 f[0, i] = self.curve.instantaneous_forward(T)
49
50 # Simulate
51 t = np.linspace(0, T_max, n_time_steps + 1)
52
53 for i in range(n_time_steps):
54 dW = np.random.normal(0, sqrt_dt)
55
56 for j, T in enumerate(maturities):
57 if T > t[i]: # Only simulate forward rates in the future
58 alpha = self.drift(t[i], T)
59 sigma = self.sigma(t[i], T)
60
61 f[i+1, j] = f[i, j] + alpha * dt + sigma * dW
62
63 return f
64
65# Example: Constant volatility HJM
66def sigma_constant(t, T):
67 return 0.01 # 1% volatility
68
69hjm = HJMModel(sigma_constant, curve)
70forward_curves = hjm.simulate_forward_curve(T_max=5, n_time_steps=100,
71 n_maturity_points=50)
72
73# Plot evolution
74fig = plt.figure(figsize=(14, 6))
75ax = fig.add_subplot(111, projection='3d')
76
77t_grid = np.linspace(0, 5, 101)
78T_grid = np.linspace(0, 5, 50)
79T_mesh, t_mesh = np.meshgrid(T_grid, t_grid)
80
81ax.plot_surface(t_mesh, T_mesh, forward_curves, cmap='viridis', alpha=0.8)
82ax.set_xlabel('Time t')
83ax.set_ylabel('Maturity T')
84ax.set_zlabel('Forward Rate f(t, T)')
85ax.set_title('HJM Forward Rate Surface Evolution')
86plt.show()
87Models forward LIBOR rates directly (market observables).
Forward LIBOR satisfies:
under the -forward measure.
Caplet pricing: Closed-form via Black's formula.
1class LIBORMarketModel:
2 """LIBOR Market Model (BGM)."""
3
4 def __init__(
5 self,
6 initial_forwards: np.ndarray,
7 volatilities: np.ndarray,
8 correlations: np.ndarray,
9 tenors: np.ndarray
10 ):
11 """
12 Args:
13 initial_forwards: Initial forward LIBOR rates
14 volatilities: Volatility for each forward rate
15 correlations: Correlation matrix between rates
16 tenors: Tenor structure (e.g., [0.25, 0.5, 0.75, ...])
17 """
18 self.L0 = initial_forwards
19 self.sigma = volatilities
20 self.rho = correlations
21 self.tenors = tenors
22 self.n_rates = len(initial_forwards)
23
24 # Cholesky decomposition for correlated Brownian motions
25 self.chol = np.linalg.cholesky(correlations)
26
27 def simulate(
28 self,
29 T: float,
30 n_steps: int,
31 n_paths: int = 1
32 ) -> np.ndarray:
33 """
34 Simulate LIBOR rates using log-normal dynamics.
35
36 Returns:
37 Array of shape (n_paths, n_steps + 1, n_rates)
38 """
39 dt = T / n_steps
40 sqrt_dt = np.sqrt(dt)
41
42 L = np.zeros((n_paths, n_steps + 1, self.n_rates))
43 L[:, 0, :] = self.L0
44
45 for step in range(n_steps):
46 # Generate correlated normal samples
47 Z = np.random.normal(0, 1, (n_paths, self.n_rates))
48 dW = (self.chol @ Z.T).T * sqrt_dt
49
50 # Update each forward rate
51 for i in range(self.n_rates):
52 # Drift correction for measure change
53 drift = 0.0
54 for j in range(i+1, self.n_rates):
55 delta = self.tenors[j] - self.tenors[j-1]
56 drift += (delta * self.sigma[j] * L[step, j] *
57 self.rho[i, j] * self.sigma[i]) / \
58 (1 + delta * L[step, j])
59
60 # Log-normal update
61 L[:, step+1, i] = L[:, step, i] * np.exp(
62 (drift - 0.5 * self.sigma[i]**2) * dt +
63 self.sigma[i] * dW[:, i]
64 )
65
66 return L
67
68 def caplet_price(
69 self,
70 strike: float,
71 maturity_idx: int,
72 notional: float = 1.0
73 ) -> float:
74 """
75 Price caplet using Black's formula.
76
77 Caplet pays: delta * max(L(T_i) - K, 0) at time T_{i+1}
78 """
79 from scipy.stats import norm
80
81 T = self.tenors[maturity_idx]
82 delta = self.tenors[maturity_idx+1] - self.tenors[maturity_idx]
83 L0 = self.L0[maturity_idx]
84 sigma = self.sigma[maturity_idx]
85
86 # Black's formula
87 d1 = (np.log(L0 / strike) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T))
88 d2 = d1 - sigma * np.sqrt(T)
89
90 # Discount factor (approximate)
91 df = 1 / (1 + L0 * delta)
92
93 return notional * delta * df * (L0 * norm.cdf(d1) - strike * norm.cdf(d2))
94
95# Example
96n_rates = 20
97tenors = np.linspace(0.25, 5.0, n_rates)
98initial_forwards = 0.03 * np.ones(n_rates) # Flat 3% curve
99volatilities = 0.2 * np.ones(n_rates) # 20% vol
100correlations = np.eye(n_rates) * 0.7 + 0.3 # 70% correlation
101
102lmm = LIBORMarketModel(initial_forwards, volatilities, correlations, tenors)
103
104# Simulate
105paths = lmm.simulate(T=5.0, n_steps=100, n_paths=1000)
106
107# Price a caplet
108caplet_price = lmm.caplet_price(strike=0.04, maturity_idx=10)
109print(f"Caplet price: {caplet_price:.6f}")
110Minimize pricing error between model and market swaption prices.
1def calibrate_hull_white_to_swaptions(
2 market_swaption_prices: np.ndarray,
3 swaption_maturities: np.ndarray,
4 swap_tenors: np.ndarray,
5 strikes: np.ndarray,
6 yield_curve: YieldCurve
7) -> dict:
8 """
9 Calibrate Hull-White model to market swaption prices.
10
11 Optimize kappa and sigma to minimize pricing error.
12 """
13 def objective(params):
14 kappa, sigma = params
15 model = HullWhiteModel(kappa, sigma, yield_curve)
16
17 errors = []
18 for i, (T_opt, tenor, K) in enumerate(zip(swaption_maturities,
19 swap_tenors, strikes)):
20 model_price = swaption_price_hw(model, T_opt, T_opt,
21 T_opt + tenor, K)
22 market_price = market_swaption_prices[i]
23 errors.append((model_price - market_price)**2)
24
25 return np.sum(errors)
26
27 from scipy.optimize import minimize
28
29 # Initial guess
30 x0 = [0.1, 0.01]
31 bounds = [(0.01, 1.0), (0.001, 0.1)]
32
33 result = minimize(objective, x0, bounds=bounds, method='L-BFGS-B')
34
35 return {
36 'kappa': result.x[0],
37 'sigma': result.x[1],
38 'rmse': np.sqrt(result.fun / len(market_swaption_prices))
39 }
401// C++ implementation for production speed
2#include <vector>
3#include <cmath>
4#include <Eigen/Dense>
5
6class HullWhiteModelCpp {
7private:
8 double kappa_;
9 double sigma_;
10 std::vector<double> forward_curve_;
11
12public:
13 HullWhiteModelCpp(double kappa, double sigma,
14 const std::vector<double>& forwards)
15 : kappa_(kappa), sigma_(sigma), forward_curve_(forwards) {}
16
17 double bondPrice(double t, double T, double r_t) const {
18 double tau = T - t;
19 double B = (1.0 - std::exp(-kappa_ * tau)) / kappa_;
20
21 // Simplified A calculation
22 double f_t = interpolateForward(t);
23 double log_A = -B * f_t - (sigma_ * sigma_ / (4.0 * kappa_)) *
24 B * B * (1.0 - std::exp(-2.0 * kappa_ * t));
25
26 return std::exp(log_A - B * r_t);
27 }
28
29 std::vector<std::vector<double>> simulate(
30 double T, int n_steps, int n_paths
31 ) {
32 // Vectorized simulation using Eigen
33 // 10x faster than Python
34 // ... implementation
35 }
36
37private:
38 double interpolateForward(double t) const {
39 // Linear interpolation
40 // ... implementation
41 }
42};
43Interest rate modeling is essential for fixed income derivatives. Start with Vasicek for intuition, use Hull-White for market calibration, and LMM for exotic derivatives. Always validate against market swaption prices and implement in C++ for production performance.
Technical Writer
NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.
Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.