/* =========================================================== * 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.util.LinkedList; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Transient; import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.time.ohlc.OHLCSeriesCollection; import org.trade.dictionary.valuetype.CalculationType; import org.trade.persistent.dao.CodeValue; import org.trade.persistent.dao.Strategy; import org.trade.strategy.data.base.RegularTimePeriod; import org.trade.strategy.data.candle.CandleItem; import org.trade.strategy.data.vostro.VostroItem; /** * 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("VostroSeries") public class VostroSeries extends IndicatorSeries { private static final long serialVersionUID = 20183087035446657L; public static final String LENGTH = "Length"; public static final String MA_TYPE = "MAType"; public static final String VOSTRO_PERIOD = "Vostro Period"; public static final String VOSTRO_RANGE = "Vostro Range"; public static final String PRICE_SOURCE = "Price Source"; private String MAType; private Integer length; private Integer vostroPeriod; private BigDecimal vostroRange; private Integer priceSource; /* * Vales used to calculate MA's. These need to be reset when the series is * cleared. */ private double multiplyer = 0; private double sum = 0.0; private double vostro1 = Double.MAX_VALUE; private double vostro2 = Double.MAX_VALUE; private LinkedList<Double> yyValues = new LinkedList<Double>(); private LinkedList<Long> volValues = new LinkedList<Long>(); private double highPlusLowSum = 0.0; private LinkedList<Double> highPlusLowValues = new LinkedList<Double>(); private double highLessLowSum = 0.0; private LinkedList<Double> highLessLowValues = new LinkedList<Double>(); private LinkedList<Double> vostro1Values = new LinkedList<Double>(); private LinkedList<Double> vostro2Values = new LinkedList<Double>(); /** * 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 strategy * Strategy * @param name * String * @param type * String * @param description * String * @param displayOnChart * Boolean * @param chartRGBColor * Integer * @param subChart * Boolean */ public VostroSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); } /** * Constructor for VostroSeries. * * @param strategy * Strategy * @param name * String * @param type * String * @param description * String * @param displayOnChart * Boolean * @param chartRGBColor * Integer * @param subChart * Boolean * @param MAType * String * @param length * Integer */ public VostroSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart, String MAType, Integer length) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); this.MAType = MAType; this.length = length; } public VostroSeries() { super(IndicatorSeries.VostroSeries); } /** * Method clone. * * @return Object * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { VostroSeries clone = (VostroSeries) super.clone(); clone.yyValues = new LinkedList<Double>(); clone.highPlusLowValues = new LinkedList<Double>(); clone.highLessLowValues = new LinkedList<Double>(); clone.volValues = new LinkedList<Long>(); clone.vostro1Values = new LinkedList<Double>(); clone.vostro2Values = new LinkedList<Double>(); return clone; } /** * 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 local calculated fields. */ public void clear() { super.clear(); multiplyer = 0; sum = 0.0; vostro1 = Double.MAX_VALUE; vostro2 = Double.MAX_VALUE; yyValues.clear(); volValues.clear(); highPlusLowSum = 0.0; highPlusLowValues.clear(); highLessLowSum = 0.0; highLessLowValues.clear(); vostro1Values.clear(); vostro2Values.clear(); } /** * Returns the time period for the specified item. * * @param index * the item index. * * @return The time period. */ public RegularTimePeriod getPeriod(int index) { final VostroItem item = (VostroItem) getDataItem(index); return item.getPeriod(); } /** * Adds a data item to the series. * * @param period * the period. * @param vostro * the vostro. */ public void add(RegularTimePeriod period, BigDecimal vostro) { if (!this.isEmpty()) { VostroItem item0 = (VostroItem) this.getDataItem(0); if (!period.getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(new VostroItem(period, vostro), true); } /** * Adds a data item to the series. * * @param notify * the notify listeners. * @param dataItem * VostroItem */ public void add(VostroItem dataItem, boolean notify) { if (!this.isEmpty()) { VostroItem item0 = (VostroItem) this.getDataItem(0); if (!dataItem.getPeriod().getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(dataItem, notify); } /** * Method getPriceSource. * * @return Integer */ @Transient public Integer getPriceSource() { try { if (null == this.priceSource) this.priceSource = (Integer) CodeValue.getValueCode(PRICE_SOURCE, this.getCodeValues()); } catch (Exception e) { this.priceSource = null; } return this.priceSource; } /** * Method setMAType. * * @param priceSource * Integer */ public void setPriceSource(Integer priceSource) { this.priceSource = priceSource; } /** * Method getVostroPeriod. * * @return Integer */ @Transient public Integer getVostroPeriod() { try { if (null == this.vostroPeriod) this.vostroPeriod = (Integer) CodeValue.getValueCode(VOSTRO_PERIOD, this.getCodeValues()); } catch (Exception e) { this.vostroPeriod = null; } return this.vostroPeriod; } /** * Method setCostroPeriod. * * @param vostroPeriod * Integer */ public void setVostroPeriod(Integer vostroPeriod) { this.vostroPeriod = vostroPeriod; } /** * Method getVostroRange. * * @return BigDecimal */ @Transient public BigDecimal getVostroRange() { try { if (null == this.vostroRange) this.vostroRange = (BigDecimal) CodeValue.getValueCode(VOSTRO_RANGE, this.getCodeValues()); } catch (Exception e) { this.vostroRange = null; } return this.vostroRange; } /** * Method setVostroRange. * * @param vostroRange * BigDecimal */ public void setVostroRange(BigDecimal vostroRange) { this.vostroRange = vostroRange; } /** * Method getLength. * * @return Integer */ @Transient public Integer getLength() { try { if (null == this.length) this.length = (Integer) CodeValue.getValueCode(LENGTH, this.getCodeValues()); } catch (Exception e) { this.length = null; } return this.length; } /** * Method setLength. * * @param length * Integer */ public void setLength(Integer length) { this.length = length; } /** * Method getMAType. * * @return String */ @Transient public String getMAType() { try { if (null == this.MAType) this.MAType = (String) CodeValue.getValueCode(MA_TYPE, this.getCodeValues()); } catch (Exception e) { this.MAType = null; } return this.MAType; } /** * Method setMAType. * * @param MAType * String */ public void setMAType(String MAType) { this.MAType = MAType; } /** * Method createSeries. * * @param source * CandleDataset * @param seriesIndex * int */ public void createSeries(CandleDataset source, int seriesIndex) { if (source.getSeries(seriesIndex) == null) { throw new IllegalArgumentException("Null source (CandleDataset)."); } for (int i = 0; i < source.getSeries(seriesIndex).getItemCount(); i++) { this.updateSeries(source.getSeries(seriesIndex), i, true); } } /** * Method updateSeries. int start() { * * int gi_144 = 5; * * int g_period_156 = 100; * * for (gi_152 = 0; gi_152 < Bars; gi_152++) { * * gd_120 = 0; * * for (gi_148 = * gi_152; gi_148 < gi_144 + gi_152; gi_148++) gd_120 += * (High[gi_148] + Low[gi_148]) / 2.0; * * gd_128 = gd_120 / gi_144; gd_120 = 0; * * for (gi_148 = gi_152; gi_148 < gi_144 + gi_152; gi_148++) gd_120 += * High[gi_148] - Low[gi_148]; * * gd_136 = (gd_120 / gi_144)/gi_144; * * g_ibuf_116[gi_152] = (Low[gi_152] - gd_128) / gd_136; * * g_ibuf_112[gi_152] = (High[gi_152] - gd_128) / gd_136; * * if (g_ibuf_112[gi_152] > 8.0 && High[gi_152] > iMA(NULL, 0, g_period_156, * 0, MODE_LWMA, PRICE_MEDIAN, gi_152)) g_ibuf_108[gi_152] = 90.0; * * else { * * if (g_ibuf_116[gi_152] < -8.0 && Low[gi_152] < iMA(NULL, 0, g_period_156, * 0, MODE_LWMA, PRICE_MEDIAN, gi_152)) g_ibuf_108[gi_152] = -90.0; else * g_ibuf_108[gi_152] = 0.0; * * } * * if (g_ibuf_112[gi_152] > 8.0 && g_ibuf_112[gi_152 - 1] > 8.0) * g_ibuf_108[gi_152] = 0; * * if (g_ibuf_112[gi_152] > 8.0 && g_ibuf_112[gi_152 - 1] > 8.0 && * g_ibuf_112[gi_152 - 2] > 8.0) g_ibuf_108[gi_152] = 0; * * if (g_ibuf_116[gi_152] < -8.0 && g_ibuf_116[gi_152 - 1] < -8.0) * g_ibuf_108[gi_152] = 0; * * if (g_ibuf_116[gi_152] < -8.0 && g_ibuf_116[gi_152 - 1] < -8.0 && * g_ibuf_116[gi_152 - 2] < -8.0) g_ibuf_108[gi_152] = 0; } return (0); * * } * * * @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)."); } if (getLength() == null || getLength() < 1) { throw new IllegalArgumentException("MA period must be greater than zero."); } if (getVostroPeriod() == null || getVostroPeriod() < 1) { throw new IllegalArgumentException("Vostro period must be greater than zero."); } if (getLength() < getVostroPeriod()) { throw new IllegalArgumentException("MA period must be greater than Vostro period."); } if (source.getItemCount() > skip) { // get the current data item... CandleItem candleItem = (CandleItem) source.getDataItem(skip); if (0 != this.getPrice(candleItem)) { double price = this.getPrice(candleItem); if (this.yyValues.size() == getLength()) { /* * 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. Sum is just used for * performance save having to sum the last set of values * each time. */ if (newBar) { sum = sum - this.yyValues.getLast() + price; this.yyValues.removeLast(); this.yyValues.addFirst(price); this.volValues.removeLast(); this.volValues.addFirst(candleItem.getVolume()); } else { sum = sum - this.yyValues.getFirst() + price; this.yyValues.removeFirst(); this.yyValues.addFirst(price); } } else { if (newBar) { sum = sum + price; this.yyValues.addFirst(price); this.volValues.addFirst(candleItem.getVolume()); } else { sum = sum + price - this.yyValues.getFirst(); this.yyValues.removeFirst(); this.yyValues.addFirst(price); this.volValues.removeFirst(); this.volValues.addFirst(candleItem.getVolume()); } } if (this.highPlusLowValues.size() == getVostroPeriod()) { /* * 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. Sum is just used for * performance save having to sum the last set of values * each time. */ if (newBar) { this.highPlusLowSum = this.highPlusLowSum - this.highPlusLowValues.getLast() + (candleItem.getHigh() + candleItem.getLow()); this.highPlusLowValues.removeLast(); this.highPlusLowValues.addFirst((candleItem.getHigh() + candleItem.getLow())); this.highLessLowSum = this.highLessLowSum - this.highLessLowValues.getLast() + (candleItem.getHigh() - candleItem.getLow()); this.highLessLowValues.removeLast(); this.highLessLowValues.addFirst((candleItem.getHigh() - candleItem.getLow())); } else { this.highPlusLowSum = this.highPlusLowSum - this.highPlusLowValues.getFirst() + (candleItem.getHigh() + candleItem.getLow()); this.highPlusLowValues.removeFirst(); this.highPlusLowValues.addFirst((candleItem.getHigh() + candleItem.getLow())); this.highLessLowSum = this.highLessLowSum - this.highLessLowValues.getFirst() + (candleItem.getHigh() - candleItem.getLow()); this.highLessLowValues.removeFirst(); this.highLessLowValues.addFirst((candleItem.getHigh() - candleItem.getLow())); } } else { if (newBar) { this.highPlusLowSum = this.highPlusLowSum + (candleItem.getHigh() + candleItem.getLow()); this.highPlusLowValues.addFirst((candleItem.getHigh() + candleItem.getLow())); this.highLessLowSum = this.highLessLowSum + (candleItem.getHigh() - candleItem.getLow()); this.highLessLowValues.addFirst((candleItem.getHigh() - candleItem.getLow())); } else { this.highPlusLowSum = this.highPlusLowSum + (candleItem.getHigh() + candleItem.getLow()) - this.highPlusLowValues.getFirst(); this.highPlusLowValues.removeFirst(); this.highPlusLowValues.addFirst((candleItem.getHigh() + candleItem.getLow())); this.highLessLowSum = this.highLessLowSum + (candleItem.getHigh() - candleItem.getLow()) - this.highLessLowValues.getFirst(); this.highLessLowValues.removeFirst(); this.highLessLowValues.addFirst((candleItem.getHigh() - candleItem.getLow())); } } if (this.yyValues.size() == getLength()) { double ma = calculateMA(this.getMAType(), this.yyValues, this.volValues, sum); double gd_128 = this.highPlusLowSum / 2.0d / this.getVostroPeriod(); double gd_136 = (this.highLessLowSum / this.getVostroPeriod()) / this.getVostroPeriod(); if (newBar && this.vostro1 != Double.MAX_VALUE) { vostro1Values.addFirst(this.vostro1); vostro2Values.addFirst(this.vostro2); if (vostro1Values.size() > 2) { vostro1Values.removeLast(); vostro2Values.removeLast(); } } this.vostro1 = (candleItem.getLow() - gd_128) / gd_136; this.vostro2 = (candleItem.getHigh() - gd_128) / gd_136; double vostro = 0; if (vostro2 > this.getVostroRange().doubleValue() && candleItem.getHigh() > ma) { vostro = 90.0; } else { if (vostro1 < (-1 * this.getVostroRange().doubleValue()) && candleItem.getLow() < ma) { vostro = -90.0; } else { vostro = 0.0; } } if (vostro2Values.size() > 0) { if (vostro2 > this.getVostroRange().doubleValue() && vostro2Values.getFirst() > this.getVostroRange().doubleValue()) { vostro = 0; } } if (vostro2Values.size() > 1) { if (vostro2 > this.getVostroRange().doubleValue() && vostro2Values.getFirst() > this.getVostroRange().doubleValue() && vostro2Values.getLast() > this.getVostroRange().doubleValue()) { vostro = 0; } } if (vostro2Values.size() > 0) { if (vostro1 < (-1 * this.getVostroRange().doubleValue()) && vostro1Values.getFirst() < (-1 * this.getVostroRange().doubleValue())) { vostro = 0; } } if (vostro1Values.size() > 1) { if (vostro1 < (-1 * this.getVostroRange().doubleValue()) && vostro1Values.getFirst() < (-1 * this.getVostroRange().doubleValue()) && vostro1Values.getLast() < (-1 * this.getVostroRange().doubleValue())) { vostro = 0; } } // _log.warn("Vostro Ind Time: " + candleItem.getPeriod() // + " wma: " + ma + " vostro: " + vostro // + " highPlusLowSum: " + this.highPlusLowSum // + " highLessLowSum: " + this.highLessLowSum // + " gd_128: " + gd_128 + " gd_136: " + gd_136 // + " vostro1: " + vostro1 + " vostro2: " + vostro2); if (newBar) { VostroItem dataItem = new VostroItem(candleItem.getPeriod(), new BigDecimal(vostro)); this.add(dataItem, false); } else { VostroItem dataItem = (VostroItem) this.getDataItem(this.getItemCount() - 1); dataItem.setVostro(vostro); } } } } } /** * Method printSeries. * */ public void printSeries() { for (int i = 0; i < this.getItemCount(); i++) { VostroItem dataItem = (VostroItem) this.getDataItem(i); _log.debug("Type: " + this.getType() + " Time: " + dataItem.getPeriod().getStart() + " Value: " + dataItem.getVostro()); } } /** * Method calculateMA. * * @param calcType * String * @param yyValues * LinkedList<Double> * @param volValues * LinkedList<Long> * @param sum * Double * @return double */ private double calculateMA(String calcType, LinkedList<Double> yyValues, LinkedList<Long> volValues, Double sum) { double ma = 0; if (CalculationType.LINEAR.equals(calcType)) { ma = sum / getLength(); } else if (CalculationType.EXPONENTIAL.equals(calcType)) { /* * Multiplier: (2 / (Time periods + 1) ) = (2 / (10 + 1) ) = 0.1818 * (18.18%). EMA: {Close - EMA(previous day)} x * multiplier + * EMA(previous day). */ if (multiplyer == 0) { ma = sum / getLength(); multiplyer = 2 / (getLength() + 1.0d); } else { ma = ((yyValues.getFirst() - yyValues.get(1)) * multiplyer) + yyValues.get(1); } /* * Use the EMA in the stored values as we need the previous one for * the calc. */ yyValues.removeFirst(); yyValues.addFirst(ma); } else if (CalculationType.WEIGHTED.equals(calcType)) { double sumYY = 0; int count = 0; for (int i = yyValues.size(); i > 0; i--) { count = count + (getLength() + 1 - i); sumYY = sumYY + (yyValues.get(i - 1) * (getLength() + 1 - i)); } ma = sumYY / count; } else if (CalculationType.WEIGHTED_VOLUME.equals(calcType)) { double sumYY = 0; double count = 0; for (int i = yyValues.size(); i > 0; i--) { count = count + ((getLength() + 1 - i) * volValues.get(i - 1)); sumYY = sumYY + (yyValues.get(i - 1) * volValues.get(i - 1) * (getLength() + 1 - i)); } ma = sumYY / count; } return ma; } /** * Method get the price. * * @param candle * CandleItem * @return double */ private double getPrice(CandleItem candle) { switch (this.getPriceSource()) { case 1: { return candle.getClose(); } case 2: { return candle.getOpen(); } case 3: { return candle.getHigh(); } case 4: { return candle.getLow(); } case 5: { return (candle.getHigh() + candle.getLow()) / 2.0d; } case 6: { return (candle.getHigh() + candle.getLow() + candle.getClose()) / 3.0d; } case 7: { return (candle.getOpen() + candle.getHigh() + candle.getLow() + candle.getClose()) / 4.0d; } default: { return candle.getClose(); } } } }