Yesterday's high and low, last week's high and low, and today's open are the levels the market keeps reacting to, and half the annotations on any trading chart are someone maintaining them by hand. This recipe maintains them for you: five levels computed from calendar-anchored higher-timeframe data, drawn as thin lines with sticky price tags on the right. Because the levels come from htf() with calendar buckets, they are exactly the same on a 1-minute chart and a 4-hour chart, and they never repaint: a "prior day high" is always the prior completed UTC day.
//@version=2
define(title="Key Levels", position="onchart", axis=false, maxBarsBack=300);
var dayCol = input(name="dayCol", type="color", defaultValue="#f5a623", label="Daily color");
var weekCol = input(name="weekCol", type="color", defaultValue="#4a90d9", label="Weekly color");
var showOpen = input(name="showOpen", type="boolean", defaultValue=true, label="Show day open");
var labelOffset = input(name="labelOffset", type="slider", defaultValue=12, label="Label offset (bars)", constraints={min: 4, max: 40, step: 1});
timeseries data = ohlcv(symbol=currentSymbol, exchange=currentExchange);
timeseries d1 = htf(data, "1D");
timeseries w1 = htf(data, "1W");
var dayOpen = htf(data, "1D", {mode: "developing"}).open;
var pdh = d1.high;
var pdl = d1.low;
var pwh = w1.high;
var pwl = w1.low;
persist firstT = na;
if (isna(firstT)) { firstT = data.time; }
func lvl(x1, price, text, col) {
if (isnum(price)) {
var interval = isnum(data.time[1]) ? data.time - data.time[1] : 0;
var rightX = data.time + labelOffset * interval;
var ax1 = (isnum(firstT) && x1 < firstT) ? firstT : x1;
line.new(ax1, price, rightX, price, { color: col, width: 1, lineStyle: "solid", stickyRight: true });
label.new(rightX, price, text + " " + tostring(price, "0.00"), { color: col, backgroundColor: "#1e222d", size: 11, stickyRight: true });
}
}
if (isLastBar) {
var DAY = 86400000;
var curDay = timestamp(year(), month(), dayOfMonth());
var dow = dayOfWeek();
var dowOff = dow == "monday" ? 0 : (dow == "tuesday" ? 1 : (dow == "wednesday" ? 2 : (dow == "thursday" ? 3 : (dow == "friday" ? 4 : (dow == "saturday" ? 5 : 6)))));
var curWeek = curDay - dowOff * DAY;
var prevDay = curDay - DAY;
var prevWeek = curWeek - 7 * DAY;
lvl(prevDay, pdh, "PDH", dayCol);
lvl(prevDay, pdl, "PDL", dayCol);
lvl(prevWeek, pwh, "PWH", weekCol);
lvl(prevWeek, pwl, "PWL", weekCol);
if (showOpen) { lvl(curDay, dayOpen, "Open", dayCol); }
}How it works
The levels come from confirmed higher-timeframe data. htf(data, "1D") aggregates the chart's bars into calendar UTC days and, read as a plain value, gives you the prior completed day, so d1.high is the prior day's high with no repaint risk and no partial-period contamination. "1W" does the same with calendar weeks. This is the same no-repaint contract as the Regime Filter recipe; the details live in Multi-Timeframe.
The day open is the one level that should move. Today's open belongs to the current, still-forming day, which is exactly what {mode: "developing"} exposes. It updates once at the day boundary and then holds.
Drawing happens once, on the last bar. Lines and labels are drawing objects, not per-bar plots, so the whole render block is gated on isLastBar. Each level is one line.new from its own period's open to a point labelOffset bars past the last candle, plus one label.new price tag. stickyRight: true pins the tag to the right edge of the plot while you pan, so the labels behave like the axis tags you are used to.
The persist firstT clamp handles shallow charts. A weekly level's period open can predate the oldest loaded bar (a weekly line on a chart that only loaded three days); anchoring at a time the chart never loaded would not render, so the left anchor clamps to the first loaded bar.
Customize it
- Add monthly levels:
timeseries m1 = htf(data, "1M");then two morelvl(...)calls withm1.high/m1.lowand a prior-month anchor (timestamp(month() == 1 ? year() - 1 : year(), month() == 1 ? 12 : month() - 1, 1))."1Q"and"1Y"work the same way. - Prior close and midpoint:
d1.closeis the prior day close;(pdh + pdl) / 2is the day's EQ, a favorite mean-reversion magnet. - Longer or shorter tags:
labelOffsetsets how far right of the last candle the lines and tags sit; it is an input, so tune it from the settings panel. - Style per period: the two color inputs keep daily and weekly levels visually distinct; add a
lineStyleinput ("dashed","dotted") if you want weekly levels quieter.