/** * 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.Arrays; import java.util.Set; import java.util.TreeSet; 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 gold future contracts. */ public final class GoldFutureExpiryCalculator implements ExchangeTradedInstrumentExpiryCalculator { /** Name of the calculator */ public static final String NAME = "GoldFutureExpiryCalculator"; /** Singleton. */ private static final GoldFutureExpiryCalculator INSTANCE = new GoldFutureExpiryCalculator(); /** Adjuster. */ private static final TemporalAdjuster LAST_DAY_ADJUSTER = TemporalAdjusters.lastDayOfMonth(); /** * Gets the singleton instance. * * @return the instance, not null */ public static GoldFutureExpiryCalculator getInstance() { return INSTANCE; } /** * Restricted constructor. */ private GoldFutureExpiryCalculator() { } //------------------------------------------------------------------------- /** * Gets trading months (not static as depends on current date). * * @param now the date today, not null * @return the valid trading months, not null */ private Month[] getTradingMonths(final LocalDate now) { // this may need improvements as the year end approaches Set<Month> ret = new TreeSet<>(); ret.add(now.getMonth()); // this month ret.add(now.getMonth().plus(1)); // next month ret.add(now.getMonth().plus(2)); // next 2 months // February, April, August, and October in next 23 months ret.add(Month.FEBRUARY); ret.add(Month.APRIL); ret.add(Month.AUGUST); ret.add(Month.OCTOBER); // June and December falling in next 72 month period ret.add(Month.JUNE); ret.add(Month.DECEMBER); // assuming this gives enough valid dates so dont go round to next 12 month period return ret.toArray(new Month[0]); } /** * Expiry date of Soybean Futures: * The 3rd last business day of the month. * See http://www.cmegroup.com/trading/metals/precious/gold_contract_specifications.html * * @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"); LocalDate expiryDate = getExpiryMonth(n, today).with(LAST_DAY_ADJUSTER); int nBusinessDays = 3; if (holidayCalendar.isWorkingDay(expiryDate)) { nBusinessDays--; } // go back to 3 business days while (nBusinessDays > 0) { expiryDate = expiryDate.minusDays(1); if (holidayCalendar.isWorkingDay(expiryDate)) { nBusinessDays--; } } return expiryDate; } @Override public LocalDate getExpiryMonth(final int n, final LocalDate today) { ArgumentChecker.isTrue(n > 0, "n must be greater than zero"); ArgumentChecker.notNull(today, "today"); LocalDate expiryDate = today; Month[] validMonths = getTradingMonths(today); for (int m = n; m > 0; m--) { expiryDate = getNextExpiryMonth(validMonths, expiryDate); } return expiryDate; } private LocalDate getNextExpiryMonth(final Month[] validMonths, final LocalDate dtCurrent) { Month mthCurrent = dtCurrent.getMonth(); int idx = Arrays.binarySearch(validMonths, mthCurrent); if (Math.abs(idx) >= (validMonths.length - 1)) { return LocalDate.of(dtCurrent.getYear() + 1, validMonths[0], dtCurrent.getDayOfMonth()); } else if (idx >= 0) { return dtCurrent.with(validMonths[idx + 1]); } else { return dtCurrent.with(validMonths[-idx + 1]); } } @Override public String getName() { return NAME; } }