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

//   __    __            __       __  __                  __       __                        __      __    __              __      _______             __    __                                          __ 
//  /  |  /  |          /  |  _  /  |/  |                /  \     /  |                      /  |    /  \  /  |            /  |    /       \           /  \  /  |                                        /  |
//  $$ |  $$ |  ______  $$ | / \ $$ |$$ |____    ______  $$  \   /$$ | __    __   _______  _$$ |_   $$  \ $$ |  ______   _$$ |_   $$$$$$$  |  ______  $$  \ $$ |  ______   _____  ____    ______    ____$$ |
//  $$ |__$$ | /      \ $$ |/$  \$$ |$$      \  /      \ $$$  \ /$$$ |/  |  /  | /       |/ $$   |  $$$  \$$ | /      \ / $$   |  $$ |__$$ | /      \ $$$  \$$ | /      \ /     \/    \  /      \  /    $$ |
//  $$    $$ |/$$$$$$  |$$ /$$$  $$ |$$$$$$$  |/$$$$$$  |$$$$  /$$$$ |$$ |  $$ |/$$$$$$$/ $$$$$$/   $$$$  $$ |/$$$$$$  |$$$$$$/   $$    $$< /$$$$$$  |$$$$  $$ | $$$$$$  |$$$$$$ $$$$  |/$$$$$$  |/$$$$$$$ |
//  $$$$$$$$ |$$    $$ |$$ $$/$$ $$ |$$ |  $$ |$$ |  $$ |$$ $$ $$/$$ |$$ |  $$ |$$      \   $$ | __ $$ $$ $$ |$$ |  $$ |  $$ | __ $$$$$$$  |$$    $$ |$$ $$ $$ | /    $$ |$$ | $$ | $$ |$$    $$ |$$ |  $$ |
//  $$ |  $$ |$$$$$$$$/ $$$$/  $$$$ |$$ |  $$ |$$ \__$$ |$$ |$$$/ $$ |$$ \__$$ | $$$$$$  |  $$ |/  |$$ |$$$$ |$$ \__$$ |  $$ |/  |$$ |__$$ |$$$$$$$$/ $$ |$$$$ |/$$$$$$$ |$$ | $$ | $$ |$$$$$$$$/ $$ \__$$ |
//  $$ |  $$ |$$       |$$$/    $$$ |$$ |  $$ |$$    $$/ $$ | $/  $$ |$$    $$/ /     $$/   $$  $$/ $$ | $$$ |$$    $$/   $$  $$/ $$    $$/ $$       |$$ | $$$ |$$    $$ |$$ | $$ | $$ |$$       |$$    $$ |
//  $$/   $$/  $$$$$$$/ $$/      $$/ $$/   $$/  $$$$$$/  $$/      $$/  $$$$$$/  $$$$$$$/     $$$$/  $$/   $$/  $$$$$$/     $$$$/  $$$$$$$/   $$$$$$$/ $$/   $$/  $$$$$$$/ $$/  $$/  $$/  $$$$$$$/  $$$$$$$/ 
//                                                                                                                                                                                                          
//                                                                                                                                                                                                          
//
//@version=5

// @description supertrend : Library dedicated to different variations of supertrend
library("supertrend", overlay=true)

import HeWhoMustNotBeNamed/enhanced_ta/10 as eta
import HeWhoMustNotBeNamed/zigzag/5 as zg

f_getCurrentState(source, states) =>
    currentState = array.size(states)
    for i = 0 to array.size(states) - 1 by 1
        if source < array.get(states, i)
            currentState := i
            break
    currentState

// @function supertrend_atr: Simple supertrend based on atr but also takes into consideration of custom MA Type, sources
// @param length : ATR Length
// @param multiplier : ATR Multiplier
// @param atrMaType : Moving Average type for ATR calculation. This can be sma, ema, hma, rma, wma, vwma, swma
// @param source : Default is close. Can Chose custom source
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param delayed : if set to true lags supertrend atr stop based on target levels.
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export supertrend_atr(simple int length, simple float multiplier, simple string atrMaType="sma",
                         float source = close, float highSource = high, float lowSource = low, simple bool waitForClose = false,
                         simple bool delayed = false) =>
	atrDiff = nz(eta.atr(atrMaType, length), ta.tr)*multiplier
	var buyStop = lowSource - atrDiff
	var sellStop = highSource + atrDiff
	var dir = 1
	
	buyStopCurrent = lowSource - atrDiff
	sellStopCurrent = highSource + atrDiff
	
	buyStopInverse = lowSource - atrDiff/2
	sellStopInverse = highSource + atrDiff/2
	
	highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	targetReached = (dir == 1 and nz(highConfirmation[1]) >= nz(sellStop[1])) or (dir == -1 and nz(lowConfirmation[1]) <= nz(buyStop[1])) or not delayed
	buyStop := dir == 1? (targetReached? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStop): targetReached ? buyStopCurrent : math.max(nz(buyStop, buyStopInverse), buyStopInverse)
	sellStop := dir == -1? (targetReached? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStop): targetReached? sellStopCurrent : math.min(nz(sellStop, sellStopInverse), sellStopInverse)
	[dir, dir > 0? buyStop : sellStop]
	

// @function supertrend_bands: Simple supertrend based on atr but also takes into consideration of custom MA Type, sources
// @param bandType : Type of band used - can be bb, kc or dc
// @param maType : Moving Average type for Bands. This can be sma, ema, hma, rma, wma, vwma, swma
// @param length : Band Length
// @param multiplier : Std deviation or ATR multiplier for Bollinger Bands and Keltner Channel
// @param source : Default is close. Can Chose custom source
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param useTrueRange : Used for Keltner channel. If set to false, then high-low is used as range instead of true range
// @param useAlternateSource - Custom source is used for Donchian Chanbel only if useAlternateSource is set to true
// @param alternateSource - Custom source for Donchian channel
// @param sticky : if set to true borders change only when price is beyond borders.
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export supertrend_bands(simple string bandType = "bb", simple string maType="sma", simple int length=20, float multiplier=2.0,
                             float source=close,float highSource = high, float lowSource = low, simple bool waitForClose = false,
                             simple bool useTrueRange=true, simple bool useAlternateSource = false, float alternateSource = close,
                             simple bool sticky=false)=>
    [bbmiddle, bbupper, bblower] = eta.bb(source, maType, length, multiplier, sticky)
    [kcmiddle, kcupper, kclower] = eta.kc(source, maType, length, multiplier, useTrueRange, sticky)
    [dcmiddle, dcupper, dclower] = eta.dc(length, useAlternateSource, alternateSource, sticky)
    
    lower = switch bandType
        "bb" => bblower
        "kc" => kclower
        => dclower
    upper = switch bandType
        "bb"=> bbupper
        "kc"=> kcupper
        => dcupper
                
    var buyStop = lower
	var sellStop = upper
	var dir = 1
	
	buyStopCurrent = lower
	sellStopCurrent = upper
	
	highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	buyStop := dir == 1? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStopCurrent
	sellStop := dir == -1? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStopCurrent
	[dir, dir > 0? buyStop : sellStop]

// @function supertrend_zigzag: Zigzag pivot based supertrend
// @param length : Zigzag Length
// @param history : number of historical pivots to consider
// @param useAlternateSource - Custom source is used for Zigzag only if useAlternateSource is set to true
// @param alternateSource - Custom source for Zigzag
// @param source : Default is close. Can Chose custom source
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param atrlength : ATR Length
// @param multiplier : ATR Multiplier
// @param atrMaType : Moving Average type for ATR calculation. This can be sma, ema, hma, rma, wma, vwma, swma
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export supertrend_zigzag(simple int length, simple int history = 1, simple bool useAlternativeSource = false, float alternativeSource=close,
                             float source = close, float highSource = high, float lowSource = low, simple bool waitForClose = false,
                             simple int atrlength=22, simple float multiplier=1.0, simple string atrMaType="sma")=>
    zigzagHistory = history*2 + 1
    [zigzagpivots, zigzagpivotbars, zigzagpivotdirs, zigzagpivotratios, zigzagoscillators,
                                     zigzagoscillatordirs, zigzagtrendbias, zigzagdivergence, 
                                     newPivot, doublePivot] = zg.zigzag(length, zigzagHistory, useAlternativeSource, alternativeSource)
    atrDiff = nz(eta.atr(atrMaType, atrlength), ta.tr)*multiplier
	var buyStop = array.min(zigzagpivots) - atrDiff
	var sellStop =array.max(zigzagpivots) + atrDiff
	var dir = 1
	
	buyStopCurrent = array.min(zigzagpivots) - atrDiff
	sellStopCurrent = array.max(zigzagpivots) + atrDiff
	
	highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	buyStop := dir == 1? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStopCurrent
	sellStop := dir == -1? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStopCurrent
	[dir, dir > 0? buyStop : sellStop]
	

// @function zupertrend: Zigzag pivot based supertrend
// @param length : Zigzag Length
// @param history : number of historical pivots to consider
// @param useAlternateSource - Custom source is used for Zigzag only if useAlternateSource is set to true
// @param alternateSource - Custom source for Zigzag
// @param source : Default is close. Can Chose custom source
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param atrlength : ATR Length
// @param multiplier : ATR Multiplier
// @param atrMaType : Moving Average type for ATR calculation. This can be sma, ema, hma, rma, wma, vwma, swma
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export zupertrend(simple int length, simple int history = 1, simple bool useAlternativeSource = false, float alternativeSource=close,
                             float source = close, float highSource = high, float lowSource = low, simple bool waitForClose = false,
                             simple int atrlength=22, simple float multiplier=1.0, simple string atrMaType="sma")=>
    zigzagHistory = history*2 + 1
    [zigzagpivots, zigzagpivotbars, zigzagpivotdirs, zigzagpivotratios, zigzagoscillators,
                                     zigzagoscillatordirs, zigzagtrendbias, zigzagdivergence, 
                                     newPivot, doublePivot] = zg.czigzag(length, zigzagHistory, useAlternativeSource?alternativeSource:highSource,  useAlternativeSource?alternativeSource:lowSource)
    atrDiff = nz(eta.atr(atrMaType, atrlength), ta.tr)*multiplier
	var buyStop = array.min(zigzagpivots) - atrDiff
	var sellStop =array.max(zigzagpivots) + atrDiff
	var dir = 1
	
	buyStopCurrent = array.min(zigzagpivots) - atrDiff
	sellStopCurrent = array.max(zigzagpivots) + atrDiff
	
	highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	buyStop := dir == 1? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStopCurrent
	sellStop := dir == -1? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStopCurrent
	[dir, dir > 0? buyStop : sellStop]
	
// @function zsupertrend: Same as zigzag supertrend. But, works on already calculated array rather than Calculating fresh zigzag
// @param zigzagpivots : Precalculated zigzag pivots
// @param history : number of historical pivots to consider
// @param source : Default is close. Can Chose custom source
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param atrlength : ATR Length
// @param multiplier : ATR Multiplier
// @param atrMaType : Moving Average type for ATR calculation. This can be sma, ema, hma, rma, wma, vwma, swma
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export zsupertrend(float[] zigzagpivots, simple int history = 1, float source = close, float highSource = high, float lowSource = low, 
                     simple bool waitForClose = false, simple string atrMaType = "sma", simple int atrlength = 22, simple float multiplier = 1)=>
    zigzagHistory = history*2 + 1
    
    shortArray = array.size(zigzagpivots) > zigzagHistory ? array.slice(zigzagpivots, 0, zigzagHistory) : zigzagpivots
    atrDiff = nz(eta.atr(atrMaType, atrlength), ta.tr)*multiplier
	var buyStop = array.min(shortArray) - atrDiff
	var sellStop =array.max(shortArray) + atrDiff
	var dir = 1
	
	buyStopCurrent = array.min(shortArray) - atrDiff
	sellStopCurrent = array.max(shortArray) + atrDiff
	
	highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	buyStop := dir == 1? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStopCurrent
	sellStop := dir == -1? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStopCurrent
	[dir, dir > 0? buyStop : sellStop]


// @function msupertrend : Dynamic trailing supertrend based on multiple bands - either bollinger bands or keltener channel
// @param bandType : Band type - can be either bb or kc
// @param source : custom source if required
// @param maType : Moving Average Type : Can be sma, ema, hma, rma, wma, vwma, swma, highlow, linreg, median
// @param length : Oscillator length - not used for TSI
// @param useTrueRange : if set to false, uses high-low.
// @param highSource : Default is high. Can also use close price for both high and low source
// @param lowSource : Default is low. Can also use close price for both high and low source
// @param waitForClose : Considers source for direction change crossover if checked. Else, uses highSource and lowSource.
// @param numberOfBands : Number of bands to generate
// @param multiplierStart : Starting ATR or Standard deviation multiplier for first band
// @param multiplierStep : Incremental value for multiplier for each band
// @param trailingDistance : Number of band states to trail for trailing stop.
// @param trailStates : If selected trails the band states along with trailing price. If unselected only price is trailed.
// @returns dir : Supertrend direction
//          supertrend : BuyStop if direction is 1 else SellStop
export msupertrend(simple string bandType="bb", float source=close, simple string maType = "sma", simple int length = 60, 
         simple bool useTrueRange=false, float highSource = high, float lowSource = low, simple bool waitForClose = false,
         simple int numberOfBands=20, simple float multiplierStart = 0.5, simple float multiplierStep = 0.5,
         simple int trailingDistance=10, simple bool trailStates = false)=>
    bands = eta.multibands(bandType, source, maType, length, useTrueRange, false, numberOfBands, multiplierStart, multiplierStep)
    currentState = f_getCurrentState(source, bands)
    var dir = 1
    var buyStopState = math.max(currentState - trailingDistance, 0)
    var sellStopState = math.min(currentState + trailingDistance, numberOfBands*2)

    var buyStop = array.get(bands, buyStopState)
    var sellStop = array.get(bands, sellStopState)
    
    currentBuyStopState = math.max(currentState - trailingDistance, 0)
    currentSellStopState = math.min(currentState + trailingDistance, numberOfBands*2)
    
    highConfirmation = waitForClose ? source : highSource
	lowConfirmation = waitForClose? source : lowSource
	dir := dir == 1 and lowConfirmation[1] < buyStop[1]? -1 : dir == -1 and highConfirmation[1] > sellStop[1]? 1 : dir
	
	buyStopState := trailStates and dir == 1? math.max(nz(buyStopState, currentBuyStopState), currentBuyStopState): currentBuyStopState
	sellStopState := trailStates and dir == -1? math.min(nz(sellStopState, currentSellStopState), currentSellStopState): currentSellStopState
	
	buyStopCurrent = array.get(bands, currentBuyStopState)
	sellStopCurrent = array.get(bands, currentSellStopState)
	
	buyStop := dir == 1? math.max(nz(buyStop, buyStopCurrent), buyStopCurrent): buyStopCurrent
	sellStop := dir == -1? math.min(nz(sellStop, sellStopCurrent), sellStopCurrent): sellStopCurrent
	[dir, dir > 0? buyStop : sellStop]
	
	
// ************************************************************* Examples ********************************************************************
type = input.string("zupertrend", "Type", options=["atr", "bands", "zigzag", "zsupertrend", "msupertrend","zupertrend"], group="Supertrend")
source = input.source(close, "Source", group="Supertrend")
highSource = input.source(high, "High", group="Supertrend")
lowSource = input.source(low, "Low", group="Supertrend")
waitForClose = input.bool(false, "Wait For Close", group="Supertrend")
stepped = input.bool(false, "Delayed/Sticky", group="Supertrend")

atrMaType = input.string("sma", "MA Type", options=["sma", "ema", "hma", "rma", "wma", "vwma", "swma", "highlow", "linreg", "median"], group="ATR")
atrLength = input.int(22, "MA Length", group="ATR")
atrMultiplier = input.int(4, "Multiplier", group="ATR")

bandType = input.string("bb", "Band Type", options=["bb", "kc", "dc"], group="Bands")
bandMaType = input.string("sma", "MA Type", options=["sma", "ema", "hma", "rma", "wma", "vwma", "swma", "highlow", "linreg", "median"], group="Bands")
bandLength = input.int(20, "MA Length", group="Bands")
bandMultiplier = input.int(2, "Multiplier", group="Bands")
useTrueRange = input.bool(true, title="Use True Range (KC)", group="Bands")
useAlternateSource = input.bool(false, title="Use Alternate Source (DC)", group="Bands")
alternateSource = input.source(close, "Alternate Source", group="Bands")
useSupertrendParamsForBands = input.bool(false, "Use Supertrend and ATR Settings", group="Bands")

numberOfBands = input.int(20, "Number of Bands", group="Multibands")
multiplierStart = input.float(0.5, "Start", group="Multibands")
multiplierStep = input.float(0.5, "Step", group="Multibands")
trailingDistance = input.int(10, "Trailing Distance", group="Multibands")
trailStates = input.bool(false, "Trail States", group = "Multibands")
zigzagLength = input.int(5, "Length", group="Zigzag")
history = input.int(1, "Pivot History", group="Zigzag")
useAlternateSourceZigzag = input.bool(false, title="Use Alternate Source", group="Zigzag")
alternateSourceZigzag = input.source(close, "Alternate Source", group="Zigzag")
useSupertrendAndAtrParamsForZigzag = input.bool(false, "Use Supertrend and ATR Settings", group="Zigzag")

dir = 1
supertrend = close
if(type == "atr")
    [dir_atr, supertrenda] = supertrend_atr(length=atrLength, multiplier=atrMultiplier, atrMaType=atrMaType,
                                                             source=source, highSource=highSource, lowSource=lowSource, 
                                                             waitForClose=waitForClose, delayed=stepped)
    dir := dir_atr
    supertrend := supertrenda
else if(type == "bands")
    if(useSupertrendParamsForBands)
        [dir_bands, supertrendb] = supertrend_bands(bandType = bandType, maType=bandMaType, length=bandLength, multiplier=bandMultiplier,
                                     source=source, highSource=highSource, lowSource=lowSource, waitForClose=waitForClose,
                                     useTrueRange=useTrueRange, useAlternateSource = useAlternateSource, alternateSource = alternateSource,
                                     sticky=stepped)
        dir := dir_bands
        supertrend := supertrendb
    else
        [dir_bands, supertrendb] = supertrend_bands(bandType = bandType, maType=bandMaType, length=bandLength, multiplier=bandMultiplier,
                                     useTrueRange=useTrueRange, useAlternateSource = useAlternateSource, alternateSource = alternateSource,
                                     sticky=stepped)
        dir := dir_bands
        supertrend := supertrendb
else if(type == "zigzag")
    if(useSupertrendAndAtrParamsForZigzag)
        [dir_zigzag, supertrendz] = supertrend_zigzag(length=zigzagLength, history = history, useAlternativeSource = useAlternateSourceZigzag, alternativeSource=alternateSourceZigzag,
                                 source=source, highSource=highSource, lowSource=lowSource, waitForClose=waitForClose,
                                 atrlength=atrLength, multiplier=atrMultiplier, atrMaType=atrMaType)
        dir := dir_zigzag
        supertrend := supertrendz
    else
        [dir_zigzag, supertrendz] = supertrend_zigzag(length=zigzagLength, history = history, useAlternativeSource = useAlternateSourceZigzag, alternativeSource=alternateSourceZigzag)
        dir := dir_zigzag
        supertrend := supertrendz
else if(type=="zsupertrend")
    [zigzagpivots, zigzagpivotbars, zigzagpivotdirs, zigzagpivotratios, zigzagoscillators,
                                     zigzagoscillatordirs, zigzagtrendbias, zigzagdivergence, 
                                     newPivot, doublePivot] = zg.zigzag(zigzagLength)
    [dir_zigzag, supertrendz] = zsupertrend(zigzagpivots, history)
    dir := dir_zigzag
    supertrend := supertrendz
else if(type=="zupertrend")
    [dir_zigzag, supertrendz] = zupertrend(length=zigzagLength, history = history, useAlternativeSource = useAlternateSourceZigzag, alternativeSource=alternateSourceZigzag,
                                 source=source, highSource=highSource, lowSource=lowSource, waitForClose=waitForClose,
                                 atrlength=atrLength, multiplier=atrMultiplier, atrMaType=atrMaType)
    dir := dir_zigzag
    supertrend := supertrendz
else
    [dir_m, supertrend_m] = msupertrend(bandType, source, bandMaType, bandLength, 
         useTrueRange, highSource, lowSource, waitForClose,
         numberOfBands, multiplierStart, multiplierStep, trailingDistance)
    dir := dir_m
    supertrend := supertrend_m

plot(supertrend, color = dir >0 ?  color.green : color.red, title="supertrend")