Skip to content
All library documents

Bootstrapping a Bond Yield Curve from Market Prices in QuantLib

Article Quant Q&A · Author: TRex

Summary

The document asks how to build a spot curve in QuantLib when coupon bonds trade away from par. Its example combines deposit rate helpers with fixed rate bond helpers, supplying each bond’s market quote, face value, payment schedule, and coupon rate. QuantLib uses those inputs to bootstrap a discount curve, from which zero rates can be read. The central distinction is that the bond helper is given a price quote; the coupon is a separate property of the bond, and does not need to equal its yield.

The accepted answer illustrates constructing a fixed rate bond helper with a quoted price and bond terms. It does not walk through the complete curve-building process or explain how to validate curve outputs. The code shown in the question also adds a list of yields to a spot-curve table, but the answer does not discuss whether those maturities align with the generated curve dates. Users should therefore treat the exchange as a clarification of helper inputs, not a full guide to conventions, data quality, or curve validation.

Key ideas

  • A fixed rate bond helper can use the observed market price as its quote input.
  • The bond coupon is specified independently of its market price and yield.
  • Bootstrapping with bond prices requires the bond’s settlement terms, face amount, schedule, coupon, and day-count convention.
  • The example does not cover full curve validation or alignment of reported yields with curve maturities.

Tags

Full text
# How do I create a term structure of a bond using QuantLib?


# How do I create a term structure of a bond using QuantLib?












Has anyone used QuantLib to create term structure (i.e bootstrapping process to produce spots) in python? I have been using the below example http://gouthamanbalaraman.com/blog/quantlib-term-structure-bootstrap-yield-curve.html

Now looks like the assumption is that coupon bonds are trading at par (i.e. price of 100) ?

`'The rest of the points are coupon bonds. We assume that the YTM given for the bonds are all par rates. So we have bonds with coupon rate same as the YTM.'`

But what if they aren't bonds we use are not trading at par? i.e. YTM and coupon rates are different?

Any help is greatly appreciated.

thanks,

```

import matplotlib
matplotlib.use('macosx')
import matplotlib.pyplot as plt
import QuantLib as ql
import pandas as pd

# Deposit rates
depo_maturities = [ql.Period(1,ql.Months), ql.Period(2,ql.Months),ql.Period(3,ql.Months),ql.Period(6,ql.Months),
                   ql.Period(9,ql.Months), ql.Period(12, ql.Months)]
depo_cpn = [.08,.24,.40,.68,.34,.52] #yields they are trading at

# Coupon Bonds
bond_maturities = [ql.Period(i, ql.Years) for i in range(2,11)]
bond_cpn = [.5,.75,.1,.625,1.5,1.25,1.625,.875,4.75]
bond_rates = [.114,.151,.187,.252,.214,.272,.311,.4089,4.74]
bond_quotes = [100.896,101.987,103.301,101.926,108.078,107.088,111.111,104.374,144.568]

bond_long_maturities = [ql.Period(12,ql.Years),ql.Period(15,ql.Years),ql.Period(20,ql.Years),ql.Period(25,ql.Years),
                        ql.Period(30,ql.Years),ql.Period(40,ql.Years),ql.Period(50,ql.Years)]
bond_long_cpn = [4.25,4.5,4.25,3.25,1.75,1.75,1.625] #coupons
bond_long_rates = [.593,.667,.767,.858,.848,.669,.543] #yields
bond_long_quotes = [142.974,152.719,162.806,151.432,123.016,135.634,148.58,]

'''####### Depo Helpers #########'''

calc_date = ql.Date(24, 3, 2020)
ql.Settings.instance().evaluationDate = calc_date

calendar = ql.UnitedKingdom()
business_convention = ql.Unadjusted
day_count = ql.Thirty360()
end_of_month = True
settlement_days = 0
face_amount = 100
coupon_frequency = ql.Period(ql.Annual)

#Create depo bondhelps
depo_helpers = [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(r/100.0)),
                                     m,
                                     settlement_days,
                                     calendar,
                                     business_convention,
                                     end_of_month,
                                     day_count )
                for r, m in zip(depo_cpn, depo_maturities)]

'''####### Bonds Helpers #########'''

day_count = ql.Thirty360()
end_of_month = True
settlement_days = 2

# create fixed rate bond helpers from fixed rate bonds
bond_cpn += bond_long_cpn
bond_maturities += bond_long_maturities
bond_quotes += bond_long_quotes
bond_rates += bond_long_rates

bond_helpers = []
for r, m, q in zip(bond_cpn, bond_maturities,bond_quotes):
    termination_date = calc_date + m
    quote = ql.QuoteHandle(ql.SimpleQuote(q))
    schedule = ql.MakeSchedule(calc_date,termination_date,m)

    helper = ql.FixedRateBondHelper(quote,settlement_days,face_amount,schedule,[r/100.0],day_count,business_convention)
    bond_helpers.append(helper)

#The yield curve is constructed by putting the two helpers together.

rate_helpers = depo_helpers + bond_helpers
yieldcurve = ql.PiecewiseLogCubicDiscount(calc_date,rate_helpers, day_count)

#The spot cpn is obtined from yieldcurve object using the zeroRate method.
spots = []
tenors = []
for d in yieldcurve.dates():
    yrs = day_count.yearFraction(calc_date, d)
    compounding = ql.Compounded
    freq = ql.Annual
    zero_rate = yieldcurve.zeroRate(yrs, compounding, freq)
    tenors.append(yrs)
    eq_rate = zero_rate.equivalentRate(day_count,
                                       compounding,
                                       freq,
                                       calc_date,
                                       d).rate()
    spots.append(100*eq_rate)

spotcurve = pd.DataFrame(dict(tenors=tenors,spots=spots))
spotcurve.set_index('tenors',inplace=True)

print('\n')
spotcurve = spotcurve.iloc[1:]
pars = depo_cpn+bond_rates
spotcurve['pars'] = pars
spotcurve['spots'] = round(spotcurve['spots'],3)
print(spotcurve)

plt.figure(figsize=(7,4))
plt.plot(spotcurve)#,'b',lw=1.5)
plt.plot(spotcurve,'ro')
plt.grid(True)
plt.xlabel('Tenor')
plt.ylabel('Curves')
plt.show()
```
```

## Answer by David Duarte (score 1, accepted)

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

You should look at the inputs for the helpers. I believe the example you pointed to is an aproximation to build the curve with the yields, but the inputs are actually in prices. Check this example that is hopefully self explanatory.

```
quote = ql.QuoteHandle(ql.SimpleQuote(115.5))
settlementDays = 2
faceAmount = 100
schedule = ql.MakeSchedule(ql.Date(15,6,2020), ql.Date(15,6,2021), ql.Period('1y'))
coupons = [0.0195]
dayCounter = ql.ActualActual()
ql.FixedRateBondHelper(quote, settlementDays, faceAmount, schedule, coupons, dayCounter)
```

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.