package org.marketcetera.photon.internal.marketdata;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.marketcetera.event.AskEvent;
import org.marketcetera.event.BidEvent;
import org.marketcetera.event.Event;
import org.marketcetera.event.MarketstatEvent;
import org.marketcetera.event.TradeEvent;
import org.marketcetera.marketdata.AssetClass;
import org.marketcetera.marketdata.Content;
import org.marketcetera.marketdata.MarketDataRequest;
import org.marketcetera.marketdata.MarketDataRequestBuilder;
import org.marketcetera.marketdata.core.manager.MarketDataProviderNotAvailable;
import org.marketcetera.marketdata.core.manager.MarketDataRequestFailed;
import org.marketcetera.marketdata.core.manager.MarketDataRequestTimedOut;
import org.marketcetera.marketdata.core.manager.NoMarketDataProvidersAvailable;
import org.marketcetera.marketdata.core.webservice.ConnectionException;
import org.marketcetera.marketdata.core.webservice.PageRequest;
import org.marketcetera.marketdata.core.webservice.UnknownRequestException;
import org.marketcetera.photon.marketdata.IMarketData;
import org.marketcetera.photon.marketdata.IMarketDataReference;
import org.marketcetera.photon.model.marketdata.MDDepthOfBook;
import org.marketcetera.photon.model.marketdata.MDItem;
import org.marketcetera.photon.model.marketdata.MDLatestTick;
import org.marketcetera.photon.model.marketdata.MDMarketstat;
import org.marketcetera.photon.model.marketdata.MDTopOfBook;
import org.marketcetera.photon.model.marketdata.impl.MDDepthOfBookImpl;
import org.marketcetera.photon.model.marketdata.impl.MDLatestTickImpl;
import org.marketcetera.photon.model.marketdata.impl.MDMarketstatImpl;
import org.marketcetera.photon.model.marketdata.impl.MDQuoteImpl;
import org.marketcetera.photon.model.marketdata.impl.MDTopOfBookImpl;
import org.marketcetera.trade.Instrument;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
/* $License$ */
/**
* This class is the central manager of market data.
*
* <p>All requests come through this class. Market data is returned in the form of a reference that can be disposed when no
* longer needed. This allow data flows to be shared so at most one is active for any given data request. Data flows are started after the first request
* and stopped after the last reference is disposed.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 1.5.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
public class MarketData
implements IMarketData
{
/**
* Create a new MarketData instance.
*
* @param inMarketDataClientProvider an <code>IMarketDataClientProvider</code> value
*/
@Inject
public MarketData(final IMarketDataClientProvider inMarketDataClientProvider)
{
Validate.notNull(inMarketDataClientProvider);
marketDataClientProvider = inMarketDataClientProvider;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#reset()
*/
@Override
public void reset()
{
// requests.clear();
if(marketDataRefreshExecutor != null) {
marketDataRefreshExecutor.shutdownNow();
}
marketDataRefreshExecutor = Executors.newScheduledThreadPool(10);
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#resubmit()
*/
@Override
public void resubmit()
{
// TODO don't do anything if the reconnect button was pressed but no connection was lost to the server
for(Entry<MarketDataReferenceKey,MarketDataDetails<?,?>> entry : requests.entrySet()) {
MarketDataDetails<?,?> marketDataDetails = entry.getValue();
marketDataDetails.disconnect();
marketDataDetails.connect();
}
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#getLatestTick(org.marketcetera.trade.Instrument)
*/
@Override
public IMarketDataReference<MDLatestTick> getLatestTick(Instrument inInstrument)
{
return getMarketDataReference(inInstrument,
Content.LATEST_TICK,
latestTickFactory,
latestTickUpdater,
TOP_UPDATE_FREQUENCY);
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#getTopOfBook(org.marketcetera.trade.Instrument)
*/
@Override
public IMarketDataReference<MDTopOfBook> getTopOfBook(Instrument inInstrument)
{
return getMarketDataReference(inInstrument,
Content.TOP_OF_BOOK,
topOfBookFactory,
topOfBookUpdater,
TOP_UPDATE_FREQUENCY);
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#getMarketstat(org.marketcetera.trade.Instrument)
*/
@Override
public IMarketDataReference<MDMarketstat> getMarketstat(Instrument inInstrument)
{
return getMarketDataReference(inInstrument,
Content.MARKET_STAT,
marketstatFactory,
marketstatUpdater,
TOP_UPDATE_FREQUENCY);
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketData#getDepthOfBook(org.marketcetera.trade.Instrument, org.marketcetera.marketdata.Content)
*/
@Override
public IMarketDataReference<MDDepthOfBook> getDepthOfBook(Instrument inInstrument,
Content inProduct)
{
switch(inProduct) {
case AGGREGATED_DEPTH:
return getMarketDataReference(inInstrument,
Content.AGGREGATED_DEPTH,
depthOfBookFactory,
aggregatedDepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case BBO10:
return getMarketDataReference(inInstrument,
Content.BBO10,
depthOfBookFactory,
bbo10DepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case LEVEL_2:
return getMarketDataReference(inInstrument,
Content.LEVEL_2,
depthOfBookFactory,
level2DepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case OPEN_BOOK:
return getMarketDataReference(inInstrument,
Content.OPEN_BOOK,
depthOfBookFactory,
openBookDepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case TOTAL_VIEW:
return getMarketDataReference(inInstrument,
Content.TOTAL_VIEW,
depthOfBookFactory,
totalViewDepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case UNAGGREGATED_DEPTH:
return getMarketDataReference(inInstrument,
Content.UNAGGREGATED_DEPTH,
depthOfBookFactory,
unaggregatedDepthUpdater,
DEPTH_UPDATE_FREQUENCY);
case MARKET_STAT:
case NBBO:
case TOP_OF_BOOK:
case DIVIDEND:
case LATEST_TICK:
default :
throw new UnsupportedOperationException();
}
}
/**
* Gets a market data reference for the given attributes.
*
* @param inInstrument an <code>Instrument</code> value
* @param inContent a <code>Content</code> value
* @param inFactory an <code>ItemFactory<MDMutableType></code> value
* @param inUpdater an <code>ItemUpdated<MDMutableType></code> value
* @param inUpdateFrequency a <code>long</code> value
* @return an <code>IMarketDataReference<MDType></code> value
*/
@SuppressWarnings("unchecked")
private <MDType extends MDItem,MDMutableType extends MDType> IMarketDataReference<MDType> getMarketDataReference(final Instrument inInstrument,
final Content inContent,
ItemFactory<MDMutableType> inFactory,
final ItemUpdater<MDMutableType> inUpdater,
final long inUpdateFrequency)
{
Validate.notNull(inInstrument);
Validate.notNull(inContent);
final MarketDataReferenceKey key = new MarketDataReferenceKey(inInstrument,
inContent);
MarketDataDetails<?,?> existingMarketDataDetails = requests.get(key);
if(existingMarketDataDetails != null) {
// somebody has already asked for this data, simply return the existing reference
existingMarketDataDetails.incrementReferenceCount();
return (IMarketDataReference<MDType>)existingMarketDataDetails.getReference();
}
// this is a new reference
MarketDataRequestBuilder builder = MarketDataRequestBuilder.newRequest()
.withAssetClass(AssetClass.getFor(inInstrument.getSecurityType()))
.withSymbols(inInstrument.getFullSymbol())
.withContent(inContent);
MarketDataRequest request = builder.create();
MDMutableType item = inFactory.create();
MarketDataDetails<MDType,MDMutableType> newMarketDataDetails = new MarketDataDetails<>(inInstrument,
inContent,
request,
item,
key,
inUpdater,
inUpdateFrequency);
newMarketDataDetails.incrementReferenceCount();
requests.put(key,
newMarketDataDetails);
newMarketDataDetails.connect();
return newMarketDataDetails.getReference();
}
/**
* Manages a market data subscription and applies updates.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private class SubscriptionRefreshJob<MDType extends MDItem,MDMutableType extends MDType>
implements Runnable
{
/**
* Create a new SubscriptionRefreshJob instance.
*
* @param inInstrument an <code>Instrument</code> value
* @param inContent a <code>Content</code> value
* @param inId a <code>long</code> value
* @param inUpdater an <code>ItemUpdater<MDMutableType></code> value
* @param inItem an <code>MDMutableType</code> value
*/
private SubscriptionRefreshJob(Instrument inInstrument,
Content inContent,
long inId,
ItemUpdater<MDMutableType> inUpdater,
MDMutableType inItem)
{
instrument = inInstrument;
content = inContent;
id = inId;
updater = inUpdater;
item = inItem;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
try {
if(marketDataClientProvider.getMarketDataClient() == null || !marketDataClientProvider.getMarketDataClient().isRunning()) {
updater.clear(item);
cancel();
return;
}
long updateTimestamp = marketDataClientProvider.getMarketDataClient().getLastUpdate(id);
if(updateTimestamp > lastUpdate) {
// TODO switch to get snapshot page
PageRequest page = new PageRequest(1,
Integer.MAX_VALUE);
Deque<Event> events = marketDataClientProvider.getMarketDataClient().getSnapshotPage(instrument,
content,
null,
page);
if(events == null || events.isEmpty()) {
return;
}
updater.update(item,
events);
lastUpdate = updateTimestamp;
}
} catch (UnknownRequestException e) {
// this is likely a transient problem, which will sort itself out shortly
SLF4JLoggerProxy.warn(MarketData.this,
e);
cancel();
} catch (ConnectionException e) {
// exception caused by a lost connection - cancel this request
SLF4JLoggerProxy.error(MarketData.this,
e);
updater.clear(item);
cancel();
} catch (Exception e) {
// this is an exception that occurred during update - must be caught or it kills the scheduled executor - log it and report a problem to the user
updater.clear(item);
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"A problem occurred updating market data for {} {} [{}]",
content,
instrument,
id);
SLF4JLoggerProxy.error(MarketData.this,
e);
}
}
/**
* Cancels this job from being run again.
*/
private void cancel()
{
if(refreshJobToken != null) {
refreshJobToken.cancel(true);
refreshJobToken = null;
}
}
/**
* market data request instrument
*/
private final Instrument instrument;
/**
* market data request content
*/
private final Content content;
/**
* server request id
*/
private final long id;
/**
* updater which updates {@link #item} when new events come in
*/
private final ItemUpdater<MDMutableType> updater;
/**
* item to update when new events come in
*/
private final MDMutableType item;
/**
* reference token for the scheduled market data refresh job, if <code>null</code>, no job is scheduled
*/
private Future<?> refreshJobToken;
/**
* tracks the last update time
*/
private long lastUpdate = 0;
}
/**
* Uniquely identifies the contents of a market data request.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private static class MarketDataReferenceKey
{
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return new HashCodeBuilder().append(content).append(instrument).toHashCode();
}
/* (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 (!(obj instanceof MarketDataReferenceKey)) {
return false;
}
MarketDataReferenceKey other = (MarketDataReferenceKey) obj;
return new EqualsBuilder().append(other.content,content).append(other.instrument,instrument).isEquals();
}
/**
* Create a new MarketDataReferenceKey instance.
*
* @param inInstrument
* @param inContent
*/
private MarketDataReferenceKey(Instrument inInstrument,
Content inContent)
{
instrument = inInstrument;
content = inContent;
}
/**
* market data instrument requested
*/
private final Instrument instrument;
/**
* market data content requested
*/
private final Content content;
}
/**
* Contains the market data reference for an instrument-content tuple.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private class MarketDataDetails<MDType extends MDItem,MDMutableItemType extends MDType>
{
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return new StringBuilder().append(instrument.getFullSymbol()).append(" - ").append(content).append(" ").append(referenceCount.get()).append(" use(s) [").append(requestId).append("]").toString();
}
/**
* Disconnects this market data unit from the market data nexus while preserving the underlying reference.
*
* <p>This market data unit may be reconnected later and all existing users
* of the underlying reference will transparently pick up the new market data deliveries.
*/
private void disconnect()
{
updater.clear(item);
if(refreshJob != null) {
refreshJob.cancel();
refreshJob = null;
}
// do NOT change the reference count because there are still some number of users of the underlying reference
try {
marketDataClientProvider.getMarketDataClient().cancel(requestId);
requestId = -1;
} catch (Exception ignored) {}
}
/**
* Render this market data unit no longer usable.
*/
private void dispose()
{
requests.remove(key);
disconnect();
}
/**
* Connects this market data unit to the market data nexus without affecting the underlying resource.
*/
private void connect()
{
if(marketDataClientProvider.getMarketDataClient() == null || !marketDataClientProvider.getMarketDataClient().isRunning()) {
// we could connect at a later time
return;
}
try {
requestId = marketDataClientProvider.getMarketDataClient().request(request,
false);
} catch (NoMarketDataProvidersAvailable | MarketDataProviderNotAvailable | MarketDataRequestTimedOut e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Request for {} - {} failed because no market data providers are available that can honor the request",
content,
instrument);
SLF4JLoggerProxy.error(this,
e,
"Request for {} - {} failed because no market data providers are available that can honor the request",
content,
instrument);
return;
} catch (MarketDataRequestFailed e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Request for {} - {} failed because there was a problem with the request",
content,
instrument);
SLF4JLoggerProxy.error(this,
e,
"Request for {} - {} failed because there was a problem with the request",
content,
instrument);
return;
} catch (Exception e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Request for {} - {} failed",
content,
instrument);
SLF4JLoggerProxy.error(this,
e,
"Request for {} - {} failed",
content,
instrument);
throw new RuntimeException(e);
}
refreshJob = new SubscriptionRefreshJob<>(instrument,
content,
requestId,
updater,
item);
refreshJob.refreshJobToken = marketDataRefreshExecutor.scheduleAtFixedRate(refreshJob,
1000,
updateFrequency,
TimeUnit.MILLISECONDS);
}
/**
* Increments and returns the reference count.
*
* @return an <code>int</code> value containing the updated reference count
*/
private int incrementReferenceCount()
{
return referenceCount.incrementAndGet();
}
/**
* Decrements and returns the reference count.
*
* @return an <code>int</code> value containing the updated the reference count
*/
private int decrementReferenceCount()
{
int updatedCount = referenceCount.decrementAndGet();
if(updatedCount <= 0) {
updatedCount = 0;
referenceCount.set(0);
dispose();
}
return updatedCount;
}
/**
* Get the reference value.
*
* @return an <code>IMarketDataReference<TypeClazz></code> value
*/
private IMarketDataReference<MDType> getReference()
{
return reference;
}
/**
* Create a new MarketDataDetails instance.
*
* @param inInstrument an <code>Instrument</code> value
* @param inContent a <code>Content</code> value
* @param inRequest a <code>MarketDataRequest</code> value
* @param inItem an <code>MDMutableItemType</code> value
* @param inKey a <code>MarketDataReferenceKey</code> value
* @param inUpdater an <code>ItemUpdated<MDMutableItemType></code> value
* @param inUpdateFrequency a <code>long</code> value
*/
private MarketDataDetails(Instrument inInstrument,
Content inContent,
MarketDataRequest inRequest,
final MDMutableItemType inItem,
final MarketDataReferenceKey inKey,
ItemUpdater<MDMutableItemType> inUpdater,
long inUpdateFrequency)
{
instrument = inInstrument;
content = inContent;
request = inRequest;
item = inItem;
updater = inUpdater;
updateFrequency = inUpdateFrequency;
key = inKey;
reference = new IMarketDataReference<MDType>() {
@Override
public MDType get()
{
return item;
}
@Override
public void dispose()
{
// this method is called if a consumer of the reference no longer needs it
decrementReferenceCount();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return new StringBuilder().append("Ref-").append(id).append(" ").append(content).append(" for ").append(instrument).append(" [").append(requestId).append("]").toString();
}
/**
* id assigned to this market data reference, used to uniquely identify a reference, not really used for anything else
*/
private long id = System.nanoTime();
};
}
/**
* indicates how frequently (in ms) to check for updates
*/
private final long updateFrequency;
/**
* updates {@link #item} when new events arrive
*/
private final ItemUpdater<MDMutableItemType> updater;
/**
* item to update when market data changes arrive
*/
private final MDMutableItemType item;
/**
* market data request ID returned from the nexus
*/
private volatile long requestId;
/**
* market data request used to create this reference
*/
private final MarketDataRequest request;
/**
* job responsible for updating the market data contents of this reference
*/
private volatile SubscriptionRefreshJob<MDType,MDMutableItemType> refreshJob;
/**
* tracks active user count
*/
private final AtomicInteger referenceCount = new AtomicInteger(0);
/**
* market data instrument requested
*/
private final Instrument instrument;
/**
* market data content requested
*/
private final Content content;
/**
* market data key which uniquely identifies the reference contents
*/
private final MarketDataReferenceKey key;
/**
* market data source
*/
private final IMarketDataReference<MDType> reference;
}
/**
* Creates a market data type value.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private interface ItemFactory<MDType extends MDItem>
{
/**
* Creates a new market data type value.
*
* @return an <code>MDType</code> value
*/
MDType create();
}
/**
* Updates an item with new events.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private interface ItemUpdater<MDType extends MDItem>
{
/**
* Updates the given item based on the given events, sorted most recent to least recent.
*
* @param inItem an <code>MDType</code> value
* @param inEvents a <code>Deque<Event></code> value
*/
void update(MDType inItem,
Deque<Event> inEvents);
/**
* Clears the given item.
*
* @param inItem an <code>MDType</code> value
*/
void clear(MDType inItem);
}
/**
* Updates depth-of-book items.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketData.java 16899 2014-05-11 16:03:04Z colin $")
private abstract static class AbstractDepthUpdater
implements ItemUpdater<MDDepthOfBookImpl>
{
/* (non-Javadoc)
* @see org.marketcetera.photon.internal.marketdata.MarketData.ItemUpdater#update(org.marketcetera.photon.model.marketdata.MDItem, java.util.Deque)
*/
@Override
public void update(final MDDepthOfBookImpl inItem,
final Deque<Event> inEvents)
{
inItem.setInstrument(inItem.getInstrument());
inItem.setProduct(getContent());
final List<AskEvent> newAsks = Lists.newArrayList();
final List<BidEvent> newBids = Lists.newArrayList();
for(Event event : inEvents) {
if(event instanceof BidEvent) {
newBids.add((BidEvent)event);
} else if(event instanceof AskEvent) {
newAsks.add((AskEvent)event);
} else {
throw new UnsupportedOperationException();
}
}
// TODO measure difference only
if(!newAsks.isEmpty()) {
inItem.getAsks().doWriteOperation(new Callable<Void>() {
@Override
public Void call()
throws Exception
{
inItem.getAsks().clear();
for(AskEvent ask : newAsks) {
MDQuoteImpl quoteItem = new MDQuoteImpl();
quoteItem.setInstrument(ask.getInstrument());
quoteItem.setPrice(ask.getPrice());
quoteItem.setSize(ask.getSize());
quoteItem.setSource(String.valueOf(ask.getSource()));
quoteItem.setTime(ask.getTimeMillis());
inItem.getAsks().add(quoteItem);
}
return null;
}
});
}
if(!newBids.isEmpty()) {
inItem.getBids().doWriteOperation(new Callable<Void>() {
@Override
public Void call()
throws Exception
{
inItem.getBids().clear();
for(BidEvent bid : newBids) {
MDQuoteImpl quoteItem = new MDQuoteImpl();
quoteItem.setInstrument(bid.getInstrument());
quoteItem.setPrice(bid.getPrice());
quoteItem.setSize(bid.getSize());
quoteItem.setSource(String.valueOf(bid.getSource()));
quoteItem.setTime(bid.getTimeMillis());
inItem.getBids().add(quoteItem);
}
return null;
}
});
}
}
/* (non-Javadoc)
* @see org.marketcetera.photon.internal.marketdata.MarketData.ItemUpdater#clear(org.marketcetera.photon.model.marketdata.MDItem)
*/
@Override
public void clear(MDDepthOfBookImpl inItem)
{
inItem.getBids().clear();
inItem.getAsks().clear();
}
/**
* Gets the content type of this market data updater.
*
* @return a <code>Content</code> value
*/
protected abstract Content getContent();
}
/**
* creates a latest-execution item
*/
private ItemFactory<MDLatestTickImpl> latestTickFactory = new ItemFactory<MDLatestTickImpl>() {
@Override
public MDLatestTickImpl create()
{
return new MDLatestTickImpl();
}
};
/**
* creates a top-of-book item
*/
private ItemFactory<MDTopOfBookImpl> topOfBookFactory = new ItemFactory<MDTopOfBookImpl>() {
@Override
public MDTopOfBookImpl create()
{
return new MDTopOfBookImpl();
}
};
/**
* creates a marketstat item
*/
private ItemFactory<MDMarketstatImpl> marketstatFactory = new ItemFactory<MDMarketstatImpl>() {
@Override
public MDMarketstatImpl create()
{
return new MDMarketstatImpl();
}
};
/**
* creates a depth-of-book item
*/
private ItemFactory<MDDepthOfBookImpl> depthOfBookFactory = new ItemFactory<MDDepthOfBookImpl>() {
@Override
public MDDepthOfBookImpl create()
{
return new MDDepthOfBookImpl();
}
};
/**
* manages latest execution updates
*/
private ItemUpdater<MDLatestTickImpl> latestTickUpdater = new ItemUpdater<MDLatestTickImpl>() {
@Override
public void update(MDLatestTickImpl inItem,
Deque<Event> inEvents)
{
Event mostRecentEvent = inEvents.getFirst();
if(mostRecentEvent instanceof TradeEvent) {
TradeEvent trade = (TradeEvent)mostRecentEvent;
inItem.setInstrument(trade.getInstrument());
inItem.setPrice(trade.getPrice());
inItem.setSize(trade.getSize());
}
}
@Override
public void clear(MDLatestTickImpl inItem)
{
inItem.setPrice(null);
inItem.setSize(null);
}
};
/**
* manages top-of-book updates
*/
private ItemUpdater<MDTopOfBookImpl> topOfBookUpdater = new ItemUpdater<MDTopOfBookImpl>() {
@Override
public void update(MDTopOfBookImpl inItem,
Deque<Event> inEvents)
{
// traverse the list from front to back, stopping when we have one each of bid and ask
boolean askFound = false;
boolean bidFound = false;
for(Event event : inEvents) {
if(event instanceof AskEvent) {
AskEvent ask = (AskEvent)event;
askFound = true;
inItem.setInstrument(ask.getInstrument());
inItem.setAskPrice(ask.getPrice());
inItem.setAskSize(ask.getSize());
} else if(event instanceof BidEvent) {
BidEvent bid = (BidEvent)event;
bidFound = true;
inItem.setInstrument(bid.getInstrument());
inItem.setBidPrice(bid.getPrice());
inItem.setBidSize(bid.getSize());
}
if(askFound && bidFound) {
break;
}
}
}
@Override
public void clear(MDTopOfBookImpl inItem)
{
inItem.setAskPrice(null);
inItem.setAskSize(null);
inItem.setBidPrice(null);
inItem.setBidSize(null);
}
};
/**
* manages market stat updates
*/
private ItemUpdater<MDMarketstatImpl> marketstatUpdater = new ItemUpdater<MDMarketstatImpl>() {
@Override
public void update(MDMarketstatImpl inItem,
Deque<Event> inEvents)
{
Event mostRecentEvent = inEvents.getFirst();
if(mostRecentEvent instanceof MarketstatEvent) {
MarketstatEvent statEvent = (MarketstatEvent)mostRecentEvent;
inItem.setInstrument(statEvent.getInstrument());
inItem.setCloseDate(statEvent.getCloseDate());
inItem.setClosePrice(statEvent.getClose());
inItem.setHighPrice(statEvent.getHigh());
inItem.setLowPrice(statEvent.getLow());
inItem.setOpenPrice(statEvent.getOpen());
inItem.setPreviousCloseDate(statEvent.getPreviousCloseDate());
inItem.setPreviousClosePrice(statEvent.getPreviousClose());
inItem.setVolumeTraded(statEvent.getVolume());
}
}
@Override
public void clear(MDMarketstatImpl inItem)
{
inItem.setCloseDate(null);
inItem.setClosePrice(null);
inItem.setHighPrice(null);
inItem.setLowPrice(null);
inItem.setOpenPrice(null);
inItem.setPreviousCloseDate(null);
inItem.setPreviousClosePrice(null);
inItem.setVolumeTraded(null);
}
};
/**
* manages aggregated depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> aggregatedDepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.AGGREGATED_DEPTH;
}
};
/**
* manages unaggregated depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> unaggregatedDepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.UNAGGREGATED_DEPTH;
}
};
/**
* manages bbo10 depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> bbo10DepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.BBO10;
}
};
/**
* manages level II depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> level2DepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.LEVEL_2;
}
};
/**
* manages open book depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> openBookDepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.OPEN_BOOK;
}
};
/**
* manages total view depth updates
*/
private ItemUpdater<MDDepthOfBookImpl> totalViewDepthUpdater = new AbstractDepthUpdater() {
@Override
protected Content getContent()
{
return Content.TOTAL_VIEW;
}
};
/**
* contains market data active requests by reference key (for reuse)
*/
private final Map<MarketDataReferenceKey,MarketDataDetails<?,?>> requests = Maps.newHashMap();
/**
* schedules market data refresh jobs
*/
private ScheduledExecutorService marketDataRefreshExecutor = Executors.newScheduledThreadPool(10);
/**
* provides access to the market data client
*/
private final IMarketDataClientProvider marketDataClientProvider;
/**
* indicates how frequently to check for top-of-book market data updates (in ms)
*/
private static final long TOP_UPDATE_FREQUENCY = 1000;
/**
* indicates how frequently to check for market data depth updates (in ms)
*/
private static final long DEPTH_UPDATE_FREQUENCY = 3000;
}