package org.marketcetera.trade; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.regex.Pattern; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.marketcetera.util.misc.ClassVersion; import quickfix.field.MaturityMonthYear; /* $License$ */ /** * Identifies a future contract. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: Future.java 16604 2013-06-26 14:49:42Z colin $ * @since 2.1.0 */ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) @ClassVersion("$Id: Future.java 16604 2013-06-26 14:49:42Z colin $") public class Future extends Instrument { /** * Parses the given <code>String</code> to create a <code>Future</code> instrument. * * @param inFullSymbol a <code>String</code> value in the form <code>SYMBOL-YYYYMM</code> or <code>SYMBOL-YYYYMDD</code> * @return a <code>Future</code> value * @throws IllegalArgumentException if the given <code>String</code> cannot be parsed */ public static Future fromString(String inFullSymbol) { inFullSymbol = StringUtils.trimToNull(inFullSymbol); Validate.notNull(inFullSymbol, Messages.NULL_SYMBOL.getText()); if(!inFullSymbol.contains("-")) { throw new IllegalArgumentException(Messages.INVALID_SYMBOL.getText(inFullSymbol)); } int dashIndex = inFullSymbol.lastIndexOf('-'); assert(dashIndex != -1); String symbol = inFullSymbol.substring(0, dashIndex); String expiry = inFullSymbol.substring(dashIndex+1); return new Future(symbol, expiry); } /** * Create a new Future instance. * * @param inSymbol a <code>String</code> value containing the future symbol * @param inExpirationMonth a <code>FutureExpirationMonth</code> value containing the future expiry month * @param inExpirationYear an <code>int</code> value containing the future expiration year (values < 100 will be considered years * in the 21st century) * @throws IllegalArgumentException if any of the parameters are invalid */ public Future(String inSymbol, FutureExpirationMonth inExpirationMonth, int inExpirationYear) { symbol = StringUtils.trimToNull(inSymbol); Validate.notNull(symbol, Messages.NULL_SYMBOL.getText()); Validate.notNull(inExpirationMonth, Messages.NULL_MONTH.getText()); Validate.isTrue(inExpirationYear > 0, Messages.INVALID_YEAR.getText(inExpirationYear)); if(inExpirationYear < 100) { inExpirationYear += 2000; } expirationMonth = inExpirationMonth; expirationYear = inExpirationYear; expirationDay = -1; } /** * Create a new Future instance. * * @param inSymbol a <code>String</code> value * @param inExpiry a <code>String</code> value containing the future expiry in the format YYYYMM or YYYYMMDD (year values < 100 will be considered years * in the 21st century) * @throws IllegalArgumentException if any of the parameters are invalid */ public Future(String inSymbol, String inExpiry) { symbol = StringUtils.trimToNull(inSymbol); Validate.notNull(symbol, Messages.NULL_SYMBOL.getText()); inExpiry = StringUtils.trimToNull(inExpiry); Validate.notNull(inExpiry, Messages.NULL_EXPIRY.getText()); Validate.isTrue(isValidDate(inExpiry), Messages.INVALID_EXPIRY.getText(inExpiry)); int year = Integer.parseInt(inExpiry.substring(0, 4)); String month = inExpiry.substring(4, 6); if(year < 100) { year += 2000; } int day = -1; if(inExpiry.length() > 6) { day = Integer.parseInt(inExpiry.substring(6)); } expirationYear = year; expirationMonth = FutureExpirationMonth.getByMonthOfYear(month); expirationDay = day; } /* (non-Javadoc) * @see org.marketcetera.trade.Instrument#getSymbol() */ @Override public String getSymbol() { return symbol; } /* (non-Javadoc) * @see org.marketcetera.trade.Instrument#getSecurityType() */ @Override public SecurityType getSecurityType() { return SecurityType.Future; } /** * Gets the symbol in the form SYMBOL-YYYYMM or SYMBOL-YYYYMMDD as appropriate. * * @return a <code>String</code> value */ @Override public String getFullSymbol() { String symbol = getSymbol(); if(FUTURE_STRING.matcher(symbol).matches()) { return symbol; } return String.format("%s-%s", //$NON-NLS-1$ symbol, getExpiryAsString()); } /** * Get the expirationMonth value. * * @return a <code>FutureExpirationMonth</code> value */ public FutureExpirationMonth getExpirationMonth() { return expirationMonth; } /** * Get the expirationYear value. * * @return an <code>int</code> value */ public int getExpirationYear() { return expirationYear; } /** * Get the expirationDay value. * * @return an <code>int</code> value containing either the expiration day or <code>-1</code> if the expiration * day is not specified */ public int getExpirationDay() { return expirationDay; } /** * Gets the future maturity as a <code>MaturityMonthYear</code> value. * * @return a <code>MaturityMonthYear</code> value */ public final MaturityMonthYear getExpiryAsMaturityMonthYear() { return new MaturityMonthYear(String.format("%1$4d%2$s", //$NON-NLS-1$ getExpirationYear(), getExpirationMonth().getMonthOfYear())); } /** * Gets the instrument expiry as a <code>String</code> in the format <code>YYYYMM</code> or <code>YYYYMMDD</code> as appropriate. * * @return a <code>String</code> value */ public String getExpiryAsString() { if(getExpirationDay() != -1) { return String.format("%1$4d%2$s%3$2d", //$NON-NLS-1$ getExpirationYear(), getExpirationMonth().getMonthOfYear(), getExpirationDay()); } else { return String.format("%1$4d%2$s", //$NON-NLS-1$ getExpirationYear(), getExpirationMonth().getMonthOfYear()); } } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((expirationMonth == null) ? 0 : expirationMonth.hashCode()); result = prime * result + expirationYear; result = prime * result + expirationDay; result = prime * result + ((symbol == null) ? 0 : symbol.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Future other = (Future) obj; if (expirationMonth != other.expirationMonth) return false; if (expirationYear != other.expirationYear) return false; if (expirationDay != other.expirationDay) return false; if (symbol == null) { if (other.symbol != null) return false; } else if (!symbol.equals(other.symbol)) return false; return true; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { if(expirationDay != -1) { return String.format("Future %s [%s %s(%s) %s]", //$NON-NLS-1$ symbol, expirationDay, expirationMonth, expirationMonth.getCode(), expirationYear); } else { return String.format("Future %s [%s(%s) %s]", //$NON-NLS-1$ symbol, expirationMonth, expirationMonth.getCode(), expirationYear); } } /** * Create a new Future instance. * * Parameterless constructor for use only by JAXB. */ protected Future() { symbol = null; expirationMonth = null; expirationYear = -1; expirationDay = -1; } /** * Determines if the given <code>String</code> contains a valid expiration date. * * @param input a <code>String</code> value * @return a <code>boolean</code> value */ private synchronized static boolean isValidDate(String input) { try { LONG_FORMAT.parse(input); } catch (ParseException e) { try { SHORT_FORMAT.parse(input); } catch (ParseException e1) { return false; } } return true; } /** * the full symbol */ private final String symbol; /** * the expiration month */ private final FutureExpirationMonth expirationMonth; /** * the expiration year */ private final int expirationYear; /** * the expiration day or <code>-1</code> if not specified */ private final int expirationDay; /** * the long format of an expiration (including day) */ private static final SimpleDateFormat LONG_FORMAT = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$ /** * the short format of an expiration */ private static final SimpleDateFormat SHORT_FORMAT = new SimpleDateFormat("yyyyMM"); //$NON-NLS-1$ /** * regex that tries to find a future symbol of an arbitrarily chosen format */ public static final Pattern FUTURE_STRING = Pattern.compile(".*-[0-9]{6}?"); //$NON-NLS-1$ private static final long serialVersionUID = 2L; /** * Initializes static values. */ static { LONG_FORMAT.setLenient(false); SHORT_FORMAT.setLenient(false); } }