0
$\begingroup$

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]

# Trade day
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()
today = calendar.adjust(startDate) 
ql.Settings.instance().evaluationDate = today 

bondSettlementDays = 0
bondSettlementDate = calendar.advance(today,ql.Period(bondSettlementDays, ql.Days))
frequency = ql.NoFrequency # NO INTEREST PAYMENTS SINCE WE HAVE ZERO-COUPON BOND
dc = ql.SimpleDayCounter() 
accrualConvention = ql.Unadjusted 
convention = ql.Unadjusted #

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

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, enter image description here 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. enter image description here

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.