lballabio / QuantLib

The QuantLib C++ library
http://quantlib.org
Other
5.37k stars 1.8k forks source link

Specify first coupon date for FixedRateBond #1406

Closed flcong closed 2 years ago

flcong commented 2 years ago

I am using python quantlib package to model US treasury bonds. I use the FixedRateBond class for Treasury bonds and generate cash flows using its cashflows() method. However, for US treasury bonds, sometimes the first coupon date is later than the first same day of the year after dated date. For example, for a bond with dated date on 1971-09-08 and maturity date on 1976-11-15 and semi-annual coupon payments, the first coupon payment was made on 1972-05-15, not on 1971-11-15. If I construct the FixedRateBond object using

Bond = ql.FixedRateBond(
    0,
    ql.UnitedStates(),
    100,
    ql.Date(8, 9, 1971),
    ql.Date(15, 11, 1976),
    ql.Period(ql.Semiannual),
    [6.25 / 100],
    ql.ActualActual(ql.ActualActual.ISMA),
    ql.Unadjusted,
    ql.Unadjusted,
)

Then, in its cashflows() method, the first payment is made on 1971-11-15. So I wonder if there is a way to specific the first coupon date when constructing an FixedRateBond object, or when using the cashflows() method? I checked the QuantLib documentation but didn't find a suitable input parameter in the constuctor.

Thank you very much!

boring-cyborg[bot] commented 2 years ago

Thanks for posting! It might take a while before we look at your issue, so don't worry if there seems to be no feedback. We'll get to it.

BohlSeb commented 2 years ago

Hi, i am not sure if this entirely solves your problem but you can specifiy the first coupon date. This can be done by specifying it in the construction of a schedule. This schedule can then be passed to the corresponding constructor of FixedRateBond (the third constructor here: https://rkapl123.github.io/QLAnnotatedSource/d8/df8/class_quant_lib_1_1_fixed_rate_bond.html#a3b40150d959cb93a73e20ae823a6b320) like so:

START = ql.Date(8, 9, 1971)
END = ql.Date(15, 11, 1976)
FIRST_COUPON_DATE = ql.Date(15, 5, 1972)

PERIOD = ql.Period(ql.Semiannual)
CALENDAR = ql.UnitedStates()
DAY_COUNT = ql.ActualActual(ql.ActualActual.ISMA)
CONVENTION = ql.Unadjusted
SETTLEMENT_DAYS = 0
END_OF_MONTH = False
RULE = ql.DateGeneration.Backward

RATE = 6.25 / 100
AMOUNT = 100.0

SCHEDULE = ql.Schedule(
    START,
    END,
    PERIOD,
    CALENDAR,
    CONVENTION,
    CONVENTION,
    RULE,
    END_OF_MONTH,
    FIRST_COUPON_DATE,  # <- firstCouponDate (optional schedule arg)
    ql.Date()           # <- nextToLastCouponDate (optional schedule arg)
)

BOND = ql.FixedRateBond(
    SETTLEMENT_DAYS,
    AMOUNT,
    SCHEDULE,
    [RATE],
    DAY_COUNT,
    CONVENTION  # <- if you want Unadjusted as the payment convention, default is Following
)

for cash_flow in BOND.cashflows():
    try:
        fixed_cpn: ql.FixedRateCoupon = ql.as_fixed_rate_coupon(cash_flow)
        print(
            ' || '.join(
                [
                    f'Accrual start: {fixed_cpn.accrualStartDate()}',
                    f'Accrual end: {fixed_cpn.accrualEndDate()}',
                    f'Accrual time: {fixed_cpn.accrualPeriod()}',
                    f'Payment date: {fixed_cpn.date()}',
                    f'Rate: {fixed_cpn.interestRate()}'
                ]
            )
        )
    except AttributeError:
        print(f'Redemption on {cash_flow.date()}: {cash_flow.amount()}')
flcong commented 2 years ago

Hi, i am not sure if this entirely solves your problem but you can specifiy the first coupon date. This can be done by specifying it in the construction of a schedule. This schedule can then be passed to the corresponding constructor of FixedRateBond (the third constructor here: https://rkapl123.github.io/QLAnnotatedSource/d8/df8/class_quant_lib_1_1_fixed_rate_bond.html#a3b40150d959cb93a73e20ae823a6b320) like so:

START = ql.Date(8, 9, 1971)
END = ql.Date(15, 11, 1976)
FIRST_COUPON_DATE = ql.Date(15, 5, 1972)

PERIOD = ql.Period(ql.Semiannual)
CALENDAR = ql.UnitedStates()
DAY_COUNT = ql.ActualActual(ql.ActualActual.ISMA)
CONVENTION = ql.Unadjusted
SETTLEMENT_DAYS = 0
END_OF_MONTH = False
RULE = ql.DateGeneration.Backward

RATE = 6.25 / 100
AMOUNT = 100.0

SCHEDULE = ql.Schedule(
    START,
    END,
    PERIOD,
    CALENDAR,
    CONVENTION,
    CONVENTION,
    RULE,
    END_OF_MONTH,
    FIRST_COUPON_DATE,  # <- firstCouponDate (optional schedule arg)
    ql.Date()           # <- nextToLastCouponDate (optional schedule arg)
)

BOND = ql.FixedRateBond(
    SETTLEMENT_DAYS,
    AMOUNT,
    SCHEDULE,
    [RATE],
    DAY_COUNT,
    CONVENTION  # <- if you want Unadjusted as the payment convention, default is Following
)

for cash_flow in BOND.cashflows():
    try:
        fixed_cpn: ql.FixedRateCoupon = ql.as_fixed_rate_coupon(cash_flow)
        print(
            ' || '.join(
                [
                    f'Accrual start: {fixed_cpn.accrualStartDate()}',
                    f'Accrual end: {fixed_cpn.accrualEndDate()}',
                    f'Accrual time: {fixed_cpn.accrualPeriod()}',
                    f'Payment date: {fixed_cpn.date()}',
                    f'Rate: {fixed_cpn.interestRate()}'
                ]
            )
        )
    except AttributeError:
        print(f'Redemption on {cash_flow.date()}: {cash_flow.amount()}')

Thank you! It looks good to me.