/** * 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.financial.analytics.ircurve.NextQuarterAdjuster; import com.opengamma.financial.analytics.model.irfutureoption.FutureOptionUtils; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.util.ArgumentChecker; /** * Expiry calculator for futures and options on the VIX, the CBOE Volatility Index. <p> * VX Futures, traded on CFE, are specified here: http://cfe.cboe.com/Products/Spec_VIX.aspx <p> * VIX Index Options, traded on CBOE, are specified here: <p> * <p> * Contract Months = The Exchange may list for trading up to nine near-term serial months and five months on the February quarterly cycle for the VIX futures contract.<p> * Expiry = The Wednesday that is thirty days prior to the third Friday of the calendar month immediately following the month in which the contract expires ("Final Settlement Date"). * If the third Friday of the month subsequent to expiration of the applicable VIX futures contract is a CBOE holiday, expiry shall be thirty days prior to the CBOE business day preceding that Friday. * */ public final class VixFutureAndOptionExpiryCalculator implements ExchangeTradedInstrumentExpiryCalculator { private static final int N_SERIAL_EXPIRIES = 9; private static final Set<Month> QUARTERLY_CYCLE_MONTHS = EnumSet.of(Month.FEBRUARY, Month.MAY, Month.AUGUST, Month.NOVEMBER); 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 VixFutureAndOptionExpiryCalculator INSTANCE = new VixFutureAndOptionExpiryCalculator(); /** @return the singleton instance of the calculator, not null */ public static VixFutureAndOptionExpiryCalculator getInstance() { return INSTANCE; } /** * Restricted constructor. */ private VixFutureAndOptionExpiryCalculator() { } @Override /** * Gets monthly expiries for the first N_SERIAL_MONTHS, then switches to quarterly along the FEBRUARY 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.isTrue(n > 0, "n must be greater than 0."); if (n <= N_SERIAL_EXPIRIES) { // We look for monthly expiries return getMonthlyExpiry(n, today, holidayCalendar); } // And Quarterly expiries thereafter final int nthExpiryAfterSerialContracts = n - N_SERIAL_EXPIRIES; final LocalDate lastSerialExpiry = getMonthlyExpiry(N_SERIAL_EXPIRIES, today, holidayCalendar); return getQuarterlyExpiry(nthExpiryAfterSerialContracts, lastSerialExpiry, holidayCalendar); } @Override public LocalDate getExpiryMonth(int n, LocalDate today) { return getExpiryDate(n, today, FutureOptionUtils.WEEKDAYS); } /** * 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 * @param holidayCalendar calendar containing holidays * @return The expiry date of the nth monthly instrument */ private LocalDate getMonthlyExpiry(final int nthExpiry, final LocalDate date, final Calendar holidayCalendar) { ArgumentChecker.notNegativeOrZero(nthExpiry, "nth expiry"); ArgumentChecker.notNull(date, "date"); ArgumentChecker.notNull(holidayCalendar, "holidayCalendar"); // Compute the expiry of valuationDate's month LocalDate nextExpiry = getNextSerialExpiry(date, holidayCalendar); if (!nextExpiry.isAfter(date)) { // If it is not strictly after valuationDate... nextExpiry = getNextSerialExpiry(date.plusMonths(1), holidayCalendar); } if (nthExpiry == 1) { return nextExpiry; } return getNextSerialExpiry(nextExpiry.plusMonths(nthExpiry - 1), holidayCalendar); } // Return expiryDate that is 30 days before the 3rd Friday (or previous good day if holiday) of month following date private LocalDate getNextSerialExpiry(final LocalDate date, final Calendar holidayCalendar) { // Compute the expiry of valuationDate's month LocalDate following3rdFriday = date.plusMonths(1).with(s_dayOfMonthAdjuster); // 3rd Friday of following month while (!holidayCalendar.isWorkingDay(following3rdFriday)) { following3rdFriday = following3rdFriday.minusDays(1); // previous good day } return following3rdFriday.minusDays(30); // 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 getNextSerialExpiry(nthExpiryMonth, holidayCalendar); } @Override public String getName() { return this.getClass().getName(); } }