/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.ircurve;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Month;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.financial.analytics.ircurve.strips.DataFieldType;
import com.opengamma.financial.analytics.volatility.surface.BloombergFutureUtils;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalScheme;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.Tenor;
/**
* Provides market data ids for quarterly or monthly futures.
* This is similar to {@code BloombergFutureCurveInstrumentProvider} except no space is added between the ticker and the
* sector. It can be used with any scheme that uses the same month delivery codes as Bloomberg.
*/
public class FutureMonthCodeCurveInstrumentProvider implements CurveInstrumentProvider {
/** Month codes for futures */
private static BiMap<Month, Character> s_monthCode;
/** The future prefix */
private final String _futurePrefix;
/** The future suffix */
private final String _futureSuffix;
/** The market data field */
private final String _dataField;
/** The data field type */
private final DataFieldType _fieldType;
/** The ticker scheme */
private final ExternalScheme _scheme;
static {
s_monthCode = HashBiMap.create();
s_monthCode.put(Month.JANUARY, 'F');
s_monthCode.put(Month.FEBRUARY, 'G');
s_monthCode.put(Month.MARCH, 'H');
s_monthCode.put(Month.APRIL, 'J');
s_monthCode.put(Month.MAY, 'K');
s_monthCode.put(Month.JUNE, 'M');
s_monthCode.put(Month.JULY, 'N');
s_monthCode.put(Month.AUGUST, 'Q');
s_monthCode.put(Month.SEPTEMBER, 'U');
s_monthCode.put(Month.OCTOBER, 'V');
s_monthCode.put(Month.NOVEMBER, 'X');
s_monthCode.put(Month.DECEMBER, 'Z');
}
/**
* Sets the market data field to {@link MarketDataRequirementNames#MARKET_VALUE}
*
* @param futurePrefix The future prefix, not null
* @param futureSuffix The future postfix, not null. Empty string for no ticker postfix, if a space is required
* between the month code and the sector e.g. for Bloomberg "Z4 Comdty", the sector would be " Comdty" (with a leading
* space). Note this is different from the BloombergFutureCurveInstrumentProvider which adds the space automatically.
* @param scheme The ticker scheme, not null
*/
public FutureMonthCodeCurveInstrumentProvider(final String futurePrefix, final String futureSuffix,
final ExternalScheme scheme) {
this(futurePrefix, futureSuffix, MarketDataRequirementNames.MARKET_VALUE, DataFieldType.OUTRIGHT, scheme);
}
/**
* @param futurePrefix The future prefix, not null
* @param futureSuffix The market sector postfix, not null. Empty string for no ticker postfix, if a space is required
* between the month code and the sector e.g. for Bloomberg "Z4 Comdty", the sector would be " Comdty" (with a leading
* space). Note this is different from the BloombergFutureCurveInstrumentProvider which adds the space automatically.
* @param dataField The market data field, not null
* @param fieldType The data field type, not null
* @param scheme The ticker scheme, not null
*/
public FutureMonthCodeCurveInstrumentProvider(final String futurePrefix, final String futureSuffix,
final String dataField, final DataFieldType fieldType,
final ExternalScheme scheme) {
ArgumentChecker.notNull(futurePrefix, "future prefix");
ArgumentChecker.notNull(futureSuffix, "market sector");
ArgumentChecker.notNull(dataField, "data field");
ArgumentChecker.notNull(fieldType, "field type");
ArgumentChecker.notNull(scheme, "scheme");
_futurePrefix = futurePrefix;
_futureSuffix = futureSuffix;
_dataField = dataField;
_fieldType = fieldType;
_scheme = scheme;
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor tenor) {
throw new OpenGammaRuntimeException("Only futures supported");
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor tenor, final int periodsPerYear, final boolean isPeriodicZeroDeposit) {
throw new OpenGammaRuntimeException("Only futures supported");
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor tenor, final int numQuarterlyFuturesFromTenor) {
return createQuarterlyIRFutureStrips(curveDate, tenor, numQuarterlyFuturesFromTenor, _futurePrefix, _futureSuffix);
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor startTenor, final Tenor futureTenor, final int numFuturesFromTenor) {
//TODO there must be a more elegant way to do this
if (futureTenor.equals(Tenor.THREE_MONTHS)) {
return createQuarterlyIRFutureStrips(curveDate, startTenor, numFuturesFromTenor, _futurePrefix, _futureSuffix);
} else if (futureTenor.equals(Tenor.ONE_MONTH)) {
return createMonthlyIRFutureStrips(curveDate, startTenor, numFuturesFromTenor, _futurePrefix, _futureSuffix);
}
throw new OpenGammaRuntimeException("Can only create ids for quarterly or monthly tenors");
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor tenor, final Tenor payTenor, final Tenor receiveTenor, final IndexType payIndexType,
final IndexType receiveIndexType) {
throw new OpenGammaRuntimeException("Only futures supported");
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor tenor, final Tenor resetTenor, final IndexType indexType) {
throw new OpenGammaRuntimeException("Only futures supported");
}
@Override
public ExternalId getInstrument(final LocalDate curveDate, final Tenor startTenor, final int startIMMPeriods, final int endIMMPeriods) {
throw new UnsupportedOperationException("Only futures supported");
}
@Override
public String getMarketDataField() {
return _dataField;
}
@Override
public DataFieldType getDataFieldType() {
return _fieldType;
}
private ExternalId createQuarterlyIRFutureStrips(final LocalDate curveDate, final Tenor tenor, final int numQuartlyFuturesFromTenor, final String prefix, final String postfix) {
final StringBuilder futureCode = new StringBuilder();
futureCode.append(prefix);
final LocalDate curveFutureStartDate = curveDate.plus(tenor.getPeriod());
final String expiryCode = BloombergFutureUtils.getQuarterlyExpiryCodeForFutures(prefix, numQuartlyFuturesFromTenor, curveFutureStartDate);
futureCode.append(expiryCode);
futureCode.append(postfix);
return ExternalId.of(_scheme, futureCode.toString());
}
private ExternalId createMonthlyIRFutureStrips(final LocalDate curveDate, final Tenor tenor, final int numMonthlyFuturesFromTenor, final String prefix, final String postfix) {
final StringBuilder futureCode = new StringBuilder();
futureCode.append(prefix);
final LocalDate curveFutureStartDate = curveDate.plus(tenor.getPeriod());
final String expiryCode = BloombergFutureUtils.getMonthlyExpiryCodeForFutures(prefix, numMonthlyFuturesFromTenor, curveFutureStartDate);
futureCode.append(expiryCode);
futureCode.append(postfix);
return ExternalId.of(_scheme, futureCode.toString());
}
/**
* Gets the future prefix.
* @return The future prefix
*/
public String getFuturePrefix() {
return _futurePrefix;
}
/**
* Gets the market sector postfix.
* @return The market sector postfix
*/
public String getFutureSuffix() {
return _futureSuffix;
}
/**
* Gets the ticker scheme
* @return The ticker scheme
*/
public ExternalScheme getScheme() {
return _scheme;
}
@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (!(o instanceof FutureMonthCodeCurveInstrumentProvider)) {
return false;
}
final FutureMonthCodeCurveInstrumentProvider other = (FutureMonthCodeCurveInstrumentProvider) o;
return getFuturePrefix().equals(other.getFuturePrefix()) &&
getFutureSuffix().equals(other.getFutureSuffix()) &&
getMarketDataField().equals(other.getMarketDataField()) &&
getDataFieldType() == other.getDataFieldType() &&
getScheme() == other.getScheme();
}
@Override
public int hashCode() {
return getFuturePrefix().hashCode() ^ getFutureSuffix().hashCode() ^ getMarketDataField().hashCode()
^ getDataFieldType().hashCode() ^ getScheme().hashCode() * (2 ^ 16);
}
}