Interest Rate Swap Pricing using R code
[This article was first published on K & L Fintech Modeling, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
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. Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | #=========================================================================# # 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 graphs rm(list = ls()) # remove all files from your workspace #————————————————————————– # 1. Market Information #————————————————————————– # Zero curve from Bloomberg as of 2021-06-30 df.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 amount fixed_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 number spot_date <– as.numeric(as.Date(spot_date_ymd)) # Interpolation of zero curve v.date <– as.numeric(as.Date(df.zero$date)) v.zero <– df.zero$rate f_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 curve x11(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 CFs ni <– length(lt.cf_date$fixed) nj <– length(lt.cf_date$float) # output dataframe with CF dates and its interpolated zero df.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 discounting df.fixed$zero_DC = f_linear(df.fixed$date) # discount factor df.fixed$DF <– exp(–df.fixed$zero_DC*(df.fixed$date–spot_date)/365) # tau, CF for(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 CF df.fixed$PV = df.fixed$CF*df.fixed$DF #———————————————————- # 2) Floating Leg #———————————————————- # zero rate for discounting df.float$zero_DC = f_linear(df.float$date) # discount factor df.float$DF <– exp(–df.float$zero_DC*(df.float$date–spot_date)/365) # tau, forward rate, CF for(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 CF df.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))) | 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\)
To leave a comment for the author, please follow the link and comment on their blog: K & L Fintech Modeling.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.