StubConvention required for smart front / short final stub?

#1

I am trying to create a SONIA swap for rolldown calculation purposes which has it’s cashflows aligned with a swap maturing on 2021-01-22.

For the Jan 21 swap Bloomberg has swap payments on 2020-01-22 and 2021-01-22 and running as of 2019-01-16 that means we need to use a SMART_FIRST stub to give two payments of 371 and 366 days.

For the rolldown swap the maturity is moved 3m to 2020-10-22 to give two payments of 371 and 274 days.

I have ended with code which examines the initial stub end date and final stub start date and when they are identical use SMART_FINAL convention because PeriodicSchedule requires ‘firstRegularStartDate’ < ‘lastRegularEndDate’ - this works in almost all cases but this is a specific example where it does not.

Can you please advise how to construct a SONIA swap without resorting to using RatePeriodSwapLeg with

  • Start Date: 2019-01-16
  • End Date: 2020-10-22
  • Coupon Payment Dates: 2020-01-22, 2020-10-22
1 Like
#2

There is no way to do this at present. Here is the PR

1 Like
#3

That’s great thank you for the quick fix

#4

I’m not sure this change completely fixes the issue, below is some code to replicate the specific issue which still fails with the error

Period ‘2020-01-22’ to ‘2020-01-22’ resulted in a disallowed stub with frequency ‘P12M’

final LocalDate startDate = LocalDate.of(2019, 1, 16);
// maturity of UKT 1.5 01/22/2021 Govt minus 3m
final LocalDate endDate = LocalDate.of(2020, 10, 22);
// start date of first regular coupon of swap with maturity date 2021-01-22
final LocalDate firstRegularStartDate = LocalDate.of(2020, 1, 22);
// end date of penultimate coupon of swap with maturity date 2021-01-22
final LocalDate lastRegularEndDate = LocalDate.of(2020, 1, 22);

final NotionalSchedule notional = NotionalSchedule.of(Currency.GBP, 10_000_000);

final SwapLeg fixedLeg = RateCalculationSwapLeg.builder()
        .payReceive(PayReceive.PAY)
        .accrualSchedule(PeriodicSchedule.builder()
                .startDate(startDate)
                .endDate(endDate)
                .frequency(Frequency.P12M)
                .businessDayAdjustment(BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, HolidayCalendarIds.GBLO))
                .stubConvention(StubConvention.BOTH)
                .firstRegularStartDate(firstRegularStartDate)
                .lastRegularEndDate(lastRegularEndDate)
                .rollConvention(RollConventions.DAY_22)
                .build())
        .paymentSchedule(PaymentSchedule.builder()
                .paymentFrequency(Frequency.P12M)
                .paymentDateOffset(DaysAdjustment.NONE)
                .build())
        .notionalSchedule(notional)
        .calculation(FixedRateCalculation.of(0d, DayCounts.ACT_365F))
        .build();

SwapLeg floatLeg = RateCalculationSwapLeg.builder()
        .payReceive(PayReceive.RECEIVE)
        .accrualSchedule(PeriodicSchedule.builder()
                .startDate(startDate)
                .endDate(endDate)
                .frequency(Frequency.P12M)
                .businessDayAdjustment(BusinessDayAdjustment.of(BusinessDayConventions.MODIFIED_FOLLOWING, HolidayCalendarIds.GBLO))
                .stubConvention(StubConvention.BOTH)
                .firstRegularStartDate(firstRegularStartDate)
                .lastRegularEndDate(lastRegularEndDate)
                .rollConvention(RollConventions.DAY_22)
                .build())
        .paymentSchedule(PaymentSchedule.builder()
                .paymentFrequency(Frequency.P12M)
                .paymentDateOffset(DaysAdjustment.NONE)
                .build())
        .notionalSchedule(notional)
        .calculation(OvernightRateCalculation.of(OvernightIndices.GBP_SONIA))
        .build();

final SwapTrade trade = SwapTrade.builder().info(TradeInfo.of(startDate)).product(Swap.of(fixedLeg, floatLeg)).build();
final ResolvedSwapTrade resolvedTrade = trade.resolve(referenceData);

final ResolvedSwapLeg resolvedFixedLeg = resolvedTrade.getProduct().getPayLeg().get();
final List<LocalDate> payDates = resolvedFixedLeg.getPaymentPeriods().stream().map(e -> e.getPaymentDate()).collect(Collectors.toList());
final ResolvedSwapLeg resolvedFloatLeg = resolvedTrade.getProduct().getReceiveLeg().get();
final List<LocalDate> recDates = resolvedFloatLeg.getPaymentPeriods().stream().map(e -> e.getPaymentDate()).collect(Collectors.toList());

assertThat(payDates).containsExactly(firstRegularStartDate, endDate);
assertThat(recDates).containsExactly(firstRegularStartDate, endDate);