package org.marketcetera.client.utils; import java.util.*; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import org.marketcetera.client.Client; import org.marketcetera.client.ClientInitException; import org.marketcetera.client.ClientManager; import org.marketcetera.client.ConnectionException; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.OrderID; import org.marketcetera.trade.ReportBase; import org.marketcetera.trade.ReportBaseImpl; import org.marketcetera.trade.utils.OrderHistoryManager; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.misc.ClassVersion; import org.springframework.context.Lifecycle; /* $License$ */ /** * Provides a historically-aware {@link OrderHistoryManager} implementation. * * <p>Instantiate this class with an origin date. The origin date establishes how far back * to look for order history. Open orders are always included in the history, regardless of the * origin date. * * <p>Note that there are significant performance and resource implications when using this class. * Depending on historical order volume, this class may be required to process thousands or millions * of reports. There are two ramifications of this: * <ul> * <li>Make an effort to limit the number of instances of this class. Each instance in the same process * has access to the same reports. The only reason to have more than one instance of this class is * if more than one historical range is required. Even then, it is preferable to use a single instance * with the oldest origin date.</li> * <li>Use the most recent origin date feasible. Ideally, make this midnight of the current day, or whatever * makes sense for the current trading session. Obviously, business requirements will dictate what * the origin date is.</li> * </ul> * * <p>It may take a significant amount of time to {@link #start() start} this object as it must process historical * order history. Callers may choose to make this operation asynchronous. The object will report that it * {@link #isRunning() is running} when the processing is complete. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: LiveOrderHistoryManager.java 16841 2014-02-20 19:59:04Z colin $ * @since 2.1.4 */ @ClassVersion("$Id: LiveOrderHistoryManager.java 16841 2014-02-20 19:59:04Z colin $") public class LiveOrderHistoryManager extends OrderHistoryManager implements Lifecycle { /** * Create a new LiveOrderHistoryManager instance. * * @param inReportHistoryOrigin a <code>Date</code> value indicating the point from which to gather order history or <code>null</code> * @throws ClientInitException if a connection to the <code>Client</code> cannot be made */ public LiveOrderHistoryManager(Date inReportHistoryOrigin) throws ClientInitException { if(inReportHistoryOrigin == null) { reportHistoryOrigin = new Date(0); } else { reportHistoryOrigin = inReportHistoryOrigin; } client = ClientManager.getInstance(); } /** * Gets the open orders. * * <p>The collection returned by this operation will reflect changes to the underlying order history. * * <p>The <code>LiveOrderHistoryManager</code> object must be {@link #start() started} before this operation * may be successfully invoked. * * @return a <code>Map<OrderID,ExecutionReport></code> value * @throws IllegalStateException if the object has not started */ @Override public Map<OrderID,ExecutionReport> getOpenOrders() { if(!isRunning) { throw new IllegalStateException(org.marketcetera.client.Messages.OPEN_ORDER_LIST_NOT_READY.getText()); } return super.getOpenOrders(); } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#isRunning() */ @Override public synchronized boolean isRunning() { return isRunning; } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#start() */ @Override public synchronized void start() { if(isRunning) { stop(); } SLF4JLoggerProxy.debug(LiveOrderHistoryManager.class, "LiveOrderHistoryManager starting - collecting order history since {}", //$NON-NLS-1$ reportHistoryOrigin); // note that live reports may be flowing in from this point, but we must not process them // until we have processed all snapshot reports // collect the snapshot reports // note that there is a non-zero chance of getting a duplicate report here (one just came in // between the time we added ourself as a listener and we make the call to get historical reports - // we'll get it in both channels). this actually doesn't matter because the OrderID is the same on // each so we'll just process it twice but it won't create any duplicate records in our map keyed by OrderID final Deque<ReportBase> snapshotReports = new LinkedList<ReportBase>(); try { ReportBase[] orderHistory = client.getReportsSince(reportHistoryOrigin); SLF4JLoggerProxy.debug(LiveOrderHistoryManager.class, "{} report(s) to process", //$NON-NLS-1$ orderHistory.length); final SortedSet<ReportBase> tempSnapshotReports = new TreeSet<ReportBase>(ReportBase.ReportComparator.INSTANCE); for(ReportBase report : orderHistory) { tempSnapshotReports.add(report); } List<ReportBaseImpl> openOrders = client.getOpenOrders(); if(openOrders != null) { for(ReportBase openOrder : openOrders) { if(!tempSnapshotReports.contains(openOrder)) { tempSnapshotReports.add(openOrder); } } } snapshotReports.addAll(tempSnapshotReports); } catch (ConnectionException e) { throw new RuntimeException(e); } // snapshotReports contains all the reports as dictated by the origin date if(!snapshotReports.isEmpty()) { for(ReportBase report : snapshotReports) { LiveOrderHistoryManager.super.add(report); } snapshotReports.clear(); SLF4JLoggerProxy.debug(LiveOrderHistoryManager.class, "All historical reports processed"); //$NON-NLS-1$ } // create and start the report processor reportProcessor = new Thread(new Runnable() { @Override public void run() { try { while(isRunning) { // process any updates that exist add(updateReports.take()); } } catch (InterruptedException ignored) {} } }, "LiveOrderHistoryManager Report Processor"); //$NON-NLS-1$ reportProcessor.start(); isRunning = true; } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#stop() */ @Override public synchronized void stop() { SLF4JLoggerProxy.debug(LiveOrderHistoryManager.class, "LiveOrderHistoryManager stopping"); //$NON-NLS-1$ if(!isRunning) { return; } if(reportProcessor != null) { reportProcessor.interrupt(); try { reportProcessor.join(); } catch (InterruptedException ignored) {} reportProcessor = null; } clear(); isRunning = false; } /** * Gets the report history origin date used by this order history manager. * * @return a <code>Date</code> value */ public Date getReportHistoryOrigin() { return reportHistoryOrigin; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("LiveOrderHistoryManager [").append(isRunning?"running":"not running").append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ return builder.toString(); } /** * Get the client value. * * @return a <code>Client</code> value */ protected Client getClient() { return client; } /** * processes incoming reports from the live report channel */ private volatile Thread reportProcessor; /** * collection which contains incoming reports from the live report channel */ private final BlockingDeque<ReportBase> updateReports = new LinkedBlockingDeque<ReportBase>(); /** * date from which to gather status */ private final Date reportHistoryOrigin; /** * indicates if the object is active */ private volatile boolean isRunning = false; /** * connection to the client */ private final Client client; }