/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model;
import org.threeten.bp.DayOfWeek;
import org.threeten.bp.LocalDate;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.util.time.TimeCalculator;
import com.opengamma.financial.analytics.ircurve.NextExpiryAdjuster;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.expirycalc.ExchangeTradedInstrumentExpiryCalculator;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.Tenor;
/**
* Utility Class for computing Expiries of Future Options from ordinals (i.e. nth future after valuationDate)
* For IR Options use: TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.WEDNESDAY), new NextExpiryAdjuster()
* For Equity Options use: new SaturdayAfterThirdFridayAdjuster(), new NextEquityExpiryAdjuster()
*/
//TODO there is far too much hard-coding of assumptions (e.g. when to switch from serial to quarterly in this class.
// Add information to the surface instrument provider, not in here
public final class FutureOptionExpiries implements ExchangeTradedInstrumentExpiryCalculator {
/** Instance of {@code FutureOptionExpiries} used for Interest Rate Future Options. (Expiries on 3rd Wednesdays) */
public static final FutureOptionExpiries IR = FutureOptionExpiries.of(new NextExpiryAdjuster(3, DayOfWeek.WEDNESDAY));
/** Instance of {@code FutureOptionExpiries} used for Equity Future Options. (Expiries on Saturdays after 3rd Fridays) */
public static final FutureOptionExpiries EQUITY = FutureOptionExpiries.of(new NextExpiryAdjuster(3, DayOfWeek.FRIDAY, 1));
/** Instance of {@code FutureOptionExpiries} used for Equity Futures. (Expiries on 3rd Fridays) */
public static final FutureOptionExpiries EQUITY_FUTURE = FutureOptionExpiries.of(new NextExpiryAdjuster(3, DayOfWeek.FRIDAY));
/** The adjuster moves forward to the next IMM month, to the specified day in month*/
private final NextExpiryAdjuster _nextExpiryAdjuster;
/** _nextExpiryAdjuster.getDayOfMonthAdjuster() moves date to day within month. eg 3rd Wednesday */
/**
* Utility Class for computing Expiries of Future Options from ordinals (i.e. nth future after valuationDate)
* @param nextExpiryAdjuster Examples: NextExpiryAdjuster, NextExpiryAdjuster
*/
private FutureOptionExpiries(final NextExpiryAdjuster nextExpiryAdjuster) {
ArgumentChecker.notNull(nextExpiryAdjuster, "nextExpiryAdjuster");
_nextExpiryAdjuster = nextExpiryAdjuster;
}
/**
* Create instance of {@code FutureOptionExpiries},
* a utility Class for computing Expiries of Future Options from ordinals (i.e. nth option after valuationDate)
* @param nextExpiryAdjuster Examples: NextExpiryAdjuster, NextExpiryAdjuster
* @return the FutureOptionExpiries class, never null
*/
public static FutureOptionExpiries of(final NextExpiryAdjuster nextExpiryAdjuster) {
ArgumentChecker.notNull(nextExpiryAdjuster, "nextExpiryAdjuster");
return new FutureOptionExpiries(nextExpiryAdjuster);
}
/**
* Create instance of {@code FutureOptionExpiries},
* a utility Class for computing Expiries of Future Options from ordinals (i.e. nth option after valuationDate). <p>
* Specify the day and week in the month, plus an offset in number of days. <p>
* e.g. (3,DayOfWeek.FRIDAY,1) is the Saturday after the 3rd Friday in the month. This is different from the 3rd Saturday.
* @param week Ordinal of week in month, beginning from 1.
* @param day DayOfWeek
* @param offset Integer offset, positive or negative from the result of week,day.
* @return New instance of FutureOptionExpiries
*/
public static FutureOptionExpiries of(final int week, final DayOfWeek day, final int offset) {
final NextExpiryAdjuster nextExpiryAdjuster = new NextExpiryAdjuster(week, day, offset);
return new FutureOptionExpiries(nextExpiryAdjuster);
}
/**
* @deprecated
* Compute time between now and future or future option's settlement date,
* typically two business days before the third wednesday of the expiry month.
* @param n nth Future after now
* @param today Valuation Date
* @return OG-Analytic Time in years between now and the future's settlement date
*/
@Deprecated
public Double getFutureOptionTtm(final int n, final LocalDate today) {
final LocalDate expiry = getFutureOptionExpiry(n, today);
final LocalDate previousMonday = expiry;
return TimeCalculator.getTimeBetween(today, previousMonday);
}
/**
* Compute time between now and future or future option's settlement date,
* @param n nth Future after now
* @param today Valuation Date
* @param tenor The tenor of future option resets
* @return OG-Analytic Time in years between now and the future's settlement date
*/
public Double getFutureOptionTtm(final int n, final LocalDate today, final Tenor tenor) {
final LocalDate expiry = getExpiry(n, today, tenor);
return TimeCalculator.getTimeBetween(today, expiry);
}
/**
* Compute time between now and future or future option's settlement date,
* typically two business days before the third wednesday of the expiry month.
* @param n nth Future after now
* @param today Valuation Date
* @return OG-Analytic Time in years between now and the future's settlement date
*/
public Double getFutureTtm(final int n, final LocalDate today) {
final LocalDate expiry = getQuarterlyExpiry(n, today);
final LocalDate previousMonday = expiry.minusDays(2); //TODO this should take a calendar and do two business days, and should use a convention for the number of days
return TimeCalculator.getTimeBetween(today, previousMonday);
}
/**
* @deprecated Hard-codes in assumptions about which expiries to look for
* Gets monthly expiries for the first six months, then switch to quarterly
* @param nthFuture nth future
* @param valDate The date from which to start
* @return the expiry date of the nth option
*/
@Deprecated
public LocalDate getFutureOptionExpiry(final int nthFuture, final LocalDate valDate) {
ArgumentChecker.isTrue(nthFuture > 0, "nthFuture must be greater than 0.");
if (nthFuture <= 6) { // We look for expiries in the first 6 serial months after curveDate
return getMonthlyExpiry(nthFuture, valDate);
}
// And for Quarterly expiries thereafter
final int nthExpiryAfterSixMonths = nthFuture - 6;
final LocalDate sixMonthsForward = getMonthlyExpiry(6, valDate);
return getQuarterlyExpiry(nthExpiryAfterSixMonths, sixMonthsForward);
}
/**
* @deprecated Hard-codes in assumptions about which expiries to look for
* Get monthly expiries for the first 6 expires then look for January yearly ones
* CME options seem to follow no clear pattern some switch to yearly after 4 monthly options and some have 8 or more
* pick a value in the middle so hopefully we get a reasonable number of valid expires for all options
* @param nthFuture nth future in the future
* @param valDate date to start from
* @return expiry the expiry date of the nth option
*/
@Deprecated
public LocalDate getCMEEquityFutureOptionExpiry(final int nthFuture, final LocalDate valDate) {
ArgumentChecker.isTrue(nthFuture > 0, "nthFuture must be greater than 0.");
if (nthFuture <= 6) { // We look for expiries in the first 6 serial months after curveDate
return getMonthlyExpiry(nthFuture, valDate);
}
// And for yearly January expiry after that
final int nExpiryDone = nthFuture - 6;
final LocalDate nextYear = LocalDate.of(valDate.getYear() + nExpiryDone, 1, 1);
return getMonthlyExpiry(1, nextYear);
}
/**
* @deprecated Hard-codes in assumptions about which expiries to look for.
* Get n'th future expiry.
* Only supports One Chicago equity futures. 2 serial months and 2 quarterly
* http://www.onechicago.com/?page_id=22
* @param nthFuture nth future in the future
* @param valDate date to start from
* @return expiry the expiry date of the nth option
*/
@Deprecated
public LocalDate getOneChicagoEquityFutureExpiry(final int nthFuture, final LocalDate valDate) {
ArgumentChecker.isTrue(nthFuture > 0, "nthFuture must be greater than 0.");
if (nthFuture > 4) {
throw new OpenGammaRuntimeException("Can only have max 4 futures");
}
if (nthFuture <= 2) {
return getMonthlyExpiry(nthFuture, valDate); // 2 serial months
}
// quarterly
final int nQuarterlyLeft = nthFuture - 2;
final LocalDate twoMonthsForward = getMonthlyExpiry(2, valDate);
return getQuarterlyExpiry(nQuarterlyLeft, twoMonthsForward);
}
/**
* Given a tenor, returns the nth expiry from the date according to the expiry rule. Only handles monthly and quarterly expiries at the moment.
* @param nthExpiry The nth expiry, greater than zero
* @param date The date, not null
* @param tenor The tenor, not null
* @return The expiry date
* @throws IllegalArgumentException If the tenor is not monthly or quarterly
*/
public LocalDate getExpiry(final int nthExpiry, final LocalDate date, final Tenor tenor) {
ArgumentChecker.notNegativeOrZero(nthExpiry, "nth expiry");
ArgumentChecker.notNull(date, "date");
ArgumentChecker.notNull(tenor, "tenor");
if (tenor.equals(Tenor.ONE_MONTH)) {
return getMonthlyExpiry(nthExpiry, date);
}
if (tenor.equals(Tenor.THREE_MONTHS)) {
return getQuarterlyExpiry(nthExpiry, date);
}
throw new IllegalArgumentException("Could not handle frequency type " + tenor);
}
/**
* Given the expiry rule, returns the expiry date of the nth month.
* @param nthExpiry The nth expiry, greater than zero
* @param date The date, not null
* @return The expiry date of the nth monthly instrument
*/
public LocalDate getMonthlyExpiry(final int nthExpiry, final LocalDate date) {
ArgumentChecker.notNegativeOrZero(nthExpiry, "nth expiry");
ArgumentChecker.notNull(date, "date");
LocalDate expiry = date.with(_nextExpiryAdjuster.getDayOfMonthAdjuster()); // Compute the expiry of valuationDate's month
if (!expiry.isAfter(date)) { // If it is not strictly after valuationDate...
expiry = (date.plusMonths(1)).with(_nextExpiryAdjuster.getDayOfMonthAdjuster());
}
if (nthExpiry > 1) {
expiry = (expiry.plusMonths(nthExpiry - 1)).with(_nextExpiryAdjuster.getDayOfMonthAdjuster());
}
return expiry;
}
/**
* Given the expiry rule, returns the expiry date of the nth quarter.
* @param nthExpiry The nth expiry, greater than zero
* @param date The date, not null
* @return The expiry date of the nth quarterly instrument
*/
public LocalDate getQuarterlyExpiry(final int nthExpiry, final LocalDate date) {
ArgumentChecker.notNegativeOrZero(nthExpiry, "nth expiry");
ArgumentChecker.notNull(date, "date");
LocalDate expiry = date.with(_nextExpiryAdjuster);
for (int i = 1; i < nthExpiry; i++) {
expiry = (expiry.plusDays(7)).with(_nextExpiryAdjuster);
}
return expiry;
}
@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");
LocalDate expiry = getExpiryMonth(n, today);
while (!holidayCalendar.isWorkingDay(expiry)) {
expiry = expiry.minusDays(1);
}
return expiry;
}
@Override
public LocalDate getExpiryMonth(final int n, final LocalDate today) {
return getMonthlyExpiry(n, today);
}
@Override
public String getName() {
return "FutureOptionExpiries with " + _nextExpiryAdjuster.toString();
}
/**
* Produced n'th expiry date after today of monthly or quarterly tenor.<p>
* Assumes a previous business day convention
* @param n n'th expiry
* @param today valuation date
* @param tenor accepts ONE_MONTH or THREE_MONTHS
* @param holidayCalendar Calendar
* @return n'th expiry date
*/
public LocalDate getExpiryDate(final int n, final LocalDate today, final Tenor tenor, final Calendar holidayCalendar) {
ArgumentChecker.isTrue(n > 0, "n must be greater than zero; have {}", n);
ArgumentChecker.notNull(today, "today");
ArgumentChecker.notNull(tenor, "tenor");
ArgumentChecker.notNull(holidayCalendar, "holiday calendar");
LocalDate expiry;
if (tenor.equals(Tenor.ONE_MONTH)) {
expiry = getMonthlyExpiry(n, today);
} else if (tenor.equals(Tenor.THREE_MONTHS)) {
expiry = getQuarterlyExpiry(n, today);
} else {
throw new IllegalArgumentException("Could not handle frequency type " + tenor);
}
while (!holidayCalendar.isWorkingDay(expiry)) {
expiry = expiry.minusDays(1);
}
return expiry;
}
}