# Zero Curve from a par curve curve QuantLib

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()
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?

I believe one the problems is the interpolation, because the way you have it set up, you don't have a curve node for each of the cash flows of the bond you are pricing.

The curve you built initially uses LogCubic interpolation on discount factors and the one you are reconstructing uses linear interpolation on spot rates.

Notice that...

for cf in fixed_rate_bond.cashflows():
print(cf.date(), cf.amount())


will give you:

July 15th, 2015 3.0000000000000027
January 15th, 2016 3.0000000000000027
July 15th, 2016 3.0000000000000027
January 17th, 2017 3.0000000000000027
January 17th, 2017 100.0


But if you check you curve dates, you have: spotCurve.dates()

(Date(15,1,2015), Date(15,7,2016), Date(15,1,2017), Date(15,7,2017),

(...)

So you will be interpolating the discount factor/zero rate for 15.07.2015 and 15.01.2016.

The problem is actually the way you defined bond_maturities since the first period will have "1Y6M"

Incidentally, if you try to rebuild it with discount factors, even the way you have it set up but without linear interpolation on zero rates, it will be closer.

dfs = [spotCurve.discount(dt) for dt in spotCurve.dates()]
spotCurve = ql.MonotonicLogCubicDiscountCurve(spotCurve.dates(), dfs, day_count)
spotCurveHandle = ql.YieldTermStructureHandle(spotCurve)
bond_engine_spot = ql.DiscountingBondEngine(spotCurveHandle)
fixed_rate_bond.setPricingEngine(bond_engine_spot)
fixed_rate_bond.dirtyPrice()


which outputs: 100.00000000001228

• Thanks for comments. I've extracted more points from curve and it worked. Do you know if can do MonotonicLogCubicDiscountCurve from rates input? Mar 16 '20 at 12:06
• Yes, you can use the ql.MonotonicCubicZeroCurve or LogCubicZeroCurve classes Mar 16 '20 at 12:34