/* =========================================================== * 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 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.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.rsi.RelativeStrengthIndexItem; /** * Developed J. Welles Wilder, the Relative Strength Index (RSI) is a momentum * oscillator that measures the speed and change of price movements. RSI * oscillates between zero and 100. Traditionally, and according to Wilder, RSI * is considered overbought when above 70 and oversold when below 30. Signals * can also be generated by looking for divergences, failure swings and * centerline crossovers. RSI can also be used to identify the general trend. * * RSI is an extremely popular momentum indicator that has been featured in a * number of articles, interviews and books over the years. In particular, * Constance Brown's book, Technical Analysis for the Trading Professional, * features the concept of bull market and bear market ranges for RSI. Andrew * Cardwell, Brown's RSI mentor, introduced positive and negative reversals for * RSI. In addition, Cardwell turned the notion of divergence, literally and * figuratively, on its head. * * * RSI = 100 - 100/ 1 + RS * * RS = Average Gain / Average Loss * * @since 1.0.4 * * @see OHLCSeriesCollection * @author Simon Allen * @version $Revision: 1.0 $ */ @Entity @DiscriminatorValue("RelativeStrengthIndexSeries") public class RelativeStrengthIndexSeries extends IndicatorSeries { private static final long serialVersionUID = 20183087035446657L; public static final String LENGTH = "Length"; public static final String ROLLING_CANDLE = "RollingCandle"; private Integer length; private Boolean rollingCandle; /* * Vales used to calculate RelativeStrengthIndex. These need to be reset * when the series is cleared. */ private double posSumCloseDiff = 0; private double negSumCloseDiff = 0; private double preDiffCloseValue = 0; private double avgLossRSI = 0; private double avgGainRSI = 0; private double prevAvgLossRSI = 0; private double prevAvgGainRSI = 0; private double currentRSI = Double.MAX_VALUE; /** * 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 RelativeStrengthIndexSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); } /** * Constructor for RelativeStrengthIndexSeries. * * @param strategy * Strategy * @param name * String * @param type * String * @param description * String * @param displayOnChart * Boolean * @param chartRGBColor * Integer * @param subChart * Boolean * @param length * Integer */ public RelativeStrengthIndexSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart, Integer length) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); this.length = length; } public RelativeStrengthIndexSeries() { super(IndicatorSeries.RelativeStrengthIndexSeries); } /** * Method clone. * * @return Object * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { RelativeStrengthIndexSeries clone = (RelativeStrengthIndexSeries) super.clone(); 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(); posSumCloseDiff = 0; negSumCloseDiff = 0; preDiffCloseValue = 0; avgLossRSI = 0; avgGainRSI = 0; prevAvgLossRSI = 0; prevAvgGainRSI = 0; currentRSI = Double.MAX_VALUE; } /** * Returns the time period for the specified item. * * @param index * the item index. * * * @return The time period. */ public RegularTimePeriod getPeriod(int index) { final RelativeStrengthIndexItem item = (RelativeStrengthIndexItem) getDataItem(index); return item.getPeriod(); } /** * Adds a data item to the series. * * @param period * the period. * * * * @param relativeStrengthIndex * BigDecimal */ public void add(RegularTimePeriod period, BigDecimal relativeStrengthIndex) { if (!this.isEmpty()) { RelativeStrengthIndexItem item0 = (RelativeStrengthIndexItem) this.getDataItem(0); if (!period.getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(new RelativeStrengthIndexItem(period, relativeStrengthIndex), true); } /** * Adds a data item to the series. * * @param dataItem * the RelativeStrengthIndex. * @param notify * the notify listeners. */ public void add(RelativeStrengthIndexItem dataItem, boolean notify) { if (!this.isEmpty()) { RelativeStrengthIndexItem item0 = (RelativeStrengthIndexItem) 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 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 getRollingCandle. * * @return Boolean */ @Transient public Boolean getRollingCandle() { try { if (null == this.rollingCandle) this.rollingCandle = (Boolean) CodeValue.getValueCode(ROLLING_CANDLE, this.getCodeValues()); } catch (Exception e) { this.rollingCandle = null; } return this.rollingCandle; } /** * Method setRollingCandle. * * @param rollingCandle * Boolean */ public void setRollingCandle(Boolean rollingCandle) { this.rollingCandle = rollingCandle; } /** * 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. * * @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("RSI period must be greater than zero."); } if (source.getItemCount() > skip) { // get the current data item... CandleItem candleItem = (CandleItem) source.getDataItem(skip); double diffCloseValue = 0; if (source.getItemCount() > 1) { CandleItem prevCandleItem = (CandleItem) source.getDataItem(skip - 1); diffCloseValue = candleItem.getClose() - prevCandleItem.getClose(); if (this.getRollingCandle()) { diffCloseValue = source.getRollingCandle().getClose() - source.getPreviousRollingCandle().getClose(); } /* * 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) { if (diffCloseValue > 0) { posSumCloseDiff = posSumCloseDiff + Math.abs(diffCloseValue); } else { negSumCloseDiff = negSumCloseDiff + Math.abs(diffCloseValue); } prevAvgLossRSI = avgLossRSI; prevAvgGainRSI = avgGainRSI; preDiffCloseValue = diffCloseValue; } else { if (diffCloseValue > 0 && preDiffCloseValue > 0) { posSumCloseDiff = posSumCloseDiff + Math.abs(diffCloseValue) - Math.abs(preDiffCloseValue); } else if (diffCloseValue > 0 && preDiffCloseValue < 0) { posSumCloseDiff = posSumCloseDiff + Math.abs(diffCloseValue); negSumCloseDiff = negSumCloseDiff - Math.abs(preDiffCloseValue); } else if (diffCloseValue < 0 && preDiffCloseValue < 0) { negSumCloseDiff = negSumCloseDiff + Math.abs(diffCloseValue) - Math.abs(preDiffCloseValue); } else if (diffCloseValue < 0 && preDiffCloseValue > 0) { negSumCloseDiff = negSumCloseDiff + Math.abs(diffCloseValue); posSumCloseDiff = posSumCloseDiff - Math.abs(preDiffCloseValue); } } } if (skip >= getLength()) { if (currentRSI == Double.MAX_VALUE) { avgGainRSI = posSumCloseDiff / getLength(); avgLossRSI = negSumCloseDiff / getLength(); currentRSI = 100 - (100 / (1 + (avgGainRSI / (avgLossRSI == 0 ? 1 : avgLossRSI)))); } else { if (preDiffCloseValue > 0) { avgGainRSI = (((prevAvgGainRSI * (getLength() - 1)) + Math.abs(preDiffCloseValue))) / getLength(); avgLossRSI = (((prevAvgLossRSI * (getLength() - 1)) + 0)) / getLength(); } else { avgGainRSI = (((prevAvgGainRSI * (getLength() - 1)) + 0)) / getLength(); avgLossRSI = (((prevAvgLossRSI * (getLength() - 1)) + Math.abs(preDiffCloseValue))) / getLength(); } currentRSI = 100 - (100 / (1 + (avgGainRSI / (avgLossRSI == 0 ? 1 : avgLossRSI)))); } if (newBar) { RelativeStrengthIndexItem dataItem = new RelativeStrengthIndexItem(candleItem.getPeriod(), new BigDecimal(currentRSI)); this.add(dataItem, false); } else { RelativeStrengthIndexItem dataItem = (RelativeStrengthIndexItem) this .getDataItem(this.getItemCount() - 1); dataItem.setRelativeStrengthIndex(currentRSI); } } } } /** * Method printSeries. * */ public void printSeries() { for (int i = 0; i < this.getItemCount(); i++) { RelativeStrengthIndexItem dataItem = (RelativeStrengthIndexItem) this.getDataItem(i); _log.debug("Type: " + this.getType() + " Time: " + dataItem.getPeriod().getStart() + " Value: " + dataItem.getRelativeStrengthIndex()); } } }