package org.marketcetera.marketdata.csv;
import static org.marketcetera.marketdata.csv.Messages.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang.StringUtils;
import org.marketcetera.core.CoreException;
import org.marketcetera.event.*;
import org.marketcetera.event.impl.DividendEventBuilder;
import org.marketcetera.event.impl.MarketstatEventBuilder;
import org.marketcetera.event.impl.QuoteEventBuilder;
import org.marketcetera.event.impl.TradeEventBuilder;
import org.marketcetera.options.ExpirationType;
import org.marketcetera.options.OptionUtils;
import org.marketcetera.trade.Equity;
import org.marketcetera.trade.Instrument;
import org.marketcetera.trade.Option;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.log.I18NBoundMessage2P;
import org.marketcetera.util.log.I18NBoundMessage3P;
/* $License$ */
/**
* Basic CSV Event Translator.
*
* <p>Translates CSV files to {@link Event} objects based on a set of assumptions about the content of the CSV files.
*
* <p>This translator provides support for top-of-book and latest-tick data for equities and options. Note that
* the support for options is somewhat weak as {@link #guessExpirationType(CSVQuantum, Option)}, {@link #guessHasDeliverable(CSVQuantum, Option)},
* and {@link #guessMultiplier(CSVQuantum, Option)} all yield default values. A more robust interpretation would add support for extra fields
* for options. Option symbols must all be OSI-compliant.
*
* <p>This class is designed to be extended for specialization as necessary. Any of the methods may be overridden by a subclass to provide
* more detailed behavior.
*
* <p>The default implementation expects data in the following format:
* <table>
* <tr><td>Column0</td><td><strong>BID</strong></td><td><strong>ASK</strong></td><td><strong>TRADE</strong></td><td><strong>DIVIDEND</strong></td><td><strong>STAT</strong></td></tr>
* <tr><td>Column1</td><td><strong>Timestamp</strong></td><td><strong>Timestamp</strong></td><td><strong>Timestamp</strong></td><td><strong>Timestamp</strong></td><td><strong>Timestamp</strong></td></tr>
* <tr><td>Column2</td><td><strong>Symbol</strong></td><td><strong>Symbol</strong></td><td><strong>Symbol</strong></td><td><strong>Equity Symbol</strong></td><td><strong>Symbol</strong></td></tr>
* <tr><td>Column3</td><td><strong>QuoteDate</strong></td><td><strong>QuoteDate</strong></td><td><strong>TradeDate</strong></td><td><strong>Amount<strong></td><td><em>Open Price</em></td></tr>
* <tr><td>Column4</td><td><strong>Exchange</strong></td><td><strong>Exchange</strong></td><td><strong>Exchange</strong></td><td><strong>Currency</strong></td><td><em>High Price</em></td></tr>
* <tr><td>Column5</td><td><strong>Price</strong></td><td><strong>Price</strong></td><td><strong>Price</strong></td><td><strong>Type</strong></td><td><em>Low Price</em></td></tr>
* <tr><td>Column6</td><td><strong>Size</strong></td><td><strong>Size</strong></td><td><strong>Size</strong></td><td><strong>Frequency</strong></td><td><em>Close Price</em></td></tr>
* <tr><td>Column7</td><td> </td><td> </td><td> </td><td><strong>Status</strong></td><td><em>Previous Close Price</em></td></tr>
* <tr><td>Column8</td><td> </td><td> </td><td> </td><td><strong>Execution Date</strong></td><td><em>Volume</em></td></tr>
* <tr><td>Column9</td><td> </td><td> </td><td> </td><td><em>Record Date</em></td><td><em>Close Date</em></td></tr>
* <tr><td>Column10</td><td> </td><td> </td><td> </td><td><em>Payment Date</em></td><td><em>Previous Close Date</em></td></tr>
* <tr><td>Column11</td><td> </td><td> </td><td> </td><td><em>Declare Date</em></td><td><em>Trade High Time</em></td></tr>
* <tr><td>Column12</td><td> </td><td> </td><td> </td><td> </td><td><em>Trade Low Time</em></td></tr>
* <tr><td>Column13</td><td> </td><td> </td><td> </td><td> </td><td><em>Open Exchange</em></td></tr>
* <tr><td>Column14</td><td> </td><td> </td><td> </td><td> </td><td><em>High Exchange</em></td></tr>
* <tr><td>Column15</td><td> </td><td> </td><td> </td><td> </td><td><em>Low Exchange</em></td></tr>
* <tr><td>Column16</td><td> </td><td> </td><td> </td><td> </td><td><em>Close Exchange</em></td></tr>
* </table>
*
* <p>Fields may be omitted by skipping: <code>value,value,,value</code>. Some fields are <strong>mandatory</strong>, some are <em>optional</em>
* depending on the type of event being created. <code>Column0</code> contains the type.
*
* <p>This object is stateless. All subclasses must also be reentrant.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: BasicCSVFeedEventTranslator.java 16154 2012-07-14 16:34:05Z colin $
* @since 2.1.0
*/
@Immutable
public class BasicCSVFeedEventTranslator
extends CSVFeedEventTranslator
{
/* (non-Javadoc)
* @see org.marketcetera.marketdata.csv.CSVFeedEventTranslator#toEvent(java.lang.Object, java.lang.String)
*/
@Override
public List<Event> toEvent(Object inData,
String inHandle)
throws CoreException
{
List<Event> events = new ArrayList<Event>();
CSVQuantum data = (CSVQuantum)inData;
// the first element is the type, must be one of: {BID,ASK,TRADE,DIVIDEND,STAT} (case-insensitive)
if(data.getLine().length == 0) {
throw new CoreException(EMPTY_LINE);
}
EventType type = guessEventType(data);
if(type.equals(EventType.BID)) {
validateBid(data);
events.add(processBid(data));
} else if(type.equals(EventType.ASK)) {
validateAsk(data);
events.add(processAsk(data));
} else if(type.equals(EventType.TRADE)) {
validateTrade(data);
events.add(processTrade(data));
} else if(type.equals(EventType.DIVIDEND)) {
validateDividend(data);
events.add(processDividend(data));
} else if(type.equals(EventType.STAT)) {
validateMarketstat(data);
events.add(processMarketstat(data));
} else {
throw new CoreException(new I18NBoundMessage2P(UNKNOWN_BASIC_EVENT_TYPE,
data.toString(),
type));
}
return events;
}
/**
* Validates the given line as a market statistic.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateMarketstat(CSVQuantum inData)
throws CoreException
{
validateRequiredFields(inData,
requiredMarketstatFields);
}
/**
* Validates the given line as a dividend.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateDividend(CSVQuantum inData)
throws CoreException
{
validateRequiredFields(inData,
requiredDividendFields);
}
/**
* Validates the given line as a bid.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateBid(CSVQuantum inData)
throws CoreException
{
validateQuote(inData);
}
/**
* Validates the given line as an ask.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateAsk(CSVQuantum inData)
throws CoreException
{
validateQuote(inData);
}
/**
* Validates the given line as a quote.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateQuote(CSVQuantum inData)
throws CoreException
{
validateRequiredFields(inData,
requiredQuoteFields);
}
/**
* Validates the given line as a trade.
*
* @param inData a <code>CSVQuantum</code> value
* @throws CoreException if the required fields are not present
*/
protected void validateTrade(CSVQuantum inData)
throws CoreException
{
validateRequiredFields(inData,
requiredTradeFields);
}
/**
* Confirms that the given fields are included in the given line.
*
* @param inData a <code>CSVQuantum</code> value
* @param inRequiredFields a <code>Set<Integer></code> value
* @throws CoreException if the required fields are not present
*/
protected void validateRequiredFields(CSVQuantum inData,
Set<Integer> inRequiredFields)
throws CoreException
{
for(int field : inRequiredFields) {
if(field > inData.getLine().length-1) {
throw new CoreException(new I18NBoundMessage2P(LINE_MISSING_REQUIRED_FIELDS,
inData.toString(),
inRequiredFields.toString()));
}
}
}
/**
* Processes the given data line as a market stat.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>MarketstatEvent</code> value
* @throws CoreException if an error occurs processing the line as a market stat
*/
protected MarketstatEvent processMarketstat(CSVQuantum inData)
throws CoreException
{
if(inData == null) {
throw new NullPointerException();
}
try {
Instrument instrument = guessInstrument(inData); // 2
MarketstatEventBuilder builder = MarketstatEventBuilder.marketstat(instrument);
builder.withTimestamp(guessEventTimestamp(inData)) // 1
.withOpenPrice(guessOpenPrice(inData)) // 3
.withHighPrice(guessHighPrice(inData)) // 4
.withLowPrice(guessLowPrice(inData)) // 5
.withClosePrice(guessClosePrice(inData)) // 6
.withPreviousClosePrice(guessPreviousClosePrice(inData)) // 7
.withVolume(guessVolume(inData)) // 8
.withCloseDate(guessCloseDate(inData)) // 9
.withPreviousCloseDate(guessPreviousCloseDate(inData)) // 10
.withTradeHighTime(guessTradeHighTime(inData)) // 11
.withTradeLowTime(guessTradeLowTime(inData)) // 12
.withOpenExchange(guessOpenExchange(inData)) // 13
.withHighExchange(guessHighExchange(inData)) // 14
.withLowExchange(guessLowExchange(inData)) // 15
.withCloseExchange(guessCloseExchange(inData)); // 16
if(instrument instanceof Option) {
Option option = (Option)instrument;
builder.withExpirationType(guessExpirationType(inData,
option))
.withMultiplier(guessMultiplier(inData,
option))
.withUnderlyingInstrument(guessUnderlyingInstrument(inData,
option))
.hasDeliverable(guessHasDeliverable(inData,
option));
}
return builder.create();
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(UNABLE_TO_CONSTRUCT_MARKETSTAT,
String.valueOf(inData)));
}
}
/**
* Processes the given data line as a dividend.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>DividendEvent</code> value
* @throws CoreException if an error occurs processing the line as a dividend
*/
protected DividendEvent processDividend(CSVQuantum inData)
throws CoreException
{
if(inData == null) {
throw new NullPointerException();
}
DividendEventBuilder builder = DividendEventBuilder.dividend();
try {
builder.withAmount(guessDividendAmount(inData))
.withCurrency(guessDividendCurrency(inData))
.withDeclareDate(guessDividendDeclareDate(inData))
.withExecutionDate(guessDividendExecutionDate(inData))
.withFrequency(guessDividendFrequency(inData))
.withEquity(guessDividendEquity(inData))
.withPaymentDate(guessDividendPaymentDate(inData))
.withRecordDate(guessDividendRecordDate(inData))
.withStatus(guessDividendStatus(inData))
.withType(guessDividendType(inData))
.withTimestamp(guessEventTimestamp(inData));
return builder.create();
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(UNABLE_TO_CONSTRUCT_DIVIDEND,
String.valueOf(inData)));
}
}
/**
* Processes the given data line as a bid.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BidEvent</code> value
* @throws CoreException if an error occurs processing the line as a bid
*/
protected BidEvent processBid(CSVQuantum inData)
throws CoreException
{
if(inData == null) {
throw new NullPointerException();
}
try {
Instrument instrument = guessInstrument(inData);
return (BidEvent)processQuote(inData,
QuoteEventBuilder.bidEvent(instrument),
instrument);
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(UNABLE_TO_CONSTRUCT_QUOTE,
String.valueOf(inData)));
}
}
/**
* Processes the given data line as an ask.
*
* @param inData a <code>CSVQuantum</code> value
* @return an <code>AskEvent</code> value
* @throws CoreException if an error occurs processing the line as an ask
*/
protected AskEvent processAsk(CSVQuantum inData)
throws CoreException
{
if(inData == null) {
throw new NullPointerException();
}
try {
Instrument instrument = guessInstrument(inData);
return (AskEvent)processQuote(inData,
QuoteEventBuilder.askEvent(instrument),
instrument);
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(UNABLE_TO_CONSTRUCT_QUOTE,
String.valueOf(inData)));
}
}
/**
* Processes the given data line as a trade.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>TradeEvent</code> value
* @throws CoreException if an error occurs processing the line as a trade
*/
protected TradeEvent processTrade(CSVQuantum inData)
throws CoreException
{
if(inData == null) {
throw new NullPointerException();
}
try {
Instrument instrument = guessInstrument(inData);
TradeEventBuilder<? extends TradeEvent> builder = TradeEventBuilder.tradeEvent(instrument);
builder.withTradeDate(guessTradeDate(inData))
.withExchange(guessExchange(inData))
.withPrice(guessPrice(inData))
.withSize(guessSize(inData))
.withTimestamp(guessEventTimestamp(inData));
if(instrument instanceof Option) {
Option option = (Option)instrument;
builder.withExpirationType(guessExpirationType(inData,
option))
.withMultiplier(guessMultiplier(inData,
option))
.withUnderlyingInstrument(guessUnderlyingInstrument(inData,
option))
.hasDeliverable(guessHasDeliverable(inData,
option));
}
return builder.create();
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(UNABLE_TO_CONSTRUCT_TRADE,
String.valueOf(inData)));
}
}
/**
* Processes the given data line as a quote.
*
* @param inData a <code>CSVQuantum</code> value
* @param inBuilder a <code>QuoteEventBuilder<? extends QuoteEvent></code> value
* @param inInstrument an <code>Instrument</code> value
* @return a <code>QuoteEvent</code> value
* @throws CoreException if an error occurs processing the line as a quote
*/
protected QuoteEvent processQuote(CSVQuantum inData,
QuoteEventBuilder<? extends QuoteEvent> inBuilder,
Instrument inInstrument)
throws CoreException
{
inBuilder.withAction(guessQuoteAction(inData)) // optional
.withExchange(guessExchange(inData)) // required
.withMessageId(guessMessageId(inData)) // required
.withPrice(guessPrice(inData)) // required
.withQuoteDate(guessQuoteDate(inData)) // required
.withSize(guessSize(inData)) // required
.withTimestamp(guessEventTimestamp(inData)); // required
// that should do it for your basic stuff, now, if necessary, add the option-specific works
if(inInstrument instanceof Option) {
Option option = (Option)inInstrument;
inBuilder.withExpirationType(guessExpirationType(inData, // optional
option))
.withMultiplier(guessMultiplier(inData, // optional
option))
.withUnderlyingInstrument(guessUnderlyingInstrument(inData, // optional
option))
.hasDeliverable(guessHasDeliverable(inData, // optional
option));
}
return inBuilder.create();
}
/**
* Guesses the event type from the given line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>Type</code> value
* @throws CoreException if the type cannot be determined
*/
protected EventType guessEventType(CSVQuantum inData)
throws CoreException
{
String type = guessString(inData,
0);
try {
return EventType.valueOf(type.toUpperCase());
} catch (Exception e) {
throw new CoreException(new I18NBoundMessage2P(UNKNOWN_BASIC_EVENT_TYPE,
inData.toString(),
type));
}
}
/**
* Guesses the dividend type from the given data line.
*
* <p>The dividend type is assumed to be the sixth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>DividendType</code> value
* @throws CoreException if the given data cannot be interpreted as a dividend type
*/
protected DividendType guessDividendType(CSVQuantum inData)
throws CoreException
{
String type = guessString(inData,
5);
if(type == null) {
return null;
}
try {
return DividendType.valueOf(type.toUpperCase());
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage2P(CANNOT_INTERPRET_DIVIDEND_TYPE,
inData.toString(),
type));
}
}
/**
* Guesses the dividend status from the given data line.
*
* <p>The dividend status is assumed to be the eighth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>DividendStatus</code> value
* @throws CoreException if the given data cannot be interpreted as a dividend status
*/
protected DividendStatus guessDividendStatus(CSVQuantum inData)
throws CoreException
{
String value = guessString(inData,
7);
if(value == null) {
return null;
}
try {
return DividendStatus.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage2P(CANNOT_INTERPRET_DIVIDEND_STATUS,
inData.toString(),
value));
}
}
/**
* Guesses the dividend record date from the given data line.
*
* <p>The dividend payment date is assumed to be the tenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a dividend record date
*/
protected String guessDividendRecordDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
9);
}
/**
* Guesses the dividend payment date from the given data line.
*
* <p>The dividend payment date is assumed to be the eleventh element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a dividend payment date
*/
protected String guessDividendPaymentDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
10);
}
/**
* Guesses the dividend equity from the given data line.
*
* <p>The dividend equity is assumed to be the third element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return an <code>Equity</code> value
* @throws CoreException if the given data cannot be interpreted as a dividend <code>Equity</code> value
*/
protected Equity guessDividendEquity(CSVQuantum inData)
throws CoreException
{
String symbol = guessString(inData,
2);
if(symbol == null) {
return null;
}
return new Equity(symbol);
}
/**
* Guesses the dividend frequency from the given data line.
*
* <p>The dividend frequency is assumed to be the seventh element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>DividendFrequency</code> value
* @throws CoreException if the given data cannot be interpreted as a <code>DividendFrequency</code> value
*/
protected DividendFrequency guessDividendFrequency(CSVQuantum inData)
throws CoreException
{
String frequencyValue = guessString(inData,
6);
if(frequencyValue == null) {
return null;
}
try {
return DividendFrequency.valueOf(frequencyValue.toUpperCase());
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage2P(CANNOT_INTERPRET_DIVIDEND_FREQUENCY,
inData.toString(),
frequencyValue));
}
}
/**
* Guesses the dividend execution date from the given data line.
*
* <p>The dividend execution date is assumed to be the ninth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if an error occurs retrieving the dividend execution date
*/
protected String guessDividendExecutionDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
8);
}
/**
* Guesses the dividend declare date from the given data line.
*
* <p>The dividend declare date is assumed to be the twelfth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if an error occurs retrieving the dividend declare date
*/
protected String guessDividendDeclareDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
11);
}
/**
* Guesses the dividend currency from the given data line.
*
* <p>The dividend currency is assumed to be the fifth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if an error occurs retrieving the dividend currency
*/
protected String guessDividendCurrency(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
4);
}
/**
* Guesses the dividend amount from the given data line.
*
* <p>The dividend amount is assumed to be the fourth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value
* @throws CoreException if an error occurs retrieving the dividend amount
*/
protected BigDecimal guessDividendAmount(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
3);
}
/**
* Guesses the trade date of the given data line.
*
* <p>The trade date is assumed to be the fourth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if an error occurs retrieving the trade date
*/
protected String guessTradeDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
3);
}
/**
* Guesses whether the contract of the data line has deliverables.
*
* @param inData a <code>CSVQuantum</code> value
* @param inOption an <code>Option</code> value
* @return a <code>boolean</code> value
* @throws CoreException if the given data yields a contract that has deliverables
*/
protected boolean guessHasDeliverable(CSVQuantum inData,
Option inOption)
throws CoreException
{
return true;
}
/**
* Guesses the underlying instrument of the given data line.
*
* <p>Assumes that the underlying instrument is an {@link Equity} and that
* the option is OSI-compatible.
*
* @param inData a <code>CSVQuantum</code> value
* @param inOption an <code>Option</code> value
* @return an <code>Instrument</code> value
* @throws CoreException if the given data does not yield an underlying instrument
*/
protected Instrument guessUnderlyingInstrument(CSVQuantum inData,
Option inOption)
throws CoreException
{
// this is suboptimal as underlyings can be other types of instruments
return new Equity(inOption.getSymbol());
}
/**
* Guesses the contract multiplier of the given data line.
*
* @param inData a <code>CSVQuantum</code> value
* @param inOption an <code>Option</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a <code>BigDecimal</code> value
*/
protected BigDecimal guessMultiplier(CSVQuantum inData,
Option inOption)
throws CoreException
{
return BigDecimal.ONE;
}
/**
* Guesses the expiration type of the given data line.
*
* @param inData a <code>CSVQuantum</code> value
* @param inOption an <code>Option</code> value
* @return an <code>ExpirationType</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as an <code>ExpirationType</code>
*/
protected ExpirationType guessExpirationType(CSVQuantum inData,
Option inOption)
throws CoreException
{
return ExpirationType.UNKNOWN;
}
/**
* Guesses the size of the given data line.
*
* <p>The size is assumed to be the seventh element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the data cannot be interpreted as a size
*/
protected BigDecimal guessSize(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
6);
}
/**
* Guesses the quote date from the given data line.
*
* <p>The quote date is assumed to be the fourth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if an error occurs
*/
protected String guessQuoteDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
3);
}
/**
* Guesses the price of the given data line.
*
* <p>The price is assumed to be the sixth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the data cannot be interpreted as a price
*/
protected BigDecimal guessPrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
5);
}
/**
* Guesses the message ID from the given data line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>long</code> value
* @throws CoreException if the data cannot be interpreted as a long
*/
protected long guessMessageId(CSVQuantum inData)
throws CoreException
{
return counter.incrementAndGet();
}
/**
* Guesses the <code>Instrument</code> type from the symbol.
*
* <p>This method makes one large assumption: the character <code>:</code> can be used only to separate CFI Code (ISO 10962) and the
* symbol. A symbol that otherwise includes <code>:</code> will not be processed correctly. This method does not support escaped
* <code>:</code> values.
*
* <p>The line must contain at least three elements with the symbol in the third element.
*
* @param inData a <code>CSVQuantum</code> value containing an element of data from the CSV file
* @return an <code>Instrument</code> value
* @throws CoreException if an error occurs parsing the symbol string
* @throws ArrayIndexOutOfBoundsException if the given data does not contain symbol information at the expected position
*/
protected Instrument guessInstrument(CSVQuantum inData)
throws CoreException
{
String symbol = guessString(inData,
2);
if(symbol == null) {
return null;
}
if(symbol.contains(":")) { //$NON-NLS-1$
// assume the symbol contains a CFI (ISO10962) code
String[] chunks = symbol.split(":"); //$NON-NLS-1$
if(chunks.length != 2) {
// unknown symbol format
throw new CoreException(new I18NBoundMessage1P(UNKNOWN_SYMBOL_FORMAT,
symbol));
}
String cfiCodeChunk = chunks[0].toUpperCase();
String symbolChunk = chunks[1];
if(UNSUPPORTED_CFI_CODES.contains(cfiCodeChunk)) {
// CFI code valid but not supported by METC
throw new CoreException(new I18NBoundMessage3P(UNSUPPORTED_CFI_CODE,
symbol,
cfiCodeChunk,
SUPPORTED_CFI_CODES.toString()));
}
if(!SUPPORTED_CFI_CODES.contains(cfiCodeChunk)) {
// CFI code invalid
throw new CoreException(new I18NBoundMessage3P(INVALID_CFI_CODE,
symbol,
cfiCodeChunk,
SUPPORTED_CFI_CODES.toString()));
}
if(cfiCodeChunk.equals("E")) { //$NON-NLS-1$
return new Equity(symbolChunk);
} else if(cfiCodeChunk.equals("O")) { //$NON-NLS-1$
// this must be an option, assume it's in OSI format
try {
return OptionUtils.getOsiOptionFromString(symbolChunk);
} catch (IllegalArgumentException e) {
// the option is not in OSI format
throw new CoreException(new I18NBoundMessage2P(NOT_OSI_COMPLIANT,
symbol,
symbolChunk));
}
}
// this is a programming mistake - the code is alleged to be supported (contained by SUPPORTED_CFI_CODES)
// but is neither E nor O. therefore, someone added a code to SUPPORTED_CFI_CODES but did not add a new if clause
throw new UnsupportedOperationException();
}
// the symbol does not contain ":", therefore we can assume it's a symbol on its own
// we cannot assume the symbol is an Equity, so, first try it on as an Option, but don't get discouraged if it doesn't work
try {
return OptionUtils.getOsiOptionFromString(symbol);
} catch (IllegalArgumentException e) {
// s'ok, this just must be an equity - note, this is a limiting assumption, when we activate additional classes, this will need to be expanded
}
return new Equity(symbol);
}
/**
* Guesses the quote type from the data line.
*
* <p>A <code>QuoteAction</code> indicates if the corresponding quote is an add (new quote) or if it's
* a replacement for an existing quote. All top-of-book quotes should be of type {@link QuoteAction#ADD}.
* Depth-of-book quotes may of any type.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>QuoteAction</code> value or <code>null</code>
*/
protected QuoteAction guessQuoteAction(CSVQuantum inData)
throws CoreException
{
return QuoteAction.ADD;
}
/**
* Guesses the exchange from the data line.
*
* <p>The exchange is assumed to be the fifth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
*/
protected String guessExchange(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
4);
}
/**
* Guesses the event timestamp from the data line.
*
* <p>Note that the event timestamp is the time the event carries. This is <em>not necessarily</em>
* the same time as when the event occurred, i.e., it is not the exchange timestamp.
*
* <p>The event timestamp is assumed to be the second element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>Date</code> value containing the timestamp for the event or <code>null</code>
* @throws CoreException if the event could not be parsed
*/
protected Date guessEventTimestamp(CSVQuantum inData)
throws CoreException
{
return guessDate(inData,
1);
}
/**
* Guesses the close exchange from the given data line.
*
* <p>The close exchange is assumed to be the seventeenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a close exchange
*/
protected String guessCloseExchange(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
16);
}
/**
* Guesses the low exchange from the given data line.
*
* <p>The low exchange is assumed to be the sixteenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a low exchange
*/
protected String guessLowExchange(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
15);
}
/**
* Guesses the high exchange from the given data line.
*
* <p>The high exchange is assumed to be the fifteenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a high exchange
*/
protected String guessHighExchange(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
14);
}
/**
* Guesses the open exchange from the given data line.
*
* <p>The open exchange is assumed to be the fourteenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as an open exchange
*/
protected String guessOpenExchange(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
13);
}
/**
* Guesses the previous trade low time from the given data line.
*
* <p>The trade low time is assumed to be the thirteenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a trade low time
*/
protected String guessTradeLowTime(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
12);
}
/**
* Guesses the previous trade high time from the given data line.
*
* <p>The trade high time is assumed to be the twelfth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a trade high time
*/
protected String guessTradeHighTime(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
11);
}
/**
* Guesses the previous close date from the given data line.
*
* <p>The previous close date is assumed to be the eleventh element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a close close date
*/
protected String guessPreviousCloseDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
10);
}
/**
* Guesses the low price from the given data line.
*
* <p>The low price is assumed to be the sixth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a low price
*/
protected BigDecimal guessLowPrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
5);
}
/**
* Guesses the high price from the given data line.
*
* <p>The high price is assumed to be the fifth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a high price
*/
protected BigDecimal guessHighPrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
4);
}
/**
* Guesses the close price from the given data line.
*
* <p>The close price is assumed to be the seventh element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a close price
*/
protected BigDecimal guessClosePrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
6);
}
/**
* Guesses the volume from the given data line.
*
* <p>The volume is assumed to be the ninth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a volume
*/
protected BigDecimal guessVolume(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
8);
}
/**
* Guesses the previous close price from the given data line.
*
* <p>The previous close price is assumed to be the eighth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a close price
*/
protected BigDecimal guessPreviousClosePrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
7);
}
/**
* Guesses the open price from the given data line.
*
* <p>The open price is assumed to be the fourth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as an open price
*/
protected BigDecimal guessOpenPrice(CSVQuantum inData)
throws CoreException
{
return guessBigDecimal(inData,
3);
}
/**
* Guesses the close date from the given data line.
*
* <p>The close date is assumed to be the tenth element in the line.
*
* @param inData a <code>CSVQuantum</code> value
* @return a <code>String</code> value or <code>null</code>
* @throws CoreException if the given data cannot be interpreted as a close date
*/
protected String guessCloseDate(CSVQuantum inData)
throws CoreException
{
return guessString(inData,
9);
}
/**
* Interprets the given <code>String</code> as a <code>String</code> value.
*
* @param inData a <code>CSVQuantum</code> value
* @param inIndex an <code>int</code> value
* @return a <code>String</code> value or <code>null</code>
*/
protected String guessString(CSVQuantum inData,
int inIndex)
{
if(inData == null) {
return null;
}
if(inIndex >= inData.getLine().length) {
return null;
}
return StringUtils.trimToNull(inData.getLine()[inIndex]);
}
/**
* Interprets the given <code>String</code> as a <code>Date</code> value.
*
* <p>Note that this value is not interpreted exactly as a <code>Date</code>. The value is interpreted
* as a long, and then transformed to a <code>Date</code>.
*
* @param inData a <code>CSVQuantum</code> value
* @param inIndex an <code>int</code> value
* @return a <code>Date</code> value or <code>null</code>
* @throws CoreException if the value cannot be interpreted as a <code>Date</code>
*/
protected Date guessDate(CSVQuantum inData,
int inIndex)
throws CoreException
{
String dataChunk = guessString(inData,
inIndex);
if(dataChunk == null) {
return null;
}
try {
return new Date(Long.parseLong(dataChunk));
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(CANNOT_GUESS_DATE,
dataChunk));
}
}
/**
* Interprets the given <code>String</code> as a <code>BigDecimal</code> value.
*
* @param inData a <code>CSVQuantum</code> value
* @param inIndex an <code>int</code> value
* @return a <code>BigDecimal</code> value or <code>null</code>
* @throws CoreException if the value cannot be interpreted as a <code>BigDecimal</code>
*/
protected BigDecimal guessBigDecimal(CSVQuantum inData,
int inIndex)
throws CoreException
{
String dataChunk = guessString(inData,
inIndex);
if(dataChunk == null) {
return null;
}
try {
return new BigDecimal(dataChunk);
} catch (Exception e) {
throw new CoreException(e,
new I18NBoundMessage1P(CANNOT_GUESS_BIG_DECIMAL,
dataChunk));
}
}
/**
* used to uniquely identify events
*/
protected static final AtomicLong counter = new AtomicLong(0);
/**
* CFI codes of supported instruments
*/
protected static final Set<String> SUPPORTED_CFI_CODES = new HashSet<String>(Arrays.asList(new String[] { "E","O" } )); //$NON-NLS-1$ //$NON-NLS-2$
/**
* valid but unsupported CFI codes
*/
protected static final Set<String> UNSUPPORTED_CFI_CODES = new HashSet<String>(Arrays.asList(new String[] { "D","R","F","M" } )); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
/**
* default fields required for quotes
*/
protected static final Set<Integer> requiredQuoteFields = new HashSet<Integer>(Arrays.asList(new Integer[] { 0,1,2,3,4,5,6 }));
/**
* default fields required for trades
*/
protected static final Set<Integer> requiredTradeFields = new HashSet<Integer>(Arrays.asList(new Integer[] { 0,1,2,3,4,5,6 }));
/**
* default fields required for dividends
*/
protected static final Set<Integer> requiredDividendFields = new HashSet<Integer>(Arrays.asList(new Integer[] { 0,1,2,3,4,5,6,7,8 }));
/**
* default fields required for market stat events
*/
protected static final Set<Integer> requiredMarketstatFields = new HashSet<Integer>(Arrays.asList(new Integer[] { 0,1,2 }));
/**
* The event type of a CSV event.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: BasicCSVFeedEventTranslator.java 16154 2012-07-14 16:34:05Z colin $
* @since 2.1.0
*/
public static enum EventType
{
BID,
ASK,
TRADE,
DIVIDEND,
STAT;
}
}