/* =========================================================== * 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.math.BigDecimal; import java.time.ZonedDateTime; import java.util.Collections; import java.util.LinkedList; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Transient; import org.jfree.data.ComparableObjectItem; import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.time.ohlc.OHLCSeriesCollection; import org.trade.core.util.TradingCalendar; import org.trade.core.valuetype.Percent; import org.trade.core.valuetype.ValueTypeException; import org.trade.persistent.dao.Candle; import org.trade.persistent.dao.CodeValue; import org.trade.persistent.dao.Contract; import org.trade.persistent.dao.Strategy; 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; /** * A list of (RegularTimePeriod, open, high, low, close) data items. * * @since 1.0.4 * * @see OHLCSeriesCollection * @author Simon Allen * @version $Revision: 1.0 $ */ @Entity @DiscriminatorValue("CandleSeries") public class CandleSeries extends IndicatorSeries { private static final long serialVersionUID = 20183087035446657L; public static final String SYMBOL = "Symbol"; public static final String CURRENCY = "Currency"; public static final String EXCHANGE = "Exchange"; public static final String SEC_TYPE = "SECType"; private Contract contract; private String symbol; private String currency; private String exchange; private String secType; private ZonedDateTime startTime; private ZonedDateTime endTime; private int barSize = 0; private Candle candleBar = null; private Percent percentChangeFromClose = new Percent(0); private Percent percentChangeFromOpen = new Percent(0); // Parms used for the rolling candle bar. private RollingCandle rollingCandle = new RollingCandle(); private RollingCandle prevRollingCandle = null; private Double sumVwapVolume = new Double(0); private Long sumVolume = new Long(0); private Integer sumTradeCount = new Integer(0); private LinkedList<RollingCandle> rollingCandleValues = new LinkedList<RollingCandle>(); private LinkedList<Double> openValues = new LinkedList<Double>(); private LinkedList<Double> highValues = new LinkedList<Double>(); private LinkedList<Double> lowValues = new LinkedList<Double>(); private LinkedList<Long> volumeValues = new LinkedList<Long>(); private LinkedList<Integer> tradeCountValues = new LinkedList<Integer>(); private LinkedList<Double> vwapVolumeValues = new LinkedList<Double>(); public CandleSeries() { super(IndicatorSeries.CandleSeries, true, 0, false); } /** * Creates a new empty series. By default, items added to the series will be * sorted into ascending order by period, and duplicate periods will not be * allowed. * * @param contract * the Contract for this candle series. * @param barSize * the length in minutes for each bar ie. 5, 15, 30, 60 * */ public CandleSeries(CandleSeries series, int barSize, ZonedDateTime startTime, ZonedDateTime endTime) { super(series.getContract().getSymbol(), IndicatorSeries.CandleSeries, series.getDisplaySeries(), 0, series.getSubChart()); this.symbol = series.getContract().getSymbol(); this.contract = series.getContract(); this.barSize = series.getBarSize(); this.startTime = startTime; this.endTime = endTime; } /** * Creates a new empty series. By default, items added to the series will be * sorted into ascending order by period, and duplicate periods will not be * allowed. * * @param legend * the title that appears on the bottom of the chart. * @param contract * the Contract for this candle series. * @param barSize * the length in minutes for each bar ie. 5, 15, 30, 60 * * */ public CandleSeries(String legend, Contract contract, int barSize, ZonedDateTime startTime, ZonedDateTime endTime) { super(legend, IndicatorSeries.CandleSeries, true, 0, false); this.contract = contract; this.symbol = contract.getSymbol(); this.barSize = barSize; this.startTime = startTime; this.endTime = endTime; } /** * Constructor for CandleSeries. * * @param strategy * Strategy * @param name * String * @param type * String * @param description * String * @param displayOnChart * Boolean * @param chartRGBColor * Integer * @param subChart * Boolean */ public CandleSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); } /** * Method createSeries. * * @param source * CandleDataset * @param seriesIndex * int */ public void createSeries(CandleDataset source, int seriesIndex) { } /** * Returns the contract ID. * * * @return contractId. */ @Transient public Contract getContract() { if (null == this.contract) { this.contract = new Contract(this.getSecType(), this.getSymbol(), this.getExchange(), this.getCurrency(), null, null); } return this.contract; } /** * Method getStartTime. * * @return ZonedDateTime */ @Transient public ZonedDateTime getStartTime() { return this.startTime; } /** * Method setStartTime. * * @param startTime * ZonedDateTime */ public void setStartTime(ZonedDateTime startTime) { this.startTime = startTime; } /** * Method getEndTime. * * @return ZonedDateTime */ @Transient public ZonedDateTime getEndTime() { return this.endTime; } /** * Method setEndTime. * * @param endTime * ZonedDateTime */ public void setEndTime(ZonedDateTime endTime) { this.endTime = endTime; } /** * Method getSymbol. * * @return String */ @Transient public String getSymbol() { try { if (null == this.symbol) this.symbol = (String) CodeValue.getValueCode(SYMBOL, this.getCodeValues()); } catch (Exception e) { this.symbol = null; } return this.symbol; } /** * Method setSymbol. * * @param symbol * String */ public void setSymbol(String symbol) { this.symbol = symbol; } /** * Method getCurrency. * * @return String */ @Transient public String getCurrency() { try { if (null == this.currency) this.currency = (String) CodeValue.getValueCode(CURRENCY, this.getCodeValues()); } catch (Exception e) { this.currency = null; } return this.currency; } /** * Method setCurrency. * * @param currency * String */ public void setCurrency(String currency) { this.currency = currency; } /** * Method getExchange. * * @return String */ @Transient public String getExchange() { try { if (null == this.exchange) this.exchange = (String) CodeValue.getValueCode(EXCHANGE, this.getCodeValues()); } catch (Exception e) { this.exchange = null; } return this.exchange; } /** * Method setExchange. * * @param exchange * String */ public void setExchange(String exchange) { this.exchange = exchange; } /** * Method getSecType. * * @return String */ @Transient public String getSecType() { try { if (null == this.secType) this.secType = (String) CodeValue.getValueCode(SEC_TYPE, this.getCodeValues()); } catch (Exception e) { this.secType = null; } return this.secType; } /** * Method setSecType. * * @param secType * String */ public void setSecType(String secType) { this.secType = secType; } /** * Returns the time period for the specified item. * * @param index * the item index. * * @return The time period. */ public RegularTimePeriod getPeriod(int index) { final CandleItem item = (CandleItem) getDataItem(index); return item.getPeriod(); } /** * Returns the time period for the specified item. * * @return The time period. */ @Transient public int getBarSize() { return this.barSize; } /** * Returns the time period for the specified item. * * @param barSize * Integer */ public void setBarSize(Integer barSize) { this.barSize = barSize; } /** * Returns the data item at the specified index. * * @param index * the item index. * @return The data item. */ public ComparableObjectItem getDataItem(int index) { return super.getDataItem(index); } /** * Adds a data item to the series. * * @param period * the period. * @param open * the open-value. * @param high * the high-value. * @param low * the low-value. * @param close * the close-value. * @param volume * the volume-value. * @param vwap * the vwap-value. * @param tradeCount * the tradeCount-value. * @param contract * Contract * @param lastUpdateDate * Date */ public void add(Contract contract, Tradingday tradingday, RegularTimePeriod period, double open, double high, double low, double close, long volume, double vwap, int tradeCount, ZonedDateTime lastUpdateDate) { if (!this.isEmpty()) { CandleItem item0 = (CandleItem) this.getDataItem(0); if (!period.getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(new CandleItem(contract, tradingday, period, open, high, low, close, volume, vwap, tradeCount, lastUpdateDate), true); } /** * Adds a data item to the series. * * @param candleItem * CandleItem * @param notify * boolean */ public void add(CandleItem candleItem, boolean notify) { if (!this.isEmpty()) { CandleItem item0 = (CandleItem) this.getDataItem(0); if (!candleItem.getPeriod().getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(candleItem, notify); } /** * Returns the true/false if the date falls within a period. * * @param date * the date for which we want a period. * @return exists */ public int indexOf(ZonedDateTime date) { for (int i = this.data.size(); i > 0; i--) { CandleItem item = (CandleItem) this.data.get(i - 1); if (date.isAfter(item.getPeriod().getEnd())) { return -1; } if ((date.isAfter(item.getPeriod().getStart()) || date.equals(item.getPeriod().getStart())) && (date.isBefore(item.getPeriod().getEnd()) || date.equals(item.getPeriod().getEnd()))) { return i - 1; } } return -1; } /** * Returns the last completed candle or -1 if still building. * * @param time * the date for which we want a candle period to be updated. * * @param open * the open-value. * @param high * the high-value. * @param low * the low-value. * @param close * the close-value. * @param volume * the volume value. * @param vwap * the volume weighted price. * @param tradeCount * the number of trades. * * @param rollupInterval * the interval to roll up Vwap * @param lastUpdateDate * Date the update time. * @return completedCandle the last completed candle or -1 if still building */ boolean buildCandle(ZonedDateTime time, double open, double high, double low, double close, long volume, double vwap, int tradeCount, int rollupInterval, ZonedDateTime lastUpdateDate) { int index = this.indexOf(time); // _log.error("Symbol :" + this.getSymbol() + " Bar Time: " + time // + " Index: " + index + " open: " + open + " high: " + high // + " low: " + low + " close: " + close + " volume: " + volume // + " vwap: " + vwap + " tradeCount: " + tradeCount // + " rollupInterval: " + rollupInterval); CandleItem candleItem = null; boolean newCandle = false; if (index > -1) { candleItem = (CandleItem) this.getDataItem(index); if (null == lastUpdateDate) lastUpdateDate = candleItem.getPeriod().getEnd(); this.rollCandle(candleItem.getPeriod(), rollupInterval, open, high, low, close, volume, tradeCount, vwap, lastUpdateDate); if (candleItem.getHigh() < high) { candleItem.setHigh(high); } if (candleItem.getLow() > low) { candleItem.setLow(low); } candleItem.setClose(close); if (rollupInterval > 1) { candleItem.setVolume(candleItem.getVolume() + volume); candleItem.setCount(candleItem.getCount() + tradeCount); } else { candleItem.setVolume(volume); candleItem.setCount(tradeCount); } candleItem.setVwap(this.rollingCandle.getVwap()); candleItem.setLastUpdateDate(lastUpdateDate); } else { RegularTimePeriod period = this.getPeriodStart(time, this.getBarSize()); Tradingday tradingday = new Tradingday( TradingCalendar.getDateAtTime(period.getStart(), this.getStartTime()), TradingCalendar.getDateAtTime(period.getStart(), this.getEndTime())); if (null == lastUpdateDate) lastUpdateDate = period.getEnd(); this.rollCandle(period, rollupInterval, open, high, low, close, volume, tradeCount, vwap, lastUpdateDate); candleItem = new CandleItem(this.getContract(), tradingday, period, open, high, low, close, volume, this.rollingCandle.getVwap(), tradeCount, lastUpdateDate); this.add(candleItem, false); newCandle = true; } // printCandleItem(candleItem); return newCandle; } /** * Removes all data items from the series and, unless the series is already * empty, sends a {@link SeriesChangeEvent} to all registered listeners. * Clears down and resets all the Vwap calculated fields. */ public void clear() { this.openValues.clear(); this.highValues.clear(); this.lowValues.clear(); this.volumeValues.clear(); this.tradeCountValues.clear(); this.vwapVolumeValues.clear(); this.rollingCandleValues.clear(); super.clear(); } /** * Method clone. * * @return Object * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { CandleSeries clone = (CandleSeries) super.clone(); clone.contract = (Contract) this.getContract().clone(); clone.symbol = this.getSymbol(); clone.currency = this.getCurrency(); clone.exchange = this.getExchange(); clone.secType = this.getSecType(); clone.startTime = this.getStartTime(); clone.endTime = this.getEndTime(); clone.barSize = this.getBarSize(); clone.rollingCandle = new RollingCandle(); return clone; } /** * Method getPeriodStart. * * @param time * Date * @param barSize * int * @return RegularTimePeriod */ @Transient public RegularTimePeriod getPeriodStart(ZonedDateTime time, int barSize) { /* * For 60min time period start the clock at 9:00am. This matches most * charting platforms. */ ZonedDateTime startBusDate = TradingCalendar.getDateAtTime(time, this.getStartTime()); if (3600 == barSize) { if (startBusDate.getMinute() == 30) { startBusDate = startBusDate.minusMinutes(30); if (time.getMinute() == 30 && startBusDate.equals(time)) { time = time.minusMinutes(30); } } } long periodsFromDayStart = TradingCalendar.getDurationInSeconds(startBusDate, time) / barSize; ZonedDateTime startDateTime = startBusDate.plusSeconds((periodsFromDayStart * barSize)); return new CandlePeriod(startDateTime, barSize); } /** * Method getRollingCandle. * * @return RollingCandle */ @Transient public RollingCandle getRollingCandle() { return this.rollingCandleValues.getFirst(); } /** * Method getRollingCandle. * * @param index * int * @return RollingCandle */ @Transient public RollingCandle getRollingCandle(int index) { return this.rollingCandleValues.get(index); } /** * Method getRollingCandleSize. * * @return int */ @Transient public int getRollingCandleSize() { return this.rollingCandleValues.size(); } /** * Method getPreviousRollingCandle. * * @return RollupCandle */ @Transient public RollingCandle getPreviousRollingCandle() { return this.prevRollingCandle; } /** * Method updateSeries. * * @param source * CandleSeries * @param skip * int * @param newBar * boolean */ public void updateSeries(CandleSeries source, int skip, boolean newBar) { if (source == null) { throw new IllegalArgumentException("Null source (CandleSeries)."); } /* * Do not want to add the new bar. */ if (source.getItemCount() > skip) { // get the current data item... CandleItem candleItem = (CandleItem) source.getDataItem(skip); /* * If the item does not exist in the series then this is a new time * period and so we need to remove the last in the set and add the * new periods values. Otherwise we just update the last value in * the set. */ if (newBar) { this.add(candleItem, true); } else { CandleItem dataItem = (CandleItem) this.getDataItem(this.getItemCount() - 1); this.update(dataItem.getPeriod(), dataItem.getCandle()); } } } /** * Method getAverageBar. * * @param startDate * Date * @param endDate * Date * @param wieghted * boolean * @return Candle */ public Candle getAverageBar(ZonedDateTime startDate, ZonedDateTime endDate, boolean wieghted) { int itemCount = this.getItemCount() - 1; long sumVolume = 0; int sunTradeCount = 0; double sunHighPriceXVolume = 0; double sunLowPriceXVolume = 0; double sunOpenPriceXVolume = 0; double sunClosePriceXVolume = 0; double sunClosePriceXVolumeVwap = 0; double numberOfCandles = 0; CandleItem candle = null; for (int i = itemCount; i > -1; i--) { candle = (CandleItem) this.getDataItem(i); if ((candle.getPeriod().getStart().equals(startDate) || candle.getPeriod().getStart().isAfter(startDate)) && (candle.getPeriod().getStart().equals(endDate) || candle.getPeriod().getStart().isBefore(endDate))) { if (candle.getVolume() > 0) numberOfCandles++; sunHighPriceXVolume = sunHighPriceXVolume + ((wieghted ? candle.getVolume() : 1) * candle.getHigh()); sunLowPriceXVolume = sunLowPriceXVolume + ((wieghted ? candle.getVolume() : 1) * candle.getLow()); sunOpenPriceXVolume = sunOpenPriceXVolume + ((wieghted ? candle.getVolume() : 1) * candle.getOpen()); sunClosePriceXVolume = sunClosePriceXVolume + ((wieghted ? candle.getVolume() : 1) * candle.getClose()); sunClosePriceXVolumeVwap = sunClosePriceXVolumeVwap + (candle.getVolume() * candle.getClose()); sumVolume = sumVolume + candle.getVolume(); sunTradeCount = sunTradeCount + candle.getCount(); } } if (numberOfCandles > 0 && sumVolume > 0) { CandlePeriod period = new CandlePeriod(startDate, endDate); Candle avgCandle = new Candle(getContract(), period, 0, 0, 0, Double.MAX_VALUE, TradingCalendar.getDateTimeNowMarketTimeZone()); avgCandle.setHigh(new BigDecimal((sunHighPriceXVolume / (wieghted ? sumVolume : numberOfCandles)))); avgCandle.setLow(new BigDecimal((sunLowPriceXVolume / (wieghted ? sumVolume : numberOfCandles)))); avgCandle.setOpen(new BigDecimal((sunOpenPriceXVolume / (wieghted ? sumVolume : numberOfCandles)))); avgCandle.setClose(new BigDecimal((sunClosePriceXVolume / (wieghted ? sumVolume : numberOfCandles)))); avgCandle.setVwap(new BigDecimal(sunClosePriceXVolumeVwap / sumVolume)); avgCandle.setVolume(sumVolume); avgCandle.setTradeCount(sunTradeCount); return avgCandle; } return null; } /** * Method getBar. * * @param startDate * Date * @param endDate * Date * @return Candle */ public Candle getBar(ZonedDateTime startDate, ZonedDateTime endDate) { if (null != this.candleBar) { if (this.candleBar.getStartPeriod().equals(startDate) && this.candleBar.getEndPeriod().equals(endDate)) { return this.candleBar; } else { this.candleBar = null; } } int itemCount = this.getItemCount() - 1; long sumVolume = 0; int sumTradeCount = 0; double sunClosePriceXVolumeVwap = 0; CandleItem candle = null; for (int i = itemCount; i > -1; i--) { candle = (CandleItem) this.getDataItem(i); if ((candle.getPeriod().getStart().equals(startDate) || candle.getPeriod().getStart().isAfter(startDate)) && (candle.getPeriod().getStart().isBefore(endDate))) { if (null == this.candleBar) { this.candleBar = new Candle(getContract(), candle.getPeriod(), 0, 0, Double.MAX_VALUE, 0, TradingCalendar.getDateTimeNowMarketTimeZone()); this.candleBar.setEndPeriod(candle.getPeriod().getEnd()); } if (this.candleBar.getClose().doubleValue() == 0) this.candleBar.setClose(new BigDecimal(candle.getClose())); if (this.candleBar.getHigh().doubleValue() < candle.getHigh()) this.candleBar.setHigh(new BigDecimal(candle.getHigh())); if (this.candleBar.getLow().doubleValue() > candle.getLow()) this.candleBar.setLow(new BigDecimal(candle.getLow())); sunClosePriceXVolumeVwap = sunClosePriceXVolumeVwap + (candle.getVolume() * candle.getClose()); sumVolume = sumVolume + candle.getVolume(); sumTradeCount = sumTradeCount + candle.getCount(); } } if (null != candle) { this.candleBar.setStartPeriod(candle.getPeriod().getStart()); this.candleBar.setOpen(new BigDecimal(candle.getOpen())); this.candleBar.setTradeCount(sumTradeCount); if (sumVolume > 0) { this.candleBar.setVwap(new BigDecimal(sunClosePriceXVolumeVwap / sumVolume)); this.candleBar.setVolume(sumVolume); } else { this.candleBar.setVwap(new BigDecimal(sunClosePriceXVolumeVwap)); this.candleBar.setVolume(0L); } } return this.candleBar; } /** * Method getPercentChangeFromClose. * * @return Percent */ @Transient public Percent getPercentChangeFromClose() { return percentChangeFromClose; } /** * Method getPercentChangeFromOpen. * * @return Percent */ @Transient public Percent getPercentChangeFromOpen() { return percentChangeFromOpen; } /** * Method updatePercentChanged. * * @param candleItem * CandleItem */ public void updatePercentChanged(CandleItem candleItem) { ZonedDateTime prevDay = TradingCalendar.getPrevTradingDay(candleItem.getPeriod().getStart()); ZonedDateTime prevDayEnd = TradingCalendar.getDateAtTime(prevDay, this.getEndTime()); prevDayEnd = prevDayEnd.minusSeconds(1); ZonedDateTime prevDayStart = TradingCalendar.getDateAtTime(prevDay, this.getStartTime()); ZonedDateTime todayOpen = TradingCalendar.getDateAtTime(candleItem.getPeriod().getStart(), this.getStartTime()); int index = this.indexOf(todayOpen); if (index > -1) { CandleItem openCandleItem = (CandleItem) this.getDataItem(index); try { percentChangeFromOpen.setValue( new Percent((candleItem.getClose() - openCandleItem.getOpen()) / openCandleItem.getOpen())); } catch (ValueTypeException ex) { _log.error("Could not set ValueType Msg: " + ex.getMessage(), ex); } if (candleItem.getPeriod().getStart().isAfter(prevDayEnd)) { if (this.indexOf(prevDayStart) > -1 && this.indexOf(prevDayEnd) > -1) { Candle prevDayCandle = this.getBar(prevDayStart, prevDayEnd); // _log.info("prevDayCandle Start:" // + prevDayCandle.getStartPeriod() + " End period: " // + prevDayCandle.getEndPeriod() + " Open:" // + prevDayCandle.getOpen() + " High: " // + prevDayCandle.getHigh() + " Low:" // + prevDayCandle.getLow() + " Close: " // + prevDayCandle.getClose()); try { percentChangeFromClose .setValue(new Percent((candleItem.getClose() - prevDayCandle.getClose().doubleValue()) / prevDayCandle.getClose().doubleValue())); } catch (ValueTypeException ex) { _log.error("Could not set ValueType Msg: " + ex.getMessage(), ex); } } } } } /** * Method printSeries. * */ public void printSeries() { for (int i = 0; i < this.getItemCount(); i++) { CandleItem dataItem = (CandleItem) this.getDataItem(i); _log.debug("Type: " + this.getType() + " Time: " + dataItem.getPeriod().getStart() + " Open: " + dataItem.getOpen() + " Close: " + dataItem.getClose() + " High: " + dataItem.getHigh() + " Low: " + dataItem.getLow() + " Volume: " + dataItem.getVolume()); } } /** * Method printCandleItem. * * @param candleItem * CandleItem */ public void printCandleItem(CandleItem dataItem) { _log.debug("Symbol: " + this.getSymbol() + " Start Time: " + dataItem.getPeriod().getStart() + " Open: " + dataItem.getOpen() + " High: " + dataItem.getHigh() + " Low: " + dataItem.getLow() + " Close: " + dataItem.getClose() + " Vwap: " + dataItem.getVwap() + " Volume: " + dataItem.getVolume() + " Count: " + dataItem.getCount() + " LastUpdateDate: " + dataItem.getLastUpdateDate()); } /** * Method rollCandle. Creates a rolling candle that is the sum of the under * lying candle. So 5 sec bars rolled up to 5min bars will rollup interval * of 5min/5sec = 60. * * @param period * the candle period. * @param rollupInterval * the rollup Interval. * @param open * the open-value. * @param high * the high-value. * @param low * the low-value. * @param close * the close-value. * @param volume * the volume value. * @param vwap * the volume weighted price. * @param tradeCount * the number of trades. * @param lastUpdateDate * the lastUpdateDate */ private void rollCandle(RegularTimePeriod period, int rollupInterval, double open, double high, double low, double close, long volume, int tradeCount, double vwap, ZonedDateTime lastUpdateDate) { if (rollupInterval != this.rollingCandle.rollupInterval || this.isEmpty()) { /* * Going to a lower period i.e say we were 5 min bars now going to * 5sec bars within the current 5min bar. */ if (!this.isEmpty()) { /* * Build current bar */ CandleItem candleItem = (CandleItem) this.getDataItem(this.getItemCount() - 1); if (candleItem.getPeriod().equals(period)) { this.rollingCandle = new RollingCandle(period, rollupInterval, candleItem.getOpen(), candleItem.getHigh(), candleItem.getLow(), candleItem.getClose(), candleItem.getVolume(), candleItem.getCount(), candleItem.getVwap(), lastUpdateDate); this.sumVwapVolume = new Double(candleItem.getVwap() * candleItem.getVolume()); this.sumVolume = candleItem.getVolume(); this.sumTradeCount = candleItem.getCount(); } else { this.sumVwapVolume = new Double(0); this.sumVolume = new Long(0); this.sumTradeCount = new Integer(0); this.rollingCandle.rollupInterval = rollupInterval; } if (this.getItemCount() > 1) { CandleItem prevCandleItem = (CandleItem) this.getDataItem(this.getItemCount() - 2); this.prevRollingCandle = new RollingCandle(prevCandleItem.getPeriod(), this.rollingCandle.rollupInterval, prevCandleItem.getOpen(), prevCandleItem.getHigh(), prevCandleItem.getLow(), prevCandleItem.getClose(), prevCandleItem.getVolume(), prevCandleItem.getCount(), prevCandleItem.getVwap(), prevCandleItem.getLastUpdateDate()); } } else { this.rollingCandle.rollupInterval = rollupInterval; this.rollingCandle.open = open; this.sumVwapVolume = new Double(0); this.sumVolume = new Long(0); this.sumTradeCount = new Integer(0); } this.openValues.clear(); this.highValues.clear(); this.lowValues.clear(); this.volumeValues.clear(); this.tradeCountValues.clear(); this.vwapVolumeValues.clear(); this.rollingCandleValues.clear(); } updateRollingCandle(period, rollupInterval, open, high, low, close, volume, tradeCount, vwap, lastUpdateDate); } /** * Method updateRollupCandle. Creates a rolling candle that is the sum of * the under lying candle. So 5 sec bars rolled up to 5min bars will rollup * interval of 5min/5sec = 60. * * @param period * the candle period. * @param rollupInterval * the rollup Interval. * @param open * the open-value. * @param high * the high-value. * @param low * the low-value. * @param close * the close-value. * @param volume * the volume value. * @param vwap * the volume weighted price. * @param tradeCount * the number of trades. * @param lastUpdateDate * the lastUpdateDate */ private void updateRollingCandle(RegularTimePeriod period, int rollupInterval, double open, double high, double low, double close, long volume, int tradeCount, double vwap, ZonedDateTime lastUpdateDate) { if (rollupInterval == this.rollingCandleValues.size()) { this.prevRollingCandle = this.rollingCandleValues.removeLast(); this.rollingCandle.open = this.openValues.removeLast(); if (this.openValues.isEmpty()) this.rollingCandle.open = open; if (this.rollingCandle.high == this.highValues.removeLast()) { if (this.highValues.isEmpty()) { this.rollingCandle.high = high; } else { this.rollingCandle.high = Collections.max(this.highValues); } } if (this.rollingCandle.low == this.lowValues.removeLast()) { if (this.lowValues.isEmpty()) { this.rollingCandle.low = low; } else { this.rollingCandle.low = Collections.min(this.lowValues); } } sumVolume = sumVolume - this.volumeValues.removeLast(); sumVwapVolume = sumVwapVolume - this.vwapVolumeValues.removeLast(); sumTradeCount = sumTradeCount - this.tradeCountValues.removeLast(); } this.rollingCandle.period = period; this.rollingCandle.lastUpdateDate = lastUpdateDate; this.openValues.addFirst(open); this.highValues.addFirst(high); if (high > this.rollingCandle.high) this.rollingCandle.high = high; this.lowValues.addFirst(low); if (low < this.rollingCandle.low) this.rollingCandle.low = low; this.rollingCandle.close = close; this.tradeCountValues.addFirst(tradeCount); sumTradeCount = sumTradeCount + tradeCount; this.rollingCandle.tradeCount = sumTradeCount; this.volumeValues.addFirst(volume); sumVolume = sumVolume + volume; this.rollingCandle.volume = sumVolume; this.vwapVolumeValues.addFirst(vwap * volume); sumVwapVolume = sumVwapVolume + this.vwapVolumeValues.getFirst(); if (sumVolume > 0) { this.rollingCandle.vwap = sumVwapVolume / sumVolume; } else { this.rollingCandle.vwap = this.rollingCandle.close; } // _log.info("**Date: " + period.getStart() + " sumVwapVolume: " // + sumVwapVolume + " sumVolume: " + sumVolume + " volume: " // + volume + " vwap: " + this.rollingCandle.vwap); try { this.rollingCandleValues.addFirst((RollingCandle) this.rollingCandle.clone()); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block _log.error("Error updateRollingCandle cannot clone candle Msg: " + e.getMessage()); } } public class RollingCandle implements Cloneable { private int rollupInterval = 0; private RegularTimePeriod period = null; private double open = 0; private double high = 0; private double low = Double.MAX_VALUE; private double close = 0; private long volume = 0; private int tradeCount = 0; private double vwap = 0; private ZonedDateTime lastUpdateDate = null; public RollingCandle() { } public RollingCandle(RegularTimePeriod period, int rollupInterval, double open, double high, double low, double close, long volume, int tradeCount, double vwap, ZonedDateTime lastUpdateDate) { this.rollupInterval = rollupInterval; this.period = period; this.open = open; this.high = high; this.low = low; this.close = close; this.volume = volume; this.tradeCount = tradeCount; this.vwap = vwap; this.lastUpdateDate = lastUpdateDate; } /** * Method getPeriod. * * @return CandlePeriod */ public RegularTimePeriod getPeriod() { return this.period; } /** * Method getRollupInterval. * * @return int */ public int getRollupInterval() { return this.rollupInterval; } /** * Method getOpen. * * @return double */ public double getOpen() { return this.open; } /** * Method getHigh. * * @return double */ public double getHigh() { return this.high; } /** * Method getLow. * * @return double */ public double getLow() { return this.low; } /** * Method getClose. * * @return double */ public double getClose() { return this.close; } /** * Method getVwap. * * @return double */ public double getVwap() { return this.vwap; } /** * Method getVolume. * * @return long */ public long getVolume() { return this.volume; } /** * Method getTradeCount. * * @return int */ public int getTradeCount() { return this.tradeCount; } /** * Method getLastUpdateDate. * * @return ZonedDateTime */ public ZonedDateTime getLastUpdateDate() { return this.lastUpdateDate; } public boolean getSide() { return this.close >= this.open; } /** * Method clone. * * @return Object * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { return (RollingCandle) super.clone(); } } }