FVGs & CEs + Alerts: simple & efficient method
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//FVG indicator: Paints FVGs and their midlines (CEs). Stops painting when CE is hit, or when fully filled; user choice of threshold. This threshold is also used in the Alert conditions.
//Big thank you to @Bjorgum for his fantastic extendAndRemove method. Modified here for use with boxes and to integrate Alerts
//29th June'23 added IOFED alerts (price merely enters FVG above or below)
//5th Nov'23 added simple 'FVG confirmed' alerts
// © twingall

//@version=5
indicator("FVGs & CEs + Alerts: simple method", overlay = true, max_boxes_count = 500, max_lines_count = 500)
numDays = input.int(7, "number of days lookback")
showUP = input.bool(true, "'UP' FVGs:", inline ='1')
colUp = input.color(color.new(color.blue, 86), "", inline ='1')
showDN = input.bool(true, "'DOWN' FVGs:", inline ='2')
colDn = input.color(color.new(color.orange, 86), "", inline ='2')
showCE = input.bool(true, "show CE", inline ='3')
ceCol = input.color(color.new(color.black, 1), "| color:", inline ='3')
ceStyle = input.string(line.style_dotted, "| style:", options=[line.style_dotted,line.style_solid,  line.style_dashed], inline ='3')
deleteFilledBoxes = input.bool(false, "delete filled boxes & lines")
CEcond = input.bool (true, "Use CE (as opposed to Full Fill)", group = 'conditions/alerts', tooltip = "If toggled OFF, FVGs and CEs will paint until FVG has been completely filled.\n\nThis threshold is used for Above/Below threshold Alert conditions too (but does not effect the IOFED alerts):\ni.e. this will determine if your 'ABOVE threshold' alert fires when price hits latest active FVG CE ABOVE or latest active FVG Full Fill ABOVE\n\nAlerts are set by clicking the three dots on the indicator display line.")
colorNone = color.new(color.white, 100)
_day = 24*3600*1000
var box bxUp = na, var box bxDn = na, var line lnUp = na, var line lnDn = na
var array<box> bxUpArr = array.new<box>(0), var array<line> lnUpArr = array.new<line>(0)
var array<box> bxDnArr = array.new<box>(0), var array<line> lnDnArr = array.new<line>(0)
dnCE = high[1] + (low[3]-high[1])/2
upCE = low[1] - (low[1]-high[3])/2
if low[3] > high[1] and time> timenow- numDays*_day and showDN
    bxDnArr.push(box.new(bar_index-3, low[3], bar_index, high[1], bgcolor = colDn, border_color = colorNone))
    lnDnArr.push(line.new(bar_index-3, dnCE, bar_index, dnCE, color = showCE?ceCol:colorNone, style =ceStyle))
if high[3] < low[1] and time> timenow- numDays*_day and showUP
    bxUpArr.push(box.new(bar_index-3, low[1], bar_index, high[3], bgcolor = colUp, border_color = colorNone))
    lnUpArr.push(line.new(bar_index-3, upCE, bar_index, upCE, color = showCE?ceCol:colorNone, style =ceStyle))

var array<int> _countArr =array.new<int>(0)
var array<int> _countArrIOFED =array.new<int>(0)


    //modified form of @Bjorgum's looping function.  This stops boxes/lines painting when price passes to or through them
extendAndRemoveBx(array<box> boxArray, array<line> lineArray, array<int> countArr1, array<int> countArr2, simple bool isBull, int maxSize) => 
    if boxArray.size() > 0
        for i = boxArray.size() -1 to 0
            line ln = lineArray.get(i)
            box bx = boxArray.get(i)
            bx.set_right(bar_index)
            ln.set_x2(bar_index)
            float price = CEcond?ln.get_price(bar_index):(isBull?bx.get_top():bx.get_bottom())    
            float price_IOFED = isBull?bx.get_bottom():bx.get_top()          
            int m = isBull ? 1 : -1
            float hiLo = isBull ? high : low  
            if hiLo * m > price * m 
                boxArray.remove(i)
                lineArray.remove(i)
                countArr1.push(isBull?1:-1) //for 'above/below threshold alerts; counter sum will decrement 1 on lower threshold hit, increment 1 on upper threshold hit
                if deleteFilledBoxes
                    bx.set_bgcolor(colorNone)
                    ln.set_color(colorNone)
            if hiLo*m>price_IOFED*m
                countArr2.push(isBull?1:-1)

    if boxArray.size() > maxSize
        box.delete(boxArray.shift())
        line.delete(lineArray.shift())

extendAndRemoveBx(bxDnArr,lnDnArr,_countArr,_countArrIOFED, true, 12) //12 should be good for around 2200 bars of history
extendAndRemoveBx(bxUpArr, lnUpArr,_countArr,_countArrIOFED, false, 12)

upThresholdLst = array.sum(_countArr)>array.sum(_countArr)[1]
dnThresholdLst = array.sum(_countArr)<array.sum(_countArr)[1]

upIOFEDlast= array.sum(_countArrIOFED)>array.sum(_countArrIOFED)[1]
dnIOFEDlast= array.sum(_countArrIOFED)<array.sum(_countArrIOFED)[1]

alertcondition(upThresholdLst, "ABOVE threshold of latest active Up FVG (CE or fvg High)", "price has crossed threshold of latest active Up FVG")
alertcondition(dnThresholdLst, "BELOW threshold of latest active Down FVG (CE or fvg low)", "price has crossed threshold of latest active Down FVG")

alertcondition(upIOFEDlast, "IOFED into latest active Up FVG", "price has entered latest active UP FVG")
alertcondition(dnIOFEDlast, "IOFED into latest active Down FVG", "price has entered latest active Down FVG")

alertcondition(low[3] > high[1], "Simple alert: Down FVG confirmed", "Down FVG has formed")
alertcondition(high[3] < low[1], "Simple alert: Up FVG confirmed", "Up FVG has formed")