Skip to content

Premium collection — free download

STDV PO3 — Power of Three + CISD Projections

Power of Three windows, CISD triggers and standard-deviation projections with reaction zones, events and an optional trade layer.

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

What it does

STDV PO3 maps the Power of Three idea (accumulation, manipulation, distribution) onto the chart. It finds the manipulation leg that makes a higher-timeframe window's high or low, waits for a change in the state of delivery (CISD), and projects that leg forward in standard deviations so you can see where the move is likely to react, continue or exhaust.

It is a map of price, not a signal service. Version 5.0 keeps every structure on the chart with its leg, CISD level, projections and zones, and can optionally draw the setups and trade levels introduced in v3.0 and v4.0.

How it works

  1. Window: a higher-timeframe candle (the PO3 candle) chosen automatically from the chart timeframe, or a session.
  2. Manipulation leg: a run of down candles that makes the window low (up candles for the high).
  3. CISD: a candle closes beyond the open of the first candle of that leg. All decisions are made on bar close.
  4. Projection: 0 = the CISD level and 1 = the leg extreme. Levels −1 to −4 are drawn on the expansion side: −1 to −1.5 is the re-accumulation zone, −2 to −2.5 the main reaction zone and −3.5 to −4 the completion band.
  5. Events: −2 reached, a body close beyond −2.5 (continuation), −4 reached (max expansion), an opposite CISD after −2 (reversal) and a close back beyond 1 (invalidated).
  6. Confluence: where the −4 of one projection overlaps the −2/−2.5 of another, a confluence box is drawn.

How to use it

Use it on M1–M15 for intraday index, gold and forex charts. Read the higher timeframe first for direction, then treat −2/−2.5 as the first objective and expect −4 only after a close beyond −2.5. Older versions are included for traders who prefer their simpler drawings: v1.0 is the cleanest map, v3.0 the most explicit trade model.

Settings

InputDefaultWhat it changes
PO3 windowAutomaticWhich higher-timeframe candle defines the leg
Projection levels−1 … −4The standard-deviation lines and zones drawn
Trading windowsLondon, New YorkWhen setups may appear (trade layer)
HTF bias / displacement filtersOffRequire trend agreement or a strong CISD candle
Trade layerOffDraws entry, stop and targets for each setup

Limitations

Projections describe where price often reacts; they do not predict that it will. The setups were tested by the author in the MetaTrader 5 Strategy Tester and the results were mixed across different years, so treat any trade layer as a study tool. The indicator needs clean intraday data, and the panel record ignores spread and uses bar data, so it is not a backtest.

Pine Script source

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

STDV_PO3_v5.0.pine

693 lines

//@version=6
// STDV PO3 v5.0: the full concept map of v1.0 with the v4.0 setups.
// Every sweep + CISD becomes a structure and keeps its drawing:
//   manipulation leg (leg start → sweep extreme), CISD level, fib 1 / 0, projection levels and the
//   three zones (-1/-1.5 Silver Bullet, -2/-2.5 reaction, -3.5/-4 max expansion).
// Each structure is tracked bar by bar (decisions on bar close):
//   -2 reached → zone lights up; body close beyond -2.5 → "continuation"; -4 reached → "max expansion";
//   an opposite CISD whose leg reached -2 or deeper → "◆ reversal" (SD exhaustion);
//   close back beyond 1 → "invalidated", the structure fades.
// Confluence (TTrades): a -4 that lands inside another structure's -2/-2.5 zone is marked in gold.
// Setups: Fractal (C2 raid of the previous HTF candle), PO3 (window open / extreme), SD exhaustion.
// Optional trade layer: entry at the CISD close, stop beyond the leg, TP1 -1 (half, stop to entry), TP2 -2.
// Backtests of the trade layer on XAUUSD M1/M5 (Jul–Oct 2026) lost money; see STDV-PO3/v5.0/STRATEGY.md.
indicator("STDV PO3 v5.0", "STDV PO3", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)

// ───────────────────────────── inputs
grpS = "Setups"
useFr     = input.bool(true, "Fractal: sweep of the previous HTF candle", inline = "fr", group = grpS)
frIn      = input.timeframe("", "", inline = "fr", group = grpS, tooltip = "Empty = automatic: 1m → 15m, 2m-3m → 30m, 5m → 1H, 15m → 4H, 30m+ → D")
usePO3    = input.bool(true, "PO3: sweep of the window open / extreme", inline = "po", group = grpS)
po3In     = input.timeframe("", "", inline = "po", group = grpS, tooltip = "Empty = automatic: 1m-3m → 1H, 5m-15m → 4H, 30m+ → D")
useSD     = input.bool(true, "SD exhaustion: leg reached the opposite projection", inline = "sd", group = grpS)
sdMinK    = input.float(-2.0, "", maxval = -0.5, step = 0.5, inline = "sd", group = grpS)
minLegAtr = input.float(0.3, "Min leg size (x ATR14)", minval = 0, step = 0.1, group = grpS)
dispAtr   = input.float(0.0, "Min CISD candle body (x ATR14, 0 = off)", minval = 0, step = 0.1, group = grpS, tooltip = "Displacement: the CISD candle must have a body of at least this size")
useBiasH  = input.bool(false, "Only with the last PO3-window closure (HTF bias)", group = grpS, tooltip = "Bullish when the last closed window swept the one before it and closed back inside, or closed above its high")

grpC = "Concept map"
maxStruct = input.int(16, "Structures to keep", minval = 2, maxval = 40, group = grpC)
levelsIn  = input.string("-1,-1.5,-2,-2.5,-3,-3.5,-4", "Projection levels", group = grpC)
showZones = input.bool(true, "Zones -1/-1.5, -2/-2.5, -3.5/-4", group = grpC)
projBars  = input.int(80, "Projection length (bars)", minval = 10, maxval = 400, group = grpC)
showLegs  = input.bool(true, "Manipulation legs", group = grpC)
showEvt   = input.bool(true, "Events: continuation, max expansion, reversal, invalidated", group = grpC)
showConf  = input.bool(true, "Confluence: -4 inside another -2/-2.5", group = grpC)
showC1    = input.bool(true, "Previous HTF candle high / low (C1)", group = grpC)
showWOpen = input.bool(true, "PO3 window open", group = grpC)
showMid   = input.bool(true, "NY midnight open", group = grpC)
showKZ    = input.bool(true, "Shade trading windows", group = grpC)

grpM = "Trade layer"
showTrades = input.bool(true, "BUY / SELL signals with position boxes", group = grpM)
tp1K       = input.float(-1.0, "TP1 (SD level, take half)", maxval = -0.5, step = 0.5, group = grpM)
tp2K       = input.float(-2.0, "TP2 (SD level, exit)", maxval = -0.5, step = 0.5, group = grpM)
beAfterTp1 = input.bool(true, "Move stop to entry after TP1", group = grpM)
slBufAtr   = input.float(0.1, "Stop buffer beyond the leg (x ATR14)", minval = 0, step = 0.05, group = grpM)
minRR      = input.float(1.0, "Min reward:risk to TP2", minval = 0, step = 0.1, group = grpM)
useKZ      = input.bool(true, "Only in trading windows", group = grpM)
kz1        = input.session("0200-0500", "London", group = grpM)
kz2        = input.session("0700-1100", "New York AM", group = grpM)
kz3        = input.session("1330-1600", "New York PM", group = grpM)
maxDay     = input.int(6, "Max signals per day", minval = 1, group = grpM)
tz         = input.string("America/New_York", "Timezone", group = grpM)
keepN      = input.int(6, "Past trades to keep", minval = 0, maxval = 50, group = grpM)

grpV = "Style"
themeIn   = input.string("Auto", "Theme", options = ["Auto", "Light", "Dark"], group = grpV)
showTrig  = input.bool(true, "Next trigger line", 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: setups, trades, reversals, max expansion (create one alert with 'Any alert() function call')", group = "Alerts")

// ───────────────────────────── palette (follows the chart background unless forced)
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     // structures (v1 blue / red)
cBear   = isDark ? #FF5C7A : #D02F4A
cWin    = isDark ? #22AB94 : #089981     // trade boxes
cLoss   = isDark ? #F7525F : #F23645
cText   = isDark ? #D1D4DC : #131722
cMuted  = isDark ? #787B86 : #8A8E99
cPanel  = isDark ? #1E222D : #FFFFFF
cHead   = isDark ? #2A2E39 : #F0F3FA
cBorder = isDark ? #363A45 : #E0E3EB
cGold   = isDark ? #FFB74D : #C77700
cOnPill = #FFFFFF

// ───────────────────────────── time
atr = ta.atr(14)

autoFr() =>
    m = timeframe.in_seconds() / 60
    m <= 1 ? "15" : m <= 3 ? "30" : m <= 5 ? "60" : m <= 15 ? "240" : "D"

autoPO3() =>
    m = timeframe.in_seconds() / 60
    m <= 3 ? "60" : m <= 15 ? "240" : "D"

tfLabel(string tf) =>
    s = timeframe.in_seconds(tf)
    s >= 86400 ? "D" : s >= 3600 ? str.tostring(math.round(s / 3600)) + "H" : str.tostring(math.round(s / 60)) + "m"

frTf    = frIn == "" ? autoFr() : frIn
poTf    = po3In == "" ? autoPO3() : po3In
frName  = tfLabel(frTf)
poName  = tfLabel(poTf)
frStart = time(frTf)
newFr   = frStart != frStart[1]
poStart = time(poTf)
newPo   = poStart != poStart[1]
inKZ    = not na(time(timeframe.period, kz1, tz)) or not na(time(timeframe.period, kz2, tz)) or not na(time(timeframe.period, kz3, tz))
nyDay   = dayofmonth(time, tz)
newNY   = nyDay != nyDay[1]

bgcolor(showKZ and inKZ and timeframe.isintraday ? color.new(cGold, 95) : na, title = "Trading window")

// ───────────────────────────── types
type Struct
    int          id
    bool         bull
    float        zero          // CISD level
    float        one           // leg extreme
    int          bRun          // first candle of the leg
    int          bExt          // bar of the extreme
    int          bCisd
    int          tCisd
    int          bEnd
    string       setups
    bool         live     = true
    bool         hit2     = false
    bool         cont     = false
    bool         hit4     = false
    bool         failed   = false
    array<line>  lines
    array<label> labels
    array<box>   boxes

type Trade
    bool         bull
    int          state             // 1 open, 2 closed
    float        entry
    float        sl
    float        tp1
    float        tp2
    float        risk
    bool         mFr
    bool         mPo
    bool         mSd
    string       setups = ""
    bool         tp1Hit = false
    int          bSig
    label        sig
    label        res
    box          pBox
    box          rBox
    array<line>  lines
    array<label> labels

// ───────────────────────────── state
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 Trade  cur      = na
var Struct lastBull = na
var Struct lastBear = na
var int    nextId   = 0
var trades    = array.new<Trade>()
var structs   = array.new<Struct>()
var ctxLines  = array.new<line>()
var ctxLabels = array.new<label>()
var line  trigL  = na
var label trigLb = na

var float midOpen  = na
var int   dayCount = 0
// PO3 window and HTF bias (closure of the last window against the one before)
var int   wsT   = na
var float wOpen = na
var float wHigh = na
var float wLow  = na
var float wClose = na
var float c1H   = na
var float c1L   = na
var int   bias  = 0
// fractal: current (0), previous (1, C1) and the one before (2) HTF candles
var int   fT0 = na
var int   fT1 = na
var float fH0 = na
var float fL0 = na
var float fH1 = na
var float fL1 = na
var float fH2 = na
var float fL2 = na
// bullish CISD (down leg)
var bool  prevDn    = false
var bool  pendBull  = false
var float bullLevel = na
var float legLow    = na
var int   tLow      = na
var int   bLow      = na
var int   bRunDn    = na
// bearish CISD (up leg)
var bool  prevUp    = false
var bool  pendBear  = false
var float bearLevel = na
var float legHigh   = na
var int   tHigh     = na
var int   bHigh     = na
var int   bRunUp    = na
// next trigger
var int    watchDir = 0
var float  wLevel   = na
var string wSetups  = ""
// record: total, Fractal, PO3, SD
var stN = array.new<int>(4, 0)
var stW = array.new<int>(4, 0)
var stR = array.new<float>(4, 0.0)
// event counters for the panel
var int nRev  = 0
var int nCont = 0
var int nMax  = 0
var int nFail = 0

bool sigBuy  = false
bool sigSell = false

// ───────────────────────────── helpers
px(float v) =>
    str.tostring(v, format.mintick)

fmtR(float r) =>
    (r >= 0 ? "+" : "−") + str.tostring(math.abs(r), "0.0") + "R"

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

lvlOf(float zero, float one, float k) =>
    zero + k * (one - zero)

lvl(Struct s, float k) =>
    lvlOf(s.zero, s.one, k)

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

addCtx(line l, label lb) =>
    ctxLines.push(l)
    ctxLabels.push(lb)
    if ctxLines.size() > 12
        ctxLines.shift().delete()
        ctxLabels.shift().delete()

// setup conditions for a leg extreme `ext` made at time `tExt`
frCond(bool bull, float ext, int tExt) =>
    bull ? (tExt >= fT0 and ext <= fL0 and ext < fL1) or (tExt >= fT1 and tExt < fT0 and ext <= fL1 and ext < fL2 and ext <= fL0) : (tExt >= fT0 and ext >= fH0 and ext > fH1) or (tExt >= fT1 and tExt < fT0 and ext >= fH1 and ext > fH2 and ext >= fH0)

poCond(bool bull, float ext, int tExt) =>
    tExt >= wsT and (bull ? ext <= wLow and ext < wOpen : ext >= wHigh and ext > wOpen)

// deepest level the leg reached in a live opposite structure (na if none)
sdDepth(bool bull, float ext, int tExt) =>
    float best = na
    for q in structs
        if q.bull != bull and q.live and tExt >= q.tCisd and q.one != q.zero
            k = (ext - q.zero) / (q.one - q.zero)
            if na(best) or k < best
                best := k
    best

setupText(bool fr, bool po, float k) =>
    s = ""
    if fr
        s += frName + " sweep"
    if po
        s += (s == "" ? "" : " · ") + "PO3 " + poName
    if not na(k)
        s += (s == "" ? "" : " · ") + "SD " + fmtK(k)
    s

// ───────────────────────────── structures
method wipeS(Struct s) =>
    for l in s.lines
        l.delete()
    for lb in s.labels
        lb.delete()
    for b in s.boxes
        b.delete()

evt(Struct s, int x, float y, string txt, color c, bool up) =>
    if showEvt
        s.labels.push(label.new(x, y, txt, color = #00000000, style = up ? label.style_label_up : label.style_label_down, textcolor = c, size = size.tiny))

drawStruct(Struct s) =>
    c  = s.bull ? cBull : cBear
    xr = s.bEnd
    if showLegs
        s.lines.push(line.new(s.bRun, s.zero, s.bExt, s.one, color = color.new(c, 25), width = 2))
        s.labels.push(label.new(s.bExt, s.one, s.setups, color = #00000000, style = s.bull ? label.style_label_up : label.style_label_down, textcolor = c, size = size.tiny))
    s.lines.push(line.new(s.bRun, s.zero, xr, s.zero, color = c, style = line.style_dashed))
    s.labels.push(label.new(xr, s.zero, "0 · CISD", color = #00000000, style = label.style_label_left, textcolor = c, size = size.tiny))
    s.lines.push(line.new(s.bExt, s.one, xr, s.one, color = color.new(c, 40)))
    s.labels.push(label.new(xr, s.one, "1", color = #00000000, style = label.style_label_left, textcolor = color.new(c, 30), size = size.tiny))
    for k in lv
        y   = lvl(s, k)
        key = k == -2 or k == -4
        s.lines.push(line.new(s.bCisd, y, xr, y, color = key ? c : color.new(c, 45), style = key ? line.style_solid : line.style_dotted))
        s.labels.push(label.new(xr, y, fmtK(k), color = #00000000, style = label.style_label_left, textcolor = key ? c : color.new(c, 30), size = size.tiny))
    if showZones
        for z in array.from(-1.0, -2.0, -3.5)
            a  = lvl(s, z)
            b  = lvl(s, z == -3.5 ? -4.0 : z - 0.5)
            tr = z == -1.0 ? 92 : z == -2.0 ? 86 : 82
            s.boxes.push(box.new(s.bCisd, math.max(a, b), xr, math.min(a, b), border_color = #00000000, bgcolor = color.new(c, tr)))

fade(Struct s) =>
    for l in s.lines
        l.set_color(color.new(cMuted, 70))
    for b in s.boxes
        b.set_bgcolor(color.new(cMuted, 94))

// TTrades confluence: this structure's -4 inside another's -2/-2.5 zone (or the other way round)
confluence(Struct s) =>
    if showConf
        for q in structs
            if q.id != s.id and q.live and q.bull == s.bull
                for pair in array.from(0, 1)
                    a = pair == 0 ? s : q
                    b = pair == 0 ? q : s
                    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
                        x = math.max(s.bCisd, q.bCisd)
                        s.boxes.push(box.new(x, hi, math.max(s.bEnd, q.bEnd), lo, border_color = cGold, border_style = line.style_dashed, bgcolor = color.new(cGold, 85), text = "◆ −4 / −2 confluence", text_color = cGold, text_size = size.tiny, text_halign = text.align_right, text_valign = text.align_top))

// ───────────────────────────── trades
rOf(Trade t, float p) =>
    (t.bull ? p - t.entry : t.entry - p) / t.risk

tag(Trade t, int x, float y, string s, color c) =>
    t.labels.push(label.new(x, y, s, color = #00000000, style = label.style_label_left, textcolor = c, size = size.small))

hl(Trade t, int x2, float y, color c, string st) =>
    t.lines.push(line.new(t.bSig, y, x2, y, color = c, style = st, width = 2))

method wipe(Trade t) =>
    for l in t.lines
        l.delete()
    for lb in t.labels
        lb.delete()
    t.lines.clear()
    t.labels.clear()

drawLive(Trade t) =>
    t.wipe()
    xr = bar_index + 6
    t.pBox.set_right(xr)
    t.rBox.set_right(xr)
    hl(t, xr, t.entry, cText, line.style_solid)
    hl(t, xr, t.sl, cLoss, line.style_solid)
    hl(t, xr, t.tp1, cWin, line.style_dotted)
    hl(t, xr, t.tp2, cWin, line.style_solid)
    tag(t, xr, t.entry, "Entry  " + px(t.entry), cText)
    tag(t, xr, t.sl, "Stop  " + px(t.sl) + "   " + fmtR(rOf(t, t.sl)), cLoss)
    tag(t, xr, t.tp1, "TP1 ½  " + px(t.tp1) + "   " + fmtR(rOf(t, t.tp1)) + (t.tp1Hit ? "  ✓" : ""), cWin)
    tag(t, xr, t.tp2, "TP2  " + px(t.tp2) + "   " + fmtR(rOf(t, t.tp2)), cWin)

newTrade(bool bull, float entry, float sl, float tp1, float tp2, bool fr, bool po, bool sd, string setups) =>
    t = Trade.new(bull, 1, entry, sl, tp1, tp2, math.abs(entry - sl), fr, po, sd, setups, bSig = bar_index, lines = array.new<line>(), labels = array.new<label>())
    grade = (fr ? 1 : 0) + (po ? 1 : 0) + (sd ? 1 : 0)
    if showTrades
        t.sig  := label.new(bar_index, bull ? low : high, (bull ? "BUY" : "SELL") + (grade >= 2 ? "  A+" : ""), yloc = bull ? yloc.belowbar : yloc.abovebar, style = bull ? label.style_label_up : label.style_label_down, color = bull ? cWin : cLoss, textcolor = cOnPill, size = size.small)
        t.pBox := box.new(bar_index, math.max(entry, tp2), bar_index + 6, math.min(entry, tp2), border_color = #00000000, bgcolor = color.new(cWin, 86))
        t.rBox := box.new(bar_index, math.max(entry, sl), bar_index + 6, math.min(entry, sl), border_color = #00000000, bgcolor = color.new(cLoss, 86))
        drawLive(t)
    t

record(Trade t, float r) =>
    for i = 0 to 3
        if i == 0 or (i == 1 and t.mFr) or (i == 2 and t.mPo) or (i == 3 and t.mSd)
            stN.set(i, stN.get(i) + 1)
            stW.set(i, stW.get(i) + (r > 0.05 ? 1 : 0))
            stR.set(i, stR.get(i) + r)

finish(Trade t, float exitPx, float r) =>
    t.wipe()
    t.state := 2
    if showTrades
        t.pBox.set_right(bar_index)
        t.rBox.set_right(bar_index)
        t.pBox.set_bgcolor(color.new(cWin, 92))
        t.rBox.set_bgcolor(color.new(cLoss, 92))
        t.res := label.new(bar_index, exitPx, math.abs(r) < 0.05 ? "BE" : fmtR(r), style = label.style_label_left, color = r > 0.05 ? cWin : r < -0.05 ? cLoss : cMuted, textcolor = cOnPill, size = size.tiny)
    record(t, r)

dropTrade(Trade t) =>
    t.wipe()
    t.sig.delete()
    t.res.delete()
    t.pBox.delete()
    t.rBox.delete()

// ───────────────────────────── main (closed bars only)
if barstate.isconfirmed
    if newNY
        midOpen  := open
        dayCount := 0
        if showMid and timeframe.isintraday
            nb = math.round(86400 / timeframe.in_seconds())
            addCtx(line.new(bar_index, open, bar_index + nb, open, color = color.new(cMuted, 30), style = line.style_dashed), label.new(bar_index, open, "NY open", color = #00000000, style = label.style_label_lower_left, textcolor = cMuted, size = size.tiny))

    if newPo or na(wsT)
        if not na(wsT)
            if not na(c1H)
                bias := (wLow < c1L and wClose > c1L) or wClose > c1H ? 1 : (wHigh > c1H and wClose < c1H) or wClose < c1L ? -1 : 0
            c1H := wHigh
            c1L := wLow
        wsT   := poStart
        wOpen := open
        wHigh := high
        wLow  := low
        if showWOpen
            nb = math.round(timeframe.in_seconds(poTf) / timeframe.in_seconds())
            addCtx(line.new(bar_index, open, bar_index + nb, open, color = color.new(cMuted, 50), style = line.style_dotted), label.new(bar_index, open, poName + " open", color = #00000000, style = label.style_label_lower_left, textcolor = color.new(cMuted, 20), size = size.tiny))
    else
        wHigh := math.max(wHigh, high)
        wLow  := math.min(wLow, low)
    wClose := close

    if newFr or na(fT0)
        fH2 := fH1
        fL2 := fL1
        fH1 := fH0
        fL1 := fL0
        fT1 := fT0
        fT0 := frStart
        fH0 := high
        fL0 := low
        if showC1 and not na(fH1)
            nb = math.round(timeframe.in_seconds(frTf) / timeframe.in_seconds())
            addCtx(line.new(bar_index, fH1, bar_index + nb, fH1, color = color.new(cBear, 60), style = line.style_dotted), label.new(bar_index + nb, fH1, "C1 high", color = #00000000, style = label.style_label_left, textcolor = color.new(cBear, 40), size = size.tiny))
            addCtx(line.new(bar_index, fL1, bar_index + nb, fL1, color = color.new(cBull, 60), style = line.style_dotted), label.new(bar_index + nb, fL1, "C1 low", color = #00000000, style = label.style_label_left, textcolor = color.new(cBull, 40), size = size.tiny))
    else
        fH0 := math.max(fH0, high)
        fL0 := math.min(fL0, low)

    // ---- track every live structure
    for s in structs
        if s.live
            sg = s.bull ? 1.0 : -1.0
            c  = s.bull ? cBull : cBear
            if bar_index > s.bEnd
                s.live := false
            else if sg * (close - s.one) < 0
                s.live   := false
                s.failed := true
                nFail    += 1
                fade(s)
                evt(s, bar_index, s.one, "invalidated", cMuted, not s.bull)
            else
                if not s.hit2 and sg * ((s.bull ? high : low) - lvl(s, -2)) >= 0
                    s.hit2 := true
                    if showZones and s.boxes.size() >= 2
                        s.boxes.get(1).set_border_color(color.new(c, 30))
                if not s.cont and sg * (close - lvl(s, -2.5)) > 0
                    s.cont := true
                    nCont  += 1
                    evt(s, bar_index, s.bull ? high : low, "continuation → −4", c, not s.bull)
                if not s.hit4 and sg * ((s.bull ? high : low) - lvl(s, -4)) >= 0
                    s.hit4 := true
                    nMax   += 1
                    evt(s, bar_index, lvl(s, -4), "max expansion", c, not s.bull)
                    notify((s.bull ? "bullish" : "bearish") + " projection reached −4 (max expansion)")

    // ---- manage the trade (from the bar after the signal)
    if not na(cur) and cur.state == 1
        t = cur
        if t.bull ? low <= t.sl : high >= t.sl
            r = t.tp1Hit ? 0.5 * rOf(t, t.tp1) + 0.5 * rOf(t, t.sl) : rOf(t, t.sl)
            finish(t, t.sl, r)
            notify((t.bull ? "BUY" : "SELL") + (t.tp1Hit ? " closed at entry after TP1 " : " stopped out ") + fmtR(r))
        else
            if not t.tp1Hit and (t.bull ? high >= t.tp1 : low <= t.tp1)
                t.tp1Hit := true
                if beAfterTp1
                    t.sl := t.entry
                notify((t.bull ? "BUY" : "SELL") + " TP1 hit, take half" + (beAfterTp1 ? ", stop to entry" : ""))
            if t.bull ? high >= t.tp2 : low <= t.tp2
                r = 0.5 * rOf(t, t.tp1) + 0.5 * rOf(t, t.tp2)
                finish(t, t.tp2, r)
                notify((t.bull ? "BUY" : "SELL") + " TP2 hit " + fmtR(r))
        if t.state == 1 and showTrades
            drawLive(t)

    // ---- CISD engine
    minLeg = minLegAtr * nz(atr)
    buf    = slBufAtr * nz(atr)
    isDn   = close < open
    isUp   = close > open

    for dir in array.from(1, -1)
        bull  = dir == 1
        pend  = bull ? pendBull : pendBear
        lvlC  = bull ? bullLevel : bearLevel
        ext   = bull ? legLow : legHigh
        tExt  = bull ? tLow : tHigh
        if pend and (bull ? low < ext : high > ext)
            ext  := bull ? low : high
            tExt := time
            if bull
                legLow := low
                tLow   := time
                bLow   := bar_index
            else
                legHigh := high
                tHigh   := time
                bHigh   := bar_index
        if pend and (bull ? close > lvlC : close < lvlC)
            if bull
                pendBull := false
            else
                pendBear := false
            frR = frCond(bull, ext, tExt)
            poR = poCond(bull, ext, tExt)
            k   = sdDepth(bull, ext, tExt)
            sdR = not na(k) and k <= sdMinK
            if (frR or poR or sdR) and math.abs(lvlC - ext) >= minLeg
                fr = useFr and frR
                po = usePO3 and poR
                sd = useSD and sdR
                setups = setupText(frR, poR, sdR ? k : na)
                s = Struct.new(nextId, bull, lvlC, ext, bull ? bRunDn : bRunUp, bull ? bLow : bHigh, bar_index, time, bar_index + projBars, setups, lines = array.new<line>(), labels = array.new<label>(), boxes = array.new<box>())
                nextId += 1
                drawStruct(s)
                // SD exhaustion: mark the reversal on the structure that was exhausted
                if sdR
                    nRev += 1
                    for q in structs
                        if q.bull != bull and q.live and time >= q.tCisd
                            kq = (ext - q.zero) / (q.one - q.zero)
                            if kq <= sdMinK
                                evt(q, bar_index, ext, "◆ reversal at " + fmtK(kq), bull ? cBull : cBear, bull)
                                q.live := false
                    notify((bull ? "bullish" : "bearish") + " reversal: opposite projection exhausted at " + fmtK(k))
                structs.push(s)
                confluence(s)
                if bull
                    lastBull := s
                else
                    lastBear := s
                // trade layer
                disp = dispAtr <= 0 or math.abs(close - open) >= dispAtr * nz(atr)
                okBias = not useBiasH or bias == dir
                if (fr or po or sd) and disp and okBias and (na(cur) or cur.state >= 2)
                    entry = close
                    sl    = bull ? ext - buf : ext + buf
                    tp1   = lvlOf(lvlC, ext, tp1K)
                    tp2   = lvlOf(lvlC, ext, tp2K)
                    rr    = math.abs(entry - sl) > 0 ? math.abs(tp2 - entry) / math.abs(entry - sl) : 0.0
                    inside = bull ? tp1 > entry : tp1 < entry
                    if (not useKZ or inKZ) and dayCount < maxDay and inside and rr >= minRR
                        cur := newTrade(bull, entry, sl, tp1, tp2, fr, po, sd, setups)
                        trades.push(cur)
                        dayCount += 1
                        if bull
                            sigBuy := true
                        else
                            sigSell := true
                        notify((bull ? "BUY" : "SELL") + " (" + setups + ") " + px(entry) + " · stop " + px(sl) + " · TP1 " + px(tp1) + " · TP2 " + px(tp2))

    if isDn and not prevDn
        bullLevel := open
        legLow    := low
        tLow      := time
        bLow      := bar_index
        bRunDn    := bar_index
        pendBull  := true
    if isUp and not prevUp
        bearLevel := open
        legHigh   := high
        tHigh     := time
        bHigh     := bar_index
        bRunUp    := bar_index
        pendBear  := true
    // dojis do not break a run
    if isDn or isUp
        prevDn := isDn
        prevUp := isUp

    while trades.size() > keepN + 1
        dropTrade(trades.shift())
    while structs.size() > maxStruct
        structs.shift().wipeS()

    // ---- next trigger: a qualifying leg waiting for its CISD
    free  = na(cur) or cur.state >= 2
    kB    = sdDepth(true, legLow, tLow)
    kS    = sdDepth(false, legHigh, tHigh)
    frB   = useFr and frCond(true, legLow, tLow)
    poB   = usePO3 and poCond(true, legLow, tLow)
    sdB   = useSD and not na(kB) and kB <= sdMinK
    frS   = useFr and frCond(false, legHigh, tHigh)
    poS   = usePO3 and poCond(false, legHigh, tHigh)
    sdS   = useSD and not na(kS) and kS <= sdMinK
    buyW  = pendBull and (frB or poB or sdB) and bullLevel - legLow >= minLeg
    sellW = pendBear and (frS or poS or sdS) and legHigh - bearLevel >= minLeg
    watchDir := buyW and sellW ? (tLow > tHigh ? 1 : -1) : buyW ? 1 : sellW ? -1 : 0
    if watchDir == 1
        wLevel  := bullLevel
        wSetups := setupText(frB, poB, sdB ? kB : na)
    else if watchDir == -1
        wLevel  := bearLevel
        wSetups := setupText(frS, poS, sdS ? kS : na)
    trigL.delete()
    trigLb.delete()
    if showTrig and watchDir != 0 and free
        dc = watchDir == 1 ? cBull : cBear
        x1 = watchDir == 1 ? bRunDn : bRunUp
        trigL  := line.new(x1, wLevel, bar_index + 6, wLevel, color = color.new(dc, 10), style = line.style_dashed, width = 2)
        trigLb := label.new(bar_index + 6, wLevel, (watchDir == 1 ? "CISD up if a candle closes above  " : "CISD down if a candle closes below  ") + px(wLevel), color = #00000000, style = label.style_label_left, textcolor = dc, size = size.small)

alertcondition(sigBuy, "BUY signal", "STDV PO3: BUY")
alertcondition(sigSell, "SELL signal", "STDV PO3: SELL")

// ───────────────────────────── 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, 3, 15, bgcolor = cPanel, frame_color = cBorder, frame_width = 1, border_color = cPanel, border_width = 3)
var bool  merged = false

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

row(int r, string a, string b, color bc, string c3, color c3c) =>
    cell(0, r, a, cMuted, cPanel, false)
    cell(1, r, b, bc, cPanel, true)
    cell(2, r, c3, c3c, cPanel, true)

statRow(int r, string name, int i, bool on) =>
    n = stN.get(i)
    w = n > 0 ? 100.0 * stW.get(i) / n : 0.0
    R = stR.get(i)
    row(r, name, on or i == 0 ? str.tostring(n) + " · " + str.tostring(w, "0") + "%" : "off", on or i == 0 ? cText : cMuted, n > 0 ? fmtR(R) : "", R >= 0 ? cWin : cLoss)

// position of price inside the latest structure, in SD
sdNow(Struct s) =>
    na(s) ? "—" : fmtK(math.round((close - s.zero) / (s.one - s.zero) * 10) / 10.0) + (s.failed ? " · invalid" : s.hit4 ? " · max exp." : s.cont ? " · continuation" : s.hit2 ? " · in −2 zone" : "")

if showPanel and barstate.islast
    if not merged
        table.merge_cells(panel, 1, 0, 2, 0)
        merged := true
    t      = cur
    active = not na(t) and t.state == 1
    pill   = "MAPPING"
    pillBg = cHead
    pillTx = cMuted
    if active
        dc = t.bull ? cWin : cLoss
        pill   := (t.bull ? "BUY" : "SELL") + " · LIVE"
        pillBg := dc
        pillTx := cOnPill
        row(1, "Entry", px(t.entry), cText, "", cText)
        row(2, "Stop", px(t.sl), cLoss, fmtR(rOf(t, t.sl)), cLoss)
        row(3, "TP1 · take ½", px(t.tp1), cWin, fmtR(rOf(t, t.tp1)) + (t.tp1Hit ? " ✓" : ""), cWin)
        row(4, "TP2 · exit", px(t.tp2), cWin, fmtR(rOf(t, t.tp2)), cWin)
        row(5, "Setup", t.setups, cGold, "", cText)
    else if watchDir != 0
        dc = watchDir == 1 ? cBull : cBear
        pill   := watchDir == 1 ? "BULLISH CISD FORMING" : "BEARISH CISD FORMING"
        pillBg := color.new(dc, 82)
        pillTx := dc
        row(1, "Trigger", (watchDir == 1 ? "close > " : "close < ") + px(wLevel), dc, "", cText)
        row(2, "Sweep", wSetups, cGold, "", cText)
        row(3, "", "", cText, "", cText)
        row(4, "", "", cText, "", cText)
        row(5, "", "", cText, "", cText)
    else
        row(1, "Waiting for", "a sweep, then a CISD", cText, "", cText)
        row(2, "", "", cText, "", cText)
        row(3, "", "", cText, "", cText)
        row(4, "", "", cText, "", cText)
        row(5, "", "", cText, "", cText)
    cell(0, 0, "STDV PO3 v5", cText, cHead, false)
    table.cell(panel, 1, 0, pill, text_color = pillTx, text_size = tSz, text_halign = text.align_center, bgcolor = pillBg)
    row(6, "Bull structure", sdNow(lastBull), cBull, "", cText)
    row(7, "Bear structure", sdNow(lastBear), cBear, "", cText)
    row(8, "HTF bias (" + poName + ")", bias == 1 ? "bullish" : bias == -1 ? "bearish" : "neutral", bias == 1 ? cBull : bias == -1 ? cBear : cMuted, "", cText)
    row(9, "Window", inKZ ? "open" : "closed", inKZ ? cGold : cMuted, "", cText)
    row(10, "Events", "◆" + str.tostring(nRev) + "  →" + str.tostring(nCont) + "  max " + str.tostring(nMax) + "  ✕" + str.tostring(nFail), cText, "", cText)
    statRow(11, "Record", 0, true)
    statRow(12, "· Fractal " + frName, 1, useFr)
    statRow(13, "· PO3 " + poName, 2, usePO3)
    statRow(14, "· SD exhaustion", 3, useSD)

Key inputs

Key inputs of STDV PO3 — Power of Three + CISD Projections
InputDefaultDescription
PO3 windowAutomatic from chart timeframeThe higher-timeframe candle whose manipulation leg is projected.
Projection levels-1, -1.5, -2, -2.5, -3.5, -4Standard-deviation multiples drawn on the expansion side.
Trading windowsLondon and New York (NY time)Time windows in which setups are allowed when the trade layer is on.

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