Fusion: Trend and thresholds
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Koalems

// @version=5

// ***************************************************************************************************
//
//
// NOTES
//
//
// ***************************************************************************************************

// https://www.tradingview.com/pine-script-docs/en/v5/concepts/Timeframes.html#pagetimeframes

// ***************************************************************************************************
//
//
// CONSTANTS
//
//
// ***************************************************************************************************
rad2DegConst  = 180 / math.pi
maxBarsBack   = 4999

// ***************************************************************************************************
//
//
// TYPE
//
//
// ***************************************************************************************************
indicator(
  title         = 'Fusion: Trend and thresholds',
  shorttitle    = 'Fusion: Trend',
  max_bars_back = maxBarsBack,
  overlay=false)

// ***************************************************************************************************
//
//
// INPUT
//
//
// ***************************************************************************************************

// ***************************************************************************************************
// Trend
// ***************************************************************************************************
Group7110 = '7110 Trend: Show'

trendShowOnMainChart                = input.bool(false,                         group=Group7110,                                                                        title='On main chart')

Group7120 = '7120 Trend: Show any chart'
trendShow                           = input.bool(false,                         group=Group7120,                                                                        title='Trend line')
trendShowColorsShowThresholds       = input.bool(false,                         group=Group7120,                                                                        title='  Colors show thresholds', tooltip="If unchecked colors show direction of trend only.")
trendShowThresholdMarkers           = input.bool(false,                         group=Group7120,                                                                        title='Threshold markers')

Group7130 = '7130 Trend: Show off main chart'
trendShowSlope                      = input.bool(true,                          group=Group7130,                                                                        title='Slope line')             and not trendShowOnMainChart
trendShowThresholdAsFill            = input.bool(true,                          group=Group7130,                                                                        title='Threshold as fill')      and not trendShowOnMainChart
trendShowZeroLine                   = input.bool(true,                          group=Group7130,                                                                        title='Zero line')              and not trendShowOnMainChart
trendShowPRLines                    = input.bool(false,                         group=Group7120,                                                                        title='Percentile rank lines')  and not trendShowOnMainChart

Group7150 = '7150 Trend: Threshold levels'
tooltiptrendPR = '0 - 100, percentile rank value. For shorts we look for lower than the percentile rank and visa versa for longs.'
trendThresholdLong                  = input.float(70,                           group=Group7150, minval=0, maxval=100, step=1,                                          title='Threshold slope long',  tooltip=tooltiptrendPR)
trendThresholdShort                 = input.float(30,                           group=Group7150, minval=0, maxval=100, step=1,                                          title='Threshold slope short', tooltip=tooltiptrendPR)

Group7160 = '7160 Trend: Indicator settings'
trendTimeFrameMatchChart            = input.bool(false,                         group=Group7160,                                                                        title='Time frame same as chart or')
trendTimeFrame                      = input.string('480',                       group=Group7160,                                                                        title='  Custom time frame')

trendSrcOrig                        = input.source(hl2,                         group=Group7160,                                                                        title='Source')
trendMASwitch                       = input.string('EHMA',                      group=Group7160,                                                                        title='Source: MA variation', options=['EMA', 'EHMA', 'HMA', 'LINREG', 'RMA', 'SMA', 'SWMA', 'TEHMA', 'VWMA', 'WMA'])
trendMaLen                          = input.int(100,                            group=Group7160, minval=2,                                                              title='Source: MA Length')
trendSlopeLen                       = input.int(15,                             group=Group7160, minval=1,                                                              title='Slope: length')
trendSlopePercentageRankLen         = input.int(5000,                           group=Group7160, minval=1,                                                              title='Percentage rank length')

Group7170 = '7170 Trend: Display settings'
trendColorZeroLine                  = input.color(color.new(color.white, 25),   group=Group7170,                                                                        title='Zero line')

// We don't use inputs for these because then we loose colors on the style tab.
trendLongBaseColor                  = color.new(color.green,  0)
trendShortBaseColor                 = color.new(color.red,    0)
trendNoTrendBaseColor               = color.new(color.white,  0)

// ***************************************************************************************************
//
//
// FUNCTIONS
//
//
// ***************************************************************************************************
SlopeCalc(src, Xrange) =>
//  x1 = 0                              // Theoretically everything stems from x1, x2, y1, y2 but we assume
//  x2 = Xrange                         // x1 = 0 which means we can just use Xrange instead of x1 & x2.
    y1 = ta.linreg(src, Xrange, Xrange) // Get Y1 for X (Xrange) using Linear Regression (best line fit)
    y2 = ta.linreg(src, Xrange, 0)      // Get Y2 for X (  0  )  using Linear Regression (best line fit)

//  Xrange = Xrange - 0                 // Set the X range
    Yrange = y2 - y1                    // Set the Y range
    slope = Yrange / Xrange             // Distance formula from pythagoras
    slope

// https://alanhull.com/hull-moving-average
//     Hull Moving Average (HMA) formula
//     Integer(SquareRoot(Period)) WMA [2 x Integer(Period/2) WMA(Price) - Period WMA(Price)]
// THMA is even more weighted to shorter timeframes than the EMA. The 'T' is for Tripple and is like a Tripple EMA
// so TEHMA would be more correct.
HMA (_src, _length) => ta.wma(2 * ta.wma(_src, _length / 2) - ta.wma(_src, _length    ), math.round(math.sqrt(_length)))
EHMA(_src, _length) => ta.ema(2 * ta.ema(_src, _length / 2) - ta.ema(_src, _length    ), math.round(math.sqrt(_length)))
THMA(_src, _length) => ta.wma(3 * ta.wma(_src, _length / 3) - ta.wma(_src, _length / 2) - ta.wma(_src, _length), _length)

HullMode(modeSwitch, src, len) =>
  modeSwitch == 'HMA'   ? HMA (src, len) :
  modeSwitch == 'EHMA'  ? EHMA(src, len) :
  modeSwitch == 'TEHMA' ? THMA(src, len / 2) : na

MaTypes(source, length, smoothingMode) =>
    float ma = switch smoothingMode
        'EMA'     => ta.ema(source, length)
        'EHMA'    => HullMode(smoothingMode, source, length)
        'HMA'     => HullMode(smoothingMode, source, length)
        'LINREG'  => ta.linreg(source, length, 0)
        'RMA'     => ta.rma(source, length)
        'SMA'     => ta.sma(source, length)
        "SWMA"    => ta.swma(source)
        'TEHMA'   => HullMode(smoothingMode, source, length)
        "VWMA"    => ta.vwma(source, length)
        'WMA'     => ta.wma(source, length)
        =>  
            runtime.error("No matching MA type found.")
            float(na)
    ma

// ***************************************************************************************************
//
//
// MAIN
//
//
// ***************************************************************************************************

// ***************************************************************************************************
// Trend
// ***************************************************************************************************
rangeSrc = request.security(syminfo.tickerid, trendTimeFrame, trendSrcOrig)

if trendTimeFrameMatchChart
    rangeSrc := trendSrcOrig

trendMa       = MaTypes(rangeSrc, trendMaLen, trendMASwitch)
trendSlope    = SlopeCalc(trendMa, trendSlopeLen)

trendMaDir = 
  trendMa > trendMa[2] ? 1  :
  trendMa < trendMa[2] ? -1 : 0 // 1 = up, -1 = down, 0 = horizontal

// Percentage rank
trendTooLow  = ta.percentile_linear_interpolation(trendSlope, trendSlopePercentageRankLen, trendThresholdShort)
trendTooHigh = ta.percentile_linear_interpolation(trendSlope, trendSlopePercentageRankLen, trendThresholdLong)

trendLong  = trendSlope > trendTooHigh  // Source too high by being above the percentage rank
trendShort = trendSlope < trendTooLow   // Source too low  by being below the percentage rank

trendLongStart  = not trendLong[1]  and trendLong
trendShortStart = not trendShort[1] and trendShort
trendLongEnd    = trendLong[1]  and not trendLong
trendShortEnd   = trendShort[1] and not trendShort

// ***************************************************************************************************
//
//
// SHOW
//
//
// ***************************************************************************************************
plot(trendShowPRLines ? trendTooHigh : na, color=color.aqua, title='Percentile rank high')
plot(trendShowPRLines ? trendTooLow  : na, color=color.aqua, title='Percentile rank low')

trendFillCond = trendShowThresholdAsFill and (trendLong or trendShort)

color trendDirColor = switch trendMaDir 
    1  => color.new(trendLongBaseColor,     0)
    0  => color.new(trendNoTrendBaseColor,  0)
    -1 => color.new(trendShortBaseColor,    0)

color trendDirColorFill = switch trendMaDir 
    1  => color.new(trendLongBaseColor,     88)
    0  => color.new(trendNoTrendBaseColor,  88)
    -1 => color.new(trendShortBaseColor,    88)

color trendDirColorFillOutline = switch trendMaDir 
    1  => color.new(trendLongBaseColor,     50)
    0  => color.new(trendNoTrendBaseColor,  50)
    -1 => color.new(trendShortBaseColor,    50)

if trendShowColorsShowThresholds
    trendDirColor := 
      trendLong  ? color.new(trendLongBaseColor,  0) : 
      trendShort ? color.new(trendShortBaseColor, 0) : color.new(trendNoTrendBaseColor,    0)

plot(trendShow                            ? trendMa    : na, color=trendDirColor,               style=plot.style_line,          title='Trend MA line')
plot(trendShowSlope and not trendFillCond ? trendSlope : na, color=color.new(color.orange,  0), style=plot.style_linebr,        title='Trend slope line')

hline(trendShowZeroLine and not trendShowThresholdAsFill ? 0 : na, color=trendColorZeroLine, linestyle=hline.style_dotted,      title='Zero line')

trendGoLongShortOnly      = plot(trendFillCond ? trendSlope   : na, color=trendDirColorFillOutline, style = plot.style_linebr,  title='Trend go long/short plot')
trendGoLongShortOnlyZero  = plot(trendShowThresholdAsFill ? 0 : na, color=trendColorZeroLine,       style=plot.style_line,      title='Zero line')
fill(trendGoLongShortOnly, trendGoLongShortOnlyZero,                color=trendDirColorFill,                                    title='Trend go long/short fill')

plotshape(trendShowThresholdMarkers and trendLongStart,  color=color.new(trendLongBaseColor,  45), text='Start', style=shape.labeldown, location=location.top,    size=size.small, textcolor=color.white, title='Trend: Long start')
plotshape(trendShowThresholdMarkers and trendLongEnd,    color=color.new(trendLongBaseColor,  45), text='End',   style=shape.labeldown, location=location.top,    size=size.small, textcolor=color.white, title='Trend: Long end')
plotshape(trendShowThresholdMarkers and trendShortStart, color=color.new(trendShortBaseColor, 45), text='Start', style=shape.labelup,   location=location.bottom, size=size.small, textcolor=color.white, title='Trend: Short start')
plotshape(trendShowThresholdMarkers and trendShortEnd,   color=color.new(trendShortBaseColor, 45), text='End',   style=shape.labelup,   location=location.bottom, size=size.small, textcolor=color.white, title='Trend: Short end')
