4. Constructing a yield curve

In this chapter we will go over the construction of treasury yield curve. Let’s start by importing QuantLib and other necessary libraries.

In [1]: import QuantLib as ql
        from pandas import DataFrame
        import numpy as np
        import utils
        %matplotlib inline

This is an example based on Exhibit 5-5 given in Frank Fabozzi’s Bond Markets, Analysis and Strategies, Sixth Edition.

In [2]: depo_maturities = [ql.Period(6,ql.Months), ql.Period(12, ql.Months)]
        depo_rates = [5.25, 5.5]
        
        # Bond rates
        bond_maturities = [ql.Period(6*i, ql.Months) for i in range(3,21)]
        bond_rates = [5.75, 6.0, 6.25, 6.5, 6.75, 6.80, 7.00, 7.1, 7.15, 
                      7.2, 7.3, 7.35, 7.4, 7.5, 7.6, 7.6, 7.7, 7.8]
        
        maturities = depo_maturities+bond_maturities
        rates = depo_rates+bond_rates
        DataFrame(list(zip(maturities, rates)), 
                  columns=["Maturities","Curve"],
                  index=['']*len(rates))

Out[2]: 
  Maturities Curve
  6M 5.25
  12M 5.50
  18M 5.75
  24M 6.00
  30M 6.25
  36M 6.50
  42M 6.75
  48M 6.80
  54M 7.00
  60M 7.10
  66M 7.15
  72M 7.20
  78M 7.30
  84M 7.35
  90M 7.40
  96M 7.50
  102M 7.60
  108M 7.60
  114M 7.70
  120M 7.80

Below we declare some constants and conventions used here. For the sake of simplicity, we assume that some of the constants are the same for deposit rates and bond rates.

In [3]: calc_date = ql.Date(15, 1, 2015)
        ql.Settings.instance().evaluationDate = calc_date
        
        calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
        business_convention = ql.Unadjusted 
        day_count = ql.Thirty360(ql.Thirty360.BondBasis)
        end_of_month = True
        settlement_days = 0
        face_amount = 100
        coupon_frequency = ql.Period(ql.Semiannual)
        settlement_days = 0

The basic idea of bootstrapping is to use the deposit rates and bond rates to create individual rate helpers. Then use the combination of the two helpers to construct the yield curve. As a first step, we create the deposit rate helpers as shown below.

In [4]: 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_rates, depo_maturities)
        ]

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. Using this information, we construct the fixed rate bond helpers below.

In [5]: bond_helpers = []
        for r, m in zip(bond_rates, bond_maturities):
            termination_date = calc_date + m
            schedule = ql.Schedule(calc_date,
                                   termination_date, 
                                   coupon_frequency, 
                                   calendar,
                                   business_convention, 
                                   business_convention, 
                                   ql.DateGeneration.Backward, 
                                   end_of_month)
            
            bond_helper = ql.FixedRateBondHelper(
                ql.QuoteHandle(ql.SimpleQuote(face_amount)),
                settlement_days,
                face_amount,
                schedule,
                [r/100.0],
                day_count,
                business_convention)
            bond_helpers.append(bond_helper)

The union of the two helpers is what we use in bootstrapping shown below.

In [6]: rate_helpers = depo_helpers + bond_helpers

The get_spot_rates is a convenient wrapper function that we will use to get the spot rates on a monthly interval.

In [7]: def get_spot_rates(
                yieldcurve, day_count, 
                calendar=ql.UnitedStates(ql.UnitedStates.GovernmentBond),
                months=121
        ):
            spots = []
            tenors = []
            ref_date = yieldcurve.referenceDate()
            calc_date = ref_date
            for month in range(0, months):
                yrs = month/12.0
                d = calendar.advance(ref_date, ql.Period(month, ql.Months))
                compounding = ql.Compounded
                freq = ql.Semiannual
                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)
            return DataFrame(list(zip(tenors, spots)),
                             columns=["Maturities","Curve"],
                             index=['']*len(tenors))

The bootstrapping process is fairly generic in QuantLib. You can chose what variable you are bootstrapping, and what is the interpolation method used in the bootstrapping. There are multiple piecewise interpolation methods that can be used for this process. The PiecewiseLogCubicDiscount will construct a piece wise yield curve using LogCubic interpolation of the Discount factor. Similarly PiecewiseLinearZero will use Linear interpolation of Zero rates. PiecewiseCubicZero will interpolate the Zero rates using a Cubic interpolation method.

In [8]: yc_logcubicdiscount = ql.PiecewiseLogCubicDiscount(calc_date,
                                                           rate_helpers,
                                                           day_count)

The zero rates from the tail end of the PiecewiseLogCubicDiscount bootstrapping is shown below.

In [9]: splcd = get_spot_rates(yc_logcubicdiscount, day_count)
        splcd.tail()

Out[9]: 
  Maturities Curve
  9.666667 7.981384
  9.750000 8.005292
  9.833333 8.028145
  9.916667 8.050187
  10.000000 8.071649

The yield curves using the PiecewiseLinearZero and PiecewiseCubicZero is shown below. The tail end of the zero rates obtained from PiecewiseLinearZero bootstrapping is also shown below. The numbers can be compared with that of the PiecewiseLogCubicDiscount shown above.

In [10]: yc_linearzero = ql.PiecewiseLinearZero(
             calc_date,rate_helpers,day_count
         )
         yc_cubiczero = ql.PiecewiseCubicZero(
             calc_date,rate_helpers,day_count
         )
         
         splz = get_spot_rates(yc_linearzero, day_count)
         spcz = get_spot_rates(yc_cubiczero, day_count)
         splz.tail()

Out[10]: 
  Maturities Curve
  9.666667 7.976804
  9.750000 8.000511
  9.833333 8.024221
  9.916667 8.047934
  10.000000 8.071649

All three are plotted below to give you an overall perspective of the three methods.

In [11]: fig, ax = utils.plot()
         ax.plot(splcd["Maturities"],splcd["Curve"], '.',
                 label="LogCubicDiscount")
         ax.plot(splz["Maturities"],splz["Curve"],'--', 
                 label="LinearZero")
         ax.plot(spcz["Maturities"],spcz["Curve"],
                 label="CubicZero")
         ax.set_xlabel("Months", size=12)
         ax.set_ylabel("Zero Rate", size=12)
         ax.set_xlim(0.5,10)
         ax.set_ylim([5.25,8])
         ax.legend(loc=0);
Conclusion

In this chapter we saw how to construct yield curves by bootstrapping bond quotes.