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 -1 AND current close exceeds finalUpperBand[1].
  • A flip to downtrend requires: previous direction was 1 AND current close falls below finalLowerBand[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:

flowchart TD A[New Bar Closes] --> B{bar_index == 0?} B -- Yes --> C[Seed: direction=1 finalUpperBand=basicUpper finalLowerBand=basicLower superTrend=finalLowerBand] B -- No --> D[Compute basicUpper and basicLower from src and ATR] D --> E[Apply Ratchet: finalUpperBand = min or reset finalLowerBand = max or reset] E --> F{direction prev == -1 AND close > finalUpperBand prev?} F -- Yes --> G[Flip to Uptrend direction=1 superTrend=finalLowerBand BUY signal] F -- No --> H{direction prev == 1 AND close < finalLowerBand prev?} H -- Yes --> I[Flip to Downtrend direction=-1 superTrend=finalUpperBand SELL signal] H -- No --> J[Carry Forward direction=direction prev superTrend=lower or upper band] G --> K[Plot SuperTrend Line in Green] I --> L[Plot SuperTrend Line in Red] J --> M[Plot SuperTrend Line unchanged color]

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() with close[1] and barmerge.lookahead_on to 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

Comments

Popular posts from this blog

How to Build a Volume Indicator in Pine Script v6: Buying & Selling Pressure Analysis

Pine Script v6 plot() Function: Complete Guide to Lines, Histograms, and Circles