/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.convention.expirycalc;
import java.util.Arrays;
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.financial.convention.calendar.Calendar;
import com.opengamma.util.ArgumentChecker;
/**
* Expiry calculator for soybean future options.
*/
public final class SoybeanFutureOptionExpiryCalculator implements ExchangeTradedInstrumentExpiryCalculator {
/** Name of the calculator */
public static final String NAME = "SoybeanFutureOptionExpiryCalculator";
/** Singleton. */
private static final SoybeanFutureOptionExpiryCalculator INSTANCE = new SoybeanFutureOptionExpiryCalculator();
/** Adjuster. */
private static final TemporalAdjuster LAST_DAY_ADJUSTER = TemporalAdjusters.lastDayOfMonth();
/** Adjuster. */
private static final TemporalAdjuster PREVIOUS_OR_CURRENT_FRIDAY_ADJUSTER = TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY);
/** Adjuster. */
private static final TemporalAdjuster PREVIOUS_FRIDAY_ADJUSTER = TemporalAdjusters.previous(DayOfWeek.FRIDAY);
/** Months when futures expire. */
private static final Month[] SOYBEAN_FUTURE_EXPIRY_MONTHS = {
Month.JANUARY, Month.MARCH, Month.MAY, Month.JULY,
Month.AUGUST, Month.SEPTEMBER, Month.NOVEMBER
};
/**
* Gets the singleton instance.
*
* @return the instance, not null
*/
public static SoybeanFutureOptionExpiryCalculator getInstance() {
return INSTANCE;
}
/**
* Restricted constructor.
*/
private SoybeanFutureOptionExpiryCalculator() {
}
//-------------------------------------------------------------------------
/**
* Expiry date of Soybean Future Options:
* The last Friday which precedes by at least two business days the last business day of the month preceding the option month.
* See http://www.cmegroup.com/trading/agricultural/grain-and-oilseed/soybean_contractSpecs_options.html#prodType=AME
* TODO Confirm adjustment made if Friday is not a business day. We use the business day before
*
* @param n the n'th expiry date after today, greater than zero
* @param today the valuation date, not null
* @param holidayCalendar the holiday calendar, not null
* @return the expiry date, not null
*/
@Override
public LocalDate getExpiryDate(final int n, final LocalDate today, final Calendar holidayCalendar) {
ArgumentChecker.isTrue(n > 0, "n must be greater than zero; have {}", n);
ArgumentChecker.notNull(today, "today");
ArgumentChecker.notNull(holidayCalendar, "holiday calendar");
final LocalDate expiryMonth = getExpiryMonth(n, today);
final LocalDate actualExpiryMonth = expiryMonth.minusMonths(1);
final LocalDate lastDayOfMonth = actualExpiryMonth.with(LAST_DAY_ADJUSTER);
final LocalDate lastFridayOfMonth = lastDayOfMonth.with(PREVIOUS_OR_CURRENT_FRIDAY_ADJUSTER);
LocalDate expiryDate = lastFridayOfMonth;
int nBusinessDays = 0;
LocalDate date = lastFridayOfMonth.plusDays(1);
while (!date.isAfter(lastDayOfMonth)) {
if (holidayCalendar.isWorkingDay(date)) {
nBusinessDays++;
}
if (nBusinessDays >= 2) {
while (!holidayCalendar.isWorkingDay(expiryDate)) {
expiryDate = expiryDate.minusDays(1);
}
return expiryDate;
}
date = date.plusDays(1);
}
LocalDate result = expiryDate.with(PREVIOUS_FRIDAY_ADJUSTER);
while (!holidayCalendar.isWorkingDay(result)) {
result = result.minusDays(1);
}
return result;
}
@Override
public LocalDate getExpiryMonth(final int n, final LocalDate today) {
ArgumentChecker.isTrue(n > 0, "n must be greater than zero");
ArgumentChecker.notNull(today, "today");
// There are 3 serial options
if (n < 4) {
return today.plusMonths(n); //
}
int m = n - 3;
LocalDate expiryDate = today.plusMonths(3);
while (m > 0) {
expiryDate = getNextExpiryMonth(expiryDate);
m--;
}
return expiryDate;
}
private LocalDate getNextExpiryMonth(final LocalDate dtCurrent) {
Month mthCurrent = dtCurrent.getMonth();
int idx = Arrays.binarySearch(SOYBEAN_FUTURE_EXPIRY_MONTHS, mthCurrent);
if (idx >= (SOYBEAN_FUTURE_EXPIRY_MONTHS.length - 1)) {
return LocalDate.of(dtCurrent.getYear() + 1, Month.JANUARY, dtCurrent.getDayOfMonth());
} else if (idx >= 0) {
return dtCurrent.with(SOYBEAN_FUTURE_EXPIRY_MONTHS[idx + 1]);
} else {
return dtCurrent.with(SOYBEAN_FUTURE_EXPIRY_MONTHS[-1 - idx]);
}
}
@Override
public String getName() {
return NAME;
}
}