Supply and Demand Zones - SMC Lite

Category: Indicators By: Iván González Created: July 30, 2026, 1:45 PM
July 30, 2026, 1:45 PM
Indicators
0 Comments

Introduction

Most Smart Money Concepts indicators try to draw everything at once: order blocks, fair value gaps, liquidity sweeps, internal and external structure, three timeframes of each. This one (by Pmgjiv) draws three things, and that restraint is the reason it is worth reading.

A swing high opens a supply zone. A swing low opens a demand zone. When a candle closes through a zone, the zone disappears and leaves a horizontal line labelled BOS — break of structure — where its midpoint used to be.

That is the whole indicator. Optional extras — a zigzag between pivots and HH / LH / HL / LL labels — ship switched off.

It is a price overlay and it works on any instrument and timeframe with enough history.

The Three Moving Parts

Pivots. A confirmed swing high needs ten bars on each side of it, so it appears ten bars after the fact. That delay is not a defect, it is the price of confirmation: a high is only a high once the market has failed to beat it for ten bars.

Zone geometry. The zone hangs off the pivot and is one quarter of an ATR(50) thick:

supply:  top = swing high,  bottom = top − ATR × boxWidth / 10
demand:  bottom = swing low, top = bottom + ATR × boxWidth / 10
POI    = (top + bottom) / 2

Thickness scales with volatility, so the same setting produces a proportionate zone on a quiet stock and on a nervous futures contract. The POI — point of interest — is the midpoint, the line most people actually use as the entry trigger.

The break. The test is on the close, not the wick:

supply is broken when close ≥ zone top
demand is broken when close ≤ zone bottom

A wick through the zone leaves it standing. Only a body closing beyond it kills the zone and prints the BOS.

The Overlap Filter, Which Is the Part Worth Stealing

A ten-bar pivot is a common event. Without some form of filtering, a ranging market would carpet the chart with overlapping zones within a couple of hundred bars. The way this indicator avoids that is the most transferable idea in it.

It does not filter by age, by distance in ticks, or by a maximum count. It compares the new POI against the POI of every zone still alive, and rejects the new one if it lands within two ATRs:

atr_threshold = atr * 2
upper_boundary = poi + atr_threshold
lower_boundary = poi - atr_threshold
if new_poi >= lower_boundary and new_poi <= upper_boundary
    okay_to_draw := false
    break

Density measured in volatility, not in price units. On a quiet instrument two ATRs is a small distance and zones may sit close together; on a violent one they are forced far apart. It is the same normalisation trick that makes a percentile threshold portable, applied to spacing instead of to a level, and you can bolt it onto any tool of yours that draws repeated levels.

One subtlety decides whether your translation draws the same chart or a much emptier one. box.delete() does not remove the element from the array — there is not a single array.remove in the source — so the slot of a broken zone stays occupied. But the getters of a deleted box return na, and every comparison against na is false, so a broken zone stops blocking the moment it dies. Carry the dead zones into the filter and you lose whole zones: on Tesla daily, the supply at 497 anchored to the January 2026 high never gets created, because the POI of the zone that broke to reach it — seven points below, well inside two ATRs — is still vetoing that band.

Zones Are Objects, Not Drawings

A zone here has two states. Alive, it extends to the right, bar after bar. Broken, it is deleted and its POI becomes the BOS line, frozen between the bar where the zone was born and the bar that broke it. That create/delete cycle is the state machine. Drop it and you are left with two unrelated conditions — “there was a swing high” and “price closed above something” — and a chart that no longer resembles the original.

Settings

swingLen (10). The main dial. It sets how many bars a pivot needs on each side, which controls both how many zones you get and how long you wait for them. Raise it to 20 and you get half as many zones, all of them anchored to swings you would have marked by hand; drop it to 5 and the chart fills with minor structure. Everything else in this indicator is downstream of this number.

histKeep (20). Slots per side. Live and broken zones share them, but only the live ones block the overlap filter, so this is really a cap on how many zones can be on screen at once.

boxWidth (2.5). Zone thickness as ATR × boxWidth / 10, so 2.5 means a quarter of an ATR. Thicker zones are easier to hit and harder to break; thinner ones give a tighter entry and break more often.

atrLen (50). Feeds both the thickness and the two-ATR radius of the overlap filter. A shorter ATR makes the whole thing more reactive to the current regime, and noticeably denser after a volatility collapse.

extRight (100). How far live zones extend to the right. TradingView extends them to the edge of the chart; ProBuilder needs a finite number.

maxBos (100) and maxPiv (200). How many break lines and how many pivots are kept for drawing. Pine keeps every BOS ever drawn up to 500 objects; a hundred covers several years of daily bars. Lower it to 30 or 40 if the chart feels slow on a dense intraday timeframe.

showLabels / showZigZag (0 / 0). The two decorations, off by default as in the original.

Colours are RGB triplets at the top. The text is dark grey (40, 40, 40) rather than the original’s white, because the white was only readable against a coloured box that no longer exists — on a pale fill it disappears. Raise the values if you run a dark theme.

The ProBuilder Code

//----------------------------------------------------------------------//
//PRC_FluidTrades SMC Lite
//version = 0
//30.07.26
//Autor original: Pmgjiv (FluidTrades)
//Ivan Gonzalez @ www.prorealcode.com
//Sharing ProRealTime knowledge
//----------------------------------------------------------------------//
defparam drawonlastbaronly = true
//-----Ajustes----------------------------------------------------------//
swingLen   = 10    //Swing High/Low Length (1..50)
histKeep   = 20    //History To Keep: zonas guardadas por lado (5..50)
boxWidth   = 2.5   //Supply/Demand Box Width (1..10, paso 0.5)
atrLen     = 50    //Periodo del ATR (altura de zona + filtro de solape)
extRight   = 100   //Barras de extension de las zonas hacia la derecha
maxBos     = 100   //BOS conservados (Pine los expulsa del array pero NO los borra)
maxPiv     = 200   //Pivotes conservados (etiquetas + zigzag)
showLabels = 0     //1 = etiquetas HH / LH / HL / LL
showZigZag = 0     //1 = zigzag entre pivotes
//-----Colores----------------------------------------------------------//
supR = 190         //Supply
supG = 40
supB = 190
demR = 0           //Demand
demG = 190
demB = 190
txtR = 40          //Texto SUPPLY / DEMAND / POI / BOS
txtG = 40
txtB = 40
zzR  = 0           //Zig Zag
zzG  = 0
zzB  = 0
//----------------------------------------------------------------------//
atrV   = averagetruerange[atrLen](close)
pivWin = 2 * swingLen + 1
pivOff = swingLen + 1
warmup = atrLen + pivWin + 2

hiWin = highest[pivWin](high)
loWin = lowest[pivWin](low)

//-----Semilla del estado global----------------------------------------//
IF barindex = 0 THEN
   $gS[0] = 0 - 1   //ultima barra procesada
   $gS[1] = 0       //zonas supply registradas
   $gS[2] = 0       //zonas demand registradas
   $gS[3] = 0       //BOS guardados
   $gS[4] = 0       //pivotes guardados
   $gS[5] = 0       //ultimo swing high (clasificacion HH/LH)
   $gS[6] = 0       //ultimo swing low  (clasificacion HL/LL)
ENDIF

//----------------------------------------------------------------------//
//MOTOR: una sola pasada por barra, siempre sobre la barra CERRADA
//----------------------------------------------------------------------//
IF barindex > warmup AND barindex > $gS[0] THEN
   $gS[0] = barindex
   nSup = $gS[1]
   nDem = $gS[2]
   nBos = $gS[3]
   nPiv = $gS[4]
   
   atrBuf = atrV[1] * boxWidth / 10   //altura de la zona
   atrThr = atrV[1] * 2               //radio del filtro de solape
   cls    = close[1]
   
   //-----1. pivote confirmado------------------------------------------//
   pvTy = 0
   pvY  = 0
   pvX  = 0
   IF high[pivOff] = hiWin[1] THEN
      pvTy = 1
      pvY  = high[pivOff]
      pvX  = barindex - pivOff
   ELSIF low[pivOff] = loWin[1] THEN
      pvTy = 0 - 1
      pvY  = low[pivOff]
      pvX  = barindex - pivOff
   ENDIF
   
   //-----2. registro del pivote + clasificacion HH/LH/HL/LL------------//
   IF pvTy <> 0 THEN
      IF nPiv >= maxPiv THEN
         FOR m = 1 TO nPiv - 1 DO
            $pX[m] = $pX[m+1]
            $pY[m] = $pY[m+1]
            $pT[m] = $pT[m+1]
            $pL[m] = $pL[m+1]
         NEXT
         nPiv = nPiv - 1
      ENDIF
      nPiv = nPiv + 1
      $pX[nPiv] = pvX
      $pY[nPiv] = pvY
      $pT[nPiv] = pvTy
      IF pvTy = 1 THEN
         IF pvY >= $gS[5] THEN
            $pL[nPiv] = 1
         ELSE
            $pL[nPiv] = 0
         ENDIF
         $gS[5] = pvY
      ELSE
         IF pvY >= $gS[6] THEN
            $pL[nPiv] = 1
         ELSE
            $pL[nPiv] = 0
         ENDIF
         $gS[6] = pvY
      ENDIF
   ENDIF
   
   //-----3. nuevo swing high -> zona SUPPLY----------------------------//
   IF pvTy = 1 THEN
      newTop = pvY
      newBot = newTop - atrBuf
      newMid = (newTop + newBot) / 2
      
      okDraw = 1
      FOR k = 1 TO nSup DO
         IF $sOn[k] = 1 AND newMid >= $sMid[k] - atrThr AND newMid <= $sMid[k] + atrThr THEN
            okDraw = 0
         ENDIF
      NEXT
      
      IF okDraw = 1 THEN
         IF nSup >= histKeep THEN
            FOR m = 1 TO nSup - 1 DO
               $sTop[m] = $sTop[m+1]
               $sBot[m] = $sBot[m+1]
               $sMid[m] = $sMid[m+1]
               $sX1[m]  = $sX1[m+1]
               $sOn[m]  = $sOn[m+1]
            NEXT
            nSup = nSup - 1
         ENDIF
         nSup = nSup + 1
         $sTop[nSup] = newTop
         $sBot[nSup] = newBot
         $sMid[nSup] = newMid
         $sX1[nSup]  = pvX
         $sOn[nSup]  = 1
      ENDIF
   ENDIF
   
   //-----4. nuevo swing low -> zona DEMAND-----------------------------//
   IF pvTy = 0 - 1 THEN
      newBot = pvY
      newTop = newBot + atrBuf
      newMid = (newTop + newBot) / 2
      
      okDraw = 1
      FOR k = 1 TO nDem DO
         IF $dOn[k] = 1 AND newMid >= $dMid[k] - atrThr AND newMid <= $dMid[k] + atrThr THEN
            okDraw = 0
         ENDIF
      NEXT
      
      IF okDraw = 1 THEN
         IF nDem >= histKeep THEN
            FOR m = 1 TO nDem - 1 DO
               $dTop[m] = $dTop[m+1]
               $dBot[m] = $dBot[m+1]
               $dMid[m] = $dMid[m+1]
               $dX1[m]  = $dX1[m+1]
               $dOn[m]  = $dOn[m+1]
            NEXT
            nDem = nDem - 1
         ENDIF
         nDem = nDem + 1
         $dTop[nDem] = newTop
         $dBot[nDem] = newBot
         $dMid[nDem] = newMid
         $dX1[nDem]  = pvX
         $dOn[nDem]  = 1
      ENDIF
   ENDIF
   
   //-----5. ruptura: la zona muere y deja un BOS en su POI--------------//
   FOR k = 1 TO nSup DO
      IF $sOn[k] = 1 AND cls >= $sTop[k] THEN
         $sOn[k] = 0
         IF nBos >= maxBos THEN
            FOR m = 1 TO nBos - 1 DO
               $boX1[m] = $boX1[m+1]
               $boX2[m] = $boX2[m+1]
               $boY[m]  = $boY[m+1]
               $boT[m]  = $boT[m+1]
            NEXT
            nBos = nBos - 1
         ENDIF
         nBos = nBos + 1
         $boX1[nBos] = $sX1[k]
         $boX2[nBos] = barindex - 1
         $boY[nBos]  = $sMid[k]
         $boT[nBos]  = 1
      ENDIF
   NEXT
   
   FOR k = 1 TO nDem DO
      IF $dOn[k] = 1 AND cls <= $dBot[k] THEN
         $dOn[k] = 0
         IF nBos >= maxBos THEN
            FOR m = 1 TO nBos - 1 DO
               $boX1[m] = $boX1[m+1]
               $boX2[m] = $boX2[m+1]
               $boY[m]  = $boY[m+1]
               $boT[m]  = $boT[m+1]
            NEXT
            nBos = nBos - 1
         ENDIF
         nBos = nBos + 1
         $boX1[nBos] = $dX1[k]
         $boX2[nBos] = barindex - 1
         $boY[nBos]  = $dMid[k]
         $boT[nBos]  = 0 - 1
      ENDIF
   NEXT
   
   $gS[1] = nSup
   $gS[2] = nDem
   $gS[3] = nBos
   $gS[4] = nPiv
ENDIF

//----------------------------------------------------------------------//
//DIBUJO
//----------------------------------------------------------------------//
IF islastbarupdate THEN
   xR   = barindex + extRight
   nSup = $gS[1]
   nDem = $gS[2]
   nBos = $gS[3]
   nPiv = $gS[4]
   
   //-----zonas SUPPLY vivas--------------------------------------------//
   FOR k = 1 TO nSup DO
      IF $sOn[k] = 1 THEN
         DRAWRECTANGLE($sX1[k], $sTop[k], xR, $sBot[k]) COLOURED(supR, supG, supB, 200) FILLCOLOR(supR, supG, supB, 70)
         DRAWSEGMENT($sX1[k], $sMid[k], xR, $sMid[k]) COLOURED(supR, supG, supB, 220) STYLE(dottedline, 1)
         DRAWTEXT("SUPPLY", ($sX1[k] + xR) / 2, $sMid[k], sansserif, bold, 10) COLOURED(txtR, txtG, txtB, 255)
         DRAWTEXT("POI", $sX1[k] + 5, $sMid[k], sansserif, standard, 9) COLOURED(txtR, txtG, txtB, 200)
      ENDIF
   NEXT
   
   //-----zonas DEMAND vivas--------------------------------------------//
   FOR k = 1 TO nDem DO
      IF $dOn[k] = 1 THEN
         DRAWRECTANGLE($dX1[k], $dTop[k], xR, $dBot[k]) COLOURED(demR, demG, demB, 200) FILLCOLOR(demR, demG, demB, 70)
         DRAWSEGMENT($dX1[k], $dMid[k], xR, $dMid[k]) COLOURED(demR, demG, demB, 220) STYLE(dottedline, 1)
         DRAWTEXT("DEMAND", ($dX1[k] + xR) / 2, $dMid[k], sansserif, bold, 10) COLOURED(txtR, txtG, txtB, 255)
         DRAWTEXT("POI", $dX1[k] + 5, $dMid[k], sansserif, standard, 9) COLOURED(txtR, txtG, txtB, 200)
      ENDIF
   NEXT
   
   //-----BOS-----------------------------------------------------------//
   FOR k = 1 TO nBos DO
      IF $boT[k] = 1 THEN
         DRAWSEGMENT($boX1[k], $boY[k], $boX2[k], $boY[k]) COLOURED(supR, supG, supB, 255) STYLE(line, 2)
      ELSE
         DRAWSEGMENT($boX1[k], $boY[k], $boX2[k], $boY[k]) COLOURED(demR, demG, demB, 255) STYLE(line, 2)
      ENDIF
      DRAWTEXT("BOS", ($boX1[k] + $boX2[k]) / 2, $boY[k], sansserif, bold, 10) COLOURED(txtR, txtG, txtB, 255)
   NEXT
   
   //-----zigzag entre pivotes------------------------------------------//
   IF showZigZag = 1 AND nPiv >= 2 THEN
      FOR k = 2 TO nPiv DO
         DRAWSEGMENT($pX[k-1], $pY[k-1], $pX[k], $pY[k]) COLOURED(zzR, zzG, zzB, 255) STYLE(line, 2)
      NEXT
   ENDIF
   
   //-----etiquetas de estructura---------------------------------------//
   IF showLabels = 1 THEN
      FOR k = 1 TO nPiv DO
         IF $pT[k] = 1 THEN
            IF $pL[k] = 1 THEN
               DRAWTEXT("HH", $pX[k], $pY[k] + atrV * 0.4, sansserif, bold, 9) COLOURED(txtR, txtG, txtB, 255)
            ELSE
               DRAWTEXT("LH", $pX[k], $pY[k] + atrV * 0.4, sansserif, bold, 9) COLOURED(txtR, txtG, txtB, 255)
            ENDIF
         ELSE
            IF $pL[k] = 1 THEN
               DRAWTEXT("HL", $pX[k], $pY[k] - atrV * 0.4, sansserif, bold, 9) COLOURED(txtR, txtG, txtB, 255)
            ELSE
               DRAWTEXT("LL", $pX[k], $pY[k] - atrV * 0.4, sansserif, bold, 9) COLOURED(txtR, txtG, txtB, 255)
            ENDIF
         ENDIF
      NEXT
   ENDIF
ENDIF

RETURN

Download
Filename: PRC_FluidTrades-SMC-Lite.itf
Downloads: 13
Iván González Legend
Currently debugging life, so my bio is on hold. Check back after the next commit for an update.
Author’s Profile

Comments

Logo Logo
Loading...