QuantBondCurves

library(QuantBondCurves)

The ‘QuantBondCurves’ package offers a range of functions for valuing various asset types and generating financial curves. It covers fixed-coupon assets, floating notes and swaps, with varying payment frequencies. The package also enables the calibration of spot, instantaneous forward and basis curves, making it a powerful tool for accurate and flexible bond valuation and curve generation. The valuation and calibration techniques presented here are consistent with industry standards and incorporates author’s own calculations.

Coupon Dates

The coupon.dates() function allows the user to calculate the coupon payment dates of a given asset, based on its payment frequency. In most cases, this process is straightforward. However, certain scenarios require careful attention to detail. For the former cases, the coupon.dates function starts from maturity and crawls back using the annual payment frequency freq, until analysis.date is reached:

coupon.dates(maturity = "2029-10-01", analysis.date = "2027-08-01", freq = 1, convention = "F")
#> $dates
#> [1] "2027-10-01" "2028-10-01" "2029-10-01"
#> 
#> $effective.dates
#> [1] "2027-10-01" "2028-10-02" "2029-10-01"

As can be seen, the output is comprised of two vectors. Vector $dates represents the dates on which coupon payments are due and $effective.dates adjust each date if it falls on a non-business day. This adjustment is done according to input convention, which offers the most commonly used conventions in the industry. Please refer to the help viewer for details.

The maturity input can either be expressed as a date or as a numeric value in years. An example demonstrating the latter case is provided below:

coupon.dates(maturity = 2, analysis.date = "2023-03-01", freq = 1)
#> $dates
#> [1] "2024-03-01" "2025-03-01"
#> 
#> $effective.dates
#> [1] "2024-03-01" "2025-03-03"

For this example, maturity as a date is calculated from trade date (i.e., going forward two years from “2023-03-01”). In this case, since trade.date input is not introduced, function assumes analysis.date is trade.date.

As evidenced in the previous examples, for straightforward cases, trade.date of the asset is not required.
On the other hand, non trivial cases require both trade.date and maturity as input. An example is with financial assets that possess a distinct coupon period, characterized by a different length compared to the remaining periods. Below we present an example for a bond with a long first coupon:

coupon.dates(maturity = "2025-02-28", analysis.date = "2023-09-29", 
             asset.type = "Fixed Income", freq = 4,
             trade.date = "2023-09-29", coupon.schedule = "LF")
#> $dates
#> [1] "2024-02-29" "2024-05-29" "2024-08-29" "2024-11-29" "2025-02-28"
#> 
#> $effective.dates
#> [1] "2024-02-29" "2024-05-29" "2024-08-29" "2024-11-29" "2025-02-28"

In this example, coupon.schedule is provided to establish where the “outlier” coupon period has to be introduced; see more in the help viewer.

Another detail about this function is the relationship between asset.type and freq. The input asset.type is only required if the asset type is linked to LIBOR (asset.type %in% c("LIBOR","LIBORSwaps")), as in these cases the actual trade.date is two business days after the introduced trade.date. Otherwise, the information in asset.type is only used if freq input is not provided.

Coupons

The coupons() function calculates the cash flows for the remaining life of an asset, following a date payment structure, a given face value and a specified coupon rate.

Just like coupon.dates(), most cases of coupon calculation are straightforward. Once payment dates are known, only an additional crawl back is required in order to establish the length of the upcoming coupon period. For cases when the crawl back process is inadequate (e.g., long first coupon bond), the coupons() function offers the same solution of introducing trade.date of the asset:

coupons(maturity = 4.08, analysis.date = "2021-02-28", coupon.rate = 0.03,
        asset.type = "IBR", daycount = "ACT/360", trade.date = "2020-02-29",
        coupon.schedule = "LF")
#>  [1] 0.007500000 0.007666667 0.007666667 0.007583333 0.007500000 0.007666667
#>  [7] 0.007666667 0.007583333 0.007500000 0.007666667 0.007666667 0.007583333
#> [13] 1.007583333

For this function, asset.type is always required. For asset.type in (“LIBOR”, “IBR”, “LIBORSwaps”,“UVRSwaps”, “IBRSwaps”) freq is assumed depending on the asset, if left blank. For asset.type in (“FixedIncome”,“CCS”) freq is never assumed and must be introduced. Ultimately, when dealing with assets of type “TES”, the coupon period is uniformly set to 1 year, meaning that the value specified for the daycount input is disregarded. The remaining inputs are identical to those on the coupon.dates() function.

Discount Factors

The discount.factors() function allows the user to calculate the discount factors of a given asset. Effective payment dates (dates) are required as input of the function, those can be obtained from the $effective.dates vector with the coupon.dates() function.

Only for discount.factors(), the parameter freq represents the compounding frequency to be used for calculating the discrete discount factors (e.g., freq = 2 refers to semi-annual compounded discount rates). For any other function in the ‘QuantBondCurves’ package, freq represents the annual frequency of coupon payments. Discrete compounded discount rates option can be set with rate.type = 1; alternatively, the user can choose to work with continuously compounded rates (rate.type = 0).

Here is an example for calculating discount factors with monthly compounded discount rates:

discount.factors(dates = c("2020-09-10", "2020-12-10", "2021-03-10"),
                 rates = c(0.01, 0.015, 0.017), analysis.date = "2010-09-01",
                 rate.type = 1, freq = 12)
#> [1] 0.9046521 0.8572606 0.8363377

Bond Valuation

The valuation.bonds() function allows the user to value bonds, fixed income securities, floating notes, spread income assets or fixed legs of swaps. The input coupon.rate can either receive a unique number or a vector whose length matches the length of coupon payments. A unique number should be used to value fixed income assets, fixed legs of interest rate swaps or floating notes with the method of using the floating rate observed on the previous coupon date, common in several jurisdictions:

valuation.bonds(maturity = "2026-06-01", coupon.rate = 0.06, rates = 0.08,
                principal = 1000, analysis.date= "2023-06-01")
#> [1] 948.4012

On the other hand, coupon.rate as a vector can be used to value floating notes with a forward rate for each coupon payment:

valuation.bonds(maturity = "2026-01-05", coupon.rate = c(0.04,0.043,0.05,0.052),
                rates = c(0.06,0.061,0.063,0.067), principal = 1000,
                analysis.date = "2025-01-05", asset.type = "IBR", freq = 4)
#> [1] 981.6549

Bonds price to rate

The bond.price2rate() function is a useful tool that can be used to calculate the internal rate of return (IRR) of a bond, after price as input is introduced:

bond.price2rate(maturity = "2023-01-03", analysis.date = "2021-01-03",
                price = 0.98, coupon.rate = 0.04, principal = 1,
                asset.type = "IBR", freq = 4, daycount = "ACT/365")
#> [1] 0.05153815

Bond Sensitivity

The sens.bonds() function allows the user to calculate the sensitivity of a given bond to interest rates, either by inserting in the price input a bond price (input = price) or an IRR (input = rate). The output represents the percentage change in the price of the bond resulting from a 1 basis point movement in interest rates. Here is an example for the former case:

sens.bonds(input = "price", price = 1.02, maturity = "2023-01-03",
           analysis.date = "2020-01-03", coupon.rate = 0.04,
           principal = 1, asset.type = "FixedIncome", freq = 1, 
           rate.type = 1, daycount = "ACT/365", dirty = 1)
#> [1] 2.796038

For the latter case (input = rate), price can either receive a unique IRR or a rates vector that corresponds to every coupon date:

sens.bonds(input = "rate", price = c(0.04,0.05), maturity = "2025-02-05",
           analysis.date = "2024-06-12", coupon.rate = 0.03, 
           principal = 1, asset.type = "FixedIncome", freq = 2,
           rate.type = 0)
#> [1] 0.6445436

Weighted Average Life

The average.life() function calculates the weighted average life of a given bond. Similar to sens.bonds(), price input can either receive a bond price (input = price) or an IRR (input = rate). Below we denote an example for the latter:

average.life(input = c("rate"), price = c(0.043,0.05), maturity = "2023-01-03", 
             analysis.date = "2021-01-03", coupon.rate = 0.04, principal = 1,
             asset.type = "FixedIncome", freq = 1, rate.type = 0)
#> [1] 1.960876

Curve Calibration

The curve.calibration() function calibrates and returns either a zero coupon bond or an instantaneous forward curve. The calibration process is determined by parameters such as npieces, obj, Weights, nsimul, piece.term, and approximation. Meanwhile, input data is determined by yield.curve, market.assets, noSpots, and freq. Finally, the nodes parameter specifies which terms (in years) are output of the calibrated curve.

Delving into input data, yield.curve is a vector with the IRR’s of the financial assets, with names(yield.curve) containing the maturity for each rate as numeric in years. If noSpots = 3, the first three arguments of yield.curve will be established as spot. The market.assets input is a matrix whose first column is the coupon rate of each asset and the second column contains the maturities of each asset as dates. For cases when the IRR’s of the assets are the same as the coupon rate (e.g., IBR curve), the market.assets input can be left blank. Additionally, for cases where market.assets and yield.curve are required, maturity info can be left blank either in names(yield.curve) or in the second column of market.assets. Moreover, freq parameter can be provided as either a single value that applies to all input assets, or as a vector in which each element corresponds to a specific asset.

Regarding calibration inputs, npieces determines if the calibration is done via a bootstrapping method or by minimization of the residual sum of squares (RSS). If npieces = NULL (its default), then the bootstrapping method will output a number of curve segments equal to the number of financial assets that have been input. Below is an example for an instantaneous forward curve with bootstrapping method for asset.type = TES:

# The `yield.curve` input is created for the IRR's of the market assets.
yield.curve <- c(0.1233,0.1280,0.131,0.1315,0.132,0.1322,0.1325,0.1323,0.1321,0.132)
# The output terms desired are established.
nodes <- seq(0,10, by = 0.001)
# Since for TES, IRR's and coupon rates differ, `market.assets` input is required.
# Below it is constructed.
market.assets       <- matrix(NA,nrow = 10,ncol = 2)
market.assets[1,2]  <- "2020-01-03"
market.assets[2,2]  <- "2021-01-03"
market.assets[3,2]  <- "2022-01-03"
market.assets[4,2]  <- "2023-01-03"
market.assets[5,2]  <- "2024-01-03"
market.assets[6,2]  <- "2025-01-03"
market.assets[7,2]  <- "2026-01-03"
market.assets[8,2]  <- "2027-01-03"
market.assets[9,2]  <- "2028-01-03"
market.assets[10,2] <- "2029-01-03"

market.assets[1,1]  <- 0.1233
market.assets[2,1]  <- 0.1280
market.assets[3,1]  <- 0.131
market.assets[4,1]  <- 0.1315
market.assets[5,1]  <- 0.132
market.assets[6,1]  <- 0.1322
market.assets[7,1]  <- 0.1325
market.assets[8,1]  <- 0.1323
market.assets[9,1]  <- 0.1321
market.assets[10,1] <- 0.132
# Calibration
curve.calibration(yield.curve = yield.curve, market.assets = market.assets,
                  analysis.date = "2019-01-03" , asset.type = "TES", freq = 1,
                  daycount = "ACT/365", fwd = 1, nodes = nodes, approximation = "constant")


Below, two results are presented. The first plot illustrates the results of the previous example, while the second plot showcases the same example but with a spot calibration. (fwd = 0):

 



Alternatively, if the bootstrapping method is not chosen, a curve with a number of pieces specified will be optimized in order to minimize a residual sum of squares (e.g, npieces = 4). This residual can be chosen with obj, either by defining residual as the difference between the market swap price and the estimated swap price with the calibrated curve (obj = "Price") or by defining the residual as the difference between the market IRR of the financial asset and the estimated IRR with the calibrated curve. After the residual is defined, the objective function to minimize is defined as the dot product of Weights vector and squared residuals. By default, each financial asset is assigned equiprobable weights.

A problem that arises in the optimization process is that the objective function isn’t differentiable with respect to the terms of the curve segments. To address this problem, the nsimul parameter can be used to establish the number of simulations to be performed for possible configurations of terms for each curve segment. These simulations are used as initial entries in the optimization process, which can improve the chances of finding an optimal solution. It is suggested to not use more than 20 simulations (nsimul = 20) for instantaneous forward curve due to computational cost, unless time is not an issue.

Alternatively, the user can define a unique term vector for every piece with piece.term. The piece.term parameter in the yield curve construction represents the time to maturity, in years, for a particular curve segment. It specifies the distance in years from the analysis.date to the term where the segment ends.

Finally, either using bootstrapping method or (RSS), the user can define if the curve to calibrate is linear piecewise (approximation = "linear") or constant piecewise (approximation = "constant"). Below is an example for a spot curve calibration using a predefined piece.term instead of optimizing terms with nsimul:

yield.curve <- c(0.103,0.1034,0.1092, 0.1161, 0.1233, 0.1280, 0.1310, 0.1320, 0.1325, 0.1320)
names(yield.curve) <- c(0,0.08,0.25,0.5,1,2,3,5,7,10)
nodes <- seq(0,10, by = 0.001)
# Calibration
curve.calibration (yield.curve = yield.curve, market.assets = NULL, 
                   analysis.date = "2019-01-03", asset.type = "IBRSwaps",
                   freq = 4, npieces = 2, fwd = 0, obj = "Rates",
                   piece.term = 3, nodes = nodes, approximation = "linear")


Below, the result for the previous example and its equivalent with instantaneous forward calibration is plotted:


An important detail for npieces is that for fwd = 0, npieces represents the amount of segments besides all immutable spot segments. Therefore, for the previous example, since for asset.type = "IBRSwaps" the default amount of noSpots is 4, the actual amount of npieces of the curve will be 6 (noSpots+npieces). When fwd = 1, npieces does represent the total amount of segments for the calibrated curve. For instance, in the aforementioned plot, two curves consisting of six pieces were plotted, but with different values of npieces: npieces was set to 2 for fwd = 0, whereas it was set to 6 for fwd = 1.

Finally, is it important to note that piece.term should not include the last piece term, since it is assumed that the last term coincides with the last maturity introduced in yield.curve or market.assets. Therefore, the piece.term vector must always have a length equal to npieces - 1.

Curve Calculation

The curve.calculation() function performs curve.calibration() function for multiple analysis dates. The main difference is that the series input will replace yield.curve. The series parameter represents a matrix in which every column is a yield.curve vector and names(series) contains the maturity for each rate as numeric in years. Additionally, the rownames(series) vector must contain each analysis date desired. On the other hand, market.assets is a matrix just like in curve.calibration(), but now it must contain every single asset of the series matrix.

An additional feature of curve.calculation() is that it allows the user to merge a previous.curve matrix with the output matrix of calibrated curves. Below is an example where the market.assets input is required and previous.curve for analysis dates “2014-01-01” and “2015-01-01” is constant. In this case, since for these two dates curve is already input, the calibration only takes place on “2016-01-01” and “2017-01-01”:

# `previous.curve` input
previous.curve <- matrix(0.04,nrow = 2,ncol = 8)
rownames(previous.curve) <- c("2014-01-01","2015-01-01")
colnames(previous.curve) <- c(0, 0.25, 0.5, 1:5)
# `serie` input
serie <- matrix(NA,nrow = 4,ncol = 6)
rownames(serie) <- c("2014-01-01","2015-01-01","2016-01-01","2017-01-01")
colnames(serie) <- c(0, 0.08333, 0.25, 0.5, 1, 2)
serie[1,1] <- 0.04
serie[1,2] <- 0.05
serie[1,3] <- 0.06
serie[1,4] <- 0.065
serie[1,5] <- 0.07
serie[1,6] <- 0.075
serie[2,1] <- 0.03
serie[2,2] <- 0.04
serie[2,3] <- 0.05
serie[2,4] <- 0.063
serie[2,5] <- 0.074
serie[2,6] <- 0.08
serie[3,1] <- 0.06
serie[3,2] <- 0.065
serie[3,3] <- 0.07
serie[3,4] <- 0.08
serie[3,5] <- 0.084
serie[3,6] <- 0.09
serie[4,1] <- 0.02
serie[4,2] <- 0.03
serie[4,3] <- 0.04
serie[4,4] <- 0.042
serie[4,5] <- 0.045
serie[4,6] <- 0.05
# `market.assets` input
market.assets <- matrix(NA,nrow = 10,ncol = 2)
market.assets[1,1]  <- 0.04
market.assets[2,1]  <- 0.05
market.assets[3,1]  <- 0.06
market.assets[4,1]  <- 0.07
market.assets[5,1]  <- 0.08
market.assets[6,1]  <- 0.09
market.assets[7,1]  <- 0.06
market.assets[8,1]  <- 0.07
market.assets[9,1]  <- 0.075
market.assets[10,1] <- 0.07
market.assets[1,2]  <- "2016-01-01"
market.assets[2,2]  <- "2016-02-01"
market.assets[3,2]  <- "2016-04-01"
market.assets[4,2]  <- "2016-07-01"
market.assets[5,2]  <- "2017-01-01"
market.assets[6,2]  <- "2017-02-01"
market.assets[7,2]  <- "2017-04-01"
market.assets[8,2]  <- "2017-07-01"
market.assets[9,2]  <- "2018-01-01"
market.assets[10,2] <- "2019-01-01"
# Calculation
curve.calculation(serie = serie, market.assets = market.assets, noSpots = 1,  
                  previous.curve = previous.curve, asset.type = "TES",
                  freq = 1, rate.type = 1, fwd = 0,
                  nodes = c(0, 0.25, 0.5, 1:5), approximation = "linear")
#>               0       0.25       0.5          1          2          3
#> 2014-01-01 0.04 0.04000000 0.0400000 0.04000000 0.04000000 0.04000000
#> 2015-01-01 0.04 0.04000000 0.0400000 0.04000000 0.04000000 0.04000000
#> 2016-01-01 0.06 0.06983019 0.0797851 0.08397703 0.09021406 0.09023113
#> 2017-01-01 0.02 0.04093220 0.0427312 0.04512698 0.05024461 0.05024461
#>                     4          5
#> 2014-01-01 0.04000000 0.04000000
#> 2015-01-01 0.04000000 0.04000000
#> 2016-01-01 0.09023113 0.09023113
#> 2017-01-01 0.05024461 0.05024461

Spot to Forward

The spot2forward() function allows the user to transform a spot curve into a forward instantaneous curve. Every node introduced in the spot input is transformed into a forward node. The maturity of each rate must be introduced in names(spot), as numeric in years (counting from analysis.date). Finally, approximation refers to the approximation of the initial spot curve. Therefore, when transforming a spot piecewise linear curve into an instantaneous forward curve, it is necessary to define approximation = "linear". Below is an example:

# Inputs for calibration of spot curve
yield.curve <- c(0.015,0.0175, 0.0225, 0.0275, 0.0325, 0.0375,0.04,0.0425,0.045,0.0475,0.05)
names(yield.curve) <- c(0.5,1,2,3,4,5,6,7,8,9,10)
nodes <- seq(0,10,0.001)
# Calibration
spot <- curve.calibration (yield.curve = yield.curve, market.assets = NULL,
                           analysis.date = "2019-01-03" , asset.type = "IBRSwaps",
                           freq = 4, rate.type = 0, fwd = 0, npieces = NULL, 
                           nodes = nodes, approximation = "linear")
# Spot to Forward
dates <- names(spot)
spot2forward(dates, spot, approximation = "linear")

Forward to Spot

The fwd2spot() function allows the user to transform a forward instantaneous curve into a spot curve. Every node introduced in fwd input is transformed into a spot node. Maturity of each rate as numeric in years must be introduced in names(fwd). Just like spot2forward(), approximation refers to the approximation of the initial input forward curve. Below is an example:

# Inputs for calibration of forward curve
yield.curve <- c(0.015,0.0175, 0.0225, 0.0275, 0.0325, 0.0375,0.04,0.0425,0.045,0.0475,0.05)
names(yield.curve) <- c(0.5,1,2,3,4,5,6,7,8,9,10)
nodes <- seq(0,10,0.001)
# Calibration
fwd <- curve.calibration (yield.curve = yield.curve, market.assets = NULL,
                          analysis.date = "2019-01-03", asset.type = "LIBORSwaps",
                          freq = 4, rate.type = 0, daycount = "ACT/365", 
                          npieces = NULL, fwd = 1, nodes = nodes, 
                          approximation = "linear")
# Forward to Spot
dates <- names(fwd)
fwd2spot(dates, fwd, approximation = "linear")

Basis curve

The basis.curve() function calibrates a “discount basis rate” curve according to data of cross currency swaps. It follows a similar structure with the same features as curve.calibration(). The main difference is that the data input is swaps, a matrix that contains relevant information for every Cross Currency Swap (CCS). In detail, each row represents a swap and the columns represent, respectively: maturity, legs, coupon rate of local leg, coupon rate of foreign leg, spread of local leg, spread of variable leg, principal of local leg and principal of variable leg. Columns in swaps can be placed in any order, but every column must be labeled with the following labels: colnames(swaps) <- c("Mat" ,"Legs", "C1" , "C2", "spread1", "spread2", "prin1", "prin2").

Basis curve can be calibrated with any type of Cross Currency Swap, either fixed local leg vs. fixed foreign leg (Legs = FF), fixed local leg vs. variable foreign leg (Legs = FV), variable local leg vs. a fixed foreign leg (Legs = VF) or variable local leg vs. variable foreign leg (Legs = VV).

Additionally, there is a new parameter: ex.rate. It represents the exchange rate between the two currencies involved in the CCS on analysis.date. In the next example, both new inputs are created:

ex.rate <- 4814
swaps <- rbind(c("2024-03-01", "FF", 0.07 , 0.0325, NA   , NA    , 2000 * ex.rate, 2000),
               c("2025-03-01", "VV", NA   , NA    , 0.015, 0.0175, 2000 * ex.rate, 2000),
               c("2026-03-01", "FF", 0.075, 0.03  , NA   ,  NA   , 5000000, 5000000 / ex.rate),
               c("2027-03-01", "VV", NA   , NA    , 0.01 , 0.015 , 5000000, 5000000 / ex.rate),
               c("2028-03-01", "FF", 0.08 ,0.035  , NA   , NA    , 3000000, 3000000 / ex.rate),
               c("2029-03-01", "VV", NA   , NA    , 0.01 , 0.0125, 3000000, 3000000 / ex.rate))
colnames(swaps) <- c("Mat"  ,"Legs", "C1" , "C2", "spread1", "spread2", "prin1", "prin2")

Below, an example of piecewise linear basis curve calibration using the RSS method is performed. Additionally, the constant calibration alternative is added to the plot:

# Inputs for calibration of spot curve
yield.curve <- c(0.015,0.0175, 0.0225, 0.0275, 0.0325, 0.0375,0.04,0.0425,0.045,0.0475,0.05)
names(yield.curve) <- c(0.5,1,2,3,4,5,6,7,8,9,10)
nodes <- seq(0,10,0.001)
# Calibration of local spot curve
rates <- curve.calibration (yield.curve = yield.curve, market.assets = NULL,
                           analysis.date = "2019-01-03" , asset.type = "IBRSwaps", 
                           freq = 4, rate.type = 0, fwd = 0, npieces = NULL, 
                           obj = "Price", nodes = nodes, approximation = "linear")
# Calibration of Basis Curve
nodes <- seq(0,10,0.001)
basis.curve(swaps = swaps, ex.rate = 4814, analysis.date = "2023-03-01", 
            rates = rates, rates2 = rates / 4, freq = c(2,2,2,2,1,1),
            rate.type = 1, npieces = 4, obj = "Price", Weights = NULL, 
            nsimul = 10, nodes = nodes, approximation = "linear")

Swaps Valuation

The valuation.swaps() function offers the possibility of valuing an Interest Rate Swap (IRS) or a Cross Currency Swap (CCS). For the former case, coupon.rate input is used to value the fixed leg, spread is an optional input for the variable leg and rates vector is used for discounting the cashflows. If analysis.date doesn’t belong to a coupon date, float.rate must be introduced and represents the variable rate observed on the previous coupon date.

In the next example, the analysis.date is inside the coupon dates. Therefore, even if float.rate is introduced, its effect on output is null since the function automatically establishes that float.rate is equivalent to the first entry of rates input:

valuation.swaps(maturity = "2026-07-01", analysis.date = "2023-01-01",
                asset.type = "IBRSwaps", freq = 4, coupon.rate = 0.04, 
                rates = rep(0.04,14), float.rate = 500)
#> [1] -0.001966146

For cases where analysis.date doesn’t belong to a coupon date, the float.rate input is necessary to value the upcoming coupon of variable leg:

valuation.swaps(maturity = "2026-07-01", analysis.date = "2023-02-01",
                asset.type = "IBRSwaps", freq = 4, coupon.rate = 0.04,
                rates = rep(0.04,14), float.rate = 0.042)
#> [1] -0.001482435

In the context of Cross Currency Swaps (CCS), coupon.rate is used for valuation of local fixed legs. The spread parameter represents the spread for the local variable leg, while rates are the discount rates for the local leg. The float.rate parameter denotes the variable local rate observed on the previous coupon date and just like in the IRS case, it is only required if local leg is variable and analysis.date doesn’t belong to a coupon date. In parallel, coupon.rate2, spread2, rates2 and float.rate2 represent the same attributes but for foreign legs.

rates2 input is only necessary if the foreign leg is a variable leg, in order to transform the variable into a fixed foreign leg. After which, basis.rates input is used as the discount foreign rates to value the fixed foreign leg. For rates, rates2 and basis.rates, curve.calculation() and basis.curve() can be used. These three inputs can either be a vector with a rate for every coupon date, or a curve that contains nodes with, at least, three decimals:

# Curve Calibration for `rates` input
 yield.curve <- c(0.103,0.1034,0.1092, 0.1161, 0.1233, 0.1280, 0.1310, 0.1320, 0.1325)
 names(yield.curve) <- c(0,0.08,0.25,0.5,1,2,3,5,6)
 nodes <- seq(0, 10, by = 0.001) # Our curve has nodes with three decimals.
 rates <- curve.calibration (yield.curve = yield.curve, market.assets = NULL,
                             analysis.date = "2023-03-01", asset.type = "IBRSwaps",
                             freq = 4, rate.type = 0, daycount = "ACT/365", fwd = 0, 
                             npieces = NULL, obj = "Rates", nsimul = nsimul, 
                             nodes = nodes,  approximation = "constant")
# Curve Calibration for `basis.rates` input
nodes  <- seq(0, 10, by = 0.001)
rates2 <- rates/4 # It is assumed foreign curve is proportional to local spot curve.
# Swaps input for calibration
ex.rate <- 4814
swaps <- rbind(c("2024-03-01", "FF", 0.07 , 0.0325, NA   , NA    , 2000 * ex.rate, 2000),
               c("2025-03-01", "VV", NA   , NA    , 0.015, 0.0175, 2000 * ex.rate, 2000),
               c("2026-03-01", "FF", 0.075, 0.03  , NA   ,  NA   , 5000000, 5000000 / ex.rate),
               c("2027-03-01", "VV", NA   , NA    , 0.01 , 0.015 , 5000000, 5000000 / ex.rate),
               c("2028-03-01", "FF", 0.08 ,0.035  , NA   , NA    , 3000000, 3000000 / ex.rate),
               c("2029-03-01", "VV", NA   , NA    , 0.01 , 0.0125, 3000000, 3000000 / ex.rate))
colnames(swaps) <- c("Mat"  ,"Legs", "C1" , "C2", "spread1", "spread2", "prin1", "prin2")
# Calibration
basis.rates <- basis.curve(swaps, ex.rate = 4814, analysis.date = "2023-03-01",
                           rates = rates, rates2 = rates2, freq = c(2,2,2,2,1,1), 
                           rate.type = 1, npieces = NULL, obj = "Price", 
                           Weights = NULL, nodes = nodes, approximation = "linear")

Below, an example of a CCS with two variable legs is shown; hence, all three input curves (rates, rates2, basis.rates) are required:

# Valuation
valuation.swaps (maturity = "2024-03-01", analysis.date = "2023-03-01", asset.type = "CCS",
                 freq = 2, coupon.rate = NA, rates = rates, float.rate = NULL, spread = 0.015,
                 principal = 2000 * ex.rate, Legs = "VV", ex.rate = ex.rate, 
                 basis.rates = basis.rates, coupon.rate2 = NA, rates2 = rates2, 
                 float.rate2 = NULL, spread2 = 0.0175, principal2 = 2000, rate.type = 0, 
                 daycount = "ACT/365", loc = "BOG")
#> [1] 442310.2