package org.marketcetera.messagehistory;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.marketcetera.trade.ExecutionReport;
import org.marketcetera.trade.OrderCancelReject;
import org.marketcetera.trade.OrderID;
import org.marketcetera.trade.OrderStatus;
import org.marketcetera.trade.Originator;
import org.marketcetera.trade.ReportBase;
import org.marketcetera.util.misc.ClassVersion;
import ca.odell.glazedlists.FunctionList.Function;
/* $License$ */
/**
* Function that chooses the report holder representing the status of an open order. Its input is a
* list of the entire order chain for a given order id.
*
* The result is typically the latest execution report, but there are some edge cases. For example,
* broker reports are favored over server acks. Also, obsolete orders in the chain are not
* considered, e.g. if a cancel-replace was rejected.
*
* A null report will be returned if the order chain is completely obsolete (canceled, filled,
* etc.).
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: OpenOrderListFunction.java 16889 2014-04-22 18:46:09Z colin $
* @since 1.5.0
*/
@ClassVersion("$Id: OpenOrderListFunction.java 16889 2014-04-22 18:46:09Z colin $")
final class OpenOrderListFunction implements Function<List<ReportHolder>, ReportHolder> {
private static final Comparator<ReportHolder> sComparator = new Comparator<ReportHolder>() {
@Override
public int compare(ReportHolder o1, ReportHolder o2) {
ReportBase r1 = o1.getReport();
ReportBase r2 = o2.getReport();
// sort by descending report id (sequence number)
return new CompareToBuilder().append(r2.getReportID().longValue(),
r1.getReportID().longValue()).toComparison();
}
};
@Override
public ReportHolder evaluate(List<ReportHolder> sourceValue) {
Set<OrderID> obsolete = new HashSet<OrderID>();
// out is a placeholder for a server ack if it is found
ReportHolder out = null;
ReportHolder[] reversedHolders = sourceValue.toArray(new ReportHolder[sourceValue.size()]);
// sort the list to guarantee the result is correct
// NOTE: it may be possible to accomplish the same thing without sorting
// for better performance
Arrays.sort(reversedHolders, sComparator);
// this is worst case O(n) if there are tons of OrderCancelRejects, or the latest execution
// report is a server ack, e.g. PENDING_NEW.
for (int i = 0; i < reversedHolders.length; i++) {
ReportHolder reportHolder = reversedHolders[i];
ReportBase report = reportHolder.getReport();
OrderID orderId = report.getOrderID();
// can't work with a report without an id
if (orderId == null) {
continue;
}
if (report instanceof OrderCancelReject) {
// the cancel or cancel-replace was rejected
obsolete.add(orderId);
} else if(report instanceof ExecutionReport) {
ExecutionReport eReport = (ExecutionReport) report;
if(CLOSED.contains(eReport.getOrderStatus())) {
// order has been filled, canceled, or rejected, whole chain is obsolete
return null;
} else if(eReport.isCancelable()) {
if(out != null) {
// we have a placeholder already, only override it if we find a
// broker report with the same order id, e.g. the NEW for a PENDING_NEW
if(eReport.getOriginator().forOrders() && eReport.getHierarchy().forOrders() && out.getReport().getOrderID().equals(orderId)) {
return reportHolder;
}
} else if(!obsolete.contains(orderId)) {
// this is the latest non-obsolete execution report, return it if it
// is from the broker, otherwise set the placeholder and keep iterating
// to find a broker report if one exists
if(eReport.getOriginator().forOrders() && eReport.getHierarchy().forOrders()) {
return reportHolder;
} else {
if(eReport.getOriginator() == Originator.Server) {
out = reportHolder;
}
}
}
}
}
}
// out can be null, in which case no suitable report was found.
// If it is non null, it means the latest report was a server ack and no broker
// report of the same id was found.
return out;
}
/**
* indicates status values that are to be considered closed
*/
private static final Set<OrderStatus> CLOSED = EnumSet.of(OrderStatus.Filled,OrderStatus.Canceled,OrderStatus.Rejected,OrderStatus.Expired);
}