package org.marketcetera.marketdata.core.provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.Validate;
import org.marketcetera.event.*;
import org.marketcetera.event.impl.QuoteEventBuilder;
import org.marketcetera.event.util.MarketstatEventCache;
import org.marketcetera.marketdata.Content;
import org.marketcetera.marketdata.OrderBook;
import org.marketcetera.marketdata.core.Messages;
import org.marketcetera.trade.Instrument;
import org.marketcetera.util.misc.ClassVersion;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/* $License$ */
/**
* Caches market data for a given instrument.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketdataCacheElement.java 16901 2014-05-11 16:14:11Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketdataCacheElement.java 16901 2014-05-11 16:14:11Z colin $")
public class MarketdataCacheElement
{
/**
* Create a new MarketdataCacheElement instance.
*
* @param inInstrument an <code>Instrument</code> value
* @throws IllegalArgumentException if the given instrument is <code>null</code>
*/
public MarketdataCacheElement(Instrument inInstrument)
{
Validate.notNull(inInstrument);
instrument = inInstrument;
marketstatCache = new MarketstatEventCache(inInstrument);
trade = null;
imbalance = null;
}
/**
* Gets the latest snapshot for the given content.
*
* @param inContent a <code>Content</code> value
* @return an <code>Event</code> value or <code>null</code> if no cached data exists for this content type
*/
public Event getSnapshot(Content inContent)
{
switch(inContent) {
case MARKET_STAT:
return marketstatCache.get();
case LATEST_TICK:
return trade;
case IMBALANCE:
return imbalance;
case TOP_OF_BOOK:
case NBBO:
return getOrderBookFor(inContent).getTopOfBook();
case AGGREGATED_DEPTH:
case BBO10:
case LEVEL_2:
case OPEN_BOOK:
case TOTAL_VIEW:
case UNAGGREGATED_DEPTH:
return getOrderBookFor(inContent).getDepthOfBook();
case DIVIDEND:
// TODO we actually need to return multiple events here
default:
throw new UnsupportedOperationException();
}
}
/**
* Updates the cache for the given content with the given events.
*
* @param inContent a <code>Content</code> value
* @param inEvents an <code>Event[]</code> value
* @return a <code>List<Event></code> value containing the net change represented by the given update
*/
public List<Event> update(Content inContent,
Event...inEvents)
{
List<Event> results = new ArrayList<Event>();
switch(inContent) {
case NBBO:
case UNAGGREGATED_DEPTH:
case AGGREGATED_DEPTH:
case BBO10:
case TOP_OF_BOOK:
case LEVEL_2:
case OPEN_BOOK:
case TOTAL_VIEW:
doBookUpdate(inContent,
results,
inEvents);
break;
case DIVIDEND:
for(Event event : inEvents) {
if(event instanceof DividendEvent) {
if(dividends == null) {
dividends = Lists.newArrayList();
}
dividends.add((DividendEvent)event);
results.add(event);
} else {
// TODO warn - skipping event
}
}
break;
case LATEST_TICK:
for(Event event : inEvents) {
if(event instanceof TradeEvent) {
trade = (TradeEvent)event;
results.add(event);
} else {
// TODO warn - skipping event
}
}
break;
case IMBALANCE:
for(Event event : inEvents) {
if(event instanceof ImbalanceEvent) {
imbalance = (ImbalanceEvent)event;
results.add(event);
} else {
// TODO warn - skipping event
}
}
case MARKET_STAT:
for(Event event : inEvents) {
if(event instanceof MarketstatEvent) {
if(marketstatCache == null) {
marketstatCache = new MarketstatEventCache(instrument);
}
marketstatCache.cache((MarketstatEvent)event);
} else {
// TODO warn - skipping event
}
// note that this intentionally combines a potential multitude of incoming marketstat events into a single result
results.add(marketstatCache.get());
}
break;
default:
throw new UnsupportedOperationException();
}
return results;
}
/**
* Updates the order book for the given content with the given events.
*
* @param inContent a <code>Content</code> value
* @param inoutResults a <code>Collection<Event></code> value containing the net change of the update
* @param inEvents an <code>Event[]</code> value containing the update
*/
private void doBookUpdate(Content inContent,
Collection<Event> inoutResults,
Event...inEvents)
{
OrderBook orderbook = getOrderBookFor(inContent);
for(Event event : inEvents) {
if(event instanceof QuoteEvent) {
QuoteEvent quoteEvent = (QuoteEvent)event;
if(inContent == Content.TOP_OF_BOOK) {
// generate DEL event for existing top, if necessary
if(quoteEvent.getAction() == QuoteAction.ADD) {
if(latestTop != null) {
QuoteEvent deleteEvent = null;
if(quoteEvent instanceof BidEvent) {
BidEvent latestBid = latestTop.getBid();
if(latestBid != null) {
deleteEvent = QuoteEventBuilder.delete(latestBid);
}
} else if(quoteEvent instanceof AskEvent) {
AskEvent latestAsk = latestTop.getAsk();
if(latestAsk != null) {
deleteEvent = QuoteEventBuilder.delete(latestAsk);
}
} else {
throw new UnsupportedOperationException();
}
if(deleteEvent != null) {
orderbook.process(deleteEvent);
inoutResults.add(deleteEvent);
}
}
}
}
orderbook.process(quoteEvent);
latestTop = orderbook.getTopOfBook();
inoutResults.add(quoteEvent);
} else {
throw new IllegalArgumentException(Messages.CONTENT_REQUIRES_QUOTE_EVENTS.getText(inContent,event.getClass().getName()));
}
}
}
/**
* Gets the order book for the given content.
*
* @param inContent a <code>Content</code> value
* @return an <code>OrderBook</code> value
*/
private OrderBook getOrderBookFor(Content inContent)
{
OrderBook book = orderbooks.get(inContent);
if(book == null) {
book = new OrderBook(instrument);
orderbooks.put(inContent,
book);
}
return book;
}
/**
* instrument for which market data is cached
*/
private final Instrument instrument;
/**
* order book structures, by content
*/
private final Map<Content,OrderBook> orderbooks = Maps.newHashMap();
/**
* cached dividend data
*/
private Collection<DividendEvent> dividends;
/**
* cached marketstat data
*/
private MarketstatEventCache marketstatCache;
/**
* most recent trade
*/
private TradeEvent trade;
/**
* most recent imbalance
*/
private ImbalanceEvent imbalance;
/**
* most recent top-of-book
*/
private TopOfBookEvent latestTop;
}