Change in State of Delivery (CISD) is one of the most useful ICT concepts to emerge after 2023. It tries to answer a very specific question: at what exact bar does control of the tape shift from buyers to sellers (or vice versa)? The classic structural answer is Market Structure Shift (MSS) — wait for price to break a confirmed pivot. CISD goes earlier: it fires the moment a candle closes its body beyond the open of the first candle that started the current directional leg.
That’s it. No moving averages, no oscillator, no break of structure. Just: did the close cross back through the open of the candle that initiated the run? If yes, the leg has failed and the other side is now in control.
The trade-off is obvious. CISD is faster than MSS, so it gives more signals — and more false ones. The original LuxAlgo implementation therefore offers two modes:
This ProRealTime port reproduces both modes, the minimum/maximum duration filters, and the historical visualisation (horizontal level from origin to trigger, recoloured on activation).
After the first candle of a new directional leg appears, its open becomes a candidate level. As long as the leg unfolds normally, price stays on the same side of that level. The moment a later candle closes back across it — body, not wick — the leg is considered failed and a CISD prints.
Two duration filters refine the signal:
minSeqLen: minimum bars between level creation and trigger. Filters whip-saws on the bar that created the level.maxSeqLen: maximum lifetime of a pending level. If the leg runs long enough that the level becomes irrelevant, the level is dropped without firing.In Classic mode, every directional change creates a pending level. There is exactly one bullish pending level and one bearish pending level alive at any time; a new leg replaces the previous pending level even if it never triggered. This is the original LuxAlgo behaviour (oBull/oBear singletons in the Pine source).
In Liquidity Sweep mode, a level is only armed when the previous leg has barred a prior swing: price spiked above (or below) a confirmed pivot wick and closed back on the original side — the textbook stop-hunt-then-rejection. Several swept pivots can have pending levels at the same time, so this mode uses a FIFO pool of up to ten simultaneous levels per side.
MSS waits for a confirmed pivot to be broken. That requires a swing point to exist and be taken out — typically several bars after the actual change of intent. CISD fires on the first body-close back through the leg’s origin, often several bars before the next pivot is even formed. CISD is the early trigger; MSS is the confirmation. They are complementary, not redundant.
| Parameter | Default | Description |
|---|---|---|
| `mode` | 1 | 0 = Classic, 1 = Liquidity Sweep |
| `swingLen` | 10 | Pivot lookback (used only in Sweep mode) |
| `minSeqLen` | 0 | Minimum bars between level creation and trigger |
| `maxSeqLen` | 100 | Maximum lifetime of a pending level (bars) |
| `MAXC` | 10 | Active CISD levels per side (Sweep mode) |
| `MAXP` | 10 | Active pivots per side (Sweep mode) |
| `MAXS` | 50 | Historical signals kept on screen |
//--------------------------------------------
// PRC_CISD (by LuxAlgo)
// version = 0
// 20.05.2026
// Iván González @ www.prorealcode.com
// Sharing ProRealTime knowledge
// Concepto y código Pine original: LuxAlgo
//--------------------------------------------
DEFPARAM DRAWONLASTBARONLY = TRUE
//=== PARAMETROS ===
mode = 1 // 0 = Classic, 1 = Liquidity Sweep
swingLen = 10 // Lookback pivots (solo modo Sweep)
minSeqLen = 0 // Barras minimas antes de aceptar disparo
maxSeqLen = 100 // Caducidad: barras maximas de un nivel sin disparar
//=== CAPACIDADES POOL (constantes) ===
MAXC = 10 // niveles CISD vivos por lado
MAXP = 10 // pivots vivos por lado (modo Sweep)
MAXS = 50 // historico de senales CISD para render
//=== DETECCION DE VELA ===
bull = close > open
bear = close < open
// Track de "primera vela contraria" = candidato a nivel CISD
ONCE trackBullPrc = 0
ONCE trackBullBix = 0
ONCE trackBearPrc = 0
ONCE trackBearBix = 0
IF bull AND bear[1] THEN
trackBullPrc = open
trackBullBix = barindex
ENDIF
IF bear AND bull[1] THEN
trackBearPrc = open
trackBearBix = barindex
ENDIF
ONCE trend = 0 // +1 ultimo CISD bullish, -1 ultimo CISD bearish
//=== MODO CLASSIC: singleton por direccion (replica oBull/oBear del Pine) ===
// Pine mantiene UN solo nivel activo por direccion: cada nueva pierna sobrescribe el anterior
// si no habia disparado (oBull.ln.delete()). NO es un pool en modo Classic.
IF mode = 0 THEN
// Primera vela alcista tras pierna bajista -> reset + arm cBull[0] (vigila CISD bearish)
IF bull AND bear[1] THEN
FOR iRb = 0 TO MAXC - 1 DO
$cBullAct[iRb] = 0
NEXT
$cBullX[0] = trackBullBix
$cBullY[0] = trackBullPrc
$cBullBorn[0] = barindex
$cBullAct[0] = 1
ENDIF
// Primera vela bajista tras pierna alcista -> reset + arm cBear[0] (vigila CISD bullish)
IF bear AND bull[1] THEN
FOR iRc = 0 TO MAXC - 1 DO
$cBearAct[iRc] = 0
NEXT
$cBearX[0] = trackBearBix
$cBearY[0] = trackBearPrc
$cBearBorn[0] = barindex
$cBearAct[0] = 1
ENDIF
ENDIF
//=== MODO SWEEP: detectar pivots + sweep, luego armar nivel ===
IF mode = 1 THEN
// Pivot high confirmado (asimetrico: swingLen barras izq, 1 barra der; barra pivote = [1])
IF barindex > swingLen + 1 THEN
IF high[1] = highest[swingLen + 2](high) THEN
// FIFO insert pivot high
slotPH = -1
cntPH = 0
oldIdxPH = -1
oldBornPH = 0
FOR iPH = 0 TO MAXP - 1 DO
IF $pHAct[iPH] = 1 THEN
cntPH = cntPH + 1
IF oldIdxPH = -1 THEN
oldBornPH = $pHBorn[iPH]
oldIdxPH = iPH
ELSIF $pHBorn[iPH] < oldBornPH THEN
oldBornPH = $pHBorn[iPH]
oldIdxPH = iPH
ENDIF
ELSE
IF slotPH = -1 THEN
slotPH = iPH
ENDIF
ENDIF
NEXT
IF slotPH = -1 THEN
slotPH = oldIdxPH
ENDIF
$pHX[slotPH] = barindex - 1
$pHY[slotPH] = high[1]
$pHBorn[slotPH] = barindex
$pHAct[slotPH] = 1
ENDIF
IF low[1] = lowest[swingLen + 2](low) THEN
// FIFO insert pivot low
slotPL = -1
cntPL = 0
oldIdxPL = -1
oldBornPL = 0
FOR iPL = 0 TO MAXP - 1 DO
IF $pLAct[iPL] = 1 THEN
cntPL = cntPL + 1
IF oldIdxPL = -1 THEN
oldBornPL = $pLBorn[iPL]
oldIdxPL = iPL
ELSIF $pLBorn[iPL] < oldBornPL THEN
oldBornPL = $pLBorn[iPL]
oldIdxPL = iPL
ENDIF
ELSE
IF slotPL = -1 THEN
slotPL = iPL
ENDIF
ENDIF
NEXT
IF slotPL = -1 THEN
slotPL = oldIdxPL
ENDIF
$pLX[slotPL] = barindex - 1
$pLY[slotPL] = low[1]
$pLBorn[slotPL] = barindex
$pLAct[slotPL] = 1
ENDIF
ENDIF
// Comprobar SWEEP en cada pivot vivo
// Pivot high sweepeado (high > pivot AND close < pivot) -> arma nivel cBull (vigila CISD bearish)
FOR iSH = 0 TO MAXP - 1 DO
IF $pHAct[iSH] = 1 THEN
IF barindex - $pHBorn[iSH] > maxSeqLen THEN
$pHAct[iSH] = 0
ELSIF close > $pHY[iSH] THEN
// pivot roto al alza sin sweep -> retirar
$pHAct[iSH] = 0
ELSIF high > $pHY[iSH] AND close < $pHY[iSH] THEN
// SWEEP detectado -> verifico trackBull intacto, armo cBull (CISD bearish pendiente)
ok = 1
gap = barindex - trackBullBix
IF gap > maxSeqLen THEN
ok = 0
ENDIF
IF ok = 1 THEN
FOR jG = 0 TO gap DO
IF close[jG] < trackBullPrc THEN
ok = 0
ENDIF
NEXT
ENDIF
IF ok = 1 THEN
slotG = -1
cntG = 0
oldIdxG = -1
oldBornG = 0
FOR iG = 0 TO MAXC - 1 DO
IF $cBullAct[iG] = 1 THEN
cntG = cntG + 1
IF oldIdxG = -1 THEN
oldBornG = $cBullBorn[iG]
oldIdxG = iG
ELSIF $cBullBorn[iG] < oldBornG THEN
oldBornG = $cBullBorn[iG]
oldIdxG = iG
ENDIF
ELSE
IF slotG = -1 THEN
slotG = iG
ENDIF
ENDIF
NEXT
IF slotG = -1 THEN
slotG = oldIdxG
ENDIF
$cBullX[slotG] = trackBullBix
$cBullY[slotG] = trackBullPrc
$cBullBorn[slotG] = barindex
$cBullAct[slotG] = 1
ENDIF
$pHAct[iSH] = 0
ENDIF
ENDIF
NEXT
// Pivot low sweepeado (low < pivot AND close > pivot) -> arma nivel cBear (vigila CISD bullish)
FOR iSL = 0 TO MAXP - 1 DO
IF $pLAct[iSL] = 1 THEN
IF barindex - $pLBorn[iSL] > maxSeqLen THEN
$pLAct[iSL] = 0
ELSIF close < $pLY[iSL] THEN
$pLAct[iSL] = 0
ELSIF low < $pLY[iSL] AND close > $pLY[iSL] THEN
ok2 = 1
gap2 = barindex - trackBearBix
IF gap2 > maxSeqLen THEN
ok2 = 0
ENDIF
IF ok2 = 1 THEN
FOR jG2 = 0 TO gap2 DO
IF close[jG2] > trackBearPrc THEN
ok2 = 0
ENDIF
NEXT
ENDIF
IF ok2 = 1 THEN
slotG2 = -1
cntG2 = 0
oldIdxG2 = -1
oldBornG2 = 0
FOR iG2 = 0 TO MAXC - 1 DO
IF $cBearAct[iG2] = 1 THEN
cntG2 = cntG2 + 1
IF oldIdxG2 = -1 THEN
oldBornG2 = $cBearBorn[iG2]
oldIdxG2 = iG2
ELSIF $cBearBorn[iG2] < oldBornG2 THEN
oldBornG2 = $cBearBorn[iG2]
oldIdxG2 = iG2
ENDIF
ELSE
IF slotG2 = -1 THEN
slotG2 = iG2
ENDIF
ENDIF
NEXT
IF slotG2 = -1 THEN
slotG2 = oldIdxG2
ENDIF
$cBearX[slotG2] = trackBearBix
$cBearY[slotG2] = trackBearPrc
$cBearBorn[slotG2] = barindex
$cBearAct[slotG2] = 1
ENDIF
$pLAct[iSL] = 0
ENDIF
ENDIF
NEXT
ENDIF
//=== DISPARO: en cada nivel CISD vivo ===
ONCE sigCnt = 0
bullSig = 0
bearSig = 0
sigY = 0
// Pool cBull (armado en primera vela alcista) -> dispara CISD BEARISH cuando close < level
sigOX = 0
FOR iA = 0 TO MAXC - 1 DO
IF $cBullAct[iA] = 1 THEN
ageA = barindex - $cBullBorn[iA]
IF ageA > maxSeqLen THEN
$cBullAct[iA] = 0
ELSIF close < $cBullY[iA] THEN
IF ageA >= minSeqLen THEN
bearSig = 1
sigY = $cBullY[iA]
sigOX = $cBullX[iA]
ENDIF
$cBullAct[iA] = 0
ENDIF
ENDIF
NEXT
// Pool cBear (armado en primera vela bajista) -> dispara CISD BULLISH cuando close > level
FOR iB2 = 0 TO MAXC - 1 DO
IF $cBearAct[iB2] = 1 THEN
ageB = barindex - $cBearBorn[iB2]
IF ageB > maxSeqLen THEN
$cBearAct[iB2] = 0
ELSIF close > $cBearY[iB2] THEN
IF ageB >= minSeqLen THEN
bullSig = 1
sigY = $cBearY[iB2]
sigOX = $cBearX[iB2]
ENDIF
$cBearAct[iB2] = 0
ENDIF
ENDIF
NEXT
//=== GUARDAR SENAL EN HISTORICO (FIFO) ===
IF bullSig + bearSig > 0 THEN
IF sigCnt < MAXS THEN
slotSig = sigCnt
sigCnt = sigCnt + 1
ELSE
FOR kSh = 0 TO MAXS - 2 DO
$sX[kSh] = $sX[kSh + 1]
$sY[kSh] = $sY[kSh + 1]
$sOX[kSh] = $sOX[kSh + 1]
$sDir[kSh] = $sDir[kSh + 1]
$sTrnd[kSh] = $sTrnd[kSh + 1]
NEXT
slotSig = MAXS - 1
ENDIF
$sX[slotSig] = barindex
$sY[slotSig] = sigY
$sOX[slotSig] = sigOX
IF bullSig = 1 THEN
$sDir[slotSig] = 1
// Trend opuesto al previo? -> contradice = dashed
IF trend = 1 THEN
$sTrnd[slotSig] = 1 // contradice
ELSE
$sTrnd[slotSig] = 0
ENDIF
trend = 1
ELSE
$sDir[slotSig] = -1
IF trend = -1 THEN
$sTrnd[slotSig] = 1
ELSE
$sTrnd[slotSig] = 0
ENDIF
trend = -1
ENDIF
ENDIF
//=== RENDER ===
IF islastbarupdate THEN
// 1) Niveles CISD activos: linea horizontal punteada (level pendiente)
FOR iR1 = 0 TO MAXC - 1 DO
IF $cBullAct[iR1] = 1 THEN
DRAWSEGMENT($cBullX[iR1], $cBullY[iR1], barindex, $cBullY[iR1]) STYLE(dottedLine, 1) COLOURED(8, 153, 129, 160)
ENDIF
IF $cBearAct[iR1] = 1 THEN
DRAWSEGMENT($cBearX[iR1], $cBearY[iR1], barindex, $cBearY[iR1]) STYLE(dottedLine, 1) COLOURED(242, 54, 69, 160)
ENDIF
NEXT
// 2) Pivots vivos (modo Sweep): linea horizontal gris fina del pivot hacia adelante
IF mode = 1 THEN
FOR iR2 = 0 TO MAXP - 1 DO
IF $pHAct[iR2] = 1 THEN
DRAWSEGMENT($pHX[iR2], $pHY[iR2], barindex, $pHY[iR2]) STYLE(dottedLine, 1) COLOURED(120, 123, 134, 100)
ENDIF
IF $pLAct[iR2] = 1 THEN
DRAWSEGMENT($pLX[iR2], $pLY[iR2], barindex, $pLY[iR2]) STYLE(dottedLine, 1) COLOURED(120, 123, 134, 100)
ENDIF
NEXT
ENDIF
// 3) Historico de senales disparadas: linea horizontal (origen->disparo) + DRAWTEXT centrado
FOR iR3 = 0 TO sigCnt - 1 DO
IF $sDir[iR3] = 1 THEN
rT = 8
gT = 153
bT = 129
ELSE
rT = 242
gT = 54
bT = 69
ENDIF
// Senal contra trend previo -> alpha menor (sustituye 'dashed' Pine)
IF $sTrnd[iR3] = 1 THEN
alpha = 120
ELSE
alpha = 230
ENDIF
// Linea horizontal del nivel: desde origen hasta barra de disparo
DRAWSEGMENT($sOX[iR3], $sY[iR3], $sX[iR3], $sY[iR3]) STYLE(line, 1) COLOURED(rT, gT, bT, alpha)
// Etiqueta CISD centrada entre origen y disparo
xMid = ($sOX[iR3] + $sX[iR3]) / 2
DRAWTEXT("CISD", xMid, $sY[iR3]) COLOURED(rT, gT, bT, alpha)
NEXT
ENDIF
RETURN