Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This post explains how to price an interest rate swap (IRS) using R code and Excel’s illustrations. We use swap rates, zero curve data from Bloomberg. We consider 5-Year Libor 3M IRS without OIS discounting as an pre-crisis IRS example.

# Libor Interest Rate Swap Pricing using R code

### Swap Pricing

A fixed versus floating interest rate swap exchanges a stream of cash flows determined by 1) a predetermined fixed rate and 2) floating rates which will be determined periodically in the future respectively. Since we don’t know exactly the evolution of floating rates in the future, forward rate is used for the alternative variable coupon rate, which is implied in the market forward looking information. Market participants expect the forward rate to be the expected future rate in the perspective of fair pricing.

It is worth noting that floating rates are determined at refixing dates before its corresponding interest periods in advance. Whenever we price a swap, its first variable cash flow is always known but remaining cash flows are unknown. For this unknown future variable rates, we use forward rates for its corresponding interest periods, which are implied in the current market yield curve.

Swap pricing is to calculate the net present value (NPV), which is the difference between the sum of present values of fixed legs and floating legs. When a swap participant receives fixed and pays floating cash flows, his/her swap value at time t is

\begin{align} NPV(t) & = \underbrace{\sum_{i=1}^{n_i} {CF_{t_i}^{fixed} DF^{libor}(t,t_i)}}_{\text{PV of fixed CFs}} \\ & – \underbrace{\sum_{j=1}^{n_j} {CF_{t_j}^{float} DF^{libor}(t,t_j)}}_{\text{PV of floating CFs}} \\ \end{align} \begin{align} & DF^{libor} = \text{Libor discount factor}\\ & DF^{libor}(t,t_i) = DF^{libor} \text{ from } t_i \text{ to } t \text{ for the fixed leg} \\ & DF^{libor}(t,t_j) = DF^{libor} \text{ from } t_j \text{ to } t \text{ for the floating leg} \\ & t_i = i \text{-th payment date for the fixed leg}, i=1,2,…, n_i \\ & t_j = j \text{-th payment date for the floating leg}, j=1,2,…, n_j \\ & s = \text{spot date} \end{align}

### Cash Flows

Since discount factor is the market information, we need to calculate a stream of cash flows of two legs (NA = notional amount).

#### Fixed leg

\begin{align} CF_{t_i}^{fixed} = \underbrace{ \underbrace{ \underbrace{\text{C}} _{\text{coupon rate}} \times \tau(t_{i-1},t_i) } _{\text{semi-annual fixed coupon rate}} \times NA} _{\text{semi-annual fixed coupon amount}} \\ \end{align} \begin{align} C &= \text{fixed rate} \\ \tau(t_{i-1},t_i) &= \text{day fraction(30I/360)} \\ &= (360 × \Delta Year + 30 × \Delta Month + \Delta Day) / 360 \end{align}

#### Floating leg

\begin{align} CF_{t_j}^{float} = \underbrace{ \underbrace{ \underbrace{FD^{libor}(t, t_{j-1},t_j)} _{\text{forward rate}} \times \tau(t_{j-1},t_j) } _{\text{quarterly variable coupon rate}} \times NA} _{\text{quarterly variable coupon amount}} \\ \end{align} \begin{align} FD^{libor}(t, t_{j-1},t_j) &= \text{forward rate between } t_{j-1} \text{ and } t_j \\ &\quad \text{ implied in the time } t \text{ Libor curve} \\ \tau(t_{j-1},t_j) &= \text{day fraction(ACT/360)} \\ &= \text{actual days in-between} / 360 \end{align}

### Discount Factor and Forward Rate

For the IRS swap pricing to be completed, discount factors and forward rates are needed to be calculated in the following way.

#### Discount Factor at time t

\begin{align} & DF^{libor}(t,t_i) = \exp \left(-R^{libor}(t,t_i) \times \frac{t_i – t}{365} \right) \\ \\ & R^{libor}(t,t_i) = \text{zero rate from } t_i \text{ to } t \text{ implied in the Libor curve} \end{align}

#### Forward Rate at time t

\begin{align} FD^{libor}(t, t_{j-1},t_j) = \frac{365}{t_j – t} \times \left(\frac{DF^{libor}(t,t_{j-1})}{DF^{libor}(t,t_j)}-1 \right) \end{align}

### IRS Specification

As of 2021/06/30, consider the following 5-year IRS (Pay Float & Rec Fixed) for Libor 3M index with market information (swap rates and zero curve), which are from the Bloomberg.
As can be seen the above table, the fixed coupon rate of the fixed leg is 0.96495 which is the 5-year market swap rate as definition. Payment frequency and day count convention are different between the fixed leg and float leg. This specification is not absolute but chosen conventionally. Furthermore, there are many kinds of day count conventions. For more information about the day count conventions and swap specifications, you can find lots of wep pages.

Before going to the calculation, we typically need to determine a series of 6 dates, which are dates for Interest Begin, Interest End, Accrual Begin, Accrual End, Reset Date, Payment Date. Determination of these dates requires market conventions and is somewhat complicated and important. In principal, the general swap pricing encompasses the determination of these dates.

However, since most in-house pricing system or Bloomberg or Reuter provide that information, we can calculate the following swap cash flow schedules and the net present value (NPV) with these dates. This topic will be discussed in the later post. This time, we assume away them by using the Bloomberg information regarding cash flow schedules (paymemt dates) and zero rate curve.

We try to price the 5-year IRS at spot date (s; two business days from the trade date). Therefore, pricing formula is as follows.
\begin{align} & NPV(s) = \sum_{i=1}^{n_i} {CF_{t_i}^{fixed} DF^{libor}(s, t_i)} -\sum_{j=1}^{n_j} {CF_{t_j}^{float} DF^{libor}(s, t_j)} \\ \\ & s = \text{spot date} \end{align}
Price of IRS at spot date is zero because there is no profit or loss between two counterparties at inception. Therefore, we can validate our swap pricing model by investigating whether the swap price at the spot date is zero or not.

In real pricing, we use linearly interpolated zero curve since payment dates do not coincide with dates of market zero rate curve.

### R code

The following R code implements 5-year LIBOR 3M IRS pricing with the curve date of 2021/06/30 and the spot date of 2021/07/02.

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 #=========================================================================## Financial Econometrics & Derivatives, ML/DL using R, Python, Tensorflow  # by Sang-Heon Lee ## https://kiandlee.blogspot.com#————————————————————————-## Libor 5-year fixed versus floating IRS Pricing#=========================================================================# graphics.off()  # clear all graphsrm(list = ls()) # remove all files from your workspace #————————————————————————–# 1. Market Information#————————————————————————– # Zero curve from Bloomberg as of 2021-06-30df.zero <– data.frame(    date = c(“2021-10-04”,“2021-12-15”,“2022-03-16”,“2022-06-15”,             “2022-09-21”,“2022-12-21”,“2023-03-15”,“2023-07-03”,             “2024-07-02”,“2025-07-02”,“2026-07-02”,“2027-07-02”,             “2028-07-03”,“2029-07-02”,“2030-07-02”,“2031-07-02”,             “2032-07-02”,“2033-07-05”,“2036-07-02”,“2041-07-02”,             “2046-07-02”,“2051-07-03”,“2061-07-05”,“2071-07-02”),        rate = c(0.00147746193495074, 0.00144337757980778,             0.00166389741542625,0.00175294804717070,0.00196071374597585,             0.00224582504806747,0.00264462838911974,0.00328408008984121,             0.00571530169527018,0.00795496282359075,0.00970003866673104,             0.01113416387898720,0.01229010329346910,0.01320660291639990,             0.01396222829363160,0.01461391064905110,0.01518876914165160,             0.01567359620429550,0.01673867348140660,0.01771539830734830,             0.01798302077085120,0.01801516858533200,0.01707008589009480,             0.01580574448899780    )) #————————————————————————–# 2. Libor Swap Specification#————————————————————————– spot_date_ymd  <– as.Date(“2021-07-02”)   # spot date no_amt     <– 10000000      # notional amountfixed_rate <– 0.0096495 # cf_scedule from Bloomberg lt.cf_date <– list(     fixed = c(“2022-01-04”,“2022-07-05”,“2023-01-03”,“2023-07-03”,              “2024-01-02”,“2024-07-02”,“2025-01-02”,“2025-07-02”,              “2026-01-02”,“2026-07-02”),    float = c(“2021-10-04”,“2022-01-04”,“2022-04-04”,“2022-07-05”,              “2022-10-03”,“2023-01-03”,“2023-04-03”,“2023-07-03”,              “2023-10-02”,“2024-01-02”,“2024-04-02”,“2024-07-02”,              “2024-10-02”,“2025-01-02”,“2025-04-02”,“2025-07-02”,              “2025-10-02”,“2026-01-02”,“2026-04-02”,“2026-07-02”)) #————————————————————————–# 3. Swap Pricing – Preprocessing#————————————————————————– # spot date as serial numberspot_date <– as.numeric(as.Date(spot_date_ymd)) # Interpolation of zero curvev.date   <– as.numeric(as.Date(df.zero$date))v.zero <– df.zero$ratef_linear <– approxfun(v.date, v.zero, method=“linear”)v.date.inter <– spot_date:max(v.date)v.zero.inter <– f_linear(v.date.inter) # Figures for zero curvex11(width=6, height=5); plot(v.date, v.zero, type = “b”, col = “green”, pch = 16, cex = 1.5)lines(v.date.inter, v.zero.inter, col = “blue”, type=“l”, lwd = 3)legend(“bottomright”,        legend = c(“market zero rate”, “interpolated zero rate”),       col = c(“green”, “blue”), lty = 1, bty = “n”, lwd = 2) # number of CFsni <– length(lt.cf_date$fixed)nj <– length(lt.cf_date$float) # output dataframe with CF dates and its interpolated zerodf.fixed = data.frame(ymd = as.Date(lt.cf_date$fixed), date = as.numeric(as.Date(lt.cf_date$fixed))) df.float = data.frame(ymd = as.Date(lt.cf_date$float), date = as.numeric(as.Date(lt.cf_date$float))) #————————————————————————–# 4. Swap Pricing – Calculation#————————————————————————– #———————————————————-#  1)  Fixed Leg#———————————————————-# zero rate for discountingdf.fixed$zero_DC = f_linear(df.fixed$date) # discount factordf.fixed$DF <– exp(–df.fixed$zero_DC*(df.fixed$date–spot_date)/365) # tau, CFfor(i in 1:ni) { ymd <– df.fixed$ymd[i]    ymd_prev <– df.fixed$ymd[i–1] if(i==1) ymd_prev <– spot_date_ymd d <– as.numeric(strftime(ymd, format = “%d”)) m <– as.numeric(strftime(ymd, format = “%m”)) y <– as.numeric(strftime(ymd, format = “%Y”)) d_prev <– as.numeric(strftime(ymd_prev, format = “%d”)) m_prev <– as.numeric(strftime(ymd_prev, format = “%m”)) y_prev <– as.numeric(strftime(ymd_prev, format = “%Y”)) # 30I/360 tau <– (360*(y–y_prev) + 30*(m–m_prev) + (d–d_prev))/360 # cash flow rate df.fixed$rate[i] <– fixed_rate        # Cash flow at time ti    df.fixed$CF[i] <– fixed_rate*tau*no_amt # day fraction} # Present value of CFdf.fixed$PV = df.fixed$CF*df.fixed$DF  #———————————————————-#  2)  Floating Leg#———————————————————- # zero rate for discountingdf.float$zero_DC = f_linear(df.float$date) # discount factordf.float$DF <– exp(–df.float$zero_DC*(df.float$date–spot_date)/365) # tau, forward rate, CFfor(i in 1:nj) { date <– df.float$date[i]    date_prev <– df.float$date[i–1] DF <– df.float$DF[i]    DF_prev   <– df.float$DF[i–1] if(i==1) { date_prev <– spot_date DF_prev <– 1 } # ACT/360 tau <– (date – date_prev)/360 # forward rate fwd_rate <– (1/tau)*(DF_prev/DF–1) # cash flow rate df.float$rate[i] <– fwd_rate        # Cash flow amount at time ti    df.float$CF[i] <– fwd_rate*tau*no_amt # day fraction} # Present value of CFdf.float$PV = df.float$CF*df.float$DF #———————————————————-#  3)  Swap Price at spot date#———————————————————- df.fixed[,–2]df.float[,–2]print(paste0(“Fixed Leg = “, round(sum(df.fixed$PV),6)))print(paste0(“Float Leg = “, round(sum(df.float$PV),6)))print(paste0(“Swap Price at spot date = “,              round(sum(df.fixed$PV) – sum(df.float$PV),6))) Colored by Color Scripter cs

### Results

The following figure draws the market zero rate curve (Bloomberg) and the linearly interpolated zero rate curve (from approxfun() R function) at 2021/06/30.

The following results indicate that the swap price is $2.719318. We expect this price to be$0 but cumulated numerical errors or unknown aspects of interpolation make this difference. But this is considered a zero value swap because the price ratio against nominal amount is 2.719318/10000000 = 0.00000027, which is nearly zero in the view of the numerical calculation.

### Final Thoughts

From this post, we can calculate the price of Libor IRS. In this process, we discount cash flows by using Libor discount factors which is implied in Libor curve. But this approach is pre-crisis approach (prior to 2008 global financial crisis). These days Libor discounting is replaced by OIS (overnight indexed swap) discounting which is the post-crisis approach (after 2008 GFC). Next post will consider the same 5-year Libor IRS using OIS discounting. $$\blacksquare$$