/******************************************************************************* * Copyright (c) 2013 Luigi Sgro. All rights reserved. This * program and the accompanying materials are made available under the terms of * the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Luigi Sgro - initial API and implementation ******************************************************************************/ package com.quantcomponents.marketdata; import java.net.ConnectException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import com.quantcomponents.core.exceptions.RequestFailedException; import com.quantcomponents.core.model.BarSize; import com.quantcomponents.core.model.DataType; import com.quantcomponents.core.model.IContract; import com.quantcomponents.core.model.ISeries; import com.quantcomponents.core.model.ISeriesPoint; import com.quantcomponents.core.model.ITaskMonitor; public abstract class MarketDataManager implements IMarketDataManager { /** * Fills a mutable time series with historical data */ public class HistoricalDataFeeder { private static final int MIN_REALTIME_DATA_RESOLUTION_SEC = 5; protected final IMarketDataProvider provider; protected final IMutableOHLCTimeSeries timeSeries; public HistoricalDataFeeder(IMarketDataProvider provider, IMutableOHLCTimeSeries timeSeries) { this.provider = provider; this.timeSeries = timeSeries; } protected Date alignToTimeResBoundary(Date input) { Calendar tmpCal = Calendar.getInstance(); tmpCal.setTime(input); tmpCal.set(Calendar.MILLISECOND, 0); int seconds = tmpCal.get(Calendar.SECOND); if (seconds % MIN_REALTIME_DATA_RESOLUTION_SEC > 0) { tmpCal.add(Calendar.SECOND, MIN_REALTIME_DATA_RESOLUTION_SEC - seconds % MIN_REALTIME_DATA_RESOLUTION_SEC); } return tmpCal.getTime(); } public void fillHistoricalData(Date startDateTime, Date endDateTime, ITaskMonitor taskMonitor) throws ConnectException, RequestFailedException { LinkedList<IOHLCPoint> backwardHistoricalData = new LinkedList<IOHLCPoint>(); LinkedList<IOHLCPoint> forwardHistoricalData = new LinkedList<IOHLCPoint>(); Date alignedStartDateTime = alignToTimeResBoundary(startDateTime); Date alignedEndDateTime = alignToTimeResBoundary(endDateTime); if (!timeSeries.isEmpty()) { Date seriesStartDateTime = timeSeries.getFirst().getIndex(); if (alignedStartDateTime.before(seriesStartDateTime)) { logger.log(Level.INFO, "Retrieving older historical data"); List<IOHLCPoint> olderHistoricalData = provider.historicalBars(timeSeries.getContract(), alignedStartDateTime, seriesStartDateTime, timeSeries.getBarSize(), timeSeries.getDataType(), timeSeries.isIncludeAfterHours(), taskMonitor); backwardHistoricalData.addAll(olderHistoricalData); } Date seriesEndDateTime = timeSeries.getLast().getIndex(); if (alignedEndDateTime.after(seriesEndDateTime)) { logger.log(Level.INFO, "Retrieving latest historical data"); List<IOHLCPoint> latestHistoricalData = provider.historicalBars(timeSeries.getContract(), seriesEndDateTime, alignedEndDateTime, timeSeries.getBarSize(), timeSeries.getDataType(), timeSeries.isIncludeAfterHours(), taskMonitor); forwardHistoricalData.addAll(latestHistoricalData); } } else { logger.log(Level.INFO, "Retrieving all historical data"); List<IOHLCPoint> allHistoricalData = provider.historicalBars(timeSeries.getContract(), alignedStartDateTime, alignedEndDateTime, timeSeries.getBarSize(), timeSeries.getDataType(), timeSeries.isIncludeAfterHours(), taskMonitor); forwardHistoricalData.addAll(allHistoricalData); } ListIterator<IOHLCPoint> backwardData = backwardHistoricalData.listIterator(backwardHistoricalData.size()); logger.log(Level.INFO, "Merging older historical data"); while (backwardData.hasPrevious()) { timeSeries.addFirstIfNotExists(backwardData.previous()); } Iterator<IOHLCPoint> forwardData = forwardHistoricalData.iterator(); logger.log(Level.INFO, "Merging latest historical data"); while (forwardData.hasNext()) { timeSeries.addLastIfNotExists(forwardData.next()); } logger.log(Level.INFO, "Finished merging historical data"); } } private static final Logger logger = Logger.getLogger(MarketDataManager.class.getName()); private volatile IStockDatabaseContainer stockDatabaseContainer; private volatile IMarketDataProvider marketDataProvider; public void setMarketDataProvider(IMarketDataProvider marketDataProvider) { this.marketDataProvider = marketDataProvider; } public void setStockDatabaseContainer(IStockDatabaseContainer stockDatabaseContainer) { this.stockDatabaseContainer = stockDatabaseContainer; } @Override public DataType[] availableDataTypes() { return getMarketDataProvider().availableDataTypes(); } @Override public BarSize[] availableBarSizes() { return getMarketDataProvider().availableBarSizes(); } @Override public List<IContract> searchContracts(IContract criteria, ITaskMonitor taskMonitor) throws ConnectException, RequestFailedException { return getMarketDataProvider().searchContracts(criteria, taskMonitor); } @Override public Collection<IStockDatabase> allStockDatabases() { return new ArrayList<IStockDatabase>(getStockDatabaseContainer().allStockDatabases()); } @Override public IStockDatabase findStockDatabase(IContract contract, DataType dataType, BarSize barSize, Boolean includeAfterHours) { for (IStockDatabase stockDb : getStockDatabaseContainer().allStockDatabases()) { if (stockDatabaseQualifies(stockDb, contract, dataType, barSize, includeAfterHours)) { return stockDb; } } return null; } @Override public IStockDatabase getStockDatabase(String ID) { return getStockDatabaseContainer().getStockDatabase(ID); } @Override public ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> getSeries(String ID) { IStockDatabase stockDatabase = getStockDatabase(ID); return stockDatabase == null ? null : stockDatabase.getVirtualTimeSeries(); } @Override public IStockDatabase createStockDatabase(IContract contract, DataType dataType, BarSize barSize, boolean includeAfterHours, TimeZone timeZone) { IStockDatabase stockDb = new StockDatabase(contract, dataType, barSize, includeAfterHours, timeZone); getStockDatabaseContainer().addStockDatabase(stockDb); return stockDb; } @Override public void removeStockDatabase(IStockDatabase stockDb) throws ConnectException, RequestFailedException { getStockDatabaseContainer().removeStockDatabase(stockDb); } @Override public void fillHistoricalData(IStockDatabase stockDb, Date startDate, Date endDate, ITaskMonitor taskMonitor) throws ConnectException, RequestFailedException { new HistoricalDataFeeder(getMarketDataProvider(), stockDb.getOHLCTimeSeries()).fillHistoricalData(startDate, endDate, taskMonitor); } @Override public int numberOfStockDatabases() { return getStockDatabaseContainer().size(); } private static boolean stockDatabaseQualifies(IStockDatabase stockDb, IContract contract, DataType dataType, BarSize barSize, Boolean includeAfterHours) { if (contract != null && !stockDb.getContract().equals(contract)) { return false; } if (dataType != null && !dataType.equals(stockDb.getDataType())) { return false; } if (barSize != null && !barSize.equals(stockDb.getBarSize())) { return false; } if (includeAfterHours != null && !includeAfterHours.equals(stockDb.isIncludeAfterHours())) { return false; } return true; } protected IStockDatabaseContainer getStockDatabaseContainer() { return stockDatabaseContainer; } protected IMarketDataProvider getMarketDataProvider() { return marketDataProvider; } }