Dual timeframe calculated candles
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © m_b_round

//@version=5
indicator("Dual timeframe calculated candles", overlay=true)

timeframeMultiplier = input.int(2, "Timeframe multiplier", step=1)
emaLength = input.int(200, "EMA Length")
ATRPeriods = input.int(14, "ATR Period")
// retainHTF = input.int(50, "HTF History to keep")
highlight = input.bool(false, "Highlight HTF Completion", tooltip="With this selected, the bar where the HTF candle is completed is highlighted - on this bar your EMA etc should match the higher timeframe.")

array_getBarsBack(_array, _barsBack) =>
    var float _ret = na
    if array.size(_array) >= _barsBack
        _ret := array.get(_array, array.size(_array) - _barsBack)
    _ret

update_HTF_atr(_array, this_tr, last_atr, _ATRPeriods) =>
    // atr is calculated using rma, which is ema but with alpha = 1 / atr Length
    float alpha = 1 / _ATRPeriods
    _ret = 0.0
    if array.size(_array) <= _ATRPeriods
        _ret := nz(array.avg(_array))
    else
        _ret := alpha * this_tr + (1 - alpha) * last_atr
    _ret

update_HTF_ema(_array, source, last_ema, _EMAPeriods) =>
    //EMA = alpha * source + (1 - alpha) * EMA[1], where alpha = 2 / (length + 1).
    float alpha = 2 / (_EMAPeriods + 1)
    _ret = 0.0
    if array.size(_array) <= _EMAPeriods
        _ret := nz(array.avg(_array), close)
    else
        _ret := alpha * source + (1 - alpha) * nz(last_ema)
    _ret

highTFinMinutes = timeframe.in_seconds()/60 * timeframeMultiplier
highTFasString = highTFinMinutes >= 1 ? str.tostring(highTFinMinutes) : str.tostring(highTFinMinutes*60) + "S"

HTF_complete = time_close(highTFasString) == time_close

var HTF_opens = array.new_float()
var HTF_closes = array.new_float()
var HTF_lows = array.new_float()
var HTF_highs = array.new_float()
var HTF_volumes = array.new_float()
var HTF_truerange = array.new_float()
var HTF_ATR = array.new_float()
var HTF_EMA = array.new_float()

countBars = nz(ta.barssince(HTF_complete[1]))
totalVol = math.sum(volume, countBars + 1)
var float _tr = 0.0
prevClose = nz(ta.valuewhen(HTF_complete, close, 0))

if HTF_complete[1] or barstate.isfirst
    array.push(HTF_highs, high)
    array.push(HTF_closes, close)
    array.push(HTF_lows, low)
    array.push(HTF_opens, open)
    array.push(HTF_volumes, volume)
    _tr := math.max(math.max(high-low, math.abs(high-nz(close[1]))), math.abs(low-nz(close[1]))) // we can run this as i's the first LTF bar in the HTF candle
    array.push(HTF_truerange, _tr)
    array.push(HTF_ATR, update_HTF_atr(HTF_truerange, _tr, array_getBarsBack(HTF_ATR, 1), ATRPeriods))
    array.push(HTF_EMA, update_HTF_ema(HTF_EMA, close, array_getBarsBack(HTF_EMA, 1), emaLength))
else
    _highest = 0.0
    _lowest = 0.0
    if array.size(HTF_highs) > 0
        _prevHigh = array.pop(HTF_highs)
        _highest := math.max(_prevHigh, high)
        array.push(HTF_highs, _highest)

    if array.size(HTF_lows) > 0
        _prevlow = array.pop(HTF_lows)
        _lowest := math.min(_prevlow, low)
        array.push(HTF_lows, _lowest)

    if array.size(HTF_closes) > 0
        array.pop(HTF_closes)
        array.push(HTF_closes, close)
    
    if array.size(HTF_volumes) > 0
        array.pop(HTF_volumes)
        array.push(HTF_volumes, totalVol)
        
    _tr := math.max(math.max(_highest-_lowest, math.abs(_highest-nz(prevClose))), math.abs(_lowest-nz(prevClose))) // we can run this as i's the first LTF bar in the HTF candle
    if array.size(HTF_truerange) > 0
        array.pop(HTF_truerange)
        array.push(HTF_truerange, _tr)
    
    array.pop(HTF_ATR)
    array.push(HTF_ATR, update_HTF_atr(HTF_truerange, _tr, array_getBarsBack(HTF_ATR, 1), ATRPeriods))

    array.pop(HTF_EMA)
    array.push(HTF_EMA, update_HTF_ema(HTF_EMA, close, array_getBarsBack(HTF_EMA, 1), emaLength))

if HTF_complete[1] and array.size(HTF_closes) > (emaLength * 1.1 + 2)
    array.shift(HTF_closes)
    array.shift(HTF_opens)
    array.shift(HTF_lows)
    array.shift(HTF_highs)
    array.shift(HTF_volumes)
    array.shift(HTF_truerange)
    array.shift(HTF_ATR)
    array.shift(HTF_EMA)

plot(array_getBarsBack(HTF_EMA, 1), color=color.blue, title="HTF EMA")
plot(array_getBarsBack(HTF_ATR, 1), color=color.red, title="HTF ATR", display=display.all-display.pane)

bgcolor(highlight and HTF_complete ? color.new(color.orange,50) : na)