Skip to content

Premium collection — free download

SD Projections — Manipulation Leg Targets

Standard-deviation projections of manipulation legs with −2/−2.5 and −4 zones, confluence marks, FVGs and equal highs/lows.

PremiumSmart moneyv1.0TradingView (Pine Script v6) Non-repainting signals Alerts

What it does

SD Projections finds the manipulation leg behind each change in the state of delivery and projects it forward in standard deviations. It draws targets and confluence; it does not give buy or sell signals in the main edition.

How it works

  1. A run of down candles makes a low; the first candle's open is the CISD level.
  2. When a candle closes above that open, the run's low becomes level 1 and its high level 0 (mirror for bearish legs).
  3. Optional filters: the leg must sweep the lowest low of the previous 20 bars and must be at least 0.5 × ATR(14).
  4. Levels 1, 0, −1, −2, −2.5 and −4 are drawn. The −2/−2.5 block is the primary reaction area; the −4/−4.5 block is maximum expansion.
  5. Events are marked: −2 reached, a close beyond −2.5 (continuation to −4 becomes more likely), −4 hit, and invalidation on a close back beyond 1.
  6. Unmitigated fair value gaps and equal highs/lows are drawn; a ◆ marks a projection level that lines up with one of them, or a −4 that sits inside another projection's −2/−2.5.

How to use it

Work top-down: use a higher timeframe for direction, put the indicator on the middle timeframe to find legs and targets, and execute on a lower timeframe. Treat −2/−2.5 as the first objective and give most weight to levels marked ◆. The ProSuite edition adds an order-block retest entry with a stop beyond the sweep and targets at −2 and −4.

Settings

InputDefaultWhat it changes
Levels1, 0, −1, −2, −2.5, −4Which projection lines are drawn
Leg must sweep liquidityOnOnly legs that take a prior low/high qualify
Min leg size0.5 × ATRFilters out small, noisy legs
Points of interestOnFVGs and equal highs/lows used for ◆ confluence

Limitations

Projections are a framework, not a forecast; a trending market can run through every level. Average legs tend to reach −2/−2.5, small expanding legs −4, and very large legs often only −1, so read the leg size before expecting a target.

Pine Script source

In TradingView open Pine Editor, paste the code, click Save and then Add to chart.

SD_Projections_v1.0.pine

393 lines

//@version=6
// SD Projections v1.0: standard deviation projections of manipulation legs, level blocks and points of interest.
// Method (TTrades, "Standard Deviation Projections"):
//   Manipulation leg : the swing that sweeps liquidity just before a change in the state of delivery (CISD):
//                      the final low before a bullish CISD, the final high before a bearish CISD.
//   Anchor           : bullish → 1 = the low, 0 = the high that made that low; bearish → 1 = the high, 0 = the low.
//   Levels           : 1, 0, -1, -2, -2.5, -4. -2 / -2.5 = primary reaction area ("low-hanging fruit");
//                      a body close beyond -2 / -2.5 → continuation and max expansion more likely; -4 = max expansion.
//   Confluence       : a level that lines up with a PD array (FVG, equal highs / lows); the -4 of one projection
//                      inside the -2 / -2.5 of another = high-probability reaction zone.
//   Leg size         : average leg → -2 / -2.5, expanding → -4 / -4.5, large leg → expect only -1.
// Projections are targets and confluence, not signals. Decisions on bar close.
indicator("SD Projections v1.0", "SD Proj", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 500)

// ───────────────────────────── inputs
grpL = "Manipulation legs"
needSweep = input.bool(true, "Leg must sweep liquidity (prior low / high)", group = grpL)
sweepLook = input.int(20, "Liquidity lookback (bars before the leg)", minval = 3, maxval = 200, group = grpL)
minLegAtr = input.float(0.5, "Min leg size (x ATR14)", minval = 0, step = 0.1, group = grpL)
showBull  = input.bool(true, "Bullish projections", inline = "dir", group = grpL)
showBear  = input.bool(true, "Bearish projections", inline = "dir", group = grpL)
keepN     = input.int(6, "Projections to keep", minval = 1, maxval = 30, group = grpL)

grpP = "Projection"
levelsIn  = input.string("1, 0, -1, -2, -2.5, -4", "Levels", group = grpP)
projBars  = input.int(60, "Length to the right (bars)", minval = 5, maxval = 400, group = grpP)
showBand  = input.bool(true, "Shade the manipulation leg", group = grpP)
blockPri  = input.bool(true, "Block −2 to −2.5 (primary reaction)", group = grpP)
blockMax  = input.bool(true, "Block −4 to −4.5 (max expansion)", group = grpP)
showEvt   = input.bool(true, "Events: −2 reached, continuation, −4 hit, invalidated", group = grpP)
legSize   = input.bool(true, "Leg size note (large leg → −1, expanding → −4)", group = grpP)

grpC = "Points of interest and confluence"
showFvg   = input.bool(true, "Fair value gaps (unmitigated)", group = grpC)
fvgKeep   = input.int(8, "FVGs to keep", minval = 1, maxval = 40, group = grpC)
showEq    = input.bool(true, "Equal highs / lows", group = grpC)
pivLen    = input.int(3, "Swing strength (bars each side)", minval = 1, maxval = 10, group = grpC)
eqTolAtr  = input.float(0.1, "Equal highs / lows tolerance (x ATR14)", minval = 0, step = 0.05, group = grpC)
confTolAtr = input.float(0.15, "Confluence tolerance (x ATR14)", minval = 0, step = 0.05, group = grpC)
showStack = input.bool(true, "−4 inside another −2 / −2.5 (stacked zone)", group = grpC)

grpV = "Style"
themeIn   = input.string("Auto", "Theme", options = ["Auto", "Light", "Dark"], group = grpV)
labelLeft = input.bool(true, "Level labels on the left (TTrades style)", group = grpV)
showPanel = input.bool(true, "Panel", group = grpV)
panelPos  = input.string("Top right", "Panel position", options = ["Top right", "Bottom right", "Top left", "Bottom left"], group = grpV)
panelSize = input.string("Small", "Panel size", options = ["Tiny", "Small", "Normal"], group = grpV)
alOn      = input.bool(true, "Alerts (create one alert with 'Any alert() function call')", group = "Alerts")

// ───────────────────────────── palette
bgLum   = 0.299 * color.r(chart.bg_color) + 0.587 * color.g(chart.bg_color) + 0.114 * color.b(chart.bg_color)
isDark  = themeIn == "Dark" or (themeIn == "Auto" and bgLum < 128)
cBull   = isDark ? #4C9AFF : #1E63D6
cBear   = isDark ? #FF5C7A : #D02F4A
cLevel  = isDark ? #9AA0AC : #6B7080
cText   = isDark ? #D1D4DC : #131722
cMuted  = isDark ? #787B86 : #8A8E99
cBand   = isDark ? #FFFFFF : #000000
cPanel  = isDark ? #1E222D : #FFFFFF
cHead   = isDark ? #2A2E39 : #F0F3FA
cBorder = isDark ? #363A45 : #E0E3EB
cGold   = isDark ? #FFD54F : #E0A800
cPoi    = isDark ? #B39DDB : #7E57C2

atr = ta.atr(14)
ph  = ta.pivothigh(high, pivLen, pivLen)
pl  = ta.pivotlow(low, pivLen, pivLen)

// ───────────────────────────── types and state
type Proj
    int          id
    bool         bull
    float        zero          // the high (low) that made the low (high)
    float        one           // the sweep extreme
    int          bLeg          // first bar of the leg
    int          bExt          // bar of the extreme
    int          bCisd
    int          bEnd
    bool         live    = true
    bool         hit2    = false
    bool         cont    = false
    bool         hit4    = false
    bool         failed  = false
    string       note    = ""
    array<line>  lines
    array<label> labels
    array<box>   boxes

type Poi
    bool  bull       // bullish FVG / equal lows (support), bearish FVG / equal highs (resistance)
    bool  isFvg
    float top
    float bottom
    int   b1
    box   bx
    line  ln
    label lb

var lv     = array.new<float>()
if barstate.isfirst
    for s in str.split(str.replace_all(levelsIn, " ", ""), ",")
        v = str.tonumber(s)
        if not na(v)
            lv.push(v)

var projs  = array.new<Proj>()
var fvgs   = array.new<Poi>()
var eqs    = array.new<Poi>()
var legs   = array.new<float>()      // recent leg sizes, for the leg-size note
var int nextId = 0
var int nStack = 0
var int nPoiConf = 0

// CISD engine
var bool  prevDn = false
var bool  prevUp = false
var bool  pendBull = false
var bool  pendBear = false
var float bullLevel = na
var float bearLevel = na
var float legLow  = na
var float legHigh = na
var int   bLow  = na
var int   bHigh = na
var int   bRunDn = na
var int   bRunUp = na

// swing points for equal highs / lows
var float lastPH = na
var int   lastPHb = na
var float lastPL = na
var int   lastPLb = na

// ───────────────────────────── helpers
lvl(Proj p, float k) =>
    p.zero + k * (p.one - p.zero)

fmtK(float k) =>
    str.tostring(k, "#.#")

notify(string msg) =>
    if alOn
        alert("SD Proj · " + syminfo.ticker + " · " + msg, alert.freq_once_per_bar_close)

method wipe(Proj p) =>
    for l in p.lines
        l.delete()
    for lb in p.labels
        lb.delete()
    for b in p.boxes
        b.delete()

// a point of interest near price y that a projection in this direction is reaching for
poiNear(bool bullProj, float y) =>
    tol = confTolAtr * nz(atr)
    string hit = ""
    for q in fvgs
        if q.bull != bullProj and y <= q.top + tol and y >= q.bottom - tol
            hit := "FVG"
    for q in eqs
        if q.bull != bullProj and math.abs(y - q.top) <= tol
            hit := q.bull ? "EQL" : "EQH"
    hit

drawProj(Proj p) =>
    c   = p.bull ? cBull : cBear
    x1  = p.bLeg
    xr  = p.bCisd + projBars
    if showBand
        p.boxes.push(box.new(x1, math.max(p.zero, p.one), p.bCisd, math.min(p.zero, p.one), border_color = #00000000, bgcolor = color.new(cBand, 94)))
    if blockPri
        a = lvl(p, -2)
        b = lvl(p, -2.5)
        p.boxes.push(box.new(p.bCisd, math.max(a, b), xr, math.min(a, b), border_color = color.new(c, 70), bgcolor = color.new(c, 86), text = "−2 / −2.5", text_size = size.tiny, text_color = color.new(c, 20), text_halign = text.align_right, text_valign = text.align_center))
    if blockMax
        a = lvl(p, -4)
        b = lvl(p, -4.5)
        p.boxes.push(box.new(p.bCisd, math.max(a, b), xr, math.min(a, b), border_color = color.new(c, 70), bgcolor = color.new(c, 80), text = "max expansion", text_size = size.tiny, text_color = color.new(c, 20), text_halign = text.align_right, text_valign = text.align_center))
    for k in lv
        y    = lvl(p, k)
        anch = k == 1 or k == 0
        key  = k == -2 or k == -2.5 or k == -4
        lc   = anch ? c : key ? color.new(c, 10) : cLevel
        p.lines.push(line.new(anch ? x1 : p.bCisd, y, xr, y, color = lc, style = anch or key ? line.style_solid : line.style_dotted, width = key ? 2 : 1))
        poi  = key ? poiNear(p.bull, y) : ""
        txt  = fmtK(k) + (poi != "" ? "  ◆ " + poi : "")
        p.labels.push(label.new(labelLeft ? (anch ? x1 : p.bCisd) : xr, y, txt, color = #00000000, style = labelLeft ? label.style_label_right : label.style_label_left, textcolor = poi != "" ? cGold : anch ? c : key ? c : cLevel, size = size.small))
    // leg line from 0 to 1
    p.lines.push(line.new(x1, p.zero, p.bExt, p.one, color = color.new(c, 30), width = 2))
    if legSize and p.note != ""
        p.labels.push(label.new(p.bExt, p.one, p.note, color = #00000000, style = p.bull ? label.style_label_up : label.style_label_down, textcolor = c, size = size.tiny))

// TTrades stacked zone: -4 of one projection inside the -2 / -2.5 of another (same direction)
stack(Proj p) =>
    n = 0
    if showStack
        for q in projs
            if q.id != p.id and q.live and q.bull == p.bull
                for pair in array.from(0, 1)
                    a  = pair == 0 ? p : q
                    b  = pair == 0 ? q : p
                    y  = lvl(a, -4)
                    lo = math.min(lvl(b, -2), lvl(b, -2.5))
                    hi = math.max(lvl(b, -2), lvl(b, -2.5))
                    if y >= lo and y <= hi
                        n += 1
                        p.boxes.push(box.new(math.max(p.bCisd, q.bCisd), hi, math.max(p.bEnd, q.bEnd), lo, border_color = cGold, border_style = line.style_dashed, bgcolor = color.new(cGold, 80), text = "◆ −4 / −2 stack", text_color = cGold, text_size = size.tiny, text_halign = text.align_left, text_valign = text.align_top))
    n

// ───────────────────────────── main (closed bars only)
if barstate.isconfirmed
    // ---- points of interest: fair value gaps
    if showFvg
        if high[2] < low
            fvgs.push(Poi.new(true, true, low, high[2], bar_index - 2, box.new(bar_index - 2, low, bar_index + 5, high[2], border_color = #00000000, bgcolor = color.new(cPoi, 85), text = "FVG", text_size = size.tiny, text_color = color.new(cPoi, 20), text_halign = text.align_left)))
        if low[2] > high
            fvgs.push(Poi.new(false, true, low[2], high, bar_index - 2, box.new(bar_index - 2, low[2], bar_index + 5, high, border_color = #00000000, bgcolor = color.new(cPoi, 85), text = "FVG", text_size = size.tiny, text_color = color.new(cPoi, 20), text_halign = text.align_left)))
        if fvgs.size() > 0
            for i = fvgs.size() - 1 to 0
                q = fvgs.get(i)
                // mitigated: a close through the far side
                if q.bull ? close < q.bottom : close > q.top
                    q.bx.delete()
                    fvgs.remove(i)
                else
                    q.bx.set_right(bar_index + 5)
        while fvgs.size() > fvgKeep
            fvgs.shift().bx.delete()

    // ---- points of interest: equal highs / lows (swing points within tolerance)
    if showEq
        tol = eqTolAtr * nz(atr)
        if not na(ph)
            if not na(lastPH) and math.abs(ph - lastPH) <= tol and bar_index - pivLen - lastPHb < 150
                lvlEq = math.max(ph, lastPH)
                eqs.push(Poi.new(false, false, lvlEq, lvlEq, lastPHb, na, line.new(lastPHb, lvlEq, bar_index - pivLen, lvlEq, color = cPoi, width = 2), label.new(bar_index - pivLen, lvlEq, "EQH", color = #00000000, style = label.style_label_left, textcolor = cPoi, size = size.tiny)))
            lastPH  := ph
            lastPHb := bar_index - pivLen
        if not na(pl)
            if not na(lastPL) and math.abs(pl - lastPL) <= tol and bar_index - pivLen - lastPLb < 150
                lvlEq = math.min(pl, lastPL)
                eqs.push(Poi.new(true, false, lvlEq, lvlEq, lastPLb, na, line.new(lastPLb, lvlEq, bar_index - pivLen, lvlEq, color = cPoi, width = 2), label.new(bar_index - pivLen, lvlEq, "EQL", color = #00000000, style = label.style_label_left, textcolor = cPoi, size = size.tiny)))
            lastPL  := pl
            lastPLb := bar_index - pivLen
        if eqs.size() > 0
            for i = eqs.size() - 1 to 0
                q = eqs.get(i)
                // swept: liquidity taken, no longer a target
                if q.bull ? low < q.top : high > q.top
                    q.ln.set_style(line.style_dotted)
                    q.ln.set_color(color.new(cPoi, 60))
                    q.lb.set_text((q.bull ? "EQL" : "EQH") + " swept")
                    eqs.remove(i)
        while eqs.size() > 8
            old = eqs.shift()
            old.ln.delete()
            old.lb.delete()

    // ---- track every live projection
    for p in projs
        if p.live
            sg = p.bull ? 1.0 : -1.0
            c  = p.bull ? cBull : cBear
            if bar_index > p.bEnd
                p.live := false
            else if sg * (close - p.one) < 0
                p.live   := false
                p.failed := true
                for l in p.lines
                    l.set_color(color.new(cMuted, 70))
                for b in p.boxes
                    b.set_bgcolor(color.new(cMuted, 94))
                if showEvt
                    p.labels.push(label.new(bar_index, p.one, "invalidated", color = #00000000, style = p.bull ? label.style_label_down : label.style_label_up, textcolor = cMuted, size = size.tiny))
            else
                if not p.hit2 and sg * ((p.bull ? high : low) - lvl(p, -2)) >= 0
                    p.hit2 := true
                    notify((p.bull ? "bullish" : "bearish") + " projection reached −2 (reaction area)")
                if not p.cont and sg * (close - lvl(p, -2.5)) > 0
                    p.cont := true
                    if showEvt
                        p.labels.push(label.new(bar_index, p.bull ? high : low, "close beyond −2.5 → −4", color = #00000000, style = p.bull ? label.style_label_down : label.style_label_up, textcolor = c, size = size.tiny))
                    notify((p.bull ? "bullish" : "bearish") + " body close beyond −2.5: continuation to −4 more likely")
                if not p.hit4 and sg * ((p.bull ? high : low) - lvl(p, -4)) >= 0
                    p.hit4 := true
                    if showEvt
                        p.lines.push(line.new(p.bCisd + projBars, lvl(p, -4), bar_index, lvl(p, -4), color = cGold, width = 2))
                        p.labels.push(label.new(bar_index, lvl(p, -4), "−4 max expansion", color = #00000000, style = label.style_label_left, textcolor = cGold, size = size.tiny))
                    notify((p.bull ? "bullish" : "bearish") + " projection hit −4 (max expansion)")

    // ---- manipulation legs: the swing before a CISD
    isDn = close < open
    isUp = close > open
    minLeg = minLegAtr * nz(atr)
    for dir in array.from(1, -1)
        bull = dir == 1
        pend = bull ? pendBull : pendBear
        if pend and (bull ? low < legLow : high > legHigh)
            if bull
                legLow := low
                bLow   := bar_index
            else
                legHigh := high
                bHigh   := bar_index
        trig = bull ? close > bullLevel : close < bearLevel
        if pend and trig
            if bull
                pendBull := false
            else
                pendBear := false
            ext   = bull ? legLow : legHigh
            bExt  = bull ? bLow : bHigh
            bRun  = bull ? bRunDn : bRunUp
            // 0 = the high (low) that made the low (high): extreme of the leg from the candle before the run
            float zero = bull ? high[bar_index - bExt] : low[bar_index - bExt]
            for i = bar_index - bExt to math.min(bar_index - bRun + 1, 400)
                zero := bull ? math.max(zero, high[i]) : math.min(zero, low[i])
            // liquidity: the leg took the extreme of the bars before it
            float prior = na
            for i = bar_index - bRun + 1 to math.min(bar_index - bRun + sweepLook, 450)
                prior := na(prior) ? (bull ? low[i] : high[i]) : bull ? math.min(prior, low[i]) : math.max(prior, high[i])
            swept = not needSweep or (bull ? ext < prior : ext > prior)
            legSz = math.abs(zero - ext)
            if swept and legSz >= minLeg and (bull ? showBull : showBear)
                // leg size against recent legs: large → −1, expanding → −4
                avgLeg = legs.size() > 0 ? legs.avg() : legSz
                note = legSz > 1.8 * avgLeg ? "large leg → expect −1" : legSz < 0.6 * avgLeg ? "small leg → −4 / −4.5 possible" : "average leg → −2 / −2.5"
                legs.push(legSz)
                if legs.size() > 12
                    legs.shift()
                p = Proj.new(nextId, bull, zero, ext, bRun - 1, bExt, bar_index, bar_index + projBars, note = note, lines = array.new<line>(), labels = array.new<label>(), boxes = array.new<box>())
                nextId += 1
                drawProj(p)
                for k in array.from(-2.0, -2.5, -4.0)
                    if poiNear(bull, lvl(p, k)) != ""
                        nPoiConf += 1
                projs.push(p)
                nStack += stack(p)
                notify((bull ? "bullish" : "bearish") + " manipulation leg + CISD: −2 " + str.tostring(lvl(p, -2), format.mintick) + " · −2.5 " + str.tostring(lvl(p, -2.5), format.mintick) + " · −4 " + str.tostring(lvl(p, -4), format.mintick))
    if isDn and not prevDn
        bullLevel := open
        legLow    := low
        bLow      := bar_index
        bRunDn    := bar_index
        pendBull  := true
    if isUp and not prevUp
        bearLevel := open
        legHigh   := high
        bHigh     := bar_index
        bRunUp    := bar_index
        pendBear  := true
    if isDn or isUp
        prevDn := isDn
        prevUp := isUp
    while projs.size() > keepN
        projs.shift().wipe()

// ───────────────────────────── panel
tPos = panelPos == "Top right" ? position.top_right : panelPos == "Bottom right" ? position.bottom_right : panelPos == "Top left" ? position.top_left : position.bottom_left
tSz  = panelSize == "Tiny" ? size.tiny : panelSize == "Normal" ? size.normal : size.small
var table panel = table.new(tPos, 2, 7, bgcolor = cPanel, frame_color = cBorder, frame_width = 1, border_color = cPanel, border_width = 3)

cell(int c, int r, string s, color tc, color bg) =>
    table.cell(panel, c, r, s, text_color = tc, text_size = tSz, text_halign = c == 0 ? text.align_left : text.align_right, bgcolor = bg)

where(bool bull) =>
    string s = "—"
    for p in projs
        if p.bull == bull
            k = (close - p.zero) / (p.one - p.zero)
            nxt = float(na)
            for l in lv
                if l < k and (na(nxt) or l > nxt)
                    nxt := l
            s := str.tostring(k, "0.0") + " SD" + (p.failed ? " · invalid" : p.hit4 ? " · max expansion" : p.cont ? " · continuation" : p.hit2 ? " · in −2 zone" : "") + (na(nxt) or p.failed ? "" : " · next " + fmtK(nxt) + " " + str.tostring(lvl(p, nxt), format.mintick))
    s

if showPanel and barstate.islast
    cell(0, 0, "SD Projections", cText, cHead)
    cell(1, 0, "manipulation leg → −2 / −2.5 / −4", cMuted, cHead)
    cell(0, 1, "Bullish (latest)", cMuted, cPanel)
    cell(1, 1, where(true), cBull, cPanel)
    cell(0, 2, "Bearish (latest)", cMuted, cPanel)
    cell(1, 2, where(false), cBear, cPanel)
    cell(0, 3, "Levels on a PD array", cMuted, cPanel)
    cell(1, 3, str.tostring(nPoiConf) + " ◆", nPoiConf > 0 ? cGold : cMuted, cPanel)
    cell(0, 4, "Stacked −4 / −2 zones", cMuted, cPanel)
    cell(1, 4, str.tostring(nStack), nStack > 0 ? cGold : cMuted, cPanel)
    cell(0, 5, "Read", cMuted, cPanel)
    cell(1, 5, "−2/−2.5 react · close beyond → −4 · −4 max", cText, cPanel)
    cell(0, 6, "Not a signal", cMuted, cPanel)
    cell(1, 6, "targets + confluence for your model", cMuted, cPanel)

Key inputs

Key inputs of SD Projections — Manipulation Leg Targets
InputDefaultDescription
Levels1, 0, -1, -2, -2.5, -4Fib levels drawn from the manipulation leg.
Leg must sweep liquidityOnThe leg extreme must take out the 20-bar low (or high) before it.
Min leg size0.5 × ATR(14)Ignores legs that are too small to project.

Educational tools, not advice. Indicators and Expert Advisors describe price; they do not predict it, and past behaviour does not guarantee future results. Test everything on a demo account first and never risk money you cannot afford to lose. See our disclaimer.

Keep exploring

Related tradingview indicators