Top/Bottom Detector
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © lirre8
//
// Don't hesitate to send me a message if you have questions, noticed any bugs or have suggestions on improvements.

//@version=5
indicator(title='Top/Bottom Detector', shorttitle='TBD', overlay=true, precision=0)

// SETTINGS
BullCandlesBack = input(defval=4, title='Candles back', group='Bull count criteria', tooltip='Number of candles back in history that the current candle is compared to')
BullSourceCurrent = input(defval=close, title='Current candle source', group='Bull count criteria', tooltip='Price of the current candle')
BullSourceHistory = input(defval=close, title='History candle source', group='Bull count criteria', tooltip='Price of the historic candle that the current candle is compared with')
BullMaxCount = input(defval=9, title='Max count', group='Bull count criteria', tooltip='The count that will mark a top')

BearCandlesBack = input(defval=4, title='Candles back', group='Bear count criteria', tooltip='Number of candles back in history that the current candle is compared to')
BearSourceCurrent = input(defval=close, title='Current candle source', group='Bear count criteria', tooltip='Price of the current candle')
BearSourceHistory = input(defval=close, title='History candle source', group='Bear count criteria', tooltip='Price of the historic candle that the current candle is compared with')
BearMaxCount = input(defval=9, title='Max count', group='Bear count criteria', tooltip='The count that will mark a bottom')

ShowMtt = input(defval=true, title='Show multi timeframe table', group='Multi Timeframe Table')
FirstTimeframe = input.timeframe(title="", defval="30", inline="timeframes1", group="Multi Timeframe Table", display=display.none)
SecondTimeframe = input.timeframe(title="", defval="60", inline="timeframes1", group="Multi Timeframe Table", display=display.none)
ThirdTimeframe = input.timeframe(title="", defval="240", inline="timeframes1", group="Multi Timeframe Table", display=display.none)
FourthTimeframe = input.timeframe(title="", defval="1D", inline="timeframes2", group="Multi Timeframe Table", display=display.none)
FifthTimeframe = input.timeframe(title="", defval="1W", inline="timeframes2", group="Multi Timeframe Table", display=display.none)
SixthTimeframe = input.timeframe(title="", defval="1M", inline="timeframes2", group="Multi Timeframe Table", display=display.none)
TableColumns = input.int(title="Columns", defval=3, minval=1, maxval=3, inline="columnsrows", group="Multi Timeframe Table", display=display.none)
TableRows = input.int(title="Rows", defval=2, minval=1, maxval=2, inline="columnsrows", group="Multi Timeframe Table", display=display.none)
TablePosition = input.string(title="Position", defval=position.top_right, group="Multi Timeframe Table", display=display.none, options=[position.top_left, position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, position.bottom_left, position.bottom_center, position.bottom_right])
TableBorderWidth = input.int(title="Border width", defval=3, group="Multi Timeframe Table", display=display.none)

ShowCount = input(defval=false, title='Show count numbers', group='Misc')
Transp = input(defval=0, title='Transparency', group='Misc', display=display.none)

// VALUES
bearCount() =>
    _bearCount = 0
    _bearCount := BearSourceCurrent < BearSourceHistory[BearCandlesBack] ? _bearCount[1] + (_bearCount[1] == BearMaxCount ? 0 : 1) : 0

bullCount() =>
    _bullCount = 0
    _bullCount := BullSourceCurrent > BullSourceHistory[BullCandlesBack] ? _bullCount[1] + (_bullCount[1] == BullMaxCount ? 0 : 1) : 0

bullCount = bullCount()
bearCount = bearCount()

// PLOT
plotchar(ShowCount and bullCount == 1 and BullMaxCount > 1, char='1', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 2 and BullMaxCount > 2, char='2', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 3 and BullMaxCount > 3, char='3', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 4 and BullMaxCount > 4, char='4', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 5 and BullMaxCount > 5, char='5', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 6 and BullMaxCount > 6, char='6', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 7 and BullMaxCount > 7, char='7', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 8 and BullMaxCount > 8, char='8', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bullCount == 9 and BullMaxCount > 9, char='9', color=color.new(color.green, Transp), location=location.abovebar, editable=false)
plotchar(ShowCount and bearCount == 1 and BearMaxCount > 1, char='1', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 2 and BearMaxCount > 2, char='2', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 3 and BearMaxCount > 3, char='3', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 4 and BearMaxCount > 4, char='4', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 5 and BearMaxCount > 5, char='5', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 6 and BearMaxCount > 6, char='6', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 7 and BearMaxCount > 7, char='7', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 8 and BearMaxCount > 8, char='8', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotchar(ShowCount and bearCount == 9 and BearMaxCount > 9, char='9', color=color.new(color.red, Transp), location=location.belowbar, editable=false)
plotarrow(bullCount >= BullMaxCount ? -1 : bearCount >= BearMaxCount ? 1 : na, colorup=color.green, colordown=color.red, maxheight=15, editable=false)

// ALERTS
if bullCount >= BullMaxCount
    alert("Top detected")
if bearCount >= BearMaxCount
    alert("Bottom detected")

alertcondition(bullCount >= BullMaxCount, "Top", "TBD top detected")
alertcondition(bearCount >= BearMaxCount, "Bottom", "TBD bottom detected")

// MULTI TIMEFRAME TABLE
f_fillCell(_table, _column, _row, _value, _timeframe) =>
    _c_color = na(_value) ? color.new(color.gray, 50) : _value == 0 ? color.gray : _value > 0 ? color.rgb(38, 166, 154) : color.rgb(240, 83, 80)
    _valueText = na(_value) ? "N/A" : _value == 0 ? '-' : _value == BullMaxCount ? "Top" : _value == -BearMaxCount ? "Bottom" : str.tostring(math.abs(_value))
    _cellText = _timeframe + "\n" + _valueText
    table.cell(_table, _column, _row, _cellText, bgcolor = color.new(_c_color, 70), text_color = _c_color, width = 6)

f_isLowerTimeframe(_timeframe) =>
    _timeframeNbr = str.tonumber(_timeframe)
    not na(_timeframeNbr) and _timeframe != "1D" ? (timeframe.isdwm ? true : timeframe.multiplier > _timeframeNbr)
         : _timeframe == "1D" ? timeframe.isweekly or timeframe.ismonthly
         : _timeframe == "1W" ? timeframe.ismonthly
         : false

mttCandleCount() =>
    _bullCount = bullCount()
    _bearCount = bearCount()
    _bullCount > 0 ?_bullCount : -_bearCount

f_setupCountForTimeframe(_timeframe) =>
    f_isLowerTimeframe(_timeframe) ? na : request.security(syminfo.tickerid, _timeframe, mttCandleCount())

f_labelForTimeframe(_timeframe) =>
    _timeframeNbr = str.tonumber(_timeframe)
    na(_timeframeNbr) or _timeframe == "1D" ? _timeframe
                      : _timeframeNbr % 60 == 0 ? str.tostring(_timeframeNbr/60) + "h" : _timeframe + "m"

if ShowMtt
    var table mttTable = table.new(TablePosition, TableColumns, TableRows, border_width=TableBorderWidth)
    f_fillCell(mttTable, 0, 0, f_setupCountForTimeframe(FirstTimeframe), f_labelForTimeframe(FirstTimeframe))
    if TableColumns > 1
        f_fillCell(mttTable, 1, 0, f_setupCountForTimeframe(SecondTimeframe), f_labelForTimeframe(SecondTimeframe))
    if TableColumns > 2
        f_fillCell(mttTable, 2, 0, f_setupCountForTimeframe(ThirdTimeframe), f_labelForTimeframe(ThirdTimeframe))
    if TableRows > 1
        f_fillCell(mttTable, 0, 1, f_setupCountForTimeframe(FourthTimeframe), f_labelForTimeframe(FourthTimeframe))
        if TableColumns > 1
            f_fillCell(mttTable, 1, 1, f_setupCountForTimeframe(FifthTimeframe), f_labelForTimeframe(FifthTimeframe))
        if TableColumns > 2
            f_fillCell(mttTable, 2, 1, f_setupCountForTimeframe(SixthTimeframe), f_labelForTimeframe(SixthTimeframe))