Visible Range Streaks of Unbroken Prior Highs/Lows [vnhilton]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © vnhilton
// ADVICE: Put this indicator visually in front of the price chart, so plots are not hidden from behind

//@version=5
indicator("Visible Range Streaks of Unbroken Prior Highs/Lows [vnhilton]", "VRSOUP H/L", true, max_lines_count = 500, max_labels_count = 500)

//Parameters
showLowPlot       = input.bool(true, "",                                                         "These will override the other toggles",                                                         "Main Plot Settings",         "Main Settings")
showhighPlot      = input.bool(true, "Show Low/High Plots?",                                                                                                                             inline = "Main Plot Settings", group = "Main Settings")
minNumber         = input.int(3,     "Minimum Number of Unbroken Prior Highs/Lows", 0, tooltip = "To start displaying streaks (streak breaks lower than this number will not be shown)",                                group = "Main Settings")

showText          = input.bool(true, "",                                                                                                     inline = "Content Settings",  group = "Toggle Settings")
showNumbers       = input.bool(true, "Show Text/Numbers?",                                                                                   inline = "Content Settings",  group = "Toggle Settings")
showBreakShape    = input.bool(true, "Show Break Shape?",     "This is the shape placed at the location where the prior high/low is broken",                               group = "Toggle Settings")
showZigZag        = input.bool(true, "Show Zig Zag?",         "Draws lines between break shapes",                                                                          group = "Toggle Settings")

lineThickness     = input.int(2, "Zig Zag Line Width", 1,                                      group = "Zig Zag Settings")
lineStyle         = input.string("Solid", "Zig Zag Line Style", ["Dashed", "Dotted", "Solid"], group = "Zig Zag Settings")

sellSignalText    = input.string("SELL", "Prior Low/High Break Text", inline = "Signal Settings", group = "Text Settings")
buySignalText     = input.string("BUY",  "",                          inline = "Signal Settings", group = "Text Settings")

labelSize         = input.string("Auto",               "Label/Break Shape Size",           ["Auto", "Tiny", "Small", "Normal", "Large", "Huge"],                                                                                                                                                                                                                                                                     inline = "Size Settings",   group = "Label Settings")
shapeSize         = input.string("Auto",               "",                                 ["Auto", "Tiny", "Small", "Normal", "Large", "Huge"],                                                                                                                                                                                                                                                                     inline = "Size Settings",   group = "Label Settings")
sellNumberLabel   = input.string("Label Text Outline", "Low/High Numbers Label Type",      ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond", "Label Text Outline"], inline = "Number Settings", group = "Label Settings")
buyNumberLabel    = input.string("Label Text Outline", "",                                 ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond", "Label Text Outline"], inline = "Number Settings", group = "Label Settings")
sellSignalLabel   = input.string("Label Text Outline", "Low/High Streak Break Label Type", ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond", "Label Text Outline"], inline = "Break Settings",  group = "Label Settings")
buySignalLabel    = input.string("Label Text Outline", "",                                 ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond", "Label Text Outline"], inline = "Break Settings",  group = "Label Settings")
sellShapeLabel    = input.string("Label Center",       "Break Sell/Buy Shape Type",        ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond"],                       inline = "Break Settings",  group = "Label Settings")
buyShapeLabel     = input.string("Label Center",       "",                                 ["None", "X-Cross", "Cross", "Triangle Up", "Triangle Down", "Flag", "Circle", "Arrow Up", "Arrow Down", "Label Up", "Label Down", "Label Left", "Label Right", "Label Lower Left", "Label Lower Right", "Label Upper Left", "Label Upper Right", "Label Center", "Label Square", "Label Diamond"],                       inline = "Break Settings",  group = "Label Settings")


unbrokenLowColor    = input.color(color.red,                   "Unbroken Low/High Numbers Color",  inline = "Number Color Settings",      group = "Color Settings")
unbrokenHighColor   = input.color(color.green,                 "",                                 inline = "Number Color Settings",      group = "Color Settings")
sellSignalColor     = input.color(color.red,                   "Prior Low/High Break Text Color",  inline = "Signal Color Settings",      group = "Color Settings")
buySignalColor      = input.color(color.green,                 "",                                 inline = "Signal Color Settings",      group = "Color Settings")
labelLowColor       = input.color(color.new(color.red,   100), "Unbroken Low/High Label Color",    inline = "Label Color Settings",       group = "Color Settings")
labelHighColor      = input.color(color.new(color.green, 100), "",                                 inline = "Label Color Settings",       group = "Color Settings")
labelLowBrokeColor  = input.color(color.new(color.red,   100), "Broken Low/High Label Color",      inline = "Label Broke Color Settings", group = "Color Settings")
labelHighBrokeColor = input.color(color.new(color.green, 100), "",                                 inline = "Label Broke Color Settings", group = "Color Settings")
sellShapeColor      = input.color(color.new(color.red,   50),  "Prior Low/High Break Shape Color", inline = "Shape Color Settings",       group = "Color Settings")
buyShapeColor       = input.color(color.new(color.green, 50),  "",                                 inline = "Shape Color Settings",       group = "Color Settings")
upLineColor         = input.color(color.new(color.green, 50),  "Up/Down Zig Zag Line Color",       inline = "Line Color Settings",        group = "Color Settings")
downLineColor       = input.color(color.new(color.red,   50),  "",                                 inline = "Line Color Settings",        group = "Color Settings")

//Label Size Selection
LabelSizing(choice) =>
    switch
        choice == "Auto"   => size.auto
        choice == "Tiny"   => size.tiny
        choice == "Small"  => size.small
        choice == "Normal" => size.normal
        choice == "Large"  => size.large
        choice == "Huge"   => size.huge

chosenLabelSize = LabelSizing(labelSize)
chosenShapeSize = LabelSizing(shapeSize)

//Label Type Selection
LabelType(choice) =>
    switch
        choice == "None"               => label.style_none
        choice == "X-Cross"            => label.style_xcross
        choice == "Cross"              => label.style_cross
        choice == "Triangle Up"        => label.style_triangleup
        choice == "Triangle Down"      => label.style_triangledown
        choice == "Flag"               => label.style_flag
        choice == "Circle"             => label.style_circle
        choice == "Arrow Up"           => label.style_arrowup
        choice == "Arrow Down"         => label.style_arrowdown
        choice == "Label Up"           => label.style_label_up
        choice == "Label Down"         => label.style_label_down
        choice == "Label Left"         => label.style_label_left
        choice == "Label Right"        => label.style_label_right
        choice == "Label Lower Left"   => label.style_label_lower_left
        choice == "Label Lower Right"  => label.style_label_lower_right
        choice == "Label Upper Left"   => label.style_label_upper_left
        choice == "Label Upper Right"  => label.style_label_upper_right
        choice == "Label Center"       => label.style_label_center
        choice == "Label Square"       => label.style_square
        choice == "Label Diamond"      => label.style_diamond
        choice == "Label Text Outline" => label.style_text_outline
        
chosenSellNumberLabel = LabelType(sellNumberLabel)
chosenBuyNumberLabel  = LabelType(buyNumberLabel)
chosenSellSignalLabel = LabelType(sellSignalLabel)
chosenBuySignalLabel  = LabelType(buySignalLabel)
chosenSellShapeLabel  = LabelType(sellShapeLabel)
chosenBuyShapeLabel   = LabelType(buyShapeLabel)

//Line Style Selection
LineStyling(choice) =>
    switch lineStyle
        "Dashed"      => line.style_dashed
        "Dotted"      => line.style_dotted
        "Solid"       => line.style_solid

chosenLineStyle = LineStyling(lineStyle)

//Variables
var int     lows      = 0
var int     lastLows  = 0
var float   prevLow   = na
var int     highs     = 0
var int     lastHighs = 0
var float   prevHigh  = na
var float[] coords    = array.new<float>()

//Visible Chart Times
leftTime  = chart.left_visible_bar_time
rightTime = chart.right_visible_bar_time

//Plots Function
Plots(streak, prevStreak, prevValue, ylock) =>
    if streak > 0
        if streak >= minNumber and showNumbers          and ((ylock == yloc.abovebar and showLowPlot) or (ylock == yloc.belowbar and showhighPlot))
            label.new(bar_index, na, str.tostring(streak), yloc = ylock, color = ylock == yloc.abovebar ? labelLowColor : labelHighColor, style = ylock == yloc.abovebar ? chosenSellNumberLabel : chosenBuyNumberLabel, textcolor = ylock == yloc.abovebar ? unbrokenLowColor : unbrokenHighColor, size = chosenLabelSize)
    else
        if prevStreak != 0 and prevStreak >= minNumber  and ((ylock == yloc.abovebar and showLowPlot) or (ylock == yloc.belowbar and showhighPlot))
            if showText
                label.new(bar_index, na, ylock == yloc.abovebar ? sellSignalText : buySignalText, yloc = ylock, color = ylock == yloc.abovebar ? labelLowBrokeColor : labelHighBrokeColor, style = ylock == yloc.abovebar ? chosenSellSignalLabel : chosenBuySignalLabel, textcolor = ylock == yloc.abovebar ? sellSignalColor : buySignalColor, size = chosenLabelSize)

            if showZigZag
                array.push(coords, float(bar_index))
                array.push(coords, prevValue)

                if array.size(coords) == 4
                    line.new(int(array.get(coords, 0)), array.get(coords, 1), int(array.get(coords, 2)), array.get(coords, 3), color = array.get(coords, 3) - array.get(coords, 1) >= 0 ? upLineColor : downLineColor, style = chosenLineStyle, width = lineThickness)

                    array.set(coords, 0, array.get(coords, 2)) 
                    array.set(coords, 1, array.get(coords, 3))

                    array.remove(coords, 3)
                    array.remove(coords, 2)

            if showBreakShape
                label.new(bar_index, prevValue, color = ylock == yloc.abovebar ? sellShapeColor : buyShapeColor, style = ylock == yloc.abovebar ? chosenSellShapeLabel : chosenBuyShapeLabel, size = chosenShapeSize)

//Streak Logic
if ((barstate.isconfirmed and timeframe.isintraday) or (not timeframe.isintraday and not barstate.isrealtime)) and time >= leftTime and time <= rightTime    
    if bar_index != 0
        if close >= prevLow
            lows     += 1
        else
            lastLows := lows
            lows     := 0

        Plots(lows,  lastLows,  prevLow,  yloc.abovebar)
        
        if close <= prevHigh
            highs     += 1
        else
            lastHighs := highs
            highs     := 0

        Plots(highs, lastHighs, prevHigh, yloc.belowbar)
    
    prevLow  := low
    prevHigh := high