How QuantLib Schedule Dates Determine Bond Coupon Accruals
Summary
The document explains why QuantLib’s fixed-rate bond coupon amounts differ from calculations made using the issue date and nominal anniversary dates. The key is that the schedule is adjusted by the selected calendar and business-day convention before coupon accrual is calculated. In the example, the issue date falls on a Sunday and the following Monday is a Canadian holiday, so the schedule starts on the next business day.
The recommended check is to inspect the generated schedule and calculate the year fraction between its actual coupon dates, then multiply by the coupon rate and face value. This reproduces the first coupon amount shown by QuantLib. The response also cautions that adding a period directly to a date does not apply business-day rules; advancing through the calendar is more appropriate when a business date is intended. The explanation depends on the calendar and schedule settings used, so coupon amounts should be interpreted against the dates the library actually generated.
Key ideas
- QuantLib computes coupon accrual using dates in the generated schedule.
- Calendars and business-day conventions can adjust issue or payment dates.
- The year fraction between scheduled coupon dates, multiplied by coupon rate and face value, gives the coupon amount.
- Inspecting the schedule helps explain differences from calculations based on unadjusted dates.
- Calendar advancement applies business-day rules, while direct period addition does not.
Tags
Full text
# Questions on the Day count issue in Bond pricing
# Questions on the Day count issue in Bond pricing
I failed to understand how does `QuantLib` manage the day-count issue when determining the actual Coupon payment. Below is my Fixed Rate Bond -
```
import QuantLib as ql
import pandas as pd
todaysDate = ql.Date(1, 9, 2019)
ql.Settings.instance().evaluationDate = todaysDate
spotDates = [todaysDate, todaysDate + ql.Period("1y"), todaysDate + ql.Period("2y"), todaysDate + ql.Period("3y")]
spotRates = [0, 0.066682, 0.067199, 0.067502]
dayCount = ql.ActualActual()
calendar = ql.Canada()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Continuous
spotCurve = ql.ZeroCurve(spotDates, spotRates, dayCount, calendar, interpolation, compounding, compoundingFrequency)
spotCurveHandle = ql.YieldTermStructureHandle(spotCurve)
issueDate = todaysDate
maturityDate = todaysDate + ql.Period("2y")
tenor = ql.Period(ql.Annual)
bussinessConvention = ql.Following
dateGeneration = ql.DateGeneration.Backward
monthEnd = False
schedule = ql.Schedule(issueDate, maturityDate, tenor, calendar, bussinessConvention, bussinessConvention, dateGeneration, monthEnd)
couponRate = 0.09
coupons = [couponRate]
settlementDays = 3
faceValue = 100
fixedRateBond = ql.FixedRateBond(settlementDays, faceValue, schedule, coupons, dayCount)
bondEngine = ql.DiscountingBondEngine(spotCurveHandle)
fixedRateBond.setPricingEngine(bondEngine)
fixedRateBond.NPV()
```
Now, I want to see the actual CFs which `QuantLib` is considering
```
for cf in fixedRateBond.cashflows():
print(cf.date().ISO(), cf.amount())
```
This gives -
```
2020-09-01 8.958904109589039
2021-09-01 8.991780821917805
2021-09-01 100.0
```
But my question is how the 1st 2 numbers are calculated. With `Actual-Actual` convention, shouldnt the 1st number would be :
```
>>> 9 * dayCount.yearFraction(issueDate + ql.Period("1d"),issueDate + ql.Period("1y"))
8.983561643835616
```
And for the 2nd Interest payment
```
>>> 9 * dayCount.yearFraction(issueDate + ql.Period("1y") + ql.Period("1d"),issueDate + ql.Period("2y"))
8.967190657983382
```
Can you please help me to understand the calculation - what am I missing here?
## Answer by David Duarte (score 1, accepted)
https://quant.stackexchange.com/a/57469
The date you are using as issueDate (01-09-2019) is a Sunday, and because you are using the Canadian Calendar, the 2nd of september is a holiday (Labor Day) so the first date would actually be 03-09-2019.
Check the dates of the schedule:
```
for dt in schedule:
print(dt)
```
September 3rd, 2019 September 1st, 2020 September 1st, 2021
Notice that when building the schedule, some dates may be adjusted with the calendar and conventions, so if you want to check how the first coupon is being determined, you should actually use:
```
dayCount.yearFraction(schedule[0], schedule[1]) * couponRate * 100
```
which gives you:
8.95890410958904
Also, note that `Date + Period` is not a good idea because it will "blindly" add a period to a date and the result might not be a business day. Using `calendar.advance(Date, Period)` would be betterShown 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.