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 ScriptUse 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.
| Bar | High / low / close | TR / ATR | Retained upper / lower | Direction / line |
|---|---|---|---|---|
| A | 108 / 104 / 107 | 4 / 3 | 110 / 102 | +1 / 110 |
| B | 112 / 108 / 111 | 5 / 4 | 110 / 102 | −1 / 102 |
| C | 113 / 109 / 112 | 4 / 4 | 119 / 103 | −1 / 103 |
| D | 116 / 100 / 115 | 16 / 10 | 119 / 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.
- Check direction signs and both retained bands against an explicit calculation.
- Separate a state, a direction flip, a wick touch and a completed-bar crossing.
- Record signal timing separately from order timing and include costs and gap handling.
- 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.
// 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 ——————————————————————————————————————————————————————

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 AgentEducational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.