Point of Control V2 
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © JohnBaron
//@version=5
indicator("Point of Control",shorttitle="POC",overlay=true,explicit_plot_zorder=true)

//======= Inputs
use_day=input.bool(false,"Daily Reset",group="Reset Trigger",tooltip="Priority Order Daily, Weekly, Candles")
use_week=input.bool(false,"Weekly Reset",group="Reset Trigger")
bars_ = input.int(800,title="# of Candles the reset",group="Reset Trigger",tooltip="Candles are counted from left to right")

//________Inputs for level increment
incr_in = input.float(0.,title="Price increment for levels, 0 will result in auto increment",group="Levels")
see_suggestion=input.bool(false,"Show Auto_Incr",group="Levels")

//________Inputs for display
show_hist=input.bool(false,title="Show historic POCs",group="Display" ,tooltip="Historical POC above/below current price")
show_grands=input.bool(false,"Show Grand POC",group="Display",tooltip="Top POC for the entire data set")
show_rank = input.bool(false,"Show Next 3 Chart POCs",group="Display", tooltip="Rank 2-4 POCs for the entire data set")
show_mnmx=input.bool(false,"Show upper and lower bounds",group="Display")


//======= Functions
Round_to_Level(s_,t_)=>
    math.round(s_/t_)*t_

// Use average range across chart to estimate an increment
Auto_Incr()=>
    avg_range = math.round_to_mintick(ta.cum(high-low)/(bar_index+1))  //calculate the running average of the range rounded to mintick
    wn_ = avg_range/syminfo.mintick                          // transform and convert to whole number
    pow_ = math.pow(10,int(math.log10(wn_)))                           //use only integer portion of log
    target_ = 2*math.ceil(wn_/pow_)*syminfo.mintick*pow_               //transform back to scale



//=============== Main Execution
src_ = high

auto_incr = Auto_Incr()                                                 //Get auto_increment
incr_ = incr_in==0? auto_incr: incr_in                                  //If input incr is 0 then use auto_incr

//______________ Set up variables
var bar_count =0
var ub_ = high
var lb_ = low
var max_v =0.
var POC_ = hlc3
var v_ = array.new_float(0,0.)
var p_ = array.new_float(0,0.)
var hp_ = array.new_float(0,0.)
var hv_ = array.new_float(0,0.)
var Grand_poc2 = 0.
var Grand_poc3 = 0.
var Grand_poc4 = 0.
var hist_POC_above=hlc3
var hist_POC_below=hlc3
var error= na(volume)           //determine if volume is available for the symbol

if error                        
    runtime.error("Indicator is only valid on symbols with  volume data") //generate error message if no volume for symbol

new_day = timeframe.change("D")     //bool true/false if new day
new_week= timeframe.change("W")     //bool true/false if new week

//______________ reset based on day/week or # of bars
reset_ = switch
    use_day => new_day
    use_week => new_week
    => bar_count>=bars_ 


//______________ store last POC_ and reset for new POC development
if reset_ or bar_index==0
    POC_idx = array.indexof(hp_,POC_)
    if POC_idx == -1
        array.push(hp_,POC_)
        array.push(hv_,max_v)
    else
        hv_element=array.get(hv_,POC_idx)
        array.set(hv_,POC_idx,max_v+hv_element)
    bar_count:=0
    ub_ := Round_to_Level(high,incr_)
    lb_ := math.min(ub_-incr_,Round_to_Level(low,incr_))
    array.clear(v_)
    array.clear(p_)
    array.push(v_,volume/2)
    array.push(v_,volume/2)
    array.push(p_, ub_)
    array.push(p_, lb_)

bar_count +=1

///______________  if upper or lower bounder expands
break_up = math.max(0.,high - ub_)
breaK_dn = math.max(0.,lb_ - low)
max_incr = int(break_up/incr_)+ (break_up%incr_>0? 1:0)
min_decr = int(breaK_dn/incr_)+ (breaK_dn%incr_>0? 1:0)

//______________ Check for upper boundary change
//______________ If a change exists then place at the beginning of the array the new price levels
if max_incr>0
    i=1
    while i<= max_incr
        vol_incr = volume/max_incr
        ub_ += incr_
        array.unshift(v_,vol_incr)
        array.unshift(p_, ub_)
        i+=1

//______________ Check for lower boundary change
//______________ If a change exists then place at the end of the array the new price levels
if min_decr>0
    j=1
    while j<= min_decr
        vol_incr = volume/min_decr       
        lb_ -= incr_
        array.push(v_,vol_incr)
        array.push(p_, lb_)
        j+=1

//______________ Array will be ordered from high to low for index 0 to array,size-1
//______________ If there is no change in the boundaries then locate current price in the defined categories
if max_incr==0 and min_decr==0
    for [idx,element] in p_
        if src_ > element and idx>0
            v_element = array.get(v_,idx-1)
            array.set(v_,idx-1, volume+v_element)
            break

//______________  Find point of control
max_v :=array.max(v_)
max_idx = array.indexof(v_,max_v)
POC_ := array.get(p_,max_idx)

//=== Sort dataset POCs
hp_sorted_ =array.sort_indices(hp_,order.descending)
hv_sorted_ =array.sort_indices(hv_,order.descending)

//______________ Get historical POCs around current price
for [idx,element] in hp_sorted_
    hp_value = array.get(hp_,element)
    hist_POC_above := hp_value
    if src_>hp_value
        hist_POC_below:= hp_value
        if idx>=1
            higher_idx = array.get(hp_sorted_,idx-1)
            hist_POC_above:= array.get(hp_, higher_idx)
        break

//______________ find the POC for entire data set
max_max_idx = array.get(hv_sorted_,0)
Grand_POC = array.get(hp_,max_max_idx)

//______________ get the next 3 high volume levels
if array.size(hv_sorted_)>=4 and show_rank
    max2_idx = array.get(hv_sorted_,1)
    max3_idx = array.get(hv_sorted_,2)
    max4_idx = array.get(hv_sorted_,3)
    Grand_poc2 := array.get(hp_, max2_idx)
    Grand_poc3 := array.get(hp_, max3_idx)
    Grand_poc4 := array.get(hp_, max4_idx)


//=============== Plots and Drawings

plot(show_hist? hist_POC_above:na,color=color.aqua,style=plot.style_circles,linewidth=1,title="hist_POC_above")
plot(show_hist? hist_POC_below:na,color=color.aqua,style=plot.style_circles,linewidth=1,title="hist_POC_below")

plot(show_rank? Grand_poc4:na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_4")
plot(show_rank? Grand_poc3:na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_3")
plot(show_rank? Grand_poc2:na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_2")
plot(show_grands? Grand_POC:na,color=color.orange,style=plot.style_cross,linewidth=2,title="Grand POC_")

plot(show_mnmx? ub_:na,color=color.blue,title= "Upper Bound")
plot(show_mnmx? lb_:na, color=color.blue,title= "Lower Bound")

plot(POC_,color=color.yellow,style=plot.style_linebr,linewidth=1,title="POC_")

//______________ Setup label and line
var lab_1 = label.new(na,na,text=na,color=color.white,textcolor=color.black,size=size.small)
var line_1 = line.new(na,na,na,na,color=color.gray, style= line.style_dashed,extend=extend.both)

//______________ Show the auto increment value
if barstate.islast and see_suggestion
    label.set_xy(lab_1,bar_index,high+2*incr_)
    label.set_text(lab_1,str.tostring(auto_incr))

//______________ Draw Verticle line Marking the Reset Point
if reset_
    line.set_xy1(line_1,bar_index,0.)
    line.set_xy2(line_1,bar_index,close)

