SuperTrend Indicator in Pine Script v6: Canonical Formula, Flip Logic, and Non-Repainting Signals
Every trend-following trader eventually faces the same frustration: a moving average that lags too far behind price, generating entries long after the move has started and exits well into the reversal. The SuperTrend indicator was designed to solve exactly this problem — it combines Average True Range (ATR) volatility with a dynamic band-switching mechanism to produce a single, clean line that sits below price in an uptrend and above price in a downtrend. When price closes beyond the opposite band, the line flips, generating a confirmed signal on the closed bar. This post builds a precise, well-defined SuperTrend variant in Pine Script v6, maps every formula term to its code variable, explains the exact flip conditions, and clarifies non-repainting behavior.
What Is SuperTrend? The Mathematical Foundation
SuperTrend is built on two components: a midpoint price and an ATR-scaled offset. The midpoint is typically the average of the high and low of each bar. The ATR, multiplied by a user-defined factor, creates an envelope around that midpoint. The indicator then tracks which side of the envelope price is on, and only switches sides when price closes decisively through the opposite band.
Core Formulas
Let $m$ be the ATR multiplier and $n$ be the ATR period. The raw bands are:
$$\text{Basic Upper Band} = \frac{\text{high} + \text{low}}{2} + m \times \text{ATR}(n)$$ $$\text{Basic Lower Band} = \frac{\text{high} + \text{low}}{2} - m \times \text{ATR}(n)$$The final bands incorporate a ratchet mechanism — they can only tighten, never widen, while price remains on the same side:
$$\text{Final Upper Band}_t = \begin{cases} \min(\text{Basic Upper}_t,\ \text{Final Upper}_{t-1}) & \text{if } \text{close}_{t-1} \leq \text{Final Upper}_{t-1} \\ \text{Basic Upper}_t & \text{otherwise} \end{cases}$$ $$\text{Final Lower Band}_t = \begin{cases} \max(\text{Basic Lower}_t,\ \text{Final Lower}_{t-1}) & \text{if } \text{close}_{t-1} \geq \text{Final Lower}_{t-1} \\ \text{Basic Lower}_t & \text{otherwise} \end{cases}$$The SuperTrend line and direction are then determined by the flip conditions:
- If the previous direction was downtrend ($\text{dir}_{t-1} = -1$) and $\text{close}_t > \text{Final Upper}_{t-1}$: flip to uptrend, SuperTrend = Final Lower Band.
- If the previous direction was uptrend ($\text{dir}_{t-1} = 1$) and $\text{close}_t < \text{Final Lower}_{t-1}$: flip to downtrend, SuperTrend = Final Upper Band.
- Otherwise: direction carries forward, SuperTrend = Final Lower Band (uptrend) or Final Upper Band (downtrend).
Formula-to-Code Mapping
Before reading the full implementation, this table maps every mathematical term directly to its Pine Script variable so there is no ambiguity between the formula and the code:
| Formula Term | Pine Script Variable | Notes |
|---|---|---|
| $(\text{high}+\text{low})/2$ | src |
Midpoint price; user-selectable |
| $\text{ATR}(n)$ | atrValue |
ta.atr() with period atrPeriod |
| $m \times \text{ATR}(n)$ | atrValue * multiplier |
Offset applied to both bands |
| Basic Upper Band | basicUpper |
Raw upper envelope, recalculated each bar |
| Basic Lower Band | basicLower |
Raw lower envelope, recalculated each bar |
| Final Upper Band$_t$ | finalUpperBand |
Ratcheted; can only tighten downward |
| Final Lower Band$_t$ | finalLowerBand |
Ratcheted; can only tighten upward |
| $\text{dir}_t$ | direction |
1 = uptrend, -1 = downtrend |
| SuperTrend$_t$ | superTrend |
The plotted line value |
Trading Strategy: When and How to Use SuperTrend
This implementation targets a specific, well-defined SuperTrend variant that matches the widely accepted formulation. Note that some broker platforms and charting tools may use slightly different band-ratcheting rules or ATR smoothing methods, which can cause minor visual differences.
The indicator is best applied in trending markets — strong directional moves in equities, crypto, or forex pairs. It performs poorly in choppy, range-bound conditions where price oscillates around the midpoint without sustained closes beyond either band. Ideal use cases include:
- Trend confirmation: Enter long only when SuperTrend is below price (direction = 1); enter short only when it is above price (direction = -1).
- Dynamic stop-loss: Trail the SuperTrend line as a stop — it tightens automatically as volatility contracts.
- Filter for other signals: Use SuperTrend direction to filter RSI or MACD signals, taking only those aligned with the trend.
Non-Repainting Behavior
All flip decisions in this implementation use the current bar's close versus prior-bar band values (e.g., close > finalUpperBand[1]). This means the signal is determined at the moment the bar closes and does not change afterward. There is no request.security() lookahead involved. The signal is confirmed on the closed bar — traders should evaluate entries only after bar close, not during the bar, to avoid acting on intra-bar fluctuations.
Full Pine Script v6 Implementation
🔽 [Click to expand] View Full Pine Script Code
//@version=6
indicator(title = "SuperTrend (Canonical Variant)", overlay = true)
// ─── Inputs ───────────────────────────────────────────────────────────────────
int atrPeriod = input.int(10, title = "ATR Period", minval = 1)
float multiplier = input.float(3.0, title = "ATR Multiplier", minval = 0.1, step = 0.1)
float src = input.source(hl2, title = "Source")
// ─── ATR ──────────────────────────────────────────────────────────────────────
// ta.atr() uses Wilder's RMA internally. It begins calculating from bar 0
// and gradually stabilizes over the first ~atrPeriod bars. It does NOT
// produce na after bar 0 — values are present from the very first bar,
// though they become more reliable once sufficient history has accumulated.
float atrValue = ta.atr(atrPeriod)
// ─── Basic Bands ──────────────────────────────────────────────────────────────
// Raw upper and lower envelopes centered on the source midpoint.
float basicUpper = src + multiplier * atrValue
float basicLower = src - multiplier * atrValue
// ─── Final Bands (Ratchet Mechanism) ─────────────────────────────────────────
// The ratchet prevents the bands from widening while price stays on the same side.
// Upper band can only move DOWN (tighten); lower band can only move UP (tighten).
// On bar 0 we seed both bands with the basic values.
var float finalUpperBand = na
var float finalLowerBand = na
if bar_index == 0
// Seed on the very first bar — no prior history available.
finalUpperBand := basicUpper
finalLowerBand := basicLower
else
// Upper band ratchet: tighten only if prior close was below prior upper band.
// If prior close broke above, reset to the new basic upper (band has flipped side).
finalUpperBand := close[1] <= finalUpperBand[1] ? math.min(basicUpper, finalUpperBand[1]) : basicUpper
// Lower band ratchet: tighten only if prior close was above prior lower band.
finalLowerBand := close[1] >= finalLowerBand[1] ? math.max(basicLower, finalLowerBand[1]) : basicLower
// ─── Direction and SuperTrend Line ────────────────────────────────────────────
// direction: 1 = uptrend (SuperTrend below price), -1 = downtrend (above price)
// Flip logic uses CURRENT close vs PRIOR band values — confirmed on bar close.
var int direction = 1
var float superTrend = na
if bar_index == 0
// Seed: assume uptrend on the first bar.
direction := 1
superTrend := finalLowerBand
else
if direction[1] == -1 and close > finalUpperBand[1]
// Was in downtrend; price closed above prior upper band → flip to uptrend.
direction := 1
superTrend := finalLowerBand
else if direction[1] == 1 and close < finalLowerBand[1]
// Was in uptrend; price closed below prior lower band → flip to downtrend.
direction := -1
superTrend := finalUpperBand
else
// No flip: carry direction forward, track the appropriate band.
direction := direction[1]
superTrend := direction == 1 ? finalLowerBand : finalUpperBand
// ─── Signals ──────────────────────────────────────────────────────────────────
// A buy signal fires on the exact bar where direction flips from -1 to 1.
// A sell signal fires on the exact bar where direction flips from 1 to -1.
bool buySignal = direction == 1 and direction[1] == -1
bool sellSignal = direction == -1 and direction[1] == 1
// ─── Plotting ─────────────────────────────────────────────────────────────────
// The SuperTrend line changes color based on trend direction.
plot(
superTrend,
title = "SuperTrend",
color = direction == 1 ? color.new(color.green, 0) : color.new(color.red, 0),
linewidth = 2
)
// Buy and sell markers plotted below/above bars at signal bars.
plotshape(
buySignal,
title = "Buy Signal",
style = shape.labelup,
location = location.belowbar,
color = color.new(color.green, 0),
text = "BUY",
size = size.small
)
plotshape(
sellSignal,
title = "Sell Signal",
style = shape.labeldown,
location = location.abovebar,
color = color.new(color.red, 0),
text = "SELL",
size = size.small
)
// Optional: background color to visually reinforce trend direction.
bgcolor(
direction == 1 ? color.new(color.green, 92) : color.new(color.red, 92),
title = "Trend Background"
)
Step-by-Step Code Walkthrough
1. ATR Calculation
ta.atr(atrPeriod) uses Wilder's smoothing (RMA) internally. It begins producing values from bar 0 and stabilizes progressively — there is no hard na warm-up window. Early bars will have ATR values that reflect limited history, but the function does not output na after bar 0. This is important: the bands are valid from the very first bar, though they become more representative of true volatility once sufficient bars have accumulated.
2. Basic Bands
Each bar, basicUpper and basicLower are computed fresh from the current src and atrValue. These are the raw, unratcheted envelopes.
3. Final Band Ratcheting
The ratchet is the key mechanism that prevents the SuperTrend line from oscillating unnecessarily. The upper band can only move down (tighten toward price) while price stays below it. If price closes above the prior upper band, the ratchet resets to the new basic upper. The lower band mirrors this logic upward. Both bands are seeded explicitly on bar_index == 0 to ensure their [1] history is valid from bar 1 onward.
4. Flip Logic (Canonical Variant)
The flip conditions depend on both the previous direction and the current close versus prior band values:
- A flip to uptrend requires: previous direction was
-1AND current close exceedsfinalUpperBand[1]. - A flip to downtrend requires: previous direction was
1AND current close falls belowfinalLowerBand[1]. - If neither condition is met, direction carries forward and the SuperTrend line tracks the appropriate band.
This is a precise variant of the widely accepted SuperTrend formulation. The use of direction[1] as a gate ensures that a flip can only occur from the opposite state — preventing spurious double-flips within a single bar. Some platform implementations omit this gate and check only the close-vs-band condition; the behavior is nearly identical in practice but this variant is more explicit.
5. Non-Repainting Confirmation
The flip decision uses close (current bar's close) versus finalUpperBand[1] and finalLowerBand[1] (prior bar's confirmed band values). Once the bar closes and the condition is evaluated, the result does not change. There is no request.security() call and no lookahead. Always evaluate signals on closed bars only — intra-bar closes may temporarily satisfy the condition before reversing.
Parameter Sensitivity
| Parameter | Default | Effect of Increasing | Effect of Decreasing |
|---|---|---|---|
ATR Period (atrPeriod) |
10 | Smoother ATR, wider bands, fewer flips | Noisier ATR, tighter bands, more flips |
Multiplier (multiplier) |
3.0 | Wider bands, fewer signals, larger stops | Tighter bands, more signals, smaller stops |
Source (src) |
hl2 | Using close shifts bands slightly | Using hl2 centers bands on bar range |
Logic Flow Diagram
The following diagram illustrates the bar-by-bar decision process for the SuperTrend flip logic:
Conclusion
- Canonical flip logic: Direction flips only when the previous direction is opposite AND the current close crosses the prior bar's final band — this gate prevents spurious double-flips and matches the widely accepted SuperTrend formulation.
- Non-repainting by design: All conditions use the current bar's close against prior confirmed band values; signals are fixed at bar close and do not change retroactively.
- ATR behavior:
ta.atr()produces values from bar 0 with no hard na window — early values reflect limited history but are numerically valid; the indicator is fully operational from the first bar.
Ideas for Further Development
- Multi-timeframe SuperTrend: Use
request.security()withclose[1]andbarmerge.lookahead_onto pull a higher-timeframe SuperTrend direction as a trend filter, taking signals only when both timeframes agree. - Adaptive multiplier: Replace the fixed multiplier with a volatility-normalized value (e.g., scaled by the ratio of current ATR to a longer-period ATR) to automatically widen bands in high-volatility regimes and tighten them in low-volatility regimes.
Related Posts
- 📄 CCI Indicator in Pine Script v6: Detect Cyclical Price Reversals with Precision
- 📄 ADX Trend Strength Indicator in Pine Script v6: Build a Compile-Tested DMI Filter
- 📄 How Does ATR Measure Volatility? A Pine Script v6 Deep Dive into Average True Range
- 📄 How Does the Stochastic Indicator Work? A Beginner's Guide to Overbought & Oversold Signals in Pine Script v6
- 📄 How to Build a Volume Indicator in Pine Script v6: Buying & Selling Pressure Analysis
Comments
Post a Comment