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

//@version=5
library("ta")

// ta Library
// v7, 2023.11.02

// This code was written using the recommendations from the Pine Script™ User Manual's Style Guide:
//   https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html



import TradingView/RelativeValue/2 as TVrv



// This library exports the following functions:

// • ao:                Awesome Oscillator
// • aroon:             Aroon Oscillator
// • atr2:              Average True Range (Alternate Version)
// • cagr:              Compound Annual Growth Rate
// • changePercent:     Calculate percentage change
// • coppock:           Coppock Curve
// • dema:              Double Exponential Moving Average
// • dema2:             Double Exponential Moving Average (Alternate Version)
// • dm:                Demarker Indicator
// • donchian:          Donchian Channel
// • ema2:              Exponential Moving Average (Alternate Version)
// • eom:               Ease of Movement
// • frama:             Fractal Adaptive Moving Average
// • ft:                Fisher Transform 
// • highestSince:      Highest value since condition 
// • ht:                Hilbert Transform function
// • ichimoku:          Ichimoku Cloud
// • ift:               Inverse Fisher Transform
// • kvo:               Klinger Volume Oscillator
// • lowestSince:       Lowest value since condition 
// • relativeVolume:    Compare volume to its historical average
// • rma2:              Rolling Moving Average (Alternate Version)
// • rms:               Root Mean Square
// • rwi:               Random Walk Index
// • stc:               Schaff Trend Cycle
// • stochFull:         Full Stochastic Oscillator
// • stochRsi:          Stochastic RSI
// • supertrend:        SuperTrend Indicator
// • supertrend2:       SuperTrend Indicator (Alternate Version)
// • szo:               Sentiment Zone Oscillator
// • t3:                Tilson Moving Average (T3)
// • t3Alt:             Tilson Moving Average (T3 Alternate Version)
// • tema:              Triple Exponential Moving Average
// • tema2:             Triple Exponential Moving Average (Alternate Version)
// • trima:             Triangular Moving Average
// • trix:              TRIX indicator
// • uo:                Ultimate Oscillator
// • vhf:               Vertical Horizontal Filter
// • vi:                Vortex Indicator
// • vStop:             Volatility Stop
// • vStop2:            Volatility Stop (Alternate Version)
// • vzo:               Volume Zone Oscillator
// • williamsFractal:   Williams' Fractal
// • wpo:               Wave Period Oscillator



// @function                Calculates the dynamic Exponentially Weighted Moving Average used in 
//                          `atr2()`, `ema2()`, `frama()`, and `rma2()`.
// @param source            (series int/float) Series of values to process.
// @param alpha             (series int/float) The smoothing parameter of the filter.
// @returns                 The dynamic exponentially weighted moving average value.
ewma(series float source, series float alpha) =>
    float result = na
    result := alpha * source + (1.0 - alpha) * nz(result[1], source)


// @function                Calculates the value of the Awesome Oscillator. 
// @param source            (series int/float) Series of values to process.
// @param shortLength       (simple int) Number of bars for the fast moving average (length). 
// @param longLength        (simple int) Number of bars for the slow moving average (length). 
// @returns                 (float) The oscillator value. 
export ao(series float source = hl2, simple int shortLength = 5, simple int longLength = 34) =>
    float result = ta.sma(source, shortLength) - ta.sma(source, longLength)


// @function                Calculates the values of the Aroon indicator.
// @param length            (simple int) Number of bars (length).
// @returns                 ([float, float]) A tuple of the Aroon-Up and Aroon-Down values. 
export aroon(simple int length) =>
    float aroonDown = 100 * (ta.lowestbars(low,   length) + length) / length
    float aroonUp   = 100 * (ta.highestbars(high, length) + length) / length
    [aroonUp, aroonDown]


// @function                An alternate ATR function to the `ta.atr()` built-in, which allows a "series float" 
//                          `length` argument.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @returns                 (float) The ATR value.
export atr2(series float length) =>
    float alpha  = 1.0 / math.max(1.0, length)
    float result = ewma(ta.tr(true), alpha)


// @function                Calculates the "Compound Annual Growth Rate" between two points in time.
// @param entryTime         (series int) The starting timestamp.
// @param entryPrice        (series int/float) The starting point's price.
// @param exitTime          (series int) The ending timestamp.
// @param exitPrice         (series int/float) The ending point's price.
// @returns                 (float) CAGR in % (50 is 50%). Returns `na` if there is not >=1D between `entryTime` and 
//                          `exitTime`, or until the two time points have not been reached by the script.
export cagr(series int entryTime, series float entryPrice, series int exitTime, series float exitPrice) =>
    int   MS_IN_ONE_DAY = 24 * 60 * 60 * 1000
    float daysBetween = (exitTime - entryTime) / MS_IN_ONE_DAY
    float result = if daysBetween >= 1 and not (na(entryPrice) or na(exitPrice))
        float years = daysBetween / 365.
        float rate  = exitPrice / entryPrice
        100 * (math.pow(rate, 1 / years) - 1)
    else
        na


// @function                Calculates the percentage difference between two distinct values.
// @param newValue          (series int/float) The current value.
// @param oldValue          (series int/float) The previous value.
// @returns                 (float) The percentage change from the `oldValue` to the `newValue`.
export changePercent(series float newValue, series float oldValue) =>
    float result = 100 * (newValue - oldValue) / oldValue


// @function                Calculates the value of the Coppock Curve indicator. 
// @param source            (series int/float) Series of values to process.
// @param longLength        (simple int) Number of bars for the fast ROC value (length). 
// @param shortLength       (simple int) Number of bars for the slow ROC value (length).
// @param smoothLength      (simple int) Number of bars for the weigted moving average value (length).
// @returns                 (float) The oscillator value. 
export coppock(series float source, simple int longLength, simple int shortLength, simple int smoothLength) =>
    float result = ta.wma(ta.roc(source, longLength) + ta.roc(source, shortLength), smoothLength)


// @function                Calculates the value of the Double Exponential Moving Average (DEMA). 
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The double exponentially weighted moving average of the `source`.
export dema(series float source, simple int length) =>
    float ema1   = ta.ema(source,  length)
    float ema2   = ta.ema(ema1, length)
    float result = 2 * ema1 - ema2


// @function                An alternate EMA function to the `ta.ema()` built-in, which allows a "series float" 
//                          `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @returns                 (float) The exponentially weighted moving average of the `source`.
export ema2(series float source, series float length) => 
    float alpha  = 2.0 / (math.max(1.0, length) + 1.0)
    float result = ewma(source, alpha)


// @function                An alternate Double Exponential Moving Average (DEMA) function to `dema()`, which allows a 
//                          "series float" `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @returns                 (float) The double exponentially weighted moving average of the `source`.
export dema2(series float source, series float length) => 
    float ema1   = ema2(source,  length)
    float ema2   = ema2(ema1, length)
    float result = 2 * ema1 - ema2


// @function                Calculates the value of the "Demarker" indicator. 
//                          "DeMark®" is a registered trademark of DeMark Analytics, LLC. This code is neither 
//                          endorsed, nor sponsored, nor affiliated with them.
// @param length            (simple int) Number of bars (length).
// @returns                 (float) The oscillator value. 
export dm(simple int length) =>
	float demax  =  math.max(ta.change(high), 0)
	float demin  = -math.min(ta.change(low),  0)
	float result =  ta.sma(demax, length) / (ta.sma(demax, length) + ta.sma(demin, length))


// @function                Calculates the values of a Donchian Channel using `high` and `low` over a given `length`.
// @param length            (series int) Number of bars (length).
// @returns                 ([float, float, float]) A tuple containing the channel high, low, and median, respectively.
export donchian(series int length) =>
    float highest = ta.highest(length)
    float lowest  = ta.lowest(length) 
    [highest, lowest, math.avg(highest, lowest)]


// @function                Calculates the value of the Ease of Movement indicator.
// @param length            (simple int) Number of bars (length).
// @param div               (simple int) Divisor used for normalzing values. Optional. The default is 10000.
// @returns                 (float) The oscillator value. 
export eom(simple int length, simple int div = 10000) =>
    float result = ta.sma(div * ta.change(hl2) * (high - low) / volume, length)


// @function                The Fractal Adaptive Moving Average (FRAMA), developed by John Ehlers, is an adaptive 
//                          moving average that dynamically adjusts its lookback period based on fractal geometry.
// @param source            (series int/float) Series of values to process.
// @param length            (series int) Number of bars (length).
// @returns                 (float) The fractal adaptive moving average of the `source`.
export frama(series float source, series int length) =>
    int   len    = math.round(length / 2)
    float hh     = ta.highest(len)
    float ll     = ta.lowest(len)
    float n1     = (hh - ll) / len
    float n2     = (hh[len] - ll[len]) / len
    float n3     = (ta.highest(length) - ta.lowest(length)) / length
    float D      = math.log((n1 + n2) / n3) / math.log(2)
    float alpha  = math.exp(-4.6 * (D - 1))
    float result = ewma(source, alpha)


// @function                Calculates the value of the Fisher Transform indicator.
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Number of bars (length).
// @returns                 (float) The oscillator value. 
export ft(series float source, simple int length) =>
    float value1 = 0.0
    float fish   = 0.0
    float hh = ta.highest(source, length)
    float ll = ta.lowest(source,  length)
    value1 := 0.66 * ((source - ll) / (hh - ll) - 0.5) + 0.67 * nz(value1[1])
    float value2 = value1 > 0.99 ? 0.999 : value1 < -0.99 ? -0.999 : value1
    fish := 0.5 * math.log((1 + value2) / (1 - value2)) + 0.5 * nz(fish[1])
    float result = fish


// @function                Tracks the highest value of a series since the last occurrence of a condition.
// @param cond              (series bool) A condition which, when `true`, resets the tracking of the highest `source`.
// @param source            (series int/float) Series of values to process. Optional. The default is `high`.
// @returns                 (float) The highest `source` value since the last time the `cond` was `true`.
export highestSince(series bool cond, series float source = high) =>
    var float result = na
    if cond 
        result := source
    result := math.max(nz(source, result), nz(result, source))


// @function                Calculates the value of the Hilbert Transform indicator. 
// @param source            (series int/float) Series of values to process.
// @returns                 (float) The oscillator value. 
export ht(series float source) =>
    float result = 0.0962 * source + 0.5769 * nz(source[2]) - 0.5769 * nz(source[4]) - 0.0962 * nz(source[6])


// @function                Calculates the midpoint between the highest `high` and lowest `low` over `length` bars.
// @param length            (series int) Number of bars (length).
// @returns                 The midpoint of the data.
midpoint(series int length) =>
    float result = math.avg(ta.highest(length), ta.lowest(length))


// @function                Calculates values of the Ichimoku Cloud indicator, including tenkan, kijun, senkouSpan1, 
//                          senkouSpan2, and chikou. 
//                          NOTE: offsets forward or backward can be done using the `offset` argument in `plot()`. 
// @param conLength         (series int) Length for the Conversion Line (Tenkan). The default is 9 periods, which 
//                          returns the mid-point of the 9 period Donchian Channel. 
// @param baseLength        (series int) Length for the Base Line (Kijun-sen). The default is 26 periods, which returns 
//                          the mid-point of the 26 period Donchian Channel. 
// @param senkouLength      (series int) Length for the Senkou Span 2 (Leading Span B). The default is 52 periods, 
//                          which returns the mid-point of the 52 period Donchian Channel. 
// @returns                 ([float, float, float, float, float]) A tuple of the Tenkan, Kijun, Senkou Span 1, 
//                          Senkou Span 2, and Chikou Span values. NOTE: by default, the senkouSpan1 and senkouSpan2  
//                          should be plotted 26 periods in the future, and the Chikou Span plotted 26 days in the past.
export ichimoku(series int conLength = 9, series int baseLength = 26, series int senkouLength = 52) =>
    float tenkan      = midpoint(conLength)
    float kijun       = midpoint(baseLength)
    float senkouSpan1 = math.avg(tenkan, kijun)
    float senkouSpan2 = midpoint(senkouLength)
    float chikou      = close
    [tenkan, kijun, senkouSpan1, senkouSpan2, chikou]


// @function                Calculates the value of the Inverse Fisher Transform indicator.
// @param source            (series int/float) Series of values to process.
// @returns                 (float) The oscillator value. 
export ift(series float source) => 
    float exp    = math.exp(2.0 * source)
    float result = (exp - 1.0) / (1.0 + exp)


// @function                Calculates the values of the Klinger Volume Oscillator.
// @param fastLen           (simple int) Length for the fast moving average smoothing parameter calculation.
// @param slowLen           (simple int) Length for the slow moving average smoothing parameter calculation.
// @param trigLen           (simple int) Length for the trigger moving average smoothing parameter calculation.
// @returns                 ([float, float]) A tuple of the KVO value, and the trigger value. 
export kvo(simple int fastLen, simple int slowLen, simple int trigLen) =>
    float trend   = math.sign(ta.change(hlc3)) * volume * 100 
    float fast    = ta.ema(trend, fastLen)
    float slow    = ta.ema(trend, slowLen)
    float kvo     = fast - slow
    float trigger = ta.ema(kvo, trigLen)
    [kvo, trigger]


// @function                Tracks the lowest value of a series since the last occurrence of a condition.
// @param cond              (series bool) A condition which, when `true`, resets the tracking of the lowest `source`.
// @param source            (series int/float) Series of values to process. Optional. The default is `low`.
// @returns                 (float) The lowest `source` value since the last time the `cond` was `true`.
export lowestSince(series bool cond, series float source = low) =>
    var float result = na
    if cond 
        result := source
    result := math.min(nz(source, result), nz(result, source))


// @function                The "zone" calculation used in `pzo()` and `vzo()` functions.
// @param source            (series float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The oscillator value.
zone(series float source, simple int length) =>
    float result = 100 * nz(ta.ema(math.sign(ta.change(close)) * source, length) / ta.ema(source, length))


// @function                Calculates the value of the Price Zone Oscillator.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The oscillator value. 
export pzo(simple int length) =>
    float result = zone(close, length)


// @function                Calculates the volume since the last change in the time value from the 
//                          `anchorTimeframe`, the historical average volume using bars from past periods 
//                          that have the same relative time offset as the current bar from the start of its 
//                          period, and the ratio of these volumes. The volume values are cumulative by default, 
//                          but can be adjusted to non-accumulated with the `isCumulative` parameter.
// @param length            (simple int) The number of periods to use for the historical average calculation.
// @param anchorTimeframe   (simple string) The anchor timeframe used in the calculation. Optional. Default is "D".
// @param isCumulative      (simple bool) If `true`, the volume values will be accumulated since the start of the last 
//                          `anchorTimeframe`. If `false`, values will be used without accumulation. Optional. The 
//                          default is `true`.
// @param adjustRealtime    (simple bool) If `true`, estimates the cumulative value on unclosed bars based on the 
//                          data since the last `anchor` condition. Optional. The default is `false`.
// @returns                 ([float, float, float]) A tuple of three float values. The first element is the current 
//                          volume. The second is the average of volumes at equivalent time offsets from past 
//                          anchors over the specified number of periods. The third is the ratio of the current volume 
//                          to the historical average volume.
export relativeVolume(
     simple int  length,              simple string anchorTimeframe = "D", 
     simple bool isCumulative = true, simple bool   adjustRealtime  = true
 ) =>
    bool  anchor   = timeframe.change(anchorTimeframe)
    float currVol  = isCumulative ? TVrv.calcCumulativeSeries(volume, anchor, adjustRealtime) : volume
    float pastVol  = TVrv.averageAtTime(volume, length, anchorTimeframe, isCumulative)
    float volRatio = currVol / pastVol
    [currVol, pastVol, volRatio]


// @function                An alternate RMA function to the `ta.rma()` built-in, which allows a "series float" 
//                          `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @returns                 (float) The rolling moving average of the `source`.
export rma2(series float source, series float length) => 
    float alpha  = 1.0 / math.max(1.0, length)
    float result = ewma(source, alpha)


// @function                Calculates the Root Mean Square of the `source` over the `length`. 
// @param source            (series int/float) Series of values to process.
// @param length            (series int) Number of bars (length).
// @returns                 (float) The RMS value. 
export rms(series float source, series int length) =>
    float result = math.sqrt(math.sum(math.pow(source, 2), length) / length)


// @function                Calculates the values of the Random Walk Index.
// @param length            (simple int) Lookback and ATR smoothing parameter length.
// @returns                 ([float, float]) A tuple of the `rwiHigh` and `rwiLow` values. 
export rwi(simple int length) =>
    float divisor = ta.atr(length) * math.sqrt(length)
    float rwiHigh = (high - nz(low[length])) / divisor
    float rwiLow  = (nz(high[length]) - low) / divisor
    [rwiHigh, rwiLow]


// @function                Calculates the value of the Schaff Trend Cycle indicator.
// @param source            (series int/float) Series of values to process.
// @param fast              (simple int) Length for the MACD fast smoothing parameter calculation.
// @param slow              (simple int) Length for the MACD slow smoothing parameter calculation.
// @param cycle             (simple int) Number of bars for the Stochastic values (length).
// @param d1                (simple int) Length for the initial %D smoothing parameter calculation.
// @param d2                (simple int) Length for the final %D smoothing parameter calculation.
// @returns                 (float) The oscillator value. 
export stc(series float source, simple int fast, simple int slow, simple int cycle, simple int d1, simple int d2) => 
    float macd   = ta.ema(source, fast) - ta.ema(source, slow)
    float k      = nz(fixnan(ta.stoch(macd, macd, macd, cycle)))
    float d      = ta.ema(k, d1)
    float kd     = nz(fixnan(ta.stoch(d, d, d, cycle)))
    float stc    = ta.ema(kd, d2)
    float result = math.max(math.min(stc, 100), 0)


// @function                Calculates the %K and %D values of the Full Stochastic indicator. 
// @param periodK           (simple int) Number of bars for Stochastic calculation. (length).
// @param smoothK           (simple int) Number of bars for smoothing of the %K value (length).
// @param periodD           (simple int) Number of bars for smoothing of the %D value (length).
// @returns                 ([float, float]) A tuple of the slow %K and the %D moving average values. 
export stochFull(simple int periodK, simple int smoothK, simple int periodD) =>
    float k = ta.sma(ta.stoch(close, high, low, periodK), smoothK)
    float d = ta.sma(k, periodD)
    [k, d]


// @function                Calculates the %K and %D values of the Stochastic RSI indicator.
// @param lengthRsi         (simple int) Length for the RSI smoothing parameter calculation.
// @param periodK           (simple int) Number of bars for Stochastic calculation. (length).
// @param smoothK           (simple int) Number of bars for smoothing of the %K value (length).
// @param periodD           (simple int) Number of bars for smoothing of the %D value (length).
// @param source            (series int/float) Series of values to process. Optional. The default is `close`.
// @returns                 ([float, float]) A tuple of the slow %K and the %D moving average values. 
export stochRsi(
     simple int lengthRsi, simple int periodK, simple int smoothK, simple int periodD, series float source = close
 ) =>
    float rsi = ta.rsi(source, lengthRsi)
    float k   = ta.sma(ta.stoch(rsi, rsi, rsi, periodK), smoothK)
    float d   = ta.sma(k, periodD)
    [k, d]


// @function                Calculates the values of the SuperTrend indicator with the ability to take candle wicks 
//                          into account, rather than only the closing price. 
// @param factor            (series int/float) Multiplier for the ATR value. 
// @param atrLength         (simple int) Length for the ATR smoothing parameter calculation.
// @param wicks             (simple bool) Condition to determine whether to take candle wicks into account when 
//                          reversing trend, or to use the close price. Optional. Default is false.
// @returns                 ([float, int]) A tuple of the superTrend value and trend direction. 
export supertrend(series float factor, simple int atrLength, simple bool wicks = false) =>
	float source        = hl2
    int   direction     = na
	float superTrend    = na
	float atr           = ta.atr(atrLength) * factor
	float upperBand     = source + atr
	float lowerBand     = source - atr
	float highPrice 	= wicks ? high : close
    float lowPrice  	= wicks ? low  : close
	float prevLowerBand = nz(lowerBand[1])
	float prevUpperBand = nz(upperBand[1])

	lowerBand := lowerBand > prevLowerBand or  lowPrice[1] < prevLowerBand ? lowerBand : prevLowerBand
	upperBand := upperBand < prevUpperBand or highPrice[1] > prevUpperBand ? upperBand : prevUpperBand
	float prevSuperTrend  = superTrend[1]
	if na(atr[1])
		direction := 1
	else if prevSuperTrend == prevUpperBand 
		direction := highPrice > upperBand ? -1 : 1
	else
		direction := lowPrice < lowerBand ? 1 : -1
	superTrend := direction == -1 ? lowerBand : upperBand
	[superTrend, direction]


// @function                An alternate SuperTrend function to `supertrend()`, which allows a "series float" 
//                          `atrLength` argument.
// @param factor            (series int/float) Multiplier for the ATR value. 
// @param atrLength         (series int/float) Length for the ATR smoothing parameter calculation.
// @param wicks             (simple bool) Condition to determine whether to take candle wicks into account when 
//                          reversing trend, or to use the close price. Optional. Default is false.
// @returns                 ([float, int]) A tuple of the superTrend value and trend direction. 
export supertrend2(series float factor, series float atrLength, simple bool wicks = false) =>
	float source        = hl2
    int   direction     = na
	float superTrend    = na
	float atr           = atr2(atrLength) * factor
	float upperBand     = source + atr
	float lowerBand     = source - atr
	float highPrice 	= wicks ? high : close
    float lowPrice  	= wicks ? low  : close
	float prevLowerBand = nz(lowerBand[1])
	float prevUpperBand = nz(upperBand[1])

	lowerBand := lowerBand > prevLowerBand or  lowPrice[1] < prevLowerBand ? lowerBand : prevLowerBand
	upperBand := upperBand < prevUpperBand or highPrice[1] > prevUpperBand ? upperBand : prevUpperBand
	float prevSuperTrend  = superTrend[1]
	if na(atr[1])
		direction := 1
	else if prevSuperTrend == prevUpperBand 
		direction := highPrice > upperBand ? -1 : 1
	else
		direction := lowPrice < lowerBand ? 1 : -1
	superTrend := direction == -1 ? lowerBand : upperBand
	[superTrend, direction]


// @function                Calculates the Generalized DEMA (GD) used in the `t3()` function.
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @param vf                (simple float) Volume factor. Affects the responsiveness.
// @returns                 (float) The GD value of the `source`.
gd(series float source, simple int length, simple float vf) =>
    float result = ta.ema(source, length) * (1 + vf) - ta.ema(ta.ema(source, length), length) * vf


// @function                Calculates the value of the Tilson Moving Average (T3). 
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @param vf                (simple float) Volume factor. Affects the responsiveness. 
// @returns                 (float) The Tilson moving average of the `source`. 
export t3(series float source, simple int length, simple float vf = 0.7) =>
    float result = gd(gd(gd(source, length, vf), length, vf), length, vf)


// @function                An alternate Generalized DEMA (GD) function to `gd()`, which allows a "series float" 
//                          `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @param vf                (simple float) Volume factor. Affects the responsiveness.
// @returns                 (float) The GD value of the `source`.
gd2(series float source, series float length, simple float vf) =>
    float result = ema2(source, length) * (1 + vf) - ema2(ema2(source, length), length) * vf


// @function                An alternate Tilson Moving Average (T3) function to `t3()`, which allows a "series float" 
//                          `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @param vf                (simple float) Volume factor. Affects the responsiveness. 
// @returns                 (float) The Tilson moving average of the `source`. 
export t3Alt(series float source, series float length, simple float vf = 0.7) =>
    float result = gd2(gd2(gd2(source, length, vf), length, vf), length, vf)


// @function                Calculates the value of the Triple Exponential Moving Average (TEMA). 
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The triple exponentially weighted moving average of the `source`.
export tema(series float source, simple int length) =>
    float ema1   = ta.ema(source,  length)
    float ema2   = ta.ema(ema1, length)
    float ema3   = ta.ema(ema2, length)
    float result = 3 * (ema1 - ema2) + ema3


// @function                An alternate Triple Exponential Moving Average (TEMA) function to `tema()`, which allows a 
//                          "series float" `length` argument.
// @param source            (series int/float) Series of values to process.
// @param length            (series int/float) Length for the smoothing parameter calculation.
// @returns                 (float) The triple exponentially weighted moving average of the `source`.
export tema2(series float source, series float length) =>
    float ema1   = ema2(source,  length)
    float ema2   = ema2(ema1, length)
    float ema3   = ema2(ema2, length)
    float result = 3 * (ema1 - ema2) + ema3


// @function                Calculates the value of the Triangular Moving Average (TRIMA).
// @param source            (series int/float) Series of values to process.
// @param length            (series int) Number of bars (length).
// @returns                 (float) The triangular moving average of the `source`.
export trima(series float source, series int length) =>
    float result = ta.sma(ta.sma(source, math.ceil(length / 2)), math.floor(length / 2) + 1)


// @function                Calculates the values of the TRIX indicator.
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @param signalLength      (simple int) Length for smoothing the signal line.
// @param exponential       (simple bool) Condition to determine whether exponential or simple smoothing is used. 
//                          Optional. The default is `true` (exponential smoothing). 
// @returns                 ([float, float, float]) A tuple of the TRIX value, the signal value, and the histogram. 
export trix(series float source, simple int length, simple int signalLength, simple bool exponential = true) =>
    float triple = ta.ema(ta.ema(ta.ema(source, length), length), length)
    float trix   = ta.roc(triple, 1)
    float signal = exponential ? ta.ema(trix, signalLength) : ta.sma(trix, signalLength)
    float hist   = trix - signal
    [trix, signal, hist]


// @function                Calculates the value of the Sentiment Zone Oscillator.
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The oscillator value. 
export szo(series float source, simple int length) =>
    float trend   = math.sign(ta.change(source))
    float sentPos = tema(trend, length)
    float result  = 100 * sentPos / length


// @function                Calculates the weighted average used in the `uo()` function.
// @param bp                (series float) A source series representing the "Buying Pressure" value.
// @param trange            (series float) A source series representing the True Range value.
// @param length            (simple int) Number of bars (length).
// @returns                 (float) The weighted average value.
uoAverage(series float bp, series float trange, simple int length) =>
    float result = math.sum(bp, length) / math.sum(trange, length)


// @function                Calculates the value of the Ultimate Oscillator. 
// @param fastLen           (series int) Number of bars for the fast smoothing average (length).
// @param midLen            (series int) Number of bars for the middle smoothing average (length).
// @param slowLen           (series int) Number of bars for the slow smoothing average (length).
// @returns                 (float) The oscillator value. 
export uo(simple int fastLen, simple int midLen, simple int slowLen) =>
    float tMax   = math.max(high, close[1])
    float tMin   = math.min(low,  close[1])
    float tr     = tMax  - tMin
    float bp     = close - tMin
    float avg1   = uoAverage(bp, tr, fastLen)
    float avg2   = uoAverage(bp, tr, midLen)
    float avg3   = uoAverage(bp, tr, slowLen)
    float result = 100 * (4 * avg1 + 2 * avg2 + avg3) / 7


// @function                Calculates the value of the Vertical Horizontal Filter. 
// @param source            (series int/float) Series of values to process.
// @param length            (simple int) Number of bars (length).
// @returns                 (float) The oscillator value. 
export vhf(series float source, simple int length) =>
    float sumChanges = math.sum(math.abs(ta.change(source)), length)
    float result     = math.abs(ta.highest(source, length) - ta.lowest(source, length)) / sumChanges


// @function                Calculates the values of the Vortex Indicator.
// @param length            (simple int) Number of bars (length).
// @returns                 ([float, float]) A tuple of the viPlus and viMinus values. 
export vi(simple int length) =>
    divisor = math.sum(ta.atr(1), length)
    viPlus  = math.sum(math.abs(high -  nz(low[1])), length) / divisor
    viMinus = math.sum(math.abs(low  - nz(high[1])), length) / divisor
    [viPlus, viMinus]


// @function                Calculates an ATR-based stop value that trails behind the `source`. Can serve as a 
//                          possible stop-loss guide and trend identifier.
// @param source            (series int/float) Series of values that the stop trails behind.
// @param atrLength         (simple int) Length for the ATR smoothing parameter calculation.
// @param atrFactor         (series int/float) The multiplier of the ATR value. Affects the maximum distance between 
//                          the stop and the `source` value. A value of 1 means the maximum distance is 100% of the 
//                          ATR value. Optional. The default is 1.
// @returns                 ([float, bool]) A tuple of the volatility stop value and the trend direction as a "bool". 
export vStop(series float source, simple int atrLength, series float atrFactor = 1) =>
    float src  = nz(source, close)
    float atrM = nz(ta.atr(atrLength) * atrFactor, ta.tr(true))
    var bool  trendUp = true
    var float max     = src
    var float min     = src
    var float stop    = 0.0
    max     := math.max(max, src)
    min     := math.min(min, src)
    stop    := nz(trendUp ? math.max(stop, max - atrM) : math.min(stop, min + atrM), src)
    trendUp := src - stop >= 0.0
    if trendUp != nz(trendUp[1], true)
        max  := src
        min  := src
        stop := trendUp ? max - atrM : min + atrM
    [stop, trendUp]


// @function                An alternate Volatility Stop function to `vStop()`, which allows a "series float" 
//                          `atrLength` argument.
// @param source            (series int/float) Series of values that the stop trails behind.
// @param atrLength         (series int/float) Length for the ATR smoothing parameter calculation.
// @param atrFactor         (series int/float) The multiplier of the ATR value. Affects the maximum distance between 
//                          the stop and the `source` value. A value of 1 means the maximum distance is 100% of the 
//                          ATR value. Optional. The default is 1.
// @returns                 ([float, bool]) A tuple of the volatility stop value and the trend direction as a "bool". 
export vStop2(series float source, series float atrLength, series float atrFactor = 1) =>
    float src  = nz(source, close)
    float atrM = nz(atr2(atrLength) * atrFactor, ta.tr(true))
    var bool  trendUp = true
    var float max     = src
    var float min     = src
    var float stop    = 0.0
    max     := math.max(max, src)
    min     := math.min(min, src)
    stop    := nz(trendUp ? math.max(stop, max - atrM) : math.min(stop, min + atrM), src)
    trendUp := src - stop >= 0.0
    if trendUp != nz(trendUp[1], true)
        max  := src
        min  := src
        stop := trendUp ? max - atrM : min + atrM
    [stop, trendUp]


// @function                Calculates the value of the Volume Zone Oscillator.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The oscillator value. 
export vzo(simple int length) =>
    float result = zone(volume, length)


// @function                Detects Williams fractals (used by `williamsFractal()`).
// @param source            (series int/float) Series of values to process.
// @param n                 (series int) Lookback in bars.
// @param direction         (simple int) Direction of the fractal to detect (+1 for up, -1 for down).
// @returns                 (series bool) `true` when a fractal was detected, `false` otherwise.
flag(series float source, series int n, simple int direction) =>
    float src  = source * direction
    bool flag  = true
    bool flag0 = true
    bool flag1 = true
    bool flag2 = true
    bool flag3 = true
    bool flag4 = true
    for i = 1 to n
        flag  := flag  and (src[n - i] <  src[n])
        flag0 := flag0 and (src[n + i] <  src[n])
        flag1 := flag1 and (src[n + 1] <= src[n] and src[n + i + 1] < src[n])
        flag2 := flag2 and (src[n + 1] <= src[n] and src[n + 2] <= src[n] and src[n + i + 2] < src[n])
        flag3 := flag3 and (src[n + 1] <= src[n] and src[n + 2] <= src[n] and src[n + 3] <= src[n] and src[n + i + 3] < src[n])
        flag4 := flag4 and (src[n + 1] <= src[n] and src[n + 2] <= src[n] and src[n + 3] <= src[n] and src[n + 4] <= src[n] and src[n + i + 4] < src[n])
    bool flags = flag0 or flag1 or flag2 or flag3 or flag4
    bool result = (flag and flags)


// @function                Detects Williams Fractals. 
// @param period            (series int) Number of bars (length).
// @returns                 ([bool, bool]) A tuple of an up fractal and down fractal. Variables are true when detected. 
export williamsFractal(series int period) =>
    bool upFractal   = flag(high, period,  1)
    bool downFractal = flag(low,  period, -1)
    [upFractal, downFractal]


// @function                Calculates the value of the Wave Period Oscillator.
// @param length            (simple int) Length for the smoothing parameter calculation.
// @returns                 (float) The oscillator value. 
export wpo(simple int length) =>
    float tt     = 2 * math.pi / math.asin(close[1] / high)
    float ti     = math.sign(ta.change(close)) * tt
    float result = ta.ema(ti, length)



plot(cagr(time[1], close[1], time, close))
