/** * The MIT License (MIT) * * Copyright (c) 2014-2017 Marc de Verdelhan & respective authors (see AUTHORS) * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package eu.verdelhan.ta4j.indicators.statistics; import eu.verdelhan.ta4j.Indicator; import eu.verdelhan.ta4j.Decimal; import eu.verdelhan.ta4j.indicators.CachedIndicator; /** * Periodical Growth Rate indicator. * * In general the 'Growth Rate' is useful for comparing the average returns of * investments in stocks or funds and can be used to compare the performance * e.g. comparing the historical returns of stocks with bonds. * * This indicator has the following characteristics: * - the calculation is timeframe dependendant. The timeframe corresponds to the * number of trading events in a period, e. g. the timeframe for a US trading * year for end of day ticks would be '251' trading days * - the result is a step function with a constant value within a timeframe * - NaN values while index is smaller than timeframe, e.g. timeframe is year, * than no values are calculated before a full year is reached * - NaN values for incomplete timeframes, e.g. timeframe is a year and your * timeseries contains data for 11,3 years, than no values are calculated for * the remaining 0,3 years * - the method 'getTotalReturn' calculates the total return over all returns * of the coresponding timeframes * * * Further readings: * Good sumary on 'Rate of Return': https://en.wikipedia.org/wiki/Rate_of_return * Annual return / CAGR: http://www.investopedia.com/terms/a/annual-return.asp * Annualized Total Return: http://www.investopedia.com/terms/a/annualized-total-return.asp * Annualized Return vs. Cumulative Return: * http://www.fool.com/knowledge-center/2015/11/03/annualized-return-vs-cumulative-return.aspx * */ public class PeriodicalGrowthRateIndicator extends CachedIndicator<Decimal> { private final Indicator<Decimal> indicator; private final int timeFrame; /** * Constructor. * Example: use timeFrame = 251 and "end of day"-ticks for annual behaviour * in the US (http://tradingsim.com/blog/trading-days-in-a-year/). * @param indicator * @param timeFrame */ public PeriodicalGrowthRateIndicator(Indicator<Decimal> indicator, int timeFrame) { super(indicator); this.indicator = indicator; this.timeFrame = timeFrame; } /** * Gets the TotalReturn from the calculated results of the method 'calculate'. * For a timeFrame = number of trading days within a year (e. g. 251 days in the US) * and "end of day"-ticks you will get the 'Annualized Total Return'. * Only complete timeFrames are taken into the calculation. * @return the total return from the calculated results of the method 'calculate' */ public double getTotalReturn() { Decimal totalProduct = Decimal.ONE; int completeTimeframes = (getTimeSeries().getTickCount() / timeFrame); for (int i = 1; i <= completeTimeframes; i++) { int index = i * timeFrame; Decimal currentReturn = getValue(index); // Skip NaN at the end of a series if (currentReturn != Decimal.NaN) { currentReturn = currentReturn.plus(Decimal.ONE); totalProduct = totalProduct.multipliedBy(currentReturn); } } return (Math.pow(totalProduct.toDouble(), (1.0 / completeTimeframes))); } @Override protected Decimal calculate(int index) { Decimal currentValue = indicator.getValue(index); int helpPartialTimeframe = index % timeFrame; double helpFullTimeframes = Math.floor((double) indicator.getTimeSeries().getTickCount() / (double) timeFrame); double helpIndexTimeframes = (double) index / (double) timeFrame; double helpPartialTimeframeHeld = (double) helpPartialTimeframe / (double) timeFrame; double partialTimeframeHeld = (helpPartialTimeframeHeld == 0) ? 1.0 : helpPartialTimeframeHeld; // Avoid calculations of returns: // a.) if index number is below timeframe // e.g. timeframe = 365, index = 5 => no calculation // b.) if at the end of a series incomplete timeframes would remain Decimal timeframedReturn = Decimal.NaN; if ((index >= timeFrame) /*(a)*/ && (helpIndexTimeframes < helpFullTimeframes) /*(b)*/) { Decimal movingValue = indicator.getValue(index - timeFrame); Decimal movingSimpleReturn = (currentValue.minus(movingValue)).dividedBy(movingValue); double timeframedReturn_double = Math.pow((1 + movingSimpleReturn.toDouble()), (1 / partialTimeframeHeld)) - 1; timeframedReturn = Decimal.valueOf(timeframedReturn_double); } return timeframedReturn; } }