Bootstrapping Discount Factors from Government Bond Prices
Summary
The document describes an attempt to bootstrap discount factors from US government bond coupon, face value, and closing price data. The proposed approach builds a cash flow matrix for securities of different maturities, then solves a linear system by applying the inverse matrix to the observed prices. The author reports that the resulting discount factor vector includes negative values and asks whether the matrix construction or omission of an error term explains the problem.
The example includes zero-coupon short maturities and coupon-paying bonds, with payment frequency used to place cash flows into matrix periods. It gives code and sample rows but no confirmed diagnosis, corrected implementation, or validation against known yields. The setup raises issues worth checking, including maturity alignment, coupon conventions, matrix conditioning, and whether the cash flows and prices are expressed consistently. The document should be read as a debugging question, not as evidence that this particular bootstrap procedure is reliable.
Key ideas
- The approach represents bond cash flows in a matrix and solves for discount factors using observed prices.
- Negative estimated discount factors prompt the author to question the cash flow matrix and model setup.
- Payment frequency and months to maturity determine the placement of coupons and principal in the matrix.
- The document offers no final diagnosis or validated discount curve.
Tags
Full text
# Issue with Naive Bootstrapping (US Government Bonds)
# Issue with Naive Bootstrapping (US Government Bonds)
I am using the formula here to determine the discount factor function. I get the data for Coupons, Face Values and Closing Prices from Thomson Reuters, which I insert into the Dataframe `df`. However, I am getting that `dicount_factors` contains negative values as a result of the inverse of `matrix_of_coupons_and_facevalues`. I am trying to decipher if there is a flaw in my code below.
```
df:
months_to_maturity orig_iss_dt \
1 6 2015-06-25 00:00:00.0
2 12 2015-06-25 00:00:00.0
3 18 2015-06-30 00:00:00.0
4 24 2015-06-15 00:00:00.0
5 30 2015-06-30 00:00:00.0
maturity_dt pay_freq_cd coupon closing_price FACE_VALUE
1 2015-12-24 00:00:00.0 NaN 0.000 99.960889 100
2 2016-06-23 00:00:00.0 NaN 0.000 99.741444 100
3 2017-06-30 00:00:00.0 2 0.625 99.968750 100
4 2018-06-15 00:00:00.0 2 1.125 100.390625 100
5 2020-06-30 00:00:00.0 2 1.625 99.984375 100
```
The code:
```
#Determining the dimensions of the matrix of coupons and face values
#pay_freq_cd is number of coupon payments per year
max_freq_payments = df['pay_freq_cd'].max()
no_of_months_between_payments_for_matrix = 12/max_freq_payments
number_of_columns_and_rows = df['months_to_maturity'].max()/no_of_months_between_payments_for_matrix
df['FACE_VALUE'] = 100
df = df.sort(['months_to_maturity'], ascending=[True])
df = df.reset_index()
for (i,row) in df.iterrows():
no_of_coupons_per_year_2 = row['pay_freq_cd']
no_of_months_between_payments_2 = 12/no_of_coupons_per_year_2
if math.isnan(no_of_months_between_payments_2):
matrix_of_coupons_and_facevalues[i,(row.months_to_maturity/no_of_months_between_payments_for_matrix)-1] = (row['FACE_VALUE'])
else:
matrix_of_coupons_and_facevalues[i,0:((row.months_to_maturity/no_of_months_between_payments_2)-1)] = row['coupon']
matrix_of_coupons_and_facevalues[i,(row.months_to_maturity/no_of_months_between_payments_2)-1] = (row['FACE_VALUE']) + row['coupon']
price_array = df['closing_price'].values
price_array = np.where(np.isnan(price_array), 100, price_array)
inv_matrix_of_coupons_and_facevalues = inv(matrix_of_coupons_and_facevalues)
dicount_factors = np.dot(inv_matrix_of_coupons_and_facevalues, price_array)
```
Edit: I am not accounting for $epsilon$ , as stated here, in the equation $AP = F + epsilon$ and can hence directly solve $AP = F$ as a system of Equations. However, I am wondering could this be where my problem stems from?
Thank YouShown 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.