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
- Window: a higher-timeframe candle (the PO3 candle) chosen automatically from the chart timeframe, or a session.
- Manipulation leg: a run of down candles that makes the window low (up candles for the high).
- CISD: a candle closes beyond the open of the first candle of that leg. All decisions are made on bar close.
- 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.
- 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).
- 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
| Input | Default | What it changes |
|---|---|---|
| PO3 window | Automatic | Which higher-timeframe candle defines the leg |
| Projection levels | −1 … −4 | The standard-deviation lines and zones drawn |
| Trading windows | London, New York | When setups may appear (trade layer) |
| HTF bias / displacement filters | Off | Require trend agreement or a strong CISD candle |
| Trade layer | Off | Draws 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)

