Skip to content
All library documents

Why Nelson–Siegel Curve Fits May Not Match Input Bond Yields

Article Quant Q&A · Author: Trevor J Richards

Summary

The document examines why a QuantLib Nelson–Siegel fitted bond discount curve can return a zero rate that differs from what a user expects from the input bond prices and coupons. The example changes one coupon while keeping the bonds priced at par; the reported rate then changes, even though the user expected a zero rate based on the first bond to remain zero.

The accepted explanation distinguishes fitting a parametric model from interpolating observed yields. A Nelson–Siegel fit estimates a curve within the model’s functional form, so it is not guaranteed to reproduce every input yield exactly. The author suggests that one set of example yields can be represented by the model while another cannot. This resolves the conceptual issue, but the discussion does not quantify fitting errors, examine calibration settings, or establish that this specific example is the only cause of discrepancies in other curve-fitting applications.

Key ideas

  • A parametric yield curve fit does not necessarily pass through every input bond yield.
  • Nelson–Siegel fitting estimates a model curve rather than directly interpolating observed data.
  • Changing one bond coupon can affect the fitted curve and the rate reported at another maturity.
  • Whether input yields can be matched depends on the model’s ability to represent their shape.

Tags

Full text
# QuantLib FittedBondDiscountCurve does not produce expected rates


# QuantLib FittedBondDiscountCurve does not produce expected rates












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.

## Answer by Trevor J Richards (score 2, accepted)

https://quant.stackexchange.com/a/77795

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.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.