Ultimate Heiken-Ashi
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradersForecast, used code from 'allanster'

//@version=5
indicator(title = "Ultimate Heiken-Ashi", shorttitle = "uHA", overlay = true)  //, scale=scale.none)
// Custom source function for toggling traditional Candle sources or Heikin-Ashi sources on a traditional Candles chart.
// Thanks to PineCoders for rounding method: https://www.pinecoders.com/faq_and_code/#how-can-i-round-to-ticks
// Thanks to @LucF and @RicardoSantos for their advice and enlightenment as always.

hknAsh = input.bool(true, title = "Heikin Ashi (else regular bars)?")
HAfact = input.float(1.0, title="Heikin Ashi factor")
lineOnly = input.bool(false, title = "Line (instead of bars)?")
existingBars  = input.bool(false, title = "Use existing bars as is (else force normal bars)?")
useModif4HA = input.bool(false, title="Use modified calculation?")
twiceHA = input.bool(false, title="Do HA 2nd time?")
useModif4HA2 = input.bool(false, title="Use modified calculation (2nd time)?")

upClr = input.color(color.green, "Up bar color")  //#0010FF90 #0040FF50  //#00BFFF50 //#26a69a50
dnClr = input.color(color.maroon, "Down bar color")  //#FFC70090 #FFA50050
lineClr = input.color(color.green, "Line color")

// functions
// isHA() =>
//     is_ha = na(open[1]) ? false : (round(open/syminfo.mintick)*syminfo.mintick == round(((nz(open[1]) + nz(close[1])) / 2) / syminfo.mintick) * syminfo.mintick ? true : false)

mkHA0(o_, h_, l_, c_, useModif) =>
    cl = math.round(((o_ + h_ + l_ + c_)/4) / syminfo.mintick) * syminfo.mintick // needs to be before /op/
    op = float(na)
    if useModif == true
        op := na(o_[1]) ? (o_ + HAfact*c_)/(1+HAfact) : (nz(op[1]) + HAfact*nz(cl[1]))/(1+HAfact)
    else
        op := na(o_[1]) ? (o_ + HAfact*c_)/(1+HAfact) : (nz(o_[1]) + HAfact*nz(c_[1]))/(1+HAfact)
    op := math.round(op / syminfo.mintick) * syminfo.mintick
    hi = math.round(math.max(h_, op, cl) / syminfo.mintick) * syminfo.mintick
    lo = math.round(math.min(l_, op, cl) / syminfo.mintick) * syminfo.mintick
    [op, hi, lo, cl]

mkHA(o_, h_, l_, c_, useModif_, twice, useModif2) =>
    [op, hi, lo, cl] = mkHA0(o_, h_, l_, c_, useModif_)
    if twice
        [op2, hi2, lo2, cl2] = mkHA0(op, hi, lo, cl, useModif2)
        op := op2
        hi := hi2
        lo := lo2
        cl := cl2
    [op, hi, lo, cl]


t = ticker.new(syminfo.prefix, syminfo.ticker, session.regular, adjustment.splits)
ono = request.security(t, timeframe.period, open, gaps=barmerge.gaps_on) // Official Normal Open
onh = request.security(t, timeframe.period, high, gaps=barmerge.gaps_on)
onl = request.security(t, timeframe.period, low, gaps=barmerge.gaps_on)
onc = request.security(t, timeframe.period, close, gaps=barmerge.gaps_on)
open_ = existingBars ? open : ono
high_ = existingBars ? high : onh
low_ = existingBars ? low :onl
close_ = existingBars ? close : onc

[O, H, L, C] = mkHA(open_, high_, low_, close_, useModif4HA, twiceHA, useModif4HA2)
if not hknAsh
    O := open_
    H := high_
    L := low_
    C := close_
upBar      = C > O
upBarOpen  = upBar ? O : na
upBarHigh  = upBar ? H : na
upBarLow   = upBar ? L : na
upBarClose = upBar ? C : na
dnBarOpen  = upBar ? na : O
dnBarHigh  = upBar ? na : H
dnBarLow   = upBar ? na : L
dnBarClose = upBar ? na : C
lineClose = lineOnly ? C : na
if lineOnly
    upBarOpen  := na
    upBarHigh  := na
    upBarLow   := na
    upBarClose := na
    dnBarOpen  := na
    dnBarHigh  := na
    dnBarLow   := na
    dnBarClose := na

plotcandle(upBarOpen, upBarHigh, upBarLow, upBarClose, title = "Up candle", color = upClr, wickcolor = upClr, bordercolor = upClr) // plot up bars
plotcandle(dnBarOpen, dnBarHigh, dnBarLow, dnBarClose, title = "Down candle", color = dnClr, wickcolor = dnClr, bordercolor = dnClr) // plot dn bars
plot(lineClose, title='Line plot', color = lineClr)

