PhantomFlow RangeDetector
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © mxrshxl_trxde

//@version=5
indicator("PhantomFlow RangeDetector", "PhantomFlow RangeDetector", true, max_boxes_count = 500)

n = input.int(2, title='Min. bars for range')
coefZone = 4
// all_color = input.color(title='All colors', defval = color.rgb(0, 255, 234, 80), group="Visual Settings", inline = 'bg_ltf_color_liquidity')
color_up = input.color(title='Background UP', defval = color.rgb(255, 0, 0, 80), group="Visual Settings", inline = 'bg_ltf_color_liquidity')
color_down = input.color(title='Background DOWN', defval = color.rgb(0, 255, 0, 80), group="Visual Settings", inline = 'bg_ltf_color_liquidity')
// color_up_setup = input.color(title='Background UP Setup', defval = color.rgb(255, 0, 234, 80), group="Visual Settings", inline = 'bg_ltff_color_liquidity')
// color_down_setup = input.color(title='Background DOWN Setup', defval = color.rgb(0, 225, 255, 80), group="Visual Settings", inline = 'bg_ltff_color_liquidity')

type zone
    int startBar
    int endBar
    float priceHigh
    float priceLow
    bool isChained
    bool isDropZone
    bool isActive
    bool isBuildZone
    string typeSort
    box bx
    bool isSetup

var array<zone> zones_all = array.new<zone>()
var array<zone> chain_zones_all = array.new<zone>()
var array<float> sized_zones = array.new<float>()

if (array.size(zones_all) != 0)
    zone lastZone = array.last(zones_all)
    if lastZone.priceLow > close or lastZone.priceHigh < close
        if (lastZone.endBar - lastZone.startBar != 1 and lastZone.endBar - lastZone.startBar >= n and not lastZone.isDropZone)
            lastZone.isChained := true
            array.push(sized_zones, lastZone.priceHigh - lastZone.priceLow)

            if (array.size(sized_zones) > 5)
                array.shift(sized_zones)
        else
            if not na(lastZone.bx)
                box.delete(lastZone.bx)
            array.remove(zones_all, array.indexof(zones_all, lastZone))

if high[1] >= close and high[1] >= open and low[1] <= close and low[1] <= open
    isContinous = true
    if (array.size(zones_all) != 0)
        zone lastZone = array.last(zones_all)
        if not lastZone.isChained
            if lastZone.priceHigh >= close and lastZone.priceHigh >= close[1] and lastZone.priceHigh >= open and lastZone.priceHigh >= open[1] and lastZone.priceLow <= close and lastZone.priceLow <= close[1] and lastZone.priceLow <= open and lastZone.priceLow <= open[1]
                lastZone.endBar := bar_index
                isContinous := false
                if not na(lastZone.bx)
                    box.delete(lastZone.bx)
                // lastZone.bx := box.new(lastZone.startBar, lastZone.priceHigh, lastZone.endBar, lastZone.priceLow, border_color = all_color, bgcolor = all_color)
            else 
                if lastZone.endBar - lastZone.startBar == 1 or lastZone.endBar - lastZone.startBar < n
                    if not na(lastZone.bx)
                        box.delete(lastZone.bx)
                    array.remove(zones_all, array.indexof(zones_all, lastZone))
                else
                    lastZone.isChained := true
                    array.push(sized_zones, lastZone.priceHigh - lastZone.priceLow)
                    if (array.size(sized_zones) > 5)
                        array.shift(sized_zones)

        if lastZone.isChained and not lastZone.isBuildZone
            lastZone.isDropZone := true
            if not na(lastZone.bx)
                box.delete(lastZone.bx)
                
    if array.size(sized_zones) != 0 and isContinous
        if (high[1] - low[1]) / array.avg(sized_zones) > coefZone
            array.push(sized_zones, high[1] - low[1])

            if (array.size(sized_zones) > 5)
                array.shift(sized_zones)
            isContinous := false
        else
            array.push(zones_all, zone.new(bar_index - 1, bar_index, high[1], low[1], false, false, false, false, na, na, false))
    
    if array.size(sized_zones) == 0 and isContinous
        array.push(zones_all, zone.new(bar_index - 1, bar_index, high[1], low[1], false, false, false, false, na, na, false))

if array.size(zones_all) != 0
    zone lastZone = array.last(zones_all)

    if not lastZone.isChained and array.size(zones_all) > 1
        lastZone := array.get(zones_all, array.size(zones_all) - 2)

    if lastZone.isChained and not lastZone.isBuildZone and not lastZone.isDropZone
        if high > lastZone.priceHigh and close > lastZone.priceHigh
            if lastZone.endBar - lastZone.startBar >= n
                lastZone.isBuildZone := true
                lastZone.isActive := true
                lastZone.typeSort := 'long'
                array.push(chain_zones_all, lastZone)
                if not na(lastZone.bx)
                    box.delete(lastZone.bx)
                lastZone.bx := box.new(lastZone.startBar, lastZone.priceHigh, lastZone.endBar, lastZone.priceLow, border_color = color_down, bgcolor = color_down)
            else
                lastZone.isDropZone := true
                if not na(lastZone.bx)
                    box.delete(lastZone.bx)
        if low < lastZone.priceLow and close < lastZone.priceLow
            if lastZone.endBar - lastZone.startBar >= n
                lastZone.isBuildZone := true
                lastZone.isActive := true
                lastZone.typeSort := 'short'
                array.push(chain_zones_all, lastZone)
                if not na(lastZone.bx)
                    box.delete(lastZone.bx)
                lastZone.bx := box.new(lastZone.startBar, lastZone.priceHigh, lastZone.endBar, lastZone.priceLow, border_color = color_up, bgcolor = color_up)
            else
                lastZone.isDropZone := true
                if not na(lastZone.bx)
                    box.delete(lastZone.bx)

// if array.size(zones_all) > 2
//     zone lastZone = array.last(zones_all)
//     zone prevZone = array.get(zones_all, array.size(zones_all) - 2)
//     zone prevPrevZone = array.get(zones_all, array.size(zones_all) - 3)

//     if lastZone.isBuildZone and prevZone.isBuildZone and prevPrevZone.isBuildZone
//         if lastZone.typeSort == "short" and prevZone.typeSort == "short" and prevPrevZone.typeSort == "long"
//             if prevPrevZone.priceHigh >= prevZone.priceLow and prevPrevZone.priceHigh < prevZone.priceHigh and lastZone.priceHigh >= prevZone.priceLow and lastZone.priceHigh < prevZone.priceHigh
//                 lastZone.isSetup := true
//                 if not na(lastZone.bx)
//                     box.delete(lastZone.bx)
//                 lastZone.bx := box.new(lastZone.startBar, lastZone.priceHigh, lastZone.endBar, lastZone.priceLow, border_color = color_up_setup, bgcolor = color_up_setup)
//         if lastZone.typeSort == "long" and prevZone.typeSort == "long" and prevPrevZone.typeSort == "short"
//             if prevPrevZone.priceLow <= prevZone.priceHigh and prevPrevZone.priceLow > prevZone.priceLow and lastZone.priceLow <= prevZone.priceHigh and lastZone.priceLow > prevZone.priceLow
//                 lastZone.isSetup := true
//                 if not na(lastZone.bx)
//                     box.delete(lastZone.bx)
//                 lastZone.bx := box.new(lastZone.startBar, lastZone.priceHigh, lastZone.endBar, lastZone.priceLow, border_color = color_down_setup, bgcolor = color_down_setup)