Interpolating the Libor rate¶
Juan M. Fonseca-Solís · March 2015 (updated on December 2024) · 7 min read
Summary¶
In this ipython notebook we'll use data from daily reference rates, such as the London interbank interest rate (LIBOR), offered monthly by the Central Bank of Costa Rica (BCCR), to explain a linear and trigonometric interpolation techniques.
History¶
In June 2012, when resolving a legal dispute, the Commodity Futures Negotiation Commission of the United States (CFTC) discovered a series of irregularities in the management of the LIBOR by the British multinational bank Barclays. The Financial Times newspaper later confirmed the manipulation of this rate since 1991, which caused an international scandal, since the LIBOR is used as a reference to determine the interest rate of the loans in foreign currency all over the world [4,5].
As a result, millionaire fines were imposed on the British bank and an investigation was performed to find more reliable interest rates, including the first-rate, a rate managed by US banks that is based on real transactions and not on opinion polls like the LIBOR. Year 2022 was the end of life for the LIBOR [4,5].
The LIBOR, along with other daily reference rates, is published once a day with a scale of four maturity levels (the time until the loan will be charged):
- 1 month
- 3 months
- 6 months
- 12 months
Because only four values are published, banks usually require to linearly interpolate other maturity levels. For example, if interest rates of 1 and 3 months LIBOR are $2.156$% and $2.74029$%, respectively, the maturity level at $1.5$ months is calculated as follows [6]:
$$ y_n = \frac{y_1-y_0}{x_1-x_0} (x_n-x_0) + y_0. $$
x = [1, 3]
y = [2.156, 2.74029]
x_n = 1.5
y_n = (y[1]-y[0])/(x[1]-x[0]) * (x_n - x[0]) + y[0]
print(f'The Libor rate for {x_n} month(s) is {y_n}.')
The Libor rate for 1.5 month(s) is 2.3020725.
In the sections below we explain how linear interpolation works and how we can use other types of interpolation, like trigonometric ones.
Getting data from the BCCR web service¶
To start, lets consume the Banco Central de Costa Rica's SOAP web service to get some of the latest data for Libor scales, and plot the result.
import matplotlib.pylab as plt
from zeep import Client # SOAP client
import xml.etree.ElementTree as ET # read XMLs
import datetime
import os
import dateutil.parser
def plotLiborRate(liborRateScale: str, liborRateCode: int):
dicc = client.service.ObtenerIndicadoresEconomicosXML(
Indicador=liborRateCode,
FechaInicio=fechaInicio,
FechaFinal=fechaFinal,
Nombre = 'Juan M. Fonseca',
SubNiveles = 'N',
CorreoElectronico = 'juanma2268@gmail.com',
Token = os.environ['BCCR_TOKEN']
)
# extract the XML that comes embedded in the dictionary data structure
raiz = ET.fromstring(dicc)
# build the signal
x = []
y = []
for hijo in raiz:
if 2<len(hijo): # weekends have no rate
x.append(dateutil.parser.isoparse(hijo[1].text))
y.append(float(hijo[2].text))
# plot
return x, y
'''
Main
'''
client = Client('https://gee.bccr.fi.cr/Indicadores/Suscripciones/WS/wsindicadoreseconomicos.asmx?WSDL')
# Libor rates (https://gee.bccr.fi.cr/indicadoreseconomicos/Documentos/DocumentosMetodologiasNotasTecnicas/Webservices_de_indicadores_economicos.pdf)
liborRateScales = {
'1': 349,
'3': 350,
'6': 351,
'12': 352,
}
# find the Libor rate since a date in the past
fechaInicio = '01/01/2022'
fechaFinal = '16/07/2022'
plt.figure()
for liborRateScale in liborRateScales.keys():
(dates, rates) = plotLiborRate(liborRateScale, liborRateScales[liborRateScale])
print(f'Last {liborRateScale} month(s) Libor rates: {rates[-2:]}')
liborRates[liborRateScale] = {'dates': dates, 'rates': rates}
plt.plot(dates, rates)
plt.legend(liborRateTypes.keys())
plt.xticks(rotation = 45)
plt.xlabel('Days')
plt.show()
Last 1 month(s) Libor rates: [2.156, 2.12029] Last 3 month(s) Libor rates: [2.74029, 2.73757] Last 6 month(s) Libor rates: [3.38129, 3.31129] Last 12 month(s) Libor rates: [3.97829, 3.89643]
Sampling of a signal¶
Even it is not continuous, the LIBOR daily rate $z$ constitutes a discrete signal, that by choosing a sampling period $T$ of one day, can be expressed mathematically as follows [2,3]: $$ \tilde{z}[i] = z(iT) \quad i \in \mathbb{Z}, $$
where $\tilde{z}[i]$ and $z(t)$ are the discrete and continuous version of the signal, respectively. This means that the continuous signal can be converted into a discrete signal by reading its values in the instants $iT$. Using a notation of ordered pairs this can also be expressed as follows:
$$ \{(x_i,\tilde{z}_i)\}_{i=-\infty}^{\infty}, $$
where $x_{i+1}-x_i = T$ seconds. In both cases it is necessary to ensure that the $T_{\max} \ge 2T$ relationship is fulfilled (where $T_\max$ is the largest component of the signal). This relationship is summarized in what is known as the Nyquist theorem. If this theorem is not followed the reconstructed signal could be corrupted with aliasing.
Note: although the Nyquist theorem is an important topic on audio and video processing applications, we will ignore it here for practical purposes and because we know that the daily rate signal is bandlimited (that is, there are no variations between days).
Lineal interpolation¶
The interpolation of a discrete signal is achieved by convolving an interpolation function $I(t)$ –also called interpolation kernel– with each sample available, then a sum of all convolutions is performed to obtain a continuous function [3]:
$$ z(t) = \sum_{i=-\infty}^{\infty}{\tilde{z}[i]I\big(\frac{t-iT}{T}\big)} $$
This is easier to understand with a signal composed of the last libor rates of the signals above:
import numpy as np
#zt=np.sin(2*np.pi*10/100*np.arange(0,N))
N=13
x = np.linspace(0, 12, N)
y = [0.0]*N
y[1] = liborRates['1']['rates'][-1]
y[3] = liborRates['3']['rates'][-1]
y[6] = liborRates['6']['rates'][-1]
y[12] = liborRates['12']['rates'][-1]
plt.stem(x, y)
<StemContainer object of 3 artists>
First, the previous signal is resampled at a higher sampling rate by adding zeros in the middle of the samples:
from scipy import signal
M=N*13
x_p = np.linspace(0, N, M)
y_p = [0.0]*M
y_p[::N] = y
plt.stem(x_p, y_p, label='One day Libor rates (upsampled)')
plt.legend()
<matplotlib.legend.Legend at 0x2203ddc3c50>
Then we perform a convolution of each sample with a triangular kernel:
kernel=signal.windows.triang(M)
z = np.zeros(M)
colors = ['r', 'g', 'm', 'y', 'b']
for i in range(1,N):
zeros = np.zeros(M)
zeros[i*N] = y[i]
z += np.convolve(zeros, kernel, 'same')
plt.stem(np.convolve(zeros, kernel, 'same'), colors[i%len(colors)], markerfmt='%so'%colors[i%len(colors)])
And the result is a signal that "connects" the points:
plt.stem(x_p, z,'ko-')
plt.stem(x_p, y_p, 'bo--')
<StemContainer object of 3 artists>
Using an inverted-bell-non-triangular kernel – called Hamming window– we can obtain a smoother approximation, here is the formula of the kernel [7]:
$$ w[n] = 0.54 - 0.46 \cos \left( 2 \pi \frac{n}{N} \right), \qquad 0 \leq n \leq N. $$
And below the result of convoluting each point with this new equation, along the sum:
kernel=signal.windows.hamming(2*N)
z = np.zeros(M)
colors = ['r', 'g', 'm', 'y', 'b']
pylab.figure()
for i in range(1,N):
zeros = np.zeros(M)
zeros[i*N]=zt[i]
z += np.convolve(zeros,kernel,'same')
pylab.stem(np.convolve(zeros,kernel,'same'),colors[i%len(colors)],markerfmt='%so'%colors[i%len(colors)])
pylab.figure()
pylab.stem(z)
pylab.stem(ztt,'ro-')
<StemContainer object of 3 artists>
References:¶
Banco Central de Costa Rica. Tasa libor 1 mes. URL: https://gee.bccr.fi.cr/indicadoreseconomicos/Cuadros/frmVerCatCuadro.aspx?idioma=1&CodCuadro=%20334 (last consulted on 04/11/15).
LCAV, Ecolé Polytechnique Federale de Lausanne. Safecast : Band-limited interpolation of radiation measurements in Fukushima URL: https://nbviewer.jupyter.org/github/LCAV/SignalsOfTheDay/blob/master/Safecast/Safecast.ipynb.
P. Prandoni, M. Vertterli. Signal processing for communications. EPFL press 2008.
Wikipedia contributors. (2019, August 1). Libor scandal. In Wikipedia, The Free Encyclopedia. Retrieved 23:50, August 15, 2019, from https://en.wikipedia.org/w/index.php?title=Libor_scandal&oldid=908807968.
Miguel Elizondo. El LIBOR dejará de existir en el 2021. La República.net, Impacto legal. Monday, July 31-th, 2017. URL: https://www.larepublica.net/noticia/el-libor-dejara-de-existir-en-el-2021.
Linear interpolation example. International Swaps and Derivatives Association. URL: https://www.isda.org/a/7KiDE/linear-interpolation-example-1-10.pdf.
Scipy. Hamming. URL: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.hamming.html (last consulted on 12/31/24).
Notes¶
- In these cases, we talk about "innovation rate" (FIR), rather than sampling frequency.
- It can be proven that the least squares method works using the standard approximation theorem.
- An excellent resource on how to use the services of the BCCR can also be found at Tico.
- The images were taken from here and here
This work is under a Creative Commons Atribución 4.0 Internacional license. The website juanfonsecasolis.github.io It is a Costa Rican blog dedicated to independent research on issues related to digital signal processing. To reuse this article and cite the source you can use Bibtex:
@online{Fonseca2015,
author = {Juan M. Fonseca-Solís},
title = { Interpolating the Libor rate },
year = 2015,
url = {https://juanfonsecasolis.github.io/blog/JFonseca.interpolacion.html},
urldate = {}
}