Price Action Concepts (PAC) is a market structure analysis indicator built around Smart Money Concepts methodology by HunterAlgos.
It tracks two independent layers of structure — internal (short-term) and swing (long-term) — and identifies key structural events: Break of Structure (BOS), Change of Character (CHoCH), and the more selective CHoCH+, which requires confirmation from a higher low or lower high before signalling a true trend reversal.
On top of that, it labels swing pivots (HH, LH, HL, LL), highlights liquidity wicks filtered by volume, and colors bars according to the active internal trend.
The result is a clean, layered view of price structure without needing multiple indicators.
The indicator uses an asymmetric lookback window to detect pivots: the left window is half the right window (left = round(right / 2)). This makes pivot detection slightly forward-biased, which improves responsiveness on the internal layer.
Two independent pivot detectors run in parallel:
internalRLB, default 5 bars). They drive the BOS/CHoCH/CHoCH+ signals on the internal structure layer.swingRLB, default 50 bars). They drive the swing structure signals and the HH/LH/HL/LL labels.
A pivot high is confirmed when high[right] is the highest value in the full left + right + 1 window. Same logic applies to pivot lows.
Once an internal pivot is stored, the indicator monitors whether price closes beyond it:
After a break is detected, the pivot is consumed (set to inactive) to avoid repeated signals.
Identical logic to the internal layer, but operating on swing pivots. The swing layer does not implement CHoCH+ — only BOS and CHoCH. Swing structure lines are drawn solid (thicker) to visually differentiate them from internal structure lines (dashed).
Every time a new swing pivot is confirmed, it is compared to the previous swing pivot of the same type:
Labels only appear from the second pivot onward (no comparison is possible on the first one).
The indicator scans for candles where a wick significantly exceeds the body, suggesting a sweep of liquidity:
Both conditions are filtered by volume: the candle’s volume must be above a long-term moving average (volMALen, default 200) to qualify. The wick-to-body ratio threshold is configurable (liqThresh, default 10×).
Qualifying wicks are highlighted with a thick colored segment directly on the wick: yellow for upper, orange for lower.
Bars are colored according to the current internal trend state:
internalRLB — Right lookback for internal pivots. Default: 5. Lower values = more sensitive, more noise. Minimum: 2.swingRLB — Right lookback for swing pivots. Default: 50. Controls how major a swing needs to be to qualify. Minimum: 2.showInternal — Which internal structure signals to display. 0 = none, 1 = all, 2 = CHoCH only, 3 = BOS only, 4 = CHoCH+ only.showSwing — Which swing structure signals to display. 0 = none, 1 = all, 2 = CHoCH only, 3 = BOS only.showLabels — Show HH/LH/HL/LL labels on swing pivots. 1 = on, 0 = off.showLiq — Show liquidity wick highlights. 1 = on, 0 = off.showCandle — Color bars by internal trend. 1 = on, 0 = off.volMALen — Length of the volume moving average used for liquidity wick filter. Default: 200.liqThresh — Minimum wick-to-body ratio to qualify as a liquidity wick. Default: 10.
BOS (Break of Structure) Trend continuation. Price has broken a pivot in the direction of the established trend. Use it to confirm momentum and look for entries in the trend direction.
CHoCH (Change of Character) First warning of a potential reversal. Price has broken a pivot against the current trend, but there is no confirmation yet. Treat it as an alert, not a signal — wait for follow-through.
CHoCH+ (Change of Character Confirmed) Stronger reversal signal. In addition to breaking against the trend, price has also formed a higher low (for bullish) or lower high (for bearish), suggesting the reversal has structural backing. More selective than CHoCH, fewer false positives.
HH / LH / HL / LL labels Read swing structure at a glance. A sequence of HH + HL confirms a swing uptrend. LH + LL confirms a swing downtrend. Mixed sequences (e.g. HH + LL) indicate range or transition.
Liquidity wicks (yellow / orange) A large wick with above-average volume on a directional candle signals that smart money may have swept liquidity. Yellow upper wicks on bullish candles can precede reversals downward; orange lower wicks on bearish candles may precede upward moves. Do not use in isolation — combine with structure signals.
Dual-layer reading The real value of the indicator comes from combining both layers. Example: swing structure is bullish (BOS on swing), but internal structure just printed a bearish CHoCH+ → possible pullback within the macro uptrend. This is where the indicator gives the most actionable context.
// --------------------------------------------
// PRC_Price Action Concepts
// Based on: HunterAlgos (PineScript v5)
// version = 0
// 04.03.2026
// Iván González @ www.prorealcode.com
// Sharing ProRealTime knowledge
// --------------------------------------------
// --- PARAMETERS ---
// --------------------------------------------
// Internal structure: right lookback (left = round(right/2))
internalRLB = 5 // min 2
// Swing structure: right lookback (left = round(right/2))
swingRLB = 50 // min 2
// Show Internal MS: 0=None, 1=All, 2=CHoCH only, 3=BOS only, 4=CHoCH+ only
showInternal = 1
// Show Swing MS: 0=None, 1=All, 2=CHoCH only, 3=BOS only
showSwing = 1
// Swing point labels (HH/LH/HL/LL): 1=show, 0=hide
showLabels = 1
// Liquidity wicks: 1=show, 0=hide
showLiq = 1
// Bar coloring by internal trend: 1=show, 0=hide
showCandle = 1
// Volume MA length for liquidity filter
volMALen = 200
// Wick threshold (wick > N * body)
liqThresh = 10
// --------------------------------------------
// --- DERIVED LOOKBACKS ---
// --------------------------------------------
internalLLB = max(1, round(internalRLB / 2))
swingLLB = max(1, round(swingRLB / 2))
// ATR for label offset
atrRef = averagetruerange[14]
// --------------------------------------------
// PIVOT DETECTION
// --------------------------------------------
// Internal pivot high (asymmetric: left=internalLLB, right=internalRLB)
newIPH = 0
totalIWin = internalLLB + internalRLB + 1
if barindex >= totalIWin then
if high[internalRLB] >= highest[totalIWin](high) then
newIPH = 1
endif
endif
// Internal pivot low
newIPL = 0
if barindex >= totalIWin then
if low[internalRLB] <= lowest[totalIWin](low) then
newIPL = 1
endif
endif
// Swing pivot high (asymmetric: left=swingLLB, right=swingRLB)
newSPH = 0
totalSWin = swingLLB + swingRLB + 1
if barindex >= totalSWin then
if high[swingRLB] >= highest[totalSWin](high) then
newSPH = 1
endif
endif
// Swing pivot low
newSPL = 0
if barindex >= totalSWin then
if low[swingRLB] <= lowest[totalSWin](low) then
newSPL = 1
endif
endif
// --------------------------------------------
// PIVOT STORAGE + LOGS
// --------------------------------------------
once iHiReady = 0
once iLoReady = 0
once sHiReady = 0
once sLoReady = 0
once itrend = 0
once strend = 0
once logHiCount = 0
once logLoCount = 0
once sphCount = 0
once splCount = 0
once lastIPH = -1
once lastIPL = -1
once lastIPHx = 0
once lastIPLx = 0
once lastSPH = -1
once lastSPL = -1
once lastSPHx = 0
once lastSPLx = 0
once lastLogHi = -1
once lastLogLo = -1
once prevLogHi = -1
once prevLogLo = -1
once prevSPH = -1
once prevSPL = 999999999
// --- Internal pivot high found ---
if newIPH then
lastIPH = high[internalRLB]
lastIPHx = barindex - internalRLB
iHiReady = 1
// Cumulative log for CHoCH+ detection
prevLogHi = lastLogHi
lastLogHi = high[internalRLB]
logHiCount = logHiCount + 1
endif
// --- Internal pivot low found ---
if newIPL then
lastIPL = low[internalRLB]
lastIPLx = barindex - internalRLB
iLoReady = 1
// Cumulative log for CHoCH+ detection
prevLogLo = lastLogLo
lastLogLo = low[internalRLB]
logLoCount = logLoCount + 1
endif
// --- Swing pivot high found ---
if newSPH then
lastSPH = high[swingRLB]
lastSPHx = barindex - swingRLB
sHiReady = 1
// Draw HH/LH label
if showLabels then
if sphCount > 0 then
if lastSPH > prevSPH then
drawtext("HH", barindex - swingRLB, high[swingRLB] + atrRef * 0.5) coloured(8,153,129)
else
drawtext("LH", barindex - swingRLB, high[swingRLB] + atrRef * 0.5) coloured(242,54,69)
endif
endif
endif
prevSPH = lastSPH
sphCount = sphCount + 1
endif
// --- Swing pivot low found ---
if newSPL then
lastSPL = low[swingRLB]
lastSPLx = barindex - swingRLB
sLoReady = 1
// Draw HL/LL label
if showLabels then
if splCount > 0 then
if lastSPL > prevSPL then
drawtext("HL", barindex - swingRLB, low[swingRLB] - atrRef * 0.5) coloured(8,153,129)
else
drawtext("LL", barindex - swingRLB, low[swingRLB] - atrRef * 0.5) coloured(242,54,69)
endif
endif
endif
prevSPL = lastSPL
splCount = splCount + 1
endif
// --------------------------------------------
// INTERNAL STRUCTURE DETECTION
// --------------------------------------------
// --- Internal Bullish Break (close crosses above pivot high) ---
if iHiReady = 1 and lastIPH > 0 then
if close > lastIPH and close[1] <= lastIPH then
// Determine type
iBullType = 0
if itrend < 0 then
// Was bearish -> CHoCH or CHoCH+
if logLoCount >= 2 and lastLogLo > prevLogLo then
iBullType = 3 // CHoCH+ (higher low confirms)
else
iBullType = 2 // CHoCH
endif
else
iBullType = 1 // BOS (trend continuation)
endif
itrend = 1
// Decide if we should draw
iBullDraw = 0
if iBullType = 1 and (showInternal = 1 or showInternal = 3) then
iBullDraw = 1
elsif iBullType = 2 and (showInternal = 1 or showInternal = 2) then
iBullDraw = 1
elsif iBullType = 3 and (showInternal = 1 or showInternal = 4) then
iBullDraw = 1
endif
if iBullDraw then
// Colors: BOS=green, CHoCH/CHoCH+=dark green
if iBullType = 1 then
ibR = 8
ibG = 153
ibB = 129
else
ibR = 0
ibG = 120
ibB = 100
endif
// Draw structure line (dashed) + label
midIBx = round((lastIPHx + barindex) / 2)
drawsegment(lastIPHx, lastIPH, barindex, lastIPH) coloured(ibR, ibG, ibB) style(dottedline2, 1)
if iBullType = 1 then
drawtext("BOS", midIBx, lastIPH + atrRef * 0.2) coloured(ibR, ibG, ibB)
elsif iBullType = 2 then
drawtext("CHoCH", midIBx, lastIPH + atrRef * 0.2) coloured(ibR, ibG, ibB)
else
drawtext("CHoCH+", midIBx, lastIPH + atrRef * 0.2) coloured(ibR, ibG, ibB)
endif
endif
// Pivot consumed
iHiReady = 0
endif
endif
// --- Internal Bearish Break (close crosses below pivot low) ---
if iLoReady = 1 and lastIPL > 0 then
if close < lastIPL and close[1] >= lastIPL then
// Determine type
iBearType = 0
if itrend > 0 then
// Was bullish -> CHoCH or CHoCH+
if logHiCount >= 2 and lastLogHi < prevLogHi then
iBearType = 3 // CHoCH+ (lower high confirms)
else
iBearType = 2 // CHoCH
endif
else
iBearType = 1 // BOS
endif
itrend = -1
// Decide if we should draw
iBearDraw = 0
if iBearType = 1 and (showInternal = 1 or showInternal = 3) then
iBearDraw = 1
elsif iBearType = 2 and (showInternal = 1 or showInternal = 2) then
iBearDraw = 1
elsif iBearType = 3 and (showInternal = 1 or showInternal = 4) then
iBearDraw = 1
endif
if iBearDraw then
// Colors: BOS=red, CHoCH/CHoCH+=dark red
if iBearType = 1 then
ieR = 242
ieG = 54
ieB = 69
else
ieR = 180
ieG = 40
ieB = 55
endif
// Draw structure line (dashed) + label
midIEx = round((lastIPLx + barindex) / 2)
drawsegment(lastIPLx, lastIPL, barindex, lastIPL) coloured(ieR, ieG, ieB) style(dottedline2, 1)
if iBearType = 1 then
drawtext("BOS", midIEx, lastIPL - atrRef * 0.2) coloured(ieR, ieG, ieB)
elsif iBearType = 2 then
drawtext("CHoCH", midIEx, lastIPL - atrRef * 0.2) coloured(ieR, ieG, ieB)
else
drawtext("CHoCH+", midIEx, lastIPL - atrRef * 0.2) coloured(ieR, ieG, ieB)
endif
endif
// Pivot consumed
iLoReady = 0
endif
endif
// --------------------------------------------
// SWING STRUCTURE DETECTION
// --------------------------------------------
// --- Swing Bullish Break ---
if sHiReady = 1 and lastSPH > 0 then
if close > lastSPH and close[1] <= lastSPH then
// Determine type
if strend < 0 then
sBullType = 2 // CHoCH
else
sBullType = 1 // BOS
endif
strend = 1
// Decide if we should draw
sBullDraw = 0
if sBullType = 1 and (showSwing = 1 or showSwing = 3) then
sBullDraw = 1
elsif sBullType = 2 and (showSwing = 1 or showSwing = 2) then
sBullDraw = 1
endif
if sBullDraw then
if sBullType = 1 then
sbR = 8
sbG = 153
sbB = 129
else
sbR = 0
sbG = 120
sbB = 100
endif
midSBx = round((lastSPHx + barindex) / 2)
drawsegment(lastSPHx, lastSPH, barindex, lastSPH) coloured(sbR, sbG, sbB) style(line, 2)
if sBullType = 1 then
drawtext("BOS", midSBx, lastSPH + atrRef * 0.4) coloured(sbR, sbG, sbB)
else
drawtext("CHoCH", midSBx, lastSPH + atrRef * 0.4) coloured(sbR, sbG, sbB)
endif
endif
// Pivot consumed
sHiReady = 0
endif
endif
// --- Swing Bearish Break ---
if sLoReady = 1 and lastSPL > 0 then
if close < lastSPL and close[1] >= lastSPL then
// Determine type
if strend > 0 then
sBearType = 2 // CHoCH
else
sBearType = 1 // BOS
endif
strend = -1
// Decide if we should draw
sBearDraw = 0
if sBearType = 1 and (showSwing = 1 or showSwing = 3) then
sBearDraw = 1
elsif sBearType = 2 and (showSwing = 1 or showSwing = 2) then
sBearDraw = 1
endif
if sBearDraw then
if sBearType = 1 then
seR = 242
seG = 54
seB = 69
else
seR = 180
seG = 40
seB = 55
endif
midSEx = round((lastSPLx + barindex) / 2)
drawsegment(lastSPLx, lastSPL, barindex, lastSPL) coloured(seR, seG, seB) style(line, 2)
if sBearType = 1 then
drawtext("BOS", midSEx, lastSPL - atrRef * 0.4) coloured(seR, seG, seB)
else
drawtext("CHoCH", midSEx, lastSPL - atrRef * 0.4) coloured(seR, seG, seB)
endif
endif
// Pivot consumed
sLoReady = 0
endif
endif
// --------------------------------------------
// LIQUIDITY WICKS
// --------------------------------------------
if showLiq then
upWick = abs(high - max(open, close))
dnWick = abs(low - min(open, close))
body = abs(close - open)
volAboveMA = volume > average[volMALen](volume)
// Upper wick liquidity (green candle only)
if close > open and upWick > liqThresh * body and volAboveMA then
drawsegment(barindex, max(open, close), barindex, high) coloured(255, 255, 0) style(line, 3)
endif
// Lower wick liquidity (red candle only)
if close < open and dnWick > liqThresh * body and volAboveMA then
drawsegment(barindex, min(open, close), barindex, low) coloured(255, 165, 0) style(line, 3)
endif
endif
// --------------------------------------------
// BAR COLORING (drawcandle by internal trend)
// --------------------------------------------
if showCandle then
if itrend > 0 then
cR = 8
cG = 153
cB = 129
elsif itrend < 0 then
cR = 242
cG = 54
cB = 69
else
cR = 120
cG = 123
cB = 134
endif
drawcandle(open, high, low, close) coloured(cR, cG, cB)
endif
// --------------------------------------------
return