CandlesGroup_Types
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © SteinG
//@version=5

// @description CandlesGroup Ojbect Type v10

library("CandlesGroup_Types", overlay = true)

//#region ****************** CandlesGroup Types ******************

// @type A point on chart
// @field price : price value
// @field bar : bar index
// @field bartime : time in UNIX format of bar
export type Point
	float price
	int bar
	int bartime


// @type Property object which contains values of all candles 
// @field name : name of property
// @field values : an array stores values of all candles. Size of array = bar_index
export type Property
    string name
    array<float> values


// @type Candles Group object which contains properties of all candles
// @field tf : timeframe
// @field is_size_increased : if true, size of value array increased at that candle. Default is false
// @field propertyNames : an array stores property names. Use as keys to get values
// @field properties : array of Property objects
export type CandlesGroup
    string tf = timeframe.period
    bool is_size_increased = false
    array<string> propertyNames
    array<Property> properties

//#endregion ******************


//#region ****************** CandlesGroup Methods ******************

export method init(CandlesGroup _self)=>
    if na(_self.propertyNames) and na(_self.properties)
        _self.propertyNames := array.from("index",
                               "time",
                               "open",
                               "high",
                               "low",
                               "close",
                               "bodyTop",
                               "bodyBottom",
                               "bodySize",
                               "candleSize",
                               "candleDirection",
                               "oc2"
                               )
        _self.properties := array.new<Property>()
        for i = 0 to _self.propertyNames.size()-1
            _self.properties.push(Property.new(_self.propertyNames.get(i), array.new_float()))
    _self


export method init(CandlesGroup _self, string[] propertyNames)=>
    if na(_self.propertyNames) and na(_self.properties)
        _self.propertyNames := propertyNames
        _self.properties := array.new<Property>()
        for i = 0 to _self.propertyNames.size()-1
            _self.properties.push(Property.new(_self.propertyNames.get(i), array.new_float()))
    _self


// @function get values array from a given property name
// @param _self : CandlesGroup object
// @param key : key name of selected property. Default is "index"
// @returns values array
export method get(CandlesGroup _self, string key="index")=>
    propertyID = _self.propertyNames.indexof(key)
    if propertyID < 0
        runtime.error("Get key error. Key not found!") 
    Property property = _self.properties.get(propertyID)
    property.values


// @function get size of values array
// @param _self : CandlesGroup object
// @returns size of values array
export method size(CandlesGroup _self)=>_self.properties.get(0).values.size()


// @function get last index of values array
// @param _self : CandlesGroup object
// @returns last index of values array
export method lastIndex(CandlesGroup _self)=>
    int lastindex = na
    if _self.size() > 0
        lastindex := _self.size()-1
    lastindex


// @function push single value to specific property
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param value : property value
// @returns CandlesGroup object
export method push(CandlesGroup _self, string key, float value)=>
    _self.get(key).push(value)
    _self


export method push(CandlesGroup _self, array<float> arr)=>
    if na(_self.properties)
        runtime.error("Candle Properties not found. Please init() before push!") 
    if arr.size() != _self.propertyNames.size()
        runtime.error("No. of properties not match!") 
    for i = 0 to arr.size()-1
        _self.properties.get(i).values.push(arr.get(i))
    _self


// @function populate ohlc to CandlesGroup
// @param _self : CandlesGroup object
// @param ohlc : array of ohlc
// @returns CandlesGroup object
export method populate(CandlesGroup _self, float[] ohlc)=>
    if na(ohlc)
        runtime.error("ohlc not found!")
    if ohlc.size() != 4
        runtime.error("ohlc size must be equal to 4!")
    _self.init()
    _open = ohlc.get(0)
    _high = ohlc.get(1)
    _low = ohlc.get(2)
    _close = ohlc.get(3)

    if not na(_self.propertyNames) and not na(_self.properties) and not (na(_open) or na(_high) or na(_low) or na(_close))
        propertyValues = array.from(bar_index,
                                 time,
                                 _open,
                                 _high,
                                 _low,
                                 _close,
                                 math.max(_open, _close),                       //bodyTop
                                 math.min(_open, _close),                       //bodyBottom
                                 math.abs(_open - _close),                      //bodySize
                                 _high - _low,                                  //candleSize
                                 _close > _open ? 1 : _close < _open ? -1 : 0,  //candleDirection
                                 (_open + _close) / 2                           //oc2
                                 )
        _self.push(propertyValues)
        _self.is_size_increased := true
    else
        _self.is_size_increased := false
    _self


// @function populate values base on given properties Names
// @param _self : CandlesGroup object
// @param values : array of property values
// @param propertiesNames : an array stores property names. Use as keys to get values
// @returns CandlesGroup object
export method populate(CandlesGroup _self, float[] values, string[] propertiesNames)=>
    if na(values)
        runtime.error("values not found!")
    if na(propertiesNames)
        runtime.error("propertiesNames not found!")
    _self.init(propertiesNames)

    if not na(_self.propertyNames) and not na(_self.properties)
        propertyValues = values
        _self.push(propertyValues)
        _self.is_size_increased := true
    else
        _self.is_size_increased := false
    _self


// @function populate values (default setup)
// @param _self : CandlesGroup object
// @returns CandlesGroup object
export method populate(CandlesGroup _self)=>
    _self.populate(array.from(open, high, low, close))


// @function get property value on previous candles. For current candle, use *.lookback()
// @param arr : array of selected property values
// @param bars_lookback : number of candles lookback. 0 = current candle. Default is 0
// @returns single property value
export method lookback(float[] arr, int bars_lookback=0)=>
    if arr.size() > bars_lookback
        arr.get(arr.size() - 1 - bars_lookback)
    else
        na


// @function get the highest property value between specific candles
// @param _self : CandlesGroup object
// @param hiSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns the highest value within candles
export method highest_within_bars(CandlesGroup _self, string hiSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar   = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    float result = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisValue = _self.get(hiSource).get(j)
            if j == startBar
                result := thisValue
                continue
            if result < thisValue
                result := thisValue
    result


// @function get the highest property value and array index between specific candles
// @param _self : CandlesGroup object
// @param returnWithIndex : the function only applicable when it is true
// @param hiSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns [the highest value within candles, Array index of that candle]
export method highest_within_bars(CandlesGroup _self, bool returnWithIndex, string hiSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar   = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    float result = na
    int resultID = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisValue = _self.get(hiSource).get(j)
            if j == startBar
                resultID := j
                result := thisValue
                continue
            if result < thisValue
                resultID := j
                result := thisValue
    if not returnWithIndex
        resultID := na
    [result, resultID]



// @function get a Point object which contains highest property value between specific candles
// @param _self : CandlesGroup object
// @param hiSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns Point object contains highest property value
export method highest_point_within_bars(CandlesGroup _self, string hiSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar   = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    int pointer     = na
    int resultIndex = na
    int resultTime  = na
    float result    = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisIndex = int(_self.get('index').get(j))
            thisTime  = int(_self.get('time').get(j))
            thisValue = _self.get(hiSource).get(j)
            if j == startBar
                pointer := j
                resultIndex := thisIndex
                resultTime := thisTime
                result := thisValue
                continue
            if result < thisValue
                pointer := j
                resultIndex := thisIndex
                resultTime := thisTime
                result := thisValue
    if na(result) or na(pointer)
        na
    else
        Point.new(result, resultIndex, resultTime)


// @function get the lowest property value between specific candles
// @param _self : CandlesGroup object
// @param loSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns the lowest value within candles
export method lowest_within_bars(CandlesGroup _self, string loSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    float result = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisValue = _self.get(loSource).get(j)
            if j == startBar
                result := thisValue
                continue
            if result > thisValue
                result := thisValue
    result


// @function get the lowest property value and array index between specific candles
// @param _self : CandlesGroup object
// @param returnWithIndex : the function only applicable when it is true
// @param loSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns [the highest value within candles, Array index of that candle]
export method lowest_within_bars(CandlesGroup _self, bool returnWithIndex, string loSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar   = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    float result = na
    int resultID = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisValue = _self.get(loSource).get(j)
            if j == startBar
                resultID := j
                result := thisValue
                continue
            if result > thisValue
                resultID := j
                result := thisValue
    if not returnWithIndex
        resultID := na
    [result, resultID]


// @function get a Point object which contains lowest property value between specific candles
// @param _self : CandlesGroup object
// @param loSource : key name of selected property
// @param start : start bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true
// @param end : end bar for calculation. Default is candles lookback value from current candle. 'index' value is used if 'useIndex' = true. Default is 0
// @param useIndex : use index instead of lookback value. Default = false
// @returns Point object contains lowest property value
export method lowest_point_within_bars(CandlesGroup _self, string loSource, int start, int end=0, bool useIndex=false)=>
    int startBar = na
    int endBar   = na
    if not useIndex
        _start = start <= 0 ? 0 : start - 1
        startBar := _self.lastIndex() - _start
        endBar   := _self.lastIndex() - end
    else
        if _self.lastIndex() >= start and _self.lastIndex() >= end and start >= 0 and end >= 0
            startBar := start
            endBar   := end
    int pointer     = na
    int resultIndex = na
    int resultTime  = na
    float result    = na
    if startBar >= 0 and endBar >= 0
        for j = startBar to endBar
            thisIndex = int(_self.get('index').get(j))
            thisTime  = int(_self.get('time').get(j))
            thisValue = _self.get(loSource).get(j)
            if j == startBar
                pointer := j
                resultIndex := thisIndex
                resultTime := thisTime
                result := thisValue
                continue
            if result > thisValue
                pointer := j
                resultIndex := thisIndex
                resultTime := thisTime
                result := thisValue
    if na(result) or na(pointer)
        na
    else
        Point.new(result, resultIndex, resultTime)


// @function Convert UNIX time to bar index of active chart
// @param _self : CandlesGroup object
// @param t : UNIX time
// @returns bar index
export method time2bar(CandlesGroup _self, int t)=>
    int result = na
    start = _self.get('time').size() - 1
    end = 0
    tf = timeframe.in_seconds()
    t_by_tf = math.floor(t / 1000 / tf)
    for i = start to end
        element = math.floor(_self.get('time').get(i) / 1000 / tf)
        if(t_by_tf == element)
            result := int(_self.get('index').get(i))
            break
    if na(result)
        runtime.error("Bar index not found!")
    int(result)


// @function Convert timestamp to bar index of active chart. User defined timezone required
// @param _self : CandlesGroup object
// @param timezone : User defined timezone
// @param YYYY : Year
// @param MMM : Month
// @param DD : Day
// @param hh : Hour. Default is 0
// @param mm : Minute. Default is 0
// @param ss : Second. Default is 0
// @returns bar index
export method time2bar(CandlesGroup _self, string timezone, int YYYY, int MMM, int DD, int hh=0, int mm=0, int ss=0)=>
    _self.time2bar(timestamp(timezone,YYYY,MMM,DD,hh,mm,ss))


// @function Convert timestamp to bar index of active chart
// @param _self : CandlesGroup object
// @param YYYY : Year
// @param MMM : Month
// @param DD : Day
// @param hh : Hour. Default is 0
// @param mm : Minute. Default is 0
// @param ss : Second. Default is 0
// @returns bar index
export method time2bar(CandlesGroup _self, int YYYY, int MMM, int DD, int hh=0, int mm=0, int ss=0)=>
    _self.time2bar(syminfo.timezone, YYYY, MMM, DD, hh, mm, ss)


// @function Convert bar index of active chart to UNIX time
// @param _self : CandlesGroup object
// @param index : bar index
// @returns UNIX time
export method bar2time(CandlesGroup _self, int index)=>
    int(_self.get('time').get(index))


// @function Get array index of CandlesGroup from bar index of active chart
// @param _self : CandlesGroup object
// @param index : bar index
// @returns array index
export method bar2arrID(CandlesGroup _self, int index)=>
    int result = na
    start = _self.get('index').size() - 1
    end = 0
    for i = start to end
        element = int(_self.get('index').get(i))
        if(index == element)
            result := i
            break
    if na(result)
        runtime.error("ArrayID not found!")
    result


// @function Get array index of CandlesGroup from UNIX time
// @param _self : CandlesGroup object
// @param t : UNIX time
// @returns array index
export method time2arrID(CandlesGroup _self, int t)=>
    int result = na
    start = _self.get('time').size() - 1
    end = 0
    for i = start to end
        element = int(_self.get('time').get(i))
        if(t == element)
            result := i
            break
    if na(result)
        runtime.error("ArrayID not found!")
    result


// @function get single property value from UNIX time
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param t : UNIX time
// @returns single property value
export method get_prop_from_time(CandlesGroup _self, string key, int t)=>_self.get(key).get(_self.time2arrID(t))


// @function get single property value from timestamp. User defined timezone required
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param timezone : User defined timezone
// @param YYYY : Year
// @param MMM : Month
// @param DD : Day
// @param hh : Hour. Default is 0
// @param mm : Minute. Default is 0
// @param ss : Second. Default is 0
// @returns single property value
export method get_prop_from_time(CandlesGroup _self, string key, string timezone, int YYYY, int MMM, int DD, int hh=0, int mm=0, int ss=0)=>
    _self.get_prop_from_time(key, timestamp(timezone,YYYY,MMM,DD,hh,mm,ss))


// @function get single property value from timestamp
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param YYYY : Year
// @param MMM : Month
// @param DD : Day
// @param hh : Hour. Default is 0
// @param mm : Minute. Default is 0
// @param ss : Second. Default is 0
// @returns single property value
export method get_prop_from_time(CandlesGroup _self, string key, int YYYY, int MMM, int DD, int hh=0, int mm=0, int ss=0)=>
    _self.get_prop_from_time(key, syminfo.timezone, YYYY, MMM, DD, hh, mm, ss)


// @function The sma function returns the moving average
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param length : number of bars
// @returns float value
export method sma(CandlesGroup _self, string key, int length)=>
    srcArr = _self.get(key)
    float result = na
    if srcArr.size() > length - 1
        sum = 0.0
        for i = 0 to length - 1
            sum := sum + srcArr.lookback(i) / length
        result := sum
    result


// @function The ema function returns the exponentially weighted moving average.
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param length : number of bars
// @returns float value
export method ema(CandlesGroup _self, string key, int length)=>
    srcArr = _self.get(key)
    alpha = 2 / (length + 1)
    start = length * 10
    float result = na
    if srcArr.size() > start
        float ema = na
        for i = start to 0
            if na(ema)
                ema := srcArr.lookback(i)
                continue
            ema := alpha * srcArr.lookback(i) + (1 - alpha) * nz(ema)
        result := ema
    result


method rma(array<float> src, int length)=>
    float result = na
    if src.size() > 0
        alpha = 1 / length
        float rma = na
        for i = 0 to src.size()-1
            element = src.get(i)
            if i == 0
                rma := element
                continue
            rma := alpha * src.get(i) + (1 - alpha) * nz(rma)
        result := rma
    result


// @function Relative Moving Average. Precision up to 4 s.f.
// @param _self : CandlesGroup object
// @param key : key name of selected property
// @param length : number of bars
// @returns float value
export method rma(CandlesGroup _self, string key, int length)=>
    srcArr = _self.get(key)
    alpha = 1 / length
    start = length * 10
    float result = na
    if srcArr.size() > start
        float rma = na
        for i = start to 0
            if na(rma)
                rma := _self.sma(key,length)
                continue
            rma := alpha * srcArr.lookback(i) + (1 - alpha) * nz(rma)
        result := rma
    result


// @function True range
// @param _self : CandlesGroup object
// @param offset : history bar offset. Default is 0
// @returns float value
export method tr(CandlesGroup _self, int offset=0)=>
    float result = na
    if _self.size() == 1
        result := _self.get('high').lookback(offset) - _self.get('low').lookback(offset)
    else if _self.size() > 1
        result := math.max(_self.get('high').lookback(offset) - _self.get('low').lookback(offset), 
                         math.abs(_self.get('high').lookback(offset) - _self.get('close').lookback(offset+1)), 
                         math.abs(_self.get('low').lookback(offset) - _self.get('close').lookback(offset+1)) 
                         )
    result


// @function Average true range returns the RMA of true range. Precision up to 4 s.f.
// @param _self : CandlesGroup object
// @param length : number of bars
// @returns float value
export method atr(CandlesGroup _self, int length)=>
    float result = na
    array<float> trArr = array.new_float()
    start = length * 10
    if _self.size() > start
        for i = start to 0
            tr = _self.tr(i)
            trArr.push(tr)
        result := trArr.rma(length)
    result


//#endregion ******************


//#region ———————————————————————————————————————————————————————————— Usage & Examples ————————————————————————————————————————————————————————————

// //declare and init CandlesGroup ojbect
// var candles = CandlesGroup.new()
// candles.populate()


// //These are the same
// high10 = high[10]
// obj_high10 = candles.get('high').lookback(10)
// plot(high10, "high10", color.new(#002FFF,0))
// plot(obj_high10, "high10", color.new(#00FF15,0))


// //These are the same
// ta_sma_close20 = ta.sma(close, 20)
// obj_sma_close20 = ta.sma(candles.get('close').lookback(), 20)
// plot(ta_sma_close20, "ta_sma_close20", color.new(#FFD000,0))
// plot(obj_sma_close20, "obj_sma_close20", color.new(#00F7FF,0))


// //You can use a very large lookback value
// ta_sma_low2000 = ta.sma(low, 2000)
// obj_sma_low2000 = ta.sma(candles.get('low').lookback(), 2000)
// plot(ta_sma_low2000, "ta_sma_low2000", color.new(#C300FF,0))
// plot(obj_sma_low2000, "obj_sma_low2000", color.new(#EEFF00,0))


// //These are the same
// ta_highest_10 = ta.highest(high, 10)
// obj_highest_10 = candles.highest_within_bars('high', 10)
// plot(ta_highest_10, "ta_highest_10", color.new(#61B0DD,0))
// plot(obj_highest_10, "obj_highest_10", color.new(#9CDF30,0))


// //These are the same
// ta_lowestbars_20 = ta.lowestbars(low, 20)
// [obj_lowestbars_20_value ,obj_lowestbars_20_ID] = candles.lowest_within_bars(true, 'low', 20)
// plot(low[-ta_lowestbars_20], "ta_lowestbars_20", color.new(#46D1A3,0), style=plot.style_stepline)
// plot(low[bar_index - obj_lowestbars_20_ID], "obj_lowestbars_20", color.new(#DAB03C,0), style=plot.style_stepline)


// //These are the same
// ta_highest_10_20 = ta.highest(high, 10)[10]
// obj_highest_10_20 = candles.highest_within_bars('high', 20, 10)
// plot(ta_highest_10_20, "ta_highest_10_20", color.new(#3D11B8,0))
// plot(obj_highest_10_20, "obj_highest_10_20", color.new(#00FF37,0))


// //CandlesGroup types do not have local scope issue. They are explicitly stored in global scope
// l1_x1 = bar_index + ta.highestbars(high, 10)
// l1_x2 = bar_index + ta.lowestbars(high, 20)
// l1_y1 = ta.highest(high,10)
// l1_y2 = ta.lowest(20)
// if barstate.islast
//     //This line drawn correctly by using global scope param
//     var l1 = line.new(l1_x1, l1_y1, l1_x2, l1_y2, xloc.bar_index, color=color.new(#FF0000, 0), width=2)
//     //This line drawn incorrectly by using local scope param
//     var l2 = line.new(bar_index+ta.highestbars(high, 10), ta.highest(high,10), 
//                      bar_index+ta.lowestbars(high, 20), ta.lowest(20), 
//                      xloc.bar_index, color=color.new(#91FF00, 0), width=3)
//     //You can call CandlesGroup methods in local scope directly
//     var l3 = line.new(candles.highest_point_within_bars('high',10).bar, candles.highest_point_within_bars('high',10).price, 
//                      candles.lowest_point_within_bars('low',20).bar, candles.lowest_point_within_bars('low',20).price, 
//                      xloc.bar_index, color=color.new(#00F7FF, 70), width=7)


// //You can get any candle properties by timestamp
// if barstate.islast
//     t = timestamp(2023,7,12,10,34)
//     var lbl1 = label.new(int(candles.get_prop_from_time('index',t)), 
//                          candles.get_prop_from_time('high',t), 
//                          str.format_time(int(candles.get_prop_from_time('time',t)), "YYYY/MM/dd HH:mm", syminfo.timezone),
//                          xloc.bar_index,
//                          yloc.price,
//                          color.white)


// //populate candlesGroup using data of current timeframe by request.security()
// [o1, h1, l1, c1] = request.security(syminfo.tickerid, timeframe.period, [open, high, low, close], barmerge.gaps_on, barmerge.lookahead_off)
// var candles_1 = CandlesGroup.new()
// candles_1.populate(array.from(o1, h1, l1, c1))
// plot(candles_1.get('high').lookback(), "candles_1m_high", color.new(#D1E049,0),style=plot.style_stepline)
// plot(candles_1.get('low').lookback(), "candles_1m_low", color.new(#00CCFF,0),style=plot.style_stepline)


// //You can declare a new CandlesGroup which contains other timeframe data
// tf_30m = '30'
// [o30m, h30m, l30m, c30m] = request.security(syminfo.tickerid, tf_30m, [open[1], high[1], low[1], close[1]], barmerge.gaps_on, barmerge.lookahead_on)
// var candles_30m = CandlesGroup.new(tf_30m)
// candles_30m.populate(array.from(o30m, h30m, l30m, c30m))
// plot(candles_30m.get('high').lookback(), "candles_30m_high", color.new(#D1E049,0),style=plot.style_stepline)
// plot(candles_30m.get('low').lookback(), "candles_30m_low", color.new(#00CCFF,0),style=plot.style_stepline)
// plot(candles_30m.is_size_increased ? 1 : 0, "is_size_increased", color.new(#FF1E00,0), display=display.status_line)


// //You can get any HTF candles' properties by timestamp
// if barstate.islast
//     t = timestamp(2023,10,10,15,00)
//     var lbl1 = label.new(int(candles_30m.get_prop_from_time('index',t)), 
//                          candles_30m.get_prop_from_time('high',t), 
//                          str.format_time(int(candles_30m.get_prop_from_time('time',t)), "YYYY/MM/dd HH:mm", syminfo.timezone),
//                          xloc.bar_index,
//                          yloc.price,
//                          color.white)


// //handling other timeframe data
// ta_highest_5_30m = request.security(syminfo.tickerid, '30', ta.highest(high, 5)[1], barmerge.gaps_on, barmerge.lookahead_on)
// [obj_highest_5_30m_value ,obj_highest_5_30m_ID] = candles_30m.highest_within_bars(true, 'high', 5)
// Point pt_highest_5_30m = candles_30m.highest_point_within_bars('high', 5)
// float series_pt_highest_5_30m = na
// if not na(pt_highest_5_30m)
//     series_pt_highest_5_30m := pt_highest_5_30m.price
// plot(ta_highest_5_30m, "ta_highest_5_30m", color.new(#46D1A3,0), style=plot.style_stepline_diamond)
// plot(obj_highest_5_30m_value, "obj_highest_5_30m_value", color.new(#933CDA,0), style=plot.style_stepline)
// plot(series_pt_highest_5_30m, "pt_highest_5_30m",color.new(#E28D8D,0),style=plot.style_stepline_diamond)
// if barstate.islast
//     var pt_high_lbl = label.new(
//      x            = na,
//      y            = na,
//      xloc         = xloc.bar_index,
//      yloc         = yloc.price,
//      textcolor   = color.white
//      )
//     pt_high_lbl.set_x(pt_highest_5_30m.bar)
//     pt_high_lbl.set_y(series_pt_highest_5_30m)
//     pt_high_lbl.set_text("Price : "+str.tostring(series_pt_highest_5_30m)+
//                          "\nBar Index : "+str.tostring(pt_highest_5_30m.bar)+
//                          "\nBar Time : "+str.format_time(pt_highest_5_30m.bartime,"Y/M/d HH:mm",syminfo.timezone)
//                          )


// ta_lowest_5_30m = request.security(syminfo.tickerid, '30', ta.lowest(low, 5)[1], barmerge.gaps_on, barmerge.lookahead_on)
// [obj_lowest_5_30m_value ,obj_lowest_5_30m_ID] = candles_30m.lowest_within_bars(true, 'low', 5)
// Point pt_lowest_5_30m = candles_30m.lowest_point_within_bars('low', 5)
// float series_pt_lowest_5_30m = na
// if not na(pt_lowest_5_30m)
//     series_pt_lowest_5_30m := pt_lowest_5_30m.price
// plot(ta_lowest_5_30m, "ta_lowest_5_30m", color.new(#46D1A3,0), style=plot.style_stepline_diamond)
// plot(obj_lowest_5_30m_value, "obj_lowest_5_30m_value", color.new(#933CDA,0), style=plot.style_stepline)
// plot(series_pt_lowest_5_30m, "pt_lowest_5_30m",color.new(#E28D8D,0),style=plot.style_stepline_diamond)
// if barstate.islast
//     var pt_low_lbl = label.new(
//      x            = na,
//      y            = na,
//      xloc         = xloc.bar_index,
//      yloc         = yloc.price,
//      style        = label.style_label_up,
//      textcolor    = color.white
//      )
//     pt_low_lbl.set_x(pt_lowest_5_30m.bar)
//     pt_low_lbl.set_y(series_pt_lowest_5_30m)
//     pt_low_lbl.set_text("Price : "+str.tostring(series_pt_lowest_5_30m)+
//                          "\nBar Index : "+str.tostring(pt_lowest_5_30m.bar)+
//                          "\nBar Time : "+str.format_time(pt_lowest_5_30m.bartime,"Y/M/d HH:mm",syminfo.timezone)
//                          )


// ta_highestbars_5_30m = request.security(syminfo.tickerid, '30', ta.highestbars(high, 5)[1], barmerge.gaps_on, barmerge.lookahead_on)
// [obj_highestbars_5_30m_value ,obj_highestbars_5_30m_ID] = candles_30m.highest_within_bars(true, 'high', 5)
// plot(-ta_highestbars_5_30m, "ta_highestbars_5_30m", color.new(#46D1A3,0), style=plot.style_stepline_diamond)
// plot(candles_30m.lastIndex() - obj_highestbars_5_30m_ID, "obj_highestbars_5_30m", color.new(#DAB03C,0), style=plot.style_stepline)


// ta_lowestbars_5_30m = request.security(syminfo.tickerid, '30', ta.lowestbars(low, 5)[1], barmerge.gaps_on, barmerge.lookahead_on)
// [obj_lowestbars_5_30m_value ,obj_lowestbars_5_30m_ID] = candles_30m.lowest_within_bars(true, 'low', 5)
// plot(-ta_lowestbars_5_30m, "ta_lowestbars_5_30m", color.new(#46D1A3,0), style=plot.style_stepline_diamond)
// plot(candles_30m.lastIndex() - obj_lowestbars_5_30m_ID, "obj_lowestbars_5_30m", color.new(#DAB03C,0), style=plot.style_stepline)



// //You can get array index of candlesGroup of other timeframe by bar_index
// if candles_30m.size() > 5
//     test_bar = int(candles_30m.get('index').lookback(5))
//     arrID = candles_30m.bar2arrID(test_bar)
//     var lbl2 = label.new(na,na,"",xloc.bar_index,yloc.price,color.white)
//     lbl2.set_xy(int(candles_30m.get('index').get(arrID)), candles_30m.get('high').get(arrID))
//     lbl2.set_text("test_bar :"+str.tostring(test_bar)+"\nArrayID : "+str.tostring(arrID)+"\nbar_index : "+str.tostring(int(candles_30m.get('index').get(arrID))))


// //You can get array index of candlesGroup of other timeframe by UNIX time
// if candles_30m.size() > 10
//     test_time = int(candles_30m.get('time').lookback(10))
//     arrID = candles_30m.time2arrID(test_time)
//     var lbl3 = label.new(na,na,"",xloc.bar_index,yloc.price,color.white)
//     lbl3.set_xy(int(candles_30m.get('index').get(arrID)), candles_30m.get('high').get(arrID))
//     lbl3.set_text("test_time :"+str.format_time(test_time,"YYYY/MM/dd HH:mm", syminfo.timezone)+ 
//                  "\nArrayID : "+str.tostring(arrID)+"\ntime : "+  
//                  str.format_time(int(candles_30m.get('time').get(arrID)),"YYYY/MM/dd HH:mm", syminfo.timezone))


// //These are the T.A. methods for multi-timeframe
// ta_sma_close20 = request.security(syminfo.tickerid, '30', ta.sma(close,20)[1], barmerge.gaps_on, barmerge.lookahead_on)
// obj_sma_close20 = candles_30m.sma('close',20)
// plot(ta_sma_close20, "ta_sma_close20", color.new(#002FFF,0),style=plot.style_stepline_diamond)
// plot(obj_sma_close20, "obj_sma_close20", color.new(#00FF15,0),style=plot.style_stepline_diamond)

// ta_ema_close20 = request.security(syminfo.tickerid, '30', ta.ema(close, 20)[1], barmerge.gaps_on, barmerge.lookahead_on)
// obj_ema_close20 = candles_30m.ema('close',20)
// plot(ta_ema_close20, "ta_ema_close20", color.new(#002FFF,0),style=plot.style_stepline_diamond)
// plot(obj_ema_close20, "obj_ema_close20", color.new(#00FF15,0),style=plot.style_stepline_diamond)

// ta_rma_close20 = request.security(syminfo.tickerid, '30', ta.rma(close, 20)[1], barmerge.gaps_on, barmerge.lookahead_on)
// obj_rma_close20 = candles_30m.rma('close',20)
// plot(ta_rma_close20, "ta_rma_close20", color.new(#002FFF,0),style=plot.style_stepline_diamond)
// plot(obj_rma_close20, "obj_rma_close20", color.new(#00FF15,0),style=plot.style_stepline_diamond)

// ta_tr_close20 = request.security(syminfo.tickerid, '30', ta.tr(true)[1], barmerge.gaps_on, barmerge.lookahead_on)
// obj_tr_close20 = candles_30m.tr()
// plot(ta_tr_close20, "ta_tr_close20", color.new(#002FFF,0),style=plot.style_stepline_diamond)
// plot(obj_tr_close20, "obj_tr_close20", color.new(#00FF15,0),style=plot.style_stepline_diamond)

// ta_atr_close20 = request.security(syminfo.tickerid, '30', ta.atr(20)[1], barmerge.gaps_on, barmerge.lookahead_on)
// obj_atr_close20 = candles_30m.atr(20)
// plot(ta_atr_close20, "ta_atr_close20", color.new(#002FFF,0),style=plot.style_stepline_diamond)
// plot(obj_atr_close20, "obj_atr_close20", color.new(#00FF15,0),style=plot.style_stepline_diamond)



// //A table showing all property values in CandlesGroup
// testGroup = candles_30m
// Candle_showStats = input.bool(true, 'Show Table', group='Candle Info Table', inline='s2')
// CandletblPosition = input.string(position.top_right, '', group='Candle Info Table', options=[position.top_right, position.bottom_right, position.top_center, position.top_left, position.bottom_left], inline='s2')
// Candle_debugtbl_textSize = input.string(size.small, '', group='Candle Info Table', options=[size.tiny, size.small, size.normal, size.large, size.huge], inline='s2')

// candle_info_title_color     = color.maroon
// candle_info_title_txt_color = color.white
// candle_info_no_color        = #8BE8FF
// candle_info_id_color        = #12B7E9
// candle_info_cell_color      = #293001
// candle_info_cell_txt_color  = color.white
// if(barstate.islast and Candle_showStats)
//     var Candle_debugTbl = table.new(position = CandletblPosition, columns = 15, rows = 21 , border_color=color.black, border_width=2)
//     //header
//     headerTextArr = array.from("No.",
//                                "Array INDEX",
//                                "bar_index",
//                                "DIST",
//                                "DATETIME",
//                                "OPEN",
//                                "HIGH",
//                                "LOW",
//                                "CLOSE",
//                                "BODY TOP",
//                                "BODY BOTTOM",
//                                "BODY SIZE",
//                                "CANDLE SIZE",
//                                "CANDLE\nDIRECTION",
//                                "(OPEN+CLOSE)/2"
//                                )
//     for i = 0 to headerTextArr.size()-1
//         table.cell(Candle_debugTbl, i,  0, headerTextArr.get(i), bgcolor=candle_info_title_color, text_color=candle_info_title_txt_color, text_size=Candle_debugtbl_textSize)

//     //total inventory <= 20
//     if testGroup.size() > 0 and testGroup.size() <= 20
//         for i = 0 to testGroup.size() - 1
//             //cell values
//             _str_no         = str.tostring(i+1)
//             _str_arryID     = str.tostring(testGroup.lastIndex()-i)
//             _str_bar_index  = str.tostring(testGroup.get().lookback(i))
//             _str_dist       = str.tostring(bar_index - testGroup.get().lookback(i))
//             _str_datetime   = str.format_time(int(testGroup.get("time").lookback(i)), "Y/M/d HH:mm", syminfo.timezone)
//             _str_open       = str.tostring(testGroup.get("open").lookback(i))
//             _str_high       = str.tostring(testGroup.get("high").lookback(i))
//             _str_low        = str.tostring(testGroup.get("low").lookback(i))
//             _str_close      = str.tostring(testGroup.get("close").lookback(i))
//             _str_bodyTop    = str.tostring(testGroup.get("bodyTop").lookback(i))
//             _str_bodyBottom = str.tostring(testGroup.get("bodyBottom").lookback(i))
//             _str_bodySize   = str.tostring(testGroup.get("bodySize").lookback(i))
//             _str_candleSize = str.tostring(testGroup.get("candleSize").lookback(i))
//             _str_candleDir  = str.tostring(testGroup.get("candleDirection").lookback(i))
//             _str_oc2        = str.tostring(testGroup.get("oc2").lookback(i))
//             cellTextArr = array.from(_str_no,
//                                      _str_arryID,
//                                      _str_bar_index,
//                                      _str_dist,
//                                      _str_datetime,
//                                      _str_open,
//                                      _str_high,
//                                      _str_low,
//                                      _str_close,
//                                      _str_bodyTop,
//                                      _str_bodyBottom,
//                                      _str_bodySize,
//                                      _str_candleSize,
//                                      _str_candleDir,
//                                      _str_oc2
//                                      )
//             //populate cells
//             for j = 0 to cellTextArr.size()-1
//                 table.cell(Candle_debugTbl, j, i+1, cellTextArr.get(j), bgcolor=candle_info_cell_color, text_color=candle_info_cell_txt_color, text_size=Candle_debugtbl_textSize)
//             //specific column color
//             table.cell_set_bgcolor(Candle_debugTbl, 0, i+1, candle_info_no_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 1, i+1, candle_info_id_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 2, i+1, candle_info_no_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 3, i+1, candle_info_id_color)
//             for k = 0 to 3
//                 table.cell_set_text_color(Candle_debugTbl, k, i+1, color.black)
    
//     //total inventory > 20
//     else if testGroup.size() > 0
//         for i = 0 to testGroup.size() - 1
//             if i >= 20
//                 break
//             //cell values
//             _str_no         = str.tostring(i+1)
//             _str_arryID     = str.tostring(testGroup.lastIndex()-i)
//             _str_bar_index  = str.tostring(testGroup.get().lookback(i))
//             _str_dist       = str.tostring(bar_index - testGroup.get().lookback(i))
//             _str_datetime   = str.format_time(int(testGroup.get("time").lookback(i)), "Y/M/d HH:mm", syminfo.timezone)
//             _str_open       = str.tostring(testGroup.get("open").lookback(i))
//             _str_high       = str.tostring(testGroup.get("high").lookback(i))
//             _str_low        = str.tostring(testGroup.get("low").lookback(i))
//             _str_close      = str.tostring(testGroup.get("close").lookback(i))
//             _str_bodyTop    = str.tostring(testGroup.get("bodyTop").lookback(i))
//             _str_bodyBottom = str.tostring(testGroup.get("bodyBottom").lookback(i))
//             _str_bodySize   = str.tostring(testGroup.get("bodySize").lookback(i))
//             _str_candleSize = str.tostring(testGroup.get("candleSize").lookback(i))
//             _str_candleDir  = str.tostring(testGroup.get("candleDirection").lookback(i))
//             _str_oc2        = str.tostring(testGroup.get("oc2").lookback(i))
//             cellTextArr = array.from(_str_no,
//                                      _str_arryID,
//                                      _str_bar_index,
//                                      _str_dist,
//                                      _str_datetime,
//                                      _str_open,
//                                      _str_high,
//                                      _str_low,
//                                      _str_close,
//                                      _str_bodyTop,
//                                      _str_bodyBottom,
//                                      _str_bodySize,
//                                      _str_candleSize,
//                                      _str_candleDir,
//                                      _str_oc2
//                                      )
//             //populate cells
//             for j = 0 to cellTextArr.size()-1
//                 table.cell(Candle_debugTbl, j, i+1, cellTextArr.get(j), bgcolor=candle_info_cell_color, text_color=candle_info_cell_txt_color, text_size=Candle_debugtbl_textSize)
//             //specific column color
//             table.cell_set_bgcolor(Candle_debugTbl, 0, i+1, candle_info_no_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 1, i+1, candle_info_id_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 2, i+1, candle_info_no_color)
//             table.cell_set_bgcolor(Candle_debugTbl, 3, i+1, candle_info_id_color)
//             for k = 0 to 3
//                 table.cell_set_text_color(Candle_debugTbl, k, i+1, color.black)

//#endregion ————————————————————