Trend Line Regression
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © dandrideng

//@version=5
indicator(title="Trend Line Regression", shorttitle="TLR", overlay=true, max_bars_back=800, max_lines_count=400, max_labels_count=400)

import dandrideng/least_squares_regression/1 as lsr

//trend line regression
lookback_left = input.int(defval=5, title="Pivot Lookback Left", group="Trend Line Regression")
lookback_right = input.int(defval=5, title="Pivot Lookback Right", group="Trend Line Regression")
reg_forward = input.int(defval=20, title="Max Lookback Forward", group="Trend Line Regression")
min_length = input.int(defval=100, title="Min Regression Length", maxval=500, group="Trend Line Regression")
max_length = input.int(defval=200, title="Max Regression Length", maxval=500, group="Trend Line Regression")
length_step = input.int(defval=100, title="Regression Length Steps", group="Trend Line Regression")
max_iteration = input.int(defval=4, title="Max Iterations of Regression", group="Trend Line Regression")
max_points = input.int(defval=30, title="Max Point Numbers of Trend Line", group="Trend Line Regression")
min_points = input.int(defval=3, title="Min Point Numbers of Trend Line", group="Trend Line Regression")
min_reg_level = input.float(defval=4, title="Min Regression Level of Trend Line", group="Trend Line Regression")
std_offset = input.float(defval=0.5, title="Multiply Regression Std", group="Trend Line Regression")
top_line_color = input.color(defval=color.red, title="Color of Top Trend Line", group="Trend Line Regression")
bottom_line_color = input.color(defval=color.green, title="Color of Bottom Trend Line", group="Trend Line Regression")
extend_lines = input.bool(defval=true, title="Extend Lines", group="Trend Line Regression") ? extend.right : extend.none
draw_pivot = input.bool(defval=false, title="Draw Piovt High/Low", group="Trend Line Regression")
show_labels = input.bool(defval=false, title="Show Labels", group="Trend Line Regression")

pivot_high = ta.pivothigh(high, lookback_left, lookback_right)
pivot_low = ta.pivotlow(low, lookback_left, lookback_right)

plot(draw_pivot ? pivot_high : na, offset=-lookback_left)
plot(draw_pivot ? pivot_low : na, offset=-lookback_left)

//check regression (functional)
check_trend_line(start, end, type, a, b) =>
    res = true
    for i = start + 1 to end - 1
        yi = a * i + b
        yi_1 = a * (i - 1) + b
        if type == "top" and (close[i] > yi and close[i - 1] > yi_1)
            res := false
            break
        else if type == "bottom" and (close[i] < yi and close[i - 1] < yi_1)
            res := false
            break
    
    res
    
//draw trend line regressions (functional)
draw_trend_line(forward, length, type) =>
    pivot_src = type == "top" ? pivot_high : pivot_low

    tick = array.new_int(max_points, 0)
    price = array.new_float(max_points, 0)
    num = 0

    for i = 0 to length - 1
        if not na(pivot_src[i + forward]) and num < max_points
            array.set(tick, num, i + lookback_left + forward)
            array.set(price, num, pivot_src[i + forward])
            num := num + 1
        
    if num > 0
        [x2, x1, a, b, r, s, n] = lsr.trend_line_lsr(tick, price, num, type, max_iteration, min_points)
        y1 = x1 * a + b
        y2 = x2 * a + b
        o = s * std_offset
        
        if n > min_points and r > min_reg_level and check_trend_line(x1, x2, type, a, b)
            var line mid_line = na
            mid_line := line.new(x1=bar_index-x1, y1=y1, x2=bar_index-x2, y2=y2)
            line.set_width(mid_line, 1)
            line.set_color(mid_line, color.new(type == "top" ? top_line_color : bottom_line_color, 25))
            line.set_extend(mid_line, extend_lines)
            line.set_style(mid_line, line.style_dashed)
        
            var line top_line = na
            top_line := line.new(x1=bar_index-x1, y1=y1+o, x2=bar_index-x2, y2=y2+o)
            line.set_width(top_line, 1)
            line.set_color(top_line, color.new(type == "top" ? top_line_color : bottom_line_color, 25))
            line.set_extend(top_line, extend_lines)
            
            var line bottom_line = na
            bottom_line := line.new(x1=bar_index-x1, y1=y1-o, x2=bar_index-x2, y2=y2-o)
            line.set_width(bottom_line, 1)
            line.set_color(bottom_line, color.new(type == "top" ? top_line_color : bottom_line_color, 25))
            line.set_extend(bottom_line, extend_lines)
            
            linefill.new(mid_line, top_line, color=color.new(type == "top" ? top_line_color : bottom_line_color, 90))
            linefill.new(mid_line, bottom_line, color=color.new(type == "top" ? top_line_color : bottom_line_color, 90))
            
            if show_labels
                var label reg_label = na
                reg_label := label.new(x=bar_index-x2, y=type == "top" ? y2+o : y2-o, textcolor=color.white)
                label.set_color(reg_label, color.new(type == "top" ? top_line_color : bottom_line_color, 30))
                label.set_text(reg_label, "Level: " + str.tostring(r, "#.###") + "\nPoints: " + str.tostring(n, "#"))
                label.set_style(reg_label, type == "top" ? label.style_label_down : label.style_label_up)

//delete all lines
all_lines = line.all
if array.size(all_lines) > 0
	for i = 0 to array.size(all_lines) - 1
		line.delete(array.get(all_lines, i))
		
//delete all labels
all_labels = label.all
if array.size(all_labels) > 0
	for i = 0 to array.size(all_labels) - 1
		label.delete(array.get(all_labels, i))

//trend line
for l = min_length to max_length by length_step
    draw_trend_line(reg_forward, l, "top")
    draw_trend_line(reg_forward, l, "bottom")
        
//end of file