/*******************************************************************************
* 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.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.quantcomponents.core.model.BarSize;
import com.quantcomponents.core.model.DataType;
import com.quantcomponents.core.model.IContract;
import com.quantcomponents.core.model.IMutableSeries;
import com.quantcomponents.core.model.ISeriesListener;
/**
* Virtual OHLC time series.
* This class consolidates information from OHLC and tick series, presenting a dynamic view of the data
* in terms of OHLC points.
* For each update, the listeners are advised with the latest values, effectively conveying both OHLC and real time tick data.
*/
public class OHLCVirtualTimeSeries implements IOHLCTimeSeries {
private static final Logger logger = Logger.getLogger(OHLCVirtualTimeSeries.class.getName());
private final OHLCTimeSeries innerTimeSeries;
private final Calendar calendar;
private final List<ISeriesListener<Date, Double>> listeners = new CopyOnWriteArrayList<ISeriesListener<Date, Double>>();
public OHLCVirtualTimeSeries(String ID, IContract contract, DataType dataType, BarSize barSize, boolean includeAfterHours, TimeZone timeZone) {
innerTimeSeries = new OHLCTimeSeries(ID, contract, barSize, dataType, includeAfterHours, timeZone);
calendar = Calendar.getInstance(timeZone);
}
private Date alignToBeginningOfBar(Date date) {
calendar.setTime(date);
getBarSize().adjustCalendarToBarBeginning(calendar);
return calendar.getTime();
}
@Override
public synchronized int size() {
if (isEmpty()) {
return 0;
}
long span = innerTimeSeries.getLast().getIndex().getTime() - innerTimeSeries.getFirst().getIndex().getTime();
return (int) (span / innerTimeSeries.getInterval()) + 1;
}
@Override
public boolean isEmpty() {
return innerTimeSeries.isEmpty();
}
public void addTick(ITickPoint tick) {
Date barDate = alignToBeginningOfBar(tick.getIndex());
IOHLCPoint oldBar = null;
OHLCPointAccumulator newBar = null;
synchronized (this) {
if (innerTimeSeries.isEmpty()) {
newBar = new OHLCPointAccumulator(innerTimeSeries.getBarSize(), innerTimeSeries.getDataType(), barDate);
newBar.addTick(tick);
innerTimeSeries.addLast(newBar);
} else {
OHLCPointAccumulator existingBar = null;
if (barDate.equals(innerTimeSeries.getFirst().getIndex())) {
existingBar = (OHLCPointAccumulator) innerTimeSeries.getFirst();
} else if (barDate.equals(innerTimeSeries.getLast().getIndex())) {
existingBar = (OHLCPointAccumulator) innerTimeSeries.getLast();
}
if (existingBar != null) {
oldBar = OHLCPoint.copy(existingBar);
existingBar.addTick(tick);
newBar = existingBar;
} else {
if (barDate.before(innerTimeSeries.getFirst().getIndex())) {
newBar = new OHLCPointAccumulator(innerTimeSeries.getBarSize(), innerTimeSeries.getDataType(), barDate);
newBar.addTick(tick);
innerTimeSeries.addFirst(newBar);
} else if (barDate.after(innerTimeSeries.getLast().getIndex())) {
newBar = new OHLCPointAccumulator(innerTimeSeries.getBarSize(), innerTimeSeries.getDataType(), barDate);
newBar.addTick(tick);
innerTimeSeries.addLast(newBar);
} else {
newBar = null; // ignoring already present historical data
}
}
}
}
if (oldBar != null) {
notifyBarUpdated(oldBar, newBar);
} else if (newBar != null) {
notifyBarAdded(newBar);
}
}
public void addOrUpdateBar(IOHLCPoint bar) {
Date barDate = alignToBeginningOfBar(bar.getIndex());
if (getBarSize() != BarSize.ONE_DAY && !barDate.equals(bar.getIndex())) {
logger.log(Level.WARNING, "Bar date: " + bar.getIndex() + " not aligned with reference date: " + barDate);
}
OHLCPointAccumulator newBar = OHLCPointAccumulator.fromIOHLCPoint(bar, innerTimeSeries.getDataType());
IOHLCPoint oldBar = null;
synchronized (this) {
if (innerTimeSeries.isEmpty()) {
innerTimeSeries.addLast(newBar);
} else {
IOHLCPoint existingBar = null;
if (barDate.equals(innerTimeSeries.getFirst().getIndex())) {
existingBar = (IOHLCPoint) innerTimeSeries.getFirst();
} else if (barDate.equals(innerTimeSeries.getLast().getIndex())) {
existingBar = (IOHLCPoint) innerTimeSeries.getLast();
}
if (existingBar != null) {
oldBar = OHLCPoint.copy(existingBar);
innerTimeSeries.updateTail(newBar);
} else {
if (barDate.before(innerTimeSeries.getFirst().getIndex())) {
innerTimeSeries.addFirst(newBar);
} else if (barDate.after(innerTimeSeries.getLast().getIndex())) {
innerTimeSeries.addLast(newBar);
} else {
newBar = null; // ignoring already present historical data
}
}
}
}
if (oldBar != null) {
notifyBarUpdated(oldBar, newBar);
} else if (newBar != null) {
notifyBarAdded(newBar);
}
}
private void notifyBarAdded(IOHLCPoint bar) {
for (ISeriesListener<Date, Double> listener : listeners) {
listener.onItemAdded(bar);
}
}
private void notifyBarUpdated(IOHLCPoint oldBar, IOHLCPoint updatedBar) {
for (ISeriesListener<Date, Double> listener : listeners) {
listener.onItemUpdated(oldBar, updatedBar);
}
}
@Override
public long getTimestamp() {
return innerTimeSeries.getTimestamp();
}
@Override
public IContract getContract() {
return innerTimeSeries.getContract();
}
@Override
public BarSize getBarSize() {
return innerTimeSeries.getBarSize();
}
@Override
public DataType getDataType() {
return innerTimeSeries.getDataType();
}
@Override
public boolean isIncludeAfterHours() {
return innerTimeSeries.isIncludeAfterHours();
}
@Override
public IOHLCPoint getFirst() {
return (IOHLCPoint) innerTimeSeries.getFirst();
}
@Override
public IOHLCPoint getLast() {
return (IOHLCPoint) innerTimeSeries.getLast();
}
@Override
public IOHLCPoint getMinimum() {
return (IOHLCPoint) innerTimeSeries.getMinimum();
}
@Override
public IOHLCPoint getMaximum() {
return (IOHLCPoint) innerTimeSeries.getMaximum();
}
@Override
public void addSeriesListener(ISeriesListener<Date, Double> listener) {
listeners.add(listener);
}
@Override
public void removeSeriesListener(ISeriesListener<Date, Double> listener) {
listeners.remove(listener);
}
@Override
public Iterator<IOHLCPoint> iterator() {
return innerTimeSeries.iterator();
}
@Override
public Iterator<IOHLCPoint> descendingIterator() {
return innerTimeSeries.descendingIterator();
}
@Override
public TimeZone getTimeZone() {
return innerTimeSeries.getTimeZone();
}
@Override
public long getInterval() {
return innerTimeSeries.getInterval();
}
@Override
public boolean isEnforceStrictSequence() {
return innerTimeSeries.isEnforceStrictSequence();
}
@Override
public IMutableSeries<Date, Double, IOHLCPoint> createEmptyMutableSeries(String ID) {
return innerTimeSeries.createEmptyMutableSeries(ID);
}
@Override
public String getPersistentID() {
return innerTimeSeries.getPersistentID();
}
}