//@version=6 indicator("Cycle Ribbon — AMD", shorttitle="Ribbon", overlay=true, max_boxes_count=500, max_labels_count=500) // ═══════════════════════════════════════════════════════════════════════════ // Every cycle on his grid, coloured A / M / D, as a ribbon of stacked rows. // // ── WHY THERE IS NO AMD SETTING ──────────────────────────────────────────── // A cycle does not need splitting into an AMD. It IS one. 30M cycle 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 falls out of the position in the parent and // there is nothing to configure: // // Sessions 4/day each is the A, M or D of the trading day // 90M 9/day each is the A, M or D of its session // 30M 27/day each is the A, M or D of its 90M // 10M 81/day each is the A, M or D of its 30M // // ── WHY A RIBBON AND NOT FOUR BACKGROUNDS ────────────────────────────────── // Four transparent layers over the candles is mud, and two of them already // fight. As stacked rows beside the price each group stays readable and you // can watch each row subdivide the one below it — which is the whole point of // a ladder that goes up in threes. // // ── THE GAP BETWEEN CYCLES IS DELIBERATE ─────────────────────────────────── // A cell ends on the LAST bar inside its cycle, so the 14:30 candle belongs to // the cycle starting at 14:30 and to nothing else. An earlier version ran each // cell to the first bar of the next cycle: tidier to look at, and wrong about // which bar belongs where. // // All New York time, DST automatic. Grid anchored 02:30, ending 15:59, taken // from his own SMT file and checked minute by minute against it. Asia runs // 18:00 to 02:29 the next day and sits outside the grid; 16:00-17:59 is empty // by design. // ═══════════════════════════════════════════════════════════════════════════ NY = "America/New_York" // ───────────────────────── which rows ───────────────────────── gR = "Rows" useSes = input.bool(true, "Sessions · 4 per day", group=gR) use90 = input.bool(true, "90M · 9 per day", group=gR) use30 = input.bool(true, "30M · 27 per day", group=gR) use10 = input.bool(false, "10M · 81 per day", group=gR, tooltip="Off by default. 81 cells a day is a lot of ink for a row you can also read off the 30M above it, and it is the row that eats the drawing budget.") daysAhead = input.int(1, "Days drawn", minval=1, maxval=2, group=gR, tooltip="Includes every cycle still to come today, because the times are known in advance. Two days with every row on is about 242 boxes of the 500 available.") // ───────────────────────── where the ribbon sits ───────────────────────── gP = "Placement" side = input.string("Below price", "Ribbon sits", options=["Below price", "Above price"], group=gP) gapATR = input.float(1.0, "Clear of the price by (x ATR)", minval=0, step=0.25, group=gP) rowATR = input.float(0.6, "Row height (x ATR)", minval=0.1, step=0.1, group=gP) rowGap = input.float(0.15, "Space between rows (x ATR)", minval=0, step=0.05, group=gP) atrLen = input.int(120, "ATR length", minval=5, group=gP) // ───────────────────────── look ───────────────────────── gS = "Look" colA = input.color(color.new(color.green, 55), "A — accumulation", group=gS) colM = input.color(color.new(color.red, 55), "M — manipulation", group=gS) colD = input.color(color.new(color.blue, 55), "D — distribution", group=gS) colAsia = input.color(color.new(color.gray, 65), "Asia (outside the grid)", group=gS) colBord = input.color(color.new(color.white, 100), "Cell border", group=gS) showLbl = input.bool(true, "Names on the Sessions and 90M rows", group=gS, tooltip="Not on 30M or 10M: 27 or 81 labels is unreadable, and those are the rows you read by colour anyway.") lblSzIn = input.string("tiny", " └ size", options=["tiny","small","normal"], group=gS) lblSz = lblSzIn == "tiny" ? size.tiny : lblSzIn == "small" ? size.small : size.normal atr = ta.atr(atrLen) // ═══════════════════════ helpers, all at global scope ═══════════════════════ // Pine will not take a function declared inside an if-block, so everything the // drawing needs is defined out here and handed its values. f_90name(int i) => i == 0 ? "LO1" : i == 1 ? "LO2" : i == 2 ? "LO3" : i == 3 ? "AM1" : i == 4 ? "AM2" : i == 5 ? "AM3" : i == 6 ? "PM1" : i == 7 ? "PM2" : "PM3" f_hhmm(int m) => int h = (m / 60) % 24 int mm = m % 60 (h < 10 ? "0" : "") + str.tostring(h) + ":" + (mm < 10 ? "0" : "") + str.tostring(mm) f_role(int idx) => idx % 3 == 0 ? colA : idx % 3 == 1 ? colM : colD var array cells = array.new_box() var array