StubConvention required for smart front / short final stub?

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

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

1 Like

That’s great thank you for the quick fix

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);