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

//@version=5

// @description This library is a collection of weapons that will help us win the war against different markets.
library("arsenal", overlay=true)

//CONSTANTS
LINE_STYLE_SOLID = "Solid"
LINE_STYLE_DOTTED = "Dotted"
LINE_STYLE_DASHED = "Dashed"

LABEL_STYLE_NONE = "None"
LABEL_STYLE_XCROSS = "X cross"
LABEL_STYLE_CROSS = "Cross"
LABEL_STYLE_TRIANGLEUP = "Triangle up"
LABEL_STYLE_TRIANGLEDOWN = "Triangle down"
LABEL_STYLE_FLAG = "Flag"
LABEL_STYLE_CIRCLE = "Circle"
LABEL_STYLE_ARROWUP = "Arrow up"
LABEL_STYLE_ARROWDOWN = "Arrow down"
LABEL_STYLE_SQUARE = "Square"
LABEL_STYLE_DIAMOND = "Diamond"

LABEL_STYLE_LABELUP = "Label up"
LABEL_STYLE_LABELDOWN = "Label down"
LABEL_STYLE_LABELLEFT = "Label left"
LABEL_STYLE_LABELRIGHT = "Label right"
LABEL_STYLE_LABELLOWERLEFT = "Label lower left"
LABEL_STYLE_LABELLOWERRIGHT = "Label lower right"
LABEL_STYLE_LABELUPPERLEFT = "Label upper left"
LABEL_STYLE_LABELUPPERRIGHT = "Label upper right"
LABEL_STYLE_LABELCENTER = "Label center"

// @function Fix timeframe
// @param _inputTF - input timeframe
export fixTimeframe(simple string _inputTF) =>
    inputTF = str.upper(_inputTF)
    inputTF := switch(inputTF)
        "S" => "1S"
        "D" => "1D"
        "W" => "1W"
        "M" => "1M"
        => inputTF
    inputTF

// @function Compares if the2 input timeframes are the same...
// @param _inputTF - input timeframe
// @param _inputTF2 - input timeframe 2
export isSameTimeframe(simple string _inputTF, simple string _inputTF2) =>
    fixTimeframe(_inputTF) == fixTimeframe(_inputTF2)

// @function Is input timeframe equal to current timeframe
// @param _inputTF - input timeframe
export isEqualToCurrentTF(simple string _inputTF) =>
    isSameTimeframe(_inputTF, timeframe.period)
    
// @function Gets the unit of time
// @param _tf - timeframe in string
// @returns unit of time
export getTimeUnit_timeframe(simple string _tf)=>
    tf = str.upper(_tf)
    timeframe = str.replace_all(tf, "S","")
    timeframe := str.replace_all(timeframe, "H","")
    timeframe := str.replace_all(timeframe, "D","")
    timeframe := str.replace_all(timeframe, "W","")
    timeframe := str.replace_all(timeframe, "M","")
    timeframeInNumber = str.tonumber(timeframe)
    if (na(timeframeInNumber))
        timeframeInNumber := 1
    timeframeInNumber

// @function Gets the unit of time
// @returns unit of time
export getTimeUnit()=>
    timeframe = str.replace_all(timeframe.period, "S","")
    timeframe := str.replace_all(timeframe, "H","")
    timeframe := str.replace_all(timeframe, "D","")
    timeframe := str.replace_all(timeframe, "W","")
    timeframe := str.replace_all(timeframe, "M","")
    timeframeInNumber = str.tonumber(timeframe)
    if (na(timeframeInNumber))
        timeframeInNumber := 1
    timeframeInNumber
    
// @function Gets the label of unit of time based on current timeframe on chart
// @returns unit of time
export getTimeUnitLabelRaw()=>
    toReturn = ""
    if (timeframe.isseconds)
        toReturn := "s"
    if (timeframe.isminutes)
        toReturn := ""
    else if (timeframe.isdaily)
        toReturn := "D"
    else if (timeframe.isweekly)
        toReturn := "W"
    else if (timeframe.ismonthly)
        toReturn := "M"
    toReturn
    
// @function Multiply the current chart's timeframe
// @returns new timeframe
export multiplyTimeframe(int mult)=>
    str.tostring(getTimeUnit() * mult) + getTimeUnitLabelRaw()

// @function Gets the label of unit of time based on current timeframe on chart
// @returns unit of time
export getTimeUnitLabel()=>
    toReturn = ""
    timeframeInNumber = str.tonumber(timeframe.period)
    if (timeframe.isseconds)
        toReturn := "s"
    if (timeframe.isminutes)
        if (timeframeInNumber < 60)
            toReturn := "m"
        else 
            if ((timeframeInNumber % 60) > 0.0)
                toReturn := "m"
            else 
                toReturn := "H"
    else if (timeframe.isdaily)
        toReturn := "D"
    else if (timeframe.isweekly)
        toReturn := "W"
    else if (timeframe.ismonthly)
        toReturn := "M"
    toReturn
    
// @function Converts to unit based on bar
// @param count - in float
// @returns unit of time
export convertToTimeUnit(float bar)=>
    string toReturn = na
    timeframeInNumber = getTimeUnit()
    if (timeframe.isminutes and timeframeInNumber >= 60)
        if (timeframeInNumber % 60 > 0)
            newTimeframe = timeframeInNumber * bar
            toReturn := str.tostring(math.floor(newTimeframe / 60)) + "H " + str.tostring(newTimeframe % 60) + "m"
        else 
            newTimeframe = timeframeInNumber * bar
            toReturn := str.tostring(math.floor(newTimeframe / 60)) + "H"
    else
        toReturn := str.tostring(getTimeUnit() * bar) + getTimeUnitLabel()
    toReturn

// @function Converts timeframe into human readable string
// @param string - timeframe
// @returns timeframe in human readable string
export timeframeToString(string timeframe)=>
    timeframeInNumber = str.tonumber(timeframe)
    toReturn = timeframe
    if (timeframe.isseconds)
        if (timeframe.period == 'S')
            toReturn := '1s'
        toReturn := str.replace_all(toReturn, "S", "s")
    if (timeframe.isminutes)
        if (timeframeInNumber < 60)
            toReturn := timeframe + "m"
        else 
            toReturn := str.tostring(math.floor(timeframeInNumber / 60)) + "H"
            if ((timeframeInNumber % 60) > 0.0)
                toReturn += ' ' + str.tostring(timeframeInNumber % 60) + "m"
    if (timeframe.period == 'D' or timeframe.period == 'W' or timeframe.period == 'M')
        toReturn := '1' + timeframe.period
    
    toReturn

// @function Gets the average of all the values from the array
// @param array - in float[]
// @returns the avarage mean
export getAverageFromArray(float[] array)=>
    float sum = 0
    for i = 0  to array.size(array) - 1
        sum := sum + array.get(array, i)
    sum / array.size(array)

// @function Gets the highest of all the values from the array
// @param array - in float[]
// @returns the highest value from the array
export getHighestFromArray(float[] array)=>
    float highest = na
    for i = 0  to array.size(array) - 1
        value = array.get(array, i)
        if (na(highest) or value > highest)
            highest := value
    highest

// @function Gets the lowest of all the values from the array
// @param array - in float[]
// @returns the lowest value from the array
export getLowestFromArray(float[] array)=>
    float lowest = na
    for i = 0  to array.size(array) - 1
        value = array.get(array, i)
        if (na(lowest) or value < lowest)
            lowest := value
    lowest

// @function Counts the number of digits before decimal point
// @param number - in float
// @returns number of digits
export numberBeforeDecimalPoint(float number) =>
    c=1
    c:=number==0?1:1+math.floor(math.log10(math.abs(number)))
    c

// @function Counts the number of digits after decimal point
// @param number - in float
// @returns number of digits
export numberAfterDecimalPoint(float number) =>
    c=0
    d=0.0
    d:=math.abs(number)
    d:=d-math.floor(d)
    for i=0 to 42
        if ( d==0 )
            break
        d:=d*10
        d:=d-math.floor(math.round(d))
        c:=c+1
    c
    
// @function Rounds number to same decimals of mintick
// @param number - in float, number to round
// @param minTick - in float, decimals to use
// @returns rounded number
export roundToMinTick(float number, float minTick)=>
    math.round(number, numberAfterDecimalPoint(minTick))

// @function Rounds number to price format (same decimals as the current asset)
// @param price - the number to format
// @returns formatted price
export toPriceFormat(float price)=>
    roundToMinTick(price, numberAfterDecimalPoint(syminfo.mintick))

// @function Rounds number to price format (same decimals as the current asset)
// @param price - the number to format
// @returns formatted price to string
export toPriceFormatString(float price)=>
    decimals = ''
    for i = 0 to numberAfterDecimalPoint(syminfo.mintick) - 1
        decimals := decimals + '0'
    str.format('{0,number,0.' + decimals + '}', price)

// @function Show an alert based on the bar's close
// @param title - title of the alert
// @returns label
export showAlerts(simple string title) =>
    label.new(bar_index, close, style=label.style_label_right, color=color.blue, textcolor=color.white, text="⏰ " + title)

// @function Converts % change
// @param prev - previous value
// @param new - new value
// @returns float: - % change in float
export percentChange(float prev, float new)=>
    (new - prev) / (prev * 1.0)

// @function Converts the number into a human readable % format
// @param number - number in float
// @returns string: - number in % that is readable to human
export toPercentString(float number)=>
    str.replace_all(str.format("{0,number,currency}%", number * 100), '$', '')

// @function Converts the number into a human readable format
// @param number - number in float
// @returns string: - number in human readable format
export toNumberString(float number)=>
    str.replace_all(str.format("{0,number,currency}", number), '$', '')
    
// @function Converts the number into a human readable format but also dropping decimals
// @param number - number in float
// @returns string: - number in human readable format
export toNumberStringWithoutDecimal(float number)=>
    s = str.replace_all(str.format("{0,number,currency}", math.floor(number)), '$', '')
    s := str.replace_all(s, '.00', '')
// // @function Checks if the res is in new bar using request.security
// // @param res - resolution of the bar to check if new
// // @returns ch: - true, false
// export isNewbarSecurity(simple string res)=>
//     c = request.security(syminfo.ticker, res, close, barmerge.gaps_on, barmerge.lookahead_on)
//     not na(c)
	
// @function Checks if the res is in new bar at the current timeframe
// @param res - resolution of the bar to check if new
// @param timezone - timezone of the resolution
// @returns ch: - 1=true, 0=false
export isNewbar(simple string res, simple string timezone) =>
    ch = 0
    if(res == '12M')
        t  = year(time('D', "", timezone))
        ch := ta.change(t) != 0 ? 1 : 0
    else
        t = time(res,"", timezone)
        ch := ta.change(t) != 0 ? 1 : 0
    ch

// @param src - source to normalize like macd histogram
// @param min - minimum value
// @param max - maximum value
// @returns normalized value
export normalize(float _src, float _min, float _max) => 
    // Normalizes series with unknown min/max using historical min/max.
    // _src: series to rescale.
    // _min: minimum value of rescaled series.
    // _max: maximum value of rescaled series.
    var _historicMin = +10e10
    var _historicMax = -10e10
    _historicMin := math.min(nz(_src, _historicMin), _historicMin)
    _historicMax := math.max(nz(_src, _historicMax), _historicMax)
    _min + (_max - _min) * (_src - _historicMin) / math.max(_historicMax - _historicMin, 10e-10)
    
// @function Gets the ms  equivalent of the given timeframe period
// @param _tf - timeframe in string
// @returns float in ms
get_ms_timeframe(_tf)=>
    tf = str.upper(_tf)
    float toReturn = na
    if (str.contains(tf, "s"))
        toReturn := 1000
    else if (str.contains(tf, "H"))
        toReturn := 1000 * 60 * 60
    else if (str.contains(tf, "D"))
        toReturn := 1000 * 60 * 60 * 24
    else if (str.contains(tf, "W"))
        toReturn := 1000 * 60 * 60 * 24 * 7
    else if (str.contains(tf, "M"))
        toReturn := 1000 * 60 * 60 * 24 * 7 * 30
    else //minutes if no label
        toReturn := 1000 * 60
    toReturn
    
// @function Gets the ms value equivalent of the given timeframe
// @param _tf - timeframe in string
// @returns float in ms
export get_ms_timeframe_value(simple string _tf)=>
    get_ms_timeframe(_tf) * getTimeUnit_timeframe(_tf)
  
// @function Gets the ratio of 2 timeframes (tf1 / tf2)
// @param tf_1 - timeframe in string
// @param tf_2 - timeframe in string
// @returns ratio in float
export  get_ms_timeframes_ratio(simple string tf_1, simple string tf_2)=>  
    get_ms_timeframe_value(tf_1) / get_ms_timeframe_value(tf_2)
    
// @function Gets the line styles in string
// @returns line styles in string
export  get_line_styles_string()=>  
    [LINE_STYLE_SOLID, LINE_STYLE_DOTTED, LINE_STYLE_DASHED]
    
// @function Gets the symbol-label styles in string
// @returns symbol-label styles in string
export  get_label_styles_symbol_string()=>  
    [LABEL_STYLE_NONE, LABEL_STYLE_XCROSS, LABEL_STYLE_CROSS, LABEL_STYLE_TRIANGLEUP, LABEL_STYLE_TRIANGLEDOWN, LABEL_STYLE_FLAG, LABEL_STYLE_CIRCLE, LABEL_STYLE_ARROWUP, LABEL_STYLE_ARROWDOWN, LABEL_STYLE_SQUARE, LABEL_STYLE_DIAMOND]

// @function Gets the text-label styles in string
// @returns text-label styles in string
export  get_label_styles_text_string()=>  
    [LABEL_STYLE_NONE, LABEL_STYLE_LABELUP, LABEL_STYLE_LABELDOWN, LABEL_STYLE_LABELLEFT, LABEL_STYLE_LABELRIGHT, LABEL_STYLE_LABELLOWERLEFT, LABEL_STYLE_LABELLOWERRIGHT, LABEL_STYLE_LABELUPPERLEFT, LABEL_STYLE_LABELUPPERRIGHT, LABEL_STYLE_LABELCENTER]

// @function Get's the equivalent value of line.style_x string 
// @param type - line_style type in string
// @returns corresponding line.style_x
export get_line_styles_value(simple string type)=>
    switch(type)
        LINE_STYLE_SOLID => line.style_solid
        LINE_STYLE_DOTTED => line.style_dotted
        LINE_STYLE_DASHED => line.style_dashed
        
// @function Get's the equivalent value of label.style_x symbol string 
// @param type - label_style symbol type in string
// @returns corresponding label.style_x symbol
export get_label_styles_symbol_value(simple string type)=>
    switch(type)
        LABEL_STYLE_NONE => label.style_none
        LABEL_STYLE_XCROSS => label.style_xcross
        LABEL_STYLE_CROSS => label.style_cross
        LABEL_STYLE_TRIANGLEUP => label.style_triangleup
        LABEL_STYLE_TRIANGLEDOWN => label.style_triangledown
        LABEL_STYLE_FLAG => label.style_flag
        LABEL_STYLE_CIRCLE => label.style_circle
        LABEL_STYLE_ARROWUP => label.style_arrowup
        LABEL_STYLE_ARROWDOWN => label.style_arrowdown
        LABEL_STYLE_SQUARE => label.style_square
        LABEL_STYLE_DIAMOND => label.style_diamond
        
// @function Get's the equivalent value of label.style_x text string 
// @param type - label_style text type in string
// @returns corresponding label.style_x text
export get_label_styles_text_value(simple string type)=>
    switch(type)
        LABEL_STYLE_NONE => label.style_none
        LABEL_STYLE_LABELUP => label.style_label_up
        LABEL_STYLE_LABELDOWN => label.style_label_down
        LABEL_STYLE_LABELLEFT => label.style_label_left
        LABEL_STYLE_LABELRIGHT => label.style_label_right
        LABEL_STYLE_LABELLOWERLEFT => label.style_label_lower_left
        LABEL_STYLE_LABELLOWERRIGHT => label.style_label_lower_right
        LABEL_STYLE_LABELUPPERLEFT => label.style_label_upper_left
        LABEL_STYLE_LABELUPPERRIGHT => label.style_label_upper_right
        LABEL_STYLE_LABELCENTER => label.style_label_center
        LABEL_STYLE_DIAMOND => label.style_diamond

// @function Get the time to exit from timenow + minutes
// @param type - minutes
// @returns time to exit  
export timetoexit_in_minutes(int _minutes)=>
    upto = 1000 * 60 * _minutes
    result = time + upto     

// @function Get the time to exit from timenow + hours
// @param type - hours
// @returns time to exit  
export timetoexit_in_hours(int _hours)=>
    upto = 1000 * 60 * 60 * _hours
    result = time + upto
   
// @function Check if condition is valid
// @param numstring - bar indices separated by pipe (|)
// @returns if condition is valid on given indices
export is_valid(simple string numstring, bool cond) =>
    valid = true
    nums = str.split(numstring, '|')
    length = array.size(nums)
    count = 0
    i = 0
    while (count < length)
        if (array.indexof(nums, str.tostring(i)) != -1)
            if (not cond[i])
                valid := false
                break
            count += 1
        i += 1
    valid
    
timezone = input.string("GMT+8", "Timezone")
res = input.timeframe("1D", "Timeframe")
//highlight each new daily bar
newbar = isNewbar(res, timezone)
bgcolor(newbar ? color.new(color.blue, 90) : na)

var label labelholder = na
if barstate.islast
    label.delete(labelholder)
    labelholder := label.new(bar_index, high, text=fixTimeframe("1W") + "|" + fixTimeframe(timeframe.period) + "|" + (isSameTimeframe("1W", "W") ? "YES" : "NO") + "|" + (is_valid('0|1', open > close) ? "YES" : "NO"), textcolor=color.white)