R: Nyholm (2018) Rotated Nelson-Siegel Model


This post implements R code to estimate the Rotated Nelson-Siegel yield curve model of Nyholm (2018).

Rotated Nelson-Siegel model

Nyholm (2018) introduced the Rotated Dynamic Nelson-Siegel (RDNS) model with the intention of enhancing the interpretability of yield factors through a more conventional framework. The primary emphasis of this model centers on the short rate component.

Since the factor loading matrix is the key distinguishing difference between DNS and RDNS models, I opt for the period-by-period OLS estimation of the Rotated Nelson-Siegel model for simplicity, rather than the Rotated DNS model.

The well-known Nelson-Siegel (NS) model has the following form (B is the NS factor loading matrix).

In order to rotate the NS factors from [Level, -Slope, Curvature] into [Short Rate, Slope, Curvature], the NS model is augmented with a rotation matrix, denoted by A, which is chosen in such a way that the desired factor-interpretation emerges, and such that I=A−1A, where I is the identity matrix.

Absolutely, keep in mind that the slope factor is defined as the difference between the long-term and short-term components: slope = long-term – short-term.

In sum, the rotation matrix A turns the NS yield factors into RNS yield factors as follows.

Nyholm (2018) rotates the factor structure to enable direct parametrization of the short rate process.

Hence, the RDNS model shares identical statistical properties with the DNS model, rendering it observationally equivalent to the DNS model.

NS and Rotated NS model

The following R code estimates the NS and Rotated NS models by using the period-by-period OLS estimation.

# Quantitative ALM, Financial Econometrics & Derivatives 
# ML/DL using R, Python, Tensorflow by Sang-Heon Lee 
# https://shleeai.blogspot.com
# Nyholm (2018) Rotated Nelson-Siegel Model
graphics.off(); rm(list = ls())
# data is located in the following address
# 'https://github.com/Financial-Times/yield-curve-sonification/blob/7d131970377380f118fb9a0e1626f5ed1ecd35ca/yield-curve-monthly-data.csv'
yield <- as.matrix(read.csv('yield-curve-monthly-data.csv')[,-1])
mat <- c(1, 3, 6, 12,  24, 36, 60, 84, 120, 240, 360)
vcol <- c("darkgray", "blue", "hotpink")
# NS and Rotated NS models
# Factor loading matrix
NS_factor_loading <-function(la, m) {
    B <- cbind(
RNS_factor_loading  <-function(la, m) {
    # short rate, conventional slope, curvature
    A1 <- c(1, 1, 0)
    A2 <- c(0,-1, 0)
    A3 <- c(0, 0, 1)
    A <- matrix(cbind(A1, A2, A3), 3, 3, byrow = TRUE)
    G <- NS_factor_loading(la, m)%*%solve(A)
# fitted yield curve
fit_NS  <- function(beta, la, m)    { 
    return(NS_factor_loading(la, m)%*%beta) 
fit_RNS  <- function(gamma, la, m)    { 
    return(RNS_factor_loading(la, m)%*%gamma) 
rmse_NS <- function(beta, la, y, m) { 
    return(sqrt(mean((y - fit_NS(beta, la, m))^2)))
rmse_RNS <- function(gamma, la, y, m) { 
    return(sqrt(mean((y - fit_RNS(gamma, la, m))^2)))
# Cross-sectional OLS
est_NS_ols <- function(la, y, m) {
    B    <- NS_factor_loading(la, m)
    beta <- solve(t(B)%*%B)%*%t(B)%*%y; 
    rmse <- rmse_NS(beta, la, y, m)
    return(list(beta=beta, la=la, rmse=rmse))
est_RNS_ols <- function(la, y, m) {
    G    <- RNS_factor_loading(la, m)
    gamma <- solve(t(G)%*%G)%*%t(G)%*%y; 
    rmse <- rmse_RNS(gamma, la, y, m)
    return(list(gamma=gamma, la=la, rmse=rmse))
# period-by-period OLS Estimation
est_NS_ols_ts <- function(la, y_all, m) {
    nobs <- nrow(y_all); nmat <- length(m)
    rmse <- rep(NA,nobs)
    beta <- matrix(NA, nobs, 3)
    for(t in 1:nobs) {
        lt <- est_NS_ols(la,y_all[t,],m)
        beta[t,] <- lt$beta; rmse[t]  <- lt$rmse
    return(list(beta=beta, la=la, rmse=rmse))
est_RNS_ols_ts <- function(la, y_all, m) {
    nobs <- nrow(y_all); nmat <- length(m)
    rmse <- rep(NA,nobs)
    gamma <- matrix(NA, nobs, 3)
    for(t in 1:nobs) {
        lt <- est_RNS_ols(la,y_all[t,],m)
        gamma[t,] <- lt$gamma; rmse[t]  <- lt$rmse
    return(list(gamma=gamma, la=la, rmse=rmse))
# Estimation
out_NS  <- est_NS_ols_ts (0.0609, yield, mat)
out_RNS <- est_RNS_ols_ts(0.0609, yield, mat)
# plot
x11(width = 16/2, height = 9/2.3)
matplot(out_NS$beta, type="l", lty=1, lwd=4, col = vcol,
        main = paste0("NS model : Avg. RMSE = ", 
legend("bottomright", pch = 16, col = vcol, cex = 0.9,
       bty = "n", legend=c("Level","-Slope","Curvature"))
x11(width = 16/2, height = 9/2.3)
matplot(out_RNS$gamma, type="l", lty=1, lwd=4, col = vcol,
        main = paste0("Rotated NS model : Avg. RMSE = ", 
legend("bottomright", pch = 16, col = vcol, cex = 0.9,
       bty = "n", legend=c("Short Rate","Slope","Curvature"))

Visit SHLee AI Financial Model to read the full article.

Leave a Reply

Note that all comments are held for moderation before publishing.

Disclosure: Interactive Brokers

Information posted on IBKR Campus that is provided by third-parties does NOT constitute a recommendation that you should contract for the services of that third party. Third-party participants who contribute to IBKR Campus are independent of Interactive Brokers and Interactive Brokers does not make any representations or warranties concerning the services offered, their past or future performance, or the accuracy of the information provided by the third party. Past performance is no guarantee of future results.

This material is from SHLee AI Financial Model and is being posted with its permission. The views expressed in this material are solely those of the author and/or SHLee AI Financial Model and Interactive Brokers is not endorsing or recommending any investment or trading discussed in the material. This material is not and should not be construed as an offer to buy or sell any security. It should not be construed as research or investment advice or a recommendation to buy, sell or hold any security or commodity. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.