CDOI Profile
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Dereek69

//@version=5
indicator('CDOI Profile', shorttitle='CDOI', overlay=true, max_boxes_count=500)

//-----------------------------------------------------------//
//---------------------- FUNCTIONS --------------------------//
//-----------------------------------------------------------//

endIf() =>
    line.delete(na)

//
initializeVP(step_size, array_l, array_h) =>  // Creates the arrays without adding any volume to it
    min = array.min(array_l)
    max = array.max(array_h)

    start = min - min % step_size
    end = max + step_size - max % step_size
    steps = (end - start) / step_size
    out_lvls = array.new_float(math.round(steps) + 1)

    for n = 0 to array.size(out_lvls) - 1 by 1
        array.set(out_lvls, n, start + step_size * n)

    out_v = array.new_float(array.size(out_lvls) - 1, 0)

    [out_v, out_lvls]

//
adaptVP(step_size, in_v, in_lvls, array_l, array_h) =>  //rescales the arrays for the L and H provided and keeps the old Volume values

    out_v = in_v
    out_lvls = in_lvls

    min = array.min(array_l)
    max = array.max(array_h)

    if array.get(out_lvls, 0) > min
        steps_away = math.round((array.get(out_lvls, 0) - min + step_size - (array.get(out_lvls, 0) - min) % step_size) / step_size)

        if steps_away > 0
            bot = array.get(out_lvls, 0)
            for n = 0 to steps_away - 1 by 1
                array.insert(out_lvls, n, bot - step_size * (steps_away - n))
                array.insert(out_v, n, 0)


    if array.get(out_lvls, array.size(out_lvls) - 1) < max
        steps_away = (max - array.get(out_lvls, array.size(out_lvls) - 1) + step_size - (max - array.get(out_lvls, array.size(out_lvls) - 1)) % step_size) / step_size

        for n = array.size(out_lvls) - 1 to array.size(out_lvls) - 1 + steps_away by 1
            array.push(out_lvls, array.get(out_lvls, n) + step_size)
            array.push(out_v, 0)

    [out_v, out_lvls]

//
addToVP(in_v, in_lvls, array_l, array_h, array_v) =>  // Adds arrays of two volumes

    out_v = in_v
    out_lvls = in_lvls

    for i = 0 to array.size(array_l) - 1 by 1
        _l = array.get(array_l, i)
        _h = array.get(array_h, i)
        _v = array.get(array_v, i)

        for n = 0 to array.size(out_lvls) - 2 by 1
            cur_tier = array.get(out_lvls, n)
            next_tier = array.get(out_lvls, n + 1)

            if _l > cur_tier and _l < next_tier and _h > cur_tier and _h < next_tier
                old_v = array.get(out_v, n)
                array.set(out_v, n, old_v + _v)

            else if _l > cur_tier and _l < next_tier
                vol_in_range = (next_tier - _l) / (_h - _l) * _v
                old_v = array.get(out_v, n)
                array.set(out_v, n, old_v + vol_in_range)

            else if _h > cur_tier and _h < next_tier
                vol_in_range = (_h - cur_tier) / (_h - _l) * _v
                old_v = array.get(out_v, n)
                array.set(out_v, n, old_v + vol_in_range)

            else if _h > next_tier and _l < cur_tier
                vol_in_range = (next_tier - cur_tier) / (_h - _l) * _v
                old_v = array.get(out_v, n)
                array.set(out_v, n, old_v + vol_in_range)


    [out_v, out_lvls]

plotDeltaBox(_in_v, _in_lvls, _start_bar, _end_bar, _transp, _ui_color) =>

    var int _box_end = bar_index
    var color _col = color.white
    var color _start_col = color.new(color.white, _transp)
    var color _pos_col = color.new(color.green, _transp)
    var color _neg_col = color.new(color.red, _transp)
    box[] _boxes = array.new_box()

    _center = math.floor((_end_bar + _start_bar) / 2)
    _max_v = array.max(_in_v)
    _min_v = array.min(_in_v)
    _max = math.max(_max_v,-_min_v)
    
    for x = 0 to array.size(_in_v) - 1 by 1
        _box_top = array.get(_in_lvls, x + 1) - int(close * 0.0005)
        _box_bot = array.get(_in_lvls, x) + int(close * 0.0005)
        _box_val = array.get(_in_v, x)

        if _box_val >= 0
            _box_end := _center + math.round(_box_val / _max * (_end_bar - _center))
            _col := color.from_gradient(_box_end, _center, _end_bar, _start_col, _pos_col)
            _col
        else
            _box_end := _center + math.round(_box_val / -_max * (_start_bar - _center))
            _col := color.from_gradient(_box_end, _start_bar, _center, _neg_col, _start_col)
            _col

        array.push(_boxes, box.new(_center, _box_top, _box_end, _box_bot, _col, bgcolor=_col))

    array.push(_boxes, box.new(_start_bar,array.max(_in_lvls),_end_bar,array.min(_in_lvls), border_color = _ui_color, bgcolor = color.new(color.white, 100)))
    _boxes

clearBoxes(box_array) =>
    if array.size(box_array) > 0
        for i = 0 to array.size(box_array) - 1 by 1
            box.delete(array.get(box_array, i))

//
sec(_tf, _src, _ref) =>
    request.security(syminfo.tickerid, _tf, _src[_ref])

//
sec2(_tf, _src, _ref) =>
    var ticker = syminfo.tickerid + "_OI"
    request.security(ticker, _tf, _src[_ref])
    
//
printArray(_array) =>
    _text = ""

    for x = 0 to array.size(_array)-1 by 1
        if str.length(_text) < 4000
            _text := _text + str.tostring(array.get(_array,x)) + "\n"
        
    _text

//
//-----------------------------------------------------------//
//---------------------- VARIABLES --------------------------//
//-----------------------------------------------------------//

cond = input.string('Session', title='Condition', options=['Session', 'Day', 'Weekly', 'Month'])
sampling = input(10, 'Sampling rate', 'max is 10 and multiplied by the timeframe it should equal the current timeframe in minutes')
tf = input('6', 'Timeframe', '1/Nth of the TF')
transp = input(50, 'Transparency of profile')
min_change = input(0.2, 'Minimum change', '% change for a bar')
last_bar_limit = input(500, 'Bars Limit', 'Limits the amounts of bars the script is run for (lower = faster loading)')
ui_color = input.color(color.white, 'UI Color', "Color of the surrounding Box and center line")
src = close

var int div = int(100 / min_change)
var float step_size = math.round_to_mintick(src / div)
var int last_plot_bar = 0
var vp = array.new_float(1, 0)
var vp_lvls = array.new_float(2, src)
var temp_boxes = array.new_box(0)
var boxes = array.new_box(0)

var high_vp = array.new_float(1, 0)
var high_vp_lvls = array.new_float(2, src)
var low_vp = array.new_float(1, 0)
var low_vp_lvls = array.new_float(2, src)

var h = array.new_float(sampling, 0)
var l = array.new_float(sampling, 0)
var v = array.new_float(sampling, 0)

//-----------------------------------------------------------//
//---------------------- CONDITIONS -------------------------//
//-----------------------------------------------------------//

sessions = cond == 'Session' and (hour == 0 and hour[1] == 23 or hour == 8 and hour[1] == 7 or hour == 16 and hour[1] == 15)
daily = cond == 'Day' and dayofweek != dayofweek[1]
weekly = cond == 'Week' and dayofweek != dayofweek[1] and dayofweek == dayofweek.monday
monthly = cond == 'Month' and month != month[1]

//-----------------------------------------------------------//
//---------------------- SAMPLING ---------------------------//
//-----------------------------------------------------------//

if sampling >= 1
    array.set(h, 0, nz(sec(tf, high, 0), high))
    array.set(l, 0, nz(sec(tf, low, 0), low))
    array.set(v, 0, nz(sec2(tf, ta.change(close), 0), 0))
if sampling >= 2
    array.set(h, 1, nz(sec(tf, high, 1), high))
    array.set(l, 1, nz(sec(tf, low, 1), low))
    array.set(v, 1, nz(sec2(tf, ta.change(close), 1), 0))
if sampling >= 3
    array.set(h, 2, nz(sec(tf, high, 2), high))
    array.set(l, 2, nz(sec(tf, low, 2), low))
    array.set(v, 2, nz(sec2(tf, ta.change(close), 2), 0))
if sampling >= 4
    array.set(h, 3, nz(sec(tf, high, 3), high))
    array.set(l, 3, nz(sec(tf, low, 3), low))
    array.set(v, 3, nz(sec2(tf, ta.change(close), 3), 0))
if sampling >= 5
    array.set(h, 4, nz(sec(tf, high, 4), high))
    array.set(l, 4, nz(sec(tf, low, 4), low))
    array.set(v, 4, nz(sec2(tf, ta.change(close), 4), 0))
if sampling >= 6
    array.set(h, 5, nz(sec(tf, high, 5), high))
    array.set(l, 5, nz(sec(tf, low, 5), low))
    array.set(v, 5, nz(sec2(tf, ta.change(close), 5), 0))
if sampling >= 7
    array.set(h, 6, nz(sec(tf, high, 6), high))
    array.set(l, 6, nz(sec(tf, low, 6), low))
    array.set(v, 6, nz(sec2(tf, ta.change(close), 6), 0))
if sampling >= 8
    array.set(h, 7, nz(sec(tf, high, 7), high))
    array.set(l, 7, nz(sec(tf, low, 7), low))
    array.set(v, 7, nz(sec2(tf, ta.change(close), 7), 0))
if sampling >= 9
    array.set(h, 8, nz(sec(tf, high, 8), high))
    array.set(l, 8, nz(sec(tf, low, 8), low))
    array.set(v, 8, nz(sec2(tf, ta.change(close), 8), 0))
if sampling >= 10
    array.set(h, 9, nz(sec(tf, high, 9), high))
    array.set(l, 9, nz(sec(tf, low, 9), low))
    array.set(v, 9, nz(sec2(tf, ta.change(close), 9), 0))


//-----------------------------------------------------------//
//---------------------- CHART ------------------------------//
//-----------------------------------------------------------//

if bar_index > (last_bar_index - last_bar_limit)

    clearBoxes(temp_boxes)

    if sessions or daily or weekly or monthly
        boxes := plotDeltaBox(vp, vp_lvls, last_plot_bar, bar_index, transp, ui_color)
        _center = math.floor((bar_index + last_plot_bar) / 2)
        line.new(_center,array.max(vp_lvls),_center,array.min(vp_lvls), color = ui_color)
        last_plot_bar := bar_index

        step_size := math.round_to_mintick(src / div)
        [_vp, _vp_lvls] = initializeVP(step_size, l, h)
        vp := _vp
        vp_lvls := _vp_lvls

        endIf()
    else
        temp_boxes := plotDeltaBox(vp, vp_lvls, last_plot_bar, bar_index, transp, ui_color)

        [a_vp, a_vp_lvls] = adaptVP(step_size, vp, vp_lvls, l, h)
        vp := a_vp
        vp_lvls := a_vp_lvls

        endIf()

    [b_vp, b_vp_lvls] = addToVP(vp, vp_lvls, l, h, v)
    vp := b_vp
    vp_lvls := b_vp_lvls
    
else
    if sessions or daily or weekly or monthly
        last_plot_bar := bar_index
        
        step_size := math.round_to_mintick(src / div)
        [_vp, _vp_lvls] = initializeVP(step_size, l, h)
        vp := _vp
        vp_lvls := _vp_lvls

        endIf()

plot(close, color=color.new(color.black, 100))

