Any Oscillator Underlay [TTF]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TheTrdFloor

//@version=5
indicator(title="Any Oscillator Underlay [TTF]", shorttitle="AOU [TTF]", overlay=false) //, precision=8
//*****   Shared Calculation Functions   *****//
//
// Double EMA
dema(src, len) =>
    de1 = ta.ema(src, len)
    de2 = ta.ema(de1, len)
    dema = 2 * de1 - de2
//
// Triple EMA
tema(src, len) =>
    te1 = ta.ema(src, len)
    te2 = ta.ema(te1, len)
    te3 = ta.ema(te2, len)
    tema = 3 * (te1 - te2) + te3
//
// Function to calculate smoothed moving average...
smoothedMovingAvg(src, len) => 
	smma = 0.0
	// TV will complain about the use of the ta.sma function use inside a function saying that it should be called on each calculation, 
	// but since we're only using it once to set the initial value for the smoothed MA (when the previous smma value is NaN - Not a Number)
	// and using the previous smma value for each subsequent iteration, this can be safely ignored
	smma := na(smma[1]) ? ta.sma(src, len) : (smma[1] * (len - 1) + src) / len 
	smma
//	
// MA calculation
ma(source, length, type) =>
    switch type
        "Simple" => ta.sma(source, length)
        "Exponential" => ta.ema(source, length)
        "Weighted" => ta.wma(source, length)
        "Volume-Weigehted" => ta.vwma(source, length)
        "Smoothed" => smoothedMovingAvg(source, length)
        "RMA" => ta.rma(source, length)
        "Linear" => ta.linreg(source, length, 0)
        "Hull" => ta.hma(source, length)
        "ALMA" => ta.alma(source, length, 0.85, 6)
        "Double EMA" => dema(source, length)
        "Triple EMA" => tema(source, length)
//
//*****   End Shared Calculation Functions   *****//
//
//
//*****   Shared Display Functions   *****//
//
// For more information on the 'standardize' and 'normalize' functions and the math and reasoning behind their use,
// here are some additional references:
//      * https://en.wikipedia.org/wiki/Feature_scaling
//
// Linear Scaling (min-max normalization - bounds values to a fixed range between 0 and 1):  x' = (x - min(x)) / (max(x) - min(x))
//      
// Dev Note: This probably won't be used terribly often as it highly-sensitive to outliers, which
//      has the side-effect of shifting the mid-line on many indicators.  Take the MACD for example.  
//      It's technically unbounded (range from -∞ to ∞ with a center-line of 0).  The normalization 
//      process (binding it to a range between 0 and 1) should theoretically shift the center-line 
//      to 0.5, but that will only ever happen in practice if the average of the input 'data' series 
//      happens to be the exact mid-point of the max - min, which will be EXTREMELY improbable 
//      (especially in a market that is trending in any way at all).
//
normalize(data, normalizationLength) =>
    float ret = na
    lowest = ta.lowest(data, normalizationLength)
    highest = ta.highest(data, normalizationLength)
    ret := (data - lowest) / (highest - lowest)
    ret
//
// Mean/Z-Score Normalization (standardization - transform data to have a mean of 0 and standard deviation of 1):  z = (x - avg(x)) / stdev(x)
//
// Dev Note: We will generally be using this function over the 'normalize' function above as it is far less
//      affected by outliers and generally preserves the mid-line of indicators.  While this is still 
//      TECHNICALLY unbounded (range from -∞ to ∞ with a center-line/mean of 0), we gain the added
//      limiter of having a fixed standard deviation of 1.  
//
//      While statistical purists will argue against the assumption that most oscillators will tend towards a normal distribution
//      (and they are technically correct as trending markets will generally skew the sample distribution towards the positive or negative side), 
//      we can still generally assume that, over a large sample size, the result is close enough to a normal distribution to enable us to use
//      the commonly-known rules of normally-distributed data with a high degree of confidence, along with retaining the behavior and mid-line
//      that the un-scaled verion has as well.  And while this still is unbound, we can effectively (conceptually) range-bind it by defining the
//      range as a number of standard deviations the mean.  Since the process of standardization makes it so that all data in the series will
//      have a mean of 0 and a standard deviation of 1, we can effectively define our own scale (e.g. 5 std dev from the mean nets a range of 
//      -5 to +5 with a mean/mid-line of 0).  Given the following table of distance from the mean in standard deviations (σ):
//
// Standard Deviations from Mean   | Proportion (%) Inside	| Proportion (%) Outside
// -----(Confidnece Interval)------|------------------------|-----------------------
// 			1σ  				   | 68.2689492%  		 	| 31.7310508%
// 			2σ  				   | 95.4499736%  			| 4.5500264% 
// 			2.575829σ  			   | 99%  					| 1%
// 			3σ  				   | 99.7300204% 			| 0.2699796%
// 			3.290527σ  			   | 99.9%  				| 0.1% 
// 			3.890592σ  			   | 99.99%  				| 0.01%
// 			4σ  				   | 99.993666%  			| 0.006334%
// 			4.5σ  				   | 99.9993204653751%  	| 0.0006795346249%  
// 			5σ  				   | 99.9999426697%  		| 0.0000573303%
// 			6σ  				   | 99.9999998027%  		| 0.0000001973%
// 			6.109410σ  			   | 99.9999999%  			| 0.0000001%
// 			6.466951σ  			   | 99.99999999%  			| 0.00000001%
// 			6.806502σ  			   | 99.999999999%  		| 0.000000001%
// 			7σ  				   | 99.9999999997440%  	| 0.000000000256%
//
//      we can see that a manually-defined range of +- 5σ should cover a smidge over 99.9999% of the total dataset, which
//      should be more than sufficient for our purposes.  Now, there will still be the occasional outlier that breaks out
//      of that range, but it should be exceptionally rare (and generally triggered by an extreme market movement). 
//
standardize(data, standardizationLength) =>
    float ret = na
    // The "official" formula simply states using an average.  Normally we would translate that formula definition to mean
    // using a Simple moving average, however since we can't look back far enough to get a true average of the entire 
    // population (i.e. all bars for all time), our mean for standardization will likely be just a hair off.  As a result,
    // we can get a slight mean-skew that can affect the accuracy of zero-line-cross-based signals by a candle or 2.
    //
    // In testing, we found that the Gaussian distribution weighting method used by the ALMA to do a pretty good job
    // of balancing that skew out, thereby significantly improving the accuracy of "zero-line" crosses in the post-transformed
    // output when compared to zero-line crosses of the original indicator with the same settings.  It's worth noting that
    // the Exponential and Hull moving averages also seemed to align pretty well with reference indicators, but the ALMA
    // edged them out by a hair - especially when looking at segments where the reference oscillator is hovering right at the mid-line
    // (small, frequent crosses above and below over the course of a dozen or so candles).
    //
    // At some point, we may refactor this indicator to make the standardization mean calculation method a configuration option,
    // but odds are pretty high that most poeple will not fully understand the implications of changing this from the default.
    mean = ma(data, standardizationLength, "ALMA") 
    stddev = ta.stdev(data, standardizationLength, false) // Last arg is to apply a bias
    ret := (data - mean) / stddev
    ret
//
// Basic function to re-scale data that has been standardized via the 'standardize' function above.
// The intent here is to target a potential value range of 100 and move the center-point from 0 to 50
//
// dataIn => The input data series to rescale
// scaleFactor => The number of standard deviations to use to define the upper and lower bounds
rescaleStandardizedData(dataIn, scaleFactor) =>
    sf = (100 / (2 * scaleFactor))
    ret = ((dataIn * sf)  + 50)
    ret
    // An alternate method of dynamically rescaling for potential further investigation, based on
    // dynamically determining the range of `dataIn` and deriving a scaling factor based on that input, is drafted below
    // for future reference.  It was decided to not implemented for the following reason:
    // 
    // While it DID accomplish the goal of up-scaling the dataset while remaining in the defined upper and lower bounds,
    // it had the side-effect of not properly representing the relative scale of the extremes in a sudden, massive movement in one direction, 
    // followed by a subsequent, massive move in the other direction (but not as extreme as the first). 
    //
    // The observed behavior is that the peak from the boom and valley from the crash would appear to be equal in magnitude, 
    // even though the reference indicator showed the valley wasn't as severe as the preceeding peak. The fixed-factor rescaling did
    // a much better job of retaining the relative strengh of those same movements when compared to the reference indicator, 
    // which is critical for any divergence-based oscillator strategy. 
	// 
    // maxDistFrom0 = math.abs(ta.highest(dataIn, 5000)) >= math.abs(ta.lowest(dataIn, 5000)) ? math.abs(ta.highest(dataIn, 5000)) : math.abs(ta.lowest(dataIn, 5000))
    // sfDenomRounded = math.ceil((maxDistFrom0*11))/10
    // sf2 = (98 / (2 * sfDenomRounded))
    // ret2 = ((dataIn * sf2) + 50)
    // ret2
//
// Basic function to rescale data that has gone through the 'normalize' function
// Since the output of the 'normalize' function is a fixed-range between 0 and 1,
// this is a pretty basic thing to do.  Like the 'rescaleStandardizedData' above,
// the intent is to have a range 100.
//
// dataIn => The input data series to rescale
rescaleNormalizedData(dataIn) =>
    ret = 100 * dataIn
    ret
//
// Simple function to get the mid-point of the Y axis 

// Since we have the functions above to help manipulate raw data of the various 
// indicators/oscillators we intend to include in this indicator into a (relatively)
// known and shared fixed-range of 100 when needed, the only thing left to do is
// make a way to "stack" them in a non-overlapping manner... hopefully...
// See comments for the 'standardize' function above.
//
// With the above stated, this should be a relatively simple process of just doing a basic
// multiplier to the input series.
//
// Dev Note: This function should only be used on the primary series of an indicator (those
//           that are directly tied to price, volume, etc.). Subsequent/supplementary calculations
//           that are derived from a primary series (e.g. moving averages) should be applied
//           to the repositioned series to ensure that the calculations accurately reflect
//           the data that will be plotted for the primary series;
//
// dataIn =>   The input data series to reposition.  
//             This function assumes that the input series is already bound to a range of
//             100 - either by the nature of the indicator itself or by leveraging the
//             other methods above to handle the rescaling.
// posIndex => Relative postion to place the input data series in the indicator "stack".
//             Like most things in programming, we will define our base index point as 0
//             and count up from there (which also aligns with how Pinescript indexes
//             arrays - more on that later!).
repositionSeries(dataIn, posIndex) =>
    ret = dataIn + (100 * posIndex)
    ret
//
// Just semi-abusing the function above to define a convenient method to get the mid-point
// of the series
getIndicatorMidpoint(posIndex) =>
    repositionSeries(50, posIndex)
//
// To try and be as user-friendly as possible, it'd be a nice touch to actually have some form
// of label/title for each sub-component displayed on the chart so that the user can easily
// see which indicator/oscillator is which.  A single-cell table would have been nice, but
// unfortunately we have no way of locating those with absolute positioning, so we'll instead
// need to use another format that DOES support absolute positioning and custom inner text.
//
// The 'label' class supports all of those requirements, so we'll go with that.  But that also
// means we'll need to calculate the exact X and Y coordinates to use to plot the titles...
//
// For the Y axis, we'll baseline off of 75 and increment by 100 - easy enough.  The X axis
// will be the tricky one...  We want to try and align it to the far-left side of the chart,
// but not SO far left that it gets cropped (which is what happens with the time-based built-in
// 'chart.left_visible_bar_time').  So we'll need to do a bit of Pinescript wizardry to derive
// the number of bars visible on the chart and back-calculate the left-most bar index, then apply
// an additional rightward offset.  And while we're at it, let's also add that there's an additional
// fudge-factor of where on-screen the right-most bar is (mid-screen, all the way to the edge, or 
// anywhere else in the visible range - we don't know) that can also affect how this text displays.
//
// This all sounds a bit complicated (and it is), but at least the X axis will remain constant for 
// all the different oscillators/indicators we will have, so at least this is a calculation we only
// have to do once.  
//
// So with all that said, let's just take it one step at a time.  We'll start by defining some inputs
// to allow users to manually adjust the right-offset and text color as-needed.
//
titleOffsetScaleFactor = input.int(title="Title Offset Scaler:", defval=5, minval=0, maxval=100, step=1, group="General Settings - Titles", inline="s1")
titleColor = input.color(title="Color:", defval=color.rgb(200, 200, 200, 0), group="General Settings - Titles", inline="s1", 
     tooltip="The 'Title Offset Scaler' setting controls the left-offset of the sub-indicator titles.  This number should generally equate to the number of candles from the left " +
     "to use to center the sub-indicator titles.\n\n" +
     "In most cases, a value between 3-10 should do.")
//
// And now to do the X-position calculation...
// This first one attempts to calculate the number of currently-visible bars on the chart.  It
// should be pretty accurate (within 1-2 bars depending on how far out the chart zoom is).
chartBarCount = (chart.right_visible_bar_time - chart.left_visible_bar_time) / (1000 * timeframe.in_seconds())
//
// Now we use the value above to get the bar index of the leftmost bar, then apply our right-shift
// to address the text truncation issue noted earlier.  We use the 'math.ceil' function here to force
// any remainder to round up as it's better to err on the right side (since this is a positive shift
// to the right).
titlePosX = (bar_index - chartBarCount) + math.ceil(chartBarCount / titleOffsetScaleFactor)
titlePosTime = (chart.left_visible_bar_time + (titleOffsetScaleFactor * (1000 * timeframe.in_seconds())))
//
// And now we can use the inputs and calculations to make a function to that will insert the desired
// title at the correct location.  Since we intend to index our component indicators so that we can
// properly stack them, we'll include an 'index' argument as well to help maintain consistency.
//
// title => The desired title text
// index => The index number of the indicator to apply the title to, which alters the Y-axis position
insertTitle(title, index) =>
    //tmplabel = label.new(x=titlePosX, xloc=xloc.bar_index, y=(85 + (int(index) * 100)), yloc=yloc.price, text=str.tostring(title), style=label.style_none, textcolor=titleColor, textalign=text.align_left)
    tmplabel = label.new(x=titlePosTime, xloc=xloc.bar_time, y=(85 + (int(index) * 100)), yloc=yloc.price, text=str.tostring(title), style=label.style_none, textcolor=titleColor, textalign=text.align_left)
    // This will delete the previous label and ensure that we only have a single 'title label' for any given index
    label.delete(tmplabel[1])
//
// In keeping with the customization of styles, we'll add some user inputs
lineStyle = input.string(title="Line Style:", defval="Solid", 
                 options=["Solid", "Dashed", "Dotted"],
                 group="General Settings - Separator", inline="s2", 
                 tooltip="This setting controls the line style for the divider lines between the sub-indicators (including the upper and lower-most lines).")
lineWidth = input.int(title="Size/Width:", defval=2, minval=0, maxval=10, group="General Settings - Separator", inline="s1")
lineColor = input.color(title="Color:", defval=color.rgb(255, 255, 255, 0), group="General Settings - Separator", inline="s1", 
                 tooltip="This setting controls the size and coloring of the divider lines noted above.")
//
// Simple function to convert our input linestyle into the corresponding real value needed for functions...
getLineStyle(styleIn) =>
	tmpLineStyle = switch (styleIn)
        "Solid" => line.style_solid
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        => line.style_solid
	tmpLineStyle
//
getHLineStyle(styleIn) =>
	tmpLineStyle = switch (styleIn)
        "Solid" => hline.style_solid
        "Dashed" => hline.style_dashed
        "Dotted" => hline.style_dotted
        => hline.style_solid
	tmpLineStyle
//
// Function to draw divider lines to help separate the sub-indicators, again using indexes as our key.
// Similar to the 'insertTitle' above, we're using 'line' objects instead of the more common 'hline'
// as 'hline' only works in a global scope (and it helps maintain consistency).
//
// index => The index number of the indicator to apply the title to, which alters the Y-axis position
insertUpperLine(index) =>
    tmpLineStyle = getLineStyle(lineStyle)
    tmpline = line.new(x1=bar_index, x2=bar_index[1], y1=((100 * index) + 100), y2=((100 * index) + 100), extend=extend.both, color=lineColor, width=lineWidth, style=tmpLineStyle)
    line.delete(tmpline[1])
    // And to be visually consistent, if inserting an upper-line for index 0, add a matching lower line as well...
    if (index == 0)
        // Ideally we'd use recursion and call this same function via 'insertUpperLine(-1)', but Pinescript doesn't support recursion,
        // so we have to code out the 2nd line manually
        tmplineLower = line.new(x1=bar_index, x2=bar_index[1], y1=0, y2=0, extend=extend.both, color=lineColor, width=lineWidth, style=tmpLineStyle)
        line.delete(tmplineLower[1])
//
// In keeping with the customization of styles, we'll add some user inputs
midLineStyle = input.string(title="Line Style:", defval="Dashed", 
                 options=["Solid", "Dashed", "Dotted"],
                 group="General Display Settings - Mid Lines", inline="s2", 
                 tooltip="This setting controls the line style for the mid-lines in the sub-indicators.")
midLineWidth = input.int(title="Size/Width:", defval=1, minval=0, maxval=10, group="General Display Settings - Mid Lines", inline="s1")
midLineColor = input.color(title="Color:", defval=color.rgb(216, 216, 216, 0), group="General Display Settings - Mid Lines", inline="s1", 
                 tooltip="This setting controls the size and coloring of the mid-lines noted above.")
//
obosLineStyle = input.string(title="Line Style:", defval="Dotted", 
                 options=["Solid", "Dashed", "Dotted"],
                 group="General Display Settings - OB/OS Lines", inline="s2", 
                 tooltip="This setting controls the line style for the OB and OS lines in the sub-indicators.")
obosLineWidth = input.int(title="Size/Width:", defval=1, minval=0, maxval=10, group="General Display Settings - OB/OS Lines", inline="s1")
obosLineColor = input.color(title="Color:", defval=color.rgb(255, 255, 255, 15), group="General Display Settings - OB/OS Lines", inline="s1", 
                 tooltip="This setting controls the size and coloring of the OB and OS lines noted above.")
//
// Now for mid-lines and OB/OS lines...
insertInnerLines(midpoint, obosOffset, inclMidline, inclOBOSLines) =>
    tempOBOSLineStyle = getLineStyle(obosLineStyle)
    tempMidLineStyle = getLineStyle(midLineStyle)
    if (inclMidline)
        tmpMidLine = line.new(x1=bar_index, x2=bar_index[1], y1=midpoint, y2=midpoint, extend=extend.both, color=midLineColor, width=midLineWidth, style=tempMidLineStyle)
        line.delete(tmpMidLine[1])
    if (inclOBOSLines)
        tmpOBLevel = midpoint + obosOffset
        tmpOSLevel = midpoint - obosOffset
        tmpOBLine = line.new(x1=bar_index, x2=bar_index[1], y1=tmpOBLevel, y2=tmpOBLevel, extend=extend.both, color=obosLineColor, width=obosLineWidth, style=tempOBOSLineStyle)
        tmpOSLine = line.new(x1=bar_index, x2=bar_index[1], y1=tmpOSLevel, y2=tmpOSLevel, extend=extend.both, color=obosLineColor, width=obosLineWidth, style=tempOBOSLineStyle)
        line.delete(tmpOBLine[1])
        line.delete(tmpOSLine[1])
//
//*****   End Shared Display Functions   *****//
//
//
//*****   Moving the sub-indicator toggles higher up in the settings based on tester feedback  *****//
//
//
g_MainIndicatorControl = "Sub-Indicator Display Toggles"
tsiDisplay = input.bool(title='Include TSI', defval=true, group=g_MainIndicatorControl, tooltip='If checked, will include the TSI in the composite indicator.  Default value: checked')
rsiDisplay = input.bool(title='Include Twin RSI', defval=true, group=g_MainIndicatorControl, tooltip='If checked, will include the Twin RSI in the composite indicator.  Default value: checked')
stochRSIDisplay = input.bool(title='Include Stochastic RSI', defval=true, group=g_MainIndicatorControl, tooltip='If checked, will include the Stochastic RSI in the composite indicator.  Default value: checked')
stochDisplay = input.bool(title='Include Stochastic', defval=false, group=g_MainIndicatorControl, tooltip='If checked, will include the Stochastic in the composite indicator.  Default value: unchecked')
uoDisplay = input.bool(title='Include Ultimate Oscillator (UO)', defval=true, group=g_MainIndicatorControl, tooltip='If checked, will include the UO in the composite indicator.  Default value: checked')
aoDisplay = input.bool(title='Include Awesome Oscillator (AO)', defval=false, group=g_MainIndicatorControl, tooltip='If checked, will include the AO in the composite indicator.  Default value: false')
macdDisplay = input.bool(title='Include MACD', defval=false, group=g_MainIndicatorControl, tooltip='If checked, will include the MACD in the composite indicator.  Default value: unchecked')
orsiDisplay = input.bool(title='Include ORSI', defval=false, group=g_MainIndicatorControl, tooltip='If checked, will include the Outback RSI in the composite indicator.\n\n' +
             'Note: This is a special-purpose indicator and covering how to use it is outside the scope of this help-text.\n\n' +
             'Suffice it to say - if you know what this is, you\'ll know how to use it.  If you don\'t know what it is, just don\'t use it (leave it disabled).\n\n' + 
             'Default value: unchecked')
//
//
//
//*****   Begin Functional Code Section   *****//
//
// In this "composite indicator" as I'll call it, we intend to include all of the following as options:
//
//  * TSI
//  * RSI
//  * Stoch RSI
//  * OG Stochastic
//  * UO 
//  * AO
//  * MACD
//
// We're going to be a bit "lazy" and take advantage of the fact that Pinescript evaluates in a linear fashion, so we
// don't need to explicitly manage and track each sub-component indicator individually.  Instead, we will let the script
// simply run through and evaluate each component indicator individually (which will each have an option to enable/disable)
// whether or not it is displayed and track where/how to display each component indicator by using a basic array and the 
// following rules:
//      1.  An empty array will be initialized at the very start of the script execution, which will be "shared"
//          by all the component indicators so that each can determine where in the "stack" they should display.
//      2.  If a component indicator is enabled, it will first check the size of the array.  This will give us 
//          the "index" to use  for that component's display (taking advantage of the fact that array indexes 
//          start at 0, and an empty array will have a size of 0).  For example, if the array size is 0, the
//          component indicator will use the 0 index to display.
//      3.  If a component indicator is enabled, once it has identified which index it should use,
//          it will push a new entry into the array so that the next component indicator will get
//          the next index.
displayIndexArray = array.new_int(0)
getCurrentIndex() => array.size(displayIndexArray)
addToIndexArray() => array.push(displayIndexArray, na)
//
// I'm a bit of a stickler for knowing that I'm using true-price for indicators like the ones
// we will be including here, and the most consistent way to ensure that is the case, regardless of
// which chart-type people may use, is to manually grab the information directly via the 'security' function.
//
// Explicitly define our ticker to help ensure that we're always getting ACTUAL price instead of relying on the input
// ticker info and input vars (as they tend to inherit the type from what's displayed on the current chart)
realPriceTicker = ticker.new(prefix=syminfo.prefix, ticker=syminfo.ticker)
// And now to grab the OHLC and Volume...
[o, h, l, c, vol] = request.security(symbol=realPriceTicker, timeframe='', expression=[open, high, low, close, volume], gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)
//
// And a quick-and-dirty function to convert 'input.source' price to the equivilent version using our real-price vars...
convertSourcePrice(src) =>
    switch (src)
        open => o
        high => h
        low => l
        close => c
        hl2 => (h + l) / 2
        hlc3 => (h + l + c) / 3
        hlcc4 => (h + l + c + c) / 4
        ohlc4 => (o + h + l + c) / 4
        => c
//
// That should be all for the "shared" stuff...  Now we'll get into all the component indicators.
// Normally I like to try and structure scripts by grouping all inputs and such together, but
// given the nature of this indicator, it seems more logical to group each component indicator
// as a single, atomic unit as much as possible, so that's the pattern we'll use instead.
//
//*****  TSI ******//
//
// The TSI range is between -100 to +100 (range of 200) with a centerpoint of 0, so we will need
// to reposition the centerpoint to 100 (to make the range 0-200), then divide by 2 (which will
// reduce the scale to 0-100 and reposition the centerpoint to 50).
//
// Indicator inputs
g_TSISettings = 'TSI Settings'
tsiLong = input.int(title='Long-Preiod Length', defval=13, group=g_TSISettings, tooltip='Original default: 25')
tsiShort = input.int(title='Short-Period Length', defval=6, group=g_TSISettings, tooltip='Original default: 13')
tsiSignal = input.int(title='Signal Preiod Length', defval=4, group=g_TSISettings, tooltip='Original default: 13')
tsiPriceSource = input.source(title='Source for price', defval=close, group=g_TSISettings, tooltip='Source for Price information.  Default: Close')
tsiShowHist = input.bool(title='Show TSI Histogram', defval=true, group=g_TSISettings, tooltip='Show the TSI Histogram along the middle line.  Default value: checked')
tsiShowFill = input.bool(title='Show TSI colored fill', defval=true, group=g_TSISettings, tooltip='Fill the area between the TSI and the TSI Signal.  Default fill color coding:\n' + 
					  'Green: TSI is above the signal line\n' +
                      'Red: TSI is below the signal line.\n\n' + 
					  'Default value: checked')

tsiShowFloatingMA = input.bool(title='Show floating TSI Moving Average', defval=true, group=g_TSISettings, tooltip='Show a floating moving average line for the TSI value.  ')
tsiMAType = input.string(title='TSI Moving Average Calculation', group=g_TSISettings, options=["Simple", "Exponential", "Weighted", "Volume-Weigehted", "Smoothed", "RMA", "Linear", "Hull", "ALMA", "Double EMA", "Triple EMA"], defval='Exponential', tooltip='Type of moving average calculation to use (default is Exponential (EMA)). \n\n' +
                  'Only relevant when "Show floating TSI Moving Average" above is enabled.')
tsiMALength = input.int(title="TSI MA Length", defval=50, minval=1, step=1, group=g_TSISettings, tooltip="Number of historical TSI values to use for calculating the MA value.  Lower numbers will react more quickly, while higher numbers will better depict Longer trends.")
//
tsiShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_TSISettings,
             tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
tsiShowOBOS = input.bool(title='Show OB/OS   ', defval=true, group=g_TSISettings, inline="obos")
tsiOBLevel = input.int(title='    Offset: ', defval=25, minval=1, maxval=50, step=1, group=g_TSISettings, inline="obos", 
             tooltip='If enabled/checked, a OB and OS lines are displayed for this sub-indicator.\n\n' +
             'The offest parameter is defined as distance from the mid-line of the sub-indicator.  A value here of "25" would set the OB line at the equivilent of "75" (a +25 offset), ' +
             'and a OS line at the equivilent of "25" (a -25 offset).\n\n' +
             'Note: Before using this setting, please make sure you understand the native range of the indicator and account for the fact that all sub-indicators have been rescaled to a 100-point range and adjust accordingly.\n\n' +
             'See "General Display Settings - OB/OS Lines" settings above to alter the OB and OS line color and style.')
//
// Start with handling some of the precursor stuff...
tsiIndex = getCurrentIndex()
tsiPrice = convertSourcePrice(tsiPriceSource)
if (tsiDisplay)
    insertUpperLine(tsiIndex)
    insertTitle("TSI", tsiIndex)
    addToIndexArray()
//
// We're going to handle the rescaling of the TSI manually since it is a
// fixed-range indicator, so it can be accurately converted to the range
// we want with some basic math.
tsi_rawValue = 50 * (1 + ta.tsi(tsiPrice, tsiShort, tsiLong))
tsi_value = repositionSeries(tsi_rawValue, tsiIndex)
tsi_midpoint = getIndicatorMidpoint(tsiIndex)
tsiSignal_line = ta.ema(tsi_value, tsiSignal)
// For the mid-line, we need to know our original mid-point and our indicator index
tsiHist = tsi_midpoint + (tsi_value - tsiSignal_line)
 // Identify if TSI is above or below tsiSignal line
tsiBullish = tsi_value > tsiSignal_line
tsiBearish = tsi_value < tsiSignal_line
//
// Plot the histogram first so it will be layered behind the TSI and TSI Signal lines
// 
// Since the normal base of a plot with an area style (the norm for histograms) defaults to a fill from the 0 line, we need to do some work-arounds to reposition
// this to the repositioned TSI mid-line.  To do this, we'll create 2 hidden plot objects to house the TSI midpoint and the calculated histogram value offset from the midpoint,
// then use those 2 hidden plots with the 'fill' function to make our own "repositioned histogram".
p_hiddenTSIMidLine = plot(tsiShowHist and tsiDisplay ? tsi_midpoint : na, title='TSI Hidden Midpoint', color=color.rgb(0, 0, 0, 100), editable=false, display=display.none)
p_hiddenTSIHist = plot(tsiShowHist and tsiDisplay ? tsiHist : na, title='TSI Hidden Hist Line', color=color.rgb(0, 0, 0, 100), editable=false, display=display.none)
fill(p_hiddenTSIMidLine, p_hiddenTSIHist, title='TSI Histogram', color=tsiShowHist and tsiDisplay ? color.rgb(207, 80, 207, 70) : na)
//
// Define the colors for the TSI so we can re-use them in the fill zone as well...
tsiBullColor = color.rgb(0, 255, 0, 0)
tsiBearColor = color.rgb(255, 0, 0, 0)
//
// Plot the midline and OB/OS lines
if (tsiDisplay)
    insertInnerLines(tsi_midpoint, tsiOBLevel, tsiShowMidline, tsiShowOBOS)
// Plot the TSI and TSI Signal lines
p_TSISignal = plot(tsiDisplay ? tsiSignal_line : na, title='TSI Signal Line', color=color.rgb(0, 127, 255, 0))
p_TSI = plot(tsiDisplay ? tsi_value : na, title='TSI', color=tsiBullish ? tsiBullColor : tsiBearish ? tsiBearColor : color.rgb(127, 127, 127, 0), display=display.all)
//
// TSI MA fun
tsiFloatingMA = ma(tsi_value, tsiMALength, tsiMAType)
p_TSIFloatingMA = plot(tsiShowFloatingMA and tsiDisplay ? tsiFloatingMA : na, title="TSI Floating MA", color=color.rgb(255, 255, 0, 0))
//
// An added bonus - area fill between the TSI and the signal line, shaded to 
// indicate whether the TSI is above or below the signal line...
//
// Similar to the histogram, plot this first so that the lines end up on top of it...
tsiFillColor = tsiBullish ? color.new(tsiBullColor, 63) : tsiBearish ? color.new(tsiBearColor, 63) : na
fill(p_TSI, p_TSISignal, color=tsiShowFill and tsiDisplay ? tsiFillColor : na, title='TSI Fill Color')
//
//
//*****  END TSI  ******//
//
//
//*****  BEGIN Twin RSI  ******//
//
//  The RSI range is between 0 and 100 with a mid-point of 50 naturally, so no rescaling is needed.
//  We will still need to handle the repositioning.
//
// Indicator inputs
g_RSISettings = "Twin RSI Settings"
//
// Fast RSI
rsiShowFast = input.bool(title='Show Fast RSI', defval=true, group=g_RSISettings, tooltip='If checked, will include the Fast RSI in the composite indicator.  Default value: checked')
rsiFastLengthInput = input.int(title="Fast RSI Length", minval=1, maxval=100, defval=4, step=1, group=g_RSISettings, tooltip='Select the lenght for the fast RSI.  Lower values will respond quicker, but may lead ' +
         'to false signals.  The sweet-spot generally tends to be a bit less than 50% (half) of the "Slow RSI Length" below')
rsiFastSourceInput = input.source(title="Fast RSI Price Source", defval=ohlc4, group=g_RSISettings, tooltip='Price source for the fast RSI.  ' +
         'The most common setting for this short of a RSI length is OHLC4 (default), as this tends be a better representation of price action/momentum for shorter RSI lengths.')
// Slow RSI
rsiSlowLengthInput = input.int(title="Slow RSI Length", minval=1, defval=10, step=1, group=g_RSISettings, tooltip='Select the lenght for the slow RSI.  Lower values will respond quicker, but may lead ' +
         'to false signals.  The sweet-spot generally tends to be between 8-20 for scalping-style strats, and and in the 50-100 range for swing-trading style strats.')
rsiSlowSourceInput = input.source(title="Slow RSI Price Source", defval=close, group=g_RSISettings, tooltip='Price source for the slow RSI.  ' +
         'The most common setting for this length is close (default) as there is generally a larger sample size of price points to analyze.')
rsiShowFill = input.bool(title='Show Twin RSI colored fill', defval=true, group=g_RSISettings, tooltip='Fill the area between the Fast and Slow RSI.  Default fill color coding:\n' + 
					  'Dark Red: Fast RSI < Slow RSI & Slow RSI < RSI MA\n' + 
                      'Light Red: Fast RSI < Slow RSI & Slow RSI > RSI MA\n' + 
                      'Dark Green: Fast RSI > Slow RSI & Slow RSI < RSI MA\n' + 
                      'Light Green: Fast RSI > Slow RSI & Slow RSI > RSI MA\n\n' + 
					  'Default value: checked')
//
// RSI Midline and OB/OS settings
rsiShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_RSISettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
rsiShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_RSISettings, inline="obos")
rsiOBLevel = input.int(title='Offset', defval=25, minval=1, maxval=50, step=1, group=g_RSISettings, inline="obos", 
			 tooltip='If enabled/checked, a OB and OS lines are displayed for this sub-indicator.\n\n' +
             'The offest parameter is defined as distance from the mid-line of the sub-indicator (slow RSI specifically in this case).  A value here of "25" would set the OB line at the equivilent of "75" (a +25 offset), ' +
             'and a OS line at the equivilent of "25" (a -25 offset).\n\n' +
             'Note: Before using this setting, please make sure you understand the native range of the indicator and account for the fact that all sub-indicators have been rescaled to a 100-point range and adjust accordingly.\n\n' +
             'See "General Display Settings - OB/OS Lines" settings above to alter the OB and OS line color and style.')
//
// MA settings
g_RSIMASettings = "RSI MA Settings"
rsiMATypeInput = input.string(title="MA Type", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], defval="Simple", group=g_RSIMASettings, tooltip='Moving Average calculation method to use as a bias/reversal filter.\n\n' +
         'This MA is calculated on the Slow RSI.')
rsiMALengthInput = input.int(title="MA Length", defval=30, minval=1, step=1, group=g_RSIMASettings, tooltip='MA length.  This is used to help define a MA filter for the slow RSI.  Sweet spot tends to be 2-3x more than the RSI Slow Length.\n\n' +
         'This MA is calculated on the Slow RSI.')
//
// Start with handling some of the precursor stuff...
rsiIndex = getCurrentIndex()
rsiSlowPrice = convertSourcePrice(rsiSlowSourceInput)
rsiFastPrice = convertSourcePrice(rsiFastSourceInput)
if (rsiDisplay)
    insertUpperLine(rsiIndex)
    insertTitle("Twin RSI", rsiIndex)
    addToIndexArray()
//
rsiMidpoint = getIndicatorMidpoint(rsiIndex)
// Fast RSI
//
fastRSIRawValue = ta.rsi(rsiFastPrice, rsiFastLengthInput)
fastRSI = repositionSeries(fastRSIRawValue, rsiIndex)
fastRSIMA = ma(fastRSI, rsiMALengthInput, rsiMATypeInput)
//
// Slow RSI
slowRSIRawValue = ta.rsi(rsiSlowPrice, rsiSlowLengthInput)
slowRSI = repositionSeries(slowRSIRawValue, rsiIndex)
slowRSIMA = ma(slowRSI, rsiMALengthInput, rsiMATypeInput)
// Define some colors...
rsiColorBullish = color.rgb(0, 255, 0, 25)
rsiColorBearish = color.rgb(255, 0, 0, 25)
getRSIFillColor(fastRSI, slowRSI, rsiMA) =>
    retColor = color.rgb(0, 0, 0, 100)
    slowRSIBelowMA = slowRSI < rsiMA ? true : false
    // Fast RSI > Slow RSI will be green.
    if (rsiShowFast and rsiShowFill)
        if (fastRSI >= slowRSI)
            retColor := slowRSIBelowMA ? color.rgb(0, 127, 0, 0) : color.rgb(0, 255, 0, 0)
        else
            retColor := slowRSIBelowMA ? color.rgb(255, 0, 0, 0) : color.rgb(255, 127, 127, 0)
    retColor
// Plot the midline and OB/OS lines
if (rsiDisplay)
    insertInnerLines(rsiMidpoint, rsiOBLevel, rsiShowMidline, rsiShowOBOS)
p_FastRSI = plot(rsiDisplay and rsiShowFast ? fastRSI : na, title="Fast RSI", color=color.rgb(128, 128, 255, 0))
p_SlowRSI = plot(rsiDisplay ? slowRSI : na, title="Slow RSI", color=color.rgb(0, 0, 255, 0))
fill(p_FastRSI, p_SlowRSI, color=getRSIFillColor(fastRSI, slowRSI, slowRSIMA), title="Fast/Slow Twin RSI Fill")
plot(rsiDisplay ? slowRSIMA : na, "RSI-based MA", color=color.rgb(255, 255, 0, 35))
//
//
//*****  END Twin RSI  ******//
//
//
//*****  BEGIN Stoch RSI  ******//
// 
//  The Stoch RSI range is between 0 and 100 with a mid-point of 50 naturally, so no rescaling is needed.
//  We will still need to handle the repositioning.
//
// Indicator inputs
g_StochRSISettings = "Stochastic RSI Settings"
//
stochRSILengthInput = input.int(title="RSI Length", minval=1, maxval=100, defval=14, step=1, group=g_StochRSISettings, tooltip='This setting controls the length to use for calculating the RSI, which is the base for the Stochastic RSI.')
stochRSISourceInput = input.source(title="Price Source", defval=close, group=g_StochRSISettings, tooltip='Price source for the calculating the RSI.  ' +
         'The most common setting for this is close (default).')
stochRSIStochLengthInput = input.int(title="Stochastic Length", minval=1, maxval=100, defval=14, step=1, group=g_StochRSISettings, tooltip='This setting controls the length to use when calculating the raw stochastic value of the RSI.')

stochRSIKLength = input.int(title="K Length", minval=1, maxval=100, defval=3, step=1, group=g_StochRSISettings, inline='sr1')
stochRSIDLength = input.int(title="D Length", minval=1, maxval=100, defval=3, step=1, group=g_StochRSISettings, inline='sr1', tooltip='The K length is the lenght to use for the MA calculation of the "fast" (K) line.\n\n' +
         'The D lenght is the length to use for the MA of the K line for the "slow" (D) line.')
//
// Stoch RSI Midline and OB/OS settings
stochRSIShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_StochRSISettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
stochRSIShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_StochRSISettings, inline="obos")
stochRSIOBLevel = input.int(title='Offset', defval=30, minval=1, maxval=50, step=1, group=g_StochRSISettings, inline="obos",
			 tooltip='If enabled/checked, a OB and OS lines are displayed for this sub-indicator.\n\n' +
             'The offest parameter is defined as distance from the mid-line of the sub-indicator.  A value here of "25" would set the OB line at the equivilent of "75" (a +25 offset), ' +
             'and a OS line at the equivilent of "25" (a -25 offset).\n\n' +
             'Note: Before using this setting, please make sure you understand the native range of the indicator and account for the fact that all sub-indicators have been rescaled to a 100-point range and adjust accordingly.\n\n' +
             'See "General Display Settings - OB/OS Lines" settings above to alter the OB and OS line color and style.')
//
// Start with handling some of the precursor stuff...
stochRSIIndex = getCurrentIndex()
stochRSIPrice = convertSourcePrice(stochRSISourceInput)
if (stochRSIDisplay)
    insertUpperLine(stochRSIIndex)
    insertTitle("Stoch RSI", stochRSIIndex)
    addToIndexArray()
//
stochRSIMidpoint = getIndicatorMidpoint(stochRSIIndex)
//
stochRSI_RawRSI = ta.rsi(stochRSIPrice, stochRSILengthInput)
stochRSI_k = repositionSeries(ta.sma(ta.stoch(stochRSI_RawRSI, stochRSI_RawRSI, stochRSI_RawRSI, stochRSIStochLengthInput), stochRSIKLength), stochRSIIndex)
stochRSI_d = ta.sma(stochRSI_k, stochRSIDLength)
// Plot the midline and OB/OS lines
if (stochRSIDisplay)
    insertInnerLines(stochRSIMidpoint, stochRSIOBLevel, stochRSIShowMidline, stochRSIShowOBOS)
//
plot(stochRSIDisplay ? stochRSI_k : na, "Stoch RSI K", color=color.rgb(41, 98, 255, 0))
plot(stochRSIDisplay ? stochRSI_d : na, "Stoch RSI D", color=color.rgb(255, 109, 0, 0))
//
//
//*****  END Stoch RSI  ******//
//
//
//*****  BEGIN OG Stochastic  ******//
//
// OG Stochastic range is between 0 and 100 with a mid-point of 50 naturally, so no rescaling is needed.
// We will still need to handle the repositioning.
//
// Indicator inputs
g_StochSettings = "Stochastic Settings"
//
//stochLengthInput = input.int(title="Stochastic Length", minval=1, maxval=100, defval=14, step=1, group=g_StochSettings, tooltip='')
stochStochLengthInput = input.int(title="Stochastic Length", minval=1, maxval=100, defval=14, step=1, group=g_StochSettings, tooltip='This setting controls the length to use when calculating the raw price stochastic')
stochKLength = input.int(title="K Length", minval=1, maxval=100, defval=1, step=1, group=g_StochSettings, inline='sr1')
stochDLength = input.int(title="D Length", minval=1, maxval=100, defval=3, step=1, group=g_StochSettings, inline='sr1', tooltip='The K length is the lenght to use for the MA calculation of the "fast" (K) line.\n\n' +
         'The D lenght is the length to use for the MA of the K line for the "slow" (D) line.')
//
// Stochastic Midline and OB/OS settings
stochShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_StochSettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
stochShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_StochSettings, inline="obos")
stochOBLevel = input.int(title='Offset', defval=25, minval=1, maxval=50, step=1, group=g_StochSettings, inline="obos",  
			 tooltip='If enabled/checked, a OB and OS lines are displayed for this sub-indicator.\n\n' +
             'The offest parameter is defined as distance from the mid-line of the sub-indicator.  A value here of "25" would set the OB line at the equivilent of "75" (a +25 offset), ' +
             'and a OS line at the equivilent of "25" (a -25 offset).\n\n' +
             'Note: Before using this setting, please make sure you understand the native range of the indicator and account for the fact that all sub-indicators have been rescaled to a 100-point range and adjust accordingly.\n\n' +
             'See "General Display Settings - OB/OS Lines" settings above to alter the OB and OS line color and style.')
//
// Start with handling some of the precursor stuff...
stochIndex = getCurrentIndex()
if (stochDisplay)
    insertUpperLine(stochIndex)
    insertTitle("Stoch", stochIndex)
    addToIndexArray()
//
stochMidpoint = getIndicatorMidpoint(stochIndex)
//
stoch_k = repositionSeries(ta.sma(ta.stoch(c, h, l, stochStochLengthInput), stochKLength), stochIndex)
stoch_d = ta.sma(stoch_k, stochDLength)
// Plot the midline and OB/OS lines
if (stochDisplay)
    insertInnerLines(stochMidpoint, stochOBLevel, stochShowMidline, stochShowOBOS)
//
plot(stochDisplay ? stoch_k : na, "Stochastic K", color=color.rgb(41, 98, 255, 0))
plot(stochDisplay ? stoch_d : na, "Stochastic D", color=color.rgb(255, 109, 0, 0))
//
//
//*****  END OG Stochastic  ******//
//
//
//*****  BEGIN UO  ******//
//
// UO range is between 0 and 100 with a mid-point of 50 naturally, so no rescaling is needed.
// We will still need to handle the repositioning.
//
// There are three steps to calculating the Ultimate Oscillator. This example uses 7, 14, 28 parameters:
// 1. Before calculating the Ultimate Oscillator, two variable need to be defined; Buying Pressure and True Range.
//
// Buying Pressure (BP) = Close - Minimum (Lowest between Current Low or Previous Close)
// True Range (TR) = Maximum (Highest between Current High or Previous Close) - Minimum (Lowest between Current Low or Previous Close)
// 2. The Ultimate Oscillator then uses these figures over three time periods:
//
// Average7 = (7 Period BP Sum) / (7 Period TR Sum)
// Average14 = (14 Period BP Sum) / (14 Period TR Sum)
// Average28 = (28 Period BP Sum) / (28 Period TR Sum)
// 3. The final Ultimate Oscillator calculations can now be made:
//
// UO = 100 x [(4 x Average7)+(2 x Average14)+Average28]/(4+2+1)
// Indicator inputs
g_UOSettings = "Ultimate Oscillator Settings"
//
uoLength1 = input.int(title="Fast Length", minval=1, maxval=100, defval=7, step=1, group=g_UOSettings, tooltip='Length to use for the fast calculation of BP/TR')
uoLength2 = input.int(title="Middle Length", minval=1, maxval=100, defval=14, step=1, group=g_UOSettings, tooltip='Length to use for the middle calculation of BP/TR')
uoLength3 = input.int(title="Slow Length", minval=1, maxval=100, defval=28, step=1, group=g_UOSettings, tooltip='Length to use for the slow calculation of BP/TR')
//
// UO Midline and OB/OS settings
uoShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_UOSettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
uoShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_UOSettings, inline="obos")
uoOBLevel = input.int(title='Offset', defval=25, minval=1, maxval=50, step=1, group=g_UOSettings, inline="obos",  
			 tooltip='If enabled/checked, a OB and OS lines are displayed for this sub-indicator.\n\n' +
             'The offest parameter is defined as distance from the mid-line of the sub-indicator.  A value here of "25" would set the OB line at the equivilent of "75" (a +25 offset), ' +
             'and a OS line at the equivilent of "25" (a -25 offset).\n\n' +
             'Note: Before using this setting, please make sure you understand the native range of the indicator and account for the fact that all sub-indicators have been rescaled to a 100-point range and adjust accordingly.\n\n' +
             'See "General Display Settings - OB/OS Lines" settings above to alter the OB and OS line color and style.')
//
// MA settings
g_UOMASettings = "UO MA Settings"
uoIncludeMA = input.bool(title='Include Moving Average', defval=true, group=g_UOMASettings, tooltip='If checked, will include a Moving Average of the UO.  Default value: checked')
uoMATypeInput = input.string(title="MA Type", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], defval="Simple", group=g_UOMASettings, 
         tooltip='Moving Average calculation method to use as a bias/reversal filter.  Only applies if "Include Moving Average" above is checked/enabled.')
uoMALengthInput = input.int(title="MA Length", defval=30, minval=1, step=1, group=g_UOMASettings, tooltip='Number of historical UO values to use for calculating the MA value.  Lower numbers will react more quickly, while higher numbers will better depict Longer trends.')
uoShowFill = input.bool(title='Include UO/MA Fill', defval=false, group=g_UOMASettings, tooltip='If checked, will include a color-coded fill between the UO and the UO MA.  Default value: unchecked')
//
// Start with handling some of the precursor stuff...
uoIndex = getCurrentIndex()
if (uoDisplay)
    insertUpperLine(uoIndex)
    insertTitle("UO", uoIndex)
    addToIndexArray()
//
uoMidpoint = getIndicatorMidpoint(uoIndex)
//
// Some functions specific to the UO...
//
// Buying Pressure (BP) = Close - Minimum (Lowest between Current Low or Previous Close)
getBuyingPressure() =>
	ret = c - math.min(l, c[1])
	ret
//
// True Range (TR) = Maximum (Highest between Current High or Previous Close) - Minimum (Lowest between Current Low or Previous Close)
getTrueRange() =>
	ret = math.max(h, c[1]) - math.min(l, c[1])
	ret
//
// UO Stub Average = (X-length BP Sum) / (X-length TR Sum)
getUOAverageStub(length) =>
	ret = 0.0
	uoBPSum = math.sum(getBuyingPressure(), length)
	uoTRSum = math.sum(getTrueRange(), length)
	ret := uoBPSum / uoTRSum
	ret
//
// UO = 100 x [(4 x Average7)+(2 x Average14)+Average28]/(4+2+1)
getUOAverage(lenShort, lenMedium, lenLong) =>
	ret = 0.0
	uoShortAvg = getUOAverageStub(lenShort)
	uoMediumAvg = getUOAverageStub(lenMedium)
	uoLongAvg = getUOAverageStub(lenLong)
	ret := 100 * (((4 * uoShortAvg) + (2 * uoMediumAvg) + (1 * uoLongAvg)) / (4 + 2 + 1))
	ret
//
// Now to get the UO and handle the repositioning...
uoOut = repositionSeries(getUOAverage(uoLength1, uoLength2, uoLength3), uoIndex)
uoMA = ma(uoOut, uoMALengthInput, uoMATypeInput)
// Plot the midline and OB/OS lines
if (uoDisplay)
    insertInnerLines(uoMidpoint, uoOBLevel, uoShowMidline, uoShowOBOS)
//
p_UO = plot(uoDisplay ? uoOut : na, title="UO", color=color.rgb(0, 255, 255, 35))
p_UOMA = plot(uoDisplay and uoIncludeMA ? uoMA : na, title="UO MA", color=color.rgb(255, 255, 0, 0))
getUOFillColor(uo, uoMA) =>
    retColor = color.rgb(0, 0, 0, 100)
    if (uoShowFill)
        if (uo >= uoMA)
            retColor := color.rgb(0, 255, 0, 0)
        else
            retColor := color.rgb(255, 0, 0, 0)
    retColor
fill(p_UO, p_UOMA, color=getUOFillColor(uoOut, uoMA), title="Ultimate Oscillator Fill")
//
//
//*****  END UO  ******//
//
//
//*****  BEGIN AO  ******//
//
// Range is technically infinite (unbounded), and actual values will be based on security price,
// so we will need to rescale this indicator.  The mid-line is at 0 (which is common for unbounded)
// oscillators like this), so we need to keep that in mind as well.  
//
// Indicator inputs
g_AOSettings = "Awesome Oscillator Settings"
//
aoScalingMethod = input.string(title="Rescaling Method", defval="Standardization", options=["Standardization","Normalization"], group=g_AOSettings, tooltip='This setting controls how the indicator rescaling is handled.\r\r' +
			 "Since the AO (like the MACD) isn't range-bound by default, we need to apply some transformation math to get it to fit in our 'indicator stack' slot.  Taking a page from 'Feature Scaling' commonly used in " +
			 "Machine Learning, we have 2 methods we can use to achieve this:\n\n" +
			 "Standardization (default): Applies an algorithm that will result in a data series with a mean of 0 and standard deviation of 1.  While this still TECHNICALLY still results in a infinte range, we can use " +
			 "the fixed standard deviation size and that it will have a shape very similar to a normal distribution (perhaps with some skew based on trend direction and strength, and some shifts in kurtosis ('tail size') based on recent volatility)." +
			 "This method has the benefit of retaining an accurate/true mid-line.\n\n" +
			 "Normalization: Applies an algorithm that will result in a data series with a fixed-range between 0 and 1.  While this method will retain all the original behavior of the oscillator itself, the act of forcing the data into a fixed-range " +
			 "in this manner does have the side-effect of often resulting in the pre-rescaled mid-line being shifted away from the true middle as well.  This makes this method of rescaling fine for techniques like divergence analysis, but would effectively " +
			 "invalidate any techniques that rely on an accurate mid-line (e.g. mid-line crosses or the use of Overbought/Oversold levels).")
aoStandardizationFactor = input.float(title="Standardization Scaling Factor", minval=0.1, maxval=25, defval=3.5, step=0.1, group=g_AOSettings, tooltip='This setting equates to the number of standard deviations we want to use for scaling data output from the ' +
			 "'Standardization' rescaling method.\n\n" +
			 "Since the output of the 'Standardization' scaling is defined as a mean of 0 and standard deviation of 1, the 'normal' resulting range would generally be around a range of 10 (from -5 to +5), which is much narrower than our desired range of 100. " +
			 "To help scale up closer to our desired range of 100, we apply a multiplier based on the number of standard deviations we are from the mean (mid-point) of 0.\n\n" +
			 "If you find the component indicator is 'spilling over' the lines defnining it's range, increasing this scaling factor will result in 'shrinking' the plots back into the intended range.  Conversely, if you feel it's too 'cramped', you can decrease " +
			 "this scaling factor to expand the plots to take up more of the allocated range space.\n\n" +
			 "Most of the time, a scaling factor between 2.5 and 5 is sufficient to 'fill the space' without 'spilling over' into other components.")
aoSource = input.source(title="Price Source", defval=hl2, group=g_AOSettings, tooltip='The price source to use for the MA calculation used by the AO.  The most common setting for this is hl2 (default).')
aoLength1 = input.int(title="Short Length", minval=1, maxval=100, defval=5, step=1, group=g_AOSettings, tooltip='This controls the length for the fast MA used for the AO calculation')
aoLength2 = input.int(title="Long Length", minval=1, maxval=100, defval=34, step=1, group=g_AOSettings, tooltip='This controls the length for the slow MA used for the AO calculation')
aoShowAsHist = input.bool(title='Display AO in Histogram style', defval=true, group=g_AOSettings, tooltip='If checked, will plot the AO in a Histogram-style plot.  Default value: checked')
//
// AO Midline and OB/OS settings
aoShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_AOSettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
// As a traditionally unbound indicator, the AO doesn't have a traditional concept of fixed OB/OS levels
//aoShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_AOSettings, inline="obos")
aoShowOBOS = false
//aoOBLevel = input.int(title='Offset', defval=25, minval=1, maxval=50, step=1, group=g_AOSettings, inline="obos", tooltip='')
aoOBLevel = 0

//
// MA settings
g_AOMASettings = "AO MA Settings"
aoIncludeMA = input.bool(title='Include Moving Average', defval=false, group=g_AOMASettings, tooltip='If checked, will include a Moving Average of the AO itself.  Default value: checked')
aoMATypeInput = input.string(title="MA Type", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], defval="Simple", group=g_AOMASettings, 
         tooltip='Moving Average calculation method to use as a bias/reversal filter.  Only applies if "Include Moving Average" above is checked/enabled.')
aoMALengthInput = input.int(title="MA Length", defval=30, minval=1, step=1, group=g_AOMASettings, tooltip='Number of historical AO values to use for calculating the MA value.  Lower numbers will react more quickly, while higher numbers will better depict Longer trends.')
aoShowFill = input.bool(title='Include AO/MA Fill', defval=true, group=g_AOMASettings, tooltip='If checked, will include a color-coded fill between the AO and the AO MA.  Default value: checked')
//
// Start with handling some of the precursor stuff...
aoIndex = getCurrentIndex()
if (aoDisplay)
    insertUpperLine(aoIndex)
    insertTitle("AO", aoIndex)
    addToIndexArray()
//
aoPrice = convertSourcePrice(aoSource)
aoMidpoint = getIndicatorMidpoint(aoIndex)
aoShort = ma(aoPrice, aoLength1, "Simple")
aoLong = ma(aoPrice, aoLength2, "Simple")
//
aoOutUnscaled = (aoShort - aoLong)
aoOutScaled = switch aoScalingMethod
	"Standardization" => rescaleStandardizedData(standardize(aoOutUnscaled, 4850), aoStandardizationFactor)
	"Normalization" => rescaleNormalizedData(normalize(aoOutUnscaled, 4850))
aoOutRepositioned = repositionSeries(aoOutScaled, aoIndex)

aoMA = ma(aoOutRepositioned, aoMALengthInput, aoMATypeInput)
// Plot the midline and OB/OS lines
if (aoDisplay)
    insertInnerLines(aoMidpoint, aoOBLevel, aoShowMidline, aoShowOBOS)
//
getAOFillColor(ao, aoRef) =>
    retColor = color.rgb(0, 0, 0, 100)
    if (ao >= aoRef)
        retColor := color.rgb(0, 255, 0, 0)
    else
        retColor := color.rgb(255, 0, 0, 0)
    retColor
//
plotcandle(aoMidpoint, math.max(aoMidpoint, aoOutRepositioned), math.min(aoMidpoint, aoOutRepositioned), aoOutRepositioned, title="AO Pseudo-Histogram", color=getAOFillColor(aoOutUnscaled, aoOutUnscaled[1]), bordercolor=getAOFillColor(aoOutUnscaled, aoOutUnscaled[1]), wickcolor=getAOFillColor(aoOutUnscaled, aoOutUnscaled[1]), display=(aoDisplay and aoShowAsHist ? display.all : display.none))
//
p_AO = plot(aoDisplay and not aoShowAsHist ? aoOutRepositioned : na, title="AO", color=color.rgb(0, 255, 255, 15))
p_AOMA = plot(aoDisplay and aoIncludeMA ? aoMA : na, title="AO MA", color=color.rgb(255, 255, 0, 0))
//
fill(p_AO, p_AOMA, color=(aoShowFill and not aoShowAsHist ? getAOFillColor(aoOutRepositioned, aoMA) : na), title="Awesome Oscillator Fill")
//
//
//*****  END AO  ******//
//
//
//*****  BEGIN MACD  ******//
//
// Range is technically infinite (unbounded), and actual values will be based on security price,
// so we will need to rescale this indicator.  The mid-line is at 0 (which is common for unbounded)
// oscillators like this), so we need to keep that in mind as well.  
//
// Indicator inputs
g_MACDSettings = "MACD Settings"
//
macdScalingMethod = input.string(title="Rescaling Method", defval="Standardization", options=["Standardization","Normalization"], group=g_MACDSettings, tooltip='This setting controls how the indicator rescaling is handled.\n\n' +
			 "Since the MACD (like the AO) isn't range-bound by default, we need to apply some transformation math to get it to fit in our 'indicator stack' slot.  Taking a page from 'Feature Scaling' commonly used in " +
			 "Machine Learning, we have 2 methods we can use to achieve this:\n\n" +
			 "Standardization (default): Applies an algorithm that will result in a data series with a mean of 0 and standard deviation of 1.  While this still TECHNICALLY still results in a infinte range, we can use " +
			 "the fixed standard deviation size and that it will have a shape very similar to a normal distribution (perhaps with some skew based on trend direction and strength, and some shifts in kurtosis ('tail size') based on recent volatility)." +
			 "This method has the benefit of retaining an accurate/true mid-line.\n\n" +
			 "Normalization: Applies an algorithm that will result in a data series with a fixed-range between 0 and 1.  While this method will retain all the original behavior of the oscillator itself, the act of forcing the data into a fixed-range " +
			 "in this manner does have the side-effect of often resulting in the pre-rescaled mid-line being shifted away from the true middle as well.  This makes this method of rescaling fine for techniques like divergence analysis, but would effectively " +
			 "invalidate any techniques that rely on an accurate mid-line (e.g. mid-line crosses or the use of Overbought/Oversold levels).")
macdStandardizationFactor = input.float(title="Standardization Scaling Factor", minval=0.1, maxval=25, defval=3.5, step=0.1, group=g_MACDSettings, tooltip='This setting equates to the number of standard deviations we want to use for scaling data output from the ' +
			 "'Standardization' rescaling method.\n\n" +
			 "Since the output of the 'Standardization' scaling is defined as a mean of 0 and standard deviation of 1, the 'normal' resulting range would generally be around a range of 10 (from -5 to +5), which is much narrower than our desired range of 100. " +
			 "To help scale up closer to our desired range of 100, we apply a multiplier based on the number of standard deviations we are from the mean (mid-point) of 0.\n\n" +
			 "If you find the component indicator is 'spilling over' the lines defnining it's range, increasing this scaling factor will result in 'shrinking' the plots back into the intended range.  Conversely, if you feel it's too 'cramped', you can decrease " +
			 "this scaling factor to expand the plots to take up more of the allocated range space.\n\n" +
			 "Most of the time, a scaling factor between 2.5 and 5 is sufficient to 'fill the space' without 'spilling over' into other components.")
macdSource = input.source(title="Price Source", defval=close, group=g_MACDSettings, tooltip='Price source to use for calculating the MA components of the MACD.  The most common setting for this is close (default).')
macdFastLength = input.int(title="Fast Length", minval=1, maxval=100, defval=12, step=1, group=g_MACDSettings, tooltip='This setting controls the length used for the fast MA component of the MACD')
macdSlowLength = input.int(title="Slow Length", minval=1, maxval=100, defval=26, step=1, group=g_MACDSettings, tooltip='This setting controls the length used for the slow MA component of the MACD')


macdBaseMATypeInput = input.string(title="MACD MA Type", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], defval="Exponential", group=g_MACDSettings, 
             tooltip='This setting controls the MA calculation method used for calculating the 2 MA components of the MACD')
macdSignalMATypeInput = input.string(title="MACD Signal MA Type", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], defval="Exponential", group=g_MACDSettings, 
             tooltip='This setting controls the MA calculation method used for calculating the MACD Signal line')
macdSignalLength = input.int(title="MACD Signal Smoothing", minval=1, maxval=50, defval=9, step=1, group=g_MACDSettings, tooltip='This setting controls the MA length when calculating the MACD Signal line')
//
// MACD Midline and OB/OS settings
macdShowMidline = input.bool(title='Show Mid Line', defval=true, group=g_MACDSettings, tooltip='If enabled/checked, a mid-line is displayed for this sub-indicator.\n\n' +
             'See "General Display Settings - Mid Lines" settings above to alter the mid-line color and style.')
// As a traditionally unbound indicator, the MACD doesn't have a traditional concept of fixed OB/OS levels
//macdShowOBOS = input.bool(title='Show OB/OS ', defval=true, group=g_MACDSettings, inline="obos")
macdShowOBOS = false
//macdOBLevel = input.int(title='Offset', defval=25, minval=1, maxval=50, step=1, group=g_MACDSettings, inline="obos", tooltip='')
macdOBLevel = 0
//
// Start with handling some of the precursor stuff...
macdIndex = getCurrentIndex()
if (macdDisplay)
    insertUpperLine(macdIndex)
    insertTitle("MACD", macdIndex)
    addToIndexArray()
//
macdPrice = convertSourcePrice(macdSource)
macdMidpoint = getIndicatorMidpoint(macdIndex)

macdFastMA = ma(macdPrice, macdFastLength, macdBaseMATypeInput)
macdSlowMA = ma(macdPrice, macdSlowLength, macdBaseMATypeInput)
macdUnscaled = (macdFastMA - macdSlowMA)

macdScaled = switch macdScalingMethod
	"Standardization" => rescaleStandardizedData(standardize(macdUnscaled, 4850), macdStandardizationFactor)
	"Normalization" => rescaleNormalizedData(normalize(macdUnscaled, 4850))
macdRepositioned = repositionSeries(macdScaled, macdIndex)

macdSignal = ma(macdRepositioned, macdSignalLength, macdSignalMATypeInput)

hist = macdMidpoint + (macdRepositioned - macdSignal)
// Plot the midline and OB/OS lines
if (macdDisplay)
    insertInnerLines(macdMidpoint, macdOBLevel, macdShowMidline, macdShowOBOS)
//
p_MACD = plot(macdDisplay ? macdRepositioned : na, title="MACD", color=color.rgb(0, 255, 255, 15))
p_MACDSignal = plot(macdDisplay ? macdSignal : na, title="MACD Signal", color=color.rgb(255, 255, 0, 0))
//
//
//*****  END MACD  ******//
//
//
//*****  BEGIN Outback RSI  ******//
//
g_ORSISettings = "Outback RSI Settings"
orsiLengthInput = input.int(title="RSI Length", defval=618, minval=1, group=g_ORSISettings)
orsiSourceInput = input.source(title="RSI Source", defval=close, group=g_ORSISettings)
orsiTrigger = input.int(title="RSI Trigger Level", defval=50, minval=1, maxval=99, group=g_ORSISettings, tooltip="RSI Trigger Level for safe entry")
orsiUseTrigger = input.bool(title="Display Safe Entries", defval=false, group=g_ORSISettings, tooltip="If activated, background coloring will differentiate between risk entry and safe entry (RSI above/below RSI trigger level")
orsiMATypeInput = input.string(title="MA Type", defval="Simple", options=["Simple", "Smoothed", "Exponential", "Double EMA", "Triple EMA", "RMA", "Weighted", "Volume-Weigehted", "Hull", "ALMA", "Linear"], group=g_ORSISettings)
orsiMALengthInput = input.int(title="MA Length", defval=200, group=g_ORSISettings)


orsiHullLength = input.int(title='MA Length', defval=227, group = g_ORSISettings)
orsiHullMult = input.float(title="Length multiplier", defval=2.0, step=0.1, minval=1.0, maxval=5.0, group = g_ORSISettings)

// Start with handling some of the precursor stuff...
orsiIndex = getCurrentIndex()
if (orsiDisplay)
    insertUpperLine(orsiIndex)
    insertTitle("O-RSI", orsiIndex)
    addToIndexArray()
//
orsiPrice = convertSourcePrice(orsiSourceInput)
orsiMidpoint = getIndicatorMidpoint(orsiIndex)

orsiRawValue = ta.rsi(orsiPrice, orsiLengthInput)
orsiRepositioned = repositionSeries(orsiRawValue, orsiIndex)
orsiTriggerReositioned = repositionSeries(orsiTrigger, orsiIndex)
orsiMA = ma(orsiRepositioned, orsiMALengthInput, orsiMATypeInput)

orsiHMALength = math.floor(orsiHullLength * orsiHullMult)
orsiHMA = ma(orsiRepositioned, orsiHMALength, "Hull")

// Will be used for "invisible plots" to allow us to use the 'fill()' function to apply our "background" color-coding
orsiUpperBound = orsiMidpoint + 49
orsiLowerBound = orsiMidpoint - 49
//
// And the "invisible plots"...
p_ORSIUpper = plot(orsiDisplay ? orsiUpperBound : na, color=color.rgb(0, 0, 0, 100), editable=false, display=display.none, title="ORSI Upper Bound")
p_ORSILower = plot(orsiDisplay ? orsiLowerBound : na, color=color.rgb(0, 0, 0, 100), editable=false, display=display.none, title="ORSI Lower Bound")

orsiRiskEntryLong = orsiUseTrigger ? (orsiRepositioned > orsiMA and orsiRepositioned > orsiHMA and orsiRepositioned < orsiTriggerReositioned ? true : false) : orsiRepositioned > orsiMA and orsiRepositioned > orsiHMA ? true : false
orsiEntryLong = orsiRepositioned > orsiMA and orsiRepositioned > orsiHMA and orsiRepositioned > orsiTriggerReositioned ? true : false

orsiRiskEntryShort = orsiUseTrigger ? (orsiRepositioned < orsiMA and orsiRepositioned < orsiHMA and orsiRepositioned > orsiTriggerReositioned ? true : false) : orsiRepositioned < orsiMA and orsiRepositioned < orsiHMA ? true : false
orsiEntryShort = orsiRepositioned < orsiMA and orsiRepositioned < orsiHMA and orsiRepositioned < orsiTriggerReositioned ? true : false

getORSIFillColor() =>
    ret = color.rgb(0, 0, 0, 100)
    if (orsiRiskEntryLong)
        ret := color.rgb(5, 102, 86, 50)
    if (orsiEntryLong and orsiTrigger)
        ret := color.rgb(0, 51, 42, 50)
    if (orsiRiskEntryShort)
        ret := color.rgb(255, 152, 0, 50)
    if (orsiEntryShort and orsiTrigger)
        ret := color.rgb(230, 81, 0, 50)
    ret

fill(p_ORSIUpper, p_ORSILower, color=orsiDisplay ? getORSIFillColor() : na, title="Outback RSI Entry Signals")

//
//
//*****  END Outback RSI  ******//
