Machine Learning: MFI Heat Map [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: MFI Heat Map [YinYangAlgorithms]")
// ~~~~~~~~~~~~~~ INPUTS ~~~~~~~~~~~~~~ //
// -- MFI
showSignals = input.bool(false, "Show MFI and SMA Crossing Signals", group="MFI", tooltip="MFI and SMA Crossing is one of the leading Bullish and Bearish Signals in this Indicator. You can also add alerts for these signals.")
plotAmount = input.int(28, "Plot Amount", minval=2, maxval=28, group="MFI", tooltip="How many plots are used in this Heat Map. (2 - 28).")
source = input.source(ohlc4, "Source", group="MFI", tooltip="The Source to use in all MFI calculations.")
smoothInitialMFI = input.int(1, "Smooth Initial MFI Length", minval=1, group="MFI", tooltip="How much to smooth the Fast and Slow MFI calculation by. 1 = No smoothing.")
smaLength = input.int(16, "MFI SMA Length", group="MFI", tooltip="What length we smooth the MFI Average over to get our MFI SMA.")
// -- Machine Learning
useKInSource = input.bool(false, "Average MFI data by adding a lookback to the Source", group="Machine Learning Settings", tooltip="While populating our Heat Map with the MFI's, should use use the Source each MFI Length increase or should we also lookback a Source each MFI Length Increase.")
distanceType = input.string("Max", "KNN Distance Requirement", options=["Max", "Min", "Both"], group="Machine Learning Settings", tooltip="To be a valid KNN, it needs to abide by a Distance calculation. Generally only Max is used, but you can change it if it suits your trading style better.")
mlLength = input.int(5, "Machine Learning Length", minval=1, group="Machine Learning Settings", tooltip="How much ML data should we store? The longer the length generally the smoother the result; which may not be as accurate for something like a Heat Map, so keeping this relatively low may lead to more accurate results.")
knnLength = input.int(2, "KNN Length", minval=1, group="Machine Learning Settings", tooltip="How many KNN are used in the slice to calculate max/min distance allowed.")
fastLength = input.int(2, "Fast Length", minval=1, group="Machine Learning Settings", tooltip="Fast MFI length used in KNN to calculate distances by comparing its distance with the Slow MFI Length.")
slowLength = input.int(5, "Slow Length", minval=2, group="Machine Learning Settings", tooltip="Slow MFI length used in KNN to calculate distances by comparing its distance with the Fast MFI Length.")
kSmoothing = input.int(1, "Smoothing Length", minval=1, group="Machine Learning Settings", tooltip="When populating our Heat Map, at what length do we start our MFI calculations with (A Higher value with result in a slower and more smoothed MFI / Heat Map).")
// -- Colors
changeBarColor = input.bool(true, "Change Bar Color", group="Heat Map Colors", tooltip="Change bar colors to MFI Avg Color")
trans = input.float(30., "Heat Map Transparency", tooltip="If there isn't any transparency it can be a little hard on the eyes. The Greater the Line Width, generally the more transparency you'll want for your eyes.", group="Heat Map Colors")
lineWidth = input.int(2, "Line Width", minval=1, tooltip="Set how wide the Heat Map lines are", group="Heat Map Colors")
//high
color_high_1_input = input.color(#de1414, "MFI 90-100 Color", group="Heat Map Colors")
color_high_2_input = input.color(#f35208, "MFI 80-89 Color", group="Heat Map Colors")
color_high_3_input = input.color(#f3970e, "MFI 70-79 Color", group="Heat Map Colors")
color_high_4_input = input.color(#f2f608, "MFI 60-69 Color", group="Heat Map Colors")
color_high_5_input = input.color(#35f60a, "MFI 50-59 Color", group="Heat Map Colors")
//low
color_low_1_input = input.color(#001b73, "MFI 40-49 Color", group="Heat Map Colors")
color_low_2_input = input.color(#003afa, "MFI 30-39 Color", group="Heat Map Colors")
color_low_3_input = input.color(#0372fa, "MFI 20-29 Color", group="Heat Map Colors")
color_low_4_input = input.color(#00a7e4, "MFI 10-19 Color", group="Heat Map Colors")
color_low_5_input = input.color(#00f2ff, "MFI 0-9 Color", group="Heat Map Colors")

// ~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~ //
//Used in KNN Function
minDist = distanceType == "Both" or distanceType == "Min"
maxDist = distanceType == "Both" or distanceType == "Max"
//high
color_high_1 = color.new(color_high_1_input, trans)
color_high_2 = color.new(color_high_2_input, trans)
color_high_3 = color.new(color_high_3_input, trans)
color_high_4 = color.new(color_high_4_input, trans)
color_high_5 = color.new(color_high_5_input, trans)
//low
color_low_1 = color.new(color_low_1_input, trans)
color_low_2 = color.new(color_low_2_input, trans)
color_low_3 = color.new(color_low_3_input, trans)
color_low_4 = color.new(color_low_4_input, trans)
color_low_5 = color.new(color_low_5_input, trans)

// ~~~~~~~~~~~~~~ FUNCTIONS ~~~~~~~~~~~~~~ //
//Get the color of MFI level for the Heat Map
getColor(_mfi) =>
    mfiColor = color.red
    if _mfi >= 90
        mfiColor := color_high_1
    else if _mfi >= 80 and _mfi < 90
        mfiColor := color_high_2
    else if _mfi >= 70 and _mfi < 80
        mfiColor := color_high_3
    else if _mfi >= 60 and _mfi < 70
        mfiColor := color_high_4
    else if _mfi >= 50 and _mfi < 60
        mfiColor := color_high_5
    else if _mfi >= 40 and _mfi < 50
        mfiColor := color_low_1
    else if _mfi >= 30 and _mfi < 40
        mfiColor := color_low_2
    else if _mfi >= 20 and _mfi < 30
        mfiColor := color_low_3
    else if _mfi >= 10 and _mfi < 20
        mfiColor := color_low_4
    else
        mfiColor := color_low_5
    mfiColor

//Simple k-Nearest Neighbor function used to filter ML Data
    //Average all data from ML Length (Setting) that is between Min/Max KNN Distance (Setting)
getKNN(_dataFast, _dataSlow) =>
    //Calculate the distance between slow and fast moving MIF
    distances = array.new_float(0)
    for i = 0 to _dataSlow.size() - 1
        distance = _dataFast.get(i) - _dataSlow.get(i)
        distances.push(distance)
    //clone the array so it doesn't modify it
    clonedDistances = distances.copy()    
    //slice the length from the array and calculate the max value from this slice
    splicedDistances = clonedDistances.slice(0, math.min(knnLength, clonedDistances.size()))
    maxDistanceAllowed  = splicedDistances.max()
    minDistanceAllowed  = splicedDistances.min()
    //scan all distances and add any that are less than max distance
    validDistances = array.new_float(0)
    for i = 0 to distances.size() - 1
        if (not maxDist or distances.get(i) <= maxDistanceAllowed) and (not minDist or distances.get(i) >= minDistanceAllowed)
            distAvg = (_dataSlow.get(i) + _dataFast.get(i)) / 2
            validDistances.push(distAvg)

    //return the average of valid distances
    validDistances.avg()

// ~~~~~~~~~~~~~~ CALCULATIONS ~~~~~~~~~~~~~~ //
//Get the fast and slow (potentially smoothed) MFIs
float mfi_fast = ta.ema(ta.mfi(source, fastLength), smoothInitialMFI)
float mfi_slow = ta.ema(ta.mfi(source, slowLength), smoothInitialMFI)
//Create a storage array to save the MFI data to be used in KNN Function
mfis_fast = array.new_float(mlLength)
mfis_slow = array.new_float(mlLength)
//Populate MFI data with previous fast and slow MFIs
for i = 0 to mlLength - 1
    mfis_fast.set(i, mfi_fast[i])
    mfis_slow.set(i, mfi_slow[i])
//Calculate the MFI's ML Average using KNN
float mfiKNN = getKNN(mfis_fast, mfis_slow)
//Create a storage array to save the MFI KNN ML Data
mfisKNN_data = array.new_float(plotAmount)
//Populate ML data with the average between MFI at loop index, and MFI from KNN data at loop index
for k = 0 to plotAmount - 1
    mfisKNN_data.set(k, math.avg(mfiKNN[k], ta.mfi(useKInSource ? source[k] : source, k + kSmoothing)))
//Calculate MFI Average scaled to fit on Heat Map
mfiAvg = ta.vwma((mfisKNN_data.avg() / 100) * plotAmount, 2)
//Calculate MFI SMA which can be used to visuallize cross'
ma = ta.sma(mfiAvg, smaLength)
//Calculate Crossing Signals
bullCross = ta.crossover(mfiAvg, ma)
bearCross = ta.crossunder(mfiAvg, ma)
//calculate bar color
barColor = getColor(mfisKNN_data.avg())

// ~~~~~~~~~~~~~~ PLOTS ~~~~~~~~~~~~~~ //
//Heat Map
plot(0, color=getColor(mfisKNN_data.get(0)), title="MFI Plot 1", linewidth = lineWidth)
plot(plotAmount > 1 ? 1 : na, color=getColor(mfisKNN_data.get(1)), title="MFI Plot 2", linewidth = lineWidth)
plot(plotAmount > 2 ? 2 : na, color=getColor(mfisKNN_data.get(2)), title="MFI Plot 3", linewidth = lineWidth)
plot(plotAmount > 3 ? 3 : na, color=getColor(mfisKNN_data.get(3)), title="MFI Plot 4", linewidth = lineWidth)
plot(plotAmount > 4 ? 4 : na, color=getColor(mfisKNN_data.get(4)), title="MFI Plot 5", linewidth = lineWidth)
plot(plotAmount > 5 ? 5 : na, color=getColor(mfisKNN_data.get(5)), title="MFI Plot 6", linewidth = lineWidth)
plot(plotAmount > 6 ? 6 : na, color=getColor(mfisKNN_data.get(6)), title="MFI Plot 7", linewidth = lineWidth)
plot(plotAmount > 7 ? 7 : na, color=getColor(mfisKNN_data.get(7)), title="MFI Plot 8", linewidth = lineWidth)
plot(plotAmount > 8 ? 8 : na, color=getColor(mfisKNN_data.get(8)), title="MFI Plot 9", linewidth = lineWidth)
plot(plotAmount > 9 ? 9 : na, color=getColor(mfisKNN_data.get(9)), title="MFI Plot 10", linewidth = lineWidth)
plot(plotAmount > 10 ? 10 : na, color=getColor(mfisKNN_data.get(10)), title="MFI Plot 11", linewidth = lineWidth)
plot(plotAmount > 11 ? 11 : na, color=getColor(mfisKNN_data.get(11)), title="MFI Plot 12", linewidth = lineWidth)
plot(plotAmount > 12 ? 12 : na, color=getColor(mfisKNN_data.get(12)), title="MFI Plot 13", linewidth = lineWidth)
plot(plotAmount > 13 ? 13 : na, color=getColor(mfisKNN_data.get(13)), title="MFI Plot 14", linewidth = lineWidth)
plot(plotAmount > 14 ? 14 : na, color=getColor(mfisKNN_data.get(14)), title="MFI Plot 15", linewidth = lineWidth)
plot(plotAmount > 15 ? 15 : na, color=getColor(mfisKNN_data.get(15)), title="MFI Plot 16", linewidth = lineWidth)
plot(plotAmount > 16 ? 16 : na, color=getColor(mfisKNN_data.get(16)), title="MFI Plot 17", linewidth = lineWidth)
plot(plotAmount > 17 ? 17 : na, color=getColor(mfisKNN_data.get(17)), title="MFI Plot 18", linewidth = lineWidth)
plot(plotAmount > 18 ? 18 : na, color=getColor(mfisKNN_data.get(18)), title="MFI Plot 19", linewidth = lineWidth)
plot(plotAmount > 19 ? 19 : na, color=getColor(mfisKNN_data.get(19)), title="MFI Plot 20", linewidth = lineWidth)
plot(plotAmount > 20 ? 20 : na, color=getColor(mfisKNN_data.get(20)), title="MFI Plot 21", linewidth = lineWidth)
plot(plotAmount > 21 ? 21 : na, color=getColor(mfisKNN_data.get(21)), title="MFI Plot 22", linewidth = lineWidth)
plot(plotAmount > 22 ? 22 : na, color=getColor(mfisKNN_data.get(22)), title="MFI Plot 23", linewidth = lineWidth)
plot(plotAmount > 23 ? 23 : na, color=getColor(mfisKNN_data.get(23)), title="MFI Plot 24", linewidth = lineWidth)
plot(plotAmount > 24 ? 24 : na, color=getColor(mfisKNN_data.get(24)), title="MFI Plot 25", linewidth = lineWidth)
plot(plotAmount > 25 ? 25 : na, color=getColor(mfisKNN_data.get(25)), title="MFI Plot 26", linewidth = lineWidth)
plot(plotAmount > 26 ? 26 : na, color=getColor(mfisKNN_data.get(26)), title="MFI Plot 27", linewidth = lineWidth)
plot(plotAmount > 27 ? 27 : na, color=getColor(mfisKNN_data.get(27)), title="MFI Plot 28", linewidth = lineWidth)
//AVG and SMA Lines
plot(mfiAvg, color=color.white, title="MFI Average", linewidth=lineWidth)
plot(ma, color=color.orange, title="MFI Smoothed", linewidth=lineWidth)
//OB / Neutral / OS Zones
hline((70. / 100.) * plotAmount, "Upper Band", color=color.new(color.white, 30))
hline(plotAmount / 2., "Middle Band", color=color.new(color.white, 30))
hline((30. / 100.) * plotAmount, "Lower Band", color=color.new(color.white, 30))
//Bull and Bear Signals
plot(showSignals and bullCross ? mfiAvg : na, color=color.green, linewidth=3, style=plot.style_circles, title="MFI Bull Cross")
plot(showSignals and bearCross ? mfiAvg : na, color=color.red, linewidth=3, style=plot.style_circles, title="MFI Bear Cross")
//Change bar color
barcolor(changeBarColor ? barColor : na)

// ~~~~~~~~~~~~~~ ALERTS ~~~~~~~~~~~~~~ //
alertcondition(bullCross, title="MFI Bull Cross", message="MFI Bull Cross")
alertcondition(bearCross, title="MFI Bear Cross", message="MFI Bear Cross")

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