Risk Factors in USD Libor Market

In this notebook, we perform a classical analysis in risk management. Using 11 years of USD libor data (deposit and swap rates), we:

  • Compute a time series of zero-coupon curves, bootstrapped from the deposit and swap rates, using the QuantLib library

  • Sample these zero-coupon curves at constant maturities

  • Perform a Principal Components Analysis on the change in zero-coupon rates, using the numpy library

The first 3 principal components account for over 95% of the total variance, and can be interpreted as follows:

  • The first factor represents an approximate parallel shift

  • The second factor represents a twist

  • The third factor represents a change in convexity

The notebook demonstrates the use of high-level functions that hide much of the complexity of QuantLib.

[2]:
import os

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as ml

import pandas as pd
from quantlib.util.rates import zero_rate, make_term_structure

Bootstrapping Zero-Coupon Yield Curves

A set of zero-coupon yield curves is bootstrapped from deposit and swap rates stored in the pandas DataFrame df_libor.

[3]:
df_libor = pd.read_pickle(os.path.join('..', 'data', 'df_libor.pkl'))

dtObs = df_libor.index

dtI = dtObs[range(0, len(dtObs) - 1, 60)]
days = [10, 30, 90, 182, 365, 365 * 2, 365 * 3,
            365 * 5, 365 * 10, 365 * 15]

    # maturity in columns, observation days in rows
zc_rate = np.empty((len(dtI), len(days)), dtype='float64')
dt_maturity = np.empty_like(zc_rate, dtype='object')

for i, obs_date in enumerate(dtI):
    rates = df_libor.xs(obs_date) / 100
    # bootstrap a term structure from the libor rates
    ts = make_term_structure(rates, obs_date)
    # compute zero-coupon rates for a range of maturities
    (dt_maturity[i, ], zc_rate[i, ]) = zero_rate(ts, days, obs_date)



Principal Components Analysis and Display

The first three principal components identify the three major risk factors, and account for 95% of the total variance:

  • The first factor represents an approximate parallel shift

  • The second factor represents a twist

  • The third factor represents a change in convexity

[26]:
from sklearn import decomposition
pca = decomposition.PCA()
# compute rate change
zc_diff = np.diff(zc_rate, axis=0)
# PCA on rate change
zc_pca = pca.fit(zc_diff)

fig = plt.figure()
fig.set_size_inches(15,6)

ax = fig.add_subplot(121)

# compute x-axis limits
dtMin = dt_maturity[0,0]
dtMax = dt_maturity[-1,-1]

ax.set_xlim(dtMin, dtMax)
ax.set_ylim(0.0, 0.1)

# plot a few curves
for i in range(0, len(dtI),3):
    ax.plot(dt_maturity[i,], zc_rate[i,])
plt.title('Zero-Coupon USD Libor: 2000 to 2010')

ax2 = fig.add_subplot(122)
ttm = np.array(days)/365.0
ax2.plot(ttm, zc_pca.components_[0], 'k--', ttm, zc_pca.components_[1], 'k:', ttm, zc_pca.components_[2], 'k')
leg = ax2.legend(('PC 1', 'PC 2', 'PC 3'))
plt.title('First 3 Principal Components of USD Libor')
plt.show()

[ ]: