Aligning the Zero Curve and Valuation Date in Bond Pricing
Summary
This QuantLib example compares the net present value of a fixed-rate bond with a separately calculated discounted cash-flow sum. The two calculations disagree because the zero curve starts several months before the stated valuation date. The accepted answer identifies this mismatch as the source of the difference and recommends starting the curve on the valuation date. With a curve aligned to that date, the bond engine and manual cash-flow calculation should agree under the same conventions.
The example also shows that a manual discounting loop can obtain each payment’s discount factor directly from the curve. The reported values illustrate the discrepancy for the particular dates, curve inputs, and bond setup shown; they are not general pricing results. The discussion does not examine other possible implementation differences, such as settlement treatment or accrued interest, so the date alignment lesson should be applied alongside checks that both methods use consistent cash flows, curve conventions, and valuation settings.
Key ideas
- A zero curve whose reference date precedes the valuation date can produce inconsistent bond pricing results.
- Aligning the curve reference date with the valuation date resolves the discrepancy described in this example.
- A manual DCF calculation can use the curve’s discount factor for each eligible cash-flow date.
- The comparison depends on consistent cash flows, settlement settings, and curve conventions.
Tags
Full text
# Fixed Rate Bond Pricing using QuantLib Python
# Fixed Rate Bond Pricing using QuantLib Python
I have tried to price a fixed rate bond using Python QuantLib and I verified my answer using a DCF model.
Below are my codes for the pricing of the fixed rate bond using Python QuantLib:
```
import QuantLib as ql
valuationDate = ql.Date(30, 6, 2020)
ql.Settings.instance().evaluationDate = valuationDate
compounding = ql.Compounded
calendar = ql.UnitedStates()
coupon = 0.05
couponFrequency = ql.Annual
issueDate = ql.Date(1, 1, 2016)
maturityDate = ql.Date(1, 1, 2024)
settlementDays = 2
settlementDate = calendar.advance(issueDate, ql.Period(settlementDays, ql.Days))
dayCount = ql.ActualActual(ql.ActualActual.ISMA)
schedule = ql.Schedule(issueDate, maturityDate, ql.Period(couponFrequency), calendar, ql.Unadjusted, ql.Unadjusted, ql.DateGeneration.Forward, True)
fixedRateBond = ql.FixedRateBond(settlementDays, 100, schedule, [coupon], ql.ActualActual(ql.ActualActual.ISMA))
curve = ql.ZeroCurve([ql.Date(1, 1, 2020), ql.Date(1, 1, 2027)], [0.01, 0.02], dayCount, calendar, ql.Linear(), compounding, couponFrequency)
handle = ql.YieldTermStructureHandle(curve)
bondEngine = ql.DiscountingBondEngine(handle)
fixedRateBond.setPricingEngine(bondEngine)
print('QuantLib NPV:', fixedRateBond.NPV())
```
The resulting NPV using QuantLib is:
> QuantLib NPV: 118.26526771080188
Below are my codes for the pricing of the fixed rate bond using the DCF model:
```
DCFs = []
for i, cf in enumerate(fixedRateBond.cashflows()):
if cf.date() >= valuationDate:
discount_factor = curve.zeroRate(cf.date(), dayCount, compounding, couponFrequency).discountFactor(valuationDate, cf.date())
DCFs.append(discount_factor * cf.amount())
print('DCF Model NPV:', sum(DCFs))
```
The resulting NPV using the DCF model is:
> DCF Model NPV: 114.13038560873856
Can someone please explain how this difference is arising? Many thanks.
## Answer by Luigi Ballabio (score 2, accepted)
https://quant.stackexchange.com/a/66425
Starting the zero curve from a date 6 months in the past with respect to the valuation date throws the bond off. If you start the curve at `valuationDate` instead, you'll get the same results.
You'll also be able to write your DCF loop more concisely, as:
```
DCFs = []
for i, cf in enumerate(fixedRateBond.cashflows()):
if cf.date() >= valuationDate:
discount_factor = curve.discount(cf.date())
DCFs.append(discount_factor * cf.amount())
print('DCF Model NPV:', sum(DCFs))
```Shown 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.