/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.convention.expirycalc;
import java.util.EnumSet;
import java.util.Set;
import org.threeten.bp.DayOfWeek;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Month;
import org.threeten.bp.temporal.TemporalAdjuster;
import org.threeten.bp.temporal.TemporalAdjusters;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.financial.analytics.ircurve.NextQuarterAdjuster;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.util.ArgumentChecker;
/**
* Expiry calculator for futures (not options) on the Russell 2000 Index.<p>
* TF Futures, traded on the ICE, are specified here: https://www.theice.com/productguide/ProductSpec.shtml?specId=86 <p>
* <p>
* Contract Months = Four months in the March/June/September/December quarterly expiration cycle.<p>
* Last Trading Day = Third Friday of the expiration month. Trading in the expiring contract ceases at 9:30 a.m. ET on the Last Trading Day.
*/
public final class RussellFutureExpiryCalculator implements ExchangeTradedInstrumentExpiryCalculator {
private static final Set<Month> QUARTERLY_CYCLE_MONTHS = EnumSet.of(Month.MARCH, Month.JUNE, Month.SEPTEMBER, Month.DECEMBER);
private static final NextQuarterAdjuster s_nextQuarterAdjuster = new NextQuarterAdjuster(QUARTERLY_CYCLE_MONTHS);
private static final TemporalAdjuster s_dayOfMonthAdjuster = TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY);
private static final RussellFutureExpiryCalculator INSTANCE = new RussellFutureExpiryCalculator();
/** @return the singleton instance of the calculator, not null */
public static RussellFutureExpiryCalculator getInstance() {
return INSTANCE;
}
/**
* Restricted constructor.
*/
private RussellFutureExpiryCalculator() {
}
@Override
/**
* Quarterly expiries along March cycle
* @param nthFuture nth future
* @param valDate The date from which to start
* @return the expiry date of the nth option
*/
public LocalDate getExpiryDate(int n, LocalDate today, Calendar holidayCalendar) {
ArgumentChecker.notNegativeOrZero(n, "nth expiry");
ArgumentChecker.notNull(today, "date");
ArgumentChecker.notNull(holidayCalendar, "holidayCalendar");
LocalDate thirdFriday = getThirdFriday(today, holidayCalendar);
if (today.isAfter(thirdFriday)) { // If it is not on or after valuationDate...
thirdFriday = getThirdFriday(today.plusMonths(1), holidayCalendar);
}
int nQuartersRemaining = QUARTERLY_CYCLE_MONTHS.contains(Month.from(thirdFriday)) ? n - 1 : n;
if (nQuartersRemaining == 0) {
return thirdFriday;
}
return getQuarterlyExpiry(nQuartersRemaining, thirdFriday, holidayCalendar);
}
@Override
public LocalDate getExpiryMonth(int n, LocalDate today) {
throw new OpenGammaRuntimeException("Russell 2000 Index Mini Futures do not have monthly expiries");
}
// Return expiryDate that is the 3rd Friday (or previous good day if holiday) of month in which date falls
private LocalDate getThirdFriday(final LocalDate date, final Calendar holidayCalendar) {
// Compute the expiry of valuationDate's month
LocalDate following3rdFriday = date.with(s_dayOfMonthAdjuster); // 3rd Friday of month in which date falls
while (!holidayCalendar.isWorkingDay(following3rdFriday)) {
following3rdFriday = following3rdFriday.minusDays(1); // previous good day
}
return following3rdFriday; // expiry is 30 days before
}
private LocalDate getQuarterlyExpiry(int nthExpiryAfterSerialContracts, LocalDate lastSerialExpiry, Calendar holidayCalendar) {
// First find the nth quarter after the lastSerialExpiry
LocalDate nthExpiryMonth = lastSerialExpiry;
for (int n = nthExpiryAfterSerialContracts; n > 0; n--) {
nthExpiryMonth = nthExpiryMonth.with(s_nextQuarterAdjuster);
}
// Then find the expiry date in that month
return getThirdFriday(nthExpiryMonth, holidayCalendar);
}
@Override
public String getName() {
return this.getClass().getName();
}
}