On this page
Trend Line Methods (TLM) - Technical Guide
Draws dynamic trend lines from recent pivot points and a 5-segment linear regression channel.
| Language | Indie Script v5 |
| Platform | TakeProfit |
| Category | Support & resistance |
| Type | Indicator |
| Author | @chartjunkie on TakeProfit |
| License | MIT |
| Live script | Open on TakeProfit |
| Source file | Trend Line Methods (TLM).indie5 |
Overview
The Trend Line Methods indicator draws two independent sets of trend lines on the price chart. The Pivot Span method connects the oldest and newest pivot highs and lows within a user-defined lookback, creating lines that adapt as new pivots form. The 5-Point Channel method divides the lookback into five segments of roughly equal length, finds the extreme high and low in each segment, and fits an ordinary least squares regression line through those points, forming a channel.
These lines are intended to highlight potential dynamic support and resistance levels and to visualize the prevailing slope of price extremes. By default, pivot span lines are dashed orange, and the 5-point channel lines are solid fuchsia. Both methods can be enabled independently and customized with color, width, and line style.
How it works
- If Pivot Span is enabled, detect pivot highs and lows using PivotHighLow with the given left/right bars.
- When a new pivot high is confirmed (not NaN), append its bar index and price to a rolling list, keeping only the most recent pivot_count entries.
- When a new pivot low is confirmed, do the same for the low list.
- If at least two pivot highs exist, compute the slope between the oldest and newest pivot, then draw a line segment from the start of the lookback to the current bar using that slope.
- Repeat the drawing step for pivot lows, erasing any previously drawn line first.
- If 5-Point Channel is enabled and enough bars exist, split the lookback into five segments and find the maximum high and minimum low in each segment.
- Collect the (x, y) pairs for the found highs and lows, then compute OLS regression slope and intercept for each set.
- Draw the regression line for highs and for lows across the lookback, erasing previous lines.
Mathematical model
Pivot Span slope
\[slope = \frac{price_{newest} - price_{oldest}}{index_{newest} - index_{oldest}}\] \[line\_price(x) = price_{oldest} + slope \cdot (x - index_{oldest})\]5-Point Channel OLS regression
For a set of (n) points ((x_i, y_i)):
\[slope = \frac{n \sum x_i y_i - \sum x_i \sum y_i}{n \sum x_i^2 - (\sum x_i)^2}\] \[intercept = \frac{\sum y_i - slope \cdot \sum x_i}{n}\] \[line\_price(x) = intercept + slope \cdot x\]Logic flow
flowchart TD
A["Start calc"] --> B{"enable_pivot_span?"}
B -->|Yes| C["Detect pivot high/low"]
C --> D{"New pivot high?"}
D -->|Yes| E["Append to high list, trim to pivot_count"]
D -->|No| F{"New pivot low?"}
F -->|Yes| G["Append to low list, trim"]
F -->|No| H["Check high list length"]
E --> H
G --> H
H --> I{"len >= 2?"}
I -->|Yes| J["Compute slope, draw high line"]
I -->|No| K["Check low list length"]
J --> K
K --> L{"len >= 2?"}
L -->|Yes| M["Compute slope, draw low line"]
L -->|No| N["End pivot span"]
M --> N
B -->|No| N
N --> O{"enable_five_point?"}
O -->|Yes| P["Split lookback into 5 segments"]
P --> Q["Find max high & min low per segment"]
Q --> R["Collect (x,y) pairs"]
R --> S{"n_hi >= 2?"}
S -->|Yes| T["OLS regression, draw high line"]
S -->|No| U{"n_lo >= 2?"}
T --> U
U -->|Yes| V["OLS regression, draw low line"]
U -->|No| W["Done"]
V --> W
O -->|No| W
Parameters
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
enable_pivot_span |
bool | true | Pivot Span | |
pivot_high_color |
color | color.ORANGE | High Color | |
pivot_low_color |
color | color.ORANGE | Low Color | |
pivot_left |
int | 5 | ≥ 1 | Pivot Left |
pivot_right |
int | 5 | ≥ 1 | Pivot Right |
pivot_count |
int | 5 | ≥ 2 | Pivot Count |
pivot_lookback |
int | 150 | ≥ 10 | Lookback |
pivot_line_width |
int | 2 | 1 - 10 | Pivot Line Width |
pivot_line_style |
int | 1 | 0 - 2 | Pivot Style 0/1/2 |
enable_five_point |
bool | false | 5-Point Channel | |
five_high_color |
color | color.FUCHSIA | 5pt High | |
five_low_color |
color | color.FUCHSIA | 5pt Low | |
five_lookback |
int | 100 | ≥ 10 | 5pt Lookback |
five_line_width |
int | 3 | 1 - 10 | 5pt Line Width |
five_line_style |
int | 0 | 0 - 2 | 5pt Style 0/1/2 |
Code walkthrough
Collecting pivot points
Lines 76-93 of Trend Line Methods (TLM).indie5:
if not isnan(ph[0]):
piv_hi_idx = self.bar_index - pivot_right
piv_hi_price = self.high[pivot_right]
self._high_idx_points.get().append(piv_hi_idx)
self._high_val_points.get().append(piv_hi_price)
while len(self._high_idx_points.get()) > pivot_count:
self._high_idx_points.get().pop(0)
self._high_val_points.get().pop(0)
# Collect pivot lows
if not isnan(pl[0]):
piv_lo_idx = self.bar_index - pivot_right
piv_lo_price = self.low[pivot_right]
self._low_idx_points.get().append(piv_lo_idx)
self._low_val_points.get().append(piv_lo_price)
while len(self._low_idx_points.get()) > pivot_count:
self._low_idx_points.get().pop(0)
self._low_val_points.get().pop(0)
When a pivot high is detected (ph[0] is not NaN), the code calculates the bar index where the pivot actually occurred (pivot_right bars ago) and the corresponding high price. These values are appended to rolling lists. The while loop ensures the lists never exceed pivot_count entries by removing the oldest element from the front. The same logic is applied for pivot lows. This maintains a sliding window of the most recent confirmed pivots.
Drawing the pivot high trend line
Lines 96-125 of Trend Line Methods (TLM).indie5:
if len(self._high_idx_points.get()) >= 2:
far_hi_idx = self._high_idx_points.get()[0]
far_hi_val = self._high_val_points.get()[0]
near_hi_idx = self._high_idx_points.get()[-1]
near_hi_val = self._high_val_points.get()[-1]
hi_bar_diff = near_hi_idx - far_hi_idx
# Declare slope before conditional (Indie scoping)
hi_slope = 0.0
if hi_bar_diff != 0:
hi_slope = (near_hi_val - far_hi_val) / hi_bar_diff
hi_intercept = far_hi_val - hi_slope * far_hi_idx
x1_hi = self.bar_index - (pivot_lookback - 1)
x2_hi = self.bar_index
y1_hi = hi_intercept + hi_slope * x1_hi
y2_hi = hi_intercept + hi_slope * x2_hi
if self._high_trend_line.get() is not None:
self.chart.erase(self._high_trend_line.get().value())
line = LineSegment(
AbsolutePosition(self.time[pivot_lookback - 1], y1_hi),
AbsolutePosition(self.time[0], y2_hi),
color=pivot_high_color,
line_style=p_style,
line_width=pivot_line_width
)
self._high_trend_line.set(line)
self.chart.draw(line)
If at least two pivot highs are stored, the oldest (index 0) and newest (index -1) are used to compute the slope. A zero bar difference is guarded against to avoid division by zero. The line’s y-values are calculated for the left edge (pivot_lookback - 1 bars ago) and the current bar using the linear equation. Any previously drawn high line is erased, then a new LineSegment is created with the chosen color, style, and width, and drawn on the chart.
Segment loop for 5-point channel
Lines 178-220 of Trend Line Methods (TLM).indie5:
for k in range(5):
seg_start = k * seg_len_base
remaining = five_lookback - seg_start
if remaining <= 0:
break
seg_len_k = min(seg_len_base, remaining) if k < 4 else remaining
max_hi = nan
barsAgo_hi = -1
for i in range(seg_len_k):
sh = seg_start + i
v = self.high[sh]
if not isnan(v) and (isnan(max_hi) or v > max_hi):
max_hi = v
barsAgo_hi = sh
if barsAgo_hi >= 0:
x_hi = float(self.bar_index - barsAgo_hi)
y_hi = self.high[barsAgo_hi]
sum_x_hi += x_hi
sum_y_hi += y_hi
sum_xy_hi += x_hi * y_hi
sum_x2_hi += x_hi * x_hi
n_hi += 1
min_lo = nan
barsAgo_lo = -1
for i in range(seg_len_k):
sl = seg_start + i
v2 = self.low[sl]
if not isnan(v2) and (isnan(min_lo) or v2 < min_lo):
min_lo = v2
barsAgo_lo = sl
if barsAgo_lo >= 0:
x_lo = float(self.bar_index - barsAgo_lo)
y_lo = self.low[barsAgo_lo]
sum_x_lo += x_lo
sum_y_lo += y_lo
sum_xy_lo += x_lo * y_lo
sum_x2_lo += x_lo * x_lo
n_lo += 1
The lookback is divided into five segments of roughly equal length. For each segment, the code scans all bars in that segment to find the maximum high and minimum low, ignoring NaN values. The bar index (converted to an x-coordinate (the bar index)) and the price are accumulated into sums for later regression. The loop handles the last segment’s remaining bars and breaks early if no bars are left.
OLS regression for the high line
Lines 223-231 of Trend Line Methods (TLM).indie5:
if n_hi >= 2:
nf_hi = float(n_hi)
denom_hi = nf_hi * sum_x2_hi - sum_x_hi * sum_x_hi
# Declare slope before conditional
slope_hi = 0.0
if denom_hi != 0.0:
slope_hi = (nf_hi * sum_xy_hi - sum_x_hi * sum_y_hi) / denom_hi
intercept_hi = (sum_y_hi - slope_hi * sum_x_hi) / nf_hi
With at least two high points collected, the ordinary least squares formulas are applied. The denominator is checked for zero to avoid division errors. The slope and intercept are computed from the accumulated sums. These coefficients define the best-fit line through the segment highs.
Drawing the 5-point high line
Lines 241-249 of Trend Line Methods (TLM).indie5:
line = LineSegment(
AbsolutePosition(self.time[five_lookback - 1], y1_hi_5pt),
AbsolutePosition(self.time[0], y2_hi_5pt),
color=five_high_color,
line_style=f_style,
line_width=five_line_width
)
self._five_high_line.set(line)
self.chart.draw(line)
The regression line is extended across the entire lookback by evaluating the linear equation at the leftmost and rightmost bar indices. The previous high line is erased, and a new LineSegment is drawn using the five_high_color and the selected line style and width. The same pattern is repeated for the low line.
Reading the chart
- Pivot Span lines (default dashed orange): Connect the oldest and newest pivot highs/lows within the lookback. They act as dynamic trendlines that may serve as support (low line) or resistance (high line).
- 5-Point Channel lines (default solid fuchsia): Represent a linear regression channel through the extreme highs and lows of five segments. The upper line often acts as resistance, the lower as support.
- Both line types extend from the start of the lookback to the current bar and update on each new bar. Because pivot lines are drawn only after confirmation (pivot_right bars later), they may repaint historically.
- No markers or labels are drawn; only the line segments appear on the chart.
Implementation notes
- Pivot lines repaint: a pivot is confirmed only after pivot_right bars, so the line may change as new pivots appear within that window.
- NaN values from PivotHighLow are explicitly checked; pivots are only collected when ph[0] or pl[0] is not NaN.
- Line style integers 0,1,2 are mapped to SOLID, DASHED, DOTTED via conditional blocks; any other value defaults to SOLID.
- The 5-point channel requires bar_index >= five_lookback to avoid accessing negative indices; segment length calculation uses floor division and handles the last segment’s remainder.
FAQ
How can I change the number of pivot points used for the Pivot Span line?
Adjust the ‘Pivot Count’ parameter. The indicator keeps only the most recent N pivot highs and lows, then draws a line between the oldest and newest in that set.
Why do the lines sometimes disappear or jump?
Pivot lines require at least two confirmed pivots within the lookback. If fewer exist, no line is drawn. Also, because pivots confirm after pivot_right bars, the line may repaint as new pivots appear or old ones fall out of the rolling window.
Can I use this indicator on a higher timeframe while viewing a lower one?
The code does not use sec_context or calc_on, so it operates only on the chart’s current timeframe. To apply it to a higher timeframe, you would need to modify the script to request a secondary context.
Full source code
Indie Script v5, as published on TakeProfit. Copy it into the platform’s script editor or open the live script.
# indie:lang_version = 5
# =============================================================================
# Trend Line Methods Indicator v1.0.0
# =============================================================================
#
# METHOD 1: PIVOT SPAN - Two-point pivot trendlines (oldest <-> newest pivot)
# METHOD 2: 5-POINT CHANNEL - OLS linear regression through 5 segment extremes
#
# REPAINT: Pivot lines confirm after pivot_right bars. This is expected behavior.
# LINE STYLES: 0 = Solid, 1 = Dashed, 2 = Dotted
# =============================================================================
from indie import indicator, param, color, MainContext, Optional
from indie.algorithms import PivotHighLow
from indie.drawings import LineSegment, AbsolutePosition, line_segment_style
from math import isnan, nan, floor
@indicator('Trend Line Methods', overlay_main_pane=True)
@param.bool('enable_pivot_span', default=True, title='Pivot Span')
@param.color('pivot_high_color', default=color.ORANGE, title='High Color')
@param.color('pivot_low_color', default=color.ORANGE, title='Low Color')
@param.int('pivot_left', default=5, min=1, title='Pivot Left')
@param.int('pivot_right', default=5, min=1, title='Pivot Right')
@param.int('pivot_count', default=5, min=2, title='Pivot Count')
@param.int('pivot_lookback', default=150, min=10, title='Lookback')
@param.int('pivot_line_width', default=2, min=1, max=10, title='Pivot Line Width')
@param.int('pivot_line_style', default=1, min=0, max=2, title='Pivot Style 0/1/2')
@param.bool('enable_five_point', default=False, title='5-Point Channel')
@param.color('five_high_color', default=color.FUCHSIA, title='5pt High')
@param.color('five_low_color', default=color.FUCHSIA, title='5pt Low')
@param.int('five_lookback', default=100, min=10, title='5pt Lookback')
@param.int('five_line_width', default=3, min=1, max=10, title='5pt Line Width')
@param.int('five_line_style', default=0, min=0, max=2, title='5pt Style 0/1/2')
class Main(MainContext):
def __init__(self):
empty_int_list: list[int] = []
empty_float_list: list[float] = []
self._high_idx_points = self.new_var(empty_int_list)
self._high_val_points = self.new_var(empty_float_list)
self._low_idx_points = self.new_var(empty_int_list)
self._low_val_points = self.new_var(empty_float_list)
none_line: Optional[LineSegment] = None
self._high_trend_line = self.new_var(none_line)
self._low_trend_line = self.new_var(none_line)
self._five_high_line = self.new_var(none_line)
self._five_low_line = self.new_var(none_line)
def calc(self, enable_pivot_span, pivot_high_color, pivot_low_color,
pivot_left, pivot_right, pivot_count, pivot_lookback,
pivot_line_width, pivot_line_style,
enable_five_point, five_high_color, five_low_color, five_lookback,
five_line_width, five_line_style):
# Get line styles
p_style = line_segment_style.SOLID
if pivot_line_style == 1:
p_style = line_segment_style.DASHED
elif pivot_line_style == 2:
p_style = line_segment_style.DOTTED
f_style = line_segment_style.SOLID
if five_line_style == 1:
f_style = line_segment_style.DASHED
elif five_line_style == 2:
f_style = line_segment_style.DOTTED
# =====================================================================
# METHOD 1: PIVOT SPAN
# =====================================================================
if enable_pivot_span:
ph, _ = PivotHighLow.new(self.high, left_bars=pivot_left, right_bars=pivot_right)
_, pl = PivotHighLow.new(self.low, left_bars=pivot_left, right_bars=pivot_right)
# Collect pivot highs
if not isnan(ph[0]):
piv_hi_idx = self.bar_index - pivot_right
piv_hi_price = self.high[pivot_right]
self._high_idx_points.get().append(piv_hi_idx)
self._high_val_points.get().append(piv_hi_price)
while len(self._high_idx_points.get()) > pivot_count:
self._high_idx_points.get().pop(0)
self._high_val_points.get().pop(0)
# Collect pivot lows
if not isnan(pl[0]):
piv_lo_idx = self.bar_index - pivot_right
piv_lo_price = self.low[pivot_right]
self._low_idx_points.get().append(piv_lo_idx)
self._low_val_points.get().append(piv_lo_price)
while len(self._low_idx_points.get()) > pivot_count:
self._low_idx_points.get().pop(0)
self._low_val_points.get().pop(0)
# Draw high trend line
if len(self._high_idx_points.get()) >= 2:
far_hi_idx = self._high_idx_points.get()[0]
far_hi_val = self._high_val_points.get()[0]
near_hi_idx = self._high_idx_points.get()[-1]
near_hi_val = self._high_val_points.get()[-1]
hi_bar_diff = near_hi_idx - far_hi_idx
# Declare slope before conditional (Indie scoping)
hi_slope = 0.0
if hi_bar_diff != 0:
hi_slope = (near_hi_val - far_hi_val) / hi_bar_diff
hi_intercept = far_hi_val - hi_slope * far_hi_idx
x1_hi = self.bar_index - (pivot_lookback - 1)
x2_hi = self.bar_index
y1_hi = hi_intercept + hi_slope * x1_hi
y2_hi = hi_intercept + hi_slope * x2_hi
if self._high_trend_line.get() is not None:
self.chart.erase(self._high_trend_line.get().value())
line = LineSegment(
AbsolutePosition(self.time[pivot_lookback - 1], y1_hi),
AbsolutePosition(self.time[0], y2_hi),
color=pivot_high_color,
line_style=p_style,
line_width=pivot_line_width
)
self._high_trend_line.set(line)
self.chart.draw(line)
# Draw low trend line
if len(self._low_idx_points.get()) >= 2:
far_lo_idx = self._low_idx_points.get()[0]
far_lo_val = self._low_val_points.get()[0]
near_lo_idx = self._low_idx_points.get()[-1]
near_lo_val = self._low_val_points.get()[-1]
lo_bar_diff = near_lo_idx - far_lo_idx
# Declare slope before conditional (Indie scoping)
lo_slope = 0.0
if lo_bar_diff != 0:
lo_slope = (near_lo_val - far_lo_val) / lo_bar_diff
lo_intercept = far_lo_val - lo_slope * far_lo_idx
x1_lo = self.bar_index - (pivot_lookback - 1)
x2_lo = self.bar_index
y1_lo = lo_intercept + lo_slope * x1_lo
y2_lo = lo_intercept + lo_slope * x2_lo
if self._low_trend_line.get() is not None:
self.chart.erase(self._low_trend_line.get().value())
line = LineSegment(
AbsolutePosition(self.time[pivot_lookback - 1], y1_lo),
AbsolutePosition(self.time[0], y2_lo),
color=pivot_low_color,
line_style=p_style,
line_width=pivot_line_width
)
self._low_trend_line.set(line)
self.chart.draw(line)
# =====================================================================
# METHOD 2: 5-POINT CHANNEL
# =====================================================================
if enable_five_point:
if self.bar_index >= five_lookback:
sum_x_hi = 0.0
sum_y_hi = 0.0
sum_xy_hi = 0.0
sum_x2_hi = 0.0
n_hi = 0
sum_x_lo = 0.0
sum_y_lo = 0.0
sum_xy_lo = 0.0
sum_x2_lo = 0.0
n_lo = 0
seg_len_base = max(1, floor(five_lookback / 5))
for k in range(5):
seg_start = k * seg_len_base
remaining = five_lookback - seg_start
if remaining <= 0:
break
seg_len_k = min(seg_len_base, remaining) if k < 4 else remaining
max_hi = nan
barsAgo_hi = -1
for i in range(seg_len_k):
sh = seg_start + i
v = self.high[sh]
if not isnan(v) and (isnan(max_hi) or v > max_hi):
max_hi = v
barsAgo_hi = sh
if barsAgo_hi >= 0:
x_hi = float(self.bar_index - barsAgo_hi)
y_hi = self.high[barsAgo_hi]
sum_x_hi += x_hi
sum_y_hi += y_hi
sum_xy_hi += x_hi * y_hi
sum_x2_hi += x_hi * x_hi
n_hi += 1
min_lo = nan
barsAgo_lo = -1
for i in range(seg_len_k):
sl = seg_start + i
v2 = self.low[sl]
if not isnan(v2) and (isnan(min_lo) or v2 < min_lo):
min_lo = v2
barsAgo_lo = sl
if barsAgo_lo >= 0:
x_lo = float(self.bar_index - barsAgo_lo)
y_lo = self.low[barsAgo_lo]
sum_x_lo += x_lo
sum_y_lo += y_lo
sum_xy_lo += x_lo * y_lo
sum_x2_lo += x_lo * x_lo
n_lo += 1
# Draw five-point high line
if n_hi >= 2:
nf_hi = float(n_hi)
denom_hi = nf_hi * sum_x2_hi - sum_x_hi * sum_x_hi
# Declare slope before conditional
slope_hi = 0.0
if denom_hi != 0.0:
slope_hi = (nf_hi * sum_xy_hi - sum_x_hi * sum_y_hi) / denom_hi
intercept_hi = (sum_y_hi - slope_hi * sum_x_hi) / nf_hi
x1_5pt = self.bar_index - five_lookback + 1
x2_5pt = self.bar_index
y1_hi_5pt = intercept_hi + slope_hi * float(x1_5pt)
y2_hi_5pt = intercept_hi + slope_hi * float(x2_5pt)
if self._five_high_line.get() is not None:
self.chart.erase(self._five_high_line.get().value())
line = LineSegment(
AbsolutePosition(self.time[five_lookback - 1], y1_hi_5pt),
AbsolutePosition(self.time[0], y2_hi_5pt),
color=five_high_color,
line_style=f_style,
line_width=five_line_width
)
self._five_high_line.set(line)
self.chart.draw(line)
# Draw five-point low line
if n_lo >= 2:
nf_lo = float(n_lo)
denom_lo = nf_lo * sum_x2_lo - sum_x_lo * sum_x_lo
# Declare slope before conditional
slope_lo = 0.0
if denom_lo != 0.0:
slope_lo = (nf_lo * sum_xy_lo - sum_x_lo * sum_y_lo) / denom_lo
intercept_lo = (sum_y_lo - slope_lo * sum_x_lo) / nf_lo
x1_5pt = self.bar_index - five_lookback + 1
x2_5pt = self.bar_index
y1_lo_5pt = intercept_lo + slope_lo * float(x1_5pt)
y2_lo_5pt = intercept_lo + slope_lo * float(x2_5pt)
if self._five_low_line.get() is not None:
self.chart.erase(self._five_low_line.get().value())
line = LineSegment(
AbsolutePosition(self.time[five_lookback - 1], y1_lo_5pt),
AbsolutePosition(self.time[0], y2_lo_5pt),
color=five_low_color,
line_style=f_style,
line_width=five_line_width
)
self._five_low_line.set(line)
self.chart.draw(line)
return