Multi-timeframe alerts in Pine Script: the right way and the wrong way
How to fire alerts based on conditions from a higher timeframe without repainting, lag, or duplicate fires.
Multi-timeframe (MTF) logic is the most-asked Pine Script question — and the most-broken Pine Script feature. Half the published "MTF indicators" repaint, fire duplicate alerts, or silently lag a bar. Here's how to do it correctly.
The repaint trap
The most common bug:
//@version=5
indicator("Bad MTF", overlay=true)
htf_close = request.security(syminfo.tickerid, "60", close)
alertcondition(close > htf_close, "Above hourly close")
This fires alerts that don't reflect what actually happened. The 60-minute close keeps updating inside the current hourly bar, so the alert fires, un-fires, and re-fires as the higher timeframe candle forms.
This is repainting. It's the reason published Pine scripts get one-star reviews.
The fix: barmerge.lookahead_off + closed-bar values
The correct version:
//@version=5
indicator("Good MTF", overlay=true)
htf_close = request.security(syminfo.tickerid, "60", close[1], lookahead=barmerge.lookahead_off)
alertcondition(close > htf_close, "Above last closed hourly")
Two changes:
close[1]— reference the prior bar's close on the higher timeframe. That bar is already finished and cannot repaint.lookahead=barmerge.lookahead_off— explicitly forbid Pine from using future data.
This is the boilerplate. Use it for every MTF reference. Every time.
Higher-TF EMA without repainting
Same pattern, applied to a derived value:
//@version=5
indicator("Daily 50EMA on intraday chart", overlay=true)
dailyEMA = request.security(
syminfo.tickerid,
"D",
ta.ema(close, 50)[1],
lookahead=barmerge.lookahead_off
)
plot(dailyEMA, color=color.orange, linewidth=2)
ta.ema(close, 50)[1] is the daily 50EMA from the prior day's close. It updates once per day at the daily close. No repaint.
Avoiding duplicate alert fires
The next bug: an alert that fires on every intraday bar while the condition is true, not just on the bar where it becomes true.
Wrong:
alertcondition(close > dailyEMA, "Above daily EMA")
Right:
crossedUp = ta.crossover(close, dailyEMA)
alertcondition(crossedUp, "Crossed above daily EMA")
alertcondition fires every bar the input is true. ta.crossover is only true on the bar where the cross happens. Use cross functions for state-change events; use raw conditions only for things you genuinely want to know about every bar.
The pattern we use for entries
A typical confirmed-entry pattern:
//@version=5
indicator("MTF Confirmed Entry", overlay=true)
// Higher TF bias
dailyEMA = request.security(
syminfo.tickerid, "D",
ta.ema(close, 50)[1],
lookahead=barmerge.lookahead_off
)
dailyBias = close > dailyEMA // long bias if true
// Intraday trigger
hourlyHigh = request.security(
syminfo.tickerid, "60",
high[1],
lookahead=barmerge.lookahead_off
)
triggerLong = ta.crossover(close, hourlyHigh)
// Combined alert
longEntry = dailyBias and triggerLong and volume > ta.sma(volume, 20)
alertcondition(longEntry, "MTF long entry")
plotshape(longEntry, style=shape.triangleup, location=location.belowbar, color=color.green)
Three layers:
- Daily bias (50EMA reclaim) — slow filter.
- Hourly trigger (break of prior hour's high) — fast trigger.
- Intraday volume confirmation — execution filter.
Each layer references the prior bar of its respective timeframe. No repaint. Single alert fire per genuine event.
When to break the rule
There's one case where it's OK to read the in-flight higher-TF bar: when you're intentionally showing the user a live read and you've labeled it as such (e.g., "live daily VWAP"). Even then, do not fire alerts on it.
TL;DR
- Always pass
lookahead=barmerge.lookahead_off. - Reference
[1](the prior bar) when you want a closed value. - Use
ta.crossover/ta.crossunderfor alerts. Don't fire on raw conditions. - Combine bias + trigger + confirmation; don't put everything in one boolean.
That's it. Four rules. They'd save 80% of broken Pine scripts on TradingView.
Keep reading
CPI day: the four 5-minute bars that actually matter
How to trade CPI release mornings without getting whipsawed — and the historical edge hiding in the first 20 minutes after 8:30 AM.
The Opening Range Breakout, broken down bar by bar
A clean breakdown of how the first 15 minutes of the cash session set the day's tone — and how to trade the break without getting trapped.
Earnings IV crush: the post-print drift is where the money is
Why trading earnings options into the print is a coin flip — and what to do the morning after instead.