//@version=6 indicator("PO3 Ranges", shorttitle="PO3 Ranges", overlay=true, max_lines_count=500, max_labels_count=500, max_boxes_count=500) // ═══════════════════════════════════════════════════════════════════════════ // The PO3 range you are in, with all of its own levels — and optionally the // range directly above and below it. // // RH / RL range high / low 0% · 100% // EQ range equilibrium 50% // IRD RH − range/9 and RL + range/9 (inside the range) // ERD RH + range/9 and RL − range/9 (outside the range) // Fractal RH/RL the middle sub-range 1/3 · 2/3 // Fractal EQ EQ of the outer sub-ranges 1/6 · 5/6 // Fractal IRD 1/3 + range/27 and 2/3 − range/27 (inside the middle sub-range) // Fractal ERD 1/3 − range/27 and 2/3 + range/27 (outside it) // // One colour for every EQ, one colour for every IRD / ERD. Width per level. // ═══════════════════════════════════════════════════════════════════════════ gR = "Range" rangeSize = input.int(243, "PO3 range", options=[3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049], group=gR) // The ranges stack edge to edge, so the neighbours share their boundary with // this one: the RH of the range below IS this RL, and the range above starts // on this RH. Those shared levels are drawn once, by the range you are in — // drawing them twice doubles the line, thickens it, and spends the drawing // budget on nothing. Their IRD and ERD swap sides across the same boundary and // are suppressed for the same reason. showAdj = input.bool(false, "Also the range above and below", group=gR, tooltip="Their labels carry +1 and -1, so the range you are actually in stays recognisable once four RH/RL lines are on screen.") // Separate from the blocks switch in Fills, because they answer different // questions. That one is "blocks or bare lines, everywhere". This one keeps // the blocks on the range you are trading and leaves the neighbours as lines — // three sets of blocks fills the screen and the neighbours are context, not // the thing you are working in. adjBlocks = input.bool(false, " └ with their blocks too", group=gR, tooltip="Off = the neighbours show lines only. The range you are in keeps its blocks.") showRH = input.bool(true, "RH", inline="rh", group=gR) rhCol = input.color(color.rgb(0, 150, 50), "", inline="rh", group=gR) wRH = input.int(1, "width", minval=1, maxval=5, inline="rh", group=gR) showRL = input.bool(true, "RL", inline="rl", group=gR) rlCol = input.color(color.rgb(200, 0, 0), "", inline="rl", group=gR) wRL = input.int(1, "width", minval=1, maxval=5, inline="rl", group=gR) showFR = input.bool(true, "Fractal RH / RL (1/3 · 2/3)", inline="fr", group=gR) frCol = input.color(color.rgb(120, 120, 120), "", inline="fr", group=gR) wFR = input.int(1, "width", minval=1, maxval=5, inline="fr", group=gR) gE = "EQ levels" eqCol = input.color(color.rgb(255, 140, 0), "Colour (all EQ's)", group=gE) showEQ = input.bool(true, "EQ (50%)", inline="eq", group=gE) wEQ = input.int(2, "width", minval=1, maxval=5, inline="eq", group=gE) showFEQ = input.bool(true, "Fractal EQ (1/6 · 5/6)", inline="feq", group=gE) wFEQ = input.int(2, "width", minval=1, maxval=5, inline="feq", group=gE) gD = "IRD / ERD levels" rdCol = input.color(color.rgb(70, 140, 230), "Colour (all IRD / ERD)", group=gD) showIRD = input.bool(true, "RH / RL IRD", inline="ird", group=gD) wIRD = input.int(1, "width", minval=1, maxval=5, inline="ird", group=gD) showERD = input.bool(true, "RH / RL ERD", inline="erd", group=gD) wERD = input.int(1, "width", minval=1, maxval=5, inline="erd", group=gD) showFIRD = input.bool(true, "Fractal IRD", inline="fird", group=gD) wFIRD = input.int(1, "width", minval=1, maxval=5, inline="fird", group=gD) showFERD = input.bool(true, "Fractal ERD", inline="ferd", group=gD) wFERD = input.int(1, "width", minval=1, maxval=5, inline="ferd", group=gD) gF = "Fills (colour + transparency in the colour picker)" // The master switch: off leaves every level as a bare line, in all drawn // ranges. The three toggles below stay as they were, so you can still drop a // single block without losing the other two. To keep blocks here but not on // the neighbours, use "with their blocks too" in the Range group. showBlocks = input.bool(true, "Blocks on (off = lines only, all ranges)", group=gF) fillRH = input.bool(true, "RH block", inline="f1", group=gF) colRH = input.color(color.new(color.rgb(0, 150, 100), 82), "", inline="f1", group=gF) fillRL = input.bool(true, "RL block", inline="f2", group=gF) colRL = input.color(color.new(color.rgb(230, 40, 40), 82), "", inline="f2", group=gF) fillFR = input.bool(true, "Fractal blocks (1/3 · 2/3)", inline="f3", group=gF) colFR = input.color(color.new(color.rgb(70, 160, 240), 80), "", inline="f3", group=gF) gG = "FVG on levels (chart timeframe)" fvgOn = input.bool(true, "Show FVG's that sit on a PO3 level", group=gG) fvgEQ = input.bool(true, " └ on EQ / fractal EQ", group=gG) fvgRng = input.bool(true, " └ on RH / RL / fractal RH / RL", group=gG) fvgRD = input.bool(true, " └ on IRD / ERD / fractal IRD / ERD", group=gG) fvgMit = input.string("Wick through", "Mitigated by", options=["Wick touch", "Wick through", "Body close through"], group=gG, tooltip="Wick touch = price trades into the gap. Wick through = a wick reaches the far side of the gap. Body close through = a candle closes beyond the far side.") fvgHide = input.bool(true, "Hide mitigated FVG's", group=gG, tooltip="Off = the box stays, stops at the mitigating candle and takes the mitigated colour.") fvgBullCol = input.color(color.new(color.rgb(0, 150, 100), 75), "Bullish", inline="fc", group=gG) fvgBearCol = input.color(color.new(color.rgb(230, 40, 40), 75), "Bearish", inline="fc", group=gG) fvgMitCol = input.color(color.new(color.rgb(130, 130, 130), 88), "Mitigated", inline="fc", group=gG) fvgMax = input.int(30, "Max open FVG's", minval=1, maxval=200, group=gG) gV = "Style" drawLeft = input.int(1000, "Draw left (bars back)", minval=20, maxval=3000, group=gV) lineEnd = input.int(20, "Line end (bars right of live candle)", minval=0, maxval=300, group=gV) showLabels = input.bool(true, "Labels", group=gV) labelOffset = input.int(30, " └ distance (bars right of live candle)", minval=0, maxval=300, group=gV) labelTxtCol = input.color(color.rgb(54, 58, 69), " └ text colour", group=gV) labelSizeIn = input.string("small", " └ size", options=["tiny", "small", "normal", "large"], group=gV) labelPrice = input.bool(false, " └ show price", group=gV) labelSize = labelSizeIn == "tiny" ? size.tiny : labelSizeIn == "small" ? size.small : labelSizeIn == "normal" ? size.normal : size.large var line[] lns = array.new_line() var label[] lbs = array.new_label() var box[] bxs = array.new_box() f_lvl(bool on, float y, color c, int w, string t) => if on array.push(lns, line.new(math.max(0, bar_index - drawLeft), y, bar_index + lineEnd, y, color=c, width=w)) if showLabels string txt = labelPrice ? t + " " + str.tostring(y, format.mintick) : t array.push(lbs, label.new(bar_index + labelOffset, y, txt, style=label.style_label_left, color=color.new(color.white, 100), textcolor=labelTxtCol, size=labelSize)) f_fill(bool on, float yTop, float yBot, color c) => if on and showBlocks array.push(bxs, box.new(math.max(0, bar_index - drawLeft), yTop, bar_index + lineEnd, yBot, bgcolor=c, border_color=na)) // ───────────── FVG's on PO3 levels ───────────── // The grid is anchored on multiples of the range from zero, so every level // repeats each range: a level is a fixed fraction f of the range. That makes // this check range-agnostic — it already finds gaps on the neighbours' levels // whether or not those ranges are drawn. float RS = float(rangeSize) f_hit(float lo, float hi, float f) => RS * (math.ceil(lo / RS - f) + f) <= hi f_onLevel(float lo, float hi) => bool a = fvgEQ and (f_hit(lo, hi, 0.5) or f_hit(lo, hi, 1.0 / 6.0) or f_hit(lo, hi, 5.0 / 6.0)) bool b = fvgRng and (f_hit(lo, hi, 0.0) or f_hit(lo, hi, 1.0 / 3.0) or f_hit(lo, hi, 2.0 / 3.0)) bool c = fvgRD and (f_hit(lo, hi, 1.0 / 9.0) or f_hit(lo, hi, 8.0 / 9.0) or f_hit(lo, hi, 8.0 / 27.0) or f_hit(lo, hi, 10.0 / 27.0) or f_hit(lo, hi, 17.0 / 27.0) or f_hit(lo, hi, 19.0 / 27.0)) a or b or c var box[] fB = array.new_box() var float[] fTop = array.new_float() var float[] fBot = array.new_float() var bool[] fBull = array.new_bool() f_drop(int i, bool del) => if del box.delete(array.get(fB, i)) array.remove(fB, i) array.remove(fTop, i) array.remove(fBot, i) array.remove(fBull, i) if fvgOn // 1) mitigation of the open FVG's if array.size(fB) > 0 for i = array.size(fB) - 1 to 0 float top = array.get(fTop, i) float bot = array.get(fBot, i) bool bull = array.get(fBull, i) bool mit = false if fvgMit == "Wick touch" mit := bull ? low <= top : high >= bot else if fvgMit == "Wick through" mit := bull ? low <= bot : high >= top else mit := barstate.isconfirmed and (bull ? close < bot : close > top) if mit if not fvgHide box b = array.get(fB, i) box.set_extend(b, extend.none) box.set_right(b, bar_index) box.set_bgcolor(b, fvgMitCol) f_drop(i, fvgHide) // 2) new FVG on the candle that just closed if barstate.isconfirmed and bar_index > 2 bool isBull = low > high[2] bool isBear = high < low[2] if isBull or isBear float lo = isBull ? high[2] : high float hi = isBull ? low : low[2] if f_onLevel(lo, hi) array.push(fB, box.new(bar_index - 2, hi, bar_index, lo, bgcolor=isBull ? fvgBullCol : fvgBearCol, border_color=na, extend=extend.right)) array.push(fTop, hi) array.push(fBot, lo) array.push(fBull, isBull) if array.size(fB) > fvgMax f_drop(0, true) // ───────────── one range, drawn ───────────── // Split in two so every fill is created before any line: within a drawing type // Pine honours creation order, and a block drawn after a line covers it. With // three ranges on screen the neighbours' blocks would otherwise sit on top of // this range's levels. // // withLow / withHigh switch off the boundary this range shares with a drawn // neighbour. See the note at showAdj. f_rangeFills(float rl, bool withLow, bool withHigh) => float rh = rl + RS float d = RS / 9.0 float fd = RS / 27.0 float fLo = rl + RS / 3.0 float fHi = rl + RS * 2.0 / 3.0 f_fill(fillRH and withHigh, rh + d, rh - d, colRH) f_fill(fillRL and withLow, rl + d, rl - d, colRL) f_fill(fillFR, fHi + fd, fHi - fd, colFR) f_fill(fillFR, fLo + fd, fLo - fd, colFR) f_rangeLines(float rl, bool withLow, bool withHigh, string tag) => float rh = rl + RS float d = RS / 9.0 float fd = RS / 27.0 float fLo = rl + RS / 3.0 float fHi = rl + RS * 2.0 / 3.0 // fractal — entirely inside the range, so never shared with a neighbour f_lvl(showFERD, fHi + fd, rdCol, wFERD, "fERD" + tag) f_lvl(showFIRD, fHi - fd, rdCol, wFIRD, "fIRD" + tag) f_lvl(showFIRD, fLo + fd, rdCol, wFIRD, "fIRD" + tag) f_lvl(showFERD, fLo - fd, rdCol, wFERD, "fERD" + tag) f_lvl(showFR, fHi, frCol, wFR, "fRH" + tag) f_lvl(showFR, fLo, frCol, wFR, "fRL" + tag) f_lvl(showFEQ, rl + RS * 5.0 / 6.0, eqCol, wFEQ, "fEQ" + tag) f_lvl(showFEQ, rl + RS / 6.0, eqCol, wFEQ, "fEQ" + tag) // range f_lvl(showERD and withHigh, rh + d, rdCol, wERD, "RH ERD" + tag) f_lvl(showIRD and withHigh, rh - d, rdCol, wIRD, "RH IRD" + tag) f_lvl(showIRD and withLow, rl + d, rdCol, wIRD, "RL IRD" + tag) f_lvl(showERD and withLow, rl - d, rdCol, wERD, "RL ERD" + tag) f_lvl(showEQ, rl + RS / 2.0, eqCol, wEQ, "EQ" + tag) f_lvl(showRH and withHigh, rh, rhCol, wRH, "RH" + tag) f_lvl(showRL and withLow, rl, rlCol, wRL, "RL" + tag) if barstate.islast while array.size(lns) > 0 line.delete(array.pop(lns)) while array.size(lbs) > 0 label.delete(array.pop(lbs)) while array.size(bxs) > 0 box.delete(array.pop(bxs)) float rl = RS * math.floor(close / RS) // fills first for every range, then the lines on top if showAdj and adjBlocks f_rangeFills(rl - RS, true, false) // below: its RH is this RL f_rangeFills(rl + RS, false, true) // above: its RL is this RH f_rangeFills(rl, true, true) if showAdj f_rangeLines(rl - RS, true, false, " -1") f_rangeLines(rl + RS, false, true, " +1") f_rangeLines(rl, true, true, "")