package org.marketcetera.core.position;
import java.math.BigDecimal;
import org.apache.commons.lang.Validate;
import org.marketcetera.core.instruments.UnderlyingSymbolSupport;
import org.marketcetera.core.position.impl.Messages;
import org.marketcetera.core.position.impl.PositionEngineImpl;
import org.marketcetera.messagehistory.ReportHolder;
import org.marketcetera.trade.ExecutionReport;
import org.marketcetera.trade.ExecutionType;
import org.marketcetera.trade.Hierarchy;
import org.marketcetera.trade.Instrument;
import org.marketcetera.trade.Originator;
import org.marketcetera.trade.ReportBase;
import org.marketcetera.trade.ReportID;
import org.marketcetera.trade.UserID;
import org.marketcetera.util.misc.ClassVersion;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.FunctionList;
import ca.odell.glazedlists.FunctionList.Function;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.util.concurrent.Lock;
/* $License$ */
/**
* Factory for creating position engines.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.5.0
*/
@ClassVersion("$Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $")
public class PositionEngineFactory {
/**
* Create a position engine for a dynamic list of trades.
*
* @param trades
* list of trades, 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
* @return a position engine
* @throws IllegalArgumentException
* if any parameter is null
*/
public static PositionEngine create(EventList<Trade<?>> trades,
IncomingPositionSupport incomingPositionSupport,
MarketDataSupport marketDataSupport,
UnderlyingSymbolSupport underlyingSymbolSupport) {
Validate.noNullElements(new Object[] { trades, incomingPositionSupport,
marketDataSupport, underlyingSymbolSupport });
Lock readLock = trades.getReadWriteLock().readLock();
readLock.lock();
try {
return new PositionEngineImpl(trades, incomingPositionSupport,
marketDataSupport, underlyingSymbolSupport);
} finally {
readLock.unlock();
}
}
/**
* Convenience method when using reports.
*
* @param reports
* list of reports, 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
* @return a position engine
* @throws IllegalArgumentException
* if any parameter is null
*/
public static PositionEngine createFromReports(
EventList<ReportBase> reports,
IncomingPositionSupport incomingPositionSupport,
MarketDataSupport marketDataSupport,
UnderlyingSymbolSupport underlyingSymbolSupport) {
Validate.noNullElements(new Object[] { reports,
incomingPositionSupport, marketDataSupport,
underlyingSymbolSupport });
Lock readLock = reports.getReadWriteLock().readLock();
readLock.lock();
try {
FilterList<ReportBase> validFills = new FilterList<ReportBase>(
reports, new ValidFillsMatcher());
FunctionList<ReportBase, Trade<?>> trades = new FunctionList<ReportBase, Trade<?>>(
validFills, new TradeFunction());
return create(trades, incomingPositionSupport, marketDataSupport,
underlyingSymbolSupport);
} finally {
readLock.unlock();
}
}
/**
* Convenience method when using report holders.
*
* @param holders
* list of report holders, 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
* @return a position engine
* @throws IllegalArgumentException
* if any parameter is null
*/
public static PositionEngine createFromReportHolders(
EventList<ReportHolder> holders,
IncomingPositionSupport incomingPositionSupport,
MarketDataSupport marketDataSupport,
UnderlyingSymbolSupport underlyingSymbolSupport) {
Validate.noNullElements(new Object[] { holders,
incomingPositionSupport, marketDataSupport,
underlyingSymbolSupport });
Lock readLock = holders.getReadWriteLock().readLock();
readLock.lock();
try {
FunctionList<ReportHolder, ReportBase> reports = new FunctionList<ReportHolder, ReportBase>(
holders, new ReportExtractor());
return createFromReports(reports, incomingPositionSupport,
marketDataSupport, underlyingSymbolSupport);
} finally {
readLock.unlock();
}
}
/**
* Function extracting reports from report holders.
*/
@ClassVersion("$Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $")
private final static class ReportExtractor implements
Function<ReportHolder, ReportBase> {
@Override
public ReportBase evaluate(ReportHolder sourceValue) {
return sourceValue.getReport();
}
}
/**
* Matcher that matches fills and partial fills that contain sufficient
* information to implement the {@link Trade} interface.
* <p>
* Invalid reports are logged and ignored.
*/
@ClassVersion("$Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $")
private final static class ValidFillsMatcher implements Matcher<ReportBase> {
@Override
public boolean matches(ReportBase item) {
Originator originator = item.getOriginator();
Hierarchy hierarchy = item.getHierarchy();
if(item instanceof ExecutionReport) {
ExecutionReport er = (ExecutionReport)item;
ExecutionType executionType = er.getExecutionType();
return originator.forPositions() && hierarchy.forPositions() && executionType != null && executionType.isFill() && isValid(er);
} else {
return false;
}
}
private boolean isValid(ExecutionReport report) {
if (notNull(report.getInstrument())
&& positive(report.getLastPrice())
&& notZero(report.getLastQuantity())) {
return true;
} else {
Messages.VALIDATION_MATCHER_INVALID_EXECUTION_REPORT.warn(PositionEngineFactory.class,
report);
return false;
}
}
private boolean notNull(Object object) {
return object != null;
}
private boolean notZero(BigDecimal number) {
return notNull(number) && number.signum() != 0;
}
private boolean positive(BigDecimal number) {
return notNull(number) && number.signum() == 1;
}
}
/**
* Function mapping execution reports to trades.
* <p>
* Note that even though the parameter type is ReportBase, the source
* elements must be ExecutionReport that represent fills or partial fills.
* Use {@link ValidFillsMatcher} to ensure this.
*/
@ClassVersion("$Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $")
private final static class TradeFunction implements
Function<ReportBase, Trade<?>> {
@Override
public Trade<?> evaluate(ReportBase sourceValue) {
return new ExecutionReportAdapter((ExecutionReport) sourceValue);
}
}
/**
* Adapts an {@link ExecutionReport} to be used as a Trade.
*/
@ClassVersion("$Id: PositionEngineFactory.java 16888 2014-04-22 18:32:36Z colin $")
private final static class ExecutionReportAdapter implements
Trade<Instrument> {
private final ExecutionReport mReport;
private final PositionKey<Instrument> mKey;
/**
* Constructor.
*
* @param report
* execution report to adapt
*/
public ExecutionReportAdapter(ExecutionReport report) {
mReport = report;
/*
* Use viewer id since the viewer is the originator, the one the
* position is associated with.
*/
UserID viewer = mReport.getViewerID();
mKey = PositionKeyFactory.createKey(report.getInstrument(), mReport
.getAccount(), viewer == null ? null : viewer.toString());
}
@Override
public PositionKey<Instrument> getPositionKey() {
return mKey;
}
@Override
public BigDecimal getPrice() {
return mReport.getLastPrice();
}
@Override
public BigDecimal getQuantity() {
BigDecimal lastQuantity = mReport.getLastQuantity();
if (lastQuantity != null) {
switch (mReport.getSide()) {
case Buy:
return lastQuantity;
case Sell:
case SellShort:
case SellShortExempt:
return lastQuantity.negate();
default:
break;
}
}
return null;
}
@Override
public long getSequenceNumber() {
ReportID reportId = mReport.getReportID();
return reportId == null ? null : reportId.longValue();
}
@Override
public String toString() {
return Messages.EXECUTION_REPORT_ADAPTER_TO_STRING.getText(String
.valueOf(getPositionKey().getInstrument().getSymbol()),
String.valueOf(getPositionKey().getAccount()), String
.valueOf(getPositionKey().getTraderId()), String
.valueOf(getPrice()),
String.valueOf(getQuantity()), String
.valueOf(getSequenceNumber()), mReport.toString());
}
}
}