You are on page 1of 6

GROUP ASSIGNMENT

Leslie Odoi: leslie.odoi@aol.com

Raphael Dombawel: raphaeldombawel8@gmail.com

The LIBOR Forward Market Model

The London Inter-Bank Offered Rates or LIBOR are benchmark short term simple interest rates at which
banks can borrow money from other banks in the London interbank market. LIBOR rates are fixed daily
by the British Bankers’ Association and are quoted for various maturities and currencies (Selic, 2006).

The LIBOR Market Model (LMM) is an interest rate model based on evolving LIBOR market forward
rates. It is also known as the Brace-Gatarek-Musiela (BGM) model, after the authors of one of the first
papers where it was introduced. In contrast to models that evolve the instantaneous short rate (Hull-
White, Black-Karasinski models) or instantaneous forward rates (Heath-Jarrow-Morton model), which
are not directly observable in the market, the objects modeled using LMM are market-observable
quantities (LIBOR forward rates). This makes LMM popular with market practitioners. Another feature
that makes the LMM popular is that it is consistent with the market standard approach for pricing caps
using Black’s formula.
The LMM can be used to price any instrument whose pay-off can be decomposed into a set of forward
rates. It assumes that the evolution of each forward rate is lognormal. Each forward rate has a time
dependent volatility and time dependent correlations with the other forward rates. After specifying
these volatilities and correlations, the forward rates can be evolved using Monte Carlo simulation.
Expectations of discounted coupons are then calculated in order to determine the fair value of an
instrument.
The standard lognormal LMM does not produce the market-observed caplet volatility smile/skew. To
produce a skew, the LMM can be extended to incorporate a local volatility model, a stochastic volatility
model, a jump diffusion model, or some combination of the above.

Mathematics underlying the model


From the lecture notes, consider the set of dates T0 , T1 ,.., TN  and let Pj (t ) be the price of a bond at
time t which expires at time T j , F (t , T j , T j 11)  F j (t ) be the forward rate between times T j and T j 1
at time t, and  j  T j 1  T j be the time between two dates.
The LFMM indicates that each market forward rate has the following SDE:
dFj (t )  Fj (t )  j (t )dt  F j (t ) j (t )dWt
Where  j (t ) and  j (t ) are the (time-dependent) drift and volatility respectively associated with F j (t )
and Wt is a Brownian motion.
j
 k Fk (t ) k j (t )
It can be shown that :  j (t )  
k (t ) 1   k Fk (t )
, where  (t )  min i, t  Ti  and  (t ) is

considered important because of how the forward rates evolve over time.
Python Codes for the simulation of LFMM

1 #relevant libraries
2 import numpy as np
3 from scipy.stats import norm
4 import matplotlib.pyplot as plt
5 import random
6 import scipy.optimize

The work started off by importing the necessary Libraries for the exercise which is shown in the table
above.

1 #Setting parameters
2 r0 = 0.2
3 alpha = 0.2
4 b = 0.08
5 sigma = 0.3
6
7 t = np.linspace(0.1,1,12)
8 sigmaj = 0.3

The next thing was to set the parameters given in the exercise, which are explained below:

The sigmaj variable is the volatility for the LFMM model which is given as 0.3

t represents years and times of simulations. In this case 1 year and simulations are monthly therefore,
12.

alpha is 0.2 as well as correlation taken as 0.2 from previous assignment info.

1 #At this stage, we will create an array of the bond prices of different tenor from our market
2 vasi_bond = np.array([99.38,98.76,98.15,97.54,96.94,96.34,95.74,95.16,94.57,93.99,93.42,92.85])
3 print("Market bond prices observed")
4 print(vasi_bond)
An array was created to contain all the observed Zero coupon bond prices given in the exercise.

1 #Setting of seed and applying the algorithms


2 np.random.seed(0)
3 n_simulations = 100000
4 n_steps = len(t)
5
6 mc_forward = np.ones([n_simulations, n_steps-1]) * (vasi_bond[:-1] - vasi_bond[1:]) / (2
*vasi_bond[1:])
7 predcorr_forward = np.ones([n_simulations, n_steps-1]) * (vasi_bond[:-1] - vasi_bond[1:]) / (2 *
vasi_bond[1:])
8 predcorr_capfac = np.ones([n_simulations, n_steps])
9 mc_capfac = np.ones([n_simulations, n_steps])
10
11 delta = np.ones([n_simulations, n_steps-1]) * (t[1:] - t[:-1])
12
13 #The delta variable stores the time increments between the bond maturities.
14 #We create this as a matrix so that we can use this to vectorise our code.

For the table above, the seed is set as well as some useful parameters.

The n simulations variable is the number of Monte Carlo simulations that was done at each time point.
The n steps variable stores the number of time steps we are simulating over.

The delta variable stores the time increments between the bond maturities

1 for i in range(1, n_steps):


2 Z = norm.rvs(size = [n_simulations, 1])
3
4 # Explicit Monte Carlo simulation
5 muhat = np.cumsum(delta[:, i:] * mc_forward[:, i:] * sigmaj**2 / (1 + delta[:, i:] * mc_forward[:, i:]),
axis = 1)
6 mc_forward[:, i:] = mc_forward[:, i:] * np.exp((muhat - sigmaj**2 / 2) * delta[:, i:] + sigmaj *
np.sqrt(delta[:, i:]) * Z)
7
8 # Predictor-Corrector Montecarlo simulation
9 mu_initial = np.cumsum(delta[:, i:] * predcorr_forward[:, i:] * sigmaj**2 / (1 + delta[:, i:] *
predcorr_forward[:, i:]), axis = 1)
10 for_temp = predcorr_forward[:, i:] * np.exp((mu_initial - sigmaj**2 / 2) * delta[:, i:] + sigmaj *
np.sqrt(delta[:, i:]) * Z)
11 mu_term = np.cumsum(delta[:, i:] * for_temp * sigmaj**2 / (1 + delta[:, i:] * for_temp), axis = 1)
12 predcorr_forward[:, i:] = predcorr_forward[:, i:] * np.exp((mu_initial + mu_term - sigmaj**2) *
delta[:, i:] / 2 + sigmaj * np.sqrt(delta[:, i:]) * Z)

We run the Explicit and Predictor-Corrector simulations at this stage as part of our simulation process as
shown in the table above. In line 5 and 6, the basic Monte-Carlo simulation is applied where line 5

  k F j (ti 1 ) k j (t )
j
equals the equation  j (ti 1 )   and that of line 6 is the equation
k i 1   k Fj (ti 1 )
    1  
Fj (ti )  F j (ti 1 ) exp   j (ti 1 )   2   i 1   j  i 1 Zi 
 2  

Now we simulate our forward rates. Because the next period’s forward rate is dependent on the
previous period’s forward rate, we will need to make use of a for loop (i.e. we can’t vectorise
everything). We apply the basic Monte Carlo simulation in lines 5 and 6. Line 5 refers to equation (6.2),
and line 6 refers to equation (6.1). The predictor-corrector method is applied in lines 9 to 12.

For the capitalization the code is given below


1 #implying capitalizations

2 mc_capfac[:, 1:] = np.cumprod(1 + delta * mc_forward, axis = 1)

3 predcorr_capfac[:, 1:] = np.cumprod(1 + delta * predcorr_forward, axis = 1)

1 #Inverting the capitalisation factors to imply bond prices (discount factors)


2 mc_price = mc_capfac**(-1)
3 predcorr_price = predcorr_capfac**(-1)
4 print("Monte Carlo Bond Price")
5 print(mc_price)
6 print("Predictor corrected bond price")
7 print (predcorr_price)

n
The capitalization factor is implemented by the use of the formula C (t0 , tn )  (1  
k 1
k Fk (tk )) . The

np.cumprod function returns the cumulative product of the elements in a matrix. The axis = 1 command
results in the np.cumprod function returning the cumulative product of each row in the matrix. The
inverse of the capitalisation factors is then taken to get the bond prices.

1 #Taking averages
2 mc_final = np.mean(mc_price, axis = 0)
3 predcorr_final = np.mean(predcorr_price, axis = 0)
4 print("Final Monte Carlo price")
5 print(mc_final)
6 print("Final Predictor corrected price")
7 print (predcorr_final)

The price of the Zero coupon bond is equivalent to finding the average discount factors as shown by the
code above.

 A(t ,T ) rt  D (t ,T ) 1  e  ( T  t )
The price of a Zero coupon bond is given by B(t , T )  e , where A(t , T )  and

 2   2 A(t , T )2
D(t , T )   b  2   A(t , T )  (T  t )  
 2  4

The closed form solution for the Bond prices in vasicek model is given below
1 #Analytical bond price vasicek
2 # Analytical bond price
3 def A(t1,t2,alpha):
4 return (1-np.exp(-alpha*(t2-t1)))/alpha
5
6 def D(t1,t2,alpha,b,sigma):
7 val1 = (t2-t1-A(t1,t2,alpha))*(sigma**2/(2*alpha**2)-b)
8 val2 = sigma**2*A(t1,t2,alpha)**2/(4*alpha)
9 return val1-val2
10
11 Def bond_price_fun(r,t,T,alpha,b,sigma):
12 return np.exp(-A(t,T,alpha)*r+D(t,T,alpha,b,sigma))
13
14 r0 = 0.05

15 # Difference between model bond prices and yield curve bond prices
16 def F(x):
17 alpha = x[0]
18 b = x[1]
19 sigma = x[2]
20 return sum(np.abs(bond_price_fun(r0,0,years,alpha,b,sigma)-bond_prices))
21 #NB Note that 𝐹 is the function that we are optimizing.
22 #This returns the absolute value of the sum of the differences between the model prices and the
market prices.

Performing optimization of values is carried out the code below

1 #We can then set our bounds, and perform the calibration using the following code:
2 #Noting that alpha, sigma and b satisfy some conditions like 0>=alpha >= 1, 0>=b >= 0.5,0>=sigma>=
1,
3
4 #Minimizing F
5
6 bnds = ((0,2),(0,0.5),(0,0.2))
7 opt_val = scipy.optimize.fmin_slsqp(F, (0.3,0.05,0.03), bounds=bnds)
8 opt_alpha = opt_val[0]
9 opt_b = opt_val[1]
10 opt_sig = opt_val[2]
The model prices are calculated by the following code

1 # Calculating model prices


2 model_prices = bond_price_fun(r0,0,years,opt_alpha,opt_b,opt_sig)
3 print ("Model Prices")
4 print (model_prices)

You might also like