Buying the first close above a resistance is one of the most common ways to get trapped: many breakouts fail and price falls back into the range. A popular way to filter them is to wait for the retest, the moment when price comes back to the broken level, holds it and closes on the right side.
The Pivot Breakout High & Low Signals indicator by Jos-ProTrader automates that sequence on the price chart. It tracks the last confirmed pivot high and pivot low, detects the close that breaks them, waits for price to come back to the broken level, and prints a BUY or SELL signal when the retest holds, optionally only in the direction of a 200-period EMA.
The indicator works in four steps:
After a signal the indicator stops waiting on that side until the next breakout, so each breakout gives at most one signal. A retest that the EMA filter rejects does not cancel the wait: a later retest of the same level can still trigger the signal.
Since pivots are confirmed a few bars late, the steps move to the new level with that delay: this is normal, the indicator never uses information from the future.
//---------------------------------------------------------------
// PRC_Pivot Breakout HL
// version = 0
// 27.10.2026
// Iván González @ www.prorealcode.com
// Author: Jos-ProTrader
// Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
DEFPARAM DRAWONLASTBARONLY = true
// ===================== INPUTS =====================
pivotLen = 5 // Pivot length: bars on each side of a pivot (min 2)
retestTolPct = 0.3 // Retest tolerance (%) around the breakout level
emaFilter = 1 // 1 = use the EMA trend filter, 0 = off
emaLength = 200 // EMA length
prd = max(2, round(pivotLen))
retestTol = retestTolPct / 100
// ===================== EMA =====================
emaV = average[emaLength, 1](close)
IF emaFilter = 1 THEN
emaA = 255
ELSE
emaA = 0
ENDIF
// ===================== PIVOT DETECTION =====================
// confirmed prd bars later: strictly above the prd bars after it,
// at least as high as the prd bars before it
once lastPivotHigh = undefined
once lastPivotLow = undefined
IF barindex >= 2 * prd THEN
hhAfter = highest[prd](high)
llAfter = lowest[prd](low)
IF high[prd] > hhAfter AND high[prd] >= hhAfter[prd + 1] THEN
lastPivotHigh = high[prd]
ENDIF
IF low[prd] < llAfter AND low[prd] <= llAfter[prd + 1] THEN
lastPivotLow = low[prd]
ENDIF
ENDIF
// ===================== BREAKOUT DETECTION =====================
bullBreakout = close crosses over lastPivotHigh
bearBreakout = close crosses under lastPivotLow
once bullLevel = undefined
once bearLevel = undefined
once waitBullRetest = 0
once waitBearRetest = 0
IF bullBreakout THEN
bullLevel = lastPivotHigh
waitBullRetest = 1
ENDIF
IF bearBreakout THEN
bearLevel = lastPivotLow
waitBearRetest = 1
ENDIF
// ===================== RETEST LOGIC =====================
bullRetest = 0
IF waitBullRetest = 1 THEN
IF low <= bullLevel * (1 + retestTol) AND low >= bullLevel * (1 - retestTol) AND close > bullLevel THEN
bullRetest = 1
ENDIF
ENDIF
bearRetest = 0
IF waitBearRetest = 1 THEN
IF high >= bearLevel * (1 - retestTol) AND high <= bearLevel * (1 + retestTol) AND close < bearLevel THEN
bearRetest = 1
ENDIF
ENDIF
// ===================== EMA FILTER =====================
buySignal = 0
IF bullRetest = 1 AND (emaFilter = 0 OR close > emaV) THEN
buySignal = 1
ENDIF
sellSignal = 0
IF bearRetest = 1 AND (emaFilter = 0 OR close < emaV) THEN
sellSignal = 1
ENDIF
// ===================== RESET =====================
IF buySignal = 1 THEN
waitBullRetest = 0
ENDIF
IF sellSignal = 1 THEN
waitBearRetest = 0
ENDIF
// ===================== SIGNALS AND BACKGROUND =====================
IF barindex >= 14 THEN
sigOff = averagetruerange[14](close)
ELSE
sigOff = high - low
ENDIF
// signal marks: stored with a pure assignment indexed by a scalar counter
// (the counter rewinds between ticks, so a signal that disappears on the
// live bar leaves nothing behind) and redrawn on the last bar
once nSig = 0
IF buySignal = 1 THEN
$sgX[nSig] = barindex
$sgY1[nSig] = low - 0.6 * sigOff
$sgY2[nSig] = low - 1.5 * sigOff
$sgD[nSig] = 1
nSig = nSig + 1
BACKGROUNDCOLOR(76, 175, 80, 38)
ENDIF
IF sellSignal = 1 THEN
$sgX[nSig] = barindex
$sgY1[nSig] = high + 0.6 * sigOff
$sgY2[nSig] = high + 1.5 * sigOff
$sgD[nSig] = -1
nSig = nSig + 1
BACKGROUNDCOLOR(242, 54, 69, 38)
ENDIF
IF islastbarupdate AND nSig > 0 THEN
FOR k = 0 TO nSig - 1 DO
sx = $sgX[k]
sy1 = $sgY1[k]
sy2 = $sgY2[k]
IF $sgD[k] = 1 THEN
DRAWTEXT("▲", sx, sy1, dialog, bold, 18) COLOURED(0, 230, 118, 255)
DRAWTEXT("BUY", sx, sy2, dialog, bold, 11) COLOURED(0, 230, 118, 255)
ELSE
DRAWTEXT("▼", sx, sy1, dialog, bold, 18) COLOURED(242, 54, 69, 255)
DRAWTEXT("SELL", sx, sy2, dialog, bold, 11) COLOURED(242, 54, 69, 255)
ENDIF
NEXT
ENDIF
RETURN emaV COLOURED(255, 152, 0, emaA) STYLE(line, 2) AS "EMA", lastPivotHigh COLOURED(242, 54, 69) STYLE(line, 2) AS "Last Pivot High", lastPivotLow COLOURED(0, 230, 118) STYLE(line, 2) AS "Last Pivot Low"