PhantomFlow AccumulationDetector
// 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(title = "PhantomFlow AccumulationDetector", shorttitle = "PhantomFlow AccumulationDetector", overlay = true, max_boxes_count = 500, max_labels_count = 500, max_lines_count = 500)

tf = input.timeframe('60', title='Timeframe')
bg_poc_color = input.color(title='Background', defval = color.rgb(7, 34, 237, 80), group="Visual Settings", inline = 'bg_ltf_color_liquidity')
va_percent = 70.
cnum = 100

type period_data
    float vah
    float poc
    float val
    float high_
    float low_
    int start_bar
    int end_bar

var array<period_data> periods_data = array.new<period_data>()

ltf = timeframe.period
ltf_seconds = timeframe.in_seconds(ltf) / 60
htf_seconds = timeframe.in_seconds(tf) / 60

num_bars_ltf = htf_seconds / ltf_seconds

is_new_bar(t) =>
    ta.change(time(t)) != 0

get_vol(y11, y12, y21, y22, height, vol) =>
    nz(math.max(math.min(math.max(y11, y12), math.max(y21, y22)) - math.max(math.min(y11, y12), math.min(y21, y22)), 0) * vol / height)

levels = array.new_float(cnum + 1)
totalvols = array.new_float(cnum, 0.)

d_switch = is_new_bar(tf)

var top = 0.0
var bot = 0.0

if not d_switch
    if top < high
        top := high
    if bot > low
        bot := low

calculateLevels(int bar_end, int bar_start) =>
    step = (top - bot) / cnum
    for x = 0 to cnum by 1
        array.set(levels, x, bot + step * x)

if d_switch
    bar_end = bar_index - 1
    bar_start = bar_index - num_bars_ltf
    volumes = array.new_float(cnum * 2, 0.)
    all_bars = bar_end - bar_start
    calculateLevels(bar_end, bar_start)
    for bars = 1 to num_bars_ltf by 1
        body_top = math.max(close[bars], open[bars])
        body_bot = math.min(close[bars], open[bars])
        itsgreen = close[bars] >= open[bars]

        topwick = high[bars] - body_top
        bottomwick = body_bot - low[bars]
        body = body_top - body_bot

        bodyvol = body * volume[bars] / (2 * topwick + 2 * bottomwick + body)
        topwickvol = 2 * topwick * volume[bars] / (2 * topwick + 2 * bottomwick + body)
        bottomwickvol = 2 * bottomwick * volume[bars] / (2 * topwick + 2 * bottomwick + body)
        for x = 0 to cnum - 1 by 1
            array.set(volumes, x, array.get(volumes, x) + (itsgreen ? get_vol(array.get(levels, x), array.get(levels, x + 1), body_bot, body_top, body, bodyvol) : 0) + get_vol(array.get(levels, x), array.get(levels, x + 1), body_top, high[bars], topwick, topwickvol) / 2 + get_vol(array.get(levels, x), array.get(levels, x + 1), body_bot, low[bars], bottomwick, bottomwickvol) / 2)
            array.set(volumes, x + cnum, array.get(volumes, x + cnum) + (itsgreen ? 0 : get_vol(array.get(levels, x), array.get(levels, x + 1), body_bot, body_top, body, bodyvol)) + get_vol(array.get(levels, x), array.get(levels, x + 1), body_top, high[bars], topwick, topwickvol) / 2 + get_vol(array.get(levels, x), array.get(levels, x + 1), body_bot, low[bars], bottomwick, bottomwickvol) / 2)

    for x = 0 to cnum - 1 by 1
        array.set(totalvols, x, array.get(volumes, x) + array.get(volumes, x + cnum))

    int poc = array.indexof(totalvols, array.max(totalvols))

    totalmax = array.sum(totalvols) * va_percent / 100.
    va_total = array.get(totalvols, poc)
    int up = poc
    int down = poc
    for x = 0 to cnum - 1 by 1
        above = 0.0
        if up + 1 < array.size(totalvols) - 1
            above += nz(array.get(totalvols, up + 1), 0.0)
            above
        if up + 2 < array.size(totalvols) - 1
            above += nz(array.get(totalvols, up + 2), 0.0)
            above

        below = 0.0
        if down - 1 > 0
            below += nz(array.get(totalvols, down - 1), 0.0)
            below
        if down - 2 > 0
            below += nz(array.get(totalvols, down - 2), 0.0)
            below

        if above > below
            up := math.min(up + 2, array.size(totalvols) - 1)
            va_total += above
            va_total
        else
            down := math.max(down - 2, 0)
            va_total += below
            va_total

        if va_total >= totalmax or down <= 0 and up >= array.size(totalvols) - 1
            break

    maxvol = array.max(totalvols)
    for x = 0 to cnum * 2 - 1 by 1
        array.set(volumes, x, array.get(volumes, x) * all_bars / (3 * maxvol))
    
    poc_level = (array.get(levels, poc) + array.get(levels, poc + 1)) / 2
    val_level = (array.get(levels, down) + array.get(levels, down + 1)) / 2
    vah_level = (array.get(levels, up) + array.get(levels, up + 1)) / 2
    
    array.push(periods_data, period_data.new(vah_level, poc_level, val_level, top, bot, bar_start, bar_end))

    // box first_box = box.new(bar_start, val_level, bar_end, vah_level, border_color = color.rgb(0, 0, 0, 100), bgcolor = bg_poc_color)
    // box second_box = box.new(bar_start, top, bar_end, vah_level, border_color = color.rgb(0, 0, 0, 100), bgcolor = bg_vah_color)
    // box three_box = box.new(bar_start, bot, bar_end, val_level, border_color = color.rgb(0, 0, 0, 100), bgcolor = bg_val_color)

if d_switch and array.size(periods_data) != 1
    period_data prev_period = array.get(periods_data, array.size(periods_data) - 2)
    period_data last_period = array.last(periods_data)

    if prev_period.vah > last_period.poc and last_period.poc > prev_period.val and prev_period.start_bar != 0
        box.new(prev_period.start_bar, math.max(prev_period.vah, last_period.vah), last_period.end_bar, math.min(prev_period.val, last_period.val), border_color = bg_poc_color, bgcolor = bg_poc_color)

if d_switch
    bot := low
    top := high