PointofControl
// 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
// This library provides a single funciton which outputs an array as a result
// The purpose of the function is to provide a volume profile POC to a script author
library(title="PointofControl",overlay=true)

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

// @function Calculates the point of control
// @param val Series to use (`low` is used if no argument is supplied).
// @param bool reset_flag forces the reset based on a condition in the main script and only used when bars_ parm is 0
// @param int bars_. when 0 is input the reset_flag drives the reset, otherwise # of bars passed until reset
// @param float incr_, input on the price increment, if 0 is used then the function will use the auto calculated level
//-----------
// @returns An 11 element array
// [0] = upper_bound
// [1] = lower_bound
// [2] = POC
// [3] = historical POC immediately above price
// [4] = historical POC immediately below price
// [5] = Grand POC, the POC for the entire data set
// [6] = Entire data set rank order 2 of POC
// [7] = Entire data set rank order 3 of POC
// [8] = Entire data set rank order 4 of POC
// [9] = auto_increment value
// [10]= reset_flag, lets you know when a reset occurred
//===========
//
export POC_f(bool reset_flag, int bars_, float incr_in)=>
	var bar_count =0
	var ub_ = high
	var lb_ = low
	var max_v =0.
	var POC_ = hlc3
	var Grand_poc2 = 0.
	var Grand_poc3 = 0.
	var Grand_poc4 = 0.
	var hist_POC_above=hlc3
	var hist_POC_below=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 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

	src_ = high

	auto_incr = Auto_Incr()

	incr_ = incr_in==0? auto_incr: incr_in

	reset_ = switch
		bars_==0 => reset_flag
		=> bar_count>=bars_ 

	if reset_ or bar_index==0
		POC_idx = array.indexof(hp_,POC_)
		if POC_idx== -1									// if -1 is returned it means the POC_ was not found, so we will save it
			array.push(hp_,POC_)
			array.push(hv_,max_v)
		else												// if the index and POC_ exists, then update the volume
			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_)						//Set upper bound
		lb_ := math.min(ub_-incr_,Round_to_Level(low,incr_))	//Set lower bound
		array.clear(v_)											//Clear the price and volume arrays
		array.clear(p_)
		array.push(v_,volume/2)									//Add the initial two values to arrays
		array.push(v_,volume/2)
		array.push(p_, ub_)
		array.push(p_, lb_)

	bar_count +=1 												//increment bar count

	break_up = math.max(0.,high - ub_)							//high vs upper bound
	break_dn = math.max(0.,lb_ - low)							//low vs lower bound
	max_incr = int(break_up/incr_)+ (break_up%incr_>0? 1:0)		//number of increments needed
	min_decr = int(break_dn/incr_)+ (break_dn%incr_>0? 1:0)		//

	// If upper boundary needs to change
	// Then add new upper increments to the beginning of the arrays
	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

	// If lower boundary needs to change
	// Then add new lower increments to the end of the array
	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

	//
	//If there is no change in the boundaries then locate current price in the defined levels
	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)				//Volume accumulated into level
				break

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

	//== sort chart dataset points of control
	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 POC across all period POCs, the max of all POCs
	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
		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)

	out_ = array.from(ub_, lb_, POC_, hist_POC_above, hist_POC_below, Grand_POC, Grand_poc2, Grand_poc3, Grand_poc4,auto_incr,(reset_?1.:0.))


//=====================================================


//==== Library Sample

//======= Inputs
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_ 	= input.float(0.,title="Price increment for levels, 0 results in auto increment",group="Levels")
see_suggestion=input.bool(false,"Show Auto_Incr",group="Levels")

//________Inputs for display
show_hist=input.bool(true,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")

reset_trigger = use_week? timeframe.change("W"):false
bars_in = use_week? 0 : bars_

POC_array = POC_f(reset_trigger,bars_in,incr_)

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

plot(show_hist? array.get(POC_array,3):na,color=color.aqua,style=plot.style_cross,linewidth=1,title="hist_POC_above")
plot(show_hist? array.get(POC_array,4):na,color=color.aqua,style=plot.style_cross,linewidth=1,title="hist_POC_below")

plot(show_grands? array.get(POC_array,5):na,color=color.red,style=plot.style_cross,linewidth=2,title="Grand POC_")
plot(show_grands? array.get(POC_array,6):na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_2")
plot(show_grands? array.get(POC_array,7):na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_3")
plot(show_grands? array.get(POC_array,8):na,color=color.fuchsia,style=plot.style_cross,linewidth=1,title="Grand POC_4")

plot(show_mnmx? array.get(POC_array,0):na,color=color.blue)
plot(show_mnmx? array.get(POC_array,1):na, color=color.blue)

plot(array.get(POC_array,2),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*array.get(POC_array,9))
	label.set_text(lab_1,str.tostring(array.get(POC_array,9)))

//______________ Draw Verticle line Marking the Reset Point
if array.get(POC_array,10)==1
	line.set_xy1(line_1,bar_index,0.)
	line.set_xy2(line_1,bar_index,close)











