How to Code a Supertrend Indicator and an EA in MQL5

How to Code a Supertrend Indicator and an EA in MQL5

By HorizonAI Team · 14 min read · Intermediate

How to Code a Supertrend Indicator and EA in MQL5

A Supertrend is simple on a chart and surprisingly easy to get wrong in MQL5. The hard part isn't the ATR formula. It's keeping the upper and lower bands locked from one bar to the next, then making an EA act only after a closed-bar trend flip.

Short answer: Build Supertrend in MT5 with an ATR handle, two trailing band buffers, and a persistent trend-state buffer. Your EA should read that state through iCustom(), compare closed bars 1 and 2, close an opposite position, then place a new order with ATR-based stop-loss and take-profit levels.

The build below uses ATR 10 and a 3.0 multiplier, plots separate green and red trend lines, and provides a deliberately small EA that trades its flips. Start with the ATR indicator primer if you need a quick refresher on what ATR measures. Then compile the indicator first, attach it to a chart, and only afterward connect the EA.

What you are building, and the rules behind it

Supertrend starts with the midpoint of each bar and offsets it by volatility:

  • Basic upper band = (high + low) / 2 + ATR × multiplier
  • Basic lower band = (high + low) / 2 - ATR × multiplier

Those basic bands are not the line you trade. The actual bands are locked. In an uptrend, the lower band can rise but can't fall. In a downtrend, the upper band can fall but can't rise. Price closing beyond the opposite locked band flips the state.

That persistence is the difference between a trailing trend line and two bands that jump around every candle. A Supertrend buy state is +1; a sell state is -1. The indicator exposes that state in buffer 2 so the EA doesn't need to infer direction from line colors.

Use these starting inputs:

InputDefaultWhy it is a sensible baseline
ATR period10Responds faster than 14 without becoming extremely twitchy
Multiplier3.0Keeps the line outside ordinary noise on many liquid markets
EA lot size0.10A test value, not a position-sizing method
Stop-loss ATR multiple1.5Gives the trade a volatility-scaled invalidation distance
Take-profit ATR multiple3.0Sets a 2:1 target relative to the initial ATR stop

For fast intraday charts, test ATR 7 with a 2.5 to 3.0 multiplier. For H1 and H4 swing systems, ATR 10 or 14 with a 3.0 to 4.0 multiplier is a more stable starting grid. Don't optimize both inputs across dozens of combinations before you have verified that the implementation and execution assumptions are sound.

Step 1: Create the MQL5 custom indicator

In MetaEditor, create a Custom Indicator named Supertrend_MQL5.mq5 under MQL5/Indicators. The exact filename matters because the EA will request it by name with iCustom().

MQL5's iATR() creates an indicator handle, rather than directly returning an ATR value. You then retrieve values with CopyBuffer(). That handle-and-buffer pattern is standard across MQL5 indicators. See the official references for iATR, CopyBuffer, and the OnCalculate event handler before you start changing the code.

Paste this complete indicator and compile it.

#property copyright "HorizonAI Learn"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   2

#property indicator_label1  "Supertrend Up"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrLimeGreen
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "Supertrend Down"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrTomato
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

input int    InpATRPeriod  = 10;
input double InpMultiplier = 3.0;

// Plotted buffers.
double UpTrendBuffer[];
double DownTrendBuffer[];

// Hidden data buffer the EA reads: +1 uptrend, -1 downtrend.
double TrendStateBuffer[];

// Calculation buffers that preserve locked band values.
double FinalUpperBuffer[];
double FinalLowerBuffer[];

int atrHandle = INVALID_HANDLE;

int OnInit()
{
   if(InpATRPeriod < 1 || InpMultiplier <= 0.0)
      return(INIT_PARAMETERS_INCORRECT);

   SetIndexBuffer(0, UpTrendBuffer,     INDICATOR_DATA);
   SetIndexBuffer(1, DownTrendBuffer,   INDICATOR_DATA);
   SetIndexBuffer(2, TrendStateBuffer,  INDICATOR_DATA);
   SetIndexBuffer(3, FinalUpperBuffer,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(4, FinalLowerBuffer,  INDICATOR_CALCULATIONS);

   ArraySetAsSeries(UpTrendBuffer, true);
   ArraySetAsSeries(DownTrendBuffer, true);
   ArraySetAsSeries(TrendStateBuffer, true);
   ArraySetAsSeries(FinalUpperBuffer, true);
   ArraySetAsSeries(FinalLowerBuffer, true);

   // Buffer 2 is data for the EA, but remains hidden on the chart.
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_NONE);
   PlotIndexSetString(0, PLOT_LABEL, "Supertrend Up");
   PlotIndexSetString(1, PLOT_LABEL, "Supertrend Down");

   atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(atrHandle == INVALID_HANDLE)
   {
      Print("Could not create ATR handle. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   IndicatorSetString(INDICATOR_SHORTNAME,
                      "Supertrend (" + IntegerToString(InpATRPeriod) + ", " +
                      DoubleToString(InpMultiplier, 1) + ")");
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < InpATRPeriod + 2)
      return(0);

   double atr[];
   ArraySetAsSeries(atr, true);

   if(CopyBuffer(atrHandle, 0, 0, rates_total, atr) != rates_total)
      return(prev_calculated);

   // Calculate from the oldest bar toward bar 0 because each bar needs i + 1.
   for(int i = rates_total - 1; i >= 0; i--)
   {
      double midpoint   = (high[i] + low[i]) * 0.5;
      double basicUpper = midpoint + InpMultiplier * atr[i];
      double basicLower = midpoint - InpMultiplier * atr[i];

      if(i == rates_total - 1)
      {
         FinalUpperBuffer[i] = basicUpper;
         FinalLowerBuffer[i] = basicLower;
         TrendStateBuffer[i] = 1.0;
      }
      else
      {
         // Lock the upper band until price closes above the prior upper band.
         if(basicUpper < FinalUpperBuffer[i + 1] ||
            close[i + 1] > FinalUpperBuffer[i + 1])
            FinalUpperBuffer[i] = basicUpper;
         else
            FinalUpperBuffer[i] = FinalUpperBuffer[i + 1];

         // Lock the lower band until price closes below the prior lower band.
         if(basicLower > FinalLowerBuffer[i + 1] ||
            close[i + 1] < FinalLowerBuffer[i + 1])
            FinalLowerBuffer[i] = basicLower;
         else
            FinalLowerBuffer[i] = FinalLowerBuffer[i + 1];

         if(close[i] > FinalUpperBuffer[i + 1])
            TrendStateBuffer[i] = 1.0;
         else if(close[i] < FinalLowerBuffer[i + 1])
            TrendStateBuffer[i] = -1.0;
         else
            TrendStateBuffer[i] = TrendStateBuffer[i + 1];
      }

      // Draw only the active side. EMPTY_VALUE prevents false connecting lines.
      UpTrendBuffer[i] = EMPTY_VALUE;
      DownTrendBuffer[i] = EMPTY_VALUE;

      if(TrendStateBuffer[i] > 0.0)
         UpTrendBuffer[i] = FinalLowerBuffer[i];
      else
         DownTrendBuffer[i] = FinalUpperBuffer[i];
   }

   return(rates_total);
}

Why this buffer layout matters

Buffers 0 and 1 are the visible green and red lines. Buffer 2 is the hidden trend state, so CopyBuffer(handle, 2, ...) gives the EA a clean +1/-1 answer. Buffers 3 and 4 hold the locked bands while calculation runs.

A common first attempt uses only the visible line buffers and reads their color or checks whether one equals EMPTY_VALUE. That works until you change plotting logic. A numeric state buffer separates trading logic from chart cosmetics.

The loop runs from the oldest bar down to bar zero. Price arrays are series arrays, meaning index 0 is the current bar and i + 1 is the prior completed bar. Calculating in that direction guarantees the prior locked bands exist when the current bar needs them.

Step 2: Attach the indicator and verify the flips

Compile the file with F7, then drag Supertrend_MQL5 from Navigator onto a chart. Use a liquid symbol first, such as EURUSD, XAUUSD, or an index CFD supplied by your broker, and start on H1.

You should see one continuous line that alternates green below price during an uptrend and red above price during a downtrend. It can change position only after price crosses the relevant locked band.

Check these three points before writing any EA logic:

  1. The line does not switch color repeatedly inside a single closed candle.
  2. Green uses the locked lower band, red uses the locked upper band.
  3. Changing ATR period or multiplier in the indicator inputs visibly changes the line.

If the line is missing, open the Experts and Journal tabs. The usual causes are an ATR handle failure, insufficient historical bars, or compiling the code as an EA instead of as a Custom Indicator. The official OnCalculate documentation is useful here because it explains the indicator calculation contract and the role of rates_total.

Step 3: Build an EA that trades confirmed flips

Create an Expert Advisor called SupertrendFlipEA.mq5 under MQL5/Experts. This EA checks only once per new bar. It compares bars 1 and 2, not bar 0, so a still-forming candle can't produce a premature entry.

It also filters positions by symbol and magic number. That prevents this small example from closing a manually opened trade just because it happens to be on the same symbol.

#property copyright "HorizonAI Learn"
#property version   "1.00"
#property strict

#include <Trade/Trade.mqh>

input int    InpATRPeriod     = 10;
input double InpMultiplier    = 3.0;
input double InpLots          = 0.10;
input double InpStopLossATR   = 1.5;
input double InpTakeProfitATR = 3.0;
input ulong  InpMagicNumber   = 260810;

CTrade trade;
int supertrendHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
{
   if(InpATRPeriod < 1 || InpMultiplier <= 0.0 || InpLots <= 0.0 ||
      InpStopLossATR <= 0.0 || InpTakeProfitATR <= 0.0)
      return(INIT_PARAMETERS_INCORRECT);

   trade.SetExpertMagicNumber(InpMagicNumber);
   trade.SetTypeFillingBySymbol(_Symbol);

   // The filename is MQL5/Indicators/Supertrend_MQL5.mq5.
   supertrendHandle = iCustom(_Symbol, _Period, "Supertrend_MQL5",
                              InpATRPeriod, InpMultiplier);
   if(supertrendHandle == INVALID_HANDLE)
   {
      Print("Could not create Supertrend handle. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(atrHandle == INVALID_HANDLE)
   {
      Print("Could not create ATR handle. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if(supertrendHandle != INVALID_HANDLE)
      IndicatorRelease(supertrendHandle);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
}

bool IsNewBar()
{
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime == 0 || currentBarTime == lastBarTime)
      return(false);

   lastBarTime = currentBarTime;
   return(true);
}

void CloseOurPositions(const ENUM_POSITION_TYPE typeToClose)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      string symbol = PositionGetString(POSITION_SYMBOL);
      long magic = PositionGetInteger(POSITION_MAGIC);
      ENUM_POSITION_TYPE positionType =
         (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      if(symbol == _Symbol && magic == (long)InpMagicNumber &&
         positionType == typeToClose)
      {
         if(!trade.PositionClose(ticket))
            Print("Could not close position ", ticket, ". Error: ", GetLastError());
      }
   }
}

bool HasOurPosition(const ENUM_POSITION_TYPE positionType)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == (long)InpMagicNumber &&
         (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == positionType)
         return(true);
   }
   return(false);
}

void OpenBuy(const double atrValue)
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double stopLoss = NormalizeDouble(ask - atrValue * InpStopLossATR, digits);
   double takeProfit = NormalizeDouble(ask + atrValue * InpTakeProfitATR, digits);

   if(!trade.Buy(InpLots, _Symbol, 0.0, stopLoss, takeProfit, "Supertrend buy"))
      Print("Buy failed. Retcode: ", trade.ResultRetcode());
}

void OpenSell(const double atrValue)
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double stopLoss = NormalizeDouble(bid + atrValue * InpStopLossATR, digits);
   double takeProfit = NormalizeDouble(bid - atrValue * InpTakeProfitATR, digits);

   if(!trade.Sell(InpLots, _Symbol, 0.0, stopLoss, takeProfit, "Supertrend sell"))
      Print("Sell failed. Retcode: ", trade.ResultRetcode());
}

void OnTick()
{
   if(!IsNewBar())
      return;

   double trendState[];
   double atr[];
   ArraySetAsSeries(trendState, true);
   ArraySetAsSeries(atr, true);

   // Bar 1 just closed. Bar 2 is the preceding closed bar.
   if(CopyBuffer(supertrendHandle, 2, 0, 3, trendState) < 3)
      return;
   if(CopyBuffer(atrHandle, 0, 0, 3, atr) < 3 || atr[1] <= 0.0)
      return;

   int currentClosedTrend = (int)MathRound(trendState[1]);
   int previousClosedTrend = (int)MathRound(trendState[2]);

   bool flippedUp = currentClosedTrend == 1 && previousClosedTrend == -1;
   bool flippedDown = currentClosedTrend == -1 && previousClosedTrend == 1;

   if(flippedUp)
   {
      CloseOurPositions(POSITION_TYPE_SELL);
      if(!HasOurPosition(POSITION_TYPE_BUY))
         OpenBuy(atr[1]);
   }
   else if(flippedDown)
   {
      CloseOurPositions(POSITION_TYPE_BUY);
      if(!HasOurPosition(POSITION_TYPE_SELL))
         OpenSell(atr[1]);
   }
}

CTrade provides the order and position methods used here. Its official class reference documents the trade request wrappers and result handling. Read it before extending the EA with partial closes or pending orders: CTrade.

The execution sequence on a flip

On a bullish flip, the EA closes sell positions carrying its magic number. It then opens one buy if it doesn't already have one. A bearish flip does the reverse.

The stop and target are measured from the fresh bid or ask using ATR from bar 1. That makes distances expand in volatile conditions and contract in quiet ones. It does not make lot size risk-consistent across symbols. For that, add tick-value-based position sizing and broker stop-level checks before treating the EA as anything more than a test framework.

Step 4: Test it in Strategy Tester before changing logic

Open MT5's Strategy Tester, select SupertrendFlipEA, choose a symbol and timeframe, and use a date range that covers trending and sideways phases. Visual mode is worth using for the first run because it shows whether entries occur at the open after a completed flip, which is exactly what this EA is designed to do.

Use this test sequence:

  1. Run the defaults on H1 with one symbol and confirm entries line up with closed-bar color changes.
  2. Test ATR periods 7, 10, and 14 while holding the multiplier at 3.0.
  3. Test multipliers 2.0, 2.5, 3.0, and 3.5 while holding the period at 10.
  4. Include realistic spread and commission settings from your trading conditions.
  5. Inspect every rejected order in the Journal before assuming the strategy logic failed.

Track more than net profit. The backtesting metrics guide helps interpret drawdown, profit factor, trade count, and expectancy together. A Supertrend system can look tidy in a strong directional stretch, then give back gains through repeated reversals in a range.

Common Supertrend MQL5 mistakes

Mistake: Trading buffer value at bar 0. The current candle can cross a band and reverse before it closes, so the EA can enter on a state that doesn't survive the bar.

Do this: Compare trendState[1] with trendState[2]. Those are two completed bars. Use IsNewBar() so the check runs once, immediately after a new bar begins.

Mistake: Recalculating each band without the lock rule. A line based only on current hl2 ± ATR × multiplier changes too freely and isn't a conventional Supertrend.

Do this: Carry the prior final upper and lower buffers forward. Permit the upper band to fall in a downtrend and the lower band to rise in an uptrend, with the close-based reset conditions shown in the indicator.

Mistake: Calling iCustom() with the wrong name or input order. MT5 can fail to find the indicator or feed its defaults in a way you didn't intend.

Do this: Save the indicator as MQL5/Indicators/Supertrend_MQL5.mq5, compile it, and call iCustom(_Symbol, _Period, "Supertrend_MQL5", InpATRPeriod, InpMultiplier) in the same order as the indicator inputs.

Mistake: Closing every position on the symbol. That can interfere with manual trades or another EA.

Do this: Set a magic number, then filter by both POSITION_SYMBOL and POSITION_MAGIC. The example does that before every close or duplicate-position check.

Mistake: Assuming an accepted Buy() means a filled trade. Brokers can reject a request because of volume limits, market conditions, or invalid stops.

Do this: Check the boolean return and inspect trade.ResultRetcode() plus the Strategy Tester Journal. Add minimum stop-distance validation when you adapt this EA to a broker-specific symbol.

Pro tips for making the system easier to modify

Keep direction separate from presentation. The hidden TrendStateBuffer means you can replace the two line plots with arrows, a colored channel, or alerts without rewriting the EA's signal definition.

Use one timeframe per EA instance. This version takes signals from _Period, the chart timeframe. If you need an H1 Supertrend to control entries on M15, create the handles with PERIOD_H1, preserve the bar-time guard for H1, and test multi-timeframe synchronization carefully.

Add filters only after you log the raw flips. A practical first filter is an EMA 200 regime rule: allow buys only above EMA 200 and sells only below it. Another is an ADX threshold such as ADX 20 to reduce range-market flips. Each filter changes the trade sample, so re-test from the beginning rather than assuming it improves the system.

Keep the TradingView build separate. If your workflow begins with chart alerts rather than MT5 testing, see the Pine Script Supertrend strategy guide. Pine and MQL5 have different execution models, so copying signal logic is safer than copying syntax.

Generating this without writing the code yourself

HorizonAI can generate and edit MQL5 indicators and EAs from plain-English chat, then compiler-check the MQL5 code. For this build, give it a specification detailed enough to preserve the locked-band and closed-bar rules:

Create an MQL5 custom indicator named Supertrend_MQL5 for MetaTrader 5. Use iATR with ATR period 10 and multiplier 3.0. Plot a green lower Supertrend line in uptrends and a red upper line in downtrends. Use locked final upper and lower bands, calculate from oldest bar to newest, and expose a hidden buffer 2 with +1 for uptrend and -1 for downtrend.

Create an MQL5 EA named SupertrendFlipEA that reads buffer 2 from Supertrend_MQL5 using iCustom. On a new bar only, compare closed bars 1 and 2. On a +1 flip, close only this EA's sell positions by symbol and magic number, then buy 0.10 lots. Reverse that on a -1 flip. Use ATR 10 for a 1.5 ATR stop-loss and 3.0 ATR take-profit. Include CTrade error logging and compile-ready code.

You'll get editable MQL5 source code in chat, and you can ask for changes such as an EMA 200 filter or risk-based sizing. HorizonAI writes the code and helps validate it; you run the EA yourself in MetaTrader 5. Try it free →

FAQs

Does MQL5 have a built-in Supertrend indicator?

No standard built-in Supertrend is provided through the core MQL5 indicator functions. Build it from an ATR handle and your own band and trend-state buffers, or use a compiled custom indicator through iCustom().

Why does the EA read buffer 2 instead of the plotted line?

Buffer 2 holds a clear +1 or -1 trend state. The plotted buffers contain EMPTY_VALUE on the inactive side, which makes chart drawing clean but is less explicit for trading decisions.

Can I use this Supertrend EA on any MT5 symbol?

The code can attach to any symbol with enough price history, but contract sizes, minimum volume, spread, and stop-distance rules vary by broker and instrument. Test the symbol's trading conditions and rejected-order messages before relying on a configuration.

What timeframe works best for Supertrend?

There isn't one universal timeframe. H1 is a practical first test because it produces fewer flips than very short charts, while ATR 10 and multiplier 3.0 provide a defined baseline for comparison.

Can I convert this MQL5 Supertrend into Pine Script or NinjaScript?

Yes. Preserve the ATR calculation, locked-band state, and closed-bar signal rule, then translate the platform-specific data and order APIs. HorizonAI can convert scripts among Pine Script, MQL5, and NinjaScript, while each platform still needs its own testing workflow.

Final thoughts

A usable Supertrend build has three moving pieces: ATR, locked bands, and a state change confirmed at candle close. Keep those pieces explicit, and you can modify entries, exits, filters, and chart styling without turning the system into a black box.

One concrete next step: run the raw ATR 10, multiplier 3.0 version in Strategy Tester before adding an EMA or ADX filter. Save the report, then compare every modification against that same baseline.

Related articles

Questions about Supertrend MQL5 code? Join our Discord to discuss with other traders!