The brief
A fixed-income desk values its bonds on a zero curve, built every morning from government par yields. This morning's screen shows six quotes in the order they arrived, and no bond at 4, 6, 8 or 9 years.
A client asks for a price on an off-the-run bond, an old issue that no screen quotes. It pays a 5.25% coupon once a year and has 6.5 years left to run. The desk values it on the curve.
Your task
- Write
par_curve(quotes): the par yields for years 1, 2, …, as a NumPy array, being the longest maturity.quotesholds(maturity, par_yield)pairs in any order, and each missing year is filled by linear interpolation. RaiseValueErrorfor no quotes, a repeated maturity, a missing 1-year quote, or a maturity that is not a positive whole number. - Write
bootstrap(par_yields): the pair(factors, zeros)of NumPy arrays for years 1 to .factorsholds the discount factors andzerosthe zero rates , compounded once a year. RaiseValueErrorif a factor comes out zero or below. - Write
discount_factor(t, factors): the factor at any timetfrom 0 to years. Between whole years, interpolate linearly, with today. RaiseValueErrorfor atoutside that range: the desk does not extrapolate. - Write
bond_value(coupon_rate, maturity, factors, face=100): the value today of a bond payingface * coupon_rateonce a year andfaceatmaturity. Coupons fall atmaturity, one year before it, and so on, back to the earliest date still after today. RaiseValueErrorfor a maturity of zero or below, or past the end of the curve.
For , the desk's rule reads, with :
The data
QUOTES in the code holds the six (maturity, par_yield) pairs as they arrived, maturities in years and yields as decimals. Each par bond pays its coupon once a year.
Hint: Where to start
Case study 1.6.4 bootstraps one maturity at a time, shortest first: . It also shows what goes wrong when the quotes are out of order or a year is missing. Fill a gap with np.interp (lesson 1.6.1).
Hint: Between the years
With continuous compounding , so , the rate times the time (case studies 1.4.6 and 1.6.4). Interpolate with np.interp, adding the point for today, then take exp to get the factor back.
Hint: Pricing the bond
List the payment dates from maturity back in steps of one year, while they are after today. Discount each payment with the factor for its own date, as case study 1.6.4 prices its par bonds. As a check, a bond paying a quoted par yield over that maturity is worth exactly face.