/* =========================================================== * TradeManager : An application to trade strategies for the Java(tm) platform * =========================================================== * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Project Info: org.trade * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Oracle, Inc. * in the United States and other countries.] * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Original Author: Simon Allen; * Contributor(s): -; * * Changes * ------- * */ package org.trade.strategy.data; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Vector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trade.core.factory.ClassFactory; import org.trade.core.util.TradingCalendar; import org.trade.core.util.Worker; import org.trade.persistent.dao.Strategy; import org.trade.persistent.dao.Tradestrategy; import org.trade.persistent.dao.Tradingday; import org.trade.strategy.data.base.RegularTimePeriod; import org.trade.strategy.data.candle.CandleItem; import org.trade.strategy.data.candle.CandlePeriod; /** */ public class StrategyData extends Worker { private final static Logger _log = LoggerFactory.getLogger(StrategyData.class); private CandleDataset baseCandleDataset = null; private CandleDataset candleDataset = null; private final List<IndicatorDataset> indicators = new ArrayList<IndicatorDataset>(); private boolean seriesChanged = true; private final Object lockStrategyWorker = new Object(); private int currentBaseCandleCount = -1; private int lastBaseCandleProcessed = -1; /** * Constructor for StrategyData. * * @param strategy * Strategy * @param baseCandleDataset * CandleDataset */ public StrategyData(Strategy strategy, CandleDataset baseCandleDataset) { this.baseCandleDataset = baseCandleDataset; candleDataset = new CandleDataset(); candleDataset.addSeries(CandleDataset.createSeries(baseCandleDataset, 0, getBaseCandleSeries().getContract(), getBaseCandleSeries().getBarSize(), getBaseCandleSeries().getStartTime(), getBaseCandleSeries().getEndTime())); for (IndicatorSeries indicator : strategy.getIndicatorSeries()) { try { /* * For each indicator create a series that is a clone for this * trade strategy. */ IndicatorSeries series = (IndicatorSeries) indicator.clone(); series.setKey(series.getName()); series.createSeries(candleDataset, 0); IndicatorDataset indicatorDataset = this.getIndicatorByType(indicator.getType()); if (null == indicatorDataset) { /* * Data-set and Series names should have the same name with * applicable extension of Series/Dataset. this allows * substitution and does not require us to have another * table in the DB to represent the Dataset which is just a * holder for series and is required by the Chart API. */ String datasetName = indicator.getType().replaceAll("Series", "Dataset"); Vector<Object> parm = new Vector<Object>(); indicatorDataset = (IndicatorDataset) ClassFactory .getCreateClass(IndicatorDataset.PACKAGE + datasetName, parm, this); this.indicators.add(indicatorDataset); } indicatorDataset.addSeries(series); } catch (Exception ex) { throw new IllegalArgumentException( "Could not construct StrategyData Object. Either indicator was not found or was not clonable Msg: " + ex.getMessage()); } } } /* * The main process thread. This will run until it is either canceled or is * done. * * (non-Javadoc) * * @see org.trade.strategy.impl.Worker#doInBackground() */ protected Void doInBackground() { /* * We initialize here to keep this instances as part of this worker * thread */ try { this.seriesChanged = false; do { /* * Lock until a candle arrives. First time in we process the * current candle. This thread is processing candles behind the * main broker queue thread. So the lastBaseCandleProcessed will * increase in value until we catch up then the thread will lock * until a new candle arrives. */ synchronized (lockStrategyWorker) { while ((!this.seriesChanged && currentBaseCandleCount == lastBaseCandleProcessed) || this.getBaseCandleSeries().isEmpty()) { lockStrategyWorker.wait(); } this.seriesChanged = false; } if (!this.isCancelled()) { if (!this.getBaseCandleSeries().isEmpty()) { /* * Another candle has been added. Add the new candle to * the base series in the dataset. */ boolean newBar = false; if (this.currentBaseCandleCount > this.lastBaseCandleProcessed) { this.lastBaseCandleProcessed++; newBar = true; } synchronized (this.getBaseCandleDataset()) { this.getCandleDataset().getSeries(0).updateSeries(this.getBaseCandleSeries(), this.lastBaseCandleProcessed, newBar); } } } } while (!this.isDone() && !this.isCancelled()); } catch (InterruptedException interExp) { // Do nothing. } catch (Exception ex1) { _log.error("Error processing candle symbol: " + this.getBaseCandleSeries().getSymbol() + " Base series size: " + this.getBaseCandleSeries().getItemCount() + " BarSize: " + this.getBaseCandleSeries().getBarSize() + " currentBaseCandleCount: " + this.currentBaseCandleCount + " Candle series size: " + this.getCandleDataset().getSeries(0).getItemCount() + " lastBaseCandleProcessed: " + this.lastBaseCandleProcessed + " BarSize: " + this.getCandleDataset().getSeries(0).getBarSize() + " Message: " + ex1.getMessage(), ex1); } finally { /* * Ok we are complete clean up. */ } return null; } public void cancel() { this.setIsCancelled(true); /* * Unlock the doInBackground that may be waiting for a candle. This will * cause a clean finish to the process. */ synchronized (lockStrategyWorker) { seriesChanged = true; lockStrategyWorker.notifyAll(); } } protected void done() { // Free some memory!! // this.clearBaseCandleSeries(); } /** * Method changeCandleSeriesPeriod. * * @param newPeriod * int */ public void changeCandleSeriesPeriod(int newPeriod) { /* * Clear down the dependent data sets and re populate from the base * candle series. */ clearChartDatasets(); this.getCandleDataset().getSeries(0).setBarSize(newPeriod); for (int i = 0; i < getBaseCandleSeries().getItemCount(); i++) { CandleItem candelItem = (CandleItem) getBaseCandleSeries().getDataItem(i); boolean newBar = this.getCandleDataset().getSeries(0).buildCandle(candelItem.getPeriod().getStart(), candelItem.getOpen(), candelItem.getHigh(), candelItem.getLow(), candelItem.getClose(), candelItem.getVolume(), candelItem.getVwap(), candelItem.getCount(), this.getCandleDataset().getSeries(0).getBarSize() / getBaseCandleSeries().getBarSize(), null); updateIndicators(this.getCandleDataset(), newBar); } this.getCandleDataset().getSeries(0).fireSeriesChanged(); } /** * Method buildCandle. * * @param time * Date * @param open * double * @param high * double * @param low * double * @param close * double * @param volume * long * @param vwap * double * @param tradeCount * int * @param rollupInterval * int This is the barSize we are trading on over the barSize of * the incoming data. Note the results should be an integer. i.e * 5min/1min 60min/5min but not 5min/2min. * * @param lastUpdateDate * Date the update time. * @return boolean */ public boolean buildCandle(ZonedDateTime time, double open, double high, double low, double close, long volume, double vwap, int tradeCount, int rollupInterval, ZonedDateTime lastUpdateDate) { boolean newBar = this.getBaseCandleSeries().buildCandle(time, open, high, low, close, volume, vwap, tradeCount, rollupInterval, lastUpdateDate); this.currentBaseCandleCount = this.getBaseCandleSeries().getItemCount() - 1; CandleItem candleItem = (CandleItem) this.getBaseCandleSeries().getDataItem(this.currentBaseCandleCount); this.getBaseCandleSeries().updatePercentChanged(candleItem); updateIndicators(this.getBaseCandleDataset(), newBar); this.getBaseCandleSeries().fireSeriesChanged(); /* * If thread Indicators the updates to all indicators and the subsequent * firing of base series changed is performed via the worker thread. * This should be used when this method is called from a broker thread * i.e. messaged bus thread. */ if (this.isRunning()) { /* * Unlock the doInBackground that may be waiting for a candle. This * will cause a clean finish to the process. */ synchronized (lockStrategyWorker) { this.seriesChanged = true; lockStrategyWorker.notifyAll(); } // _log.info("buildCandle symbol: " // + this.getBaseCandleSeries().getSymbol() + " Count: " // + this.currentCandleCount); } else { /* * Another candle has been added. Add the new candle to the base * series in the dataset. */ synchronized (this.getBaseCandleDataset()) { this.getCandleDataset().updateDataset(this.getBaseCandleDataset(), 0, newBar); } } return newBar; } /** * Method updateIndicators. Update all the indicators before notifying any * strategy workers of this even. * * @param source * CandleDataset * * @param newBar * boolean */ private void updateIndicators(CandleDataset source, boolean newBar) { for (IndicatorDataset indicator : indicators) { /* * CandleSeries are only updated via the API i.e. these are not true * indicators and are shared across Data-sets. */ if (!IndicatorSeries.CandleSeries.equals(indicator.getType(0))) { indicator.updateDataset(source, 0, newBar); } } } /** * Method updateIndicators. Update all the indicators before notifying any * strategy workers of this even. * * @param source * CandleDataset * * @param newBar * boolean */ public void createIndicators(CandleDataset source) { for (IndicatorDataset indicator : indicators) { if (!IndicatorSeries.CandleSeries.equals(indicator.getType(0))) { for (int x = 0; x < indicator.getSeriesCount(); x++) { IndicatorSeries series = indicator.getSeries(x); /* * CandleSeries are only updated via the API i.e. these are * not true indicators and are shared across Data-sets. */ series.createSeries(source, 0); } } } } public synchronized void clearBaseCandleDataset() { if (this.isRunning()) this.cancel(); this.currentBaseCandleCount = -1; this.lastBaseCandleProcessed = this.currentBaseCandleCount; clearChartDatasets(); getBaseCandleDataset().clear(); } public void clearChartDatasets() { for (IndicatorDataset indicator : indicators) { if (!IndicatorSeries.CandleSeries.equals(indicator.getType(0))) { indicator.clear(); } } getCandleDataset().clear(); } /** * Method getIndicators. * * @return List<IndicatorDataset> */ public List<IndicatorDataset> getIndicators() { return indicators; } /** * Method getIndicators. * * @param type * String * @return IndicatorDataset */ public IndicatorDataset getIndicatorByType(String type) { for (int index = 0; index < indicators.size(); index++) { IndicatorDataset series = indicators.get(index); if (series.getType(0).equals(type)) { return series; } } return null; } /** * Method getBaseCandleDataset. * * @return CandleDataset */ public CandleDataset getBaseCandleDataset() { return baseCandleDataset; } /** * Method getBaseCandleSeries. * * @return CandleSeries */ public CandleSeries getBaseCandleSeries() { return baseCandleDataset.getSeries(0); } /** * Method getCandleDataset. * * @return CandleDataset */ public CandleDataset getCandleDataset() { return candleDataset; } /** * Method create. * * @param tradestrategy * Tradestrategy * @return StrategyData */ public static StrategyData create(final Tradestrategy tradestrategy) { CandleDataset candleDataset = new CandleDataset(); CandleSeries candleSeries = new CandleSeries(tradestrategy.getContract().getSymbol(), tradestrategy.getContract(), tradestrategy.getBarSize(), tradestrategy.getTradingday().getOpen(), tradestrategy.getTradingday().getClose()); candleDataset.addSeries(candleSeries); return new StrategyData(tradestrategy.getStrategy(), candleDataset); } /** * Method doDummyData. * * @param series * CandleSeries * @param start * Tradingday * @param noDays * int * @param barSize * int * @param longTrade * boolean * @param milliSecondsDeplay * int */ public static void doDummyData(CandleSeries series, Tradingday start, int noDays, int barSize, boolean longTrade, int milliSecondsDeplay) { double high = 33.98; double low = 33.84; double open = 33.90; double close = 33.95; double vwap = 34.94; int longShort = 1; if (!longTrade) { high = 34.15; low = 34.01; open = 34.10; close = 34.03; vwap = 34.02; longShort = -1; } long volume = 100000; int tradeCount = 100; if (barSize == 1) { barSize = (int) TradingCalendar.getDurationInSeconds(start.getOpen(), start.getClose()); } long count = (TradingCalendar.getDurationInSeconds(start.getOpen(), start.getClose()) / barSize) * noDays; RegularTimePeriod period = new CandlePeriod(start.getOpen(), barSize); series.clear(); for (int i = 0; i < count; i++) { series.buildCandle(period.getStart(), open, high, low, close, volume, vwap, tradeCount, 1, null); high = high + (0.02 * longShort); low = low + (0.02 * longShort); open = open + (0.02 * longShort); close = close + (0.02 * longShort); vwap = vwap + (0.02 * longShort); period = period.next(); if (period.getStart().equals(start.getClose())) { period = new CandlePeriod( TradingCalendar.getTradingDayStart(TradingCalendar.getNextTradingDay(period.getStart())), barSize); } try { if (milliSecondsDeplay > 0) Thread.sleep(milliSecondsDeplay); } catch (InterruptedException e) { _log.error(" Thread interupt: " + e.getMessage()); } } } public void printDatasets() { this.getBaseCandleSeries().printSeries(); for (int i = 0; i < this.getCandleDataset().getSeriesCount(); i++) { IndicatorSeries series = this.getCandleDataset().getSeries(i); series.printSeries(); } for (IndicatorDataset indicatorDataset : this.getIndicators()) { for (int i = 0; i < indicatorDataset.getSeriesCount(); i++) { IndicatorSeries series = indicatorDataset.getSeries(i); series.printSeries(); } } } }