Double Tops & Bottoms [QuantVue]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © QuantVue

//@version=5
indicator("Double Tops & Bottoms [QuantVue]", overlay = true, max_lines_count = 500, max_labels_count = 500)

//----------settings----------//
lookback = input.int(9, 'Pivot lookback')
maxLen = input.int(90, 'Maximum Length Between Tops/Bottoms')

var g0 = 'Style Settings'
dtCol = input.color(color.red, 'Double Top Color', group = g0)
dbCol = input.color(color.green, 'Double Bottom Color', group = g0)
shapeSize = input.string('Tiny', 'Shape Size', ['Auto', 'Tiny', 'Small', 'Normal', 'Large'], group = g0)
lineStyle = input.string('Solid', 'Line Style', options = ['Dotted', 'Dashed', 'Solid'], group = g0)
lineWidth = input.int(1, 'Line Thickness', minval = 1, maxval = 5, step = 1, group = g0)

var g1 = 'Alerts'
dbAlert = input.bool(true, 'Double Bottom', group = g1)
dtAlert = input.bool(true, 'Double Top', group = g1)

//----------udt----------//
type pivot
    float p 
    int b 
    line l
    bool a = true
    bool h = true

//----------methods----------//
method sizer(string this)=>
    switch this
        'Auto' => size.auto
        'Tiny' => size.tiny
        'Small' => size.small
        'Normal' => size.normal
        'Large' => size.large

method switcher(string this)=>
    switch this
        'Dashed' => line.style_dashed
        'Dotted' => line.style_dotted
        'Solid' => line.style_solid

method run(array<pivot> this)=>
    for [idx,p] in this
        if p.a
            if bar_index - p.b > maxLen
                p.a := false
                p.l.delete()
            if (high < p.p and p.h) or (low > p.p and not p.h)
                p.l.set_x2(bar_index)
            else if (close > p.p and p.h) or (close < p.p and not p.h)
                p.l.delete()
                p.a := false
            if high > p.p and close < p.p and p.h
                p.l.set_x2(bar_index)
                label.new(bar_index, high, yloc = yloc.abovebar, style = label.style_triangledown, color = dtCol, size = shapeSize.sizer())
                if dbAlert
                    alert('Double Top', alert.freq_once_per_bar_close)
                p.a := false
            else if low < p.p and close > p.p and not p.h
                p.l.set_x2(bar_index)
                label.new(bar_index, high, yloc = yloc.belowbar, style=label.style_triangleup, color = dbCol, size = shapeSize.sizer())
                if dtAlert
                    alert('Double Bottom', alert.freq_once_per_bar_close)
                p.a := false

//----------variables----------//
var phArr = array.new<pivot>()
var plArr = array.new<pivot>()

//----------pivot points----------//
ph = ta.pivothigh(lookback,lookback)
pl = ta.pivotlow(lookback,lookback)

if ph
    phArr.unshift(pivot.new(high[lookback], bar_index[lookback], line.new(bar_index[lookback], high[lookback], bar_index, 
         high[lookback], color = dtCol, style = lineStyle.switcher(), width = lineWidth)))
if pl
    plArr.unshift(pivot.new(low[lookback], bar_index[lookback], line.new(bar_index[lookback], low[lookback], bar_index, 
         low[lookback], color = dbCol, style = lineStyle.switcher(), width = lineWidth), h = false))

//----------run methods----------//
phArr.run()
plArr.run()