TradingView indicator guides

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 example

Fix 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.

Constant-range example: r = 2, filter seeded at 100
BarSourceFiltered priceUp / down countStored sideConfirmed signal
0100100 (seed)0 / 0NeutralNone
11031011 / 0LongNone: first side
21041022 / 0LongNone
3103102 (held)2 / 0LongNone
4991010 / 1ShortSell at close
5981000 / 2ShortNone
61031011 / 0LongBuy 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

Range Filter questions

The reference is the public Range Filter Buy and Sell 5min, guikroth version, publication J8GzFGfD revision 2. Its double EMA and filter recurrence are distinct from the configurable Range Filter [DW]. Our v6 example explicitly seeds the filter at source and confirms labels at bar close; it is not an exact copy of the reference startup behavior.

An unchanged filter preserves both counters. An upward move adds one to the upward count and resets the downward count; a downward move does the reverse. These are counts of filter movements since the last opposite movement, not consecutive rising or falling candle counts.

No. This example starts with a neutral side. The first qualifying direction establishes that side without a label. A buy requires a previous short side, and a sell requires a previous long side. The displayed labels also require the bar to close.

Bar-close confirmation prevents this script from printing a label on a temporary intrabar condition. The live filter and bands still move while the bar is open. An external source can repaint independently, and revised market data or a different history start can change recalculated values.

No. They are the current filter plus and minus the smoothed range. They do not represent a probability, guaranteed support or a tested stop distance. Since the range is nonnegative, the filter itself cannot cross outside its own current bands.

No. Range Filter transforms an input price series on existing bars. Range Bars construct chart bars from price movement. Range trading is a strategy that trades between chosen boundaries. This indicator alone defines neither chart construction nor a complete trading strategy.