/* =========================================================== * 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 javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import org.jfree.data.time.ohlc.OHLCSeriesCollection; import org.trade.persistent.dao.Contract; 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.heikinashi.HeikinAshiItem; /** * Heikin-Ashi Candlesticks are an offshoot from Japanese candlesticks. * Heikin-Ashi Candlesticks use the open-close data from the prior period and * the open-high-low-close data from the current period to create a combo * candlestick. The resulting candlestick filters out some noise in an effort to * better capture the trend. In Japanese, Heikin means "average" and "ashi" * means "pace" (EUDict.com). Taken together, Heikin-Ashi represents the * average-pace of prices. Heikin-Ashi Candlesticks are not used like normal * candlesticks. Dozens of bullish or bearish reversal patterns consisting of * 1-3 candlesticks are not to be found. Instead, these candlesticks can be used * to identify trending periods, potential reversal points and classic technical * analysis patterns. * * * Heikin-Ashi Candlesticks are based on price data from the current * open-high-low-close, the current Heikin-Ashi values and the prior Heikin-Ashi * values. Yes, it is a bit complicated. In the formula below, a "(0)" denotes * the current period. A "(-1)" denotes the prior period. "HA" refers to * Heikin-Ashi. Let's take each data point one at a time. * * * 1. The Heikin-Ashi Close is simply an average of the open, high, low and * close for the current period. * * HA-Close = (Open(0) + High(0) + Low(0) + Close(0)) / 4 * * 2. The Heikin-Ashi Open is the average of the prior Heikin-Ashi candlestick * open plus the close of the prior Heikin-Ashi candlestick. * * HA-Open = (HA-Open(-1) + HA-Close(-1)) / 2 * * 3. The Heikin-Ashi High is the maximum of three data points: the current * period's high, the current Heikin-Ashi candlestick open or the current * Heikin-Ashi candlestick close. * * HA-High = Maximum of the High(0), HA-Open(0) or HA-Close(0) * * 4. The Heikin-Ashi low is the minimum of three data points: the current * period's low, the current Heikin-Ashi candlestick open or the current * Heikin-Ashi candlestick close. * * HA-Low = Minimum of the Low(0), HA-Open(0) or HA-Close(0) * * @since 1.0.4 * * @see OHLCSeriesCollection * @author Simon Allen * @version $Revision: 1.0 $ */ @Entity @DiscriminatorValue("HeikinAshiSeries") public class HeikinAshiSeries extends IndicatorSeries { private static final long serialVersionUID = 20183087035446657L; /** * 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 HeikinAshiSeries(Strategy strategy, String name, String type, String description, Boolean displayOnChart, Integer chartRGBColor, Boolean subChart) { super(strategy, name, type, description, displayOnChart, chartRGBColor, subChart); } public HeikinAshiSeries() { super(IndicatorSeries.HeikinAshiSeries); } /** * Returns the time period for the specified item. * * @param index * the item index. * * * @return The time period. */ public RegularTimePeriod getPeriod(int index) { final HeikinAshiItem item = (HeikinAshiItem) getDataItem(index); return item.getPeriod(); } /** * Adds a data item to the series. * * @param period * the period. * * * @param contract * Contract * @param open * double * @param high * double * @param low * double * @param close * double * @param lastUpdateDate * ZonedDateTime */ public void add(Contract contract, RegularTimePeriod period, double open, double high, double low, double close, ZonedDateTime lastUpdateDate) { if (!this.isEmpty()) { HeikinAshiItem item0 = (HeikinAshiItem) this.getDataItem(0); if (!period.getClass().equals(item0.getPeriod().getClass())) { throw new IllegalArgumentException("Can't mix RegularTimePeriod class types."); } } super.add(new HeikinAshiItem(contract, period, open, high, low, close, lastUpdateDate), true); } /** * Adds a data item to the series. * * * @param notify * the notify listeners. * @param dataItem * HeikinAshiItem */ public void add(HeikinAshiItem dataItem, boolean notify) { if (!this.isEmpty()) { HeikinAshiItem item0 = (HeikinAshiItem) 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 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 clone. * * @return Object * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { HeikinAshiSeries clone = (HeikinAshiSeries) super.clone(); return clone; } /** * Method updateSeries. Heikin Ashi charts calculate their own open (HAO), * high (HAH), low (HAL), and close (HAC), using the actual open (O), high * (H), low (L), and close (C), of the time frame (e.g. the open, high, low, * and close, of each five minutes). * * HAO = (HAO-1 + HAC-1) / 2 * * HAC = (O + H + L + C) / 4 * * HAH = Highest(H, HAO, HAC) * * HAL = Lowest(L, HAO, HAC) * * @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 (source.getItemCount() > skip) { // get the current data item... CandleItem candleItem = (CandleItem) source.getDataItem(skip); if (source.getItemCount() > 1) { /* * Get the prev candle the new candle may be just forming or * completed if back testing. Hiekin-Ashi bats must be formed * from completed bars. */ int index = this.indexOf(candleItem.getPeriod()); double xOpenPrev = 0; double xClosePrev = 0; if (index < 1) { if (this.isEmpty()) { xOpenPrev = candleItem.getOpen(); xClosePrev = candleItem.getClose(); } else { HeikinAshiItem prevItem = (HeikinAshiItem) this.getDataItem(this.getItemCount() - 1); xClosePrev = prevItem.getClose(); xOpenPrev = prevItem.getOpen(); } } else { HeikinAshiItem prevItem = (HeikinAshiItem) this.getDataItem(this.getItemCount() - 2); xClosePrev = prevItem.getClose(); xOpenPrev = prevItem.getOpen(); } double xClose = (candleItem.getOpen() + candleItem.getHigh() + candleItem.getLow() + candleItem.getClose()) / 4; double xOpen = (xOpenPrev + xClosePrev) / 2; double xHigh = Math.max(candleItem.getHigh(), Math.max(xClosePrev, xOpenPrev)); double xLow = Math.min(candleItem.getLow(), Math.min(xClosePrev, xOpenPrev)); if (index < 0) { this.add(new HeikinAshiItem(source.getContract(), candleItem.getPeriod(), xOpen, xHigh, xLow, xClose, candleItem.getLastUpdateDate()), false); } else { HeikinAshiItem currDataItem = (HeikinAshiItem) this.getDataItem(index); currDataItem.setOpen(xOpen); currDataItem.setHigh(xHigh); currDataItem.setLow(xLow); currDataItem.setClose(xClose); currDataItem.setLastUpdateDate(candleItem.getLastUpdateDate()); } } } } /** * Method printSeries. * */ public void printSeries() { for (int i = 0; i < this.getItemCount(); i++) { HeikinAshiItem dataItem = (HeikinAshiItem) this.getDataItem(i); _log.debug("Type: " + this.getType() + " Time: " + dataItem.getPeriod().getStart() + " Open: " + dataItem.getOpen() + " Close: " + dataItem.getClose() + " High: " + dataItem.getHigh() + " Low: " + dataItem.getLow()); } } }