/* ===========================================================
* 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());
}
}
}