Nelson-Siegel-Svensson Yield Curve model 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 introduces Nelson-Siegel-Svensson (NSS) yield curve model which is an extension of Nelson-Siegel (NS) model with an additional curvature factor. It aims to fit longer term maturities well. Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Nelson-Siegel-Svensson model
Svensson (1995) suggests an extended NS model which is typically called NSS model. Many central bank use NSS model and Gürkaynak et al. (2007) also use it to fit U.S. Treasury yield curve. The reason why NSS model is widely used is evident. As can be seen the following dynamic figure, It fits yields at long-term maturities well.
For your information, this blog has dealt with Nelson-Siegel model as follows.
Nelson-Siegel-Svensson model
Nelson-Siegel model is a non-linear least square problem with 6 parameters with some inequality constraints.
\[\begin{align} y(\tau) &= \beta_1 + \beta_2 \left( \frac{1-e^{- \tau \lambda_1 }}{\tau \lambda_1 }\right) \\ &+ \beta_3 \left(\frac{1-e^{- \tau \lambda_1 }}{\tau \lambda_1 }-e^{- \tau \lambda_1 }\right) \\ &+ \beta_4 \left(\frac{1-e^{- \tau \lambda_2 }}{\tau \lambda_2 }-e^{- \tau \lambda_2 }\right) \end{align}\] \[\begin{align} \beta_1 > 0, \beta_1 + \beta_2 >0 \\ \lambda_1 > \lambda_2 > 0 \end{align}\]
Here, \(\tau\) is a maturity and \(y(\tau)\) is a continuously compounded spot rate with \(\tau\) maturity. \(\beta_1, \beta_2, \beta_3\, \beta_4\) are coefficient parameters. \(\lambda_1\) and \(\lambda_2 \) are the decay parameters.
\(\lambda_1\) and \(\lambda_2\) determine the shapes of slope and two curvature factor loadings as follows.
In the above figure, I use \(\lambda_1 = 0.0609\) and \(\lambda_2 = 0.01 \), which represent the maximum of curvature loadings are attained at nearly 30-month and 180-month respectively. Smaller \(\lambda_2\) fits the yield curve at longer maturities well but it lowers the interpretability of the level factor. For this reason too small \(\lambda_2\) is not appropriate and is needed to be constrained by some reasonable upper and lower bounds.
Restrictions on \(\lambda_1\) and \(\lambda_2\)
The most difficult part of the estimation of NSS model is how to choose \(\lambda_1\) and \(\lambda_2\). Without suitable restrictions on these two parameters, estimates of these parameters exhibit too excessive or erratic. It is typical to determine \(\lambda_1\) from ranges of medium-term maturities and \(\lambda_2\) from ranges of long-term maturities. I set two ranges of maturities as 1 ~ 5 years and 6 ~ 20 year for example.
However, reasonable constrains on two decay parameters are dependent on which data is used.
R code
The following R code estimates parameters of NSS model with yield curves at four dates and compares these results with that of NS model.
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 | #========================================================# # Quantitative ALM, Financial Econometrics & Derivatives # ML/DL using R, Python, Tensorflow by Sang-Heon Lee # # https://kiandlee.blogspot.com #——————————————————–# # Estimation of Nelson-Siegel-Svensson model #========================================================# graphics.off(); rm(list = ls()) library(alabama) #———————————————– # Nelson-Siegel-Svensson function #———————————————– # NSS factor loading nss_loading <– function(la,m) { la1 <– la[1]; la2 <– la[2] C <– cbind( rep(1,length(m)), (1–exp(–la1*m))/(la1*m), (1–exp(–la1*m))/(la1*m)–exp(–la1*m), (1–exp(–la2*m))/(la2*m)–exp(–la2*m)) return(C) } # fitting function nss_fit <– function(para, m) { beta <– para[1:4] return(nss_loading(para[5:6],m)%*%beta) } # objective function nss_objf <– function(para, y, m) { return(sqrt(mean((y – nss_fit(para, m))^2))) } # constrOptim.nl constraint function ( > ) nss_constf <– function(para, y, m) { beta <– para[1:4] la1 <– para[5]; la2 <– para[6] h <– rep(NA, 6) # b1 > 0, b1+b2 > 0 h[1] <– beta[1] h[2] <– beta[1]+beta[2] # 0.15 > la1 > 0.03 h[3] <– 0.15–la1 h[4] <– la1–0.03 # 0.025 > la2 > 0.0075 h[5] <– 0.025–la2 h[6] <– la2–0.0075 return(h) } #=========================================================== # 1. Read data #=========================================================== # Estimated parameters of Nelson-Siegel (Benchmark) str.ns_est<– “ beta1 beta2 beta3 lambda rmse 7.949446 0.2933681 -0.08749462 0.11555381 0.07665367 7.052864 -1.5778886 -0.42253575 0.01823114 0.06432032 5.586903 -0.5600670 -1.43976821 0.03357762 0.06423196 6.011739 0.3470154 -1.00018107 0.04565435 0.09314726″ m.ns_est <– read.table(text = str.ns_est, header=TRUE) str.zero <– “ mat 19890630 19950929 19980831 20000929 3 8.171964 5.413123 4.931036 6.180960 6 8.143305 5.509881 4.949141 6.207158 9 8.123782 5.592753 4.962873 6.204367 12 8.111922 5.654558 4.962513 6.176521 24 7.974091 5.782513 4.824109 5.932045 36 7.943757 5.841313 4.868401 5.862433 48 7.959505 5.920732 4.888494 5.825789 60 7.942511 5.967207 4.900205 5.799035 72 8.000704 6.013260 4.980854 5.826354 84 8.010956 6.055806 4.977891 5.847383 96 8.020059 6.151399 5.007556 5.859937 108 8.027901 6.224049 5.054961 5.875451 120 8.037343 6.265079 5.100250 5.912506 144 8.063573 6.364383 5.200412 6.002328 180 8.052892 6.512078 5.347487 6.093959 240 7.971860 6.718515 5.482335 6.091155 300 7.861711 6.789321 5.422557 5.972525 360 7.721083 6.599990 5.246325 5.697709″ df <– read.table(text = str.zero, header=TRUE) m <– df$mat; nmat <– length(m) #=========================================================== # 2. Parameter estimation #=========================================================== ctrl.optim <– list(maxit=50000, trace=0, reltol = 1e–10, abstol = 1e–10) ctrl.outer <– list(eps = 1e–10, itmax = 50000, method = “Nelder-Mead”, NMinit = TRUE) # output container m.nss_est <– matrix(NA, 4, 7) m.nss_fit <– matrix(NA, length(m), 4) # Estimation of Nelson-Siegel-Svensson for(i in 1:4) { y <– df[,1+i] x_init <– c(y[nmat], y[1]–y[nmat], 2*y[6] –y[1]–y[nmat], 2*y[15]–y[1]–y[nmat], 0.0609, 0.01) opt <– constrOptim.nl(y = y, m = m, par=x_init, fn=nss_objf, hin=nss_constf, control.optim =ctrl.optim, control.outer=ctrl.outer) m.nss_est[i,] <– c(opt$par, opt$value) m.nss_fit[,i] <– nss_fit(opt$par, m) colnames(m.nss_est) <– c(“beta1”, “beta2”, “beta3”, “beta4”, “lambda1”, “lambda2”,“rmse”) } colnames(m.nss_fit) <– paste0(“fitting-“,1:4) #======================================================= # 3. Estimation results #======================================================= # NSS factor loading mi <– 1:360 x11(width = 16/2.5, height = 5); matplot(mi, nss_loading(c(0.0609, 0.01),mi), type = “l”, lwd=6, xlab = “Maturity (month)”, ylab = “Factor loading”) legend(“right”, legend=c(“Level”, “Slope”, “Curvature1”, “Curvature2”), pch = c(15,16), border=“white”, box.lty=0, cex=1, col = c(“black”,“#DF536B”, “#61D04F”, “#2297E6”)) # Parameter estimates m.ns_est m.nss_est # data and fitted yields round(cbind(df[,–1], m.nss_fit),3) | cs |
The following estimation results show that NSS model provides good in-sample fit than NS model except the second date. This is related to the restrictions on the decay parameters. When these constraints are relaxed, the results will changed. I expect that.
Four selected dates, fitting results of NSS model show relative good fitting results. Instead, we can find that level factor (\(\beta_1\)) is affected by the introduction of the second curvature factor (\(\beta_4\)). In other words, there is some distinct difference in level factors between NS model and NSS model.
Using the same data, it seems that NS model has some difficulties in fitting yields at longer-term maturities. Unlike NSS model, the level factor of NS model represent current long-term rate well. However, in most cases, NS model is sufficiently good fitting results. In other words, it is likely that the second curvature factor can be a superfluous factor in many normal days, which is considered an overfitting.
In particular, when the second curvature factor is not identified by data, so called compounding effect can take place. This is the case when two curvature estimates are too large in magnitude with the opposite signs. This compounding effect can be occurred between the slope and the curvature factors.
Concluding Remarks
This post estimates Nelson-Siegel-Svensson yield curve model which aims to fit longer term maturities well. It is worth noting that to get reasonable parameter estimates, proper restrictions on two decay parameters are needed. I think that research on estimation of NSS model is under way.
Reference
Gürkaynak, R. S., B. Sack, and J. H. Wright (2007), “The U.S. Treasury Yield Curve: 1961 to the Present,” Journal of Monetary Economics 54-8, 2291–2304.
Svensson, L.E. (1995), “Estimating Forward Interest Rates with the Extended Nelson and Siegel Method,” Sveriges Riksbank Quarterly Review 3, 13–26.
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.