Multi Currency Swap Pricing with SwapPricer

[This article was first published on R on The CuRious Financial Risk ManageR, 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.

I have released a new version of the package SwapPricer on GitHub here.

As we are now at version 1.0.1 the toolbox is able to price using just a one-curve framework but is able to price multiple currencies (ie. CHF, EUR, GBP, JPY and USD) and any convention in terms of coupon frequency, day count convention.

We are working to introduce OIS Discounting in the next releases

SwapPricer: instructions for use

To run a multi-currency swap portfolio valuation you need the following three “ingredients”:

  1. A table with the characteristics of the swap, like the following one

    Table 1: Swap portfolio
    ID currency notional start.date maturity.date strike type standard time.unit.pay time.unit.receive dcc.pay dcc.receive
    Swap 25y EUR 10,000,000 19/01/2007 19/01/2032 0.06% receiver TRUE NA NA NA NA
    Swap 30y GBP 1,000,000 24/04/2012 24/04/2042 1.00% payer TRUE NA NA NA NA
    Swap 10y USD 2,000,000 21/02/2012 21/02/2022 0.25% receiver TRUE NA NA NA NA
    Swap 2y16y GBP 7,500,000 14/04/2021 14/04/2037 1.50% receiver TRUE NA NA NA NA
    Swap non standard EUR 15,000,000 26/05/2014 26/05/2039 2.00% payer FALSE 12 3 act/365 act/365
    Swap 10y semi fixed EUR 10,000,000 26/05/2014 26/05/2024 0.10% payer FALSE 6 6 act/365 act/365
    Swap 30y quarter floating GBP 1,000,000 24/04/2012 24/04/2042 2.00% receiver FALSE 3 12 act/360 act/365
    Swap 10y irregular USD 2,000,000 21/02/2012 21/02/2022 0.25% receiver FALSE 6 12 act/365 act/365
    Swap 2y16y EUR 7,500,000 14/04/2021 14/04/2037 1.50% payer FALSE 12 12 act/365 act/360

    These are the columns that have to be in the

    • ID (character) is a custom ID for the swap
    • currency (character) as per the ISO 4217 standard
    • notional (numeric)
    • start.date (Date)
    • maturity.date (Date)
    • strike (numeric)
    • type (string) either payer or receiver. It is not case-sensitive.
    • standard (logical)

    Only if the field standard is set to FALSE then we need four additional fields.

    An example of portfolio is provided with the package and is called swap.basket.

    • time.unit.pay and time.unit.receive (integer) are the number of months for the frequency of the leg (ie. monthly would have a time.unit of 1, quarterly of 3, semiannual of 6 and annual of 12)
    • dcc.pay and dc..receive (character) as per the fmdates helper
  2. The date at which the swaps are being priced

  3. As many interest rate lists as per the currencies in the swap portfolio. The list is made of a string with the code of the currency and a with a tibble with the discounting factor curve with two columns: Dates and Discount Factors (df). Here is the structure of interest rate object:

    str(SwapPricer::EUR.curves)
    ## List of 2
    ##  $ currency: chr "EUR"
    ##  $ discount:Classes 'tbl_df', 'tbl' and 'data.frame':    26 obs. of  2 variables:
    ##   ..$ Date: Date[1:26], format: "2019-04-15" ...
    ##   ..$ df  : num [1:26] 1 1 1 1 1 ...

    Where the discount tibble of the list has the same characteristics of the df.table object described in this post For your reference, I provided three curve objects with the package: EUR.curves, GBP.curves and USD.curves

Now we can price our test multi-currency and non-standard swap portfolio by simply running SwapPortfolioPricing function with the three items in the order of description and appending as many curves as the number of currencies in the book.

A sample code is shown here:

today <- lubridate::ymd(20190414)

results <- SwapPricer::SwapPortfolioPricing(SwapPricer::swap.basket, 
                                            today, 
                                            SwapPricer::EUR.curves, 
                                            SwapPricer::GBP.curves, 
                                            SwapPricer::USD.curves)

and these are the results in table format:

Table 2: Pricing results
swap.id currency clean.mv dirty.mv accrual.pay accrual.receive par pv01
Swap 25y EUR -881,814.61 -874,994.31 5,441.11 1,379.18 0.77% -12,393.65
Swap 30y GBP 105,100.47 104,668.39 -4,712.33 4,280.26 1.54% 1,948.54
Swap 10y USD -119,319.88 -126,214.04 -7,630.28 736.11 2.43% -548.15
Swap 2y16y GBP -94,850.43 -94,850.43 -0.00 0.00 1.59% -10,393.05
Swap non standard EUR -2,591,763.24 -2,861,567.21 -263,835.62 -5,968.36 1.07% 27,917.04
Swap 10y semi fixed EUR -16,340.53 -29,897.79 -3,808.22 -9,749.04 0.07% 5,132.42
Swap 30y quarter floating GBP 88,255.22 105,650.31 -2,056.96 19,452.05 1.55% -1,941.05
Swap 10y irregular USD -119,595.14 -126,677.82 -7,795.01 712.33 2.44% -546.46
Swap 2y16y EUR -361,098.10 -361,098.10 -0.00 0.00 1.18% 11,170.90

So long RQuantLib

Apart from the new definition of the curve object, most of the changes have happened “behind the scenes”. One thing that I had to do to make the deployment easier on all the OSs was to remove any reference to Quantlib. I am a huge fan of the RQuantLib package, but I realised I was using it just for adjusting dates for holidays. So I decided to take a different approach:

  1. I downloaded all the holidays and saved it as internal objects in my package
  2. I created a very simple routine to adjust for holidays, as in the following piece of code:
AdvanceDate <- function(dates, currency, eom.check) {
  holiday <- holidays[[currency]]
  check <- TRUE %in% ((dates %in% holiday) |
                        (weekdays(dates) %in% "Saturday") |
                        (weekdays(dates) %in% "Sunday"))
  if (check) {
    repeat {
      if (eom.check) {
        dates <- dplyr::case_when((dates %in% holiday) ~ dates - 1,
                                      (weekdays(dates) %in% "Saturday") ~ dates - 1,
                                      (weekdays(dates) %in% "Sunday") ~ dates - 2,
                                      TRUE ~ dates)
      } else {
        dates <- dplyr::case_when((dates %in% holiday) ~ dates + 1,
                                      (weekdays(dates) %in% "Saturday") ~ dates + 2,
                                      (weekdays(dates) %in% "Sunday") ~ dates + 1,
                                      TRUE ~ dates)
      }
      check <- TRUE %in% ((dates %in% holiday) |
                            (weekdays(dates) %in% "Saturday") |
                            (weekdays(dates) %in% "Sunday"))
      if (!check) {
        return(dates)
      }
    }
  } else {
    return(dates)
  }
}

You can see that adjusting for holidays is a recursive exercise: let’s assume we have Easter Sunday in the cashflow schedule. It automatically gets adjusted to Monday, but isince Easter Monday is also an holiday, the function will run a second time to further advance to Tuesday.

The performance of the function is guaranteed by the fact that the dplyr::case_when function is vectorised. In fact, the overall procedure takes almost the same time to run as with the RQuantLib function.

To leave a comment for the author, please follow the link and comment on their blog: R on The CuRious Financial Risk ManageR.

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.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)