Channel Based Zigzag [HeWhoMustNotBeNamed]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HeWhoMustNotBeNamed

//   __    __            __       __  __                  __       __                        __      __    __              __      _______             __    __                                          __ 
//  /  |  /  |          /  |  _  /  |/  |                /  \     /  |                      /  |    /  \  /  |            /  |    /       \           /  \  /  |                                        /  |
//  $$ |  $$ |  ______  $$ | / \ $$ |$$ |____    ______  $$  \   /$$ | __    __   _______  _$$ |_   $$  \ $$ |  ______   _$$ |_   $$$$$$$  |  ______  $$  \ $$ |  ______   _____  ____    ______    ____$$ |
//  $$ |__$$ | /      \ $$ |/$  \$$ |$$      \  /      \ $$$  \ /$$$ |/  |  /  | /       |/ $$   |  $$$  \$$ | /      \ / $$   |  $$ |__$$ | /      \ $$$  \$$ | /      \ /     \/    \  /      \  /    $$ |
//  $$    $$ |/$$$$$$  |$$ /$$$  $$ |$$$$$$$  |/$$$$$$  |$$$$  /$$$$ |$$ |  $$ |/$$$$$$$/ $$$$$$/   $$$$  $$ |/$$$$$$  |$$$$$$/   $$    $$< /$$$$$$  |$$$$  $$ | $$$$$$  |$$$$$$ $$$$  |/$$$$$$  |/$$$$$$$ |
//  $$$$$$$$ |$$    $$ |$$ $$/$$ $$ |$$ |  $$ |$$ |  $$ |$$ $$ $$/$$ |$$ |  $$ |$$      \   $$ | __ $$ $$ $$ |$$ |  $$ |  $$ | __ $$$$$$$  |$$    $$ |$$ $$ $$ | /    $$ |$$ | $$ | $$ |$$    $$ |$$ |  $$ |
//  $$ |  $$ |$$$$$$$$/ $$$$/  $$$$ |$$ |  $$ |$$ \__$$ |$$ |$$$/ $$ |$$ \__$$ | $$$$$$  |  $$ |/  |$$ |$$$$ |$$ \__$$ |  $$ |/  |$$ |__$$ |$$$$$$$$/ $$ |$$$$ |/$$$$$$$ |$$ | $$ | $$ |$$$$$$$$/ $$ \__$$ |
//  $$ |  $$ |$$       |$$$/    $$$ |$$ |  $$ |$$    $$/ $$ | $/  $$ |$$    $$/ /     $$/   $$  $$/ $$ | $$$ |$$    $$/   $$  $$/ $$    $$/ $$       |$$ | $$$ |$$    $$ |$$ | $$ | $$ |$$       |$$    $$ |
//  $$/   $$/  $$$$$$$/ $$/      $$/ $$/   $$/  $$$$$$/  $$/      $$/  $$$$$$/  $$$$$$$/     $$$$/  $$/   $$/  $$$$$$/     $$$$/  $$$$$$$/   $$$$$$$/ $$/   $$/  $$$$$$$/ $$/  $$/  $$/  $$$$$$$/  $$$$$$$/ 
//                                                                                                                                                                                                          
//                                                                                                                                                                                                          
//
//@version=5
indicator("Channel Based Zigzag [HeWhoMustNotBeNamed]", "cZigzag", overlay=true)

import HeWhoMustNotBeNamed/ta/1 as eta
import HeWhoMustNotBeNamed/arrays/1 as pa

bandType = input.string('Bollinger Bands', '', ['Bollinger Bands', 'Keltner Channel', 'Donchian Channel'], group='Bands', inline='b1')
maType = input.string('sma', '', options=['sma', 'ema', 'hma', 'rma', 'wma', 'vwma', 'highlow', 'linreg', 'median'], inline = 'b1', group='Bands')
maLength = input.int(20, '', minval = 5, step=5, inline = 'b1', group='Bands')
multiplier = input.float(2.0, '', step=0.5, minval=0.5, group='Bands', inline='b1', tooltip = 'Band parameters let you select type of band/channel, moving average base, length and multiplier')
useTrueRange = input.bool(true, 'Use True Range (For KC only)', group='Bands', tooltip = 'Used only for keltener channel calculation')
sticky = input.bool(true, 'Sticky', group='Bands', tooltip = 'Sticky bands avoids changing the band unless there is breach. Meaning, band positions only change when price is outside the band.')
showBands = input.bool(true, 'Display Bands', group='Bands', tooltip = 'If selected, displays bands on chart')
highlightBreakout = input.bool(false, 'Highlight Breakout Bars', group='Bands', tooltip = 'If selected, only breakout candles are highlighted. Rest of the candles will have silver color')
float upper = 0.0
float lower = 0.0
float middle = 0.0

type Pivot
    float price
    int bar
    int bartime
    int direction

unshift(zigzag, p)=>
    if(array.size(zigzag) > 1)
        llastPivot = array.get(zigzag, 1)
        p.direction := p.price*p.direction > llastPivot.price*p.direction ? p.direction*2 : p.direction

    array.unshift(zigzag, p)
    if(array.size(zigzag)>15)
        array.pop(zigzag)

if(bandType == 'Bollinger Bands')
    [bmiddle, bupper, blower] = eta.bb(close, maType, maLength, multiplier, sticky)
    upper := bupper
    lower := blower
    middle := bmiddle

if(bandType == 'Keltner Channel')
    [kmiddle, kupper, klower] = eta.kc(close, maType, maLength, multiplier, useTrueRange, sticky)
    upper := kupper
    lower := klower
    middle := kmiddle

if(bandType == 'Donchian Channel')
    [dmiddle, dupper, dlower] = eta.dc(maLength, false, close, sticky)
    upper := dupper
    lower := dlower
    middle := dmiddle

addPivot(zigzag, direction)=>
    lastRemoved = false
    price = direction > 0? high : low
    Pivot p = Pivot.new(price, bar_index, time, int(direction))
    if(array.size(zigzag) == 0)
        unshift(zigzag, p)
    else
        lastPivot = array.get(zigzag, 0)
        lastPivotDirection = math.sign(lastPivot.direction)
        if (lastPivotDirection != p.direction)
            unshift(zigzag, p)
        if(lastPivotDirection == p.direction and lastPivot.price*direction < p.price*direction)
            array.remove(zigzag, 0)
            unshift(zigzag, p)
            lastRemoved := true
    lastRemoved
       

add_pivots(zigzag, pHigh, pLow)=>
    count = 0
    removeLast = false
    if(array.size(zigzag) == 0)
        if(pHigh)
            addPivot(zigzag, 1)
            count+=1
        if(pLow)
            addPivot(zigzag, -1)
            count+=1
    else
        lastPivot = array.get(zigzag, 0)
        lastDir = math.sign(lastPivot.direction)
        if(pHigh and lastDir == 1)  or (pLow and lastDir == -1)
            removeLast := addPivot(zigzag, lastDir)
            count := removeLast ? count+1 : count
        if(pHigh and (lastDir == -1)) or (pLow and (lastDir == 1))
            addPivot(zigzag, -lastDir)
            count+=1

    [count, removeLast]

draw_zigzag(zigzag, count, removeLast)=>
    var zigzaglines = array.new<line>()
    if(removeLast and array.size(zigzaglines) > 0)
        pa.shift(zigzaglines)
    if(array.size(zigzag) > count and count >= 1)
        for i=count to 1
            startPivot = array.get(zigzag, count)
            endPivot = array.get(zigzag, count-1)
            startDirection = startPivot.direction
            endDirection = endPivot.direction
            lineColor = endDirection == 2? color.green : endDirection == -2? color.red : color.silver
            ln = line.new(startPivot.bartime, startPivot.price, endPivot.bartime, endPivot.price, xloc.bar_time, color=lineColor, width = 2)
            pa.unshift(zigzaglines, ln, 500)
    zigzaglines


var zigzag = array.new<Pivot>()

pHigh = high >= upper
pLow = low <= lower

[count, removeLast] = add_pivots(zigzag, pHigh, pLow)

barcolor(pHigh or pLow or not highlightBreakout? na : color.silver)

plot(showBands? upper:na, 'Upper', color.green, 0, plot.style_linebr)
plot(showBands? lower:na, 'Lower', color.red, 0, plot.style_linebr)

zigzaglines = draw_zigzag(zigzag, count, removeLast)