Smoothed Heikin Ashi Trend on Chart - TraderHalai
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/

// © TraderHalai

//@version=5
indicator("Smoothed Heikin Ashi Trend on Chart - TraderHalai", " SHA Trend - TraderHalai", overlay=true)

//Inputs
i_repaint               =       input (true,  "Repaint -  Keep on for live / Off for backtest", group = "Global Options")

i_useSmooth             =       input ( true, "Use smoothing Heikin Ashi", group="Smooth Heikin Ashi Trend")
i_heikinAshiMethod      =       input.string("Heikin Ashi Close", "Heikin Ashi Method", options=["Heikin Ashi Close", "Heikin Ashi Better Close"], group="Smooth Heikin Ashi Trend")
i_smoothingMethod       =       input.string("SMA", "Smoothing method", options=["SMA", "EMA", "HMA", "VMWA", "RMA"] , group="Smooth Heikin Ashi Trend")
i_smoothingPeriod       =       input ( 10, "Smoothing period" , group="Smooth Heikin Ashi Trend")
i_adjustForVolatility   =       input (true,  "Adapt for Volatility (ATR)", group="Smooth Heikin Ashi Trend")

i_showVolatilityPivot   =       input (false, "Show Volatility Pivot Line", group="Volatility Pivot Line")
i_volPivot_maType       =       input.string  ("Three-pole Ehlers Smoother", "HAVP Smoothing type", options= ["Two-pole Ehlers Butterworth", "Two-pole Ehlers smoother", "Three-pole Ehlers Butterworth", "Three-pole Ehlers Smoother"], group='Volatility Pivot Line')
i_volSmoothingPeriod    =       input.int     (13,   "Smoothing Length",                 minval=1,                                                           group='Volatility Pivot Line')


i_infoBox               =       input ( true, "Show Info Box"        , group="Info Box")
i_decimalP              =       input ( 2,    "Prices Decimal Places", group="Info Box") 
i_boxOffSet             =       input ( 5,    "Info Box Offset"      , group="Info Box")


//Security functions to avoid repaint, as per PineCoders
f_security(_symbol, _res, _src, _repaint) => request.security(_symbol, _res, _src[_repaint ? 0 : barstate.isrealtime ? 1 : 0])[_repaint ? 0 : barstate.isrealtime ? 0 : 1]

//Common Variables
timeperiod = timeframe.period

candleClose = f_security(syminfo.tickerid, timeperiod, close, i_repaint)
candleOpen = f_security(syminfo.tickerid, timeperiod, open, i_repaint)
candleLow = f_security(syminfo.tickerid, timeperiod, low, i_repaint)
candleHigh = f_security(syminfo.tickerid, timeperiod, high, i_repaint)


//Heikin Ashi Reverse Close function
heikinAshiReverseClose(bool volatilityAdjusted) =>
    haTicker = ticker.heikinashi(syminfo.tickerid)
    haClose = f_security(haTicker, timeperiod, close, i_repaint)
    haOpen = f_security(haTicker, timeperiod, open, i_repaint)
    haLow = f_security(haTicker, timeperiod, low, i_repaint)
    haHigh= f_security(haTicker, timeperiod, high, i_repaint)


    reverseClose = (2 * (haOpen[1] + haClose[1])) - candleHigh - candleLow - candleOpen

    if(reverseClose < candleLow and volatilityAdjusted)
        reverseClose := (candleLow + reverseClose) / 2

    if(reverseClose > candleHigh and volatilityAdjusted)
        reverseClose := (candleHigh + reverseClose) / 2
    
    reverseClose

//Heikin Ashi Better Reverse Close function - As described by BNP Paribas in 2005

heikinAshiBetterReverseClose(bool volatilityAdjusted) =>
    habClose = ((candleOpen + candleClose) /2 ) + ((candleClose - candleOpen)/(candleHigh-candleLow)) * (math.abs(candleClose - candleOpen) / 2)
    habOpen = 0.
    habOpen := (nz(habOpen[1]) + habClose[1])/2


    //Calculate reverse Better HA close - as this formula uses Square roots, it is possible for non real values to be produced, therefore we need to try all 4 permutations
    
    // c = 1/2 (sqrt(-8 a h + 8 a l + h^2 - 2 h l + 8 h o + l^2 - 8 l o) + h - l + 2 o) - Formula sourced from Wolfram Alpha, solving for close price.

    reverseBetterA = (-math.sqrt(candleHigh - candleLow)    * math.sqrt(candleHigh - candleLow + 8 * candleOpen - 8 * habOpen) + candleHigh - candleLow + 2*candleOpen) /2
    reverseBetterB = (math.sqrt(candleHigh - candleLow)     * math.sqrt(candleHigh - candleLow - 8 * candleOpen + 8 * habOpen) + candleHigh - candleLow + 2*candleOpen) /2
    reverseBetterC = (-math.sqrt(candleHigh - candleLow)     * math.sqrt(candleHigh - candleLow - 8 * candleOpen + 8 * habOpen) - candleHigh + candleLow + 2*candleOpen) /2
    reverseBetterD = (math.sqrt(candleHigh - candleLow)     * math.sqrt(candleHigh - candleLow - 8 * candleOpen + 8 * habOpen) - candleHigh + candleLow + 2*candleOpen) /2

    //Try reverse reverse function to see which one fits

    rreverseBetterA = ((candleOpen + reverseBetterA) /2 ) + ((reverseBetterA - candleOpen)/(candleHigh-candleLow)) * (math.abs(reverseBetterA - candleOpen) / 2)
    rreverseBetterB = ((candleOpen + reverseBetterB) /2 ) + ((reverseBetterB - candleOpen)/(candleHigh-candleLow)) * (math.abs(reverseBetterB - candleOpen) / 2)
    rreverseBetterC = ((candleOpen + reverseBetterC) /2 ) + ((reverseBetterC - candleOpen)/(candleHigh-candleLow)) * (math.abs(reverseBetterC - candleOpen) / 2)
    rreverseBetterD = ((candleOpen + reverseBetterD) /2 ) + ((reverseBetterD - candleOpen)/(candleHigh-candleLow)) * (math.abs(reverseBetterD - candleOpen) / 2)

    float reverseClose = na

    if(rreverseBetterA == habOpen)
        reverseClose := reverseBetterA
    else if(rreverseBetterB == habOpen)
        reverseClose := reverseBetterB
    else if(rreverseBetterC == habOpen)
        reverseClose := reverseBetterC
    else if(rreverseBetterD == habOpen)
        reverseClose := reverseBetterD
        
    if(reverseClose < candleLow and volatilityAdjusted)
        reverseClose := (candleLow + reverseClose) / 2

    if(reverseClose > candleHigh and volatilityAdjusted)
        reverseClose := (candleHigh + reverseClose) / 2
    
    reverseClose

//EHLER Smoothers

//Three pole Ehlers Butterworth
_3polebuttfilt(src, len)=>
    a1 = 0., b1 = 0., c1 = 0.
    coef1 = 0., coef2 = 0., coef3 = 0., coef4 = 0.
    bttr = 0., trig = 0.
    a1 := math.exp(-math.pi / len)
    b1 := 2 * a1 * math.cos(1.738 * math.pi / len)
    c1 := a1 * a1
    coef2 := b1 + c1
    coef3 := -(c1 + b1 * c1)
    coef4 := c1 * c1
    coef1 := (1 - b1 + c1) * (1 - c1) / 8
    bttr := coef1 * (src + 3 * nz(src[1]) + 3 * nz(src[2]) + nz(src[3])) + coef2 * nz(bttr[1]) + coef3 * nz(bttr[2]) + coef4 * nz(bttr[3])
    bttr := bar_index < 4 ? src : bttr
    trig := nz(bttr[1])
    bttr

_ssf3(src, length) =>
    arg = math.pi / length
    a1 = math.exp(-arg)
    b1 = 2 * a1 * math.cos(1.738 * arg)
    c1 = math.pow(a1, 2)

    coef4 = math.pow(c1, 2)
    coef3 = -(c1 + b1 * c1)
    coef2 = b1 + c1
    coef1 = 1 - coef2 - coef3 - coef4

    src1 = nz(src[1], src)
    src2 = nz(src[2], src1)
    src3 = nz(src[3], src2)

    ssf = 0.0
    ssf := coef1 * src + coef2 * nz(ssf[1], src1) + coef3 * nz(ssf[2], src2) + coef4 * nz(ssf[3], src3)

//Three pole Ehlers smoother
_3polesss(src, len)=>
    a1 = 0., b1 = 0., c1 = 0.
    coef1 = 0., coef2 = 0., coef3 = 0., coef4 = 0.
    filt = 0., trig = 0.
    a1 := math.exp(-math.pi / len)
    b1 := 2 * a1 * math.cos(1.738 * math.pi / len)
    c1 := a1 * a1
    coef2 := b1 + c1
    coef3 := -(c1 + b1 * c1)
    coef4 := c1 * c1
    coef1 := 1 - coef2 - coef3 - coef4
    filt := coef1 * src + coef2 * nz(filt[1]) + coef3 * nz(filt[2]) + coef4 * nz(filt[3])
    filt := bar_index < 4 ? src : filt
    trig := nz(filt[1])
    filt

//Two pole Ehlers Butterworth
_2polebutter(src, len)=>
    a1 = 0., b1 = 0.
    coef1 = 0., coef2 = 0., coef3 = 0.
    bttr = 0., trig = 0.
    a1 := math.exp(-1.414 * math.pi / len)
    b1 := 2 * a1 * math.cos(1.414 * math.pi / len)
    coef2 := b1
    coef3 := -a1 * a1
    coef1 := (1 - b1 + a1 * a1) / 4
    bttr := coef1 * (src + 2 * nz(src[1]) + nz(src[2])) + coef2 * nz(bttr[1]) + coef3 * nz(bttr[2])
    bttr := bar_index < 3 ? src : bttr
    trig := nz(bttr[1])
    bttr

//Two pole Ehlers smoother
_2poless(src, len)=>
    a1 = 0., b1 = 0.
    coef1 = 0., coef2 = 0., coef3 = 0.
    filt = 0., trig = 0.
    a1 := math.exp(-1.414 * math.pi / len)
    b1 := 2 * a1 * math.cos(1.414 * math.pi / len)
    coef2 := b1
    coef3 := -a1 * a1
    coef1 := 1 - coef2 - coef3
    filt := coef1 * src + coef2 * nz(filt[1]) + coef3 * nz(filt[2])
    filt := bar_index < 3 ? src : filt
    trig := nz(filt[1])
    filt

//Volatility Pivot Line Smoothing options
f_volatilityMaType (_price, _len, _type) =>
    _type == 'Three-pole Ehlers Smoother' ? _3polesss(_price, _len) : _type == "Two-pole Ehlers Butterworth" ?  _2polebutter(_price, _len) : _type == "Two-pole Ehlers smoother" ?  _2poless(_price, _len) :  _type == "Three-pole Ehlers Butterworth" ?  _3polebuttfilt(_price, _len) : _ssf3(_price, _len)

//Rounding function to truncate decimal places
f_truncdNum ( Val, DecPl ) => 
    Fact = math.pow (10, DecPl)
    int( Val * Fact) / Fact


//Smoothing Heikin Ashi
    
reverseClose = i_heikinAshiMethod == "Heikin Ashi Better Close" ? heikinAshiBetterReverseClose(i_adjustForVolatility) : heikinAshiReverseClose(i_adjustForVolatility)

smaSmoothed = ta.sma(reverseClose, i_smoothingPeriod)
emaSmoothed = ta.ema(reverseClose, i_smoothingPeriod)
hmaSmoothed = ta.hma(reverseClose, i_smoothingPeriod)
vwmaSmoothed = ta.vwma(reverseClose, i_smoothingPeriod)
rmaSmoothed = ta.rma(reverseClose, i_smoothingPeriod)

shouldApplySmoothing = i_useSmooth and i_smoothingPeriod > 1 

smoothedReverseClose = reverseClose

if(shouldApplySmoothing)
    if(i_smoothingMethod == "SMA")
        smoothedReverseClose := smaSmoothed
    else if(i_smoothingMethod == "EMA")
        smoothedReverseClose := emaSmoothed
    else if(i_smoothingMethod == "HMA")
        smoothedReverseClose := hmaSmoothed
    else if(i_smoothingMethod == "VWMA")
        smoothedReverseClose := vwmaSmoothed
    else if(i_smoothingMethod == "RMA")
        smoothedReverseClose := rmaSmoothed
    else 
        smoothedReverseClose := reverseClose // Default to non-smoothed for invalid smoothing type
    
    
//Calculations
haBull = candleClose >= smoothedReverseClose

delta = candleClose - reverseClose

haPivotPrice = reverseClose + delta[1]

haPivotPrice := f_volatilityMaType(haPivotPrice, i_volSmoothingPeriod, i_volPivot_maType)

// Compute Info Label
haDirectionText = haBull ? 'BULLISH Above: ' + '\t\t\t\t\t\t\t\t' : 'BEARISH Below: ' + '\t\t\t\t\t\t\t\t'
haPivotText = (haBull and close > haPivotPrice) ? 'EXPANDING above: ' + '\t\t\t\t': (not(haBull) and close < haPivotPrice) ? 'EXPANDING below: ' + '\t\t\t\t' : (haBull and close <= haPivotPrice) ? 'CONTRACTING below: ' : (not(haBull and close >= haPivotPrice)) ? 'CONTRACTING above: ' : 'Currently equals'

var label  Infobox      = na

labelXLoc       = time_close + ( i_boxOffSet * ( time_close - time_close[1] ) )
infoBoxText     =  haDirectionText + str.tostring(f_truncdNum(smoothedReverseClose, i_decimalP)) + '\n' + haPivotText + + str.tostring(f_truncdNum(haPivotPrice, i_decimalP))
// InfoBox Plot

if i_infoBox
    Infobox := label.new ( labelXLoc, close, infoBoxText, xloc.bar_time, yloc.price, color.new(#00000f, 50), label.style_label_left, color.white )

label.delete ( Infobox[1] )

//Plots
haCol = haBull ? color.green : color.red
plot(smoothedReverseClose, color=haCol, linewidth=2)
plot(i_showVolatilityPivot? haPivotPrice : na, color=color.gray)

