KNN Regression [SS]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//  /$$$$$$   /$$                                                         /$$                                            
// /$$__  $$ | $$                                                        | $$                                            
//| $$  \__//$$$$$$    /$$$$$$  /$$    /$$ /$$$$$$   /$$$$$$   /$$$$$$$ /$$$$$$    /$$$$$$  /$$    /$$ /$$$$$$   /$$$$$$$
//|  $$$$$$|_  $$_/   /$$__  $$|  $$  /$$//$$__  $$ /$$__  $$ /$$_____/|_  $$_/   /$$__  $$|  $$  /$$//$$__  $$ /$$_____/
// \____  $$ | $$    | $$$$$$$$ \  $$/$$/| $$$$$$$$| $$  \__/|  $$$$$$   | $$    | $$$$$$$$ \  $$/$$/| $$$$$$$$|  $$$$$$ 
// /$$  \ $$ | $$ /$$| $$_____/  \  $$$/ | $$_____/| $$       \____  $$  | $$ /$$| $$_____/  \  $$$/ | $$_____/ \____  $$
//|  $$$$$$/ |  $$$$/|  $$$$$$$   \  $/  |  $$$$$$$| $$       /$$$$$$$/  |  $$$$/|  $$$$$$$   \  $/  |  $$$$$$$ /$$$$$$$/
// \______/   \___/   \_______/    \_/    \_______/|__/      |_______/    \___/   \_______/    \_/    \_______/|_______/ 

//       ___________________
//      /                   \
//     /  _____        _____ \
//    /  /     \      /     \  \
// __/__/       \____/       \__\_____
//|           ___________           ____|
// \_________/           \_________/
//            \  /////// /
//             \/////////
// © Steversteves

//@version=5
indicator("KNN Regression [SS]", overlay=true)

len = input.int(500, "Assessment Length") 
regtp = input.string("Autoregression of Source", "Regression Type", ["Autoregression of Source", "Regression of Another Ticker"])
sym = input.symbol("", "Alternate Ticker")
smoth = input.bool(true, "Smoothe Results?")
bands = input.bool(true, "Plot Outerbands") 
src = input.source(close, "Source") 
reg_type = input.string("Cluster", "KNN Regression Type", ["Last Instance", "Avg", "Cluster"])
clust = input.int(3, "KNN Clusters")
tol = input.float(20, "Tolerance for Clusters")



// Security request 
at = request.security(sym, "", src, lookahead = barmerge.lookahead_on) 

// KNN Regresion 

// Inputs //
// Input #1 y variable (dependent) 
// Input #2 x variable (independent) 
// Input #3 Length (assessment length) 
// Input #4 String variable determining the type of KNN regression, must be entered as either "Last Instance", "Cluster" or "Average"
// Input #5 interger for number of clusters you want to include 
// Input #6 float of tolerance, because of the lookback limitations, clusters are done within a tolerance range (i.e. 0.05 or 0.5 etc.) 

// Outputs // 
// Output #1: Result (the result of the KNN regression) 
// Output #2: Error of Estimates 
// Output #3: Correlation of Model 
// Output #4: Simple R2 of model 



knn_regression(float y, float x, int len, string last_instance_or_cluster_or_avg, int clusters, float tolerance) => 
    int index_of = 0
    float result = 0.0
    float error = 0.0 
    int inst = 0 
    float y_result = 0.0 
    float cor = 0.0 
    float r2 = 0.0 
    y_in = y[1]
    ed_y = math.abs(math.sqrt(math.pow(y[1] - x[1],2)))
    ed_x = math.abs(math.sqrt(math.pow(y - x, 2)))
    ed_array = array.new<float>() 
    y_array = array.new<float>() 
    cluster_array = array.new<float>() 
    cluster_avg = array.new<float>() 
    for i = 0 to len 
        array.push(ed_array, ed_y[i]) 
        array.push(y_array, y_in[i])
    // Find KNN 
    if last_instance_or_cluster_or_avg == "Last Instance"
        if array.includes(ed_array, ed_x) 
            index_of := array.indexof(ed_array, ed_x) 
            result := array.get(y_array, index_of) 
        else if result[1] > 0 
            result := result[1] 
        else if result[2] > 0 
            result := result[2] 
        else if result[3] > 0 
            result := result[3] 
        else 
            na 
    else if last_instance_or_cluster_or_avg == "Avg" 
        for i = 0 to array.size(ed_array) - 1 
            if array.get(ed_array, i) == ed_x
                y_result += array.get(y_array, i)
                inst += 1 
                
        result := y_result / inst 
    else if last_instance_or_cluster_or_avg == "Cluster" 
        for i = 0 to array.size(ed_array) - 1 
            if array.get(ed_array, i) >= ed_x - tolerance and array.get(ed_array, i) <= ed_x + tolerance 
                array.push(cluster_array, array.get(y_array, i))
        if array.size(cluster_array) - 1 > clusters 
            for i = 0 to clusters 
                array.push(cluster_avg, array.get(cluster_array, i))
            result := array.avg(cluster_avg) 


    // Error 
    bool above_result = y > result 
    bool below_result = y < result 
    above_arr = array.new<float>() 
    below_arr = array.new<float>() 
    above_dif = y - result 
    below_dif = result - y 
    for i = 0 to len 
        if above_result[i]
            array.push(above_arr, above_dif[i])
        else if below_result[i] 
            array.push(below_arr, below_dif[i])
        else 
            na 
    avg1 = array.avg(above_arr) 
    avg2 = array.avg(below_arr) 
    error := math.avg(avg1, avg2) 
    // Correlation 
    result_array = array.new<float>() 
    for i = 0 to array.size(y_array) - 1 
        array.push(result_array, result[i])
    cov = array.covariance(result_array, y_array) 
    y_sd = array.stdev(y_array) 
    r_sd = array.stdev(result_array) 
    cor := cov / (y_sd * r_sd) 
    // R2 
    r2 := math.pow(cor, 2) 
    [result, error, cor, r2]

float result = 0.0
float err = 0.0 
float cor_r = 0.0 
float r2_r = 0.0 



if regtp == "Autoregression of Source"
    [reg, er, cor, r2] = knn_regression(src, src[1], len, str.tostring(reg_type), clust, tol)
    result := reg 
    err := er 
    cor_r := cor 
    r2_r := r2 
else if regtp == "Regression of Another Ticker"
    [tick, ter, tcor, tr2] = knn_regression(src, at, len, str.tostring(reg_type), clust, tol)
    result := tick 
    err := ter 
    cor_r := tcor 
    r2_r := tr2



float result_plot = 0.0 
float ucl_plot = 0.0 
float lcl_plot = 0.0 

if smoth 
    result_plot := ta.sma(result, 14) 
    ucl_plot := ta.sma(result + err, 14)
    lcl_plot := ta.sma(result- err, 14) 
else 
    result_plot := result  
    ucl_plot := result + err 
    lcl_plot := result - err 



plot(result_plot, linewidth=2) 
plot(bands ? ucl_plot : na, linewidth=2) 
plot(bands ? lcl_plot : na, linewidth=2) 

    
var table data = table.new(position.top_right, 5, 5, bgcolor = color.black) 
table.cell(data, 1, 1, text = "Regression Type: " + str.tostring(reg_type), bgcolor = color.black, text_color = color.white) 
if reg_type == "Cluster"
    table.cell(data, 1, 2, text = "# of Clusters: " + str.tostring(clust), bgcolor = color.black, text_color = color.white)  
table.cell(data, 1, 3, text = "Correlation: " + str.tostring(math.round(cor_r, 2)), bgcolor = color.black, text_color = color.white) 
table.cell(data, 1, 4, text = "R2: " + str.tostring(math.round(r2_r, 2)), bgcolor = color.black, text_color = color.white) 

    
    