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

//@version=5

// @description: A collection of custom tools & utility functions commonly used with my scripts. This Library is work in progress.
library("Punchline_Lib")


// --- BEGIN MATH FUNCTIONS {
    
// -----------------------------------------------------------
// ----------------------- ROUND SMART -----------------------
// -----------------------------------------------------------
// @function Truncates decimal points of a float value based on the amount of digits before the decimal point
// @param float _value any number
// @returns float
export roundSmart(float _value) =>
    precision =  (_value < 1 ? 10000 : 
                 (_value < 10 ? 1000 :
                 (_value < 100 ? 100 :
                 (_value < 1000 ? 10 : 1))))
    if _value < 0
        _value
    else
        math.round(_value * precision) / precision

// @function Returns a factor for rounding based on a sample input and a desired precision
// @param float _sample sample input to give an idea for the number setup
// @param string _precision desired precision (zero, max, low, high)
// @returns float
export fRound(float _sample, string _precision) => 
     _precision == "zero" ? 1 :
     _precision == "max" ? 100000000 :
     _sample / close < 0.00001 ? 100000000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1): 
     _sample / close < 0.0001 ? 10000000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 
     _sample / close < 0.001 ? 1000000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 
     _sample / close < 0.01 ? 100000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 
     _sample / close < 0.1 ? 10000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 
     _sample / close < 1 ? 1000 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 
     _sample / close < 10 ? 100 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1) : 1 / (_precision == "low" ? 10 : 1) * (_precision == "high" ? 10 : 1)

// @function Returns a suggestion for an excgabge rate based on the base currency provided as an input to the function
// @param string _base symbol of the base currency to produce a quote currency against
// @param float _exchangeRate alternative exchange Rate if no better can be determined
// @returns float
export get_exchangeRate(string _base, float _exchangeRate) =>
    if (syminfo.currency == _base 
                             or ((_base == 'USD' or _base == 'USDT' or _base == 'USDC' or _base == 'BUSD' or _base == 'USDP' or _base == 'DAI') 
                                  and (syminfo.currency == 'USDT' or syminfo.currency == 'USDC' or syminfo.currency == 'BUSD' or syminfo.currency == 'USDP' or syminfo.currency == 'DAI'))
                             )
        1
    else
        nz(_exchangeRate, 1.0)

// @function Calculates up to 10 retracements in one go
// @param float _high high value
// @param float _low low value
// @param float _pct_01 retracement %
// @param float _pct_02 retracement %
// @param float _pct_03 retracement %
// @param float _pct_04 retracement %
// @param float _pct_05 retracement %
// @param float _pct_06 retracement %
// @param float _pct_07 retracement %
// @param float _pct_08 retracement %
// @param float _pct_09 retracement %
// @param float _pct_10 retracement %
// @returns array of retracement values
export get_retracement(float _high, float _low, float _pct_01, float _pct_02, float _pct_03, float _pct_04, float _pct_05, float _pct_06, float _pct_07, float _pct_08, float _pct_09, float _pct_10) =>
    distance_ = math.max(_high, _low) - math.min(_high, _low)
    ret_01_L = _high - distance_ * _pct_01
    ret_01_S = _high - distance_ * (1 - _pct_01)
    ret_02_L = _high - distance_ * _pct_02
    ret_02_S = _high - distance_ * (1 - _pct_02)
    ret_03_L = _high - distance_ * _pct_03
    ret_03_S = _high - distance_ * (1 - _pct_03)
    ret_04_L = _high - distance_ * _pct_04
    ret_04_S = _high - distance_ * (1 - _pct_04)
    ret_05_L = _high - distance_ * _pct_05
    ret_05_S = _high - distance_ * (1 - _pct_05)
    ret_06_L = _high - distance_ * _pct_06
    ret_06_S = _high - distance_ * (1 - _pct_06)
    ret_07_L = _high - distance_ * _pct_07
    ret_07_S = _high - distance_ * (1 - _pct_07)
    ret_08_L = _high - distance_ * _pct_08
    ret_08_S = _high - distance_ * (1 - _pct_08)
    ret_09_L = _high - distance_ * _pct_09
    ret_09_S = _high - distance_ * (1 - _pct_09)
    ret_10_L = _high - distance_ * _pct_10
    ret_10_S = _high - distance_ * (1 - _pct_10)
    [
     ret_01_L, ret_01_S,
     ret_02_L, ret_02_S,
     ret_03_L, ret_03_S,
     ret_04_L, ret_04_S,
     ret_05_L, ret_05_S,
     ret_06_L, ret_06_S,
     ret_07_L, ret_07_S,
     ret_08_L, ret_08_S,
     ret_09_L, ret_09_S,
     ret_10_L, ret_10_S
     ]

// } END MATH FUNCTIONS

// --- BEGIN STRING FUNCTIONS {

// -----------------------------------------------------------
// --------------------- TO STRING SMART ---------------------
// -----------------------------------------------------------
// @function converts a float to a string, intelligently cutting off decimal points
// @param float _value any number
// @returns string
export tostring_smart(float _value) =>
    format =  (_value < 1 ? "#.####" : 
             (_value < 10 ? "#.###" :
             (_value < 100 ? "#.##" :
             (_value < 1000 ? "#.#" : "#"))))
    if _value < 0
        str.tostring(_value)
    else
        str.tostring(_value, format)

// @function converts a string into an array of floats
// @param string _string input string to be converted
// @param string _separator separator to separate string by
// @param float _defVal default value in case no content is found
// @returns array of floats
export string_to_array_float(string _string, string _separator, float _defVal) =>
    a_str = str.split(_string, _separator)
    if array.size(a_str) == 0
        array.push(a_str, str.tostring(_defVal))
    a_float = array.new_float(0)
    for i = 0 to array.size(a_str)-1
        array.push(a_float, str.tonumber(str.replace_all(array.get(a_str, i), " ", "")))
    a_float

// @function pushes 20 floats into an array and returns the array
// @param float f01 float to push into the array
// @param float f02 float to push into the array
// @param float f03 float to push into the array
// @param float f04 float to push into the array
// @param float f05 float to push into the array
// @param float f06 float to push into the array
// @param float f07 float to push into the array
// @param float f08 float to push into the array
// @param float f09 float to push into the array
// @param float f10 float to push into the array
// @param float f11 float to push into the array
// @param float f12 float to push into the array
// @param float f13 float to push into the array
// @param float f14 float to push into the array
// @param float f15 float to push into the array
// @param float f16 float to push into the array
// @param float f17 float to push into the array
// @param float f18 float to push into the array
// @param float f19 float to push into the array
// @param float f20 float to push into the array
// @returns array of floats
export floats_to_array_of_20(
     float f01,
     float f02,
     float f03,
     float f04,
     float f05,
     float f06,
     float f07,
     float f08,
     float f09,
     float f10,
     float f11,
     float f12,
     float f13,
     float f14,
     float f15,
     float f16,
     float f17,
     float f18,
     float f19,
     float f20
     ) =>
    lFloats = array.new_float(0)
    array.push(lFloats, f01)
    array.push(lFloats, f02)
    array.push(lFloats, f03)
    array.push(lFloats, f04)
    array.push(lFloats, f05)
    array.push(lFloats, f06)
    array.push(lFloats, f07)
    array.push(lFloats, f08)
    array.push(lFloats, f09)
    array.push(lFloats, f10)
    array.push(lFloats, f11)
    array.push(lFloats, f12)
    array.push(lFloats, f13)
    array.push(lFloats, f14)
    array.push(lFloats, f15)
    array.push(lFloats, f16)
    array.push(lFloats, f17)
    array.push(lFloats, f18)
    array.push(lFloats, f19)
    array.push(lFloats, f20)
    lFloats

// @function pushes 20 ints into an array and returns the array
// @param int i01 int to push into the array
// @param int i02 int to push into the array
// @param int i03 int to push into the array
// @param int i04 int to push into the array
// @param int i05 int to push into the array
// @param int i06 int to push into the array
// @param int i07 int to push into the array
// @param int i08 int to push into the array
// @param int i09 int to push into the array
// @param int i10 int to push into the array
// @param int i11 int to push into the array
// @param int i12 int to push into the array
// @param int i13 int to push into the array
// @param int i14 int to push into the array
// @param int i15 int to push into the array
// @param int i16 int to push into the array
// @param int i17 int to push into the array
// @param int i18 int to push into the array
// @param int i19 int to push into the array
// @param int i20 int to push into the array
// @returns array of ints
export ints_to_array_of_20(
     int i01,
     int i02,
     int i03,
     int i04,
     int i05,
     int i06,
     int i07,
     int i08,
     int i09,
     int i10,
     int i11,
     int i12,
     int i13,
     int i14,
     int i15,
     int i16,
     int i17,
     int i18,
     int i19,
     int i20
     ) =>
    lInts = array.new_int(0)
    array.push(lInts, i01)
    array.push(lInts, i02)
    array.push(lInts, i03)
    array.push(lInts, i04)
    array.push(lInts, i05)
    array.push(lInts, i06)
    array.push(lInts, i07)
    array.push(lInts, i08)
    array.push(lInts, i09)
    array.push(lInts, i10)
    array.push(lInts, i11)
    array.push(lInts, i12)
    array.push(lInts, i13)
    array.push(lInts, i14)
    array.push(lInts, i15)
    array.push(lInts, i16)
    array.push(lInts, i17)
    array.push(lInts, i18)
    array.push(lInts, i19)
    array.push(lInts, i20)
    lInts

// } END STRING FUNCTIONS

// --- BEGIN FORMAT FUNCTIONS {
// -----------------------------------------------------------
// ------------------------ FILL CELL ------------------------
// -----------------------------------------------------------
// @function Create a table cell based on params handed over
// @param table _table Table to be edited
// @param int _column column of cell to be edited
// @param int _row row of cell to be edited
// @param float _value value to be added into cell
// @param string _text text to be added into cell
// @returns table.cell
export fillCell(table _table, int _column, int _row, float _value, string _text) =>
    c_color_ = color.green
    transp_ = 80
    cellText_ = str.tostring(_value, "#.#####") + "\n" + _text
    table.cell(_table, _column, _row, cellText_, bgcolor = color.new(c_color_, transp_), text_color = c_color_, width = 6) 

// @function Converts a string into a label size
// @param string _size the desired size as string
// @returns size
export size(string _size) =>
    (_size == "Huge") ? size.huge :
     (_size == "Large") ? size.large :
     (_size == "Small") ? size.small :
     (_size == "Tiny") ? size.tiny :
     (_size == "Auto") ? size.auto :
         size.normal

// @function Converts a string into a position
// @param string _position the desired position as string
// @returns position
export position(string _position) =>
    (_position == "top right") ? position.top_right :
     (_position == "right") ? position.middle_right :
     (_position == "bottom right") ? position.bottom_right :
     (_position == "bottom") ? position.bottom_center :
     (_position == "bottom left") ? position.bottom_left :
     (_position == "left") ? position.middle_left :
     (_position == "top left") ? position.top_left :
     (_position == "top") ? position.top_center :
         position.bottom_right

// } END FORMAT FUNCTIONS

// --- BEGIN TA FUNCTIONS {

// -----------------------------------------------------------------------------
// ---------------------------------- PIVOTS -----------------------------------
// -----------------------------------------------------------------------------
// {
// @function detects pivots in a series of values
// @param series _src source of data points
// @param int _length depth for scanning
// @param bool _isHigh look for high (true) or low (false)
// @returns [int, float] bar index, pivot point
pivots(_src, _length, _isHigh) =>
    p_ = nz(_src[_length])

    if _length == 0
        [bar_index, p_]
    else
        isFound_ = true
        for i = 0 to _length - 1 by 1
            if _isHigh and _src[i] > p_
                isFound_ := false
                isFound_
            if not _isHigh and _src[i] < p_
                isFound_ := false
                isFound_

        for i = _length + 1 to 2 * _length by 1
            if _isHigh and _src[i] >= p_
                isFound_ := false
                isFound_
            if not _isHigh and _src[i] <= p_
                isFound_ := false
                isFound_

        if isFound_ and _length * 2 <= bar_index
            [bar_index[_length], p_]
        else
            [int(na), float(na)]

// @function draws a line from previous pivot to next pivot
// @param float _dev deviation
// @param bool _isHigh true if to be processed coordinate is a high, false otherwise
// @param int _index index of next price to process
// @param float _price new price to be evaluated
// @param color _color color for drawing line from one pivot to another pivot (#00000000 to draw no line)
// @param int _width width of line for lines
// @param bool _prev_pivot_is_a_high true if to previous pivot is a high, false otherwise 
// @param line _lineLast id of previous line
// @param int _linesCount counter of how many lines have been drawn so far 
// @param float _devThreshold allowed deviation
// @param int _iLast index of previous pivot point
// @param float _pLast price of previous pivot point
// @returns [line, bool, bool] new line (if any), high (or no high), true if new pivot is significant according to allowed deviation threshold
export pivotFound(float _dev, bool _isHigh, int _index, float _price, color _color, int _width, bool _prev_pivot_is_a_high, line _lineLast, int _linesCount, float _devThreshold, int _iLast, float _pLast) =>
    if _prev_pivot_is_a_high == _isHigh and not na(_lineLast)
        // same direction
        if _prev_pivot_is_a_high ? _price > _pLast : _price < _pLast
            if _linesCount <= 1
                line.set_xy1(_lineLast, _index, _price)
            line.set_xy2(_lineLast, _index, _price)
            [_lineLast, _prev_pivot_is_a_high, false]
        else
            [line(na), bool(na), false]
    else
        // reverse the direction (or create the very first line)
        if na(_lineLast)
            id_ = line.new(_index, _price, _index, _price, color=_color, width=_width)
            [id_, _isHigh, true]
        else
            // price move is significant
            if math.abs(_dev) >= _devThreshold
                id_ = line.new(_iLast, _pLast, _index, _price, color=_color, width=_width)
                [id_, _isHigh, true]
            else
                [line(na), bool(na), false]

// @function detects high and low pivots in a series of values
// @param int _depth depth for scanning
// @returns [int, float, int, float] bar index of high pivot, high pivot, bar index of low pivot, low pivot
export get_pivots(int _depth) =>
    [iH, pivot_high] = pivots(high, math.floor(_depth / 2), true)
    [iL, pivot_low] = pivots(low, math.floor(_depth / 2), false)
    [iH, pivot_high, iL, pivot_low]

// }

// -----------------------------------------------------------------------------
// ------------------------------ MOVING AVERAGES (MA) -------------------------
// -----------------------------------------------------------------------------
// {
get_ama(src, length, fastLength, slowLength, prev_ama) =>
    fastAlpha = 2 / (fastLength + 1)
    slowAlpha = 2 / (slowLength + 1)

    hh = ta.highest(length + 1)
    ll = ta.lowest(length + 1)

    mltp = hh - ll != 0 ? math.abs(2 * src - ll - hh) / (hh - ll) : 0

    ssc = mltp * (fastAlpha - slowAlpha) + slowAlpha

    ama = 0.0
    ama := nz(prev_ama) + math.pow(ssc, 2) * (src - nz(prev_ama))
    ama

// Arnaud Legoux Moving Average manual calculation
get_alma(src, windowsize, offset, sigma) =>
    m = offset * (windowsize - 1)
    //m = math.floor(offset * (windowsize - 1)) // Used as m when math.floor=true
    s = windowsize / sigma
    norm = 0.0
    sum = 0.0
    for i = 0 to windowsize - 1
        weight = math.exp(-1 * math.pow(i - m, 2) / (2 * math.pow(s, 2)))
        norm := norm + weight
        sum := sum + src[windowsize - i - 1] * weight
    sum / norm

// @function Gets a Moving Average based on type and source
// @param string _type The type of MA
// @param series float _src The source for the MA
// @param simple int _len The MA period
// @param int _fastLength Fast EMA Length (AMA only)
// @param int _slowLength Slow EMA Length (AMA only)
// @param float _prev_maVal Previous MA value (AMA only)
// @param simple float _offset Offset for ALMA/LSMA calculation (ALMA and LSMA only)
// @param int _sigma Sigma for ALMA calculation (ALMA only)
// @returns A moving average with the given parameters
export get_ma(string _type, series float _src, simple int _len, int _fastLength, int _slowLength, float _prev_maVal, simple float _offset, simple int _sigma) =>
    e = ta.ema(_src, int(_len))
    switch _type
        // single moving average
        'SMA' => ta.sma(_src, int(_len))
        // exponential moving average
        'EMA' => e
        // double exponential moving average
        'DEMA' => 2 * e - ta.ema(e, int(_len))
        // triple exponential moving average
        'TEMA' => 3 * (e - ta.ema(e, _len)) + ta.ema(ta.ema(e, _len), _len)
        // adaptive moving average
        'AMA' => get_ama(_src, _len, _fastLength, _slowLength, _prev_maVal)
        // Arnaud Legoux Moving Average
        'ALMA'=> ta.alma(_src, _len, _offset, _sigma)
        // hull moving average
        'HMA' => ta.hma(_src, _len)
        // 
        'RMA' => ta.rma(_src, _len)
        // weighted moving average
        'WMA' => ta.wma(_src, _len)
        // volume-weighted moving average
        'VWMA' => ta.vwma(_src, _len)
        // Least Squares Moving Average
        'LSMA' => ta.linreg(_src, _len, int(_offset))
        // Volume-weighted average price
        "VWAP" => ta.vwap
        => na
// }

// -----------------------------------------------------------------------------
// -------------------- EXPONENTIAL MOVING AVERAGE (OHLC) ----------------------
// -----------------------------------------------------------------------------
// {
// @function calculates EMA for 4 values based on _length. Ideal in with security call
// @param float _o open
// @param float _h high
// @param float _l low
// @param float _c close
// @param float _length length
// @returns [float, float, float, float]
export get_ema_ohlc(float _o, float _h, float _l, float _c, float _length) => 
    [ta.ema(_o, int(_length)), ta.ema(_h, int(_length)), ta.ema(_l, int(_length)), ta.ema(_c, int(_length))]

// }

// -----------------------------------------------------------------------------
// ------------------------ JURIK MOVING AVERAGES (JMA) ------------------------
// -----------------------------------------------------------------------------
// {
// @function calculates Jurik Moving Average
// @param int _length Length
// @param int _phase Phase
// @param int _power Power
// @returns float
export jma(int _length, int _phase, int _power) =>
    phaseRatio = _phase < -100 ? 0.5 : _phase > 100 ? 2.5 : _phase / 100 + 1.5
    beta = 0.45 * (_length - 1) / (0.45 * (_length - 1) + 2)
    alpha = math.pow(beta, _power)
    jma = 0.0
    e0 = 0.0
    //e0 := (1 - alpha) * param_jma_src + alpha * nz(e0[1])
    e0 := (1 - alpha) * close + alpha * nz(e0[1])
    e1 = 0.0
    //e1 := (param_jma_src - e0) * (1 - beta) + beta * nz(e1[1])
    e1 := (close - e0) * (1 - beta) + beta * nz(e1[1])
    e2 = 0.0
    e2 := (e0 + phaseRatio * e1 - nz(jma[1])) * math.pow(1 - alpha, 2) + math.pow(alpha, 2) * nz(e2[1])
    jma := e2 + nz(jma[1])
    jma
// }

// -----------------------------------------------------------------------------
// ------------------------------ CANDLE RATIO ---------------------------------
// -----------------------------------------------------------------------------
// {
// @function get ratio between current candle and candle[index]
// @param int _index index of the candle you want to compare the current candle to
// @returns float difference in size between current and selected candle
export getRatio_singleCandle(int _index) =>
    size_currentCandle = high - low
    size_prevCandle = high[_index] - low[_index]
    size_currentCandle / size_prevCandle
// }

// -----------------------------------------------------------------------------
// ----------------------------------- ADX ------------------------------------
// -----------------------------------------------------------------------------
// {
dirmov(len) =>
    up = ta.change(high)
    down = -ta.change(low)
    plusDM = na(up) ? na : up > down and up > 0 ? up : 0
    minusDM = na(down) ? na : down > up and down > 0 ? down : 0
    truerange = ta.rma(ta.tr, len)
    plus = fixnan(100 * ta.rma(plusDM, len) / truerange)
    minus = fixnan(100 * ta.rma(minusDM, len) / truerange)
    [plus, minus]
// @function calculates ADX for specified Legnth and Smoothing
// @param int _dilen ADX DI Length
// @param int _adxlen ADX Smoothing
// @returns float
export adx(simple int _dilen, simple int _adxlen) =>
    [plus, minus] = dirmov(_dilen)
    sum = plus + minus
    100 * ta.rma(math.abs(plus - minus) / (sum == 0 ? 1 : sum), _adxlen)
//}

// -----------------------------------------------------------------------------
// --------------------- HIGHEST HIGH and LOWEST LOW ---------------------------
// -----------------------------------------------------------------------------
// {
// @function calculates Highest High and lowest low within a specified length
// @param series float h high
// @param series float l low
// @param series float c close
// @param int _length Length to scan for values
// @returns [float, float, float, float]
export highestHigh_lowestLow(series float _h, series float _l, series float _c, int _length) =>
    hh = ta.highest(_h, _length)[1]
    ll = ta.lowest(_l, _length)[1]
    hc = ta.highest(_c, _length)[1]
    lc = ta.lowest(_c, _length)[1]
    [hh, ll, hc, lc]

// }

// -----------------------------------------------------------------------------
// ------------------------------ ICHIMOKU CLOUD -------------------------------
// -----------------------------------------------------------------------------
// {
donchian(len) => math.avg(ta.lowest(len), ta.highest(len))
// @function creates values for the Ichimoku Cloud
// @param int _conversionPeriods Conversion Periods
// @param int _basePeriods Base Periods
// @param int _laggingSpan2Periods Lagging Span Periods
// @returns [float, float, float, float]
export imc(int _conversionPeriods, int _basePeriods, int _laggingSpan2Periods) =>
    conversionLine_ = donchian(_conversionPeriods)
    baseLine_ = donchian(_basePeriods)
    leadLine1_ = math.avg(conversionLine_, baseLine_)
    leadLine2_ = donchian(_laggingSpan2Periods)
    [conversionLine_, baseLine_, leadLine1_, leadLine2_]
// }

// -----------------------------------------------------------------------------
// ------------------------------ WILLIAM FRACTAL ------------------------------
// -----------------------------------------------------------------------------
// {
// @function Calculates William Fractals. 
// @param int _periods Number of periods and keep a minimum value of 2 for error handling.
// @returns [bool, bool]
export williamFractal(int _periods) =>
    n = _periods
    
    // UpFractal
    bool upflagDownFrontier = true
    bool upflagUpFrontier0 = true
    bool upflagUpFrontier1 = true
    bool upflagUpFrontier2 = true
    bool upflagUpFrontier3 = true
    bool upflagUpFrontier4 = true
    
    for i = 1 to n
        upflagDownFrontier := upflagDownFrontier and (high[n-i] < high[n])
        upflagUpFrontier0 := upflagUpFrontier0 and (high[n+i] < high[n])
        upflagUpFrontier1 := upflagUpFrontier1 and (high[n+1] <= high[n] and high[n+i + 1] < high[n])
        upflagUpFrontier2 := upflagUpFrontier2 and (high[n+1] <= high[n] and high[n+2] <= high[n] and high[n+i + 2] < high[n])
        upflagUpFrontier3 := upflagUpFrontier3 and (high[n+1] <= high[n] and high[n+2] <= high[n] and high[n+3] <= high[n] and high[n+i + 3] < high[n])
        upflagUpFrontier4 := upflagUpFrontier4 and (high[n+1] <= high[n] and high[n+2] <= high[n] and high[n+3] <= high[n] and high[n+4] <= high[n] and high[n+i + 4] < high[n])
    flagUpFrontier = upflagUpFrontier0 or upflagUpFrontier1 or upflagUpFrontier2 or upflagUpFrontier3 or upflagUpFrontier4
    
    upFractal = (upflagDownFrontier and flagUpFrontier)
    
    
    // downFractal
    bool downflagDownFrontier = true
    bool downflagUpFrontier0 = true
    bool downflagUpFrontier1 = true
    bool downflagUpFrontier2 = true
    bool downflagUpFrontier3 = true
    bool downflagUpFrontier4 = true
    
    for i = 1 to n
        downflagDownFrontier := downflagDownFrontier and (low[n-i] > low[n])
        downflagUpFrontier0 := downflagUpFrontier0 and (low[n+i] > low[n])
        downflagUpFrontier1 := downflagUpFrontier1 and (low[n+1] >= low[n] and low[n+i + 1] > low[n])
        downflagUpFrontier2 := downflagUpFrontier2 and (low[n+1] >= low[n] and low[n+2] >= low[n] and low[n+i + 2] > low[n])
        downflagUpFrontier3 := downflagUpFrontier3 and (low[n+1] >= low[n] and low[n+2] >= low[n] and low[n+3] >= low[n] and low[n+i + 3] > low[n])
        downflagUpFrontier4 := downflagUpFrontier4 and (low[n+1] >= low[n] and low[n+2] >= low[n] and low[n+3] >= low[n] and low[n+4] >= low[n] and low[n+i + 4] > low[n])
    flagDownFrontier = downflagUpFrontier0 or downflagUpFrontier1 or downflagUpFrontier2 or downflagUpFrontier3 or downflagUpFrontier4
    
    downFractal = (downflagDownFrontier and flagDownFrontier)

    [upFractal, downFractal]
// }

// } END TA FUNCTIONS