anandanand84/technicalindicators

Ichimoku indicator error

werlang opened this issue · 2 comments

It seems that the Ichimoku indicator spanA and spanB lines are calculated wrong.
They should be plotted displacement periods in the future, but they are not.

Here is a reference for the calculation:
https://www.investopedia.com/terms/i/ichimoku-cloud.asp

I created a chart using TradingvVew lightweight charts, and plotted the ichimoku values as lines. To compare, I picked the same chart on TradingView with the ichimoku indicator enabled. It seems clear to me that the conversion and base lines are plotted right, but spanA and spanB are not plotted in the future, as they should be.

image

I believe the error is here. The commented part is responsible for the displacement.

// if(spanCounter < params.displacement) {
// spanCounter++
// } else {
// spanA = spanAs.shift()
// spanB = spanBs.shift()
// }

I corrected this error. Tried to follow the instructions to create a pull request, but got a bunch of errors.
This should fix the error:

class IchimokuCloud extends Indicator {
    constructor(input) {
        super(input);
        this.result = [];
        var defaults = {
            conversionPeriod: 9,
            basePeriod: 26,
            spanPeriod: 52,
            displacement: 26
        };
        var params = Object.assign({}, defaults, input);
        var currentConversionData = new FixedSizeLinkedList(params.conversionPeriod * 2, true, true, false);
        var currentBaseData = new FixedSizeLinkedList(params.basePeriod * 2, true, true, false);
        var currenSpanData = new FixedSizeLinkedList(params.spanPeriod * 2, true, true, false);
        let spanHistory = [];
        this.generator = (function* () {
            let result;
            let tick;
            let period = Math.max(params.conversionPeriod, params.basePeriod, params.spanPeriod, params.displacement);
            let periodCounter = 1;
            tick = yield;
            while (true) {
                // Keep a list of lows/highs for the max period
                currentConversionData.push(tick.high);
                currentConversionData.push(tick.low);
                currentBaseData.push(tick.high);
                currentBaseData.push(tick.low);
                currenSpanData.push(tick.high);
                currenSpanData.push(tick.low);
                if (periodCounter < period) {
                    periodCounter++;
                }
                else {
                    // Tenkan-sen (ConversionLine): (9-period high + 9-period low)/2))
                    let conversionLine = (currentConversionData.periodHigh + currentConversionData.periodLow) / 2;
                    // Kijun-sen (Base Line): (26-period high + 26-period low)/2))
                    let baseLine = (currentBaseData.periodHigh + currentBaseData.periodLow) / 2;
                    // Senkou Span A (Leading Span A): (Conversion Line + Base Line)/2))
                    let spanA = (conversionLine + baseLine) / 2;
                    // Senkou Span B (Leading Span B): (52-period high + 52-period low)/2))
                    let spanB = (currenSpanData.periodHigh + currenSpanData.periodLow) / 2;
                    // Senkou Span A / Senkou Span B offset by 26 periods
                    spanHistory.push([ spanA, spanB ]);

                    result = {
                        conversion: conversionLine,
                        base: baseLine,
                    };

                    if(spanHistory.length == params.displacement) {
                        [ spanA, spanB ] = spanHistory.shift();
                        result.spanA = spanA;
                        result.spanB = spanB;
                    }
                }
                tick = yield result;
            }
        })();
        this.generator.next();
        input.low.forEach((tick, index) => {
            var result = this.generator.next({
                high: input.high[index],
                low: input.low[index],
            });
            if (result.value) {
                this.result.push(result.value);
            }
        });
    }
    nextValue(price) {
        return this.generator.next(price).value;
    }
}