-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Open
Labels
Description
When one (accidentally) passes a paymentDate earlier than end date to OvernightIndexedCoupon, amount and rate calculated byCompoundingOvernightIndexedCouponPricer will be inf or nan.
It's because swapletRate() = averageRate(accrualEndDate) divides by accruedPeriod(accrualEndDate) which will be zero in that case.
Possible fixes
-
Fail fast
Add a constructor guard:QL_REQUIRE(paymentDate_ >= accrualEndDate_, "Payment date cannot be earlier than accrual end date");
-
Let
averageRate(date)calculate the accrued period on its ownaccruedPeriod = yearFraction(accrualStartDate, std::min(accrualEndDate, date), refStart, refEnd);
If a change should be made, I would be happy to address it in pull request. I would tend towards option 2, since it would be consistent with IborCoupon where the problem does not occur.
Here is a small example
import math
import QuantLib as ql
ACCRUAL_START = ql.Date(18, 9, 2025)
ql.Settings.instance().evaluationDate = ACCRUAL_START
H = ql.YieldTermStructureHandle(ql.FlatForward(ACCRUAL_START, 0.05, ql.Actual365Fixed()))
ESTR = ql.Estr(H)
CAL = ql.WeekendsOnly()
ACCRUAL_END = CAL.advance(ACCRUAL_START, ql.Period(6, ql.Months))
PAYMENT_DATE = CAL.advance(ACCRUAL_END, -1, ql.Days) # Whoops
COUPON = ql.OvernightIndexedCoupon(PAYMENT_DATE, 1.0, ACCRUAL_START, ACCRUAL_END, ESTR)
print(f'{ACCRUAL_START = }') # Date(18,9,2025)
print(f'{ACCRUAL_END = }') # Date(18,3,2026)
print(f'{PAYMENT_DATE = }') # Date(17,3,2026)
print(math.isinf(COUPON.amount())) # True
print(math.isinf(COUPON.rate())) # True
# The problem does not occur for IborCoupons
EURIBOR = ql.Euribor6M(H)
EURIBOR.addFixing(ql.Date(16, 9, 2025), 0.05)
IBOR_COUPON = ql.IborCoupon(PAYMENT_DATE, 1.0, ACCRUAL_START, ACCRUAL_END, 2, EURIBOR)
IBOR_COUPON.setPricer(ql.BlackIborCouponPricer())
print(IBOR_COUPON.rate()) # 0.05
print(IBOR_COUPON.amount()) # 0.02513888888888889