ICT Fair Value Gap [LM]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © lmatl

//@version=5
indicator("ICT Fair Value Gap [LM]", "ICT FVG [LM]", overlay=true)

// atr settings
i_atrLength = input.int(28, 'ATR MA length', group='general settings')
i_atrMultiplier = input.float(1.5, 'ATR multiplier', group='general settings')

// box settings
i_boxCount = input.int(5, 'Box count', minval=1, group='box settings')
i_upCandleBoxColor = input.color(color.green, 'Up candle box color', group='box settings')
i_downCandleBoxColor = input.color(color.red, 'Down candle box color', group='box settings')
i_shrinkBoxes = input(false, 'Shrink where PA touches', group='box settings')

upBoxColor = color.new(i_upCandleBoxColor, 70)
downBoxColor = color.new(i_downCandleBoxColor, 70)

var boxArray = array.new_box()


f_isUpCandle(_index) =>
    open[_index] <= close[_index]


f_isFairValueGap() =>
    bool bearCondition = close[3] <= high[2] and close[1] <= close[2] and high < low[2]
    bool bullCondition = close[3] >= low[2] and close[1] >= close[2] and low > high[2]
    
    priceDiff = high[1] - low[1]
    atrValue = ta.atr(i_atrLength)
    bool middleCandleVolatilityCondition = priceDiff > atrValue * i_atrMultiplier
    
    bool isUpCandle = f_isUpCandle(1)
    bool isGapClosed = isUpCandle ? high[2] >= low : low[2] <= high
    [isGapClosed, bearCondition, bullCondition, middleCandleVolatilityCondition]


f_extendArray(_boxArray) =>
    if array.size(_boxArray) > 0
        for i = array.size(_boxArray) - 1 to 0 by 1
            boxToExtend = array.get(_boxArray, i)

            bottom = box.get_bottom(boxToExtend)
            top = box.get_top(boxToExtend)
            right = box.get_right(boxToExtend)
            box.set_right(array.get(_boxArray, i), bar_index)
            
            hasClosed = (open <= bottom and high > bottom and high >= top or open >= top and low < top and low <= bottom)

            if i_shrinkBoxes and open >= top and low < top and low > bottom
                box.set_top(array.get(_boxArray, i), low)

            if i_shrinkBoxes and open <= bottom and high > bottom and high < top
                box.set_bottom(array.get(_boxArray, i), high)
                
            if (hasClosed)
                alert('box has closed')

            if i_shrinkBoxes and hasClosed
                box.delete(array.get(_boxArray, i))
                array.remove(boxArray, i)

[isGapClosed, bearCondition, bullCondition, middleCandleVolatilityCondition] = f_isFairValueGap()

bool isFairValueGap = false
isFairValueGap := (bearCondition or bullCondition) and not isGapClosed and middleCandleVolatilityCondition and not isFairValueGap[1]

if isFairValueGap
    isUpCandle = f_isUpCandle(1)
    top = isUpCandle ? low : low[2]
    bottom = isUpCandle ? high[2] : high
    fvgBox = box.new(bar_index[1], top, bar_index, bottom, bgcolor=isUpCandle ? upBoxColor : downBoxColor, border_style=line.style_dashed, border_color=isUpCandle ? upBoxColor : downBoxColor, xloc=xloc.bar_index)
    
    if array.size(boxArray) >= i_boxCount
        box.delete(array.shift(boxArray))
    array.push(boxArray, fvgBox)

f_extendArray(boxArray)


