Pivot Points High/Low (Simplified)


Hidden Redirect Link
Loading...

How to Create a Pivot Points High/Low Indicator Using Drawings

Indie 5.4.0 introduced powerful drawings functionality — the ability to draw objects on charts that are not tied to time series. Let's step-by-step create an indicator to find local extremes (pivots) to explore these capabilities.

What are drawings?

Drawings allow you to:

  • Place labels and lines anywhere on the chart
  • Update and remove previously created objects

Unlike regular series, drawings remain on the chart and can be modified even on historical bars.

Step 1: Simple Pivot Detection

Let's start with a basic version that simply finds local highs:

# indie:lang_version = 5from indie import indicatorfrom indie.algorithms import SinceHighestfrom indie.drawings import LabelAbs, AbsolutePosition@indicator('Simple Pivots', overlay_main_pane=True)def Main(self):    # Find how many bars have passed since the last high    since_high = SinceHighest.new(self.high, 10)    # If since_high[0] == 0, the current bar is a new high    if since_high[0] == 0:        # Create a label with the current price        label = LabelAbs(            str(self.high[0]),  # Label text            AbsolutePosition(self.time[0], self.high[0])  # Position on chart        )        # Draw the label        self.chart.draw(label)
Step 1: Simple Pivot Detection
Step 1: Simple Pivot Detection

What's happening:

  • SinceHighest.new(self.high, 10) looks for the highest value in the last 10 bars
  • When it returns 0, it means a new local high
  • LabelAbs creates a text label at absolute chart coordinates
  • self.chart.draw() places the label on the chart

This code draws some labels on the chart, which works well. However, there's a problem: each time a new pivot is found, the previous one should be removed if it's too close to the new one. We'll come back to solving this later.

Step 2: Adding Low Detection and Styling

Now let's add local low detection and improve the appearance:

# indie:lang_version = 5from indie import indicator, colorfrom indie.algorithms import SinceHighest, SinceLowestfrom indie.drawings import LabelAbs, AbsolutePosition, callout_position@indicator('Pivots High/Low Basic', overlay_main_pane=True)def Main(self):    since_high = SinceHighest.new(self.high, 10)    since_low = SinceLowest.new(self.low, 10)    # New high    if since_high[0] == 0:        label = LabelAbs(            str(round(self.high[0], 2)),            AbsolutePosition(self.time[0], self.high[0]),            text_color=color.GREEN,  # Green for highs            bg_color=color.BLACK(0),  # Transparent background            callout_position=callout_position.TOP_RIGHT        )        self.chart.draw(label)    # New low    if since_low[0] == 0:        label = LabelAbs(            str(round(self.low[0], 2)),            AbsolutePosition(self.time[0], self.low[0]),            text_color=color.RED,  # Red for lows            bg_color=color.BLACK(0),            callout_position=callout_position.BOTTOM_LEFT        )        self.chart.draw(label)
Step 2: Adding Low Detection and Styling
Step 2: Adding Low Detection and Styling

What's new:

  • Added low detection using SinceLowest
  • Configured colors: green for highs, red for lows 
  • callout_position determines where the label is positioned relative to the point 
  • Used round() to format prices nicely

Now we’ve added low pivots and fine-tuned the label styles, which makes them look better. However, the issue mentioned at the end of the Step 1 chapter is still present. That’s why we now need to implement smarter label management.

Step 3: Smart Label Management with Memory

Let's add a "memory" system to control when we create new labels vs update existing ones:

# indie:lang_version = 5from indie import indicator, param, color, MainContext, Optionalfrom indie.algorithms import SinceHighest, SinceLowestfrom indie.drawings import LabelAbs, AbsolutePosition, callout_position@indicator('Pivot Points High/Low', overlay_main_pane=True)@param.int('length', default=10, min=1, title='Lookback period')class Main(MainContext):    def __init__(self, length):        none_label: Optional[LabelAbs] = None        self._high_pivot = self.new_var(none_label)        self._low_pivot = self.new_var(none_label)        self._high_pivot_bar = self.new_var(0)        self._low_pivot_bar = self.new_var(0)        self._length = length    def calc(self) -> None:        # Search for new extremes        since_high = SinceHighest.new(self.high, self._length + 1)        since_low = SinceLowest.new(self.low, self._length + 1)        if since_high[0] == 0:            self._handle_high_pivot()        if since_low[0] == 0:            self._handle_low_pivot()    def _handle_high_pivot(self) -> None:        existing_pivot = self._high_pivot.get()        if (existing_pivot is not None and            self.bar_index - self._high_pivot_bar.get() <= self._length):            # Update existing label (recent pivot)            existing_pivot.value().position = AbsolutePosition(self.time[0], self.high[0])            existing_pivot.value().text = str(round(self.high[0], 2))            self.chart.draw(existing_pivot.value())            self._high_pivot_bar.set(self.bar_index)        else:            # Create new label (no pivot or old pivot)            new_label = LabelAbs(                str(round(self.high[0], 2)),                AbsolutePosition(self.time[0], self.high[0]),                text_color=color.GREEN,                bg_color=color.BLACK(0),                callout_position=callout_position.TOP_RIGHT,                font_size=11            )            self.chart.draw(new_label)            self._high_pivot.set(new_label)            self._high_pivot_bar.set(self.bar_index)    def _handle_low_pivot(self) -> None:        existing_pivot = self._low_pivot.get()        if (existing_pivot is not None and            self.bar_index - self._low_pivot_bar.get() <= self._length):            # Update existing label (recent pivot)            existing_pivot.value().position = AbsolutePosition(self.time[0], self.low[0])            existing_pivot.value().text = str(round(self.low[0], 2))            self.chart.draw(existing_pivot.value())            self._low_pivot_bar.set(self.bar_index)        else:            # Create new label (no pivot or old pivot)            new_label = LabelAbs(                str(round(self.low[0], 2)),                AbsolutePosition(self.time[0], self.low[0]),                text_color=color.RED,                bg_color=color.BLACK(0),                callout_position=callout_position.BOTTOM_LEFT,                font_size=11            )            self.chart.draw(new_label)            self._low_pivot.set(new_label)            self._low_pivot_bar.set(self.bar_index)

Key concepts: 

  • self.new_var() creates variables that persist between calc() calls 
  • Memory management: Remember recent pivots, automatically create new ones for distant pivots
  • Smart updating: Update existing labels if they're recent (within lookback period)
  • Configurable: Users can adjust the lookback period via Settings panel

How it works: 

  • Recent pivots (< lookback period): Label moves to new extreme 
  • Distant pivots (≥ lookback period): Old label stays fixed, new label created 
  • Result: Clean spacing with automatic pivot management
Step 3: Smart Label Management with Memory
Step 3: Smart Label Management with Memory

Redundant labels are no longer being rendered on the chart. Great progress!

Core Principles of Working with Drawings

  1. Creating objects: LabelAbs, LineSegment, etc.
  2. Placement: self.chart.draw(object)
  3. Updating: Change object properties and call draw() again 
  4. Removal: self.chart.erase(object)
  5. State management: Use self.new_var() to store data between bars

Drawings open up huge possibilities for creating interactive and informative indicators in Indie!

education
indie
pivots
© Licensed under MIT

Comments

loading