Range Filter on TradingView: Filtering Rules and Signal Timing
The Range Filter indicator holds its last value until price moves beyond an adaptive distance. Its price line, direction counters and buy/sell labels are separate calculations. This guide follows one public implementation, shows exactly when each changes, and provides a Pine Script v6 example with close-confirmed labels.
Educational algorithm reference. Signals are not evidence of profitability or investment advice.
Read and copy the exampleFix the version before comparing values
The reference is Range Filter Buy and Sell 5min, guikroth version, publication J8GzFGfD, revision 2. Its source credits DonovanWall, guikroth and tvenn. The published defaults are close, period 100 and multiplier 3.0. These are reference settings, not a claim that they suit every market.
DonovanWall's Range Filter [DW] includes different filter types and range measurements. Do not combine its settings with this simplified recurrence and expect identical values. Record publication ID, revision, source, symbol, session, timeframe and available history for any comparison.
The example below keeps the simplified smoothing and movement rules but replaces the reference's zero fallback at startup with an explicit source-price seed. It also initializes the signal side to neutral and requires a closed bar for labels and alerts. Early values can differ from the published script.
Smoothed range and filter recurrence
For source x, period n and positive multiplier m, first smooth absolute source changes with an n-period EMA. Smooth that result again with a (2n − 1)-period EMA, then multiply by m. This uses close-to-close changes when x is close; it is not ATR and does not directly include candle highs and lows.
change[t] = abs(x[t] - x[t-1])
a[t] = EMA(change, n)
r[t] = m * EMA(a, 2*n - 1)
if x[t] > F[t-1]: F[t] = max(F[t-1], x[t] - r[t])
otherwise: F[t] = min(F[t-1], x[t] + r[t])
upper[t] = F[t] + r[t]
lower[t] = F[t] - r[t]A move exactly equal to r leaves the filter unchanged. For example, previous F = 100, x = 102 and r = 2 gives F = max(100, 100) = 100. With x = 103, F becomes 101. With x = 97, F becomes 99. When r = 0, the filter follows source; the strict source-above/below-filter signal tests then fail.
For a smoothing check, assume n = 2, m = 1 and successive absolute changes of 2, 4, 0. Seed each EMA with its first valid input. The first EMA has alpha 2/3 and produces 2, 3.333333, 1.111111. The second has length 3 and alpha 1/2, giving ranges 2, 2.666667, 1.888889. A larger multiplier widens those same ranges without changing either EMA.
Filter price, direction count, flip and alert
The hypothetical sequence below fixes r at 2 to isolate the recurrence. It is not an EMA output sample or a backtest. All rows represent closed bars. The side starts neutral, so the first long candidate does not print a buy label.
| Bar | Source | Filtered price | Up / down count | Stored side | Confirmed signal |
|---|---|---|---|---|---|
| 0 | 100 | 100 (seed) | 0 / 0 | Neutral | None |
| 1 | 103 | 101 | 1 / 0 | Long | None: first side |
| 2 | 104 | 102 | 2 / 0 | Long | None |
| 3 | 103 | 102 (held) | 2 / 0 | Long | None |
| 4 | 99 | 101 | 0 / 1 | Short | Sell at close |
| 5 | 98 | 100 | 0 / 2 | Short | None |
| 6 | 103 | 101 | 1 / 0 | Long | Buy at close |
At bar 3 the filter is flat, yet the upward count stays 2. At bar 4, min(102, 99 + 2) = 101. The falling filter resets the upward count, starts the downward count at 1, and changes the established long side to short. Only this reversal creates the sell condition.
A long candidate requires source above the filter, a positive upward count and source different from its prior value. A short candidate requires source below the filter, a positive downward count and changed source. Unchanged source produces no candidate, even if a changing range moves the filter. A buy needs the prior stored side to be short; a sell needs it to be long. Neither a rising candle nor every filter movement is a new signal.
On an open bar, source, range and candidates can change. The example gates its final labels with barstate.isconfirmed. A temporary short candidate that disappears before close produces no sell label or alert. The filter and bands themselves remain live during that bar.
Pine Script v6 with confirmed signals
Paste this complete indicator into the Pine Editor. It exposes filter price, range and both counts in the Data Window. The first valid source seeds the filter; unavailable range values hold it. The two EMA calls run on every bar.
//@version=6
// Educational adaptation of the public guikroth/tvenn Range Filter.
// Original concept: DonovanWall. Public reference: J8GzFGfD, revision 2.
// Changes: explicit source seed, v6, close-confirmed labels and alerts.
indicator("Range Filter: confirmed flips", overlay=true)
source = input.source(close, "Source")
period = input.int(100, "Sampling period", minval=1)
multiplier = input.float(3.0, "Range multiplier", minval=0.1)
averageChange = ta.ema(math.abs(source - source[1]), period)
smoothedRange = ta.ema(averageChange, period * 2 - 1) * multiplier
var float filtered = na
previousFilter = filtered[1]
if na(previousFilter)
filtered := source
else if not na(source) and not na(smoothedRange)
filtered := source > previousFilter ? math.max(previousFilter, source - smoothedRange) : math.min(previousFilter, source + smoothedRange)
else
filtered := previousFilter
var int upward = 0
var int downward = 0
upward := filtered > filtered[1] ? nz(upward[1]) + 1 : filtered < filtered[1] ? 0 : nz(upward[1])
downward := filtered < filtered[1] ? nz(downward[1]) + 1 : filtered > filtered[1] ? 0 : nz(downward[1])
sourceChanged = not na(source[1]) and source != source[1]
longCandidate = source > filtered and sourceChanged and upward > 0
shortCandidate = source < filtered and sourceChanged and downward > 0
var int side = 0
previousSide = nz(side[1], 0)
side := longCandidate ? 1 : shortCandidate ? -1 : previousSide
buy = barstate.isconfirmed and longCandidate and previousSide == -1
sell = barstate.isconfirmed and shortCandidate and previousSide == 1
filterColor = upward > 0 ? color.teal : downward > 0 ? color.red : color.gray
filterPlot = plot(filtered, "Filtered price", color=filterColor, linewidth=2)
highPlot = plot(filtered + smoothedRange, "Upper band", color=color.new(color.teal, 60))
lowPlot = plot(filtered - smoothedRange, "Lower band", color=color.new(color.red, 60))
fill(highPlot, filterPlot, color=color.new(color.teal, 92))
fill(lowPlot, filterPlot, color=color.new(color.red, 92))
plot(upward, "Upward count", display=display.data_window)
plot(downward, "Downward count", display=display.data_window)
plot(smoothedRange, "Smoothed range", display=display.data_window)
plotshape(buy, title="Confirmed buy", text="BUY", style=shape.labelup, location=location.belowbar, color=color.teal, textcolor=color.white, size=size.tiny)
plotshape(sell, title="Confirmed sell", text="SELL", style=shape.labeldown, location=location.abovebar, color=color.red, textcolor=color.white, size=size.tiny)
alertcondition(buy, "Confirmed Range Filter buy", "Range Filter buy: {{ticker}} {{interval}} close={{close}}")
alertcondition(sell, "Confirmed Range Filter sell", "Range Filter sell: {{ticker}} {{interval}} close={{close}}")The code remains selectable if clipboard access is unavailable.
Compare values before creating an alert
- Use standard candles, the same source, settings, symbol and history window. Compare the smoothed range first, then filtered price, counters and stored side. A different seed can cause early mismatches.
- Inspect a flat-filter bar and the first opposite movement. Confirm that a plateau retains the count and the first established side prints no label.
- In TradingView's Create Alert dialog, select this indicator and either Confirmed Range Filter buy or sell. Choose Once Per Bar Close. Adding the indicator alone does not create a running alert.
- Recreate the alert after changing the script, inputs, symbol or timeframe. TradingView runs a saved copy of that context; historical labels do not send retrospective notifications.
Timing reference: TradingView alerts and bar states.
Limits to check before using the labels
A wider range suppresses smaller movements but can delay reversals. A narrower range can create repeated flips in sideways prices. Neither setting has a universal win rate. A gap can move the filter abruptly; the indicator does not model fills, spreads, commissions or stop execution.
The bands are arithmetic offsets from the current filter, not confidence intervals. Because r is nonnegative, the filter cannot cross above its own upper band or below its own lower band. Price crossing a band is a different condition from this script's reversal signal.
External indicator sources can have their own repainting or missing-data behavior. Changing the chart's starting history changes EMA seeds and can affect the recursive state. Use the same dataset when investigating mismatches and treat nonstandard chart prices as a separate test.
Turn the verified rules into your own script
Pineify's Pine Script AI Coding Agent can draft and edit indicator code from a written specification. Provide the public reference, EMA lengths, seed rule, plateau counters and close condition. Ask it to check syntax, then compare the generated values in TradingView before enabling alerts. Syntax validation cannot establish trading performance or replicate unavailable proprietary code.
Open Pine Script AI Coding Agent