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

// v354 (v1) feature complete, optimisation todo
// v453 (v5) fixed max_bars_back through not updating box left and line start x coordinates on re-draw, added lines to mark candle ranges used for profile

//@version=5
import robbatt/lib_plot_objects/34 as D

// @description a library with functions to calculate a volume profile for either a set of candles within the current chart, or a single candle from its lower timeframe security data. All you need is to feed the 
library("lib_profile", overlay = true)

//@description this is a container for each bucket of the Profile. See Profile description for more info
//@field idx the index of this Bucket within the Profile starting with 0 for the lowest Bucket at the bottom of the Profile
//@field value the value of this Bucket, can be volume or time, for using time pass and array of 1s to the update function
//@field top the top of this Bucket's price range (for calculation)
//@field btm the bottom of this Bucket's price range (for calculation)
//@field center the center of this Bucket's price range (for plotting)
//@field fraction the fraction this Bucket's value is compared to the total of the Profile
//@field plot_bucket_line the line that resembles this bucket and it's valeu in the Profile
export type Bucket
	int idx
	float value
	float top
	float btm
	float center
	float fraction
	D.Line plot_bucket_line

//@function deletes this bucket's plot from the chart
export method delete(Bucket this) =>
	if na(this) ? false : not na(this.plot_bucket_line)
		this.plot_bucket_line.delete()
	this

//@function hides this bucket's plot from the chart by setting the line end to na
export method hide(Bucket this) =>
	if na(this) ? false : not na(this.plot_bucket_line)
		this.plot_bucket_line.hide()
	this

//@function updates this bucket's data
export method update(Bucket this, float top, float bottom, float value, float fraction) =>
	this.top := top
	this.btm := bottom
	this.center := math.avg(top, bottom)
	this.value := value
	this.fraction := fraction
	this

//@function allows debug print of a bucket
export method tostring(Bucket this) =>
	str.format('idx {0} - top: {1} - center: {2} - bottom: {3} - value: {4} - fraction: {5}', this.idx, this.top, this.center, this.btm, this.value, this.fraction)

//@function allows drawing a line in a Profile, representing this bucket and it's value + it's value's fraction of the Profile total value
//@param start_t the time x coordinate of the line's left end (depends on the Profile box)
//@param start_i the bar_index x coordinate of the line's left end (depends on the Profile box)
//@param end_t the time x coordinate of the line's right end (depends on the Profile box)
//@param end_i the bar_index x coordinate of the line's right end (depends on the Profile box)
//@param args the default arguments for the line style
//@param line_color the color override for POC/VAH/VAL lines
export method draw(Bucket this, int start_t, int start_i, int end_t, int end_i, D.LineArgs args, color line_color) =>
	if na(this.plot_bucket_line)
		this.plot_bucket_line := D.create_line(start_t, start_i, this.center, end_t, end_i, this.center, args)
	// this.plot_bucket_line.start.update(start_t, start_i, this.center)
	this.plot_bucket_line.end.update(end_t, end_i, this.center)
	this.plot_bucket_line.draw()
	if not na(line_color)
		this.plot_bucket_line.plot.set_color(line_color)
	this

//@description arguments for plotting a Profile
//@field show_poc whether to plot a POC line across the Profile Box (default: true)
//@field show_profile whether to plot a line for each Bucket in the Profile Box, indicating the value per Bucket (Price range), e.g. volume that occured in a certain time and price range (default: false)
//@field show_va whether to plot a VAH/VAL line across the Profile Box (default: false)
//@field show_va_fill whether to fill the 'value' area between VAH/VAL line (default: false)
//@field show_background whether to fill the Profile Box with a background color (default: false)
//@field show_labels whether to add labels to the right end of the POC/VAH/VAL line (default: false)
//@field show_price_levels whether add price values to the labels to the right end of the POC/VAH/VAL line (default: false)
//@field extend whether extend the Profile Box to the current candle (default: false)
//@field default_size the default min. width of the Profile Box (default: 30)
//@field args_poc_line arguments for the poc line plot
//@field args_va_line arguments for the va line plot
//@field args_poc_label arguments for the poc label plot
//@field args_va_label arguments for the va label plot
//@field args_profile_line arguments for the Bucket line plots
//@field va_fill_color color for the va area fill plot
export type ProfileArgs
	bool show_poc = true
	bool show_profile = false
	bool show_va = false
	bool show_va_fill = false
	bool show_background = false
	bool show_labels = false
	bool show_price_levels = false
	bool extend = false

	int default_size = 30
	D.LineArgs args_poc_line
	D.LineArgs args_va_line
	D.LabelArgs args_poc_label
	D.LabelArgs args_va_label
	D.LineArgs args_profile_line
	D.BoxArgs args_profile_bg
	color va_fill_color = color.yellow

//@description
//@field start left x coordinate for the Profile Box
//@field end right x coordinate for the Profile Box
//@field resolution the amount of buckets/price ranges the Profile will dissect the data into
//@field vah_threshold_pc the percentage of the total data value to mark the upper threshold for the main value area
//@field val_threshold_pc the percentage of the total data value to mark the lower threshold for the main value area
//@field args the style arguments for the Profile Box
//@field h the highest price of the data
//@field l the lowest price of the data
//@field total the total data value (e.g. volume of all candles, or just one each to analyse candle distribution over time)
//@field buckets the Bucket objects holding the data for each price range bucket
//@field poc_bucket_index the Bucket index in buckets, that holds the poc Bucket
//@field vah_bucket_index the Bucket index in buckets, that holds the vah Bucket
//@field val_bucket_index the Bucket index in buckets, that holds the val Bucket
//@field poc the according price level marking the Point Of Control
//@field vah the according price level marking the Value Area High
//@field val the according price level marking the Value Area Low
//@field plot_poc
//@field plot_vah
//@field plot_val
//@field plot_poc_label
//@field plot_vah_label
//@field plot_val_label
//@field plot_va_fill
//@field plot_profile_bg
export type Profile
	// params
	int start
	int end
	int resolution
	float vah_threshold_pc
	float val_threshold_pc
	ProfileArgs args

	// range data
	float h
	float l
	float total
	Bucket[] buckets

	// value levels
	int poc_bucket_index
	int vah_bucket_index
	int val_bucket_index

	// price levels
	float poc
	float vah
	float val

	// plots
	D.Line plot_poc
	D.Line plot_vah
	D.Line plot_val
	D.Label plot_poc_label
	D.Label plot_vah_label
	D.Label plot_val_label
	D.LineFill plot_va_fill
	D.Box plot_profile_bg

export method apply_style(Profile this, ProfileArgs args) =>
    if not na(this) and not na(args)
        this.args := args
        
	    if not na(this.plot_poc)
            this.plot_poc.apply_style(args.args_poc_line)            
	    if not na(this.plot_vah)
            this.plot_vah.apply_style(args.args_va_line)
	    if not na(this.plot_val)
            this.plot_val.apply_style(args.args_va_line)
	    if not na(this.plot_poc_label)
            this.plot_poc_label.apply_style(args.args_poc_label)
	    if not na(this.plot_vah_label)
            this.plot_vah_label.apply_style(args.args_va_label)
	    if not na(this.plot_val_label)
            this.plot_val_label.apply_style(args.args_va_label)
	    if not na(this.plot_va_fill)
            this.plot_va_fill.plot.set_color(args.va_fill_color)
	    if not na(this.plot_profile_bg)
            this.plot_profile_bg.apply_style(args.args_profile_bg, D.BoxTextArgs.new())

//@description deletes this Profile's and Buckets' plots
export method delete(Profile this) =>
	if not na(this)
		if not na(this.buckets)
			for bucket in this.buckets
				bucket.delete()
		if not na(this.plot_poc)
			this.plot_poc.delete()
		if not na(this.plot_vah)
			this.plot_vah.delete()
		if not na(this.plot_val)
			this.plot_val.delete()
		if not na(this.plot_poc_label)
			this.plot_poc_label.delete()
		if not na(this.plot_vah_label)
			this.plot_vah_label.delete()
		if not na(this.plot_val_label)
			this.plot_val_label.delete()
		if not na(this.plot_va_fill)
			this.plot_va_fill.delete()
		if not na(this.plot_profile_bg)
			this.plot_profile_bg.delete()
	this


//@function hides this Profile's plot from the chart by setting the line end to na
export method hide(Profile this) =>
	if not na(this)
		if not na(this.buckets)
			for bucket in this.buckets
				bucket.hide()
		if not na(this.plot_poc)
			this.plot_poc.hide()
		if not na(this.plot_vah)
			this.plot_vah.hide()
		if not na(this.plot_val)
			this.plot_val.hide()
		if not na(this.plot_poc_label)
			this.plot_poc_label.hide()
		if not na(this.plot_vah_label)
			this.plot_vah_label.hide()
		if not na(this.plot_val_label)
			this.plot_val_label.hide()
		if not na(this.plot_profile_bg)
			this.plot_profile_bg.hide()
	this

_delete_all(id) =>
	if not na(id)
		for item in id
			item.delete()
	id
//@description deletes a list of Buckets' plots
export method delete(Bucket[] this) => _delete_all(this)
//@description deletes a list of Profiles' plots
export method delete(Profile[] this) => _delete_all(this)

//@description initialises ProfileArgs with default values if not set, will be called by update() and draw() to ensure both functions can work properly
export method init(ProfileArgs this) =>
	if not na(this)
		if na(this.args_poc_line)
			this.args_poc_line := D.LineArgs.new(color.orange, line.style_dotted, 1, xloc.bar_time)
		if na(this.args_va_line)
			this.args_va_line := D.LineArgs.new(color.yellow, line.style_dotted, 1, xloc.bar_time)
		if na(this.args_poc_label)
			this.args_poc_label := D.LabelArgs.new(color.orange, #00000000, style = label.style_label_lower_right, xloc = xloc.bar_time)
		if na(this.args_va_label)
			this.args_va_label := D.LabelArgs.new(color.yellow, #00000000, style = label.style_label_lower_right, xloc = xloc.bar_time)
		if na(this.args_profile_line)
			this.args_profile_line := D.LineArgs.new(color.rgb(120, 123, 134, 40), line.style_dotted, 1, xloc.bar_time)
		if na(this.args_profile_bg)
			this.args_profile_bg := D.BoxArgs.new(#00000000, bg_color = color.rgb(120, 123, 134, 92), xloc = xloc.bar_time)
	this

//@description initialises Profile with default values if not set, will be called by update() and draw() to ensure both functions can work properly
export method init(Profile this) =>
	if not na(this)
		if na(this.buckets)
			this.buckets := array.new<Bucket>(this.resolution)
			for i = 0 to this.resolution - 1
				this.buckets.set(i, Bucket.new(i))
		if na(this.args)
			this.args := ProfileArgs.new().init()


//@function split a chart/parent bar into 'resolution' sections, figure out in which section the most volume/time was spent, by analysing a given set of (intra)bars' top/bottom/volume values. Then return price center of the bin with the highest volume, essentially marking the point of control / highest volume (poc) in the chart/parent bar.
//@param tops array of range top/high values (either from ltf or chart candles using history() function
//@param bottoms array of range bottom/low values (either from ltf or chart candles using history() function
//@param values array of range volume/1 values (either from ltf or chart candles using history() function (1s can be used for analysing candles in bucket/price range over time)
//@param resolution amount of buckets/price ranges to sort the candle data into (analyse how much volume / time was spent in a certain bucket/price range) (default: 25)
//@param vah_pc a threshold percentage (of values' total) for the top end of the value area (default: 80)
//@param val_pc a threshold percentage (of values' total) for the bottom end of the value area (default: 20)
//@param bucket_buffer optional buffer of empty Buckets to fill, if omitted a new one is created and returned. The buffer length must match the resolution
//@returns poc (price level), vah (price level), val (price level), poc_index (idx in buckets), vah_index (idx in buckets), val_index (idx in buckets), buckets (filled buffer or new)
export profile(float[] tops, float[] bottoms, float[] values, int resolution = 25, float vah_pc = 80, float val_pc = 20, Bucket[] bucket_buffer = na) =>

	// DATA
	float h = tops.max()
	float l = bottoms.min()
	float total = values.sum()
    int range_count = array.size(values)

    float vah_threshold = total * (vah_pc / 100)
    float val_threshold = total * (val_pc / 100)
	float value_range = h - l
	float bucket_size = value_range / resolution

	// RESULTS
    Bucket[] buckets = na(bucket_buffer) ? array.new<Bucket>(resolution) : bucket_buffer.size() != resolution ? array.new<Bucket>(resolution) : bucket_buffer
	float poc = na
    float vah = na
    float val = na
	int poc_index = 0
	int vah_index = 0
	int val_index = 0

    if range_count > 0
        float poc_volume = 0
        float sum_volume = 0        

		for i = 0 to resolution - 1
            float bucket_bottom = l + i * bucket_size
            float bucket_top = bucket_bottom + bucket_size
			float bucket_value = 0

			// check all top-btm ranges, add the value (partly) to the bucket on overlap
            for j = 0 to range_count - 1
                float range_value = array.get(values, j)
                float range_top = array.get(tops, j)
                float range_btm = array.get(bottoms, j)
				float range_size = range_top - range_btm
				float range_value_fraction = 0
                
                if range_top <= bucket_top and (range_btm > bucket_bottom or i == 0 and range_btm >= bucket_bottom)// inside bucket (also if range_size == 0 due to high == low)
                    range_value_fraction := 1
                else if range_top > bucket_top and range_btm < bucket_bottom // around bucket
                    range_value_fraction := bucket_size / range_size
                else if range_top > bucket_bottom and range_top <= bucket_top and range_btm < bucket_bottom // sticks into bucket from below (only count part in bucket)
                    range_value_fraction := (range_top - bucket_bottom) / range_size
                else if range_btm < bucket_top and range_btm >= bucket_bottom and range_top > bucket_top // sticks into bucket from above (only count part in bucket)
                    range_value_fraction := (bucket_top - range_btm) / range_size

				bucket_value += range_value_fraction * range_value

			bucket = buckets.get(i).update(bucket_top, bucket_bottom, bucket_value, bucket_value / total)

            // track highest value (POC) inside the profile AND it's index 
            if bucket_value > poc_volume
                poc_volume := bucket_value
                poc_index := i

            // while going through the profile, accumulate the bucket values. As soon as we cross the value area thresholds with a bucket, we know where the value area starts / ends.
            if sum_volume < val_threshold and val_threshold <= (sum_volume + bucket_value)
                val_index := i
            if sum_volume < vah_threshold and vah_threshold <= (sum_volume + bucket_value)
                vah_index := i

            sum_volume += bucket_value

        poc := buckets.get(poc_index).center
        vah := buckets.get(vah_index).center
        val := buckets.get(val_index).center

    [poc, vah, val, poc_index, vah_index, val_index, buckets]


//@function update this Profile's data (recalculates the whole profile and applies the result to this object) TODO optimisation to calculate this incremental to improve performance in realtime on high resolution
//@param tops array of range top/high values (either from ltf or chart candles using history() function
//@param bottoms array of range bottom/low values (either from ltf or chart candles using history() function
//@param values array of range volume/1 values (either from ltf or chart candles using history() function (1s can be used for analysing candles in bucket/price range over time)
export method update(Profile this, float[] tops, float[] bottoms, float[] values) =>
	this.init()	

	[poc, vah, val, poc_index, vah_index, val_index, buckets] = profile(tops, bottoms, values, this.resolution, this.vah_threshold_pc, this.val_threshold_pc, this.buckets)

	this.h := tops.max()
	this.l := bottoms.min()
	this.total := values.sum()
	this.poc := poc
	this.vah := vah
	this.val := val
	this.poc_bucket_index := poc_index
	this.vah_bucket_index := vah_index
	this.val_bucket_index := val_index
	this
	
//@function split a chart/parent bar into 'resolution' sections, figure out in which section the most volume/time was spent, by analysing a given set of (intra)bars' top/bottom/volume values. Then return price center of the bin with the highest volume, essentially marking the point of control / highest volume (poc) in the chart/parent bar.
//@param start_idx the bar_index at which the Profile should start drawing
//@param tops array of range top/high values (either from ltf or chart candles using history() function
//@param bottoms array of range bottom/low values (either from ltf or chart candles using history() function
//@param values array of range volume/1 values (either from ltf or chart candles using history() function (1s can be used for analysing candles in bucket/price range over time)
//@param resolution amount of buckets/price ranges to sort the candle data into (analyse how much volume / time was spent in a certain bucket/price range) (default: 25)
//@param vah_pc a threshold percentage (of values' total) for the top end of the value area (default: 80)
//@param val_pc a threshold percentage (of values' total) for the bottom end of the value area (default: 20)
//@param bucket_buffer optional buffer of empty Buckets to fill, if omitted a new one is created and returned. The buffer length must match the resolution
//@returns poc (price level), vah (price level), val (price level), poc_index (idx in buckets), vah_index (idx in buckets), val_index (idx in buckets), buckets (filled buffer or new)
export create_profile(int start_idx, float[] tops, float[] bottoms, float[] values, int resolution = 25, float vah_pc = 80, float val_pc = 20, ProfileArgs args) =>
	Profile.new(start_idx, start_idx + 1, resolution, vah_pc, val_pc, args).update(tops, bottoms, values)

//@function draw all components of this Profile (Box, Background, Bucket lines, POC/VAH/VAL overlay levels and labels)
//@param forced_width allows to force width of the Profile Box, overrides the ProfileArgs.default_size and ProfileArgs.extend arguments (default: na)
export method draw(Profile this, int forced_width = na) =>
	this.init() 

	var tf_ms = timeframe.in_seconds() * 1000
	
	if na(this) ? false : this.buckets.size() > 0
		int bars_since_start = bar_index - this.start
		int bars_since_end = bar_index - this.end
		int start_t = time - bars_since_start * tf_ms

		int len = not na(forced_width) ? forced_width : this.args.extend ? math.max(bars_since_start, this.args.default_size) : this.args.default_size
		int ext_t = start_t + len * tf_ms
		int ext_i = this.start + len

		if this.args.show_background
			if na(this.plot_profile_bg)
				this.plot_profile_bg := D.Box.new(chart.point.new(start_t, this.start, this.h), chart.point.new(ext_t, ext_i, this.l), args = this.args.args_profile_bg, text_args = D.BoxTextArgs.new())
			this.plot_profile_bg.left_top.update(start_t, this.start, this.h)
			this.plot_profile_bg.right_bottom.update(ext_t, ext_i, this.l)
			this.plot_profile_bg.draw()

		if this.args.show_profile
			poc_bucket = this.buckets.get(this.poc_bucket_index)
			for bucket in this.buckets
				if not na(bucket) and not na(poc_bucket)
					int bucket_len = math.ceil(len * bucket.value / poc_bucket.value)
					int bucket_end_i = this.start + bucket_len
					int bucket_end_t = start_t + bucket_len * tf_ms
					bucket_line_color = 
						 bucket.idx == this.poc_bucket_index ? this.args.args_poc_line.line_color : 
						 bucket.idx == this.vah_bucket_index or bucket.idx == this.val_bucket_index ? this.args.args_va_line.line_color : 
						 this.args.args_profile_line.line_color	
					bucket.draw(start_t, this.start, bucket_end_t, bucket_end_i, this.args.args_profile_line, bucket_line_color)

		if this.args.show_poc
			if na(this.plot_poc)
				this.plot_poc := D.Line.new(chart.point.new(start_t, this.start, this.poc), chart.point.new(ext_t, ext_i, this.poc), this.args.args_poc_line)
			this.plot_poc.start.update(start_t, this.start, this.poc)
			this.plot_poc.end.update(ext_t, ext_i, this.poc)
			this.plot_poc.draw()

			if this.args.show_labels
				if na(this.plot_poc_label)
					this.plot_poc_label := D.Label.new(this.plot_poc.end, 'POC', this.args.args_poc_label)
				this.plot_poc_label.txt := this.args.show_price_levels ? str.format('POC ({0})', this.poc) : 'POC'
				this.plot_poc_label.tooltip := str.format('POC ({0})', this.poc)
				this.plot_poc_label.draw()
			
		if this.args.show_va
			if na(this.plot_vah)
				this.plot_vah := D.Line.new(chart.point.new(start_t, this.start, this.vah), chart.point.new(ext_t, ext_i, this.vah), this.args.args_va_line)
			if na(this.plot_val)
				this.plot_val := D.Line.new(chart.point.new(start_t, this.start, this.val), chart.point.new(ext_t, ext_i, this.val), this.args.args_va_line)
			
			this.plot_vah.start.update(start_t, this.start, this.vah)
			this.plot_vah.end.update(ext_t, ext_i, this.vah)
			this.plot_vah.draw()
			this.plot_val.start.update(start_t, this.start, this.val)
			this.plot_val.end.update(ext_t, ext_i, this.val)
			this.plot_val.draw()

			if this.args.show_labels
				if na(this.plot_vah_label)
					this.plot_vah_label := D.Label.new(this.plot_vah.end, 'VAH', this.args.args_va_label)
				if na(this.plot_val_label)
					this.plot_val_label := D.Label.new(this.plot_val.end, 'VAL', this.args.args_va_label)

				this.plot_vah_label.txt := this.args.show_price_levels ? str.format('VAH ({0})', this.vah) : 'VAH'
				this.plot_vah_label.tooltip := str.format('VAH ({0})', this.vah)
				this.plot_vah_label.draw()
				this.plot_val_label.txt := this.args.show_price_levels ? str.format('VAL ({0})', this.val) : 'VAL'
				this.plot_val_label.tooltip := str.format('VAL ({0})', this.val)
				this.plot_val_label.draw()

			if this.args.show_va_fill 
				if na(this.plot_va_fill)
					this.plot_va_fill := D.LineFill.new(this.plot_val, this.plot_vah, this.args.va_fill_color)
				this.plot_va_fill.draw()
	this


//////////////////////////////////////////////////////////////////////////////////
//#region 								DEMO
//////////////////////////////////////////////////////////////////////////////////

var resolution = input.int(30, 'resolution')
var vah_pc = input.float(80, 'VAH %')
var val_pc = input.float(20, 'VAL %')


ProfileArgs args = ProfileArgs.new(
 show_poc = input.bool(true, 'show POC', inline = 'poc'), 
 args_poc_line = D.LineArgs.new(input.color(color.orange, '', inline = 'poc'), line.style_dashed, 1), 

 show_va = input.bool(true, 'Value Area', inline = 'va'), 
 args_va_line = D.LineArgs.new(input.color(color.yellow, '', inline = 'va'), line.style_dotted, 1), 
 show_va_fill = input.bool(true, 'fill', inline = 'va'), 
 va_fill_color = input.color(#ffeb3b0e, '', inline = 'va'),

 show_labels = input.bool(true, 'Show labels', inline = 'labels'),
 show_price_levels = input.bool(true, 'with price', inline = 'labels'),
 args_poc_label = D.LabelArgs.new(input.color(color.orange, '', inline = 'labels'), #00000000, style = label.style_label_left, size = size.tiny), 
 args_va_label =  D.LabelArgs.new(input.color(color.yellow, '', inline = 'labels'), #00000000, style = label.style_label_left, size = size.tiny), 

 show_profile = input.bool(true, 'show Profile', inline = 'profile'), 
 args_profile_line = D.LineArgs.new(input.color(#787b869e, '', inline = 'profile'), line.style_solid, 3), 
 show_background = input.bool(true, 'with background', inline = 'profile'), 
 args_profile_bg = D.BoxArgs.new(#00000000, bg_color = input.color(#787b860f, '', inline = 'profile')),
 
 extend = input.bool(true, 'extend profile', inline = 'extend'), 
 default_size = input.int(20, 'min width', inline = 'extend')
 )

var g_use_cases = 'DEMO'
var use_case = input.string('High Volume Candle + LTF Detail', 'Use Case', ['High Volume Candle + LTF Detail', 'High Volume Candle + Lookback Range', 'Current Candle Live', 'Current Range Live'], group = g_use_cases)
var use_case_LTF = str.contains(use_case, 'LTF')
var use_case_lookback = str.contains(use_case, 'Lookback')
var use_case_live = str.contains(use_case, 'Candle Live')
var use_case_live_range = str.contains(use_case, 'Range Live')

/////////////////////////////////////////////////////////////////////////////////////////////////////
// 						USE CASE (profile single high volume candle with ltf data)
/////////////////////////////////////////////////////////////////////////////////////////////////////

//@description returns a lower timeframe to get a reasonable amount of data for volume spread analysis (>1M -> 1D) / (>1W -> 1H) / (>5m -> 10S) / (else 1S)
//@param allow_seconds if true this will use second based timeframes for the volume data (only for premium users)
export get_ltf(simple bool allow_seconds = false) =>
	tf_min = timeframe.in_seconds() / 60
	// 10080 minutes == 7 days
	// 43200 minutes == 30 days
	ltf = tf_min >= 43200 ? '1D' : tf_min >= 10080 ? '60' : tf_min >= 1440 ? '15' : tf_min >= 60 or not allow_seconds ? '1' : tf_min >= 5 ? '10S' : '1S'

var ltf = get_ltf()
var tf_min = timeframe.in_seconds() / 60
var ltf_label = label.new(bar_index, 0, str.format('res: {0}min -> {1}',tf_min, ltf), color = #00000000, textcolor = chart.fg_color, style = label.style_label_lower_left, size = size.tiny )
[ltf_tops, ltf_bottoms, ltf_values] = request.security_lower_tf(syminfo.tickerid, ltf, [high, low, volume])

var volume_threshold_len = input.int(50, 'High volume candle - SMA len', inline = 'volume_threshold', group = g_use_cases)
var volume_threshold_mult = input.float(3, 'x', minval = 1, step =0.1, inline = 'volume_threshold', group = g_use_cases)
var Profile obj_prev = na
var Profile obj = na

volume_sma = ta.sma(volume, volume_threshold_len)
if use_case_LTF
	if volume > volume_sma * volume_threshold_mult
		if not na(obj)
			if not na(obj_prev)
				obj_prev.buckets.delete()
				obj_prev.plot_profile_bg.delete()
				obj_prev.plot_poc_label.delete()
				obj_prev.plot_vah_label.delete()
				obj_prev.plot_val_label.delete()
			obj_prev := obj
		obj := create_profile(bar_index, ltf_tops, ltf_bottoms, ltf_values, resolution, vah_pc, val_pc, args)

		if ltf_values.size() == 0
			label.new(bar_index, close, "ltf data empty\ncan't plot profile", yloc = yloc.abovebar, color = color.red, textcolor = color.white, size = size.normal)

		if not na(obj.plot_profile_bg)
			ltf_label.set_xy(obj.plot_profile_bg.left_top.index, obj.plot_profile_bg.left_top.price)

	obj.draw()


/////////////////////////////////////////////////////////////////////////////////////////////////////
// 							USE CASE (profile timeframe from chart history candles)
/////////////////////////////////////////////////////////////////////////////////////////////////////


var ProfileArgs args2 = args.copy()
if barstate.isfirst
	args2.va_fill_color := #fe84090e
	args2.args_poc_line := D.LineArgs.new(color.red, line.style_solid, 2)
	args2.args_va_line := D.LineArgs.new(color.orange, line.style_dotted, 2)
	args2.args_poc_label := D.LabelArgs.new(color.red, #00000000, style = label.style_label_left, size = size.tiny)
	args2.args_va_label :=  D.LabelArgs.new(color.orange, #00000000, style = label.style_label_left, size = size.tiny)
	args2.args_profile_line := D.LineArgs.new(#787b869e, line.style_solid, 4)



_history(src, int len, int offset = 0) =>
	start_idx = math.max(0, bar_index - len - offset)
	end_idx = math.max(0, math.min(bar_index - offset, bar_index))
	len_sanitized = end_idx - start_idx

	var buffer = array.from(src)
	if not barstate.isfirst
		buffer.push(src)

	if buffer.size() > len + offset
		buffer.shift()

	buffer.slice(0,len_sanitized)

//@function allows fetching an array of values from the history series with offset from current candle
export history(int src, int len, int offset = 0) => _history(src, len, offset)
//@function allows fetching an array of values from the history series with offset from current candle
export history(float src, int len, int offset = 0) => _history(src, len, offset)
//@function allows fetching an array of values from the history series with offset from current candle
export history(bool src, int len, int offset = 0) => _history(src, len, offset)
//@function allows fetching an array of values from the history series with offset from current candle
export history(string src, int len, int offset = 0) => _history(src, len, offset)


// direct source previous candles
var history_len = input.int(20, 'Range Candles', inline = 'range')
var history_offset = input.int(2, 'Range Offset', inline = 'range')
tops = history(high, history_len, history_offset)
bottoms = history(low, history_len, history_offset)
values_volume = history(volume, history_len, history_offset)

var Profile obj2_prev = na
var Profile obj2 = na
if use_case_lookback
	if volume > volume_sma * volume_threshold_mult
		if not na(obj2)
			if bar_index - obj2.start < args2.default_size
				if not na(obj2_prev)
					obj2_prev.delete()
				obj2_prev := obj2
			else
				obj2.buckets.delete()
				obj2.plot_profile_bg.delete()
		obj2 := create_profile(bar_index, tops, bottoms, values_volume, resolution, vah_pc, val_pc, args2)
		range_size = values_volume.size()
		if bar_index > range_size + history_offset
			range2_line = line.new(bar_index - (range_size + history_offset),  obj2.l, bar_index - history_offset,  obj2.l, color = color.gray, width = 2)
		
	obj2.draw()


/////////////////////////////////////////////////////////////////////////////////////////////////////
// 							USE CASE (profile timeframe from chart history candles)
/////////////////////////////////////////////////////////////////////////////////////////////////////

var Profile obj3 = na
if use_case_live
	obj3 := create_profile(bar_index, ltf_tops, ltf_bottoms, ltf_values, resolution, vah_pc, val_pc, args).draw()
	if barstate.isconfirmed
		obj3.delete()

var Profile obj4 = na
var line range4_line = line.new(na, na, na, na, color = color.gray, width = 2)
if use_case_live_range
	range_size = values_volume.size()
	obj4 := create_profile(bar_index, tops, bottoms, values_volume, resolution, vah_pc, val_pc, args).draw()

	if bar_index > range_size + history_offset
		range4_line.set_xy1(bar_index - (range_size + history_offset),  obj4.l)
		range4_line.set_xy2(bar_index - history_offset,  obj4.l)
	if barstate.isconfirmed
		obj4.delete()

plot(bar_index, 'bar index', display = display.data_window)
var bool show_ltf_bug_note = input.bool(true, 'show LTF bug note', tooltip = 'at the time of creating this, TV had a bug where no LTF data was available during Replay Stepping, causing the profile calculation to fail. When it is fixed this should not show anymore. The Jump to... tool works though')
plotshape(show_ltf_bug_note and (na(ltf_values) ? true : ltf_values.size() == 0), 'No LTF data available for this bar', shape.xcross, location.bottom, color.red, size = size.tiny)
