I'm trying to understand why pricing a par bond with zero curve, contracted from par bonds themselves doesn't give me par. (Based on examples here)
import QuantLib as ql
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]
calc_date = ql.Date(15, 1, 2015)
ql.Settings.instance().evaluationDate = calc_date
calendar = ql.UnitedStates()
bussiness_convention = ql.Unadjusted
day_count = ql.ActualActual(ql.ActualActual.Bond) #My fix relative to the source
end_of_month = True
settlement_days = 0
face_amount = 100
coupon_frequency = ql.Period(ql.Semiannual)
settlement_days = 0
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,
bussiness_convention,
bussiness_convention,
ql.DateGeneration.Backward,
end_of_month)
helper = ql.FixedRateBondHelper(ql.QuoteHandle(ql.SimpleQuote(face_amount)),
settlement_days,
face_amount,
schedule,
[r/100.0],
day_count,
bussiness_convention,
)
bond_helpers.append(helper)
yieldcurve = ql.PiecewiseLogCubicDiscount(calc_date, bond_helpers, day_count)
yieldCurveHandle = ql.YieldTermStructureHandle(yieldcurve)
Now if I check the price of a bond with a yield from the input I should have par
termination_date = calc_date + bond_maturities[1] # 2Y bond
schedule = ql.Schedule(calc_date, termination_date, coupon_frequency, calendar,
bussiness_convention, bussiness_convention, ql.DateGeneration.Backward,
end_of_month)
coupons = [bond_rates[1] / 100]
fixed_rate_bond = ql.FixedRateBond(settlement_days, face_amount, schedule, coupons, day_count)
bond_engine = ql.DiscountingBondEngine(yieldCurveHandle)
fixed_rate_bond.setPricingEngine(bond_engine)
fixed_rate_bond.dirtyPrice()
Indeed
100.00000000001226
Now, I want to extract zero curve from the curve object
spots = []
tenors = []
for d in yieldcurve.dates():
yrs = day_count.yearFraction(calc_date, d)
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(eq_rate)
From this curve I want to build a zero curve (based on example here)
spotCurve = ql.ZeroCurve(yieldcurve.dates(), spots, day_count, calendar, ql.Linear(),
ql.Compounded, ql.Semiannual)
spotCurveHandle = ql.YieldTermStructureHandle(spotCurve)
I expect the price of the bond still be par, but
bond_engine_spot = ql.DiscountingBondEngine(spotCurveHandle)
fixed_rate_bond.setPricingEngine(bond_engine_spot)
fixed_rate_bond.dirtyPrice()
100.0999007315603
Any ideas what I'm missing here?