Skip to main content
QuantForged
T-41D 00:00:00
Join Discord
BlogWhy Supertrend flips on inside bars (and how to fix it)
Engineering9 min read

Why Supertrend flips on inside bars (and how to fix it)

A small piece of band-trailing logic decides whether your trend filter behaves. Most open-source Supertrend ports get it backwards. Here is what the correct rule looks like and why it matters on real charts.

Split-panel trading chart: chaotic oscillating red bands on the left labeled FLIPPING, clean blue trailing line on the right labeled TRAILING.
TL;DR

A properly built Supertrend trails its bands only while price sits inside them. The moment price closes through, the band resets. If you invert that comparison (the most common bug in open-source ports) you get a band that tracks price, not structure, and you see phantom flips on inside bars and low-volatility noise.

I review a lot of trend-filter code. Footprint ports, Heikin Ashi derivatives, the usual suspects. Supertrend is the one that fails code review most often, even in popular repos with thousands of stars. It is not that the calculation is hard. It is that one line of logic, the band-trailing rule, is easy to write backwards and the result still looks right on a trending chart. It only misbehaves in the specific condition that matters most for a trend filter: sideways price action.

If you have ever watched Supertrend flip green, red, green, red across three inside bars on a flat ES session open, this post is for you.

What Supertrend is actually doing

The mechanics are straightforward. You compute an ATR over some lookback (10 is the textbook default, though 14 and 22 are more common in practice on futures). You add and subtract a multiplier of ATR (2.0, 3.0, sometimes as high as 4.0) from a reference price. The reference is typically HL2, the average of the bar high and low.

That gives you two basic bands per bar:

basicUpper = HL2 + mult * ATR
basicLower = HL2 - mult * ATR

But raw basic bands are useless as a trend filter. They jiggle around with every tick of ATR and every bar-to-bar swing in HL2. To turn them into a stable trend reference, you trail them. That trailing rule is where almost every port gets it wrong.

The correct trailing rule

A Supertrend band trails while price remains inside it, and resets to the new basic band when price breaks through it. That sentence is the whole indicator. Everything else is plumbing.

Written out:

// Upper band: the ceiling in a downtrend
if (Close[1] <= upperBand[1]) {
    // Price was inside, trail it downward (never upward)
    finalUpper = Math.Min(basicUpper, upperBand[1]);
} else {
    // Price closed above the previous band, reset
    finalUpper = basicUpper;
}

// Lower band: the floor in an uptrend
if (Close[1] >= lowerBand[1]) {
    finalLower = Math.Max(basicLower, lowerBand[1]);
} else {
    finalLower = basicLower;
}

Read that carefully. The comparison on the upper band is Close[1] <= upperBand[1]. Previous close, previous band. If the prior close was at or below the prior band, we are still inside a downtrend, so we trail. We take the tighter of the two possible values: the new basic upper, or the previous trailed upper. Which ever is lower wins, because we are ratcheting the ceiling down.

If the prior close was above the band, price has escaped the downtrend regime, and we snap the upper band back to whatever the raw basic upper is now. That reset is the mechanism that lets the indicator flip.

The bug that ships in most ports

Here is the version I see copy-pasted across maybe half of the Supertrend repos on GitHub:

// WRONG: inverted comparison
if (Close[1] > upperBand[1]) {
    finalUpper = Math.Min(basicUpper, upperBand[1]);
} else {
    finalUpper = basicUpper;
}

Spot it? The author swapped the direction of the comparison. The band now trails after price has broken out, and resets while price is still inside. The effect is a band that does not trail price through a trend. Instead it snaps around on every bar where close happens to sit the “wrong” side of the previous band, which on a quiet inside bar is random.

Visually, it looks almost identical on a trending chart. Bars that close well away from the band produce the same band value either way. The bug only surfaces in three specific situations:

  1. Inside bars at the edge of a band. The band flips itself rather than holding structure.
  2. Low-ATR session opens. The first twenty minutes of RTH on ES, for example, when ATR has barely woken up and price is tape-reading around the settle.
  3. Range-bound chop. Exactly the condition a trend filter is supposed to filter out.

Which means the bug is invisible in the exact context where the indicator looks good anyway, and catastrophic in the context where you actually need the filter.

Three inside bars · band behavior
CORRECTBUGGY

Same three bars. Correct band holds structure. Buggy band bounces with every HL2 tick.

How to verify your own implementation

Five minute sanity check. Pull up a 5-minute ES chart on a low volatility Tuesday afternoon between 13:00 and 15:00 Eastern. Sit the Supertrend on it with default parameters. If the band flips more than once or twice in two hours of sideways price, it is almost certainly the inverted comparison. A correct Supertrend on that same window should sit still, possibly with one regime change if the afternoon resolved one way.

For a harder test, force an inside bar sequence synthetically. In a unit test or a paper-trade replay:

  • Seed a bar with Close above upperBand (establish downtrend reset).
  • Feed three inside bars with Close inside the previous band.
  • Check whether the band value is monotonically non-increasing across those three bars.

A correct implementation holds or tightens the band. A buggy one will reset it to basic values and you will see the band bounce up and down with each bar’s HL2.

What else to get right, while you are in there

A few things that tend to come up once you are auditing:

Use CurrentBar guards

Nothing kills a trend filter faster than division-by-zero or accessing upperBand[1] on bar 0. Gate the calculation behind if (CurrentBar < AtrPeriod) return; at the top of OnBarUpdate. In the first N bars, set the band values to sensible defaults (I usually seed them to close, which produces a flat trailing line until real ATR kicks in).

Handle the direction flip correctly

When price closes through upperBand, you enter an uptrend. Your active band flips from upper to lower. The naive code looks like this:

if (trend[1] == -1 && Close[0] > finalUpper[0]) {
    trend[0] = 1;
    finalLower[0] = basicLower[0]; // reset to fresh basic
} else if (trend[1] == 1 && Close[0] < finalLower[0]) {
    trend[0] = -1;
    finalUpper[0] = basicUpper[0]; // reset to fresh basic
}

The subtle point: when you flip direction, reset the new active band to its basic value. Not to its last trailed value. The last trailed value is stale, carried over from when it was the inactive band and not being updated on new ticks.

Do not mix ATR sources

Wilder’s ATR and simple-moving-average ATR give different values, especially in the first hundred bars. Pick one. Document it. If you are porting from TradingView Pine, note that ta.atr() uses Wilder’s smoothing. If you are porting from a NinjaTrader built-in, check whether it is ATR() or a custom. The discrepancy is a classic source of “why does my indicator look different than TradingView” complaints.

The broader lesson

Supertrend is a three-line calculation and a one-line trailing rule. Almost every derivative indicator, including Half Trend, Trend Magic, and various “SSL” channels, uses the same band-trailing pattern. If you see a vendor shipping four variants of a trend indicator and none of them acknowledge the inside-bar problem, you can infer something about how carefully they audited any of them.

The QuantForged build of Supertrend ships with the correct comparison, tick-level direction detection gated behind IsFirstTickOfBar, Wilder-smoothed ATR by default, and a public changelog going back to v1.0.0. You can read the full spec and parameters on the Supertrend page, or the comparison rule is in the indicator’s source, not buried in a DLL.

If you are building your own: the rule is Close[1] <= upperBand[1], not Close[1] > upperBand[1]. One character. Biggest behavioral difference in the indicator.

§ From QuantForged

The Edge 10, on every platform we ship. Locked for life.

Ten institutional-grade indicators across Context, Trend, Orderflow, and Structure. NinjaTrader 8 today. cTrader, TradingView, MT5, Tradovate rolling through 2026. Every future port included.

  • Context3
  • Trend2
  • Orderflow3
  • Structure2
Founders from$497$997 retail