TradingView indicator guides

Supertrend: ATR Bands, Direction Signs and Flip Rules

This example calls ta.supertrend(3, 10). A negative direction value selects the green uptrend line; a positive value selects the red downtrend line. The helper also computes direction-change booleans, but the script does not use them for alerts or orders. Its parameters are fixed in source.

Inspect the Pine Script

Use the retained band on the correct side

TradingView’s Supertrend documentation describes an ATR-based state calculation. The basic candidates are HL2 + factor × ATR and HL2 − factor × ATR, where HL2 = (high + low) / 2. The selected line is not simply one freshly calculated candidate on every bar.

Retain the previous upper band unless the new basic upper is lower, or the previous close exceeded the previous upper band. Retain the previous lower band unless the new basic lower is higher, or the previous close was below the previous lower band. These reset clauses mean the upper series does not only decrease forever, and the lower series does not only increase forever.

After initialization, if the previous selected Supertrend was the upper band, close must exceed the current retained upper band to switch upward. Otherwise it stays downward. If the previous selected line was the lower band, close below the current retained lower band switches downward; otherwise it stays upward. Strict equality does not trigger the corresponding flip.

The Pine reference implementation uses direction −1 for up and +1 for down, with a downward initialization state when the previous ATR is unavailable. A bullish change is therefore a crossunder of direction through zero, not a crossover. Being above the lower band while already in a down state is insufficient to reverse it.

A four-bar state example

Use factor 2 and Wilder ATR length 2 to make the arithmetic short. Assume an already initialized previous state: close 105, ATR 2, upper band 110, lower band 102, and a downward selected line at 110. These are explicit starting states for synthetic prices, not market results or a reconstruction of the built-in’s initial history.

Hypothetical updates from the specified starting state
BarHigh / low / closeTR / ATRRetained upper / lowerDirection / line
A108 / 104 / 1074 / 3110 / 102+1 / 110
B112 / 108 / 1115 / 4110 / 102−1 / 102
C113 / 109 / 1124 / 4119 / 103−1 / 103
D116 / 100 / 11516 / 10119 / 103−1 / 103

At A the close is above the lower band but below the retained upper band, so the state stays down. At B the close exceeds 110 and the selected line switches to lower band 102. At C the upper band resets upward to 119 because the previous close 111 exceeded its previous value 110; that unselected band can rise without reversing the uptrend.

At D the new basic lower is 108 − 2 × 10 = 88. The retained lower stays at 103. Close rises from 112 to 115 while the green line is flat, and the low of 100 crosses below the line intrabar without a closing reversal. Flatness does not prove weak momentum or that price is hugging the band. Chart zoom and axis scale also change the visual angle.

A displayed line is not an executable stop

The supplied indicator places no orders. A strategy using Supertrend as a stop must define when the stop price becomes available, when an order is submitted, how it updates and what happens through gaps. Computing a completed bar’s line and assuming an earlier intrabar fill at that value can introduce timing errors.

The script has no bar-close confirmation gate. Its current high, low and close can change while the bar is open, moving the ATR calculation and possibly the direction. Direction-change booleans are calculated on every bar, but there are no alertcondition or alert calls. A variable named buySignal is not a broker instruction or an active alert.

For confirmed alerts, explicitly gate the event at chart-bar close. A separate higher-timeframe version needs its own confirmed-data policy; this example makes no higher-timeframe request. A persistence rule or ADX filter is an additional testable condition, not a guarantee against losing trades.

Parameters and comparisons without implied performance

The fixed call uses factor 3 and ATR length 10; there are no input declarations in this copied script. Pineify’s separate Visual Editor configuration exposes both inputs. When comparing variants, match ATR smoothing, initialization, source midpoint, band resets, factor, chart feed and timeframe.

Increasing factor increases the basic candidate distance for a given ATR. The eventual selected line also depends on history and state, so a larger factor does not prove higher reliability or a fixed reduction in flips. ATR measures range and gaps, not direction; a volatility spike can lower a basic lower band while the retained uptrend line stays fixed.

Parabolic SAR uses an extreme point and an acceleration factor that changes at qualifying new extremes and resets on reversal. It is not a fixed factor that simply increases with elapsed time. Different trailing-stop algorithms have different state rules; none has an inherent accuracy ranking established by its name.

  1. Check direction signs and both retained bands against an explicit calculation.
  2. Separate a state, a direction flip, a wick touch and a completed-bar crossing.
  3. Record signal timing separately from order timing and include costs and gap handling.
  4. Evaluate parameter changes on an independent period. No measured win rate, false-flip percentage or best timeframe is established here.

Pine Script v6: implementation and limits

The complete source keeps the built-in calculation, unused direction-change outputs and original plots. The hidden fill anchor is (open + close) / 2, while the calculation midpoint is HL2. Unused time helpers do not apply a session filter.

Pine Script v6
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify

//======================================================================//
//                    ____  _            _  __                          //
//                   |  _ (_)_ __   ___(_)/ _|_   _                    //
//                   | |_) | | '_  / _  | |_| | | |                   //
//                   |  __/| | | | |  __/ |  _| |_| |                   //
//                   |_|   |_|_| |_|___|_|_|  __, |                   //
//                                             |___/                    //
//======================================================================//

//@version=6
indicator(title="Supertrend", overlay=true, max_labels_count=500)

//#region —————————————————————————————————————————————————— Custom Code

//#endregion ————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Common Dependence

p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_unix_time = na
    int end_unix_time = na
    int start_time_hour = na
    int start_time_minute = na
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
    end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
    [start_unix_time, end_unix_time]

p_comm_time_range_to_start_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_time_hour = na
    int start_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)

p_comm_time_range_to_end_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)

p_comm_timeframe_to_seconds(simple string tf) =>
    float seconds = 0
    tf_lower = str.lower(tf)
    value = str.tonumber(str.substring(tf_lower, 0, str.length(tf_lower) - 1))
    if str.endswith(tf_lower, 's')
        seconds := value
    else if str.endswith(tf_lower, 'd')
        seconds := value * 86400
    else if str.endswith(tf_lower, 'w')
        seconds := value * 604800
    else if str.endswith(tf_lower, 'm')
        seconds := value * 2592000
    else
        seconds := str.tonumber(tf_lower) * 60
    seconds

p_custom_sources() =>
    [open, high, low, close, volume]

//#endregion —————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Ta Dependence

p_ta_supertrend(series float factor, simple int atrPeriod) =>
    [supertrendLine, direction] = ta.supertrend(factor, atrPeriod)
    buySignal = ta.crossunder(direction, 0)
    sellSignal = ta.crossover(direction, 0)
    [supertrendLine, direction, buySignal, sellSignal]

//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Constants

// Input Groups
string P_GP_1      =      ""

//#endregion —————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Inputs

//#endregion ———————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Price Data



//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicators

[p_ind_1_supertrend, p_ind_1_direction, p_ind_1_buySignal, p_ind_1_sellSignal]      =      p_ta_supertrend(3, 10) // Supertrend


//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Conditions

//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicator Plots

// Supertrend
p_ind_1_supertrend_value = barstate.isfirst ? na : p_ind_1_supertrend
p_ind_1_supertrend_middle = plot(barstate.isfirst ? na : (open + close) / 2, "Supertrend - Middle", display=display.none)
p_ind_1_supertrend_up = plot(p_ind_1_direction < 0 ? p_ind_1_supertrend_value : na, "Supertrend - Up Trend", color=color.rgb(76, 175, 80, 0), style=plot.style_linebr, linewidth=1)
p_ind_1_supertrend_down = plot(p_ind_1_direction < 0 ? na : p_ind_1_supertrend_value, "Supertrend - Down Trend", color=color.rgb(242, 54, 69, 0), style=plot.style_linebr, linewidth=1)
fill(p_ind_1_supertrend_middle, p_ind_1_supertrend_up, title="Supertrend - Up Trend Background", color=color.rgb(76, 175, 80, 85), fillgaps=false)
fill(p_ind_1_supertrend_middle, p_ind_1_supertrend_down, title="Supertrend - Down Trend Background", color=color.rgb(242, 54, 69, 85), fillgaps=false)

//#endregion ————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Custom Plots

//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Alert

//#endregion ——————————————————————————————————————————————————————
Existing indicator chart preview
Existing visual preview. Verify exact values against the source; the image is not a performance result.

Specify a checkable adaptation in Pineify

Ask the Pine Script AI Coding Agent for explicit factor and ATR inputs and optional confirmed direction-flip alerts. If requesting a strategy, define when stops become active and how gaps and fills are handled. Review final source, verify chart values and evaluate a separate sample before drawing performance conclusions.

Open Pine Script AI Coding Agent

Educational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.

Implementation questions

No. TradingView ta.supertrend uses negative direction for up and positive direction for down. This helper detects an upward change with ta.crossunder(direction, 0).

No. When the previous selected line was the upper band, an upward reversal requires close above the current retained upper band. Bar A in the example remains down despite closing above the lower band.

No. The lower band can be retained while price rises and ATR expands. Bar D raises close from 112 to 115 while the selected line stays at 103.

No. It calculates unused change booleans and plots the line and fill. Alerts, confirmation rules and orders require explicit additional code.

The copied example has fixed arguments and no input declarations. Edit the call or add inputs. The separate Visual Editor exposes those settings.