//@version=6 indicator("Cycle Boxes — AMD", "Cycles AMD", overlay = true, max_boxes_count = 500) // ============================================================================= // Every cycle group of the 02:30-15:59 raster as an AMD box around the // candles, in the shape of his own "Cycles & Dividers": a box from the highest // high to the lowest low inside that cycle, grown bar by bar, closed one // candle before the next one opens. // // Four groups, each with its own switch: // // Sessions 4/day Asia . London . NY AM . NY PM // 90M 9/day each the A, M or D of its session // 30M 27/day each the A, M or D of its 90M // 10M 81/day each the A, M or D of its 30M // // WHY THERE IS NO AMD SETTING. A cycle is not split into an AMD — it IS one. // 30M number two is the M of its 90M, 90M number two is the M of its session. // The ladder is self-similar, so the colour follows from the position in the // parent and there is nothing to configure. That is also why 04:00, 08:30 and // 13:00 come out red, exactly as in his own file. // // THE BOXES NEST. A 10M box sits inside its 30M, which sits inside its 90M, // which sits inside its session. So the groups do not fight each other; they // frame each other. Only the 90M carries a fill by default — four fills over // the same candles is mud. // // Asia sits outside the raster (18:00-02:29, across midnight) and gets its own // colour rather than a role it does not have. 16:00-17:59 is empty by design. // // BUDGET. Nothing is ever deleted, as in his file, so TradingView drops the // oldest box once 500 are on the chart. With all four groups on that is 121 // boxes a day and about four days of history. Switch 10M off and it is 40 a // day, so roughly twelve days. // // All times NY, DST automatic. // ============================================================================= tz = "America/New_York" // ───────────────────────────── AMD colours ───────────────────────────── gA = "AMD colours" amdA = input.color(#2962ff, "A", inline = "amd", group = gA) amdM = input.color(#f23645, "M", inline = "amd", group = gA) amdD = input.color(#089981, "D", inline = "amd", group = gA) amdX = input.color(color.gray, "Asia", inline = "amd", group = gA, tooltip = "Asia runs 18:00-02:29 and falls outside the 02:30-15:59 raster, so it has no A, M or D role.") // ───────────────────────────── the four groups ───────────────────────────── // Transparency 100 = frame only. The border follows the fill but 45 steps more // solid, so a group set to 100 still has a visible edge. gS = "Sessions · 4 per day" sesOn = input.bool(true, "Boxes", group = gS) sesTr = input.int(100, "Fill transparency", minval = 0, maxval = 100, group = gS, tooltip = "100 = frame only, which is what you want for a box eight hours wide. Needs a timeframe that divides 30 minutes.") g90 = "90M · 9 per day" c90On = input.bool(true, "Boxes", group = g90) c90Tr = input.int(88, "Fill transparency", minval = 0, maxval = 100, group = g90) g30 = "30M · 27 per day" c30On = input.bool(true, "Boxes", group = g30) c30Tr = input.int(100, "Fill transparency", minval = 0, maxval = 100, group = g30) g10 = "10M · 81 per day" c10On = input.bool(false, "Boxes", group = g10, tooltip = "Off by default. 81 boxes a day eats two thirds of the 500 on offer and cuts the visible history to about four days. Needs a timeframe that divides 10 minutes.") c10Tr = input.int(100, "Fill transparency", minval = 0, maxval = 100, group = g10) // ───────────────────────────── timing ───────────────────────────── tfSec = timeframe.in_seconds() // A box boundary has to land on a bar boundary, otherwise the cycle would be // cut somewhere inside a candle. Gated silently, as in his own file. ali10 = timeframe.isintraday and 600 % tfSec == 0 ali30 = timeframe.isintraday and 1800 % tfSec == 0 mnts = hour(time, tz) * 60 + minute(time, tz) // The raster: 02:30 (minute 150) through 15:59 (minute 959). inRas = mnts >= 150 and mnts <= 959 // Keys. 0 means "no cycle here", so the box closes. int() around the division // because Pine hands back a float and an int parameter will not take one. k10 = inRas ? int((mnts - 150) / 10) + 1 : 0 k30 = inRas ? int((mnts - 150) / 30) + 1 : 0 k90 = inRas ? int((mnts - 150) / 90) + 1 : 0 // Sessions are counted from 18:00 rather than from midnight, because Asia // crosses midnight and has to keep the same key the whole way through — a key // that changed at 00:00 would cut Asia in two. tmins = mnts >= 1080 ? mnts - 1080 : mnts + 360 kSes = tmins <= 509 ? 1 : tmins <= 779 ? 2 : tmins <= 1049 ? 3 : tmins <= 1319 ? 4 : 0 // ───────────────────────────── colours ───────────────────────────── amd_col(int idx) => int m = idx % 3 m == 0 ? amdA : m == 1 ? amdM : amdD // ───────────────────────────── the box ───────────────────────────── // His own routine, unchanged in behaviour. A function carrying `var` gets its // own state per call site in Pine, which is exactly why the four groups below // are written out one by one instead of looped — a loop would be a single call // site and all four groups would share one box. f_cycleBox(bool active, int key, color bg, color bcol, int bw, string bstyle) => var bool inC = false var int prevK = 0 var box curB = na // DO NOT ADD AN OFFSET TO THESE TWO. With xloc.bar_time a timestamp lands on // the CENTRE LINE of its candle, not on its left edge, which already gives // the wanted one-candle gap before the next box. liveR = time + timeframe.in_seconds() * 1000 gapR = nz(time[1], time) if inC and active and key == prevK box.set_top(curB, math.max(box.get_top(curB), high)) box.set_bottom(curB, math.min(box.get_bottom(curB), low)) box.set_right(curB, liveR) else if inC box.set_right(curB, gapR) // trim the last candle -> gap curB := na inC := false if active and not inC curB := box.new(time, high, liveR, low, xloc = xloc.bar_time, bgcolor = bg, border_color = bcol, border_width = bw, border_style = bstyle) prevK := key inC := true int(na) // CE10235 guard. Do not delete it. // ───────────────────────────── drawing ───────────────────────────── // Outer frame first so the inner groups draw on top of it. // Sessions. London, NY AM and NY PM are the A, M and D of the trading day. kS = (sesOn and ali30) ? kSes : 0 cS = kS == 1 ? amdX : kS >= 2 ? amd_col(kS - 2) : color.gray f_cycleBox(kS != 0, kS, color.new(cS, sesTr), color.new(cS, math.max(0, sesTr - 45)), 1, line.style_solid) // 90M. Nine windows, each the A, M or D of its session. k9 = (c90On and ali30) ? k90 : 0 c9 = k9 != 0 ? amd_col(k9 - 1) : color.gray f_cycleBox(k9 != 0, k9, color.new(c9, c90Tr), color.new(c9, math.max(0, c90Tr - 45)), 1, line.style_dotted) // 30M. Each the A, M or D of its 90M. k3 = (c30On and ali30) ? k30 : 0 c3 = k3 != 0 ? amd_col(k3 - 1) : color.gray f_cycleBox(k3 != 0, k3, color.new(c3, c30Tr), color.new(c3, math.max(0, c30Tr - 45)), 1, line.style_dotted) // 10M. Each the A, M or D of its 30M. k1 = (c10On and ali10) ? k10 : 0 c1 = k1 != 0 ? amd_col(k1 - 1) : color.gray f_cycleBox(k1 != 0, k1, color.new(c1, c10Tr), color.new(c1, math.max(0, c10Tr - 45)), 1, line.style_dotted)