1D OIS periodic schedule adjusted start/end issue

Hi,

Currently, we are trying to build a curve with 1D OIS node. It looks like when we pass the non-business day as trade date, the library throws an error regarding the duplicate adjusted dates:

 Schedule calculation resulted in duplicate adjusted dates [2020-02-10, 2020-02-10] from unadjusted dates [2020-02-09, 2020-02-10] using adjustment

So the question from our side, shouldn’t 1D be applied to the adjusted start date rather than the unadjusted one? I can see why that might be tricky, adjusted dates are known only after the resolve stage, while start and end dates are generated when the trade is created. Are there any ways to deal with such issues?

Thank you!

I’m not surprised the system doesn’t handle a one day OIS - normally the OIS is longer than that so the problem wouldn’t arise. Normally, the start date of the schedule is determined using a holiday calendar in the convention called by the curve node (FixedOvernightSwapConvention), so I’m not clear why you would see the start date as a holiday. I’d suggest debugging to see what FixedOvernightSwapConvention.createTrade returns as a start date, and whether that is seen as a holiday or not (check the holiday calendar against that used when adding to get the end date).

1 Like

Thanks for the quick response! This is interesting, then the start date should have been adjusted. Dates are calculated in the following block:

LocalDate spotValue = calculateSpotDateFromTradeDate(tradeDate, refData);
LocalDate startDate = spotValue.plus(periodToStart);
LocalDate endDate = startDate.plus(tenor.getPeriod()); 

The crucial line is the first one, spotValue is calculated as:

getSpotDateOffset().adjust(tradeDate, refData);

So the spot date offset controls the adjustment. I’ve looked into the conventions defined in StandardFixedOvernightSwapConventions. And it seems that spot date offset for all conventions is created using:

DaysAdjustment spotDateOffset = DaysAdjustment.ofBusinessDays(spotLag, calendar);

The static factory method used always sets the BDA to none:

public static DaysAdjustment ofBusinessDays(int numberOfDays, HolidayCalendarId holidayCalendar) {
   return new DaysAdjustment(numberOfDays, holidayCalendar, BusinessDayAdjustment.NONE);
}

This is interesting, convention`s spot date offset adjustment is always with BDA = NONE, while FixedRateSwapLegConvention holds BusinessDayAdjustment.of(MODIFIED_FOLLOWING, calendar). Are the conventions configured in the following way intentionally? Is that dictated by the market conventions?

Thank you!

I’m guessing the spotLag (numberOfDays) is zero, which is why it is a problem. Any non-zero value would adjust the date. So, that convention needs adjusting to use BusinessDayAdjustment following in the same calendar.

1 Like

That’s correct. But adjustment could be achieved in step 1 and/or step 2. Adjustment is implemented as:

public LocalDate adjust(LocalDate date, ReferenceData refData) {
    LocalDate added = calendar.resolve(refData).shift(date, days);  // step 1
    return adjustment.adjust(added, refData); // step 2
}

If numberOfDays is non-zero, variable added will be already a valid business day. And the second step has no effect (if the calendars are the same). Making numberOfDays non-zero forces trade date to be ALWAYS adjusted, but that might not be desirable. DaysAdjustment class is really flexible and supports various cases. For example in StandardTermDepositConventions all conventions uses the following approach:

  public static final TermDepositConvention GBP_SHORT_DEPOSIT_T0 = ImmutableTermDepositConvention.of(
  "GBP-ShortDeposit-T0",
  Currency.GBP,
  BusinessDayAdjustment.of(FOLLOWING, GBLO),
  ACT_365F,
  DaysAdjustment.ofBusinessDays(0, GBLO, BusinessDayAdjustment.of(FOLLOWING, GBLO)));

numberOfDays is zero, but BDA is clearly != NONE. So in this case date is going to be adjusted correctly even without the numberOfDays. Modifying locally the StandardFixedOvernightSwapConventions to use the same approach that is used in StandardTermDepositConventions solves the problem, but I’m not entirely sure if that is the most correct approach.

Thanks!

1 Like