Lower timeframe chart
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © mickes

//@version=5
indicator("Lower timeframe chart", overlay = true)

var _intMax = 2147483647.
var _intMin = -2147483648.

_timeFrame = input.timeframe("15", "Lower time frame", group = "General")
_slotSizeAtrFactor = input.float(0.1, "Slot size ATR factor", group = "General", tooltip = "Bars are divided into slots (rows), that are decided by this factor of the ATR (14 bars)")
_bigRangeAtrMultiplier = input.float(0.5, "Big range ATR multiplier", group = "General", tooltip = "A bar is considered having a big range if it overgrows ATR (14 bars) times this factor")
_calculateBigRange = input.bool(true, "Calculate big range", group = "General", tooltip = "Takes time to calculate, disable to make the script faster")
_volumeMovingAverageLength = input.int(20, "Volume SMA length", group = "General", tooltip = "Used for defining if a bar is concidered having 'big volume' (when the volume is higher than the SMA it's concidered having 'big volume')")
_calculateBigVolume = input.bool(true, "Calculate big volume", group = "General", tooltip = "Takes time to calculate, disable to make the script faster")

_superTrendBullPercent = input.float(80, "Supertrend bull %", group = "Trend detection", tooltip = "The percentage of bars that must have a bull super trend to be in a bullish trend")
_showTrend = input.bool(true, "Show trend", group = "Trend detection", tooltip = "Shows the current lower timeframe trend (based on super trend), the percentage of bars in bull/bear trend, the bars in bull/bear trend ('/' the total number of bars) and the trend changes")

type TrendType
    string TrendString = ""
    string Progress = ""
type ChartType
    matrix<string> Matrix
    TrendType Trend
type LowerTimeFrameBarType
    float High = -1
    float Low = -1
    float Atr = -1
    int SuperTrend = -1

    bool BigRange = false
    bool BigVolume = false
    bool Green = false

    float Slot = -1

createSlots(slots, min, max, slotSize) =>
    i = 0
    while true
        slot = min + (slotSize * i)
        array.push(slots, slot)
        if slot >= max
            break
        i += 1
giveSlots(slots, bars) =>
    for [i, bar] in bars
        highLow2 = (bar.High + bar.Low) / 2
        slotIndex = array.binary_search_leftmost(slots, highLow2)
        slot = array.get(slots, slotIndex)
        bar.Slot := slot
getUsedSlots(bars) =>
    usedSlots = array.new<float>(), filledSlots = 0
    for bar in bars
        exists = array.includes(usedSlots, bar.Slot)
        if not exists
            array.push(usedSlots, bar.Slot)
            filledSlots += 1

    array.sort(usedSlots, order.descending)
    
    [usedSlots, filledSlots]
getChartMatrix(bars, barsSize, usedSlots, filledSlots) =>
    greenBar = "┼", greenBarWick = "│", redBar = "╋", redBarWick = "┃", greenBarBigVolume = "╪", greenBarBigVolumeTail = "│", redBarBigVolume = "╬", redBarBigVolumeTail = "║", noRange = "─", noRangeBigVolume = "═"

    matrixRows = filledSlots + 2, matrixColumns = barsSize + 1, m = matrix.new<string>(matrixRows, matrixColumns, "⠀")
    for [row, usedSlot] in usedSlots
        for [column, rowBar] in bars
            if rowBar.Slot == usedSlot
                if rowBar.BigVolume
                    if rowBar.Green
                        if rowBar.Low == rowBar.High
                            matrix.set(m, row + 1, column, noRangeBigVolume)
                        else
                            matrix.set(m, row + 1, column, greenBarBigVolume)
                            if rowBar.BigRange
                                matrix.set(m, row, column, greenBarBigVolumeTail)
                    else
                        if rowBar.Low == rowBar.High
                            matrix.set(m, row + 1, column, noRangeBigVolume)
                        else
                            matrix.set(m, row + 1, column, redBarBigVolume)
                            if rowBar.BigRange
                                matrix.set(m, row + 2, column, redBarBigVolumeTail)
                else
                    if rowBar.Green
                        if rowBar.Low == rowBar.High
                            matrix.set(m, row + 1, column, noRange)
                        else
                            matrix.set(m, row + 1, column, greenBar)
                            if rowBar.BigRange
                                matrix.set(m, row, column, greenBarWick)
                    else
                        if rowBar.Low == rowBar.High
                            matrix.set(m, row + 1, column, noRange)
                        else
                            matrix.set(m, row + 1, column, redBar)
                            if rowBar.BigRange
                                matrix.set(m, row + 2, column, redBarWick)

    [m, matrixRows, matrixColumns]
getChartFromMatrix(m, matrixRows) =>
    chart = ""
    for i = 0 to matrixRows - 1
        matrixRow = matrix.row(m, i)
        rowString = array.join(matrixRow, "")
        if str.length(str.replace_all(rowString, "⠀", "")) == 0
            continue  // skip empty row
        if str.length(chart) == 0
            chart += str.format("{0}", rowString)
        else
            chart += str.format("\n{0}", rowString)
    
    chart
// taken from KivancOzbilgic script SuperTrend (https://www.tradingview.com/script/r6dAP7yi)
superTrend(atrLength, source, atrFactor) =>
    atr = ta.atr(atrLength)
    up = source - (atrFactor * atr)
    up1 = nz(up[1], up)
    up := close[1] > up1 ? math.max(up, up1) : up
    dn = source + (atrFactor * atr)
    dn1 = nz(dn[1], dn)
    dn := close[1] < dn1 ? math.min(dn, dn1) : dn
    trend = 1
    trend := nz(trend[1], trend)
    trend := trend == 0 and close > dn1 ? 1 : trend == 1 and close < up1 ? 0 : trend

    trend
getSuperTrendString(superTrends, barsSize) =>
    string superTrendString = na

    for i = 0 to barsSize - 1
        superTrend = array.get(superTrends, i)
        currentSuperTrend = superTrend == 1 ? "📈" : "📉"
        if i > 0
            previousSuperTrend = array.get(superTrends, i - 1)
            if previousSuperTrend != superTrend
                superTrendString += currentSuperTrend
        else if i == 0
            superTrendString += currentSuperTrend

    superTrendString
getTrend(superTrends, bars) =>
    trend = TrendType.new()

    trends = array.sum(superTrends)
    superTrendString = getSuperTrendString(superTrends, bars)

    supertrendMinimumBull = bars * (_superTrendBullPercent * 0.01)
    if trends >= supertrendMinimumBull
        trend.TrendString := "Bull"
        trend.Progress :=  str.format("{0,number,###.#}% ({1}/{2}) {3}", (trends / bars) * 100, trends, bars, superTrendString)
    else
        trend.TrendString := "Bear"
        trend.Progress := str.format("{0,number,###.#}% ({1}/{2}) {3}", ((bars - trends) / bars) * 100, bars - trends, bars, superTrendString)

    trend
getLowerTimeFrameBar() =>
    atr = ta.atr(14)
    superTrend = superTrend(10, hl2, 3)
    volumeMovingAverage = ta.sma(volume, _volumeMovingAverageLength)

    bigRange = _calculateBigRange ? ((high - low) / 2) > (atr * _bigRangeAtrMultiplier) : false
    bigVolume = _calculateBigVolume ? volume > volumeMovingAverage : false
    Green = close >= open ? true : false
    bar = LowerTimeFrameBarType.new(high, low, atr, superTrend, bigRange, bigVolume, Green)

    bar
getLowerTimeFrameChart(lowerTimeFrameBars, lowerTimeFrameBarsSize) =>
    lowerTimeFramechart = ChartType.new()
    slotSize = 0., slots = array.new<float>(), superTrends = array.new<int>()
    for [i, bar] in lowerTimeFrameBars
        if i == lowerTimeFrameBarsSize - 1
            slotSize := bar.Atr * _slotSizeAtrFactor // get last atr for slot size

        if _showTrend
            array.push(superTrends, bar.SuperTrend)

    bars = array.size(lowerTimeFrameBars)

    createSlots(slots, low, high, slotSize)
    giveSlots(slots, lowerTimeFrameBars)

    [usedSlots, filledSlots] = getUsedSlots(lowerTimeFrameBars)
    [m, matrixRows, matrixColumns] = getChartMatrix(lowerTimeFrameBars, bars, usedSlots, filledSlots)
    lowerTimeFramechart.Matrix := m
    
    if _showTrend
        lowerTimeFramechart.Trend := getTrend(superTrends, bars)

    lowerTimeFramechart
drawChart(lowerTimeFramechart) =>
    matrixRows = matrix.rows(lowerTimeFramechart.Matrix), matrixColumns = matrix.columns(lowerTimeFramechart.Matrix)
    chart = getChartFromMatrix(lowerTimeFramechart.Matrix, matrixRows)
    var lightTheme = color.r(chart.bg_color) == 255
    var labelColor = lightTheme ? color.new(color.black, 90) : color.new(color.white, 90)
    var labelTextColor = lightTheme ? color.black : color.white
    var l = label.new(bar_index, 0, "", style = label.style_label_lower_left, color = labelColor, textcolor = labelTextColor, text_font_family = font.family_monospace)
    txt = _showTrend ? str.format("{0}\n{1} ({2})", chart, lowerTimeFramechart.Trend.TrendString, lowerTimeFramechart.Trend.Progress) : chart
    label.set_text(l,txt)
    label.set_x(l, bar_index)
    label.set_y(l, high)
alertIfTrendChange(trend) =>
    var string originalTrend = na

    if not na(originalTrend)
        if trend != originalTrend
            alert(str.format("Lower time frame trend change (from {0} to {1})", str.lower(originalTrend), str.lower(trend)), alert.freq_once_per_bar_close)
            originalTrend := trend

    if na(originalTrend)
        originalTrend := trend

lowerTimeFrameBars = request.security_lower_tf(syminfo.tickerid, _timeFrame, getLowerTimeFrameBar())
lowerTimeFrameBarsSize = array.size(lowerTimeFrameBars)

if barstate.islast
  and lowerTimeFrameBarsSize > 0
    lowerTimeFrameChart = getLowerTimeFrameChart(lowerTimeFrameBars, lowerTimeFrameBarsSize)
    if lowerTimeFrameBarsSize > 1 // only draw chart if more than one bar is available
        drawChart(lowerTimeFrameChart)
    if _showTrend
        alertIfTrendChange(lowerTimeFrameChart.Trend.TrendString)