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

//@version=5
//@description tracking highest and lowest with anchor point to track over dynamic periods, e.g. to track a Session HH/LL live and get the bar/time of the LTF wick that matches the HTF HH/LL
library("lib_tracking", overlay=true)

///////////////////////
//     TRACKING
///////////////////////

// DESIGN DECISION
// why anchored replacements for ta.highest / ta.highestbars / ta.lowest / ta.lowestbars:
// 1. they require a fixed length/lookback which makes it easier to calculate, but
// 2. this prevents us from tracking the HH/LL of a changing timeframe, e.g. live tracking the HH/LL of a running session or unfinished higher timeframe
// 3. tracking with anchor/start/reset flag allows to persist values until the next start/reset, so no other external storage is required

track_hl(series float target, int mode, bool reset = false, bool track_this_bar = true) =>
    var value = target
    var value_time = time
    var value_index = bar_index
    var track_highest = mode == 1
    var track_lowest = mode == -1

    if reset or track_this_bar
        if reset
         or track_highest and target > value
         or track_lowest and target < value
            value := target
            value_time := time
            value_index := bar_index
    
    [value, value_time, value_index]

//@description live-track highest value of an unfinished range (e.g. unfinished session or higher timeframe bar) (non constant length to ta.highest would trigger 'max_bars_back error')
//@param series to track
//@param reset boolean flag to restart tracking from this point (a.k.a anchor)
//@param track_this_bar allows enabling and disabling of tracking, e.g. before a session starts or after it ends, values can be kept until next reset.
export track_highest(series float value = high, bool reset = false, bool track_this_bar = true) =>
    [highest, highest_time, highest_index] = track_hl(value, 1, reset, track_this_bar)

//@description live-track lowest value of an unfinished range (e.g. unfinished session or higher timeframe bar) (non constant length to ta.lowest would trigger 'max_bars_back error')
//@param series to track
//@param reset boolean flag to restart tracking from this point (a.k.a anchor)
//@param track_this_bar allows enabling and disabling of tracking, e.g. before a session starts or after it ends, values can be kept until next reset.
export track_lowest(series float value = low, bool reset = false, bool track_this_bar = true) =>
    [lowest, lowest_time, lowest_index] = track_hl(value, -1, reset, track_this_bar)


//@description track the hh and ll within a higher timeframe bar, plus a signal when the bar is about to close to get the final values asap
//@param htf the higher timeframe in pinescript string notation
//@returns [bool started, bool closing, bool cancelled, float highest, int highest_time, int highest_index, float lowest, int lowest_time, int lowest_index]
export track_hl_htf(string htf, series float value_high = high, series float value_low = low) =>
    var previous_closing = false
    closing = time_close(htf) == time_close and barstate.isconfirmed
    started = timeframe.change(htf)
    cancelled = not previous_closing and started

    [highest, highest_time, highest_index] = track_hl(value_high, 1, started)
    [lowest, lowest_time, lowest_index] = track_hl(value_low, -1, started)
    previous_closing := closing

    [started, closing, cancelled, highest, highest_time, highest_index, lowest, lowest_time, lowest_index]


///////////////////////
//     DEMO
///////////////////////

// TRACKING HH AND LL IN DYNAMIC TIME FRAME
new_day = timeframe.change('1D')

[highest, highest_time, highest_index] = track_highest(high, new_day)
[lowest, lowest_time, lowest_index] = track_lowest(low, new_day)

plot(highest, 'highest')
plot(lowest, 'lowest')

if new_day
    // line.new(bar_index, 0, bar_index, 1, extend = extend.both)
    line.new(highest_time[1], highest[1], time, highest[1], xloc = xloc.bar_time)
    label.new(bar_index, highest[1], 'hh', style = label.style_label_left)
    line.new(lowest_time[1], lowest[1], time, lowest[1], xloc = xloc.bar_time)
    label.new(bar_index, lowest[1], 'll', style = label.style_label_left)



[htf_started, htf_closing, htf_cancelled, htf_highest, htf_highest_time, htf_highest_index, htf_lowest, htf_lowest_time, htf_lowest_index] = track_hl_htf('2D') // 2D
plot(htf_highest, 'htf_highest', color = color.aqua)
plot(htf_lowest, 'htf_lowest', color = color.aqua)

if htf_started
    // line.new(bar_index, 0, bar_index, 1, extend = extend.both, color = color.aqua, style = line.style_dashed)
    line.new(htf_highest_time[1], htf_highest[1], time, htf_highest[1], xloc = xloc.bar_time, color = color.aqua)
    label.new(bar_index, htf_highest[1], '2d hh', style = label.style_label_left, color = color.aqua)
    line.new(htf_lowest_time[1], htf_lowest[1], time, htf_lowest[1], xloc = xloc.bar_time, color = color.aqua)
    label.new(bar_index, htf_lowest[1], '2d ll', style = label.style_label_left, color = color.aqua)

