Relative Performance Table
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © wheeelman

// This script takes the Performance Indicator that was first published by @BeeHolder) and compares the performance of the chart
// ticker to the performance of a selected index/ticker. 

//@version=5
indicator("Relative Performance", overlay=true)


// === USER INPUTS PERFORMANCE TABLE ===
//Table Structure/format
//i_transpose = input(true, title="Transpose Table ", group="Table Settings")
//i_tablecolumns = input.int(15, "Number of columns", group = "Table Settings")
i_cell_width = input.float(0, title="Cell Width", group="Perfomance Table Settings", tooltip="Adjust Cell Width (0 will auto adjust width)")
i_border_width = input.int(2, title="Border Width", group="Perfomance Table Settings", tooltip="Adjust Cell Border Size")
i_text_size = input.string(title="Text Size", options=["Large", "Normal", "Small"], defval="Normal",group="Perfomance Table Settings")
i_posColor = input(color(#089981), title='Positive Color',group="Perfomance Table Settings")
i_negColor = input(color(#f23645), title='Negative Color',group="Perfomance Table Settings")
i_ticColor = input(color.new(#999999, 0), title='Ticker Color',group="Perfomance Table Settings")
i_sym_compare = input.symbol(title='Index/Ticker Compare', defval='SP:SPX',group="Perfomance Table Settings")
i_tablePositionY = input.string('bottom', title='Y-Position', options=['top', 'middle', 'bottom'], group="Perfomance Table Settings", inline="pos")
i_tablePositionX = input.string('right', title='X-Position', options=['left', 'center', 'right'], group="Perfomance Table Settings", inline="pos")


//Timeframe Inputs Performance Table
tooltipText = "Custom timeframes can be added via the chart's Timeframe dropdown. Until added, custom timeframes show up as 'Chart' instead."
showTF1 = input.bool(true,   "", inline = "TF1", group = "Performance: Absolute timeframes")
tf1 = input.timeframe("1W",  "", inline = "TF1", group = "Performance: Absolute timeframes", tooltip = "Select any timeframe > D, intraday is not compatible")
showTF2 = input.bool(true,   "", inline = "TF2", group = "Performance: Absolute timeframes")
tf2 = input.timeframe("1M",  "", inline = "TF2", group = "Performance: Absolute timeframes")
showTF3 = input.bool(true,   "", inline = "TF3", group = "Performance: Absolute timeframes")
tf3 = input.timeframe("3M",  "", inline = "TF3", group = "Performance: Absolute timeframes")
showTF4 = input.bool(true,   "", inline = "TF4", group = "Performance: Absolute timeframes")
tf4 = input.timeframe("6M",  "", inline = "TF4", group = "Performance: Absolute timeframes")
showTF5 = input.bool(true,   "", inline = "TF5", group = "Performance: Absolute timeframes")
tf5 = input.timeframe("9M",  "", inline = "TF5", group = "Performance: Absolute timeframes")
showTF6 = input.bool(true,   "", inline = "TF6", group = "Performance: Absolute timeframes")
tf6 = input.timeframe("52W", "", inline = "TF6", group = "Performance: Absolute timeframes")
showTF7 = input.bool(true,   "", inline = "TF7", group = "Performance: To-Date timeframes")
tf7 = input.timeframe("1W",  "", inline = "TF7", group = "Performance: To-Date timeframes")
showTF8 = input.bool(true,   "", inline = "TF8", group = "Performance: To-Date timeframes")
tf8 = input.timeframe("1M",  "", inline = "TF8", group = "Performance: To-Date timeframes")
showTF9 = input.bool(true,   "", inline = "TF9", group = "Performance: To-Date timeframes")
tf9 = input.timeframe("12M", "", inline = "TF9", group = "Performance: To-Date timeframes")


//===MISC Variables
lightTransp = 90
avgTransp = 80
heavyTransp = 70

text_size_output(type) =>
    type == 'Large' ? size.large : type == 'Normal' ? size.normal : type == 'Small' ? size.small : na
text_size_ = text_size_output(i_text_size)


//==========Performance Array
var showTFArray = array.from(showTF1, showTF2, showTF3, showTF4, showTF5, showTF6, showTF7, showTF8, showTF9,showTF1, showTF2, showTF3, showTF4, showTF5, showTF6, showTF7, showTF8, showTF9)
var timeframesArray = array.from(tf1, tf2, tf3, tf4, tf5, tf6, tf7, tf8, tf9,tf1, tf2, tf3, tf4, tf5, tf6, tf7, tf8, tf9)

if barstate.isfirst
    for i = 0 to array.size(showTFArray) - 1
        tf = array.get(timeframesArray, i)
        if not na(str.tonumber(tf))
            runtime.error(str.format("Intraday timeframes (`{0}` in Timeframe #{1}) are not compatible with this indicator.", tf, i + 1))



//====Timeframe Logic====================
timeframeInMS(timeframe) =>
    numTf = str.tonumber(timeframe)
    tfLength = str.length(timeframe)
    
    float tfNumber = na
    string tfLetter = na
    
    if numTf
        tfNumber := numTf
    else
        tfLetter := str.substring(timeframe, tfLength - 1, tfLength)
        tfNumber := str.tonumber(str.substring(timeframe, 0, tfLength - 1))
    
    tfBaseInMS = switch tfLetter
        "S" => 60 * 1000
        "D" => 24 * 60 * 60 * 1000
        "W" => 7 * 24 * 60 * 60 * 1000
        "M" => 30 * 24 * 60 * 60 * 1000
        => 60 * 60 * 1000
    
    int(tfBaseInMS * tfNumber)

fastSearch(source, target) =>
    max_bars_back(source, 366)
    left  = 0
    right = math.min(bar_index, 366)
    mid = 0
    if source < target
        0
    else
        for i = 0 to 9
            mid := math.ceil(math.avg(left, right))
            if left == right
                break
            else if source[mid] < target
                right := mid
                continue
            else if source[mid] > target
                left := mid
                continue
            else
                break
        mid

rateOfreturn(v1, v2) => (v1 - v2) * 100 / math.abs(v2)

recentclose(sec) =>
    request.security(sec, '1D', close)

performance(timeframe) =>
    sourceTime = barstate.isconfirmed ? time_close : timenow
    barsBack = fastSearch(time, sourceTime - timeframeInMS(timeframe))
    performance = request.security(syminfo.tickerid, "1D", rateOfreturn(close, close[barsBack]), lookahead=barmerge.lookahead_on)
    performance

tf7Sec = request.security(syminfo.tickerid, tf7, close[1], lookahead=barmerge.lookahead_on)
tf8Sec = request.security(syminfo.tickerid, tf8, close[1], lookahead=barmerge.lookahead_on)
tf9Sec = request.security(syminfo.tickerid, tf9, close[1], lookahead=barmerge.lookahead_on)

rel_performance(timeframe) =>
    rel_sourceTime = barstate.isconfirmed ? time_close : timenow
    rel_barsBack = fastSearch(time, rel_sourceTime - timeframeInMS(timeframe))
    rel_performance = request.security(i_sym_compare, "1D", rateOfreturn(close, close[rel_barsBack]), lookahead=barmerge.lookahead_on)
    rel_performance    

rel_tf7Sec = request.security(i_sym_compare, tf7, close[1], lookahead=barmerge.lookahead_on)
rel_tf8Sec = request.security(i_sym_compare, tf8, close[1], lookahead=barmerge.lookahead_on)
rel_tf9Sec = request.security(i_sym_compare, tf9, close[1], lookahead=barmerge.lookahead_on)

//=======END PERFORMANCE LOGIC

//52 Week High 

ftwh()=>
    fth= ta.highest(high[1],52) 
    fth
fth = request.security(syminfo.tickerid, 'W', ftwh(),lookahead=barmerge.lookahead_on)
rel_fth = request.security(i_sym_compare, 'W', ftwh(),lookahead=barmerge.lookahead_on)

pricefth = (((recentclose(syminfo.tickerid)/fth)-1)*100 )       // Price Relative to 52 week high >75
rel_pricefth = (((recentclose(i_sym_compare)/rel_fth)-1)*100 )       // Price Relative to 52 week high >75

f_fillCell_fth(_table, _column, _row, _value) =>
    _c_color = _value >= -25 ? i_posColor : i_negColor
    _transp = heavyTransp
    _cellText = str.tostring(_value, '0.00') + '%\n' + '% off 52H'
    table.cell(_table, _column, _row, _cellText, bgcolor=color.new(_c_color, _transp), text_color=_c_color, width=i_cell_width,text_size = text_size_)

//PERFORMANCE TABLE 

//How many columns are filled divided by number of rows
filled_columns = 0
for i = 0 to (array.size(showTFArray)/2)-1      
    if array.get(showTFArray, i)
        filled_columns += 1


var table perfTable = table.new(i_tablePositionY + '_' + i_tablePositionX, filled_columns+2, 2, border_width = i_border_width)

f_fillCellSec(_table, _column, _row, _value) =>
    table.cell(_table, _column, _row, _value, bgcolor=color.new(i_ticColor, avgTransp), text_color=i_ticColor, width=i_cell_width, text_size = text_size_)  

fillCell(_table, column, row, value, timeframe) =>
    _color = value >= 0 ? i_posColor : i_negColor
    transp = math.abs(value) > 10 ? heavyTransp : math.abs(value) > 5 ? avgTransp : lightTransp
    _cellText = str.tostring(value, "0.00") + "%\n" + timeframe
    table.cell(_table, column, row, _cellText, bgcolor = color.new(_color, transp), text_color = _color, width = i_cell_width, text_size = text_size_)


formatTF(tf) =>
    formattedTF = switch tf
        "52W" => "1Y"
        "12M" => "1Y"
        => tf

perftableArray = array.from(performance(tf1), performance(tf2), performance(tf3), 
 performance(tf4), performance(tf5), performance(tf6), rateOfreturn(close, tf7Sec), 
 rateOfreturn(close, tf8Sec),rateOfreturn(close, tf9Sec)
 ,rel_performance(tf1), rel_performance(tf2), rel_performance(tf3), 
 rel_performance(tf4), rel_performance(tf5), rel_performance(tf6), rateOfreturn(recentclose(i_sym_compare), rel_tf7Sec), 
 rateOfreturn(recentclose(i_sym_compare), rel_tf8Sec),rateOfreturn(recentclose(i_sym_compare), rel_tf9Sec))


perflabelArray = array.from(formatTF(tf1), formatTF(tf2), formatTF(tf3), formatTF(tf4), 
 formatTF(tf5), formatTF(tf6), formatTF(tf7) + "TD", formatTF(tf8) + "TD", formatTF(tf9) + "TD",
 formatTF(tf1), formatTF(tf2), formatTF(tf3), formatTF(tf4), 
 formatTF(tf5), formatTF(tf6), formatTF(tf7) + "TD", formatTF(tf8) + "TD", formatTF(tf9) + "TD")


if barstate.islast
    f_fillCellSec(perfTable, 0, 0, syminfo.ticker)
    f_fillCellSec(perfTable, 0, 1, i_sym_compare)
    f_fillCell_fth(perfTable,1, 0, pricefth)
    f_fillCell_fth(perfTable,1, 1, rel_pricefth)
    filled = 0
    for i = 0 to array.size(showTFArray) - 1
        if array.get(showTFArray, i)
            fillCell(perfTable, (filled % filled_columns)+2, filled / filled_columns, array.get(perftableArray, i), array.get(perflabelArray, i))
            filled += 1    