I'm devising a methodology to transform par yield to spot rates, I'd like to stick with pure python as much as possible so not really after Quantlib (or other libraries) examples. In particular I want to consume the US Treasury par yield curves. From the website I understand that these are curves based on securities that pay interest on a semiannual basis and the yields are "bond-equivalent" yields. A couple of interesting details on these curves are in the FAQs, in particular

DOES THE PAR YIELD CURVE ASSUME SEMIANNUAL INTEREST PAYMENTS OR IS IT A ZERO-COUPON CURVE? The par yield curve is based on securities that pay interest on a semiannual basis and the yields are "bond-equivalent" yields. Treasury does not create or publish daily zero-coupon curve rates.

DOES THE PAR YIELD CURVE ONLY ASSUME SEMIANNUAL INTEREST PAYMENT FROM 2-YEARS OUT (I.E., SINCE THAT IS THE SHORTEST MATURITY COUPON TREASURY ISSUE)? No. All yields on the par yield curve are on a bond-equivalent basis. Therefore, the yields at any point on the par yield curve are consistent with a semiannual coupon security with that amount of time remaining to maturity.

I'm implementing the bootstrapping methodology, as a reference I came across an article that explains it nicely I think. I got the bootstrapping to work in the code below, the spot rates I get are matching the ones in the article.

import numpy as np
from scipy.optimize import fsolve

def func(x, a, b):
    return (1 + (x / 100 / 2)) ** (a + 1) - b

par_rates = [2.0, 2.4, 2.76, 3.084, 3.3756, 3.638]
spot_rates = []

m = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
par_value = 1000

pf = 2

n_payments = int(m[-1] * pf)

# iterate over rows
for idx, rate in enumerate(par_rates):
    if m[idx] <= 1 / pf:
        rhs = 0    
        for idx2 in np.arange(0, idx, 1):    
            coupon = par_value * (rate / 100 / pf)    
            exp = idx2 + 1    
            rhs += coupon / (1 + spot_rates[idx2] / 100 / pf) ** exp
            summation = par_value - rhs

        s = (par_value + coupon) / (summation)
        root = fsolve(func, x0=rate, args=(exp, s))    

However, how can I deal with the constant maturities in the Treasury curves that start at 1 month and aren't equally spaced at 6 months (semiannual payments).

Essentially, how to deal with

  1. par rates that relate to maturities of less than 6 months? The Treasury dataset has 1, 2, 3, 4 months par rates that relate to the 4, 8, 13,17 weeks Treasury Bills
  2. maturities intervals that are greater than the semiannual interest, for instance 3 and 5yr. Shall I interpolate the par rates to evaluate par rates at 3.5/4./4.5 for instance and keep using the same methodology?
  • $\begingroup$ Would you share the reason behind that you set specific time period? Do you think there would be other related event or value to your output? $\endgroup$
    – Cloud Cho
    Nov 7, 2023 at 1:12
  • $\begingroup$ @CloudCho do you mean the maturities m in the code snippet? Those are the ones listed in the article that I shared in the question $\endgroup$
    – AleVis
    Nov 7, 2023 at 11:39
  • $\begingroup$ Thanks I could see. I agree that the time period from the article might limit your work. Do you have any result from your code to share? How far is the result to what you expect? $\endgroup$
    – Cloud Cho
    Nov 7, 2023 at 22:22


Your Answer

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