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 calculate the key rate durations (KRD). Ho (1992) introduces KRD to measure non-parallel movements of the yield curve that the existing duration measures can not describe as these are defined under the assumption of a parallel shift of the yield curve.

# Key Rate Duration using R code

Ho (1992) introduced the concept of the key rate duration (hereafter KRD), which is a new measure of interest rate risk exposure. KRD is a vector of the price sensitivity of a bond to each key rate change. The sum of the key rate durations is identical to the effective duration. The sum of the product of key rate durations and each yield changes in key rates (with negative sign) is the bond price change due to the non-parallel shift of the yield curve.

### Key Rate Shifts

Unlike the traditional effective duration, KRD is calculated not from a par yield curve but from a zero curve (spot rate) so that the bump and reprice are performed on selected spot rates (key rates). In other words, individual effective durations are calculated by shift of each spot rate at each key maturity and the sum of them is the KRD.

Formally, ith key rate shift is defined to be zero shift for maturities shorter than (i-1)th key rate and longer than (i+1)th key rate. Interestingly, the shift between these two adjacent key rates is defined by the linear interpolation with the peak at the ith key rate.

As can be seen in the above figure, yellow colored maturity is assumed to be the selected maturities of key rates. Ho (1992) suggests the size of shift as 10 bp but we use 1%(=100bp) simply for illustration purpose. A shift of one key rate by 1% affects its neighborhood of that key rate with linear interpolation. Rates outside the range of selected key maturities are set simply by the nearest key rate.

In particular, the light blue colored line (bottom) illustrates the shifts at all maturities, which resulted from 60M key rate’s change. In this case, all the key rates except 60M are not affected but two adjacent rates at 54M and 66M are affected linearly.

The ith shifted yield curve is the sum of the initial yield curve (blue colored solid line) and its shifts at all maturities, which resulted from the change of ith key rate. When we add up all shifted changes together on the initial yield curve, we can get the parallel shifted curve (red colored solid line).

### Key Rate Durations

By shifting each key rates, we can get N shifted zero curves (N is the number of key rates). Using these N shifted zero curves, each effective duration ($$KRD_i, i=1,…,N$$) is calculated with the initial zero curve.

\begin{align} KRD_i = \frac{P_{up} – P_{down}}{2 \times P_0 \times \Delta s(t_i)} \end{align}
Here, $$s(t_i)$$ is a spot rate at time $$t_i$$

\begin{align} \frac{\Delta P_i}{P} = -KRD_i \times \Delta s(t_i) \end{align}
By definition, the sum of KRDs is the effective duration of a bond.

\begin{align} D = \sum_{i=1}^N KRD_i \end{align}
The shift of the yield curve can be approximated by the sum of all the key rate shifts.

\begin{align} \frac{\Delta P}{P} = – \sum_{i=1}^N KRD_i \times \Delta s(t_i) \end{align}

### Key Rate Shifts in R code

As will be shown in R code, it is worth noting that a vector of shifts due to a key rate movement is appended horizontally with the name of its key rates as follows.

Therefore, each shifted zero curve can be calculated by simply summing the initial zero curve and the corresponding shift vector.

### R code

R code below implements the above explanations. After calculating we compare the sum of key rate durations with the traditional effective duration. When we use not 0.01 (1%) but 0.0001 (1bp) as a small change in a key rate, two duration measures are nearly the same in the perspective of numerical calculation.

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 #========================================================## Quantitative ALM, Financial Econometrics & Derivatives # ML/DL using R, Python, Tensorflow by Sang-Heon Lee ## https://kiandlee.blogspot.com#——————————————————–## Key Rate Durations#========================================================# graphics.off(); rm(list = ls()) #=======================================================# read data#=======================================================str.zero <– “matm zero             12    0.022             24    0.030             36    0.040             48    0.046             60    0.050             72    0.052             84    0.053             96    0.053            108    0.053            120    0.054″ df <– read.table(text = str.zero, header = TRUE) v.key_matm <– df$matm # maturities for key ratesnkey <– length(v.key_matm) # linear interpolation of zero ratesf_interpol <– approxfun(df$matm, df$zero, rule = 2) # fill rates between key rates by 6M tenormatm = seq(6,120,6)df.sa <– data.frame(matm = matm)nobs <– length(matm) df.sa$zero <– f_interpol(matm) # 10Y YTMYTM10 <– 0.0527780789520333CPN10 <– YTM10 # 10Y coupon = par yield rate  #=======================================================# read data#======================================================= # rate shift : 0.0001 is preferabledr = 0.0001 # make a table of dr for each key ratesfor(n in 1:nkey) {    df.sa <– cbind(df.sa, NA)    colnames(df.sa)[2+n] <– as.character(v.key_matm[n])        key_matm <– v.key_matm[n]        # set dr to the same key maturity    # set  0 to other key rates    # set NA to rates between key rates    for(i in 1:nobs) {                matmi <– df.sa$matm[i] if(matmi == key_matm) { df.sa[i,2+n] <– dr } else if (matmi %in% v.key_matm){ df.sa[i,2+n] <– 0 } } # rates between key rates is linearly interpolated f_interpol <– approxfun(matm, df.sa[,2+n], rule = 2, na.rm = TRUE) df.sa[,2+n] <– f_interpol(matm)} # p0zero <– df.sa$zeroP0 <– sum((YTM10/2)/(1+zero/2)^(matm/6)) +            1/(1+zero[nobs]/2)^(matm[nobs]/6) # all the key rate shiftszerou <– zero + rowSums(df.sa[,1:nkey+2])zerod <– zero – rowSums(df.sa[,1:nkey+2]) # effective duration : traditionalPu <– sum((YTM10/2)/(1+zerou/2)^(matm/6)) +       1/(1+zerou[nobs]/2)^(matm[nobs]/6)Pd <– sum((YTM10/2)/(1+zerod/2)^(matm/6)) +       1/(1+zerod[nobs]/2)^(matm[nobs]/6)ED <– (Pd – Pu)/(2*P0*dr) # Key rate duationsdf.KRD <– data.frame(matm = df$matm, ED = NA)for(n in 1:nkey) { # key rate shifts zerou <– zero + df.sa[,2+n] zerod <– zero – df.sa[,2+n] # effective duration : KRD Pu <– sum((YTM10/2)/(1+zerou/2)^(matm/6)) + 1/(1+zerou[nobs]/2)^(matm[nobs]/6) Pd <– sum((YTM10/2)/(1+zerod/2)^(matm/6)) + 1/(1+zerod[nobs]/2)^(matm[nobs]/6) df.KRD[n,2] <– (Pd – Pu)/(2*P0*dr)}df.KRD # check if sum of KRDs is equal to effective durationdrsum(df.KRD$ED)ED Colored by Color Scripter cs

Running this R code, we can get the following results which show that calculation of key rate durations is proper when we use 1bp as the size of shift. Of course, it depends on which problem is investigated.

### Concluding Remarks

In this post we implemented the key rate durations which is invented by Ho (1992). This can be used in the case of the non-parallel shifts of yield curve and has broad application: replicating portfolio of option embedded bonds, managing fixed-income portfolio, analyzing complex interest rate contingent claims, and so on.

### Reference

Ho, T.S.Y. (1992), “Key Rate Durations: Measures of Interest Rate Risks,” The Journal of Fixed Income 2-2, pp.29-44. $$\blacksquare$$