Pivotal Moments
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © allanster on TradingView

//@version=5
indicator('Pivotal Moments', overlay = true, max_lines_count = 500, max_labels_count = 500)

// Draws lines for each of up to 500 pivots that have never been revisited at the present moment in time.

tooltipD  = "Delays deletion by nBars or nTime of any Pivot lines that are touched or crossed by realtime bars formed after indicator has been loaded "  +
 "on chart.\n\nWhen using nBars the bar duration is from next bar until close of Nth bar. This is the recommended delay method.\n\nWhen using nTime "    + 
 "units the time duration is the minimum time of delay desired, the actual time delay may exceed specified amount. Note that this delay method can be "  +
 "computationally quite expensive and may cause erratic behavior when used with a large delay, large number of lines, or when the indicator has been "   +
 "loaded for a long while.\n\nElapsed time is checked upon each price or volume update. Frequency of the updates is dependent upon liquidity. If time "  + 
 "delay is set to 3 seconds and the next bar update after touch does not occur until 5 seconds later then the delay would be 5 seconds since that is "   + 
 "when the next check for elapsed time occurs."
tooltipQ  = "Extend levels nBars to the right of current bar. Show number of newest Pivot levels to keep"

inBarsL_  = input.int   (defval = 2,                       title = 'Pivot Bars Left',    minval = 0, maxval = 100,                   inline = '0')
inBars_R  = input.int   (defval = 2,                       title = ' Right',             minval = 0, maxval = 100,                   inline = '0')
inStyles  = input.string(defval = 'Dotted',                title = 'Lines Show As',     options = ['Dashed', 'Dotted', 'Solid'],     inline = '1')
inWidths  = input.int   (defval = 1,                       title = ' Wide ',                                                         inline = '1')
inDelayN  = input.int   (defval = 0,                       title = 'Delay Removal',      minval = 0,               step = 1,         inline = '2')
inDelayT  = input.string(defval = 'nBars',                 title = ' Units', options = ['nBars', 'nSecs', 'nMins', 'nHrs', 'nDays'], inline = '2', tooltip = tooltipD)
inOffset  = input.int   (defval = 7,                       title = 'Extend Right    ',   minval = 0, maxval = 100, step = 1,         inline = '3')
inQueueN  = input.int   (defval = 50,                      title = ' Show ',             minval = 0, maxval = 500, step = 1,         inline = '3', tooltip = tooltipQ)
inColrHi  = input.color (defval = color.new(#ff9800, 0), title = 'Colors Used   Hi',                                               inline = '4')
inColrLo  = input.color (defval = color.new(#00e676, 0), title = 'Lo',                                                             inline = '4')
inColrHL  = input.color (defval = color.new(#9c27b0, 0), title = 'HL',                                                             inline = '4')
inLabels  = input       (defval = true,                    title = 'Show Price Labels',                                              inline = '5')
inPlaced  = input       (defval = false,                   title = 'On Right',                                                       inline = '5')
inColors  = input       (defval = false,                   title = 'Show Pivot Bars',                                                inline = '6')

drawLabl(_offset, _y, _colr, _styl)=> label.new(bar_index + _offset, _y, str.tostring(_y, format.mintick), xloc.bar_index,
 yloc.price, color(na), not _styl ? label.style_none : label.style_label_left, _colr, size.normal, text.align_left)

drawLine(_inset, _offset, _y, _colr, _styl, _wide)=> line.new(bar_index - _inset, _y, bar_index + _offset, _y, xloc.bar_index,
 extend.none, _colr, _styl == 'Dashed' ? line.style_dashed : _styl == 'Dotted' ? line.style_dotted : line.style_solid, _wide)

pivotsHi = ta.pivothigh(high, inBarsL_, inBars_R)                          // hi pivot occurs
pivotsLo = ta.pivotlow(  low, inBarsL_, inBars_R)                          // lo pivot occurs
sourceHi = high[inBars_R]                                                  // price value of pivot when hi pivot is confirmed 
sourceLo =  low[inBars_R]                                                  // price value of pivot when lo pivot is confirmed
isColrHi = inLabels ? inColrHi : na                                        // disable labels with na or use transparent color(na)
isColrLo = inLabels ? inColrLo : na                                        // disable labels with na or use transparent color(na)
delayAmt =
 inDelayT == 'nBars' ? inDelayN + 1     :
 inDelayT == 'nSecs' ? inDelayN * 1e3   :
 inDelayT == 'nMins' ? inDelayN * 6e4 :
 inDelayT == 'nHrs'  ? inDelayN * 3.6e6 : 2.52e7                           // added + 1 to nBars to extend to the open of the Nth + 1 bar
                                                                           // when delay type is nTime convert nSecs to nMilliseconds

type pivType                                                               // declare UD type
    string Type                                                            // declare field for string value of 'h' or 'l'
    float  Valu                                                            // declare field for source value of pivot
    int    BarI                                                            // declare field for bar_index when touched
    int    Time                                                            // declare field for bar time when touched
    bool   Mark                                                            // declare field for flagging touched lines
    label  Labl                                                            // declare field for label of source value
    line   Line                                                            // declare field for line of source value

var pivArray = array.new<pivType>()                                        // declare an array of UD type to hold UDT objects 

if  pivotsHi                                                               // if condition occurs
    piv = pivType.new('h', sourceHi, na, na, false,
     drawLabl(inOffset, sourceHi, isColrHi, inPlaced),
     drawLine(inBars_R, inOffset, sourceHi, inColrHi, inStyles, inWidths)) // create an object of UDT type
    array.push(pivArray, piv)                                              // populate array with element object

if  pivotsLo                                                               // if condition occurs
    piv = pivType.new('l', sourceLo, na, na, false,
     drawLabl(inOffset, sourceLo, isColrLo, inPlaced),
     drawLine(inBars_R, inOffset, sourceLo, inColrLo, inStyles, inWidths)) // create an object of UDT type
    array.push(pivArray, piv)                                              // populate array with element object  

snapShot = timenow                                                         // create a single timestamp for loop, less accurate but more efficient
for i = (array.size(pivArray) == 0 ?
 na : array.size(pivArray) - 1) to 0 by 1                                  // loop backwards through array of objects
    obj = array.get(pivArray, i)                                           // assign the array's element i to arbitrarily named 'obj'
    breaksHi = obj.Type == 'h' and high >= obj.Valu                        // check if pivot hi and touched by high
    breaksLo = obj.Type == 'l' and  low <= obj.Valu                        // check if pivot lo and touched by low
    if obj.Mark == false and (breaksHi or breaksLo)                        // check unflagged (untouched) element objects for source touches
        obj.BarI := bar_index                                              // set element object's BarI field for later evaluating elapsed bars
        obj.Time := timenow                                                // set element object's Time field for later evaluating elapsed time
        obj.Mark := true                                                   // set element object's flag field for later removal once delay has been met
        trgtValu  = str.tostring(obj.Valu, '#.00')                         // format string of pivot level for alert
        srceValu  = str.tostring(close, '#.00')                            // format string of current price for alert
        if breaksHi
            alert(syminfo.prefix + ' ' + syminfo.ticker + ' ' + 
             timeframe.period +
             '\nPrice Touched Pivot Hi\nPrice: ' +
             srceValu + '\nPivot: ' + trgtValu,
             alert.freq_once_per_bar)                                      // alert when pivot hi is touched or broken by high
        if breaksLo
            alert(syminfo.prefix + ' ' + syminfo.ticker + ' ' +
             timeframe.period +
             '\nPrice Touched Pivot Lo\nPivot: ' +
             trgtValu + '\nPrice: ' + srceValu,
             alert.freq_once_per_bar)                                      // alert when pivot lo is touched or broken by low
    if obj.Mark == true and (barstate.ishistory or inDelayN == 0 or
     (inDelayT == 'nBars' and bar_index - obj.BarI >= delayAmt) or
     (inDelayT != 'nBars' and snapShot  - obj.Time >= delayAmt))           // check if flagged touches have met required amount of delay
        label.delete(obj.Labl)                                             // remove stored label drawing from chart
        line.delete(obj.Line)                                              // remove stored line drawing from chart
        array.remove(pivArray, i)                                          // remove touched element object
    label.set_x(obj.Labl, bar_index + inOffset)                            // offset remaining labels for current bar realignment
    line.set_x2(obj.Line, bar_index + inOffset)                            // offset remaining lines for current bar realignment

trashBin = array.size(pivArray) - inQueueN                                 // get current number of items exceeding queue
if trashBin > 0                                                            // if number of elements exceeds queue                   
    for elmnt  = trashBin - 1 to 0 by 1                                    // loop backwards through elements to be trashed 
        getEl  = array.get(pivArray, elmnt)                                // get object element from array
        label.delete(getEl.Labl)                                           // delete drawing before removal of object element
        line.delete(getEl.Line)                                            // delete drawing before removal of object element
        array.remove(pivArray, elmnt)                                      // remove object element from items array

barColor = pivotsHi and pivotsLo ? inColrHL :
 pivotsHi ? inColrHi : pivotsLo ? inColrLo : na                            // assign appropriate color for type of fractal
barcolor(inColors and pivotsHi ? barColor : na, -inBars_R)                 // colorize hi pivot bars
barcolor(inColors and pivotsLo ? barColor : na, -inBars_R)                 // colorize lo pivot bars