True Strength Indicator + Realtime Divergences
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tvenn

//@version=5
indicator("True Strength Indicator + Realtime Divergences", shorttitle="TSI+", overlay=false, max_bars_back = 1000, max_lines_count = 400, max_labels_count = 400, precision=3)

pulldatafromtimeframe = input.string("Chart", title="Select alternate timeframe in mins", options=["Chart", "1", "2", "3", "4", "5", "10", "15", "30", "45", "60", "120", "240"])

green   = color.new(#95BD5F, 30)
red     = color.new(#EA1889, 30)


haTicker = ticker.heikinashi(syminfo.tickerid)
[haO, haH, haL, haC] = request.security(haTicker, timeframe.period, [open, high, low, close])


//TSI settings
long    = input(title="Long Length", defval=6, group="TSI settings")
short   = input(title="Short Length", defval=13, group="TSI settings")
signal  = input(title="Signal Length",  defval=4, group="TSI settings")


//Divergence options
showlines = input(defval = true, title = "Show Divergence Lines", group="Divergence settings")
showlast = input(defval = true, title = "Show Only Last Divergence", group="Divergence settings")
dontconfirm = input(defval = true, title = "Don't Wait for Confirmation", group="Divergence settings")


//Bands
grp_Detail      = "Details"
centerline 		= input(true, title="Centerline", group=grp_Detail, inline="1", tooltip="To add a solid centerline that stays behind the oscillator, simply adjust the color of the 'Alt centerline' within the styles tab and disable this centerline")
showBands 		= input(false, title="Range bands", group=grp_Detail, inline="3", tooltip="The color and style of the range bands can be further customised in the 'Style' tab of this settings menu.")

//Band crossover dots
crossoverDotsOn = input(true, title="Crossover dots", inline="4", group=grp_Detail, tooltip="The size and color of the dots can be altered in the 'Style' tab of this settings menu.")
fadeOutDotsOn   = input(true, title="Fade out dots", inline="4", group=grp_Detail, tooltip="This will gradually fade out the historical crossover dots to minimise visual noise.")
crossoverLabel  = input(false, title="Crossover label", group=grp_Detail, tooltip="A green label will show if the TSI band is above the signal band, a red label will show if the TSI band is below the signal band.\n\nThe crossover can be used to help time trade entries.")
CloudOn         = input(title="TSI Cloud", defval=true, group=grp_Detail)
fadeOutOsc      = input(false, title="Fade out oscillator", group=grp_Detail, tooltip="Face out the oscillator leaving only the most recent periods prominent for a clearer chart.")
flipOsc         = input.bool(false, title="Flip Oscillator", group=grp_Detail, tooltip="This will flip the oscillator upside down. The purpose is for use with the flip chart feature of Tradingview (Alt+i), which does not also flip the oscillator. This may help those with a particular long/short bias to see the other side of things. Divergence lines will not be drawn.")

grp_BG          = "Background styles"
backgroundColor = input(color.new(color.white, 97), title="", group=grp_BG, inline="bg")
showBackground  = input(false, title="Background", group=grp_BG, inline="bg")
bandsDownColor  = input.color(color.new(red, 90), title="", group=grp_BG, inline="crossunder")
BackgroundSignalOn = input(false, title="TSI signal band crossunder background color", group=grp_BG, tooltip="This will colour the background according to whether the TSI band is above or below the signal band.", inline="crossunder")
subZeroColor  = input.color(color.new(red, 90), title="", group=grp_BG, inline="crossunder2")
BackgroundCenterSignalOn = input(false, title="TSI centerline crossunder background color", group=grp_BG, tooltip="This will colour the background according to whether the TSI line is above or below the centerline (0 level).\n\nThis can indicate bullishness above the centerline, and bearishness below the centerline.", inline="crossunder2")


centerlineupper = hline(1, color=color.new(#000000, 100), title="Alt centerline", linestyle=hline.style_solid, linewidth=1)
centerlinelower = hline(-1, color=color.new(#000000, 100), title="Alt centerline", linestyle=hline.style_solid, linewidth=1)
fill(centerlineupper, centerlinelower, color.new(color.orange, 100), title="Alt centerline", editable=1)

colorUpper 		= showBands ? color.new(color.gray, 30) : color.new(#777777, 100)
colorLower 		= showBands ? color.new(color.gray, 30) : color.new(#777777, 100)

band2 			= hline(60, color=colorUpper, title="Overbought", linestyle=hline.style_dotted, linewidth=1)
band1 			= hline(0, color=(centerline ? color.new(#777777, 0): na), title="Centerline", linestyle=hline.style_dotted, linewidth=1)
band0 			= hline(-60, color=colorLower, title="Oversold", linestyle=hline.style_dotted, linewidth=1)


convertToHA     = input(false, title="Enable Heikin Ashi mode on standard chart", group="Heikin Ashi mode", tooltip="This will use Heikin Ashi formula Open and Close values. The purpose is for use on standard chart types, as having this setting enabled on an actual Heikin Ashi chart will produce unrealistic Heikin Ahi average values.")
haCheckHigh     = convertToHA ? haH : high
haCheckLow      = convertToHA ? haL : low
haCheckClose    = convertToHA ? haC : close

//TSI

double_smooth(src, long, short) =>
	fist_smooth = ta.ema(src, long)
	ta.ema(fist_smooth, short)
pc = ta.change(haCheckClose)
double_smoothed_pc = double_smooth(pc, long, short)
double_smoothed_abs_pc = double_smooth(math.abs(pc), long, short)
tsi_val = 100 * (double_smoothed_pc / double_smoothed_abs_pc)
tsi_value_raw = request.security(syminfo.tickerid, pulldatafromtimeframe == "Chart" ? "" : pulldatafromtimeframe, tsi_val, barmerge.gaps_on)
tsi_value = (flipOsc ? tsi_value_raw*-1 : tsi_value_raw)
lagline=int(ta.ema(tsi_value, signal))

//Pivot settings
pp = input.int(defval = 12, title = "Pivot period", minval = 1, maxval = 50, group="Pivot point settings")
maxpp = input.int(defval = 5, title = "Maximum Pivot periods to check for divs", minval = 1, maxval = 100, group="Pivot point settings")
maxbars = input.int(defval = 100, title = "Maximum Bars to Check", minval = 1, maxval = 200, group="Pivot point settings")
source = "Close"

searchdiv = input.string(defval = "Regular/Hidden", title = "Divergence Type", options = ["Regular", "Hidden", "Regular/Hidden"], group="Divergence settings")

prd = pp


//Styles
grp_STYLE       = "Styles"
primary_color = input(green, title="TSI color", group=grp_STYLE)
secondary_color = input(red, title="Lagline color", group=grp_STYLE)
pos_reg_div_col = input(defval = green, title = "Bullish Regular Divergence", group=grp_STYLE)
neg_reg_div_col = input(defval = red, title = "Bearish Regular Divergence", group=grp_STYLE)
pos_hid_div_col = input(defval = green, title = "Bullish Hidden Divergence", group=grp_STYLE)
neg_hid_div_col = input(defval = red, title = "Bearish Hidden Divergence", group=grp_STYLE)
reg_div_l_style_ = input.string(defval = "Solid", title = "Regular Divergence Line Style", options = ["Solid", "Dashed", "Dotted"], group=grp_STYLE)
hid_div_l_style_ = input.string(defval = "Dotted", title = "Hdden Divergence Line Style", options = ["Solid", "Dashed", "Dotted"], group=grp_STYLE)
reg_div_l_width = input.int(defval = 2, title = "Regular Divergence Line Width", minval = 1, maxval = 2, group=grp_STYLE)
hid_div_l_width = input.int(defval = 2, title = "Hidden Divergence Line Width", minval = 1, maxval = 2, group=grp_STYLE)


distanceTransparency = (bar_index > (last_bar_index - 30) ? 10 : (bar_index > last_bar_index - 60 ? 20 : (bar_index > last_bar_index - 80 ? 30 : (bar_index > last_bar_index - 100 ? 40 : (bar_index > last_bar_index - 120 ? 50 : (bar_index > last_bar_index - 140 ? 60 : (bar_index > last_bar_index - 160 ? 70 : 80)))))))
dotDistanceTransparency = (bar_index > (last_bar_index - 10) ? 10 : (bar_index > last_bar_index - 20 ? 20 : (bar_index > last_bar_index - 30 ? 30 : (bar_index > last_bar_index - 40 ? 40 : (bar_index > last_bar_index - 50 ? 50 : (bar_index > last_bar_index - 60 ? 60 : (bar_index > last_bar_index - 70 ? 80 : 100)))))))
line1=plot(tsi_value, color=(fadeOutOsc ? color.new(primary_color, distanceTransparency) : primary_color), title="TSI")
line2=plot(ta.ema(tsi_value, signal), color=(fadeOutOsc ? color.new(secondary_color, distanceTransparency) : secondary_color), title="Lagline")

tsi_lagline_value=(ta.ema(tsi_value, signal))
tsi_bands_crossed_up = (tsi_value > tsi_lagline_value) ? 1 : 0

cloudDistanceTransparency = (bar_index > last_bar_index - 40 ? 50 : (bar_index > last_bar_index - 100 ? 75 : 100))
CloudColor = CloudOn and tsi_value > lagline ? (fadeOutOsc ? color.new(primary_color, cloudDistanceTransparency) : color.new(primary_color, 75)) : (CloudOn and tsi_value <= lagline ? (fadeOutOsc ? color.new(secondary_color, cloudDistanceTransparency) : color.new(secondary_color, 75)) : na)
fill(line1, line2, color=CloudColor)

//background fill options
fill(band2, band0, BackgroundSignalOn and tsi_bands_crossed_up ? na : BackgroundSignalOn and (tsi_bands_crossed_up == 0) ? bandsDownColor : na, title="Bands crossover background colors", editable=1)
fill(band2, band0, BackgroundCenterSignalOn and tsi_value > 0 ? na : BackgroundCenterSignalOn and tsi_value < 0  ? subZeroColor : na, title="Centerline crossover background colors", editable=1)


//=Crossover dots
plot((crossoverDotsOn and (not fadeOutDotsOn) and ta.cross(tsi_value, lagline)) ? tsi_value : na, style=plot.style_circles, color=ta.crossover(tsi_value, lagline) ? color.new(primary_color, 0) : ta.crossunder(tsi_value, lagline) ? color.new(secondary_color, 0) : na, title="Band crossover dots", linewidth=2)
plot((crossoverDotsOn and fadeOutDotsOn and ta.cross(tsi_value, lagline)) ? tsi_value : na, style=plot.style_circles, color=ta.crossover(tsi_value, lagline) ? color.new(primary_color, dotDistanceTransparency) : ta.crossunder(tsi_value, lagline) ? color.new(secondary_color, dotDistanceTransparency) : na, title="Band crossover fadeout dots", linewidth=2)


fill(band0, band2, showBackground ? backgroundColor : na)

// set line styles
var reg_div_l_style = reg_div_l_style_ == "Solid" ? line.style_solid : 
                       reg_div_l_style_ == "Dashed" ? line.style_dashed :
                       line.style_dotted
var hid_div_l_style = hid_div_l_style_ == "Solid" ? line.style_solid : 
                       hid_div_l_style_ == "Dashed" ? line.style_dashed :
                       line.style_dotted

// get indicators
tsi = tsi_value

// keep indicator colors in arrays
var indicators_name = array.new_string(11)
var div_colors = array.new_color(4)
if barstate.isfirst
    //colors
    array.set(div_colors, 0, pos_reg_div_col)
    array.set(div_colors, 1, neg_reg_div_col)
    array.set(div_colors, 2, pos_hid_div_col)
    array.set(div_colors, 3, neg_hid_div_col)

// Check if we get new Pivot High Or Pivot Low
float ph = ta.pivothigh(haCheckClose, prd, prd)
float pl = ta.pivotlow(haCheckClose, prd, prd)

// keep values and positions of Pivot Highs/Lows in the arrays
var int maxarraysize = 20
var ph_positions = array.new_int(maxarraysize, 0)
var pl_positions = array.new_int(maxarraysize, 0)
var ph_vals = array.new_float(maxarraysize, 0.)
var pl_vals = array.new_float(maxarraysize, 0.)

// add PHs to the array
if ph
    array.unshift(ph_positions, bar_index)
    array.unshift(ph_vals, ph)
    if array.size(ph_positions) > maxarraysize
        array.pop(ph_positions)
        array.pop(ph_vals)

// add PLs to the array
if pl
    array.unshift(pl_positions, bar_index)
    array.unshift(pl_vals, pl)
    if array.size(pl_positions) > maxarraysize
        array.pop(pl_positions)
        array.pop(pl_vals)

// functions to check Regular Divergences and Hidden Divergences
// function to check positive regular or negative hidden divergence
// cond == 1 => positive_regular, cond == 2=> negative_hidden
positive_regular_positive_hidden_divergence(src, cond)=>
    divlen = 0
    prsc = haCheckClose
    // if indicators higher than last value and close price is higher than las close 
    if dontconfirm or src > src[1] or haCheckClose > haCheckClose[1]
        startpoint = dontconfirm ? 0 : 1 // don't check last candle
        // we search last 15 PPs
        for x = 0 to maxpp - 1
            len = bar_index - array.get(pl_positions, x) + prd
            // if we reach non valued array element or arrived 101. or previous bars then we don't search more
            if array.get(pl_positions, x) == 0 or len > maxbars
                break
            if len > 5 and 
               ((cond == 1 and src[startpoint] > src[len] and prsc[startpoint] < nz(array.get(pl_vals, x))) or
               (cond == 2 and src[startpoint] < src[len] and prsc[startpoint] > nz(array.get(pl_vals, x))))
                slope1 = (src[startpoint] - src[len]) / (len - startpoint)
                virtual_line1 = src[startpoint] - slope1
                slope2 = (haCheckClose[startpoint] - haCheckClose[len]) / (len - startpoint)
                virtual_line2 = haCheckClose[startpoint] - slope2
                arrived = true
                for y = 1 + startpoint to len - 1
                    if src[y] < virtual_line1 or nz(haCheckClose[y]) < virtual_line2
                        arrived := false
                        break
                    virtual_line1 := virtual_line1 - slope1
                    virtual_line2 := virtual_line2 - slope2
                
                if arrived
                    divlen := len
                    break
    divlen

// function to check negative regular or positive hidden divergence
// cond == 1 => negative_regular, cond == 2=> positive_hidden
negative_regular_negative_hidden_divergence(src, cond)=>
    divlen = 0
    prsc = haCheckClose
    // if indicators higher than last value and close price is higher than las close 
    if dontconfirm or src < src[1] or haCheckClose < haCheckClose[1]
        startpoint = dontconfirm ? 0 : 1 // don't check last candle
        // we search last 15 PPs
        for x = 0 to maxpp - 1
            len = bar_index - array.get(ph_positions, x) + prd
            // if we reach non valued array element or arrived 101. or previous bars then we don't search more
            if array.get(ph_positions, x) == 0 or len > maxbars
                break
            if len > 5 and 
               ((cond == 1 and src[startpoint] < src[len] and prsc[startpoint] > nz(array.get(ph_vals, x))) or 
               (cond == 2 and src[startpoint] > src[len] and prsc[startpoint] < nz(array.get(ph_vals, x))))
                slope1 = (src[startpoint] - src[len]) / (len - startpoint)
                virtual_line1 = src[startpoint] - slope1
                slope2 = (haCheckClose[startpoint] - nz(haCheckClose[len])) / (len - startpoint)
                virtual_line2 = haCheckClose[startpoint] - slope2
                arrived = true
                for y = 1 + startpoint to len - 1
                    if src[y] > virtual_line1 or nz(haCheckClose[y]) > virtual_line2
                        arrived := false
                        break
                    virtual_line1 := virtual_line1 - slope1
                    virtual_line2 := virtual_line2 - slope2
                
                if arrived
                    divlen := len
                    break
    divlen

// calculate 4 types of divergence if enabled in the options and return divergences in an array
calculate_divs(cond, indicator)=>
    divs = array.new_int(4, 0)
    array.set(divs, 0, cond and (searchdiv == "Regular" or searchdiv == "Regular/Hidden") ? positive_regular_positive_hidden_divergence(indicator, 1) : 0)
    array.set(divs, 1, cond and (searchdiv == "Regular" or searchdiv == "Regular/Hidden") ? negative_regular_negative_hidden_divergence(indicator, 1) : 0)
    array.set(divs, 2, cond and (searchdiv == "Hidden" or searchdiv == "Regular/Hidden")  ? positive_regular_positive_hidden_divergence(indicator, 2) : 0)
    array.set(divs, 3, cond and (searchdiv == "Hidden" or searchdiv == "Regular/Hidden")  ? negative_regular_negative_hidden_divergence(indicator, 2) : 0)
    divs

// array to keep all divergences
var all_divergences = array.new_int(4) // 1 indicator * 4 divergence = 4 elements
// set related array elements
array_set_divs(div_pointer, index)=>
    for x = 0 to 3
        array.set(all_divergences, index * 4 + x, array.get(div_pointer, x))

// set divergences array 
array_set_divs(calculate_divs(true, tsi), 0)

// keep line in an array
var pos_div_lines = array.new_line(0)
var neg_div_lines = array.new_line(0)
var pos_div_labels = array.new_label(0)
var neg_div_labels = array.new_label(0) 

// remove old lines and labels if showlast option is enabled
delete_old_pos_div_lines()=>
    if array.size(pos_div_lines) > 0    
        for j = 0 to array.size(pos_div_lines) - 1 
            line.delete(array.get(pos_div_lines, j))
        array.clear(pos_div_lines)

delete_old_neg_div_lines()=>
    if array.size(neg_div_lines) > 0    
        for j = 0 to array.size(neg_div_lines) - 1 
            line.delete(array.get(neg_div_lines, j))
        array.clear(neg_div_lines)

delete_old_pos_div_labels()=>
    if array.size(pos_div_labels) > 0 
        for j = 0 to array.size(pos_div_labels) - 1 
            label.delete(array.get(pos_div_labels, j))
        array.clear(pos_div_labels)

delete_old_neg_div_labels()=>
    if array.size(neg_div_labels) > 0    
        for j = 0 to array.size(neg_div_labels) - 1 
            label.delete(array.get(neg_div_labels, j))
        array.clear(neg_div_labels)

// delete last creted lines and labels until we met new PH/PV 
delete_last_pos_div_lines_label(n)=>
    if n > 0 and array.size(pos_div_lines) >= n    
        asz = array.size(pos_div_lines)
        for j = 1 to n
            line.delete(array.get(pos_div_lines, asz - j))
            array.pop(pos_div_lines)
        if array.size(pos_div_labels) > 0  
            label.delete(array.get(pos_div_labels, array.size(pos_div_labels) - 1))
            array.pop(pos_div_labels)

delete_last_neg_div_lines_label(n)=>
    if n > 0 and array.size(neg_div_lines) >= n    
        asz = array.size(neg_div_lines)
        for j = 1 to n
            line.delete(array.get(neg_div_lines, asz - j))
            array.pop(neg_div_lines)
        if array.size(neg_div_labels) > 0  
            label.delete(array.get(neg_div_labels, array.size(neg_div_labels) - 1))
            array.pop(neg_div_labels)
            
// variables for Alerts
pos_reg_div_detected = false
neg_reg_div_detected = false
pos_hid_div_detected = false
neg_hid_div_detected = false

// to remove lines/labels until we met new // PH/PL
var last_pos_div_lines = 0
var last_neg_div_lines = 0
var remove_last_pos_divs = false 
var remove_last_neg_divs = false
if pl
    remove_last_pos_divs := false
    last_pos_div_lines := 0
if ph
    remove_last_neg_divs := false
    last_neg_div_lines := 0

// draw divergences lines and labels
divergence_text_top = ""
divergence_text_bottom = ""
distances = array.new_int(0)
dnumdiv_top = 0
dnumdiv_bottom = 0
top_label_col = color.white
bottom_label_col = color.white
old_pos_divs_can_be_removed = true
old_neg_divs_can_be_removed = true
startpoint = dontconfirm ? 0 : 1 // used for don't confirm option

for x = 0 to 0
    div_type = -1
    for y = 0 to 3
        if array.get(all_divergences, x * 4 + y) > 0 // any divergence?
            div_type := y
            if (y % 2) == 1 
                dnumdiv_top := dnumdiv_top + 1
                top_label_col := array.get(div_colors, y)
            if (y % 2) == 0
                dnumdiv_bottom := dnumdiv_bottom + 1
                bottom_label_col := array.get(div_colors, y)
            if not array.includes(distances, array.get(all_divergences, x * 4 + y))  // line not exist ?
                array.push(distances, array.get(all_divergences, x * 4 + y))
                new_line = (showlines and not flipOsc) ? line.new(x1 = bar_index - array.get(all_divergences, x * 4 + y), 
                          y1 = (source == "Close" ? tsi[array.get(all_divergences, x * 4 + y)] : 
                                           (y % 2) == 0 ? haCheckLow[array.get(all_divergences, x * 4 + y)] : 
                                                          haCheckHigh[array.get(all_divergences, x * 4 + y)]),
                          x2 = bar_index - startpoint,
                          y2 = (source == "Close" ? tsi[startpoint] : 
                                           (y % 2) == 0 ? haCheckLow[startpoint] : 
                                                          haCheckHigh[startpoint]),
                          color = array.get(div_colors, y),
                          style = y < 2 ? reg_div_l_style : hid_div_l_style,
                          width = y < 2 ? reg_div_l_width : hid_div_l_width
                          )
                          : na
                if (y % 2) == 0
                    if old_pos_divs_can_be_removed
                        old_pos_divs_can_be_removed := false
                        if not showlast and remove_last_pos_divs
                            delete_last_pos_div_lines_label(last_pos_div_lines)
                            last_pos_div_lines := 0
                        if showlast
                            delete_old_pos_div_lines()
                    array.push(pos_div_lines, new_line)
                    last_pos_div_lines := last_pos_div_lines + 1
                    remove_last_pos_divs := true
                    
                if (y % 2) == 1
                    if old_neg_divs_can_be_removed
                        old_neg_divs_can_be_removed := false
                        if not showlast and remove_last_neg_divs
                            delete_last_neg_div_lines_label(last_neg_div_lines)
                            last_neg_div_lines := 0
                        if showlast
                            delete_old_neg_div_lines()
                    array.push(neg_div_lines, new_line)
                    last_neg_div_lines := last_neg_div_lines + 1
                    remove_last_neg_divs := true
                    
            // set variables for alerts
            if y == 0
                pos_reg_div_detected := true
            if y == 1
                neg_reg_div_detected := true
            if y == 2
                pos_hid_div_detected := true
            if y == 3
                neg_hid_div_detected := true



                                 
    
alertcondition(pos_reg_div_detected and not pos_reg_div_detected[1], title='Bullish Regular Divergence in TSI', message='Bullish Regular Divergence in TSI')
alertcondition(neg_reg_div_detected and not neg_reg_div_detected[1], title='Bearish Regular Divergence in TSI', message='Bearish Regular Divergence in TSI')
alertcondition(pos_hid_div_detected and not pos_hid_div_detected[1], title='Bullish Hidden Divergence in TSI', message='Bullish Hidden Divergence in TSI')
alertcondition(neg_hid_div_detected and not neg_hid_div_detected[1], title='Bearish Hidden Divergence in TSI', message='Bearish Hidden Divergence in TSI')

alertcondition((pos_reg_div_detected and not pos_reg_div_detected[1]) or (pos_hid_div_detected and not pos_hid_div_detected[1]), title='Bullish Divergence in TSI', message='Bullish Divergence in TSI')
alertcondition((neg_reg_div_detected and not neg_reg_div_detected[1]) or (neg_hid_div_detected and not neg_hid_div_detected[1]), title='Bearish Divergence in TSI', message='Bearish Divergence in TSI')

alertcondition((neg_reg_div_detected and not neg_reg_div_detected[1]) or (neg_hid_div_detected and not neg_hid_div_detected[1]) or (pos_reg_div_detected and not pos_reg_div_detected[1]) or (pos_hid_div_detected and not pos_hid_div_detected[1]), title='Divergence in TSI', message='Divergence in TSI')

alertcondition(crossoverDotsOn and (tsi_value > tsi_lagline_value) and not (tsi_value[1] > tsi_lagline_value[1]), title='TSI bands crossover bullish', message='TSI bands crossover bullish')
alertcondition(crossoverDotsOn and (tsi_value < tsi_lagline_value) and not (tsi_value[1] < tsi_lagline_value[1]), title='TSI bands crossover bearish', message='TSI bands crossover bearish')

//Oscillator name label
var table isFlippedLabel = table.new(position.bottom_left, 1, 1, bgcolor = color.new(color.black, 100), frame_width = 0, frame_color = color.new(#000000, 100))
var table bandState = table.new(position.middle_right, 1, 2, bgcolor = color.new(color.black, 100), frame_width = 0, frame_color = color.new(#000000, 100))

bandCrossedUp = tsi_val > tsi_lagline_value ? true : false
d = math.abs(tsi_val - tsi_lagline_value) > 14 ? color.white : na

if barstate.islast and flipOsc
    // We only populate the table on the last bar.
    table.cell(isFlippedLabel, 0, 0, text="Flipped", text_color=color.gray, text_halign=text.align_left, text_size=size.small, bgcolor=color.new(#000000, 100))

if barstate.islast and crossoverLabel
    table.cell(bandState, 0, 0, text="●", text_color=(math.abs(tsi_val - tsi_lagline_value) > 15 and bandCrossedUp ? color.orange : bandCrossedUp ? primary_color : color.new(color.gray, 90)), text_halign=text.align_left, text_size=size.large, bgcolor=color.new(#000000, 100))
    table.cell(bandState, 0, 1, text="●", text_color=(math.abs(tsi_val - tsi_lagline_value) > 15 and not bandCrossedUp ? color.orange : not bandCrossedUp ? secondary_color : color.new(color.gray, 90)), text_halign=text.align_left, text_size=size.large, bgcolor=color.new(#000000, 100))