Equalizing Bond Coupons with a Schedule-Based Day Count
Summary
The document addresses unequal coupon amounts generated by QuantLib’s amortizing fixed-rate bond constructor when monthly payments start on the last day of a month. The example shows February’s short length shifting interest between adjacent payments, even though the other monthly amounts match. Changing the business-day adjustment and day-count convention alone did not resolve the behavior in the reported setup.
The proposed remedy is to build an explicit backward schedule from issue date to maturity, preserving end-of-month treatment, then use an Actual/Actual ISMA day-count convention tied to that schedule. The bond is constructed from the schedule, coupon rate, and day-count convention rather than relying on the simpler period-based constructor. The answer also points out that a genuinely amortizing bond generally needs a sequence of changing notionals; with a single nominal amount, the instrument is effectively a plain fixed-rate bond. The document offers a practical QuantLib configuration, but does not provide a rerun showing the resulting payment amounts or discuss other schedule conventions.
Key ideas
- An end-of-month start can produce uneven monthly coupon accrual around February.
- The suggested setup uses an explicit schedule with end-of-month handling.
- Actual/Actual ISMA should be constructed with the schedule for this example.
- An amortizing bond generally requires multiple nominal amounts to represent principal reductions.
Tags
Full text
# quantlib: make AmortizingFixedRateBond coupon payments equal
# quantlib: make AmortizingFixedRateBond coupon payments equal
I found some unexpected result when trying to call AmortizingFixedRateBond wiht daily coupon payments, but starting in the last day of the month.
```
settlementDays = 0
calendar = ql.Russia()
nominal = 1000
startDate = ql.Date(31, 8, 2021) #ql.Date.todaysDate()
duration = ql.Period('12m')
frequency = ql.Monthly
interest = 0.07
daytimeConvention = ql.Thirty360()
businessAdjustment = ql.ModifiedFollowing
annuityAsBond = ql.AmortizingFixedRateBond(settlementDays, calendar, nominal, startDate, duration, frequency, interest, daytimeConvention, businessAdjustment, ql.Date())
leg = ql.Leg([*annuityAsBond.cashflows()])
df = pd.DataFrame({'Date': [c.date().to_date() for c in leg],
'Payment': [c.amount() for c in leg]})
df = df.groupby('Date').sum()
```
Output:
```
Payment
Date
2021-09-30 86.526746
2021-10-29 86.526746
2021-11-30 86.526746
2021-12-31 86.526746
2022-01-31 86.526746
2022-02-28 **86.296602**
2022-03-31 **86.823502**
2022-04-29 86.526746
2022-05-31 86.526746
2022-06-30 86.526746
2022-07-29 86.526746
2022-08-31 86.526746
```
As you see, the annuity calculator breaks a little bit (basically it transfers a single day interest from one month to another due to shortness of the February). At the same time, it is totally working as expected if `startDate` is not the end of the month. I tried different combinations of the convention and businessAdjustment, but this end of month trick is persistent. Is there a way around it to obtain correct coupons?
## Answer by ql.user2511 (score 1)
https://quant.stackexchange.com/a/67732
You should use the following day convention, but first construct your schedule using the issue date and the maturity date of the bond.
```
schedule = ql.Schedule(issueDate, maturityDate, ql.Period(frequency), calendar, businessAdjustment, businessAdjustment, ql.DateGeneration.Backward, True)
dayConvention = ql.ActualActual(ql.ActualActual.ISMA, schedule)
```
Moreover, change your `annuityAsBond` to the following:
```
annuityAsBond = ql.AmortizingFixedRateBond(settlementDays, [nominal], schedule, [interest], dayConvention, businessAdjustment)
```
Also, if it is an amortizing fixed rate bond, shouldn't there be more than one nominal? Else, it becomes a simple fixed rate bond.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.