Machine Learning: Support and Resistance [YinYangAlgorithms]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    ,@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@        @@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@         @@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           @@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@        .@@@@@@@@@@@@@@@            @@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@          *@@@@@@@@@@@@@@             @@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@         @@@@@@@@@@@@@@@               @@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@                 @@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                  @@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.                    @@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                      @@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.                         @
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                             @
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,                                       @
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                                @
// @@@@@@@@@@@@@@@@@@@@@@@@@@@                                                    @
// @@@@@@@@@@@@@@@@@@@@@@@@@                                                     @@
// @@@@@@@@@@@@@@@@@@@@@@@                                                       @@
// @@@@@@@@@@@@@@@@@@@@@@                                                       @@@
// @@@@@@@@@@@@@@@@@@@@@*                @@@@@                                 @@@@
// @@@@@@@@@@@@@@@@@@@@@               @@@@@@@@@                              @@@@@
// @@@@@@@@@@@@@@@@@@@@@              @@@@@@@@@@@                           @@@@@@@
// @@@@@@@@@@@@@@@@@@@@@               @@@@@@@@%                           @@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@                                                @@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@                                            @@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@                                        %@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@                                   @@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                           @@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// © YinYangAlgorithms

//@version=5
indicator("Machine Learning Support and Resistance [YinYangAlgorithms]", overlay=true)
// ~~~~~~~~~~~ INPUTS ~~~~~~~~~~~ //
source = input.source(close, "Source", tooltip="This source is the base for all our calculations")
mlLength = input.int(100, "Machine Learning Length", minval=2, tooltip="How much projection data are we storing and using to make calculations")
smoothingLength = input.int(14, "Smoothing Length", minval=1, tooltip="We need to smooth calculations such as RSI, EMA and VWMA. What length are we smoothing it with.")
vwmaProjectionLength = input.int(1, "VWMA ML Projection Length", minval=1, tooltip="How far into our Machine Learning data should we average for our VWMA. Please note the 'Smoothing Length' is still applied here after getting the Projection Average.")
showLongTermMemory = input.string("Hard Line", "Long Term Memory", options=["Average", "Hard Line", "None"], tooltip="Long term memory has the same storage length but is only updated once per Machine Learning Length. For instance, if Machine Learning Length is 100, it will save the Average of our data once every 100 bars. This means its memory is an average of 10,000 bars of Machine Learning. 'Average' connects its values diagonally whereas 'Hard Line' holds its value until it changes.")
useAverageLastDistance = input.bool(false, "Use Average Last Distance In Potential Movement", tooltip="This can help accuracy but generally also displaces the Support and Resistance by projecting it further")
showCurrentProjection = input.bool(false, "Show Current Projection", tooltip="Projections occur for each bar, and our Machine Learning utilizes these projections by storing and evaluating them. This toggle will display the Current Projection Line which is used to create all our Projections.")

// ~~~~~~~~~~~ FUNCTIONS ~~~~~~~~~~~ //
//Calculate the current bars projection
getCurrentProjection(_src, _rsi, _ma) =>
    //Difference between source and MA
    dif = _src - _ma
    //Difference between last source and MA
    prev = _src[1] - _ma[1]
    //We need to know the direction the RSI is currently moving
    prevRSI = _rsi - _rsi[1]
    //The average over the last MM length so we know our relationship to it
    avg = 0.
    for i = 0 to mlLength - 1
        avg += _src[i] - _ma[i]
    avg := avg / mlLength
    //get the absolute difference between our current values and the averages
    avgDif = math.abs(dif) - math.abs(avg)
    //calculate the average distance between our last 2 bars
    avgLastDistance = math.avg(dif, prev)
    //calculate our potential movement
    potentialMovement = useAverageLastDistance ? avgDif + math.avg(avg, avgLastDistance) : avgDif + avg
    //calculate our projection direction
    projDir = 1.
    if prev > 0
        projDir += 0.05
    else
        projDir -= 0.05
    if dif > avgDif
        projDir += 0.05
    else
        projDir -= 0.05
    if prevRSI > 0
        projDir += 0.05
    else
        projDir -= 0.05
    if (_rsi > 30 and _rsi[1] <= 30)
        projDir += 0.1
    else if (_rsi < 70 and _rsi[1] >= 70)
        projDir -= 0.1
    //calculate our projection mult
    projMult = 1.
    if (_rsi > 30 and _rsi[1] <= 30) or (_rsi < 70 and _rsi[1] >= 70)
        projMult += 0.1
    if dif > prev
        projMult += 0.05
    //apply mult to potential movement and create projection
    projection = 0.
    if projDir >= 1 
        projection := _src + (potentialMovement * projMult)
    else
        projection := _src - (potentialMovement * projMult)
    //return projection
    projection

//Calculate the Machine Learning projection over a given length with possible sorting
getProjection(_data, _length, _sorting) =>
    //If specified, sort the data accordingly
    if _sorting == "Ascending"
        _data.sort(order.ascending)
    else if _sorting == "Descending"
        _data.sort(order.descending)
    //Compute the average of the KNN (if sorted) or most recent data over a specified length
        //If using sorting this allows it to compute its KNN over X length rather than a fixed KNN length
    avg = 0.
    for i = 0 to _length - 1
        avg += array.get(_data, i)
    avg / _length

// ~~~~~~~~~~~ CALCULATIONS ~~~~~~~~~~~ //
//our current values used to create projections
src = source[1]
rsi = ta.rsi(src, smoothingLength)
ma = ta.ema(src, smoothingLength)

//our projection of this bar
float projection = 0.
projection := getCurrentProjection(src, rsi, ma)

//save what the previous projections were 
data = array.new_float(mlLength) 

for i = 0 to mlLength - 1
    data.set(i, projection[i])

//save long term memory
longTermMemory = array.new_float(mlLength)

if bar_index % mlLength == 0
    longTermMemory.push(data.avg())
else if barstate.islast and showLongTermMemory == "Average"
    longTermMemory.set(longTermMemory.size() - 1, data.avg())

//calculate projections with varying lengths
ltm = showLongTermMemory == "Average" ? longTermMemory.avg() : ta.sma(longTermMemory.avg(), 1)
avg = data.avg()
vwma = ta.vwma(getProjection(data, math.min(vwmaProjectionLength, mlLength), ""), smoothingLength)
strongSupport = getProjection(data, 2, "Ascending")
strongResistance = getProjection(data, 2, "Descending")
support = getProjection(data, math.max(mlLength / 10, 2), "Ascending")
resistance = getProjection(data, math.max(mlLength / 10, 2), "Descending")

// ~~~~~~~~~~~ PLOTS ~~~~~~~~~~~ //
plot(showCurrentProjection ? projection : na, color=color.white)
ss = plot(strongSupport, color=color.green)
sr = plot(strongResistance, color=color.red)
s = plot(support, color=color.new(color.green, 50))
r = plot(resistance, color=color.new(color.red, 50))
a = plot(avg, color=color.purple)
v = plot(vwma, color=color.blue)
plot(showLongTermMemory != "None" ? ltm : na, color=color.maroon)

fill(ss, s, color=color.new(color.green, 95))
fill(sr, r, color=color.new(color.red, 95))
midColor = vwma > avg ? color.green : color.red
fill(v, a, color=color.new(midColor, 95))

// ~~~~~~~~~~~ END ~~~~~~~~~~~ //