True Strength Index
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © sxiong1111
// Script Created On:   9/23/2022
// Script Updated On:   10/5/2022
// Script Version:      1.6
// Description:         True Strength Index formula/calculation derived from Investopedia (https://www.investopedia.com/terms/t/tsi.asp)
//                      The variables and calculations derived from the same exact formula shown from Investopedia site above.  Please see the site above for reference information and more information about the True Strength Index.
//                      The basics of the True Strength Index is utilizing three EMA's.  The standard EMA's used are 13, 13 and 25.  Then it is smoothed to create a price direction that syncs with the current market trend.
//                      Key Takeaways of the True Strength Indicator (detailed information can be found on the Investopedia site):
//                              - TSI is a technical momentum oscillator to identify trends, reversals and can help determine overbought & oversold levels.
//                              - If above the zero line, it can be thought that the security is in a bull trend and bearish if otherwise.
//                              - TSI does have a weakness, as the signals shown can generate false signals.  Investopedia recommends that the TSI indicator shouldn't be used alone, but with other technical indicator(s) and/or utilizing price action.
//
//                      Personally, I disagree with the general consensus of just plopping 2 additional lines at fixed levels and call it good as oversold or overbought levels.
//                      With a couple hours of tinkering & experimentation, I think I've came up with a better oversold and overbought levels (which could probably be optimized further).  I'm maintaining 2 other different versions with different algorithms (unpublished for my own use for testing purposes).
//                      Please note that the oversold & overbought calculations/levels used in this indicator does not follow any official documents or recommendations.
//                      The only thing shared between this and the original TSI design by William Blau is the number '30' for the oversold/overbought level threshold.  I'm just using it in a different manner with this indicator, completely different from the original intention of the True Strength Indicator.
//
//                      With version 1.6 and in a continued effort to support and improve this indicator, I've decided to make a couple more feature changes/updates starting with this version and not just add some cosmetic changes.
//
//                      How to Determine Oversold and Overbought Levels?
//                              Using Version 1 (this version unchanged from the 1.5 public release):
//                                      - If the oversold/overbought line crosses either above or below the TSI line (not just above the TSI moving average line, but also above the TSI line as well), then you are in either oversold or overbought territory.
//                                      - With version 1 of the oversold/overbought levels, you may see the initial spike up/down indicating a strong volume trend upwards/downwards and then slowly fading.  The mountain/peaks you see can give you an edge in guessing an entry (or re-entry point).
//                                      - Look for double/triple/quadruple tops/bottoms, followed by a fade back into the TSI line.  This indicates a potential reversal.  Weak and stocks trending sideways may not even peak below/above the TSI line.
//                                      - Since different chart windows with different timeframes can yield different results, ideally, you'd want to configure the settings to where the highest or lowest point of the security price just about intersects with the TSI line.
//                                      - So the initial reversal has a high mountain/peak and as the security's price starts to reverse, the overbought/oversold line will eventually fade towards the TSI line (ultimately, intersecting it).
//
//                              Using Version 2 (new with the 1.6 public release):
//                                      - Sorry, still experimenting with the 2nd iteration of the oversold & overbought levels (this is being worked on through different unpublished versions).
//
//                      New with version 1.6 is a trend direction helper, as sometimes the True Strength Index can often generate false signals (if used alone).  By enabling the helper, a mid-line will be shown that generally bounces between the TSI line and the TSI moving average and the color shown here denotes the trend direction.
//                      Yes, the original TSI trend can often show a bearish or bullish trend, yet the security price can move into the opposite direction.  This is why the True Strength Index indicator can sometimes provide false signals.  By enabling the trend direction helper, the color of this mid-line can help point you into a potential
//                      trend reversal, while the main TSI indicator is showing the complete opposite.  Keep in mind that if enabling the trend direction helper on lower time frames, you can get that rapid succession of alternating colors between bullish and bearish signals.


//@version=5
indicator(title = "True Strength Index", shorttitle = "TSI", timeframe = "", timeframe_gaps = true, overlay = false)


// User Input
tsi_mode = input.bool(title = "Use Original TSI Design Concept?", defval = true, tooltip = "If enabled, this indicator intends to replicate the original design by William Blau's True Strength Index (but not at 100% of Blau's original design concept, since there's quite a few different customizations).\n\nIf disabled, the modified version will be used, which creates mountains/peaks, which closely follows price action (keeping everything else the same).  If using the modified version, you can disable the fill color for better visual clarity (if the lines are too jagged).", group = "True Strength Index Settings")
tsi_maMode = input.string(title = "Moving Average", defval = "EMA", options = ["EMA", "HULL 1", "HULL 2"], tooltip = "The original design concept of the True Strength Indicator is based on the Exponential Moving Average.\n\n● EMA = Use EMA for everything\n\n● HULL 1 = Use HULL MA for everything\n\n● HULL 2 = Use HULL MA; the main ma line stays as EMA", group = "True Strength Index Settings")
tsi_pcpModeSetting = input.int(title = "Closing Price Lookback", defval = 1, options = [1, 2, 3], tooltip = "The original design concept of the True Strength Indicator takes account only the latest prior closing price along with the current closing price.  By averaging the prior two or three closing prices, you can achieve a bit more of a smoothing effect allowing you to stay in the trade a bit longer for potential maximum gain.  Of course, the opposite can be true, potentially wiping some gains.\n\nThe default value is 1 and the difference between setting this at 2 or 3, aside from leaving it at 1 is nearly neglibile.", group = "True Strength Index Settings")
tsi_smoothS = input.int(title = "TSI MA Smoothing (Fast)", defval = 13, tooltip = "This is the fast EMA is used for the TSI smoothing.  The default value is 13.", group = "True Strength Index Settings")
tsi_smoothL = input.int(title = "TSI MA Smoothing (Slow)", defval = 25, tooltip = "This is the slow EMA is used for the TSI smoothing.  The default value is 25.", group = "True Strength Index Settings")
tsi_ema = input.int(title = "Moving Average", defval = 13, tooltip = "This is the main TSI moving average line.  The default value is 13.\n\nPlease note that since this factors in the TSI value, this moving average line does not represent a normal moving average line (whether that's an Exponential Moving Average or HULL Moving Average).", group = "True Strength Index Settings")
tsi_zeroLine = input.bool(title = "Show the Zero Line?", defval = false, tooltip = "Shows or hides the zero line.", group = "True Strength Index Settings")
tsi_zeroLineColor = input.color(color.new(#FFF72C, 80), title = "Zero Line Color", tooltip = "You can change the zero line color here.", group = "True Strength Index Settings")
tsi_zeroLineWidth = input.int(title = "Zero Line Thickness", defval = 1, options = [1, 2, 3, 4, 5], tooltip = "You can change the zero line thickness here.", group = "True Strength Index Settings")

tsi_lineWidth = input.int(title = "Line Thickness", defval = 1, options = [1, 2, 3, 4, 5], tooltip = "You can change the line thickness for the TSI lines here.", group = "True Strength Index Line & Color Settings")
tsi_maLineBold = input.bool(title = "Accentuate the TSI Moving Average Line?", defval = false, tooltip = "If enabled, the TSI moving average line will have an extra added line thickness for increased visibility.", group = "True Strength Index Line & Color Settings")
tsi_curlup = input.color(color.new(#FFEB3B, 60), title = "TSI Curl Line Color", tooltip = "", group = "True Strength Index Line & Color Settings", inline = "1")
tsi_curldn = input.color(color.new(#FFEB3B, 60), title = "", tooltip = "The color on the left corresponds to the TSI curl line bullish color.  The color on the right corresponds to the TSI curl line bearish color.", group = "True Strength Index Line & Color Settings", inline = "1")

tsi_nocurl = input.color(color.new(#9598A1, 60), title = "TSI Line Color", tooltip = "", group = "True Strength Index Line & Color Settings", inline = "2")
tsi_stbull = input.color(color.new(#00BCD4, 60), title = "", tooltip = "", group = "True Strength Index Line & Color Settings", inline = "2")
tsi_stbear = input.color(color.new(#E91E63, 60), title = "", tooltip = "The color on the left corresponds to the neutral TSI line color.  The color in the middle corresponds to the bullish TSI line color.  The color on the right corresponds to the bearish TSI line color.", group = "True Strength Index Line & Color Settings", inline = "2")

tsi_mabull = input.color(color.new(#00BCD4, 10), title = "TSI Moving Average Line Color", tooltip = "", group = "True Strength Index Line & Color Settings", inline = "3")
tsi_mabear = input.color(color.new(#E91E63, 10), title = "", tooltip = "The color on the left corresponds to the bullish TSI moving average line color.  The color on the right corresponds to the bearish TSI moving average line color.", group = "True Strength Index Line & Color Settings", inline = "3")

tsi_fcbull = input.color(color.new(#00BCD4, 80), title = "Fill Color", tooltip = "", group = "True Strength Index Line & Color Settings", inline = "4")
tsi_fcbear = input.color(color.new(#E91E63, 80), title = "", tooltip = "The color on the left corresponds to the bullish TSI fill color.  The color on the right corresponds to the bearish TSI fill color.", group = "True Strength Index Line & Color Settings", inline = "4")
tsi_fchide = input.bool(title = "Hide the Fill Color?", defval = false, tooltip = "You can quickly enable and disable the fill color here.  This is purely cosmetic and has no effect on the functionality of this indicator.", group = "True Strength Index Line & Color Settings")

tsi_ooLevelEN = input.bool(title = "Enable the Oversold & Overbought Lines?", defval = false, tooltip = "If enabled, the oversold and overbought lines will be shown.", group = "True Strength Index Oversold & Overbought Settings")
tsi_ooLevelMD = input.string(title = "Oversold & Overbought Mode", defval = "Scalp", options = ["Scalp", "Day Trade", "Swing Short", "Swing Long", "Eternity"], tooltip = "Oversold and overbought levels can be configured in different modes.  To maximize the effectiveness of this setting, please ensure the chart window you're applying this indicator to has the appropriate timeframe or the timeframe of this indicator is set accordingly.  The default mode is Scalp mode.\n\n● Scalp = Ideal for scalps & day trading\n\n● Day Trade = Ideal for day trading\n\n● Swing Short = Ideal for short-term swings\n\n● Swing Long = Ideal for long-term swings\n\n● Eternity = Ideal if you are immortal & holding forever", group = "True Strength Index Oversold & Overbought Settings")
tsi_ooLevelST = input.int(title = "Oversold & Overbought Level", defval = 30, options = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80], tooltip = "TSI is based on price movements and oversold/overbought levels.  By default, -30 and +30 is typically defined as the normal threshold.  Other securities may vary within a different threshold.  You can adjust the oversold and overbought level threshold here.\n\nIf you have the 'Use Original TSI Design Concept' option disabled at the top of this settings window, then you may need to significantly bump up the oversold/overbought level threshold (probably at minimum 50).\n\nThe default value is 30.  The minimum value is 10 and the maximum value is 80.", group = "True Strength Index Oversold & Overbought Settings")
tsi_ooLevelLT = input.int(title = "Oversold & Overbought Line Thickness", defval = 1, options = [1, 2, 3, 4, 5], tooltip = "You can change the line thickness of the oversold/overbought lines here.", group = "True Strength Index Oversold & Overbought Settings")
tsi_ooColorNT = input.color(color.new(#9598A1, 60), title = "Oversold/Overbought Line Color", tooltip = "You can assign a color for the oversold/overbought line here.", group = "True Strength Index Oversold & Overbought Settings")

tsi_trendEnable = input.bool(title = "Enable TSI Trend Assist?", defval = false, tooltip = "The True Strength Index (by itself) can be unreliable in visualizing trend direction.  By enabling the trend helper, a mid-line will be shown that bounces between the TSI line and the TSI moving average.  Most of the time, it's normally centered perfectly mid-way between the TSI line and the TSI moving average.\n\nThe important piece of information here is the color of the line.  Is it bullish or bearish?  By utilizing price action and comparing the color of this line to the TSI, you can get a better sense of general trend direction (or perhaps a confirmation of trend direction).", group = "True Strength Index Trend Helper")
tsi_trendLineTh = input.int(title = "TSI Trend Line Thickness", defval = 1, options = [1, 2, 3, 4, 5], tooltip = "You can change the thickness of the TSI trend line/circle here.", group = "True Strength Index Trend Helper")
tsi_trendLineSt = input.string(title = "TSI Trend Line Style", defval = "Line", options = ["Line", "Circle"], tooltip = "You can choose from line or circles for the TSI trend line.", group = "True Strength Index Trend Helper")
tsi_trendColorNeut = input.color(color.new(#9598A1, 60), title = "TSI Trend Line Color", tooltip = "", group = "True Strength Index Trend Helper", inline = "5")
tsi_trendColorBull = input.color(color.new(#00BCD4, 60), title = "", tooltip = "", group = "True Strength Index Trend Helper", inline = "5")
tsi_trendColorBear = input.color(color.new(#E91E63, 60), title = "", tooltip = "The color on the left corresponds to the neutral TSI trend line color.  The color in the middle corresponds to the bullish TSI trend line color.  The color on the right corresponds to the bearish TSI trend line color.", group = "True Strength Index Trend Helper", inline = "5")


// Variables
tsi_sxk = ta.ema(ta.stoch(close, high, ta.change(low), 8), 3)
isToday = ((year(timenow) == year(time)) and (month(timenow) == month(time)) and (dayofmonth(timenow) == dayofmonth(time))) ? true : false
var tsi_Lhigh = 0.0
var tsi_Llow = 0.0
epd_period = 8
tsi_sxd = ta.ema(tsi_sxk, 3)
if (tsi_ooLevelMD == "Scalp")
    epd_period := 8
else if (tsi_ooLevelMD == "Day Trade")
    epd_period := 13
else if (tsi_ooLevelMD == "Swing Short")
    epd_period := 20
else if (tsi_ooLevelMD == "Swing Long")
    epd_period := 34
else
    epd_period := 55

epd_ema = ta.ema(close, epd_period)
epd_dist = ((close - epd_ema) / epd_ema) * 100


// Double Smoothing (TSI requires that we use double-smoothing)
_doubleSmooth(src, tsi_smoothS, tsi_smoothL) =>
    if (tsi_mode)
        yi = tsi_maMode == "EMA" ? ta.ema(src, tsi_smoothL) : ta.hma(src, tsi_smoothL)
        tsi_maMode == "EMA" ? ta.ema(yi, tsi_smoothS) : ta.hma(yi, tsi_smoothS)
    else
        yi = ta.hma(src, tsi_smoothL)
        er = ta.hma(src, tsi_smoothS)
        math.avg(yi, er)


// Calculations (formula and variable names derived from Investopedia)
tsi_tmp3 = close[3]
tsi_tmp2 = close[2]
tsi_tmp1 = close[1]
tsi_pc = ta.change(close)                                                       // price change (the built-in function takes care of this work)

if (tsi_pcpModeSetting >= 2)
    tsi_tmp = 0.0
    if (tsi_pcpModeSetting == 2)
        tsi_tmp := math.avg(tsi_tmp1, tsi_tmp2)
    else
        tsi_tmp := math.avg(tsi_tmp1, tsi_tmp2, tsi_tmp3)
    tsi_pc := (close - tsi_tmp)

tsi_pcds = _doubleSmooth(tsi_pc, tsi_smoothS, tsi_smoothL)                      // price change double smoothed
tsi_apcds = _doubleSmooth(math.abs(tsi_pc), tsi_smoothS, tsi_smoothL)           // absolute price change double smoothed
tsi_tsi = (tsi_pcds / tsi_apcds) * 100                                          // true strength index (this is the basic calculation)
tsi_line = ta.ema(tsi_tsi, tsi_ema)
if (tsi_maMode != "EMA")
    tsi_line := tsi_maMode == "HULL 1" ? ta.hma(tsi_tsi, tsi_ema) : ta.ema(tsi_tsi, tsi_ema)

tsi_mPoint = math.avg(tsi_tsi, tsi_line)
t_trendBull = tsi_tsi >= tsi_tsi[1] ? true : false
p_tsiLineColor = tsi_nocurl
p_tsiEMALColor = tsi_nocurl
tsiFillColor = tsi_nocurl
p_tsiTrendLineColor = tsi_trendColorNeut

if (t_trendBull == true)
    if (tsi_tsi < tsi_line)
        p_tsiLineColor := tsi_curlup
    else
        p_tsiLineColor := tsi_stbull

if (t_trendBull == false)
    if (tsi_tsi >= tsi_line)
        p_tsiLineColor := tsi_curldn
    else
        p_tsiLineColor := tsi_stbear

if (tsi_line >= tsi_line[1])
    p_tsiEMALColor := tsi_mabull
else
    p_tsiEMALColor := tsi_mabear

if (tsi_tsi >= tsi_line)
    tsiFillColor := tsi_fcbull
else
    tsiFillColor := tsi_fcbear

if (tsi_fchide == true)
    tsiFillColor := color.new(#000000, 100)

epd_dist := epd_dist * tsi_ooLevelST
epd_pAct = (tsi_line + epd_dist)
tsi_sxDist = (tsi_sxk - tsi_sxd)
tsi_sxDistFinal = 0.0
if (math.abs(tsi_sxDist) <= 10)
    tsi_sxDistFinal := 0.0
else if ((math.abs(tsi_sxDist) > 10) and (math.abs(tsi_sxDist) <= 20))
    tsi_sxDistFinal := 1.0
else
    tsi_sxDistFinal := 1.5

if (tsi_sxk >= tsi_sxd)
    tsi_mPoint := tsi_mPoint + (tsi_sxDistFinal)
else
    tsi_mPoint := tsi_mPoint - (tsi_sxDistFinal)

if ((tsi_tsi > tsi_Lhigh) and isToday and (tsi_tsi > 0))
    tsi_Lhigh := tsi_tsi

if ((tsi_tsi < tsi_Llow) and isToday and (tsi_tsi < 0))
    tsi_Llow := tsi_tsi

if (tsi_sxk > tsi_sxd)
    p_tsiTrendLineColor := tsi_trendColorBull
else
    p_tsiTrendLineColor := tsi_trendColorBear


// Alerts
tsi_buy = ta.crossover(tsi_tsi, tsi_line)
tsi_sell = ta.crossunder(tsi_tsi, tsi_line)
tsi_cross = ta.cross(tsi_tsi, tsi_line)
tsi_Trendbuy = ta.crossover(tsi_sxk, tsi_sxd)
tsi_Trendsell = ta.crossunder(tsi_sxk, tsi_sxd)
tsi_Trendcross = ta.cross(tsi_sxk, tsi_sxd)
alertcondition(tsi_buy, title = "TSI Cross Up", message = "{{ticker}}: TSI Crossed Up")
alertcondition(tsi_sell, title = "TSI Cross Down", message = "{{ticker}}: TSI Crossed Down")
alertcondition(tsi_cross, title = "TSI Cross", message = "{{ticker}}: TSI Crossed")
alertcondition(tsi_buy, title = "TSI Trend Cross Up", message = "{{ticker}}: TSI Trend Crossed Up")
alertcondition(tsi_sell, title = "TSI Trend Cross Down", message = "{{ticker}}: TSI Trend Crossed Down")
alertcondition(tsi_cross, title = "TSI Trend Cross", message = "{{ticker}}: TSI Trend Crossed")


// Visuals
p_tsiLine = plot(tsi_tsi, title = "TSI Line", color = p_tsiLineColor, linewidth = tsi_lineWidth, style = plot.style_line, trackprice = false)
p_tsiEMAL = plot(tsi_line, title = "TSI MA Line", color = p_tsiEMALColor, linewidth = tsi_maLineBold == true ? tsi_lineWidth + 1 : tsi_lineWidth, style = plot.style_line, trackprice = false)
fill(p_tsiLine, p_tsiEMAL, title = "TSI Fill", color = tsiFillColor, fillgaps = true)
p_tsiZero = plot(tsi_zeroLine == true ? 0 : na, title = "Zero Line", color = tsi_zeroLineColor, linewidth = tsi_zeroLineWidth, style = plot.style_line, trackprice = false)
p_ooLLine = plot(tsi_ooLevelEN == true ? epd_pAct : na, title = "OB/OS Line", color = tsi_ooColorNT, linewidth = tsi_ooLevelLT, style = plot.style_line, trackprice = false)
p_tsiTrendLine = plot(tsi_trendEnable == true ? tsi_mPoint : na, title = "TSI Trend Line", color = p_tsiTrendLineColor, linewidth = tsi_trendLineTh, style = tsi_trendLineSt == "Line" ? plot.style_line : plot.style_circles, trackprice = false)
