Skip to content

OvernightIndexedCoupon: amount() and rate() return inf/nan if paymentDate < accrualEndDate #2328

@BohlSeb

Description

@BohlSeb

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

  1. Fail fast
    Add a constructor guard:

    QL_REQUIRE(paymentDate_ >= accrualEndDate_,
               "Payment date cannot be earlier than accrual end date");
  2. Let averageRate(date) calculate the accrued period on its own

    accruedPeriod = 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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions