Oscillator Edges
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © jarzido

//@version=5
indicator('Oscillator Edges', overlay=true)

src = input(title='Input', defval=close)

preset = input.string(title="Osc. Preset", defval="Stoch 1", options=["None", "Specified RSI", "Specified Stoch",  "Stoch 1", "Stoch 2"] )
showOsc = input.bool(title="Oscillator Mode",
     tooltip="Check this if you want to show the oscillator. Uncheck this if you prefer to use the indicator on the main chart with moving averages. " +
      "To use this, be sure to disable all the moving averages 1-6.", defval=false)
length = input.int(40, title="RSI Length", minval=1)
smoothing = input.int(title="RSI Smoothing", defval=1, minval=1)
lag = input.int(title="Lag", tooltip="For plot backshadow", defval=3, minval=1)
maColorChange = input.bool(title="MA Osc Color", tooltip="Use Oscillator color change for MA Colors", defval=true)


//High Inputs
osc_max = input.int(title="Osc. Max", inline="maxVal",
     tooltip="Maximum value of the oscillator. Lower this if you want color to change more drastically/sooner. Otherwise leave alone unless you're using one not built-in.",
     defval=100)
osc_high = input(title='Osc. High', inline="high", tooltip="Above this value will trigger alerts/shapes", defval=90)
shC = input(title='', inline="maxVal", defval=#FD1717)
hC = input(title='', inline="high", defval=#FFEE00)

//Low Inputs
osc_low = input(title='Osc. Low',inline="lowest", tooltip="Below this value will trigger alerts/shapes", defval=15)
osc_min = input.int(title="Osc. Min", inline="low",
     tooltip="Minimum value of the oscillator. Raise this if you want color to change more drastically/sooner. Otherwise leave alone unless you're using one not built-in.",
     defval=0)
lC = input(title='', inline="lowest", defval=#003193)
slC = input(title='', inline="low", defval=#00FF0E)

//Background fill color
bfC = input.color(title="Band Fill Color",
     tooltip = "Color to fill in the band when used in oscillator mode. Also used to fill in the neutral spaces between moving averages when not in oscillator mode.",
     defval=color.rgb(126, 87, 194, 82))
lowerBandLvl = input.float(title="Band Oscillator Levels", inline="band", tooltip="Band High/low levels for when you're in oscillator mode. Also " +
     "used for triggering bear/bull signals.", defval = 30)
upperBandLvl = input.float(title="", inline="band", defval = 70)

     
//Neutral Fill color
nSymbolColor = input.color(title="Neutral Symbol Color", tooltip="The neutral color for symbols when the oscillator is between values, should you choose to use it. Enable visibility for these in the 'Styles' panel.",
     defval=color.new(color.gray, 50))

//Minimum and maximum transparency for the fill gradient and oscillator
oscShadow= input.int(title="Osc Shadow transparency", defval=50)
mint = input.int(title="Gradient Min Transparency", defval=75)
maxt = input.int(title="Gradient Max Transparency", defval=92)
qG = input.int(title="Gradient Smoothing",
     tooltip="An EMA is applied to the oscillator value to smoothen out transitions. This is the EMA lookback. Lower value=faster, choppier color transitions. Higher Value = smoother, less subtle transitions.", minval=1, defval=3)


//Most of this came out of the built in ribbon from Tradingview. Their built in scripts save so much time and effort, I'm truly thankful they believe in open sourcing their code. <3 
ma1_length = input.int(9      , ""     , inline="MA #1", minval=1)
ma2_length = input.int(14     , ""     , inline="MA #2", minval=1)
ma3_length = input.int(21     , ""     , inline="MA #3", minval=1)
ma4_length = input.int(50     , ""     , inline="MA #4", minval=1)
ma5_length = input.int(100    , ""     , inline="MA #5", minval=1)
ma6_length = input.int(200    , ""     , inline="MA #6", minval=1)

show_ma1   = input(true   , "MA №1", inline="MA #1")
show_ma2   = input(true   , "MA №2", inline="MA #2")
show_ma3   = input(true   , "MA №3", inline="MA #3")
show_ma4   = input(true   , "MA №4", inline="MA #4")
show_ma5   = input(true   , "MA №5", inline="MA #5")
show_ma6   = input(true   , "MA №6", inline="MA #6")


ma1_type   = input.string("EMA"  , ""     , inline="MA #1", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])
ma2_type   = input.string("EMA"  , ""     , inline="MA #2", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])
ma3_type   = input.string("EMA"  , ""     , inline="MA #3", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])
ma4_type   = input.string("EMA"  , ""     , inline="MA #4", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])
ma5_type   = input.string("EMA"  , ""     , inline="MA #5", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])
ma6_type   = input.string("EMA"  , ""     , inline="MA #6", options=["SMA", "EMA", "RMA", "WMA", "VWMA", "HMA"])

ma1_color  = input(color.new(#FFFFFF, 95), ""     , inline="MA #1")
ma2_color  = input(color.new(#FFFFFF, 80), ""     , inline="MA #2")
ma3_color  = input(color.new(#FFFFFF, 65), ""     , inline="MA #3")
ma4_color  = input(color.new(#FFFFFF, 50), ""     , inline="MA #4")
ma5_color  = input(color.new(#FFFFFF, 35), ""     , inline="MA #5")
ma6_color  = input(color.new(#FFFFFF, 20), ""     , inline="MA #6")


//-------------------------------------------Functions
//built-in preset
bips(type) =>
    type == "None" ? src : 
     type == "Specified RSI" ? ta.sma(ta.rsi(src, length), smoothing) :
     type == "Specified Stoch" ? ta.sma(ta.stoch(close, high, low, length), smoothing) :
     type == "Stoch 1" ? ta.sma(ta.stoch(close, high, low, 40), 2) :
     type == "Stoch 2" ? ta.sma(ta.stoch(close, high, low, 81), 2) :
     src


ocC(n) =>
    color = color.white
    if n >= osc_high
        color.from_gradient(n, osc_high, osc_max, hC, shC)
    else if n <= osc_low
        color.from_gradient(n, osc_min, osc_low, lC, slC)
    else
        color.white
        
        
osc=bips(preset)

val = osc[0]  //I'm lazy, but this could help depending on the language/compiler. I'm not sure in terms of Pine.

cc = ocC(val)

oH = val >= osc_high
oL = val <= osc_low
lbC = showOsc? #787B86 : na
sbgc = showOsc? bfC : na
midBandLvl =((upperBandLvl - lowerBandLvl)/2) + lowerBandLvl
rsiUpperBand = plot(showOsc? upperBandLvl : na, "OSC Upper Band", color=lbC)
rsiLowerBand = plot(showOsc? lowerBandLvl : na, "OSC Lower Band", color=lbC)
m = plot(showOsc? midBandLvl : na, "OSC Middle Band", color=lbC )
t = plot(showOsc? osc_high : na, "OSC Edge Band", color=showOsc? color.new(hC, 10) : na, style=plot.style_circles)
b = plot(showOsc? osc_low: na, "OSC Edge Band", color=showOsc? color.new(lC, 10): na, style=plot.style_circles)
fill(rsiUpperBand, rsiLowerBand, color=bfC, title="OSC Background Fill")


pc = (oL or oH) ? cc : color.new(#FFFFFF, 20)
bc = color.new(pc, 90)

p1 = plot(showOsc ? osc : na, title="Oscillator", color=pc )
p2 = plot(showOsc ? ta.ema(osc, lag) : na, title="Osc Lag", color=color.new(#FFFFFF, 100))
fill(p1, p2, color=showOsc? color.new(ocC(val), oscShadow) : na)

//--------------------------Moving Average stuff. A good amount of this comes directly from the builtin ribbon
ma(source, length, type) =>
    type == "SMA" ? ta.sma(source, length) :
     type == "EMA" ? ta.ema(source, length) :
     type == "RMA" ? ta.rma(source, length) :
     type == "WMA" ? ta.wma(source, length) :
     type == "VWMA" ? ta.vwma(source, length) :
     type == "HMA" ? ta.hma(source, length) :
     na

ggc(oc, n) =>
    vv = ta.ema(val, qG)
    r = osc_max - osc_min
    cor = r/2
    if (not maColorChange)
        bfC
    else
        if (vv > cor)
            if (vv < osc_high)
                color.from_gradient(vv, cor, osc_high, color.new(bfC, mint), color.new(hC, maxt-(mint/2)))             //high
            else 
                color.from_gradient(vv, osc_high, osc_max, color.new(hC, maxt-(mint/2)+1), color.new(shC, maxt))        //highest
        
        else 
            if (vv > osc_low)
                color.from_gradient(vv, osc_low, cor, color.new(lC, maxt-(mint/2)), color.new(bfC, mint))               //low
            else 
                color.from_gradient(vv, osc_min, osc_low, color.new(slC, mint), color.new(lC, maxt-(mint/2)))           //lowest
        
        

ma1 = ma(src, ma1_length, ma1_type)
ma2 = ma(src, ma2_length, ma2_type)
ma3 = ma(src, ma3_length, ma3_type)
ma4 = ma(src, ma4_length, ma4_type)
ma5 = ma(src, ma5_length, ma5_type)
ma6 = ma(src, ma6_length, ma6_type)
allMa = array.from(ma1, ma2, ma3, ma4, ma5, ma6)

mp1 = plot(show_ma1 ? ma1 : na, color = ma1_color, title="MA №1")
mp2 = plot(show_ma2 ? ma2 : na, color = ma2_color, title="MA №2")
mp3 = plot(show_ma3 ? ma3 : na, color = ma3_color, title="MA №3")
mp4 = plot(show_ma4 ? ma4 : na, color = ma4_color, title="MA №4")
mp5 = plot(show_ma5 ? ma5 : na, color = ma3_color, title="MA №5")
mp6 = plot(show_ma6 ? ma6 : na, color = ma4_color, title="MA №6")
    
fill(mp1, mp2, color=ggc(bc, 0))
fill(mp2, mp3, color=ggc(bc, 1))
fill(mp3, mp4, color=ggc(bc, 2))
fill(mp4, mp5, color=ggc(bc, 3))
fill(mp5, mp6, color=ggc(bc, 4))






//------------------------------Adding MA Cross and RSI Cross
c2 = ta.ema(osc, lag)
cO = ta.crossover(osc, c2)
cU = ta.crossunder(osc, c2)



//  This to me seems like a very inefficient way that goes through a lot of un-needed comparisons.
//  @Tradingview, please forgive me for unnecessarily excercising your servers for the sake of readability and clean-ish code :C 
//  I'm sure there's a more effective way to do this, purhaps via converting binary to a 6 bit int and moving from there, or perhaps in terms of just going through the array,
//  but I'm unfortunately not too familiar with how to do that in pine. I'd love to know for future reference, so I hope if somebody reading this knows how, they drop a comment. 

    //At first I started just simply creating comparisons of all the possible scenarios, 
    //and then one-by-one examine and confirm the behavior with predicted value, but then realized it quickly became a mess.
    //I a truth table in excel, got rid of all the contradictions (cO AND cU, cU AND cU[1], etc) and was left with what you see.
    //if the descriptive claims don't resonate with you, feel free to drop a comment. I'd really like to hear what people think about this.
    //Personally, I think the most relatively significant ones are where there was a cross in the previous candle, AND we are either: 
    //  A) crossing into high/low territorry immidiately after a cross
    //  B) there were two crosses in different directions. 
    //That brings us into mainly utilizing: 1, 3, 5, 7, 11, 13.
    //Even more significant, would be when the prior conditions are BOTH true, AND there was a cross on the previous candle. I'll call these "Double Cross" from now on.
dTruth(a,b,c,d,e,f) => //cU, cO, oL, oH, cu[1], cO[1]
    //
    (( (a==1) == cU   ) and
     ( (b==1) == cO   ) and
     ( (c==1) == oL   ) and
     ( (d==1) == oH   ) and
     ( (e==1) == cU[1]) and
     ( (f==1) == cO[1]) )

                                                //just organizing my thoughts...    
    ///                   cU, cO, oL, oH, cU[1], cO[1]      [cU/cO][oH/oL][dc]
upDownLow    =    	dTruth(1,	0,	1,	0,	0,	1)      //          ***            //Oscillator Low.      Crossed Over, Crossing Under.       Possible Continuation
cuol         =    	dTruth(1,	0,	1,	0,	0,	0)      //          **|            //Oscillator Low.      Crossing Under                      Looks bottomy, but more research is needed.
upDownHigh   =    	dTruth(1,	0,	0,	1,	0,	1)      //          ***            //Oscillator High.     Crossed Over, Crossing Under.
cuoh         =      dTruth(1,	0,	0,	1,	0,	0)      //          **|            //Oscillator High.     Crossed Under
upDown       =      dTruth(1,	0,	0,	0,	0,	1)      //          *|*            //--------------       Crossed Over, Crossing Under,
down         =      dTruth(1,	0,	0,	0,	0,	0)      //          *||            //--------------       Crossing Under
downUpLow    =      dTruth(0,	1,	1,	0,	1,	0)      //          ***            //Oscillator Low.      Crossed Under, Crossing Over
cool         =      dTruth(0,	1,	1,	0,	0,	0)      //          **|            //Oscillator Low.      Crossing Over.
downUpHigh   =    	dTruth(0,	1,	0,	1,	1,	0)      //          ***            //Oscillator High.     Crossed Under, Crossing Over
cooh         =      dTruth(0,	1,	0,	1,	0,	0)      //          **|            //Oscillator High.     Crossing Over
downUp       =      dTruth(0,	1,	0,	0,	1,	0)      //          *|*            //--------------       Crossed under, Crossing Over        Low Bounce
up           =      dTruth(0,	1,	0,	0,	0,	0)      //          *||            //--------------       Crossing Over.  
//v13        =      dTruth(0,	0,	1,	0,	1,	0)      //          |*|            //Oscillator Low.      Crossed under
//v14        =      dTruth(0,	0,	1,	0,	0,	1)      //          |*|            //Oscillator Low.      Crossed Over      
oscLow       =      dTruth(0,	0,	1,	0,	0,	0)      //          |*|            //Oscillator Low,      ----------------------------------------------------------------------
//v16        =      dTruth(0,	0,	0,	1,	1,	0)      //          |*|            //Oscillator High.     Crossed under.                      Possible Reversal
//v17        =      dTruth(0,	0,	0,	1,	0,	1)      //          |*|            //Oscillator High.     Crossed over.                       Possible Continuation
oscHigh      =   	dTruth(0,	0,	0,	1,	0,	0)      //          |*|            //Oscillator High. 
//v19        =   	dTruth(0,	0,	0,	0,	1,	0)      //          |||            //Previous candlestick crossed up, and continued in the current direction. Did not reach high. 
//v20        =   	dTruth(0,	0,	0,	0,	0,	1)      //          |||            //Previous candlestick crossed down, and continued in the current direction. Did not reach low. 

//Even more notes/comments:
//I've ommited a few, such as v19/v20 due to their prevelance, and because we've reached pines 64 plots. oops. 
//13,14,16,17,19,20 are ommmitted because they are reduntant with 2,8,4,10,5,6 [1] respectively.
//technically upDown and DownUp can also be omitted for now

//I really want to maintain such that the user can change and save default options for the indicator. If we have size/shape/location dynamic, the user loses this option. 
 //Therefore, we must spell out every single scenario. 
 //I will omit scenarios that happen often, and only include significant ones, such as the [double cross AND high/low]
symbolSize = size.tiny

//Oscillator High/Low Only
plotshape(oscLow,  title='Oscillator Low',                       style=shape.arrowup,      color=cc,           location=location.bottom,   size=symbolSize)
plotshape(oscHigh,  title='Oscillator High',                     style=shape.arrowdown,    color=cc,           location=location.top,      size=symbolSize)

plotshape(upDownHigh,  title='Up-Down High Bounce',              style=shape.triangleup,   color=cc,           location=location.top,      size=symbolSize)
plotshape(upDownLow,  title='Up-Down Low Bounce',                style=shape.triangleup,   color=cc,           location=location.bottom,   size=symbolSize)

plotshape(downUpHigh,  title='Down-Up High Bounce',              style=shape.triangledown, color=cc,           location=location.top,      size=symbolSize)
plotshape(downUpLow,  title='Down-Up Low Bounce',                style=shape.triangledown, color=cc,           location=location.bottom,   size=symbolSize)

plotshape(cuoh,  title='Crossing Under, High',                   style=shape.xcross,       color=cc,           location=location.top,      size=symbolSize)
plotshape(cuol,  title='Crossing Under, Low',                    style=shape.diamond,      color=cc,           location=location.bottom,   size=symbolSize)

plotshape(cooh,  title='Crossing Over, High',                    style=shape.diamond,      color=cc,           location=location.top,      size=symbolSize)
plotshape(cool,  title='Crossing Over, Low',                     style=shape.xcross,       color=cc,           location=location.bottom,   size=symbolSize)

plotshape(up and not showOsc,    title='Crossing Up',                       style=shape.triangleup,       color=cc,           location=location.bottom,   size=symbolSize, display=display.none)
plotshape(down and not showOsc,  title='Crossing Down,',                     style=shape.triangledown,       color=cc,           location=location.top,   size=symbolSize, display=display.none)
  
     


alertcondition(oH and not oH[1], 'Oscillator High,', 'Oscillator High!')
alertcondition(oL and not oL[1], 'Oscillator Low,', 'Oscillator Low!')
