The fastest way to cut false signals is to stop trading against the higher timeframe. This recipe reads a confirmed 4h trend, tints every candle green or red by that regime, and only marks an entry when a fast/slow EMA cross on your chart timeframe lines up with the 4h direction. The key word is confirmed: the higher-timeframe values come from htf(), which never looks ahead, so a signal that shows up in history would have shown up live, at the same bar. No repaint, no hindsight.
//@version=2
// ============================================================================
// NO-REPAINT 4H TREND GATE
// htf() reads a confirmed higher timeframe with no look-ahead. The 4h EMAs
// are plotted so the script always has visible output; candle tinting and
// entry markers are added when the confirmed 4h regime is available.
// ============================================================================
define(title="4H Trend Gate (no-repaint)", position="onchart", axis=false);
var fastLen = input(name="fastLen", type="number", defaultValue=9, label="Fast EMA", constraints={min: 2, max: 100, step: 1});
var slowLen = input(name="slowLen", type="number", defaultValue=21, label="Slow EMA", constraints={min: 5, max: 200, step: 1});
var upCol = input(name="upCol", type="color", defaultValue="#22d3a5", label="Bull Tint");
var dnCol = input(name="dnCol", type="color", defaultValue="#ff5b7f", label="Bear Tint");
timeseries d = ohlcv(symbol=currentSymbol, exchange=currentExchange);
// Confirmed 4h view of the SAME data: one row per chart bar, no repaint.
timeseries h4 = htf(d, "4h");
var h4Fast = ema(source=h4.close, period=fastLen);
var h4Slow = ema(source=h4.close, period=slowLen);
var bull4h = isnum(h4Fast) && isnum(h4Slow) && h4Fast > h4Slow;
var bear4h = isnum(h4Fast) && isnum(h4Slow) && h4Fast < h4Slow;
timeseries h4FastLine = h4Fast;
timeseries h4SlowLine = h4Slow;
// Chart-timeframe trigger, gated by the higher-timeframe regime.
timeseries fast = ema(source=d.close, period=fastLen);
timeseries slow = ema(source=d.close, period=slowLen);
var crossedUp = isnum(fast[0]) && isnum(slow[0]) && fast[0] > slow[0] && fast[1] <= slow[1];
var crossedDown = isnum(fast[0]) && isnum(slow[0]) && fast[0] < slow[0] && fast[1] >= slow[1];
plotLine(h4FastLine, colors=[upCol], width=1, label=["4H Fast EMA"], desc=["Confirmed 4h fast EMA"]);
plotLine(h4SlowLine, colors=[dnCol], width=1, label=["4H Slow EMA"], desc=["Confirmed 4h slow EMA"]);
// Tint the actual candles by regime. Needs enough loaded history for the 4h
// slow EMA: roughly slowLen * 4 chart hours on a 1h chart.
if (bull4h) { barcolor(opacity(upCol, 18)); }
if (bear4h) { barcolor(opacity(dnCol, 18)); }
if (crossedUp && bull4h) {
plotShape(value=d.low[0], shape="triangleup", width=10, colors=[upCol], fill=true, location="belowBar", label=["Long (4h agrees)"], desc=["Chart-timeframe cross up confirmed by the 4h regime"]);
}
if (crossedDown && bear4h) {
plotShape(value=d.high[0], shape="triangledown", width=10, colors=[dnCol], fill=true, location="aboveBar", label=["Short (4h agrees)"], desc=["Chart-timeframe cross down confirmed by the 4h regime"]);
}How it works
The higher-timeframe view. htf(d, "4h") takes the chart's own data and re-buckets it into confirmed 4h bars, returning one value per chart bar. "Confirmed" means a 4h candle only contributes once it has closed, so the line never changes after the fact. This is what makes the gate trustworthy: the green and red tints you see on old bars are exactly what you would have seen live.
The regime. Two EMAs on the 4h close define the trend. When the fast 4h EMA is above the slow one the market is in an uptrend (bull4h), below it a downtrend (bear4h). Both flags isnum-guard the EMAs so the warm-up region counts as neither, rather than a coin flip.
The trigger. Separately, the script computes the same fast/slow EMAs on your chart timeframe and detects the moment they cross. crossedUp is true on the single bar where fast moves from at-or-below slow to above it. That is the entry idea on its own.
The gate. The entry only prints when the trigger and the regime agree: crossedUp && bull4h for longs, crossedDown && bear4h for shorts. A bullish cross during a 4h downtrend is silently dropped, which is the whole point. Most chop happens when you fight the higher timeframe.
The two render channels. The script shows its state two ways. barcolor() tints every candle by the 4h regime, so the background context is always visible. plotShape() drops a triangle only on confirmed, gated entries. The 4h EMA lines are also plotted so there is always something on screen, even before a single cross fires.
Render note. Candle tinting uses the
barcolorchannel. If your chart does not renderbarcolor, the entry triangles and the 4h EMA lines still show, but the green/red candle background will not. The triangle markers carry the same regime information regardless.
Customize it
- EMA speed.
fastLenandslowLendrive both the regime and the trigger. Widen the gap (for example 21 / 55) for fewer, slower signals, tighten it for more. Because the same lengths feed the 4h regime, changing them reshapes the whole gate. - Regime timeframe.
"4h"is the trend horizon. Bump it to"1d"to demand agreement with the daily trend (stricter, fewer entries), or"1h"for a looser gate on lower-timeframe charts. Any stringhtf()accepts works. See multi-timeframe. - Loaded history. The 4h regime needs enough loaded bars for its slow EMA to warm up, roughly
slowLen * 4chart hours on a 1h chart. If candles never tint, load more history. - Different trigger. The cross is just one idea. Swap it for an RSI level, a breakout, or any condition you like, and keep the
&& bull4h/&& bear4hgate to filter it by trend. - Colors.
upColanddnColset both the tint and the markers, so the chart stays coherent when you recolor. - Make it actionable. Replace each
plotShapewith analert()to be notified only on trend-aligned entries.
Concepts used
- Multi-timeframe for the no-repaint
htf()regime - Moving averages for the
emalines and crosses - Plotting for
barcolor,plotLine, andplotShape - Color functions for
opacitycandle tints