Wick-to-Body Ratio Trend Forecast | Flux Charts
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fluxchart
//

//@version=5
indicator("Wick to Body Ratio Trend Forecast | Flux Charts", "WBR Forecast | Flux Charts", overlay = true, max_bars_back = 1000, max_lines_count = 500)
max_bars_back(time, 1000)

//  inputs
int loopback = input.int(400, "Loop Back Period", 35, 700, 10 ,"Number of candles to look back at." , group = "Algorithm Settings")
int forecast = input.int(10, "Forcast Line Length" , 5, 32, 1, "Bar length of forcasted lines", group = "Algorithm Settings")
string ratio_type = input.string("General Ratio", "Ratio Type",  [ "General Ratio", "Top Wick", "Bottom Wick","Body Ratio"], "The Type of ratio used to calculate the forcast", group = "Algorithm Settings")
bool direction_bias = input.bool(true, "Directional candles", "The algorithim will only use ratios of candles that have the same direction. ex: If the candle is bullish it will only find bullish candles with the same ratio.", group = "Algorithm Settings")
string default_acceptable_range = input.string("Medium", "Default Accuracy",  [ "High", "Medium", "Low"], "Effects maximum inaccuracy that the script will allow for a ratio to be considered matched.", group = "Algorithm Settings")
//  Forcast Lines
color line_color = input(color.white, "Line Color", "The color of the forcasted line.", group = "Forcast Lines")
bool pattern_lines = input.bool(false, "Previous trends", "Display the idividual trend lines", "lines" , "Forcast Lines")
bool regressed_pattern_lines = input.bool(false, "Best Fit Previous trends", "Display the idividual regressed trend lines", "lines" , "Forcast Lines")
// Past Trend
bool trend_square = input.bool(true, "Square Trends", "Display a square around the candles following the candle with a similar ratio.", group="Past Trends")
bool trend_square_labels = input.bool(true, "Label Ratios", "Display the candles ratios that are being used.", group="Past Trends")
color deviation_high = input(color.red, "High | Confidence Color", "The color of the forcasted line." , "The color to use when the indicator is high confidence.")
color deviation_low = input(color.blue, "Low | Confidence Color", "The color of the forcasted line." , "The color to use when the indicator is high confidence.")

//  Other
bool display_logs = input.bool(true, "Display Warning Messages", "Display warning messages", group = "Other")

//Declarations
varip float accuracy = .02
varip bool ratio_not_findable = false
var log_table = table.new(position.top_right, 5, 2, color.green)
candle_forcast_matrix = matrix.new<float>()
length = 14
var square_labels = array.new<label>()
var trend_boxes = array.new<box>()
label ratio_label = na
int last_bar = 0
//clean up 

//wick ratios
general_ratio() => ((math.abs(low - high) - math.abs(close - open)) / math.abs(close - open)) * ((close - open)/ math.abs(close - open)) 
top_wick() => ((math.max(close, open) - high) /  math.abs(close - open)) * ((close - open)/ math.abs(close - open)) 
bottom_wick() => ((math.min(close, open) - low) /  math.abs(close - open)) * ((close - open)/ math.abs(close - open)) 
body_ratio() =>   (math.abs(close - open) / math.abs(high - low)) * ((close - open)/ math.abs(close - open)) 
float ratio = ratio_type == "General Ratio" ? general_ratio() : ratio_type == "Top Wick" ? top_wick() : ratio_type == "Bottom Wick" ? bottom_wick() : body_ratio()

//regressions
linear_regression(y, price) => 
    xy = 0.0
    x_summation = 0.0
    x_squared_summation = 0.0
    array_close = array.new_float(forecast)
    for i = 1 to array.size(y)
        array.set(array_close, i-1, array.get(y, i-1) + price)
        xy += (i * array.get(array_close, i-1))
        x_summation += i
        x_squared_summation += math.pow(i, 2)
    x_squared_sum_mean = x_squared_summation/ array.size(y)
    _x = x_summation/array.size(y)
    _y = array.avg(array_close)
    _xy = xy/array.size(y)
    x_squared_mean = x_squared_summation/array.size(y)
    m = ((_x*_y) - _xy)/(math.pow(_x,2) - x_squared_sum_mean)
    b = _y - (m *_x)
    [m, b]


// logic
if barstate.islast
    if trend_square_labels
        ratio_label := label.new(bar_index[1], na, text = "Ratio: " + str.tostring(math.round_to_mintick(ratio[1])) + "x", yloc = yloc.abovebar, color=color.blue, textcolor = color.white)
    while(true)
        for i = forecast + last_bar to (loopback + forecast)
            float bars_difference = (math.abs(math.abs(ratio[i]) - math.abs(ratio[1])))
            bool ratio_direction_match =  (ratio[i] / math.abs(ratio[i])) == (ratio[1] / math.abs(ratio[1]))
            if i < (forecast+last_bar)
                na   
            else if (bars_difference <= accuracy and ratio_direction_match) or (bars_difference <= accuracy and not direction_bias)
                candles = array.new_float(forecast)
                highs = array.new_float(forecast)
                lows = array.new_float(forecast)
                for y = 0 to forecast - 1
                    array.set(candles,y, close[i - y - 1] - close[i])
                    array.set(highs, y, high[i - y])
                    array.set(lows, y, low[i - y])
                matrix.add_row(candle_forcast_matrix, matrix.rows(candle_forcast_matrix), candles)
                top = highs.max()
                bottom = lows.min()
                trend_color = ratio
                if(trend_square_labels)
                    color direction_color = ratio[i] > 0 ? color.green : color.red
                    //square_labels.push(label.new(bar_index[i], na, text = "Match: " + str.tostring(math.round_to_mintick(ratio[i])) + "x" , yloc = yloc.abovebar, color=direction_color, textcolor=color.white))
                    square_label = label.new(bar_index[i], na, text = "Match: " + str.tostring(math.round_to_mintick(ratio[i])) + "x" , yloc = yloc.abovebar, color=direction_color, textcolor=color.white)
                    array.push(square_labels, square_label)

                if(trend_square)
                    trend_box = box.new(bar_index[i], top, bar_index[i - forecast],  bottom, border_color = color.gray, bgcolor = color.new(color.silver, 60)) 
                    array.push(trend_boxes, trend_box)
                    // box.delete(trend_box[1])
                
                last_bar := i
            
        int matrix_rows = matrix.rows(candle_forcast_matrix)
        if accuracy > .05  and matrix_rows == 0
            if display_logs
                table.cell(log_table, 0, 0, "Warning")
                table.cell(log_table, 0, 1, "No bars with acceptable range raise loopback")
            ratio_not_findable := true
            break
        else if accuracy > .05 and matrix_rows < 5
            if display_logs
                table.cell(log_table, 0, 0, "Warning")
                table.cell(log_table, 0, 1, "Too Few refrences raise loopback")
            break
        else if accuracy <= 0 and matrix_rows > 15
            if display_logs
                table.cell(log_table, 0, 0, "Warning")
                table.cell(log_table, 0, 1, "Too much data lower loopback")
            ratio_not_findable := true
            break
        else if matrix_rows < 5
            accuracy := accuracy + .01
        else if matrix_rows > 15
            accuracy := accuracy - .005
        else 
            ratio_not_findable := false
            break
    if ratio_not_findable == false
        if regressed_pattern_lines or pattern_lines
            for i = 0 to (matrix.rows(candle_forcast_matrix) -1)
                [m, b] = linear_regression(matrix.row(candle_forcast_matrix, i), close[1])
                if regressed_pattern_lines
                    line.new(bar_index, b, bar_index + forecast, close + (m * (forecast)))
                if pattern_lines
                    for y = 0 to (matrix.columns(candle_forcast_matrix) - 2)
                        line.new(bar_index + y  , close + matrix.get(candle_forcast_matrix, i, y), bar_index + y + 1 , close + matrix.get(candle_forcast_matrix, i, y +1), color=line_color)
        for i = 0 to (matrix.columns(candle_forcast_matrix) -2)
            matrix.col(candle_forcast_matrix, i)
            deviation = (matrix.max(candle_forcast_matrix) - matrix.min(candle_forcast_matrix))/2
            color avg_line_color = color.from_gradient(array.stdev(matrix.col(candle_forcast_matrix, i)), 0, deviation,  deviation_high, deviation_low)
            line.new(bar_index + i + 1, close + array.avg(matrix.col(candle_forcast_matrix, i)), bar_index + i + 2, close + array.avg(matrix.col(candle_forcast_matrix, i + 1)), color = avg_line_color, style = line.style_arrow_right, width = 2)
            
int array_size = array.size(square_labels)

if array_size[1] > 0
    //label.new(bar_index, na, str.tostring(array_size[1]), yloc = yloc.belowbar)
    for i = 0 to array_size[1] - 1
        label.delete(array.get(square_labels, i))
        box.delete(array.get(trend_boxes, i))
label.delete(ratio_label[1])
