Skip to content

Bootstrapping discount factors from swap rates

Course exercises 58–59 · Part I

Problem

From the par rates of interest rate swaps with annual payments, recover the discount factors for each maturity: first by solving a linear system (exercise 58), then with a reusable function, a recursive formula, and the inverse step from discount factors back to swap rates (exercise 59).

Method

A par swap with rate \(z_n\) and maturity \(n\) has zero value, so for each maturity:

\[ z_n \sum_{k=1}^{n} v_k + v_n = 1 \]

Stacking the equations for \(n = 1, \dots, N\) gives a lower-triangular system \(A\,v = \mathbf{1}\), with the swap rates below the diagonal and \(1 + z_n\) on it. The same condition, solved one maturity at a time, gives the recursion and its inverse:

\[ v_n = \frac{1 - z_n \sum_{k<n} v_k}{1 + z_n}, \qquad z_n = \frac{1 - v_n}{\sum_{k \le n} v_k} \]

Code

clear all, clc

% Data Input

z = [0.021, 0.025, 0.032, 0.043];       % swap rates
z = z';                                 % we need a column vector


% Point 1

    % 1. square matrix where each row holds the corresponding swap rate
    M = repmat(z, 1, length(z));        % repeat z once vertically and 4 times horizontally

    % 2. keep the lower triangular part (rates on and below the diagonal)
    A = tril(M);                        % sets to zero everything above the main diagonal

    % 3. add 1 on the main diagonal only
    A = A + eye(length(z)); % eye() is the identity matrix: adding it puts a 1 on the diagonal


% Point 2
    v = A \ ones(length(z), 1)         % backslash: solves the linear system A*v = 1

    check = A * v                      % verification: should return a vector of ones
    % v are the discount factors for the 1, 2, 3 and 4 year maturities

% Point 2 alternative

    v = inv(A) * ones(4, 1)
    check = A * v
clear, clc

%% 1
% see the function F_bootstrap

%% 2

z = [0.0185; 0.0223; 0.0297; 0.0313];

v = F_bootstrap(z);

vv = zeros(length(z), 1);

for k = 1:length(z)
    vv(k) = (1 - z(k)*sum(vv(1:k-1))) / (1 + z(k));
end

vv

%% 3

zz = zeros(length(v), 1);

for k = 1:length(v)
    zz(k) = (1 - v(k)) / sum(v(1:k));
end

zz
function [v] = F_bootstrap(z)

z = reshape(z, length(z), 1)
n = length(z);

A = repmat(z, 1, n);
A = tril(A);
A = A + eye(n);

v = inv(A) * ones(n, 1)

end
import numpy as np

# Data Input
z = np.array([0.021, 0.025, 0.032, 0.043])     # swap rates

# %% Point 1

# 1. square matrix where each row holds the corresponding swap rate
M = np.tile(z.reshape(-1, 1), (1, len(z)))     # MATLAB: repmat(z, 1, length(z)) with z a column
                                               # reshape(-1, 1) turns z into a column

# 2. keep the lower triangular part
A = np.tril(M)

# 3. add 1 on the main diagonal
A = A + np.eye(len(z))

# %% Point 2
v = np.linalg.solve(A, np.ones(len(z)))        # MATLAB: v = A \ ones(length(z), 1)
print("v =", v)

check = A @ v                                  # verification: should return a vector of ones
print("check =", check)

# %% Point 2 alternative
v = np.linalg.inv(A) @ np.ones(4)              # MATLAB: inv(A) * ones(4, 1)
print("v =", v)
check = A @ v
print("check =", check)
import numpy as np
from F_bootstrap import F_bootstrap

# %% 1
# see the function F_bootstrap

# %% 2

z = np.array([0.0185, 0.0223, 0.0297, 0.0313])

v = F_bootstrap(z)

vv = np.zeros(len(z))

for k in range(len(z)):                        # MATLAB: for k = 1:length(z)
    vv[k] = (1 - z[k] * np.sum(vv[:k])) / (1 + z[k])   # MATLAB: sum(vv(1:k-1))  ->  Python: vv[:k]

print("vv =", vv)

# %% 3

zz = np.zeros(len(v))

for k in range(len(v)):
    zz[k] = (1 - v[k]) / np.sum(v[:k+1])       # MATLAB: sum(v(1:k))  ->  Python: v[:k+1]

print("zz =", zz)
import numpy as np


def F_bootstrap(z):
    # input:  z := swap rates
    # output: v := discount factors

    z = np.array(z)
    n = len(z)

    A = np.tile(z.reshape(-1, 1), (1, n))      # each row holds its own swap rate
    A = np.tril(A)
    A = A + np.eye(n)

    v = np.linalg.inv(A) @ np.ones(n)
    return v

Results

Maturity Swap rate (ex. 58) Discount factor Swap rate (ex. 59) Discount factor
1 2.10% 0.9794 1.85% 0.9818
2 2.50% 0.9517 2.23% 0.9568
3 3.20% 0.9091 2.97% 0.9152
4 4.30% 0.8417 3.13% 0.8830

In exercise 58, \(A\,v\) returns a vector of ones. In exercise 59 the recursion gives the same discount factors as the linear system, and the inverse formula returns the original swap rates.

Takeaways

  • Bootstrapping is a triangular linear system: each maturity only needs the shorter ones.
  • A \ b solves the system directly and is the numerically preferred way; inv(A) * b gives the same result here but computes a full inverse that is not needed.
  • Being able to go from rates to discount factors and back is the check that both formulas are right.