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.


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.
Nelson-Siegel-Svensson factor loadings

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)),    
        (1exp(la1*m))/(la1*m),             
        (1exp(la1*m))/(la1*m)exp(la1*m),
        (1exp(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.15la1
    h[4< la10.03
    
    # 0.025 > la2 > 0.0075
    h[5< 0.025la2
    h[6< la20.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 = 1e10, abstol = 1e10)
ctrl.outer < list(eps = 1e10, itmax = 50000
                   method = “Nelder-Mead”, NMinit = TRUE)
 
# output container
m.nss_est < matrix(NA, 47)
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[6y[1]y[nmat], 
                2*y[15]y[1]y[nmat], 0.06090.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.06090.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.

Nelson-Siegel-Svensson Yield Curve model using R code


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.

Nelson-Siegel-Svensson Yield Curve model using R code
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.
Nelson-Siegel-Svensson Yield Curve model using R code
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.

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)