Max drawdown days
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HeWhoMustNotBeNamed

//   __    __            __       __  __                  __       __                        __      __    __              __      _______             __    __                                          __ 
//  /  |  /  |          /  |  _  /  |/  |                /  \     /  |                      /  |    /  \  /  |            /  |    /       \           /  \  /  |                                        /  |
//  $$ |  $$ |  ______  $$ | / \ $$ |$$ |____    ______  $$  \   /$$ | __    __   _______  _$$ |_   $$  \ $$ |  ______   _$$ |_   $$$$$$$  |  ______  $$  \ $$ |  ______   _____  ____    ______    ____$$ |
//  $$ |__$$ | /      \ $$ |/$  \$$ |$$      \  /      \ $$$  \ /$$$ |/  |  /  | /       |/ $$   |  $$$  \$$ | /      \ / $$   |  $$ |__$$ | /      \ $$$  \$$ | /      \ /     \/    \  /      \  /    $$ |
//  $$    $$ |/$$$$$$  |$$ /$$$  $$ |$$$$$$$  |/$$$$$$  |$$$$  /$$$$ |$$ |  $$ |/$$$$$$$/ $$$$$$/   $$$$  $$ |/$$$$$$  |$$$$$$/   $$    $$< /$$$$$$  |$$$$  $$ | $$$$$$  |$$$$$$ $$$$  |/$$$$$$  |/$$$$$$$ |
//  $$$$$$$$ |$$    $$ |$$ $$/$$ $$ |$$ |  $$ |$$ |  $$ |$$ $$ $$/$$ |$$ |  $$ |$$      \   $$ | __ $$ $$ $$ |$$ |  $$ |  $$ | __ $$$$$$$  |$$    $$ |$$ $$ $$ | /    $$ |$$ | $$ | $$ |$$    $$ |$$ |  $$ |
//  $$ |  $$ |$$$$$$$$/ $$$$/  $$$$ |$$ |  $$ |$$ \__$$ |$$ |$$$/ $$ |$$ \__$$ | $$$$$$  |  $$ |/  |$$ |$$$$ |$$ \__$$ |  $$ |/  |$$ |__$$ |$$$$$$$$/ $$ |$$$$ |/$$$$$$$ |$$ | $$ | $$ |$$$$$$$$/ $$ \__$$ |
//  $$ |  $$ |$$       |$$$/    $$$ |$$ |  $$ |$$    $$/ $$ | $/  $$ |$$    $$/ /     $$/   $$  $$/ $$ | $$$ |$$    $$/   $$  $$/ $$    $$/ $$       |$$ | $$$ |$$    $$ |$$ | $$ | $$ |$$       |$$    $$ |
//  $$/   $$/  $$$$$$$/ $$/      $$/ $$/   $$/  $$$$$$/  $$/      $$/  $$$$$$/  $$$$$$$/     $$$$/  $$/   $$/  $$$$$$/     $$$$/  $$$$$$$/   $$$$$$$/ $$/   $$/  $$$$$$$/ $$/  $$/  $$/  $$$$$$$/  $$$$$$$/ 
//                                                                                                                                                                                                          
//                                                                                                                                                                                                          
//
//@version=5
indicator("Max drawdown days", overlay=true)

i_startTime = input.time(defval=timestamp('01 Jan 2010 00:00 +0000'), title='Start Time', group='Backtest Window')
i_endTime = input.time(defval=timestamp('01 Jan 2099 00:00 +0000'), title='End Time', group='Backtest Window')
inDateRange = time >= i_startTime and time <= i_endTime

i_tablePosition = input.string(defval=position.top_right, title='Table Position', 
                         options=[position.bottom_right, position.bottom_center, position.bottom_left, position.middle_right, 
                         position.middle_center, position.middle_left, position.top_right, position.top_center, position.top_left], group='Display')
i_text_size = input.string(size.normal, title='Text Size', options=[size.tiny, size.small, size.normal, size.large, size.huge], group='Display')
i_headerColor = color.maroon
i_neutralColor = #FFEEBB

get_ath_data()=>
    var recoveryDaysArray = array.new_int()
    var dipDaysArray = array.new_int()
    
    var dateStart = str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
    dateEnd = str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
    var ath = high
    var daysFromAth = 0
    var localLow = low
    var daysFromAthToLocalLow = 0
    
    var maxDaysFromAthToLowcalLow = 0
    var maxDaysForRecovery = 0
    ath := math.max(ath, high)
    var athDay = ''
    var localLowDay = ''
    lastRecoveryDay = ''
    var maxDrawdownAthDay = ''
    var maxDrawdownDipDay = ''
    
    var maxRecoveryAthDay = ''
    var maxRecoveryRecoveryDay = ''
    var stillInDip = false
    var stillInRecovery = false
    if ath == high
        if(daysFromAthToLocalLow >=  nz(array.median(dipDaysArray), 1) and daysFromAth >= nz(array.median(recoveryDaysArray), 1))
            array.push(recoveryDaysArray, daysFromAth)
            array.push(dipDaysArray, daysFromAthToLocalLow)
        daysFromAth := 0
        localLow := low
        daysFromAthToLocalLow := 0
        athDay := str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
        stillInRecovery := false
    else
        daysFromAth += 1
        localLow := math.min(localLow, low)
        lastRecoveryDay := str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
        if(low == localLow)
            daysFromAthToLocalLow := daysFromAth
            localLowDay := str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
        else
            stillInDip := false
    
    if(maxDaysFromAthToLowcalLow < daysFromAthToLocalLow)
        maxDaysFromAthToLowcalLow := daysFromAthToLocalLow
        maxDrawdownAthDay := athDay
        maxDrawdownDipDay := localLowDay
        stillInDip := true
        
    if(maxDaysForRecovery < daysFromAth)
        maxDaysForRecovery := daysFromAth
        maxRecoveryAthDay := athDay
        maxRecoveryRecoveryDay := lastRecoveryDay
        stillInRecovery := true
        
    localLowData = str.tostring(maxDaysFromAthToLowcalLow) + ' ('+maxDrawdownAthDay+' - '+maxDrawdownDipDay+')'
    localRecoveryData = str.tostring(maxDaysForRecovery) + ' ('+maxRecoveryAthDay+' - '+maxRecoveryRecoveryDay+')'
    
    currentLocalLowData = str.tostring(daysFromAthToLocalLow) + ' ('+athDay+' - '+localLowDay+')'
    currentLocalRecoveryData = str.tostring(daysFromAth) + ' ('+athDay+' - )'

    medianRecoveryBars = array.median(recoveryDaysArray)
    medianDipBars = array.median(dipDaysArray)
    
    timeframe = dateStart + ' - ' + dateEnd
    currentTimeframe = athDay+' - '
    currentlyInDip = daysFromAth == daysFromAthToLocalLow and daysFromAth > medianDipBars
    currentlyInRecovery = daysFromAth > medianRecoveryBars

    [localLowData, localRecoveryData, timeframe, stillInDip, stillInRecovery, 
             currentLocalLowData, currentLocalRecoveryData, currentTimeframe, currentlyInDip, currentlyInRecovery,
             medianDipBars, medianRecoveryBars]
    
[localLowData, localRecoveryData, timeframe, stillInDip, stillInRecovery,
     currentLocalLowData, currentLocalRecoveryData, currentTimeframe, currentlyInDip, currentlyInRecovery,
     medianDipBars, medianRecoveryBars] = get_ath_data()

var table stats = table.new(position=i_tablePosition, columns=6, rows=4, border_width=1)
table.cell(table_id=stats, column=1, row=0, text='All Time', bgcolor=i_headerColor, text_color=color.white, text_size=i_text_size)
table.cell(table_id=stats, column=0, row=2, text='Max drawdown bars', bgcolor=color.maroon, text_color=color.white, text_size=i_text_size)
table.cell(table_id=stats, column=0, row=3, text='Max recovery bars', bgcolor=color.maroon, text_color=color.white, text_size=i_text_size)
table.cell(table_id=stats, column=1, row=1, text=timeframe, bgcolor=color.blue, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=1, row=2, text=localLowData, bgcolor=stillInDip ? color.red : i_neutralColor, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=1, row=3, text=localRecoveryData, bgcolor=stillInRecovery? color.red:i_neutralColor, text_color=color.black, text_size=i_text_size)

table.cell(table_id=stats, column=2, row=1, text='Median', bgcolor=color.blue, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=2, row=2, text=str.tostring(medianDipBars), bgcolor=i_neutralColor, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=2, row=3, text=str.tostring(medianRecoveryBars),bgcolor=i_neutralColor, text_color=color.black, text_size=i_text_size)
if(inDateRange)
    [_localLowData, _localRecoveryData, _timeframe, _stillInDip, _stillInRecovery,_,_,_,_,_,_medianDipBars,_medianRecoveryBars] = get_ath_data()
    table.cell(table_id=stats, column=3, row=0, text='Window', bgcolor=i_headerColor, text_color=color.white, text_size=i_text_size)
    table.cell(table_id=stats, column=3, row=1, text=_timeframe, bgcolor=color.blue, text_color=color.black, text_size=i_text_size)
    table.cell(table_id=stats, column=3, row=2, text=_localLowData, bgcolor=_stillInDip ? color.red : i_neutralColor, text_color=color.black, text_size=i_text_size)
    table.cell(table_id=stats, column=3, row=3, text=_localRecoveryData,  bgcolor=_stillInRecovery? color.red:i_neutralColor, text_color=color.black, text_size=i_text_size)
    table.cell(table_id=stats, column=4, row=1, text='Median', bgcolor=color.blue, text_color=color.black, text_size=i_text_size)
    table.cell(table_id=stats, column=4, row=2, text=str.tostring(_medianDipBars), bgcolor=i_neutralColor, text_color=color.black, text_size=i_text_size)
    table.cell(table_id=stats, column=4, row=3, text=str.tostring(_medianRecoveryBars),bgcolor=i_neutralColor, text_color=color.black, text_size=i_text_size)
    
table.cell(table_id=stats, column=5, row=0, text='Current', bgcolor=i_headerColor, text_color=color.white, text_size=i_text_size)
table.cell(table_id=stats, column=5, row=1, text=currentTimeframe, bgcolor=color.blue, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=5, row=2, text=currentLocalLowData, bgcolor=currentlyInDip ? color.red : i_neutralColor, text_color=color.black, text_size=i_text_size)
table.cell(table_id=stats, column=5, row=3, text=currentLocalRecoveryData, bgcolor=currentlyInRecovery? color.red:i_neutralColor, text_color=color.black, text_size=i_text_size)