package jtrade.marketfeed;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jtrade.JTradeException;
import jtrade.Symbol;
import jtrade.io.MarketDataIO;
import jtrade.io.TickReader;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TickFileMarketFeed extends FileMarketFeed {
private static final Logger logger = LoggerFactory.getLogger(TickFileMarketFeed.class);
protected SynchronizedTickDataReader tickDataReader;
protected Class<? extends TickDataFileReader> tickDataFileReaderClass;
protected List<TickListener> tickListeners;
public TickFileMarketFeed() {
this(null, null, null, (Symbol[]) null);
}
public TickFileMarketFeed(File dataDir) {
this(dataDir, null, null, (Symbol[]) null);
}
public TickFileMarketFeed(String dataDir) {
this(new File(dataDir), null, null, (Symbol[]) null);
}
public TickFileMarketFeed(File dataDir, DateTime fromDate, DateTime toDate) {
this(dataDir, fromDate, toDate, (Symbol[]) null);
}
public TickFileMarketFeed(TickFileMarketFeed marketFeed) {
this(marketFeed.dataDir, marketFeed.fromDate, marketFeed.toDate, marketFeed.symbols);
}
public TickFileMarketFeed(String dataDir, String fromDate, String toDate, String... symbols) {
this(new File(dataDir), DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(fromDate), DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(toDate),
parseSymbols(symbols));
}
public TickFileMarketFeed(String dataDir, String fromDate, String toDate, List<Symbol> symbols) {
this(new File(dataDir), DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(fromDate), DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(toDate), symbols
.toArray(new Symbol[symbols.size()]));
}
public TickFileMarketFeed(File dataDir, DateTime fromDate, DateTime toDate, Symbol... symbols) {
super(dataDir);
this.tickDataFileReaderClass = DefaultTickDataFileReader.class;
reset(fromDate, toDate, symbols);
}
@Override
public void reset(DateTime fromDate, DateTime toDate, Symbol... symbols) {
if (isConnected()) {
disconnect();
}
this.fromDate = fromDate;
this.toDate = toDate;
this.symbols = symbols;
filesBySymbol = findSymbolFiles(symbols, fromDate, toDate, 0, true);
tickDataReader = new SynchronizedTickDataReader();
tickListeners = new ArrayList<TickListener>();
if (fromDate != null && toDate != null) {
logger.info(String.format("Initialized market feed, from %s to %s using data folder \"%s\": %s", fromDate.toString("yyyy-MM-dd"),
toDate.toString("yyyy-MM-dd"), dataDir, filesBySymbol.toString()));
} else {
logger.info(String.format("Initialized market feed using data folder \"%s\": %s", dataDir, filesBySymbol.toString()));
}
}
@Override
public void doConnect() {
new Thread(tickDataReader).start();
}
@Override
public void doDisconnect() {
tickDataReader.stop();
try {
while (!tickDataReader.isDone()) {
Thread.sleep(50);
}
} catch (InterruptedException e) {
}
}
@Override
public void removeAllListeners() {
super.removeAllListeners();
for (TickDataFileReader r : tickDataReader.readers) {
r.listeners.clear();
}
tickListeners.clear();
}
@Override
public Tick getLastTick(Symbol symbol) {
TickDataFileReader r = tickDataReader.readersBySymbol.get(symbol);
return r != null ? r.prevTick : null;
}
@Override
public void addTickListener(TickListener listener) {
tickListeners.add(listener);
}
@Override
public void removeTickListener(TickListener listener) {
tickListeners.remove(listener);
for (TickDataFileReader reader : tickDataReader.readersBySymbol.values()) {
removeTickListener(reader.symbol, listener);
}
}
@Override
public void addTickListener(Symbol symbol, TickListener listener) {
addTickListener(symbol, listener, false, null);
}
@Override
public void addTickListener(Symbol symbol, TickListener listener, boolean marketDepth, Cleaner cleaner) {
TickDataFileReader reader = tickDataReader.readersBySymbol.get(symbol);
if (reader == null) {
throw new IllegalArgumentException(String.format("Symbol has no matching data file: %s", symbol));
}
if (reader.cleaner == null && cleaner != null) {
reader.cleaner = cleaner;
}
if (reader.skipMarketDepth && marketDepth) {
reader.skipMarketDepth = false;
}
if (!reader.listeners.contains(listener)) {
reader.listeners.add(listener);
}
}
@Override
public void removeTickListener(Symbol symbol, TickListener listener) {
TickDataFileReader reader = tickDataReader.readersBySymbol.get(symbol);
if (reader != null) {
reader.listeners.remove(listener);
if (reader.listeners.isEmpty() && reader.cleaner != null) {
reader.cleaner.reset();
}
}
}
private void fireTickEvent(TickDataFileReader reader) {
boolean valid = isValidTick(reader, reader.currTick);
if (!valid && isValidTick(reader, reader.prevTick)) {
reader.currTick.ask = reader.prevTick.ask;
reader.currTick.bid = reader.prevTick.bid;
reader.currTick.askSize = reader.prevTick.askSize;
reader.currTick.bidSize = reader.prevTick.bidSize;
reader.currTick.price = reader.prevTick.price;
reader.currTick.lastSize = 0;
reader.currTick.volume = reader.prevTick.volume;
valid = true;
}
if (valid) {
int len = reader.listeners.size();
for (int i = 0; i < len; i++) {
try {
reader.listeners.get(i).onTick(reader.currTick);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
len = tickListeners.size();
for (int i = 0; i < len; i++) {
try {
tickListeners.get(i).onTick(reader.currTick);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
}
private boolean isValidTick(TickDataFileReader reader, Tick tick) {
if (tick == null) {
return false;
}
if (tick.ask <= 0 || tick.bid <= 0 || tick.price <= 0 || tick.ask < tick.bid) {
return false;
}
if (reader.cleaner != null && Double.isNaN(reader.cleaner.update(tick.dateTime, tick.price))) {
return false;
}
return true;
}
final class SynchronizedTickDataReader implements Runnable {
Map<Symbol, TickDataFileReader> readersBySymbol;
LinkedList<TickDataFileReader> readers;
boolean running;
boolean done;
SynchronizedTickDataReader() {
this.readersBySymbol = new LinkedHashMap<Symbol, TickDataFileReader>();
this.readers = new LinkedList<TickDataFileReader>();
for (Entry<Symbol, List<File>> entry : filesBySymbol.entrySet()) {
try {
TickDataFileReader context = (TickDataFileReader) TickFileMarketFeed.this.tickDataFileReaderClass.getConstructors()[0].newInstance(
TickFileMarketFeed.this, entry.getKey(), entry.getValue());
readersBySymbol.put(entry.getKey(), context);
} catch (Exception e) {
throw new JTradeException(e);
}
}
}
public void stop() {
running = false;
for (TickDataFileReader reader : readersBySymbol.values()) {
reader.reset();
}
}
public boolean isDone() {
return done;
}
@Override
public void run() {
long start = System.currentTimeMillis();
running = true;
try {
Tick t = null;
DateTime nextDay = null;
DateTime nextHour = null;
DateTime nextMinute = null;
for (TickDataFileReader reader : readersBySymbol.values()) {
reader.openNextFile();
while ((t = reader.nextTick()) != null && !fromDate.isBefore(t.getDateTime())) {
}
if (t == null) {
reader.reader.close();
continue;
}
nextMinute = (nextMinute == null || t.getDateTime().isBefore(nextMinute) ? t.getDateTime() : nextMinute);
readers.add(reader);
}
if (readers.isEmpty()) {
return;
}
while (running && !readers.isEmpty()) {
Collections.sort(readers);
TickDataFileReader reader = readers.removeFirst();
t = reader.currTick;
nextDay = nextMinute.dayOfMonth().roundCeilingCopy();
nextHour = nextMinute.hourOfDay().roundCeilingCopy();
nextMinute = nextMinute.minuteOfHour().roundCeilingCopy();
while (nextMinute.isBefore(t.dateTime) || nextMinute.isEqual(t.dateTime)) {
fireMinuteEvent(nextMinute);
if (nextHour.isBefore(nextMinute) || nextHour.isEqual(nextMinute)) {
fireHourEvent(nextHour);
if (nextDay.isBefore(nextHour) || nextDay.isEqual(nextHour)) {
fireDayEvent(nextDay);
nextDay = nextDay.plusDays(1);
}
nextHour = nextHour.plusHours(1);
}
nextMinute = nextMinute.plusMinutes(1);
}
fireTickEvent(reader);
t = reader.nextTick();
if (t == null || t.getDateTime().isAfter(toDate)) {
reader.reader.close();
continue;
}
readers.add(reader);
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
} finally {
for (TickDataFileReader reader : readers) {
try {
reader.reader.close();
} catch (Exception e) {
}
}
readers.clear();
done = true;
if (logger.isDebugEnabled()) {
logger.debug("run() time: {}", (System.currentTimeMillis() - start));
}
TickFileMarketFeed.this.disconnect();
}
}
}
public abstract class TickDataFileReader implements Comparable<TickDataFileReader> {
Symbol symbol;
List<File> files;
int next_file;
TickReader reader;
Tick currTick;
Tick prevTick;
List<TickListener> listeners;
Cleaner cleaner;
boolean skipMarketDepth;
public TickDataFileReader(Symbol symbol, List<File> files) throws IOException {
this.symbol = symbol;
this.files = files;
this.listeners = new ArrayList<TickListener>();
}
public void reset() {
next_file = 0;
currTick = null;
prevTick = null;
if (cleaner != null) {
cleaner.reset();
}
}
public abstract void openNextFile() throws IOException;
public boolean hasNextFile() {
return next_file < files.size();
}
public abstract Tick nextTick() throws IOException;
@Override
public int compareTo(TickDataFileReader o) {
return currTick.dateTime.compareTo(o.currTick.dateTime);
}
@Override
protected void finalize() throws Throwable {
if (reader != null) {
reader.close();
}
}
}
public class DefaultTickDataFileReader extends TickDataFileReader {
public DefaultTickDataFileReader(Symbol symbol, List<File> file) throws IOException {
super(symbol, file);
}
@Override
public void openNextFile() throws IOException {
if (reader != null) {
reader.close();
}
reader = MarketDataIO.createTickReader(files.get(next_file++), skipMarketDepth);
}
@Override
public Tick nextTick() throws IOException {
while (true) {
Tick tick = reader.readTick();
if (tick == null) {
if (hasNextFile()) {
openNextFile();
continue;
}
return null;
}
prevTick = currTick;
currTick = tick;
return currTick;
}
}
}
}