package org.marketcetera.core.position.impl;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.Map;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.marketcetera.core.instruments.UnderlyingSymbolSupport;
import org.marketcetera.core.position.Grouping;
import org.marketcetera.core.position.IncomingPositionSupport;
import org.marketcetera.core.position.MarketDataSupport;
import org.marketcetera.core.position.PositionEngine;
import org.marketcetera.core.position.PositionKey;
import org.marketcetera.core.position.PositionRow;
import org.marketcetera.core.position.Trade;
import org.marketcetera.core.position.impl.GroupingList.GroupMatcher;
import org.marketcetera.core.position.impl.GroupingList.GroupMatcherFactory;
import org.marketcetera.trade.Instrument;
import org.marketcetera.util.misc.ClassVersion;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FunctionList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.FunctionList.AdvancedFunction;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.util.concurrent.Lock;
import com.google.common.collect.Maps;
/* $License$ */
/**
* Position engine implementation.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $
* @since 1.5.0
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
public final class PositionEngineImpl implements PositionEngine {
/**
* Comparator for PositionRows that imposes a default ordering of the data.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class PositionRowComparator implements
Comparator<PositionRow> {
@Override
public int compare(PositionRow o1, PositionRow o2) {
return new CompareToBuilder().append(o1.getTraderId(),
o2.getTraderId()).append(o1.getUnderlying(),
o2.getUnderlying())
.append(o1.getAccount(), o2.getAccount()).toComparison();
}
}
/**
* Supports grouping of trades by trader id, symbol, and account.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class TradeGroupMatcher implements
GroupMatcher<Trade<?>> {
private final PositionKey<?> mKey;
public TradeGroupMatcher(Trade<?> trade) {
mKey = trade.getPositionKey();
}
@Override
public boolean matches(Trade<?> item) {
return internalCompare(item.getPositionKey()) == 0;
}
@Override
public int compareTo(GroupMatcher<Trade<?>> o) {
TradeGroupMatcher rhs = (TradeGroupMatcher) o;
return internalCompare(rhs.mKey);
}
private int internalCompare(PositionKey<?> key) {
return PositionKeyComparator.INSTANCE.compare(mKey, key);
}
}
/**
* Creates group matchers from trades. Used by {@link GroupingList}.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class TradeGroupMatcherFactory implements
GroupMatcherFactory<Trade<?>, GroupMatcher<Trade<?>>> {
@Override
public TradeGroupMatcher createGroupMatcher(final Trade<?> element) {
return new TradeGroupMatcher(element);
}
};
/**
* Supports grouping of positions by a number of grouping criteria.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class GroupingMatcher implements
GroupMatcher<PositionRow> {
private final String[] mValues;
private final Grouping[] mGrouping;
public GroupingMatcher(PositionRow row, Grouping... grouping) {
mGrouping = grouping;
mValues = getValues(row);
}
private String[] getValues(PositionRow item) {
String[] values = new String[mGrouping.length];
for (int i = 0; i < mGrouping.length; i++) {
values[i] = mGrouping[i].get(item);
}
return values;
}
@Override
public boolean matches(PositionRow item) {
return Arrays.equals(mValues, getValues(item));
}
@Override
public int compareTo(GroupMatcher<PositionRow> o) {
GroupingMatcher other = (GroupingMatcher) o;
return new CompareToBuilder().append(mValues, other.mValues)
.toComparison();
}
}
/**
* Creates group matchers from position rows.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class GroupingMatcherFactory implements
GroupMatcherFactory<PositionRow, GroupMatcher<PositionRow>> {
private final Grouping[] mGroupings;
public GroupingMatcherFactory(Grouping... groupings) {
mGroupings = groupings;
}
@Override
public GroupingMatcher createGroupMatcher(final PositionRow element) {
return new GroupingMatcher(element, mGroupings);
}
}
/**
* Converts an {@link EventList} of positions into a dynamically updated
* summary {@link PositionRow}. Used by {@link FunctionList}.
*/
@ClassVersion("$Id: PositionEngineImpl.java 16154 2012-07-14 16:34:05Z colin $")
private final static class SummarizeFunction implements
AdvancedFunction<EventList<PositionRow>, PositionRow> {
private final Map<EventList<PositionRow>, SummaryRowUpdater> map = new IdentityHashMap<EventList<PositionRow>, SummaryRowUpdater>();
private final Grouping[] mGrouping;
public SummarizeFunction(Grouping... grouping) {
mGrouping = grouping;
}
@Override
public void dispose(EventList<PositionRow> sourceValue,
PositionRow transformedValue) {
SummaryRowUpdater calc = map.remove(sourceValue);
calc.dispose();
}
@Override
public PositionRow reevaluate(EventList<PositionRow> sourceValue,
PositionRow transformedValue) {
return map.get(sourceValue).getSummary();
}
@Override
public PositionRow evaluate(EventList<PositionRow> sourceValue) {
PositionRow row = sourceValue.get(0);
// use the key data from the first row...it won't exactly match all
// rows, but it's sufficient for further grouping
PositionRowImpl summary = new PositionRowImpl(row.getInstrument(),
row.getUnderlying(), row.getAccount(), row.getTraderId(), mGrouping, sourceValue);
SummaryRowUpdater calculator = new SummaryRowUpdater(summary);
map.put(sourceValue, calculator);
return calculator.getSummary();
}
}
private final MarketDataSupport mMarketDataSupport;
private final IncomingPositionSupport mIncomingPositionSupport;
private final UnderlyingSymbolSupport mUnderlyingSymbolSupport;
private final SortedList<Trade<?>> mSorted;
private final GroupingList<Trade<?>> mGrouped;
private final EventList<PositionRow> mPositionsBase;
private final EventList<PositionRow> mFlatView;
private final Map<PositionKey<?>, PositionRowUpdater> mPositions = Maps
.newHashMap();
/**
* Constructor.
*
* @param trades
* base list of reports to drive the positions lists, cannot be
* null
* @param incomingPositionSupport
* support for incoming positions, cannot be null
* @param marketDataSupport
* support for market data, cannot be null
* @param underlyingSymbolSupport
* support for underlying symbol, cannot be null
* @throws IllegalArgumentException
* if any parameter is null
*/
public PositionEngineImpl(EventList<Trade<?>> trades,
IncomingPositionSupport incomingPositionSupport,
MarketDataSupport marketDataSupport,
UnderlyingSymbolSupport underlyingSymbolSupport) {
Validate.noNullElements(new Object[] { trades, incomingPositionSupport,
marketDataSupport, underlyingSymbolSupport });
mMarketDataSupport = marketDataSupport;
mIncomingPositionSupport = incomingPositionSupport;
mUnderlyingSymbolSupport = underlyingSymbolSupport;
mSorted = new SortedList<Trade<?>>(trades, new Comparator<Trade<?>>() {
@Override
public int compare(Trade<?> o1, Trade<?> o2) {
return new Long(o1.getSequenceNumber()).compareTo(new Long(o2
.getSequenceNumber()));
}
});
mGrouped = new GroupingList<Trade<?>>(mSorted,
new TradeGroupMatcherFactory());
mPositionsBase = new BasicEventList<PositionRow>(mGrouped
.getReadWriteLock());
for (PositionKey<?> key : mIncomingPositionSupport
.getIncomingPositions().keySet()) {
addPosition(key, null);
}
for (EventList<Trade<?>> trade : mGrouped) {
addPosition(trade);
}
mGrouped
.addListEventListener(new ListEventListener<EventList<Trade<?>>>() {
@Override
public void listChanged(
ListEvent<EventList<Trade<?>>> listChanges) {
while (listChanges.next()) {
if (listChanges.getType() == ListEvent.INSERT) {
EventList<Trade<?>> newTrades = listChanges
.getSourceList().get(
listChanges.getIndex());
addPosition(newTrades);
}
}
}
});
mFlatView = new ObservableElementList<PositionRow>(
new SortedList<PositionRow>(mPositionsBase,
new PositionRowComparator()), GlazedLists
.beanConnector(PositionRow.class, true,
"positionMetrics")); //$NON-NLS-1$
}
private void addPosition(EventList<Trade<?>> trades) {
PositionKey<?> key = getKey(trades);
PositionRowUpdater updater = mPositions.get(key);
if (updater == null) {
addPosition(key, trades);
} else {
updater.connect(trades);
}
}
private PositionKey<?> getKey(EventList<Trade<?>> sourceValue) {
Trade<?> trade = sourceValue.get(0);
return trade.getPositionKey();
}
private void addPosition(PositionKey<?> key, EventList<Trade<?>> trades) {
Instrument instrument = key.getInstrument();
String underlying = mUnderlyingSymbolSupport.getUnderlying(instrument);
PositionRowImpl positionRow = new PositionRowImpl(instrument,
underlying,
key.getAccount(), key.getTraderId(), mIncomingPositionSupport
.getIncomingPositionFor(key));
PositionRowUpdater updater = new PositionRowUpdater(positionRow,
trades, mMarketDataSupport);
mPositions.put(key, updater);
mPositionsBase.add(positionRow);
}
@Override
public PositionData getFlatData() {
return new PositionData() {
@Override
public EventList<PositionRow> getPositions() {
return mFlatView;
}
@Override
public void dispose() {
// nothing to do, the flat view is kept in memory
}
};
}
@Override
public PositionData getGroupedData(Grouping... groupings) {
Validate.noNullElements(groupings);
if (groupings.length != 2 || groupings[0] == groupings[1]) {
throw new UnsupportedOperationException();
}
/*
* The API accepts two groupings, i.e. Underlying, then Account. We
* compute the other implied one because it is needed to do the
* matching.
*/
EnumSet<Grouping> complements = EnumSet.complementOf(EnumSet.of(
groupings[0], groupings[1]));
if (complements.size() != 1) {
throw new IllegalStateException();
}
Grouping complement = complements.iterator().next();
/*
* This builds up a chain of lists off of mFlatView, each grouping and
* summarizing its children.
*
* We are essentially builds a tree from the
* bottom up. The leaf nodes are the positions, i.e. the elements in
* mFlatView. Intermediary nodes are summaries of their children.
*/
Lock lock = mFlatView.getReadWriteLock().readLock();
lock.lock();
try {
/*
* The first grouping is on all Grouping values, i.e. partition the
* positions into groups that match on everything.
*/
final GroupingList<PositionRow> grouped1 = new GroupingList<PositionRow>(
mFlatView, new GroupingMatcherFactory(groupings[0], groupings[1], complement));
/*
* Now make a new list that has one element for each group that
* summarizes the position values. These form the next level of tree nodes.
*/
final FunctionList<EventList<PositionRow>, PositionRow> summarized1 = new FunctionList<EventList<PositionRow>, PositionRow>(
grouped1, new SummarizeFunction(groupings[0], groupings[1], complement));
/*
* Partition the above summary list into groups that match on the
* two provided values.
*/
final GroupingList<PositionRow> grouped2 = new GroupingList<PositionRow>(
summarized1, new GroupingMatcherFactory(groupings[0],
groupings[1]));
/*
* Summarize the new groups to make the next level of tree nodes.
*/
final FunctionList<EventList<PositionRow>, PositionRow> summarized2 = new FunctionList<EventList<PositionRow>, PositionRow>(
grouped2, new SummarizeFunction(groupings[0], groupings[1]));
/*
* Partition the above summary list into groups that match on the
* final grouping.
*/
final GroupingList<PositionRow> grouped3 = new GroupingList<PositionRow>(
summarized2, new GroupingMatcherFactory(groupings[0]));
/*
* Summarize the final groups for the top level nodes.
*/
final FunctionList<EventList<PositionRow>, PositionRow> summarized3 = new FunctionList<EventList<PositionRow>, PositionRow>(
grouped3, new SummarizeFunction(groupings[0]));
return new PositionData() {
@Override
public EventList<PositionRow> getPositions() {
return summarized3;
}
@Override
public void dispose() {
summarized3.dispose();
grouped3.dispose();
summarized2.dispose();
grouped2.dispose();
summarized1.dispose();
grouped1.dispose();
}
};
} finally {
lock.unlock();
}
}
@Override
public void dispose() {
mPositionsBase.dispose();
mGrouped.dispose();
mSorted.dispose();
mMarketDataSupport.dispose();
}
}