# Nelson-Siegel-Svensson Yield Curve Estimation From Zero-rates Using QuantLib (Python)

I am using QuantLib in Python to estimate yield curves using the Nelson-Siegel-Svensson (NSS) model with zero-rates as input. Since the NSS model in QuantLib uses the discount function to estimate the parameters I simply use the zero-rates as bonds with no interest-rate. However, using the code I have noticed that for some trade dates the program perform quite poorly and I do not know why. I suspect is has something to do with the way I define the zero-bonds but I am not sure. Furthermore, I have noticed through trial-and-error that if I add some small number (a “simplexlambda”?) to the ql.FittedBondDiscountCurve program the estimation in some cases performs better. I do not know how to correctly choose a number so fit is better.

Is there any way I can change my code so it will more consistently estimate yield curves which fits the input (the zero-rates) better? I know I can use for example Cubic Spline however I would very much like to use the NSS model to estimate the yield curve.

My code is,

#zero-rates
zero_rates=[-0.005683497, -0.006091677, -0.006167227, -0.006020827, -0.005731884,
-0.003626564, -0.001838833, -0.000665441, 0.000556985]

#Time to maturity for the zero-rates
maturities =[1,2,3,4,5,10,15,20,30]

dato='2020-10-01'

# creating maturity dates
dates=[]

dato_split=re.split( '-',dato)           # month              # year
startDate = ql.Date(int(dato_split[2]), int(dato_split[1]), int(dato_split[0]))
print('startDate: ',startDate)
dates.append(startDate)

end_dates=[]

for years in maturities:
endDate = startDate + ql.Period(years, ql.Years)

end_dates.append(endDate)
dates.append(endDate)

###########################################

times = [ql.SimpleDayCounter().yearFraction(startDate, dt) for dt in dates]

#The price of the zero-coupon bonds are then,
counter = 1
zero_prices=[]
for zero_rate in zero_rates:

zero_price=np.exp(-zero_rate*times[counter])
counter+=1

zero_prices.append(zero_price)

###########################################

pgbs = pd.DataFrame(
{'maturity': dates[1:],
'px': zero_prices})

###########################################

calendar = ql.NullCalendar()
ql.Settings.instance().evaluationDate = today

bondSettlementDays = 0
frequency = ql.NoFrequency # NO INTEREST PAYMENTS SINCE WE HAVE ZERO-COUPON BOND
dc = ql.SimpleDayCounter()

###########################################

instruments = []
for idx, row in pgbs.iterrows():
maturity = row.maturity

schedule = ql.Schedule(
bondSettlementDate,
maturity,
ql.Period(frequency),
calendar,
accrualConvention,
accrualConvention,
ql.DateGeneration.Backward,
False)

dates2 = [dt for dt in schedule]

helper = ql.FixedRateBondHelper(
ql.QuoteHandle(ql.SimpleQuote(row.px)),
bondSettlementDays,
1, # face amount
schedule,
[0], #coupon rate => zero.
dc,
convention,
1)

instruments.append(helper)

params = [bondSettlementDate, instruments, dc]

###########################################

fittingMethods = {
'NelsonSiegelFitting': ql.NelsonSiegelFitting(),
'SvenssonFitting': ql.SvenssonFitting(),
}

accuracy=1e-10
numIter = 10000
guess = []

fittedBondCurveMethods = {

label: ql.FittedBondDiscountCurve(*params, method,accuracy,numIter,guess)
for label, method in fittingMethods.items()
}

# The NSS model
curve = fittedBondCurveMethods.get('SvenssonFitting')

# Estimated parameters
estimated_paramters=[parameter for parameter in curve.fitResults().solution()]

maturities =[1,2,3,4,5,10,15,20,30]
t = np.linspace(0.1,30)
rates_nss = [curve.zeroRate(n, ql.Annual).rate() for n in t]
plt.figure(figsize=(15,5)),
plt.title(date)
plt.axhline(y=0, color='black', linestyle='-',linewidth=1); #zeroline
plt.plot(t, rates_nss,label='NSS Estimated Curve');
plt.plot(maturities, results[1],marker='o',label='Input Zero-rates');
plt.legend();


Running the code should result in this, which clearly is a poor fit. If I add -0.3 in the ql.FittedBondDiscountCurve program, so fittedBondCurveMethods = {label: ql.FittedBondDiscountCurve(*params, method,accuracy,numIter,guess,-0.3) for label, method in fittingMethods.items()}  , I get a good fit. See the second screenshot.

I would very much like to find a permanent solution to the initial poor fit so I can use the code to estimate yield curves for many other trade dates and not having to adjust the "simplexlambda" each time.