├── LICENSE ├── main.py ├── README.md └── nelsonsiegelsvensson.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Qnity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ The Nelson-Siegel-Svensson is a popular extension of the 4-parameter Nelson-Siegel method to 6 parameters. It is an algorithm for interpolating/extrapolating the yield curve among other uses. 2 | Scennson introduces two extra parameters to better fit the variety of shapes of either the instantaneous forward rate or yield curves that are observed in practice. 3 | A desirable property of the model is that it produces a smooth and well-behaved forward rate curve. 4 | Another desirable property is the intuitive explanation of the parameters. beta0 is the long-term interest rate and beta0+beta1 is the instantaneous short-term rate. 5 | To find the optimal value of the parameters, the Nelder-Mead simplex algorithm is used (Already implemented in the scipy package). The link to the optimization algorithm is 6 | Gao, F. and Han, L. Implementing the Nelder-Mead simplex algorithm with adaptive parameters. 2012. Computational Optimization and Applications. 51:1, pp. 259-277 7 | """ 8 | from nelsonsiegelsvensson import * 9 | import numpy as np 10 | 11 | ## Inputs 12 | # - Observed yield rates (YieldVec) 13 | # - Maturity of each observed yield (TimeVec) 14 | # - Initial guess for parameters (beta0, beta1, beta2,beta3, labda0 , and lambda1) 15 | # - Target maturities (TimeResultVec) 16 | 17 | TimeVec = np.array([1,2,5,10,25]) 18 | YieldVec = np.array([0.0039, 0.0061, 0.0166, 0.0258, 0.0332]) 19 | beta0 = 0.1 # initial guess 20 | beta1 = 0.1 # initial guess 21 | beta2 = 0.1 # initial guess 22 | beta3 = 0.1 # initial guess 23 | lambda0 = 1 # initial guess 24 | lambda1 = 1 # initial guess 25 | 26 | TimeResultVec = np.array([1,2,5,10,25,30,31]) # Maturities for yields that we are interested in 27 | 28 | ## Implementation 29 | OptiParam = NSSMinimize(beta0, beta1, beta2, beta3, lambda0, lambda1, TimeVec, YieldVec) # The Nelder-Mead simplex algorithem is used to find the parameters that result in a curve with the minimum residuals compared to the market data. 30 | 31 | # Print the yield curve with optimal parameter to compare with the data provided 32 | print(NelsonSiegelSvansson(TimeResultVec, OptiParam[0], OptiParam[1], OptiParam[2], OptiParam[3], OptiParam[4], OptiParam[5])) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 🐍 Nelson-Siegel-Svannson algorithm 🐍 4 | 5 |

6 | 7 |
8 | 9 | Popular algorithm for fitting a yield curve to observed data. 10 | 11 | ## Problem 12 | Data on bond yields is usually available only for a small set of maturities, while the user is normally interested in a wider range of yields. 13 | 14 | ## Solution 15 | A popular solution is to use an algorithm to find a function that fits the existing datapoints. This way, the function can be used to interpolate/extrapolate any other point. The Nelson-Siegel-Svannson model is a curve-fitting-algorithm that is flexible enough to approximate most real-world applications. 16 | 17 | The Nelson-Siegel-Svensson is an extension of the 4-parameter Nelson-Siegel method to 6 parameters. Scennson introduced two extra parameters to better fit the variety of shapes of either the instantaneous forward rate or yield curves that are observed in practice. 18 | 19 | Advantages: 20 | - It produces a smooth and well-behaved forward rate curve. 21 | - The intuitive explanation of the parameters. `beta0` is the long-term interest rate and `beta0+beta1` is the instantaneous short-term rate. 22 | 23 | To find the optimal value of the parameters, the Nelder-Mead simplex algorithm is used (Already implemented in the scipy package). The link to the optimization algorithm is Gao, F. and Han, L. Implementing the Nelder-Mead simplex algorithm with adaptive parameters. 2012. Computational Optimization and Applications. 51:1, pp. 259-277. 24 | 25 | The formula for the yield curve (Value of the yield for a maturity at time 't') is given by the formula: 26 | 27 | ![formula](https://render.githubusercontent.com/render/math?math=\r(t)=\beta_{1}) + 28 | ![formula](https://render.githubusercontent.com/render/math?math=\beta_{2}) 29 | ![formula](https://render.githubusercontent.com/render/math?math=\big(\frac{1-exp(\frac{-t}{\lambda_1})}{\frac{t}{\lambda_1}}\big)) + 30 | ![formula](https://render.githubusercontent.com/render/math?math=\beta_{3}) 31 | ![formula](https://render.githubusercontent.com/render/math?math=\big(\frac{1-exp(\frac{-t}{\lambda_1})}{\frac{t}{\lambda_1}}-exp(\frac{-t}{\lambda_1})\big)) + 32 | ![formula](https://render.githubusercontent.com/render/math?math=\beta_{4}) 33 | ![formula](https://render.githubusercontent.com/render/math?math=\big(\frac{1-exp(\frac{-t}{\lambda_2})}{\frac{t}{\lambda_2}}-exp(\frac{-t}{\lambda_2})\big)) 34 | 35 | ### Parameters 36 | 37 | - Observed yield rates `YieldVec`. 38 | - Maturity of each observed yield `TimeVec`. 39 | - Initial guess for parameters `beta0`, `beta1`, `beta2`, `beta3`, `labda0`, and `lambda1`. 40 | - Target maturities `TimeResultVec`. 41 | 42 | ### Desired output 43 | 44 | - Calculated yield rates for maturities of interest `TimeResultVec`. 45 | 46 | ## Getting started 47 | 48 | The user is interested in the projected yield for government bonds with a maturity in 1,2,5,10,25,30, and 31 years. They have data on government bonds maturing in 49 | 1, 2, 5, 10, and 25 years. The calculated yield for those bonds is 0.39%, 0.61%, 1.66%, 2.58%, and 3.32%. 50 | 51 | ```bash 52 | from nelsonsiegelsvensson import * 53 | import numpy as np 54 | 55 | TimeVec = np.array([1, 2, 5, 10, 25]) 56 | YieldVec = np.array([0.0039, 0.0061, 0.0166, 0.0258, 0.0332]) 57 | beta0 = 0.1 # initial guess 58 | beta1 = 0.1 # initial guess 59 | beta2 = 0.1 # initial guess 60 | beta3 = 0.1 # initial guess 61 | lambda0 = 1 # initial guess 62 | lambda1 = 1 # initial guess 63 | 64 | TimeResultVec = np.array([1, 2, 5, 10, 25, 30, 31]) # Maturities for yields that we are interested in 65 | 66 | ## Implementation 67 | OptiParam = NSSMinimize(beta0, beta1, beta2, beta3, lambda0, lambda1, TimeVec, YieldVec) # The Nelder-Mead simplex algorithm is used to find the parameters that result in a curve with the minimum residuals compared to the market data. 68 | 69 | # Print the yield curve with optimal parameter to compare with the data provided 70 | print(NelsonSiegelSvansson(TimeResultVec, OptiParam[0], OptiParam[1], OptiParam[2], OptiParam[3], OptiParam[4], OptiParam[5])) 71 | ``` 72 | -------------------------------------------------------------------------------- /nelsonsiegelsvensson.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | 4 | def NelsonSiegelSvansson(T, beta0: float, beta1: float, beta2: float, beta3: float, lambda0: float, lambda1: float): 5 | """ 6 | NelsonSiegelSvansson calculates the interpolated/extrapolated curve at points in the array "T" using the Nelson-Siegel-Svannson algorithm, 7 | parameterized with parameters beta0, beta1, beta2, beta3, lambda0, lambda1. It returns a numpy ndarray of points. 8 | 9 | Arguments: 10 | T: n x 1 ndarray of maturities for which the user wants to calculate the corresponding rate. 11 | beta0: 1 x 1 floating number, representing the first factor of the NSS parametrization. 12 | beta1: 1 x 1 floating number, representing the second factor of the NSS parametrization. 13 | beta2: 1 x 1 floating number, representing the third factor of the NSS parametrization. 14 | beta3: 1 x 1 floating number, representing the fourth factor of the NSS parametrization. 15 | lambda0: 1 x 1 floating number, representing the first shape parameter lambda of the NSS parametrization. 16 | lambda1: 1 x 1 floating number, representing the second shape parameter lambda of the NSS parametrization. 17 | 18 | Returns: 19 | n x 1 ndarray of interpolated/extrapolated points corresponding to maturities inside T. Where n is the length of the vector T. 20 | 21 | Implemented by Gregor Fabjan from Qnity Consultants on 11/07/2023 22 | """ 23 | alpha1 = (1-np.exp(-T/lambda0)) / (T/lambda0) 24 | alpha2 = alpha1 - np.exp(-T/lambda0) 25 | alpha3 = (1-np.exp(-T/lambda1)) / (T/lambda1) - np.exp(-T/lambda1) 26 | 27 | return beta0 + beta1*alpha1 + beta2*alpha2 + beta3*alpha3 28 | 29 | def NSSGoodFit(params: list, TimeVec, YieldVec): 30 | """ 31 | NSSGoodFit calculates the residuals between the yield predicted by the NSS algorithm with the specified parameterization and the market observed ones. 32 | 33 | Arguments: 34 | params: 6 x 1 tuple containing the 6 parameters of the NSS algorithm. The sequence of parameters needs to be (beta0, ..., beta4, lambda0, lambda1). 35 | TimeVec: n x 1 ndarray of maturities for which the yields in YieldVec were observed. 36 | YieldVec: n x 1 ndarray of observed yields. 37 | 38 | Returns: 39 | 1 x 1 float number Euclidean distance between the calculated points and observed data. 40 | 41 | Implemented by Gregor Fabjan from Qnity Consultants on 11/07/2023 42 | """ 43 | 44 | return np.sum((NelsonSiegelSvansson(TimeVec, params[0], params[1], params[2], params[3], params[4], params[5])-YieldVec)**2) 45 | 46 | def NSSMinimize(beta0: float, beta1: float, beta2: float, beta3: float, lambda0: float, lambda1: float, TimeVec, YieldVec) -> list: 47 | """ 48 | NSSMinimize uses the built-in minimize function from the Python's scipy package. The function sets up the parameters and the function NSSGoodFit as to make it 49 | compatible with the way minimize requires its arguments. If the optimization does not converge, the output is an empty array. 50 | 51 | Arguments: 52 | beta0: 1 x 1 floating number, representing the first factor of the NSS parametrization. 53 | beta1: 1 x 1 floating number, representing the second factor of the NSS parametrization. 54 | beta2: 1 x 1 floating number, representing the third factor of the NSS parametrization. 55 | beta3: 1 x 1 floating number, representing the fourth factor of the NSS parametrization. 56 | lambda0: 1 x 1 floating number, representing the first shape parameter lambda of the NSS parametrization. 57 | lambda1: 1 x 1 floating number, representing the second shape parameter lambda of the NSS parametrization. 58 | TimeVec: n x 1 ndarray of maturities for which the yields in YieldVec were observed. 59 | YieldVec: n x 1 ndarray of observed yields. 60 | 61 | Returns: 62 | 6 x 1 array of parameters and factors that best fit the observed yields (or an empty array if the optimization was not successful). 63 | 64 | Source: 65 | - https://docs.scipy.org/doc/scipy/reference/optimize.minimize-neldermead.html 66 | - https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method 67 | 68 | Implemented by Gregor Fabjan from Qnity Consultants on 11/07/2023 69 | """ 70 | 71 | opt_sol = minimize(NSSGoodFit, x0=np.array([beta0, beta1, beta2, beta3, lambda0, lambda1]), args = (TimeVec, YieldVec), method="Nelder-Mead") 72 | if (opt_sol.success): 73 | return opt_sol.x 74 | else: 75 | return [] 76 | --------------------------------------------------------------------------------