Pricing floating legs of interest rate swaps
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
In this post we will close the trilogy on (old style) swap pricing. In particular, we will look at how downloading the data for the variable rate needed to calculate the variable leg accrual.
Part 1 gave the general idea behind tidy pricing interest rate swaps using a 7 lines pipe
Part 2 went much more into detail and priced some real world contract comparing the results obtained vs Bloomberg and showing significantly good results.
The only part missing was calculating the accrual for the floating leg. To do this we need the information about the historical level of the interest rate to which the leg is linked.
For standard EUR contracts, this rate is the 6 months’ EURIBOR. For those of you who are interested in understanding more what this rate is can go this link
Sourcing the data is not a problem, but doing it the R
way (ie. for free), can be.
Luckily the awesome Quandl data provider actually provides the information we need. Quandl is a premier source for financial, economic, and alternative datasets, serving investment professionals. Quandl’s platform is used by over 400,000 people, including analysts from the world’s top hedge funds, asset managers and investment banks.
Pulling data from Quandl is very easy using the Quandl
package available on CRAN
Its logic is quite simple:
Quandl::Quandl(code, start_date, end_date)
where code is a unique identifier that can be sourced from the Quandl website.
In particular, we shoudl be grateful to the Bank of France which provides all the interest rate information we need. Going on their page we can in fact find that the code for the 6 months EURIBOR rate is “BOF/QS_D_IEUTIO6M”.
Let’s then see what the output of this very simple function is with our code:
rates <- Quandl::Quandl("BOF/QS_D_IEUTIO6M", start_date = lubridate::dmy("01-01-2018"), end_date = lubridate::dmy("31-12-2018")) rates %>% knitr::kable(caption = "Output from Quandl", "html") %>% kableExtra::kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>% kableExtra::scroll_box(height = "500px")
Date | Value |
---|---|
2018-12-31 | -0.237 |
2018-12-27 | -0.237 |
2018-12-24 | -0.237 |
2018-12-21 | -0.238 |
2018-12-20 | -0.238 |
2018-12-19 | -0.238 |
2018-12-18 | -0.238 |
2018-12-17 | -0.237 |
2018-12-14 | -0.238 |
2018-12-13 | -0.239 |
2018-12-12 | -0.241 |
2018-12-11 | -0.244 |
2018-12-10 | -0.245 |
2018-12-07 | -0.246 |
2018-12-06 | -0.246 |
2018-12-05 | -0.246 |
2018-12-04 | -0.247 |
2018-12-03 | -0.248 |
2018-11-30 | -0.251 |
2018-11-29 | -0.253 |
2018-11-28 | -0.256 |
2018-11-27 | -0.256 |
2018-11-26 | -0.256 |
2018-11-23 | -0.257 |
2018-11-22 | -0.257 |
2018-11-21 | -0.257 |
2018-11-20 | -0.257 |
2018-11-19 | -0.257 |
2018-11-16 | -0.257 |
2018-11-15 | -0.257 |
2018-11-14 | -0.257 |
2018-11-13 | -0.257 |
2018-11-12 | -0.257 |
2018-11-09 | -0.257 |
2018-11-08 | -0.257 |
2018-11-07 | -0.258 |
2018-11-06 | -0.258 |
2018-11-05 | -0.257 |
2018-11-02 | -0.258 |
2018-11-01 | -0.258 |
2018-10-31 | -0.259 |
2018-10-30 | -0.259 |
2018-10-29 | -0.259 |
2018-10-26 | -0.259 |
2018-10-25 | -0.259 |
2018-10-24 | -0.259 |
2018-10-23 | -0.259 |
2018-10-22 | -0.261 |
2018-10-19 | -0.262 |
2018-10-18 | -0.265 |
2018-10-17 | -0.266 |
2018-10-16 | -0.265 |
2018-10-15 | -0.266 |
2018-10-12 | -0.267 |
2018-10-11 | -0.267 |
2018-10-10 | -0.268 |
2018-10-09 | -0.268 |
2018-10-08 | -0.267 |
2018-10-05 | -0.267 |
2018-10-04 | -0.268 |
2018-10-03 | -0.267 |
2018-10-02 | -0.268 |
2018-10-01 | -0.268 |
2018-09-28 | -0.268 |
2018-09-27 | -0.268 |
2018-09-26 | -0.267 |
2018-09-25 | -0.267 |
2018-09-24 | -0.267 |
2018-09-21 | -0.268 |
2018-09-20 | -0.268 |
2018-09-19 | -0.267 |
2018-09-18 | -0.268 |
2018-09-17 | -0.268 |
2018-09-14 | -0.269 |
2018-09-13 | -0.269 |
2018-09-12 | -0.269 |
2018-09-11 | -0.269 |
2018-09-10 | -0.269 |
2018-09-07 | -0.269 |
2018-09-06 | -0.269 |
2018-09-05 | -0.269 |
2018-09-04 | -0.269 |
2018-09-03 | -0.268 |
2018-08-31 | -0.268 |
2018-08-30 | -0.268 |
2018-08-29 | -0.266 |
2018-08-28 | -0.266 |
2018-08-27 | -0.265 |
2018-08-24 | -0.266 |
2018-08-23 | -0.266 |
2018-08-22 | -0.266 |
2018-08-21 | -0.266 |
2018-08-20 | -0.266 |
2018-08-17 | -0.266 |
2018-08-16 | -0.266 |
2018-08-15 | -0.266 |
2018-08-14 | -0.266 |
2018-08-13 | -0.266 |
2018-08-10 | -0.267 |
2018-08-09 | -0.266 |
2018-08-08 | -0.268 |
2018-08-07 | -0.268 |
2018-08-06 | -0.268 |
2018-08-03 | -0.268 |
2018-08-02 | -0.269 |
2018-08-01 | -0.269 |
2018-07-31 | -0.268 |
2018-07-30 | -0.268 |
2018-07-27 | -0.269 |
2018-07-26 | -0.269 |
2018-07-25 | -0.269 |
2018-07-24 | -0.269 |
2018-07-23 | -0.269 |
2018-07-20 | -0.269 |
2018-07-19 | -0.269 |
2018-07-18 | -0.269 |
2018-07-17 | -0.269 |
2018-07-16 | -0.269 |
2018-07-13 | -0.268 |
2018-07-12 | -0.271 |
2018-07-11 | -0.271 |
2018-07-10 | -0.269 |
2018-07-09 | -0.270 |
2018-07-06 | -0.270 |
2018-07-05 | -0.269 |
2018-07-04 | -0.269 |
2018-07-03 | -0.270 |
2018-07-02 | -0.269 |
2018-06-29 | -0.270 |
2018-06-28 | -0.270 |
2018-06-27 | -0.270 |
2018-06-26 | -0.270 |
2018-06-25 | -0.269 |
2018-06-22 | -0.268 |
2018-06-21 | -0.268 |
2018-06-20 | -0.268 |
2018-06-19 | -0.268 |
2018-06-18 | -0.268 |
2018-06-15 | -0.268 |
2018-06-14 | -0.268 |
2018-06-13 | -0.268 |
2018-06-12 | -0.267 |
2018-06-11 | -0.267 |
2018-06-08 | -0.267 |
2018-06-07 | -0.269 |
2018-06-06 | -0.269 |
2018-06-05 | -0.269 |
2018-06-04 | -0.269 |
2018-06-01 | -0.269 |
2018-05-31 | -0.269 |
2018-05-30 | -0.269 |
2018-05-29 | -0.269 |
2018-05-28 | -0.269 |
2018-05-25 | -0.271 |
2018-05-24 | -0.271 |
2018-05-23 | -0.270 |
2018-05-22 | -0.271 |
2018-05-18 | -0.271 |
2018-05-17 | -0.270 |
2018-05-16 | -0.272 |
2018-05-15 | -0.271 |
2018-05-14 | -0.271 |
2018-05-11 | -0.271 |
2018-05-10 | -0.271 |
2018-05-09 | -0.269 |
2018-05-08 | -0.269 |
2018-05-07 | -0.269 |
2018-05-04 | -0.269 |
2018-05-03 | -0.269 |
2018-05-02 | -0.269 |
2018-04-30 | -0.269 |
2018-04-27 | -0.269 |
2018-04-26 | -0.269 |
2018-04-25 | -0.270 |
2018-04-24 | -0.270 |
2018-04-23 | -0.270 |
2018-04-20 | -0.271 |
2018-04-19 | -0.270 |
2018-04-18 | -0.271 |
2018-04-17 | -0.270 |
2018-04-16 | -0.270 |
2018-04-13 | -0.271 |
2018-04-12 | -0.271 |
2018-04-11 | -0.270 |
2018-04-10 | -0.271 |
2018-04-09 | -0.270 |
2018-04-06 | -0.270 |
2018-04-05 | -0.271 |
2018-04-04 | -0.271 |
2018-04-03 | -0.270 |
2018-03-29 | -0.271 |
2018-03-28 | -0.271 |
2018-03-27 | -0.271 |
2018-03-26 | -0.271 |
2018-03-23 | -0.270 |
2018-03-22 | -0.271 |
2018-03-21 | -0.272 |
2018-03-20 | -0.273 |
2018-03-19 | -0.272 |
2018-03-16 | -0.272 |
2018-03-15 | -0.271 |
2018-03-14 | -0.271 |
2018-03-13 | -0.271 |
2018-03-12 | -0.271 |
2018-03-09 | -0.271 |
2018-03-08 | -0.272 |
2018-03-07 | -0.272 |
2018-03-06 | -0.271 |
2018-03-05 | -0.272 |
2018-03-02 | -0.271 |
2018-03-01 | -0.271 |
2018-02-28 | -0.270 |
2018-02-27 | -0.271 |
2018-02-26 | -0.271 |
2018-02-23 | -0.271 |
2018-02-22 | -0.270 |
2018-02-21 | -0.271 |
2018-02-20 | -0.273 |
2018-02-19 | -0.274 |
2018-02-16 | -0.274 |
2018-02-15 | -0.276 |
2018-02-14 | -0.276 |
2018-02-13 | -0.276 |
2018-02-12 | -0.278 |
2018-02-09 | -0.278 |
2018-02-08 | -0.278 |
2018-02-07 | -0.278 |
2018-02-06 | -0.279 |
2018-02-05 | -0.278 |
2018-02-02 | -0.278 |
2018-02-01 | -0.278 |
2018-01-31 | -0.279 |
2018-01-30 | -0.278 |
2018-01-29 | -0.278 |
2018-01-26 | -0.278 |
2018-01-25 | -0.278 |
2018-01-24 | -0.278 |
2018-01-23 | -0.276 |
2018-01-22 | -0.277 |
2018-01-19 | -0.276 |
2018-01-18 | -0.275 |
2018-01-17 | -0.274 |
2018-01-16 | -0.272 |
2018-01-15 | -0.274 |
2018-01-12 | -0.271 |
2018-01-11 | -0.271 |
2018-01-10 | -0.271 |
2018-01-09 | -0.271 |
2018-01-08 | -0.271 |
2018-01-05 | -0.271 |
2018-01-04 | -0.271 |
2018-01-03 | -0.271 |
2018-01-02 | -0.271 |
We now have to incorporate this code into the overall coding strategy described in the previous post
Before we dive in how I coded this, let’s see how I changed the data structure of how a swap is defined:
swap.25y <- list(notional = 10000000, start.date = lubridate::ymd(20070119), maturity.date = lubridate::ymd(20320119), strike = 0.00059820, type = "receiver", time.unit = list(pay = 6, receive = 12), dcc = list(pay = 0, receive = 6), calendar = "TARGET")
This new data structure now allows to define - as it should be - different
characteristics (like coupon frequency or day count convention) for the different
legs (payer
and receiver
) of the swap. In particular, we can specify in the
type
variable whether the swap is a receiver or a payer one.
Let’s look atthe code now. The function that gets modified the most is the
SwapCashflowYFCalculation
which I have re-named as CashFlowPricing
one
which now looks as follows:
CashFlowPricing <- function(today, start.date, maturity.date, type, time.unit, dcc, calendar) { # Part 1: Calculate the whole cashflow dates cashflows <- seq(from = 0, to = (lubridate::year(maturity.date) - lubridate::year(start.date)) * 12, by = time.unit) %>% purrr::map_dbl(~RQuantLib::advance(calendar = calendar, dates = start.date, n = .x, timeUnit = 2, bdc = 1, emr = TRUE)) %>% lubridate::as_date() %>% {if (start.date < today) append(today, .) else .} # Part 2: calculate accrual and rate fixing days accrual.date <- cashflows[today - cashflows > 0] if (!identical(as.double(accrual.date), double(0))) { accrual.date %<>% max() if (stringr::str_detect(type, "floating")) { fixing.date <- accrual.date %>% {RQuantLib::advance(calendar = calendar, dates = ., n = -2, timeUnit = 0, bdc = 1, emr = TRUE)} } else { fixing.date <- NULL } accrual.yf <- accrual.date %>% {RQuantLib::yearFraction(today, ., dcc)} %>% `*`(-1) } else { fixing.date <- NULL accrual.yf <- 0 } # Part 3: Tidy and return the list of relevant dates cashflows %<>% purrr::map_dbl(~RQuantLib::yearFraction(today, .x, dcc)) %>% tibble::tibble(yf = .) %>% dplyr::filter(yf >= 0) return(list(cashflows = cashflows, accrual.yf = accrual.yf, fixing.date = fixing.date)) }
Let’s analyise the code:
Part 1 is actually the core of the code previously described
Part 2 is the new code.
accrual.date
is the date from which the accrual starts to be calculated. This gets converted into a year fraction and saved intoaccrual.yf
. The if statement calculates the date at which the floating EURIBOR rate has to be snapped from Quandl only for the floating rate. This date is stored in thefixing.date
variable and it considers a 2 days lag which is standard for the European market.Part 3 finally converts and returns all the future cashflows in terms of year fraction
You can note that we now calculate the cashflows for the floating leg even if
it will note be used by the OLDParSwapRate
function. This will be needed for
future developments when we will introduce the OIS discounting…(stay tuned!!)
I can now calculate the accrual and for this purpose I developed a brand new function
called CalculateAccrual
CalculateAccrual <- function(swap.dates, leg.type, swap, direction) { # Part 1: calculate the accrual rate if (!is.null(swap.dates$fixing.date)) { rate <- Quandl::Quandl("BOF/QS_D_IEUTIO6M", start_date = swap.dates$fixing.date, end_date = swap.dates$fixing.date) %>% tibble::as_tibble(.) %>% dplyr::select(Value) %>% as.double %>% `/`(100) } else { rate <- swap$strike } # Part 2: Calculate the value of the accrual swap.dates %>% purrr::pluck("accrual.yf") %>% `*`(swap$notional * rate * switch(leg.type, "pay" = -1, "receive" = 1)) }
This is smaller and easier function:
Part 1: for floating legs we use Quandl to download the needed data and extract the rate information. For the fixed one, we just use the strike of the swap.
Part 2: we calculate the actual accrual amount using the classical function
Let’s see the final result on the 25 years’ swap we use as test:
## # A tibble: 1 x 7 ## swap.id clean.mv dirty.mv accrual.pay accrual.receive par pv01 ## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 Swap 25y -881815. -874994. 5441. 1379. 0.00771 -12394.
Let’s compare the result with the Bloomberg screenshot
You can see that we now perfectly price also the receiving accrual and, of course, also the total one.
We finish today’s post by looking at the pricing of the basket. You can see that now the algorithm correctly identifies what are the floating and the fixed legs depending on the type of the swap (ie receiver or payer).
swap.id | clean.mv | dirty.mv | accrual.pay | accrual.receive | par | pv01 |
---|---|---|---|---|---|---|
Swap 25y | -881,814.61 | -874,994.32 | 5,441.11 | 1,379.18 | 0.77% | -12,393.65 |
Swap 30y | 233,691.75 | 123,999.52 | -97,222.22 | -12,470.00 | 1.11% | 20,867.00 |
Swap 10y | 222,083.28 | 236,146.61 | 6,702.22 | 7,361.11 | -0.14% | -5,724.42 |
Swap 2y16y | 360,095.21 | 360,095.21 | -0.00 | 0.00 | 1.18% | -11,163.37 |
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.