OPEN-SOURCE SCRIPT

PIP Algorithm

1 317


# **Script Overview (For Non-Coders)**

1. **Purpose**
- The script tries to capture the essential “shape” of price movement by selecting a limited number of “key points” (anchors) from the latest bars.
- After selecting these anchors, it draws straight lines between them, effectively simplifying the price chart into a smaller set of points without losing major swings.

2. **How It Works, Step by Step**
1. We look back a certain number of bars (e.g., 50).
2. We start by drawing a straight line from the **oldest** bar in that range to the **newest** bar—just two points.
3. Next, we find the bar whose price is *farthest away* from that straight line. That becomes a new anchor point.
4. We “snap” (pin) the line to go exactly through that new anchor. Then we re-draw (re-interpolate) the entire line from the first anchor to the last, in segments.
5. We repeat the process (adding more anchors) until we reach the desired number of points. Each time, we choose the biggest gap between our line and the actual price, then re-draw the entire shape.
6. Finally, we connect these anchors on the chart with red lines, visually simplifying the price curve.

3. **Why It’s Useful**
- It highlights the most *important* bends or swings in the price over the chosen window.
- Instead of plotting every single bar, it condenses the information down to the “key turning points.”

4. **Key Takeaway**
- You’ll see a small number of red line segments connecting the **most significant** points in the price data.
- This is especially helpful if you want a simplified view of recent price action without minor fluctuations.


## **Detailed Logic Explanation**
# **Script Breakdown (For Coders)**

Pine Script®
//@version=5 indicator(title="PIP Algorithm", overlay=true) // 1. Inputs length = input.int(50, title="Lookback Length") num_points = input.int(5, title="Number of PIP Points (≥ 3)") // 2. Helper Functions // --------------------------------------------------------------------- // reInterpSubrange(...): // Given two “anchor” indices in `linesArr`, linearly interpolate // the array values in between so that the subrange forms a straight line // from linesArr[segmentLeft] to linesArr[segmentRight]. reInterpSubrange(linesArr, segmentLeft, segmentRight) => float leftVal = array.get(linesArr, segmentLeft) float rightVal = array.get(linesArr, segmentRight) int segmentLen = segmentRight - segmentLeft if segmentLen > 1 for i = segmentLeft + 1 to segmentRight - 1 float ratio = (i - segmentLeft) / segmentLen float interpVal = leftVal + (rightVal - leftVal) * ratio array.set(linesArr, i, interpVal) // reInterpolateAllSegments(...): // For the entire “linesArr,” re-interpolate each subrange between // consecutive breakpoints in `lineBreaksArr`. // This ensures the line is globally correct after each new anchor insertion. reInterpolateAllSegments(linesArr, lineBreaksArr) => array.sort(lineBreaksArr, order.asc) for i = 0 to array.size(lineBreaksArr) - 2 int leftEdge = array.get(lineBreaksArr, i) int rightEdge = array.get(lineBreaksArr, i + 1) reInterpSubrange(linesArr, leftEdge, rightEdge) // getMaxDistanceIndex(...): // Return the index (bar) that is farthest from the current “linesArr.” // We skip any indices already in `lineBreaksArr`. getMaxDistanceIndex(linesArr, closeArr, lineBreaksArr) => float maxDist = -1.0 int maxIdx = -1 int sizeData = array.size(linesArr) for i = 1 to sizeData - 2 bool isBreak = false for b = 0 to array.size(lineBreaksArr) - 1 if i == array.get(lineBreaksArr, b) isBreak := true break if not isBreak float dist = math.abs(array.get(linesArr, i) - array.get(closeArr, i)) if dist > maxDist maxDist := dist maxIdx := i maxIdx // snapAndReinterpolate(...): // "Snap" a chosen index to its actual close price, then re-interpolate the entire line again. snapAndReinterpolate(linesArr, closeArr, lineBreaksArr, idxToSnap) => if idxToSnap >= 0 float snapVal = array.get(closeArr, idxToSnap) array.set(linesArr, idxToSnap, snapVal) reInterpolateAllSegments(linesArr, lineBreaksArr) // 3. Global Arrays and Flags // --------------------------------------------------------------------- // We store final data globally, then use them outside the barstate.islast scope to draw lines. var float[] finalCloseData = array.new_float() var float[] finalLines = array.new_float() var int[] finalLineBreaks = array.new_int() var bool didCompute = false var line[] pipLines = array.new_line() // 4. Main Logic (Runs Once at the End of the Current Bar) // --------------------------------------------------------------------- if barstate.islast // A) Prepare closeData in forward order (index 0 = oldest bar, index length-1 = newest) float[] closeData = array.new_float() for i = 0 to length - 1 array.push(closeData, close) // B) Initialize linesArr with a simple linear interpolation from the first to the last point float[] linesArr = array.new_float() float firstClose = array.get(closeData, 0) float lastClose = array.get(closeData, length - 1) for i = 0 to length - 1 float ratio = (length > 1) ? (i / float(length - 1)) : 0.0 float val = firstClose + (lastClose - firstClose) * ratio array.push(linesArr, val) // C) Initialize lineBreaks with two anchors: 0 (oldest) and length-1 (newest) int[] lineBreaks = array.new_int() array.push(lineBreaks, 0) array.push(lineBreaks, length - 1) // D) Iteratively insert new breakpoints, always re-interpolating globally int iterationsNeeded = math.max(num_points - 2, 0) for _iteration = 1 to iterationsNeeded // 1) Re-interpolate entire shape, so it's globally up to date reInterpolateAllSegments(linesArr, lineBreaks) // 2) Find the bar with the largest vertical distance to this line int maxDistIdx = getMaxDistanceIndex(linesArr, closeData, lineBreaks) if maxDistIdx == -1 break // 3) Insert that bar index into lineBreaks and snap it array.push(lineBreaks, maxDistIdx) array.sort(lineBreaks, order.asc) snapAndReinterpolate(linesArr, closeData, lineBreaks, maxDistIdx) // E) Save results into global arrays for line drawing outside barstate.islast array.clear(finalCloseData) array.clear(finalLines) array.clear(finalLineBreaks) for i = 0 to array.size(closeData) - 1 array.push(finalCloseData, array.get(closeData, i)) array.push(finalLines, array.get(linesArr, i)) for b = 0 to array.size(lineBreaks) - 1 array.push(finalLineBreaks, array.get(lineBreaks, b)) didCompute := true // 5. Drawing the Lines in Global Scope // --------------------------------------------------------------------- // We cannot create lines inside barstate.islast, so we do it outside. array.clear(pipLines) if didCompute // Connect each pair of anchors with red lines if array.size(finalLineBreaks) > 1 for i = 0 to array.size(finalLineBreaks) - 2 int idxLeft = array.get(finalLineBreaks, i) int idxRight = array.get(finalLineBreaks, i + 1) float x1 = bar_index - (length - 1) + idxLeft float x2 = bar_index - (length - 1) + idxRight float y1 = array.get(finalCloseData, idxLeft) float y2 = array.get(finalCloseData, idxRight) line ln = line.new(x1, y1, x2, y2, extend=extend.none) line.set_color(ln, color.red) line.set_width(ln, 2) array.push(pipLines, ln)

1. **Data Collection**
- We collect the **most recent** `length` bars in `closeData`. Index 0 is the oldest bar in that window, index `length-1` is the newest bar.

2. **Initial Straight Line**
- We create an array called `linesArr` that starts as a simple linear interpolation from `closeData[0]` (the oldest bar’s close) to `closeData[length-1]` (the newest bar’s close).

3. **Line Breaks**
- We store “anchor points” in `lineBreaks`, initially `[0, length - 1]`. These are the start and end of our segment.

4. **Global Re-Interpolation**
- Each time we want to add a new anchor, we **re-draw** (linear interpolation) for *every* subrange `[lineBreaks, lineBreaks[i+1]]`, ensuring we have a globally consistent line.
- This avoids the “local subrange only” approach, which can cause clustering near existing anchors.

5. **Finding the Largest Distance**
- After re-drawing, we compute the vertical distance for each bar `i` that isn’t already a line break. The bar with the biggest distance from the line is chosen as the next anchor (`maxDistIdx`).

6. **Snapping and Re-Interpolate**
- We “snap” that bar’s line value to the actual close, i.e. `linesArr[maxDistIdx] = closeData[maxDistIdx]`. Then we globally re-draw all segments again.

7. **Repeat**
- We repeat these insertions until we have the desired number of points (`num_points`).

8. **Drawing**
- Finally, we connect each consecutive pair of anchor points (`lineBreaks`) with a `line.new(...)` call, coloring them red.
- We offset the line’s `x` coordinate so that the anchor at index 0 lines up with `bar_index - (length - 1)`, and the anchor at index `length-1` lines up with `bar_index` (the current bar).

**Result**:
You get a simplified representation of the price with a small set of line segments capturing the largest “jumps” or swings. By re-drawing the entire line after each insertion, the anchors tend to distribute more *evenly* across the data, mitigating the issue where anchors bunch up near each other.



Enjoy experimenting with different `length` and `num_points` to see how the simplified lines change!

Declinazione di responsabilità

Le informazioni ed i contenuti pubblicati non costituiscono in alcun modo una sollecitazione ad investire o ad operare nei mercati finanziari. Non sono inoltre fornite o supportate da TradingView. Maggiori dettagli nelle Condizioni d'uso.