0
$\begingroup$

I am using the QuantLib library to fit yield curves. For a $\\\$100$ face bond, with price equal to $\\\$100$, and coupon equal to $\\\$0$, I would expect it to provide a zeroRate of $0.0\%$.

However, it seems that when I also provide it with non-zero coupons at times beyond the maturity date, it sometimes returns a zeroRate of $0.0\%$, and sometimes something else.

Consider the following code:

import pandas as pd
import QuantLib as ql

calendar = ql.UnitedStates(ql.UnitedStates.Settlement)
today = calendar.adjust(ql.Date(19, 12, 2019))

maturity_list = [ql.Date(19,12,2020),ql.Date(15,4,2021),ql.Date(15,4,2022)]
coupon_list = [0,5.0,4.0]
px_list = [100,100,100]

ql.Settings.instance().evaluationDate = today

pgbs = pd.DataFrame(
    {'maturity' : maturity_list,
    'coupon' : coupon_list,
    'px' : px_list
    })

bondSettlementDays = 0
bondSettlementDate = calendar.advance(
    today,
    ql.Period(bondSettlementDays, ql.Days))

frequency = ql.Annual
dc = ql.ActualActual(ql.ActualActual.ISMA)

accrualConvention = ql.Unadjusted
convention = ql.Unadjusted
redemption = 100.0

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)
    helper = ql.FixedRateBondHelper(
            ql.QuoteHandle(ql.SimpleQuote(row.px)),
            bondSettlementDays,
            100.0,
            schedule,
            [row.coupon / 100],
            dc,
            convention,
            redemption)

    instruments.append(helper)

params = [bondSettlementDate, instruments, dc]

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

fittedBondCurveMethods = {
    label: ql.FittedBondDiscountCurve(*params, method)
    for label, method in fittingMethods.items()
}

curve = fittedBondCurveMethods.get('NelsonSiegelFitting')

print('Zero rate: ',curve.zeroRate(maturity_list[0],dc,ql.Compounded,frequency))

This returns a zeroRate of $0.00\%$, as I would expect.

If, however, the sixth line of code is replaced by the line coupon_list = [0,5.0,5.0], then all of a sudden it returns a non-zero zeroRate of $1.89\%$!

Could someone please shed some light on this for me?

Many thanks!

NOTE: The code above was adapted from here: https://quantlib-python-docs.readthedocs.io/en/latest/termstructures.html#ql.FittedBondDiscountCurve.

EDIT1: Attempts I have made to understand this include changing assumptions such as frequency, dc, accrualConvention, and others, with no success.

$\endgroup$
2
  • $\begingroup$ My current working assumption is that the new coupon_list mentioned in the question introduces some instability in the Nelson Siegel curve fitting being used ... but I don't know enough about that algorithm to say whether that guess is reasonable or not. $\endgroup$ Dec 20, 2023 at 19:48
  • $\begingroup$ What is your intended economic meaning of coupon rates past the maturity date? $\endgroup$ Dec 20, 2023 at 20:15

1 Answer 1

2
$\begingroup$

I now understand the Nelson-Siegel algorithm, and realize that since the

fittedBondCurveMethods.get('NelsonSiegelFitting')

line is fitting a model, and not interpolating data, it need not necessarily replicate the yields that were fed to it. I think the specific issue I mentioned above shows that while zcb yields of 0, 5, 4 at the specified dates could be accommodated by a NS model, yields of 0, 5, 5 just could not, and that is fine.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.