//@version=5 indicator("Auto Trend Lines - Long Period", overlay=true, max_lines_count=500) // Input parameters lookback_period = input.int(50, "Lookback Period for Pivot Detection", minval=10, maxval=200) min_touches = input.int(2, "Minimum Touches for Valid Trend Line", minval=2, maxval=5) max_lines = input.int(20, "Maximum Number of Trend Lines", minval=5, maxval=50) max_bars_back = input.int(500, "Maximum Bars Back for Lines", minval=100, maxval=2000) merge_similar_lines = input.bool(true, "Merge Similar Lines") slope_tolerance = input.float(0.1, "Slope Tolerance for Merging (%)", minval=0.01, maxval=1.0) price_tolerance = input.float(0.5, "Price Tolerance for Merging (%)", minval=0.1, maxval=2.0) line_extend = input.string("right", "Line Extension", options=["none", "left", "right", "both"]) show_support = input.bool(true, "Show Support Lines") show_resistance = input.bool(true, "Show Resistance Lines") // Enhanced K-line features show_close_based_lines = input.bool(true, "Show Close-Based Trend Lines (Thick)") show_wick_based_lines = input.bool(true, "Show Wick-Based Trend Lines (Thin)") show_current_hl = input.bool(true, "Show Current Bar High/Low Points") // Volume filtering use_volume_filter = input.bool(true, "Enable Volume Filtering") volume_threshold_multiplier = input.float(1.5, "Volume Threshold Multiplier", minval=0.5, maxval=5.0) volume_lookback = input.int(20, "Volume Average Lookback", minval=5, maxval=100) // Line styling support_color = input.color(color.green, "Support Line Color") resistance_color = input.color(color.red, "Resistance Line Color") close_support_color = input.color(color.lime, "Close-Based Support Color") close_resistance_color = input.color(color.maroon, "Close-Based Resistance Color") thin_line_width = input.int(2, "Thin Line Width (Wick-Based)", minval=1, maxval=5) thick_line_width = input.int(5, "Thick Line Width (Close-Based)", minval=3, maxval=15) line_style = input.string("solid", "Line Style", options=["solid", "dashed", "dotted"]) current_hl_color = input.color(color.yellow, "Current High/Low Point Color") // Convert line style get_line_style() => switch line_style "solid" => line.style_solid "dashed" => line.style_dashed "dotted" => line.style_dotted => line.style_solid // Pivot detection pivot_high = ta.pivothigh(high, lookback_period, lookback_period) pivot_low = ta.pivotlow(low, lookback_period, lookback_period) // Arrays to store pivot points with time var array high_prices = array.new() var array high_bars = array.new() var array high_times = array.new() var array low_prices = array.new() var array low_bars = array.new() var array low_times = array.new() // Arrays to store close-based pivot points var array high_close_prices = array.new() var array high_close_bars = array.new() var array low_close_prices = array.new() var array low_close_bars = array.new() // Arrays to store volume data for pivots var array high_volumes = array.new() var array low_volumes = array.new() var array high_close_volumes = array.new() var array low_close_volumes = array.new() // Calculate volume average for filtering volume_avg = ta.sma(volume, volume_lookback) // Store pivot points (high/low based) with volume data if not na(pivot_high) pivot_bar = bar_index - lookback_period pivot_time = math.round(time[lookback_period]) pivot_volume = volume[lookback_period] // Only store if within reasonable distance if bar_index - pivot_bar <= max_bars_back array.push(high_prices, pivot_high) array.push(high_bars, pivot_bar) array.push(high_times, pivot_time) array.push(high_volumes, pivot_volume) // Keep only recent pivots to manage memory if array.size(high_prices) > 50 array.shift(high_prices) array.shift(high_bars) array.shift(high_times) array.shift(high_volumes) if not na(pivot_low) pivot_bar = bar_index - lookback_period pivot_time = math.round(time[lookback_period]) pivot_volume = volume[lookback_period] // Only store if within reasonable distance if bar_index - pivot_bar <= max_bars_back array.push(low_prices, pivot_low) array.push(low_bars, pivot_bar) array.push(low_times, pivot_time) array.push(low_volumes, pivot_volume) // Keep only recent pivots to manage memory if array.size(low_prices) > 50 array.shift(low_prices) array.shift(low_bars) array.shift(low_times) array.shift(low_volumes) // Store close-based pivot points (always calculate for close-based trend lines) with volume data pivot_high_close = ta.pivothigh(close, lookback_period, lookback_period) pivot_low_close = ta.pivotlow(close, lookback_period, lookback_period) if not na(pivot_high_close) pivot_bar = bar_index - lookback_period pivot_volume = volume[lookback_period] if bar_index - pivot_bar <= max_bars_back array.push(high_close_prices, pivot_high_close) array.push(high_close_bars, pivot_bar) array.push(high_close_volumes, pivot_volume) if array.size(high_close_prices) > 50 array.shift(high_close_prices) array.shift(high_close_bars) array.shift(high_close_volumes) if not na(pivot_low_close) pivot_bar = bar_index - lookback_period pivot_volume = volume[lookback_period] if bar_index - pivot_bar <= max_bars_back array.push(low_close_prices, pivot_low_close) array.push(low_close_bars, pivot_bar) array.push(low_close_volumes, pivot_volume) if array.size(low_close_prices) > 50 array.shift(low_close_prices) array.shift(low_close_bars) array.shift(low_close_volumes) // Function to calculate line slope and y-intercept get_line_params(x1, y1, x2, y2) => slope = (y2 - y1) / (x2 - x1) intercept = y1 - slope * x1 [slope, intercept] // Function to get y value at given x using line equation get_y_at_x(slope, intercept, x) => slope * x + intercept // Structure to store trend line data type TrendLineData float slope float intercept int bar1 float price1 int bar2 float price2 int touches bool is_resistance // Function to check if two lines are similar and should be merged lines_are_similar(line1, line2, slope_tol, price_tol) => // Check slope similarity slope_diff = math.abs(line1.slope - line2.slope) avg_slope = (math.abs(line1.slope) + math.abs(line2.slope)) / 2 slope_similar = avg_slope == 0 ? slope_diff < 0.001 : (slope_diff / avg_slope) < (slope_tol / 100) // Check price level similarity at a common point mid_bar = math.round((line1.bar1 + line1.bar2 + line2.bar1 + line2.bar2) / 4) price1_at_mid = get_y_at_x(line1.slope, line1.intercept, mid_bar) price2_at_mid = get_y_at_x(line2.slope, line2.intercept, mid_bar) price_diff = math.abs(price1_at_mid - price2_at_mid) avg_price = (price1_at_mid + price2_at_mid) / 2 price_similar = (price_diff / avg_price) < (price_tol / 100) slope_similar and price_similar // Function to merge two similar trend lines merge_lines(line1, line2) => // Choose the line with more touches, or the longer one if line1.touches > line2.touches line1 else if line2.touches > line1.touches line2 else // If same touches, choose the longer line length1 = math.abs(line1.bar2 - line1.bar1) length2 = math.abs(line2.bar2 - line2.bar1) length1 >= length2 ? line1 : line2 // Function to check if pivot has sufficient volume has_sufficient_volume(pivot_volume, avg_volume) => not use_volume_filter or pivot_volume >= (avg_volume * volume_threshold_multiplier) // Function to count touches for wick-based trend lines with volume filtering count_wick_touches(slope, intercept, start_bar, end_bar, is_resistance, tolerance_pct = 0.5) => touches = 0 current_bar = bar_index pivot_prices = is_resistance ? high_prices : low_prices pivot_bars_array = is_resistance ? high_bars : low_bars pivot_volumes_array = is_resistance ? high_volumes : low_volumes if array.size(pivot_prices) > 0 for i = 0 to array.size(pivot_prices) - 1 bar_idx = array.get(pivot_bars_array, i) price = array.get(pivot_prices, i) pivot_volume = array.get(pivot_volumes_array, i) if bar_idx >= start_bar and bar_idx <= end_bar and (current_bar - bar_idx) <= max_bars_back // Check volume filter if has_sufficient_volume(pivot_volume, volume_avg) expected_price = get_y_at_x(slope, intercept, bar_idx) tolerance = expected_price * tolerance_pct / 100 if is_resistance if math.abs(price - expected_price) <= tolerance and price >= expected_price - tolerance touches += 1 else if math.abs(price - expected_price) <= tolerance and price <= expected_price + tolerance touches += 1 touches // Function to count touches for close-based trend lines with volume filtering count_close_touches(slope, intercept, start_bar, end_bar, is_resistance, tolerance_pct = 0.5) => touches = 0 current_bar = bar_index close_prices = is_resistance ? high_close_prices : low_close_prices close_bars_array = is_resistance ? high_close_bars : low_close_bars close_volumes_array = is_resistance ? high_close_volumes : low_close_volumes if array.size(close_prices) > 0 for i = 0 to array.size(close_prices) - 1 bar_idx = array.get(close_bars_array, i) price = array.get(close_prices, i) pivot_volume = array.get(close_volumes_array, i) if bar_idx >= start_bar and bar_idx <= end_bar and (current_bar - bar_idx) <= max_bars_back // Check volume filter if has_sufficient_volume(pivot_volume, volume_avg) expected_price = get_y_at_x(slope, intercept, bar_idx) tolerance = expected_price * tolerance_pct / 100 if is_resistance if math.abs(price - expected_price) <= tolerance and price >= expected_price - tolerance touches += 1 else if math.abs(price - expected_price) <= tolerance and price <= expected_price + tolerance touches += 1 touches // Function to draw trend lines draw_trend_lines() => var array wick_resistance_lines = array.new() var array wick_support_lines = array.new() var array close_resistance_lines = array.new() var array close_support_lines = array.new() // Clear old lines for line_obj in wick_resistance_lines line.delete(line_obj) for line_obj in wick_support_lines line.delete(line_obj) for line_obj in close_resistance_lines line.delete(line_obj) for line_obj in close_support_lines line.delete(line_obj) array.clear(wick_resistance_lines) array.clear(wick_support_lines) array.clear(close_resistance_lines) array.clear(close_support_lines) current_bar = bar_index // Arrays to store trend line candidates var array wick_resistance_candidates = array.new() var array wick_support_candidates = array.new() var array close_resistance_candidates = array.new() var array close_support_candidates = array.new() array.clear(wick_resistance_candidates) array.clear(wick_support_candidates) array.clear(close_resistance_candidates) array.clear(close_support_candidates) // Collect WICK-based resistance line candidates (thin lines) if show_wick_based_lines and show_resistance and array.size(high_prices) >= 2 for i = 0 to array.size(high_prices) - 2 for j = i + 1 to array.size(high_prices) - 1 bar1 = array.get(high_bars, i) price1 = array.get(high_prices, i) volume1 = array.get(high_volumes, i) bar2 = array.get(high_bars, j) price2 = array.get(high_prices, j) volume2 = array.get(high_volumes, j) if bar2 - bar1 < lookback_period or (current_bar - bar1) > max_bars_back or (current_bar - bar2) > max_bars_back continue // Apply volume filter to both pivot points if use_volume_filter and (not has_sufficient_volume(volume1, volume_avg) or not has_sufficient_volume(volume2, volume_avg)) continue [slope, intercept] = get_line_params(bar1, price1, bar2, price2) touches = count_wick_touches(slope, intercept, bar1, current_bar, true) if touches >= min_touches trend_line = TrendLineData.new(slope, intercept, bar1, price1, bar2, price2, touches, true) array.push(wick_resistance_candidates, trend_line) // Collect CLOSE-based resistance line candidates (thick lines) if show_close_based_lines and show_resistance and array.size(high_close_prices) >= 2 for i = 0 to array.size(high_close_prices) - 2 for j = i + 1 to array.size(high_close_prices) - 1 bar1 = array.get(high_close_bars, i) price1 = array.get(high_close_prices, i) volume1 = array.get(high_close_volumes, i) bar2 = array.get(high_close_bars, j) price2 = array.get(high_close_prices, j) volume2 = array.get(high_close_volumes, j) if bar2 - bar1 < lookback_period or (current_bar - bar1) > max_bars_back or (current_bar - bar2) > max_bars_back continue // Apply volume filter to both pivot points if use_volume_filter and (not has_sufficient_volume(volume1, volume_avg) or not has_sufficient_volume(volume2, volume_avg)) continue [slope, intercept] = get_line_params(bar1, price1, bar2, price2) touches = count_close_touches(slope, intercept, bar1, current_bar, true) if touches >= min_touches trend_line = TrendLineData.new(slope, intercept, bar1, price1, bar2, price2, touches, true) array.push(close_resistance_candidates, trend_line) // Collect WICK-based support line candidates (thin lines) if show_wick_based_lines and show_support and array.size(low_prices) >= 2 for i = 0 to array.size(low_prices) - 2 for j = i + 1 to array.size(low_prices) - 1 bar1 = array.get(low_bars, i) price1 = array.get(low_prices, i) volume1 = array.get(low_volumes, i) bar2 = array.get(low_bars, j) price2 = array.get(low_prices, j) volume2 = array.get(low_volumes, j) if bar2 - bar1 < lookback_period or (current_bar - bar1) > max_bars_back or (current_bar - bar2) > max_bars_back continue // Apply volume filter to both pivot points if use_volume_filter and (not has_sufficient_volume(volume1, volume_avg) or not has_sufficient_volume(volume2, volume_avg)) continue [slope, intercept] = get_line_params(bar1, price1, bar2, price2) touches = count_wick_touches(slope, intercept, bar1, current_bar, false) if touches >= min_touches trend_line = TrendLineData.new(slope, intercept, bar1, price1, bar2, price2, touches, false) array.push(wick_support_candidates, trend_line) // Collect CLOSE-based support line candidates (thick lines) if show_close_based_lines and show_support and array.size(low_close_prices) >= 2 for i = 0 to array.size(low_close_prices) - 2 for j = i + 1 to array.size(low_close_prices) - 1 bar1 = array.get(low_close_bars, i) price1 = array.get(low_close_prices, i) volume1 = array.get(low_close_volumes, i) bar2 = array.get(low_close_bars, j) price2 = array.get(low_close_prices, j) volume2 = array.get(low_close_volumes, j) if bar2 - bar1 < lookback_period or (current_bar - bar1) > max_bars_back or (current_bar - bar2) > max_bars_back continue // Apply volume filter to both pivot points if use_volume_filter and (not has_sufficient_volume(volume1, volume_avg) or not has_sufficient_volume(volume2, volume_avg)) continue [slope, intercept] = get_line_params(bar1, price1, bar2, price2) touches = count_close_touches(slope, intercept, bar1, current_bar, false) if touches >= min_touches trend_line = TrendLineData.new(slope, intercept, bar1, price1, bar2, price2, touches, false) array.push(close_support_candidates, trend_line) // Process and draw lines separately for wick-based and close-based if merge_similar_lines // Process WICK-based resistance lines (thin) if array.size(wick_resistance_candidates) > 0 merged_wick_resistance = array.new() for i = 0 to array.size(wick_resistance_candidates) - 1 current_line = array.get(wick_resistance_candidates, i) should_merge = false if array.size(merged_wick_resistance) > 0 for j = 0 to array.size(merged_wick_resistance) - 1 existing_line = array.get(merged_wick_resistance, j) if lines_are_similar(current_line, existing_line, slope_tolerance, price_tolerance) merged_line = merge_lines(current_line, existing_line) array.set(merged_wick_resistance, j, merged_line) should_merge := true break if not should_merge array.push(merged_wick_resistance, current_line) // Draw wick-based resistance lines (thin) lines_drawn = 0 if array.size(merged_wick_resistance) > 0 for i = 0 to array.size(merged_wick_resistance) - 1 if lines_drawn >= max_lines break trend_line = array.get(merged_wick_resistance, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=resistance_color, width=thin_line_width, style=get_line_style(), extend=extend_type) array.push(wick_resistance_lines, line_obj) lines_drawn += 1 // Process CLOSE-based resistance lines (thick) if array.size(close_resistance_candidates) > 0 merged_close_resistance = array.new() for i = 0 to array.size(close_resistance_candidates) - 1 current_line = array.get(close_resistance_candidates, i) should_merge = false if array.size(merged_close_resistance) > 0 for j = 0 to array.size(merged_close_resistance) - 1 existing_line = array.get(merged_close_resistance, j) if lines_are_similar(current_line, existing_line, slope_tolerance, price_tolerance) merged_line = merge_lines(current_line, existing_line) array.set(merged_close_resistance, j, merged_line) should_merge := true break if not should_merge array.push(merged_close_resistance, current_line) // Draw close-based resistance lines (thick) lines_drawn = 0 if array.size(merged_close_resistance) > 0 for i = 0 to array.size(merged_close_resistance) - 1 if lines_drawn >= max_lines break trend_line = array.get(merged_close_resistance, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=close_resistance_color, width=thick_line_width, style=get_line_style(), extend=extend_type) array.push(close_resistance_lines, line_obj) lines_drawn += 1 // Process WICK-based support lines (thin) if array.size(wick_support_candidates) > 0 merged_wick_support = array.new() for i = 0 to array.size(wick_support_candidates) - 1 current_line = array.get(wick_support_candidates, i) should_merge = false if array.size(merged_wick_support) > 0 for j = 0 to array.size(merged_wick_support) - 1 existing_line = array.get(merged_wick_support, j) if lines_are_similar(current_line, existing_line, slope_tolerance, price_tolerance) merged_line = merge_lines(current_line, existing_line) array.set(merged_wick_support, j, merged_line) should_merge := true break if not should_merge array.push(merged_wick_support, current_line) // Draw wick-based support lines (thin) lines_drawn = 0 if array.size(merged_wick_support) > 0 for i = 0 to array.size(merged_wick_support) - 1 if lines_drawn >= max_lines break trend_line = array.get(merged_wick_support, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=support_color, width=thin_line_width, style=get_line_style(), extend=extend_type) array.push(wick_support_lines, line_obj) lines_drawn += 1 // Process CLOSE-based support lines (thick) if array.size(close_support_candidates) > 0 merged_close_support = array.new() for i = 0 to array.size(close_support_candidates) - 1 current_line = array.get(close_support_candidates, i) should_merge = false if array.size(merged_close_support) > 0 for j = 0 to array.size(merged_close_support) - 1 existing_line = array.get(merged_close_support, j) if lines_are_similar(current_line, existing_line, slope_tolerance, price_tolerance) merged_line = merge_lines(current_line, existing_line) array.set(merged_close_support, j, merged_line) should_merge := true break if not should_merge array.push(merged_close_support, current_line) // Draw close-based support lines (thick) lines_drawn = 0 if array.size(merged_close_support) > 0 for i = 0 to array.size(merged_close_support) - 1 if lines_drawn >= max_lines break trend_line = array.get(merged_close_support, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=close_support_color, width=thick_line_width, style=get_line_style(), extend=extend_type) array.push(close_support_lines, line_obj) lines_drawn += 1 else // Original behavior without merging - draw all valid candidates // Draw wick-based resistance lines (thin) lines_drawn = 0 if array.size(wick_resistance_candidates) > 0 for i = 0 to array.size(wick_resistance_candidates) - 1 if lines_drawn >= max_lines break trend_line = array.get(wick_resistance_candidates, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=resistance_color, width=thin_line_width, style=get_line_style(), extend=extend_type) array.push(wick_resistance_lines, line_obj) lines_drawn += 1 // Draw close-based resistance lines (thick) lines_drawn := 0 if array.size(close_resistance_candidates) > 0 for i = 0 to array.size(close_resistance_candidates) - 1 if lines_drawn >= max_lines break trend_line = array.get(close_resistance_candidates, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=close_resistance_color, width=thick_line_width, style=get_line_style(), extend=extend_type) array.push(close_resistance_lines, line_obj) lines_drawn += 1 // Draw wick-based support lines (thin) lines_drawn := 0 if array.size(wick_support_candidates) > 0 for i = 0 to array.size(wick_support_candidates) - 1 if lines_drawn >= max_lines break trend_line = array.get(wick_support_candidates, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=support_color, width=thin_line_width, style=get_line_style(), extend=extend_type) array.push(wick_support_lines, line_obj) lines_drawn += 1 // Draw close-based support lines (thick) lines_drawn := 0 if array.size(close_support_candidates) > 0 for i = 0 to array.size(close_support_candidates) - 1 if lines_drawn >= max_lines break trend_line = array.get(close_support_candidates, i) extend_type = switch line_extend "left" => extend.left "right" => extend.right "both" => extend.both => extend.none line_obj = line.new(trend_line.bar1, trend_line.price1, trend_line.bar2, trend_line.price2, color=close_support_color, width=thick_line_width, style=get_line_style(), extend=extend_type) array.push(close_support_lines, line_obj) lines_drawn += 1 // Execute trend line drawing every 10 bars to optimize performance if bar_index % 10 == 0 draw_trend_lines() // Show current bar high/low points if show_current_hl var line current_high_line = na var line current_low_line = na // Delete previous lines if not na(current_high_line) line.delete(current_high_line) if not na(current_low_line) line.delete(current_low_line) // Draw current bar high/low points current_high_line := line.new(bar_index, high, bar_index, high, color=current_hl_color, width=3, style=line.style_solid) current_low_line := line.new(bar_index, low, bar_index, low, color=current_hl_color, width=3, style=line.style_solid) // Plot pivot points for reference (optional) plot_pivots = input.bool(false, "Show Pivot Points") plotshape(plot_pivots and not na(pivot_high), style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small) plotshape(plot_pivots and not na(pivot_low), style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small) // Plot close-based pivot points (moved to global scope) plotshape(plot_pivots and not na(pivot_high_close), style=shape.diamond, location=location.abovebar, color=color.orange, size=size.small) plotshape(plot_pivots and not na(pivot_low_close), style=shape.diamond, location=location.belowbar, color=color.blue, size=size.small) // Volume filtering visualization show_volume_info = input.bool(false, "Show Volume Information") if show_volume_info // Plot volume threshold line volume_threshold = volume_avg * volume_threshold_multiplier // Create a table to show volume information var table volume_table = table.new(position.top_right, 2, 4, bgcolor=color.white, border_width=1) if barstate.islast table.cell(volume_table, 0, 0, "Volume Info", text_color=color.black, text_size=size.small) table.cell(volume_table, 1, 0, "", text_color=color.black, text_size=size.small) table.cell(volume_table, 0, 1, "Avg Volume:", text_color=color.black, text_size=size.small) table.cell(volume_table, 1, 1, str.tostring(math.round(volume_avg)), text_color=color.black, text_size=size.small) table.cell(volume_table, 0, 2, "Threshold:", text_color=color.black, text_size=size.small) table.cell(volume_table, 1, 2, str.tostring(math.round(volume_threshold)), text_color=color.black, text_size=size.small) table.cell(volume_table, 0, 3, "Current:", text_color=color.black, text_size=size.small) table.cell(volume_table, 1, 3, str.tostring(math.round(volume)), text_color=volume >= volume_threshold ? color.green : color.red, text_size=size.small) // Alert conditions resistance_break = ta.crossover(close, ta.highest(high, 5)) support_break = ta.crossunder(close, ta.lowest(low, 5)) alertcondition(resistance_break, title="Resistance Break", message="Price broke above resistance level") alertcondition(support_break, title="Support Break", message="Price broke below support level")