Gann Fan
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LonesomeTheBlue

//@version=5
indicator("Gann Fan", overlay = true, max_bars_back = 400)
// get colors into the array
var color [] colors = array.from(
 input(defval = color.red, title = "1/2", inline = "c1", group = "colors"),
 input(defval = color.green, title = "1/3", inline = "c1", group = "colors"),
 input(defval = #0097a7, title = "1/", inline = "c1", group = "colors"),
 input(defval = color.olive, title = "1/8", inline = "c1", group = "colors"),
 input(defval = color.teal, title = "2/1", inline = "c2", group = "colors"),
 input(defval = #9598a1, title = "3/1", inline = "c2", group = "colors"),
 input(defval = color.yellow, title = "4/1", inline = "c2", group = "colors"),
 input(defval = #a5d6a7, title = "8/1", inline = "c2", group = "colors"),
 input(defval = color.blue, title = "1/1", inline = "c3", group = "colors"))
//Transparency or the bg colors
transp = input.int(defval = 80, title = "Transparency", minval = 0, maxval = 100, tooltip = "Possible values are from 0 (not transparent) to 100 (invisible)")
// labels
showlabels = input.bool(defval = true, title = "Labels", inline = "labels")
labelloc = input.int(defval = 50, title = "|  Label location", minval = 1, maxval = 300, inline = "labels")
lstyle1i = input.string(defval = 'Solid', title = "Line Style", options = ['Solid', 'Dashed', 'Dotted'], inline = "lstyle")
lstyle2i = input.string(defval = 'Dashed', title = "", options = ['Solid', 'Dashed', 'Dotted'], inline = "lstyle")
loopbackprd = input.int(defval = 280, title = "Loopback Period", tooltip = "Loopback period to search Highest/Lowest Pivot Points")
pperiod = input.int(defval = 5, title = "Pivot Period")

// calculate middle line, using this line we will calculate other lines
float l_ = ta.lowest(292)
float h_ = ta.highest(292)
float perc = (h_ - l_) /82
float hh = h_ + perc * 10
float ll = l_ - perc * 8
float middle = (hh - ll) / 131.0
var lstyle1 = lstyle1i == 'Solid' ? line.style_solid : lstyle1i == 'Dashed' ? line.style_dashed : line.style_dotted
var lstyle2 = lstyle2i == 'Solid' ? line.style_solid : lstyle2i == 'Dashed' ? line.style_dashed : line.style_dotted
// levels
var divs = array.from(2.0, 3.0, 4.0, 8.0)

// remove old pivot points
remove_old_pivots(pval, ploc)=>
    for x = array.size(ploc) - 1 to 0
        if bar_index - array.get(ploc, x) > loopbackprd
            array.pop(ploc)
            array.pop(pval)
            
// get highest/lowest locations
var pivothighs = array.new_float(0, na)
var pivothighsloc = array.new_int(0, na)
var pivotlows = array.new_float(0, na)
var pivotlowsloc = array.new_int(0, na)
ph = ta.pivothigh(pperiod, pperiod)
pl = ta.pivotlow(pperiod, pperiod)
if ph
    array.unshift(pivothighsloc, bar_index - pperiod)
    array.unshift(pivothighs, ph)
    remove_old_pivots(pivothighs, pivothighsloc)
if pl
    array.unshift(pivotlowsloc, bar_index - pperiod)
    array.unshift(pivotlows, pl)
    remove_old_pivots(pivotlows, pivotlowsloc)

l_bar_index = array.size(pivotlowsloc) > 0 ? array.get(pivotlowsloc, array.lastindexof(pivotlows, array.min(pivotlows))) : na
h_bar_index = array.size(pivothighsloc) > 0 ? array.get(pivothighsloc, array.lastindexof(pivothighs, array.max(pivothighs))) : na
lwest = low[bar_index - l_bar_index]
hghest = high[bar_index - h_bar_index]

// get line style
get_style(x)=>
    x == 1 ? l_bar_index <= h_bar_index ? lstyle1 : lstyle2 : l_bar_index > h_bar_index ? lstyle1 : lstyle2

// remove old lines/labels/linefills
remove_old_lll(lines, labels, linefills)=>
    for x = 0 to array.size(lines) - 1
        line.delete(array.get(lines, x))
    for x = 0 to array.size(labels) - 1
        label.delete(array.get(labels, x))
    for x = 0 to array.size(linefills) - 1
        linefill.delete(array.get(linefills, x))

// get bar_index using y location and start point
line_get_bar_index(gline, x_bar_index, yloc, mult)=>
    ystart = line.get_price(gline, x_bar_index)
    slope = math.abs(ystart - line.get_price(gline, x_bar_index + 1))
    int ret = x_bar_index + math.floor((yloc - ystart) / slope) * mult

// draw fan lines from lowest
if l_bar_index > 0 and l_bar_index < bar_index
    var uplines = array.new_line(9, na)
    var ulinefill = array.new_linefill(8, na)
    var labels = array.new_label(9, na)
    remove_old_lll(uplines, labels, ulinefill)
    
    // draw 1/1 line, show its label and keep its y level
    array.set(uplines, 0, line.new(x1 = l_bar_index, y1 = lwest, x2 = l_bar_index + 1, y2 = lwest + middle, extend = extend.right, color = array.get(colors, 8), style = get_style(1)))
    xloc = l_bar_index + labelloc
    yloc11 = line.get_price(array.get(uplines, 0), xloc)
    if showlabels
        array.set(labels, 0, label.new(x = xloc, y = yloc11, text = "1/1", textcolor = array.get(colors, 8), style = label.style_none))
    
    // draw other fan lines, labels, linefills 
    for x = 0 to 3
        array.set(uplines, x + 1, line.new(x1 = l_bar_index, y1 = lwest, x2 = bar_index, y2 = lwest + (line.get_price(array.get(uplines, 0), bar_index) - lwest) / array.get(divs,x), 
                                           extend = extend.right, 
                                           color = array.get(colors, x), 
                                           style = get_style(1)))
        array.set(ulinefill, x, linefill.new(array.get(uplines, x + 1), array.get(uplines, x), color.new(array.get(colors, x), transp)))
        if showlabels
            yloc = line.get_price(array.get(uplines, x + 1), xloc)
            array.set(labels, x + 1, label.new(x = xloc, y = yloc, text = "1/" + str.tostring(array.get(divs, x)), textcolor = array.get(colors, x), style = label.style_none))
        
        array.set(uplines, x + 5, line.new(x1 = l_bar_index, y1 = lwest, x2 = l_bar_index + 1, y2 = lwest + middle * array.get(divs,x), 
                                           extend = extend.right, 
                                           color = array.get(colors, x + 4), 
                                           style = get_style(1)))
        array.set(ulinefill, x + 4, linefill.new(array.get(uplines, x + 5), array.get(uplines, x == 0 ? 0 : x + 4), color.new(array.get(colors, x + 4), transp)))
        if showlabels
            xlocc = line_get_bar_index(array.get(uplines, x + 5), l_bar_index, yloc11, 1)
            array.set(labels, x + 5, label.new(x = xlocc, y = yloc11, text = str.tostring(array.get(divs, x)) + "/1", textcolor = array.get(colors, x + 4), style = label.style_none))

// draw fan lines from highest      
if h_bar_index > 0 and h_bar_index < bar_index
    var downlines = array.new_line(9, na)
    var labels = array.new_label(9, na)
    var dlinefill = array.new_linefill(8, na)
    remove_old_lll(downlines, labels, dlinefill)
    
    // draw 1/1 line, show its label and keep its y level
    array.set(downlines, 0, line.new(x1 = h_bar_index, y1 = hghest, x2 = h_bar_index + 1, y2 = hghest - middle, extend = extend.right, color = array.get(colors, 8), style = get_style(2)))
    xloc = h_bar_index + labelloc
    yloc11 = line.get_price(array.get(downlines, 0), xloc)
    if showlabels
        array.set(labels, 0, label.new(x = xloc, y = yloc11, text = "1/1", textcolor = array.get(colors, 8), style = label.style_none))
    
    // draw other fan lines, labels, linefills
    for x = 0 to 3
        array.set(downlines, x + 1, line.new(x1 = h_bar_index, y1 = hghest, x2 = bar_index, y2 = hghest - (hghest - line.get_price(array.get(downlines, 0), bar_index)) / array.get(divs,x), 
                                             extend = extend.right, 
                                             color = array.get(colors, x + 4), 
                                             style = get_style(2)))
        array.set(dlinefill, x, linefill.new(array.get(downlines, x + 1), array.get(downlines, x), color.new(array.get(colors, x + 4), transp)))
        if showlabels
            yloc = line.get_price(array.get(downlines, x + 1), xloc)
            array.set(labels, x + 1, label.new(x = xloc, y = yloc, text = str.tostring(array.get(divs, x)) + "/1", textcolor = array.get(colors, x + 4), style = label.style_none))
            
        array.set(downlines, x + 5, line.new(x1 = h_bar_index, y1 = hghest, x2 = h_bar_index + 1, y2 = hghest - middle * array.get(divs,x), 
                                             extend = extend.right, 
                                             color = array.get(colors, x), 
                                             style = get_style(2)))
        array.set(dlinefill, x + 4, linefill.new(array.get(downlines, x + 5), array.get(downlines, x == 0 ? 0 : x + 4), color.new(array.get(colors, x), transp)))
        if showlabels
            xlocc = line_get_bar_index(array.get(downlines, x + 5), h_bar_index, yloc11, -1)
            array.set(labels, x + 5, label.new(x = xlocc, y = yloc11, text = "1/" + str.tostring(array.get(divs, x)), textcolor = array.get(colors, x), style = label.style_none))
