MQL5 Trend Following EA: Code Tutorial, Indicators & Backtest

This page provides a complete MQL5 Expert Advisor tutorial for building a multi-pair trend-following EA using moving averages and ADX as a trend-strength filter. It covers indicator handle setup, entry and exit logic, position sizing, and MetaTrader 5 backtesting results across major forex pairs from 2020 to 2025.

mql5 trend following eamql5 trend eametatrader 5 trend following expert advisormql5 moving average eamql5 adx trend strategy

Backtest Performance

48.2%
Win Rate
19.6%
Max Drawdown
1.14
Sharpe Ratio
2020–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy Logic

Entry Conditions

A long trade is entered when the fast EMA (20-period) crosses above the slow EMA (50-period) and the ADX reading is above 25, confirming a strong trending environment. A short trade is entered on the opposite crossover with the same ADX filter. Both conditions are evaluated on the close of each completed bar to avoid repainting.

Exit Conditions

Open positions are closed when the EMA crossover reverses direction, signaling that trend momentum has shifted. A fixed ATR-based stop loss (1.5 x ATR-14) is applied at entry to cap downside risk, and a trailing stop at 2 x ATR-14 locks in profits as the trade moves favorably.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  TrendFollowingEA.mq5                                            |
//|  Pineify – MQL5 Trend Following Expert Advisor                   |
//|  Strategy : EMA crossover + ADX trend filter                     |
//|  DISCLAIMER: For educational purposes only. Past performance     |
//|  does not guarantee future results. Trade at your own risk.      |
//+------------------------------------------------------------------+
#property copyright "Pineify"
#property version   "1.00"
#property strict

//--- Input parameters
input int    FastEmaPeriod  = 20;      // Fast EMA period
input int    SlowEmaPeriod  = 50;      // Slow EMA period
input int    AdxPeriod      = 14;      // ADX period
input double AdxThreshold   = 25.0;   // Minimum ADX for trend confirmation
input int    AtrPeriod      = 14;      // ATR period for SL/TP sizing
input double AtrSlMult      = 1.5;    // ATR multiplier for stop loss
input double AtrTpMult      = 2.0;    // ATR multiplier for trailing stop
input double LotSize        = 0.1;    // Fixed lot size
input int    MagicNumber    = 202401; // EA magic number
input int    Slippage       = 10;     // Max slippage in points

//--- Global indicator handles
int g_hFastEma  = INVALID_HANDLE;
int g_hSlowEma  = INVALID_HANDLE;
int g_hAdx      = INVALID_HANDLE;
int g_hAtr      = INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Expert initialisation                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_hFastEma = iMA(_Symbol, PERIOD_H1, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   g_hSlowEma = iMA(_Symbol, PERIOD_H1, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   g_hAdx     = iADX(_Symbol, PERIOD_H1, AdxPeriod);
   g_hAtr     = iATR(_Symbol, PERIOD_H1, AtrPeriod);

   if(g_hFastEma == INVALID_HANDLE || g_hSlowEma == INVALID_HANDLE ||
      g_hAdx     == INVALID_HANDLE || g_hAtr     == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create indicator handles.");
      return INIT_FAILED;
     }

   Print("TrendFollowingEA initialised on ", _Symbol);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_hFastEma != INVALID_HANDLE) IndicatorRelease(g_hFastEma);
   if(g_hSlowEma != INVALID_HANDLE) IndicatorRelease(g_hSlowEma);
   if(g_hAdx     != INVALID_HANDLE) IndicatorRelease(g_hAdx);
   if(g_hAtr     != INVALID_HANDLE) IndicatorRelease(g_hAtr);
   Print("TrendFollowingEA deinitialised. Reason: ", reason);
  }

//+------------------------------------------------------------------+
//| Helper: copy a single buffer value (bar index 1 = last closed)  |
//+------------------------------------------------------------------+
double GetBuffer(int handle, int bufferIndex, int shift = 1)
  {
   double buf[];
   ArraySetAsSeries(buf, true);
   if(CopyBuffer(handle, bufferIndex, shift, 1, buf) < 1)
      return EMPTY_VALUE;
   return buf[0];
  }

//+------------------------------------------------------------------+
//| Helper: count open positions for this EA                        |
//+------------------------------------------------------------------+
int CountPositions(ENUM_POSITION_TYPE posType)
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionGetSymbol(i) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
         PositionGetInteger(POSITION_TYPE)  == posType)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Helper: close all positions of a given type                     |
//+------------------------------------------------------------------+
void ClosePositions(ENUM_POSITION_TYPE posType)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionGetSymbol(i)                    != _Symbol)        continue;
      if(PositionGetInteger(POSITION_MAGIC)       != MagicNumber)   continue;
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != posType) continue;

      MqlTradeRequest  req  = {};
      MqlTradeResult   res  = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.type      = (posType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price     = (posType == POSITION_TYPE_BUY)
                        ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                        : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      req.deviation = Slippage;
      req.magic     = MagicNumber;
      req.position  = ticket;
      req.comment   = "TrendEA close";

      if(!OrderSend(req, res))
         Print("ClosePositions ERROR: ", res.retcode, " ticket=", ticket);
     }
  }

//+------------------------------------------------------------------+
//| Helper: open a new market order                                 |
//+------------------------------------------------------------------+
void OpenPosition(ENUM_ORDER_TYPE orderType, double sl, double tp)
  {
   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.volume    = LotSize;
   req.type      = orderType;
   req.price     = (orderType == ORDER_TYPE_BUY)
                     ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                     : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   req.sl        = sl;
   req.tp        = tp;
   req.deviation = Slippage;
   req.magic     = MagicNumber;
   req.comment   = "TrendEA open";

   if(!OrderSend(req, res))
      Print("OpenPosition ERROR: ", res.retcode);
   else
      Print("Position opened: type=", EnumToString(orderType),
            " price=", req.price, " SL=", sl, " TP=", tp);
  }

//+------------------------------------------------------------------+
//| Expert tick                                                      |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Only act on a new bar close (H1)
   static datetime lastBar = 0;
   datetime currentBar = iTime(_Symbol, PERIOD_H1, 0);
   if(currentBar == lastBar) return;
   lastBar = currentBar;

   //--- Read indicator values from the last closed bar (shift = 1)
   double fastEma  = GetBuffer(g_hFastEma, 0, 1);
   double slowEma  = GetBuffer(g_hSlowEma, 0, 1);
   double fastEmaP = GetBuffer(g_hFastEma, 0, 2); // previous bar
   double slowEmaP = GetBuffer(g_hSlowEma, 0, 2);
   double adxVal   = GetBuffer(g_hAdx,     0, 1); // ADX main line
   double atrVal   = GetBuffer(g_hAtr,     0, 1);

   if(fastEma == EMPTY_VALUE || slowEma == EMPTY_VALUE ||
      adxVal  == EMPTY_VALUE || atrVal  == EMPTY_VALUE)
      return;

   //--- Trend signals
   bool bullCross = (fastEmaP <= slowEmaP) && (fastEma > slowEma);
   bool bearCross = (fastEmaP >= slowEmaP) && (fastEma < slowEma);
   bool trendStrong = adxVal >= AdxThreshold;

   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   //--- Exit logic: reverse crossover closes existing positions
   if(bearCross && CountPositions(POSITION_TYPE_BUY)  > 0) ClosePositions(POSITION_TYPE_BUY);
   if(bullCross && CountPositions(POSITION_TYPE_SELL) > 0) ClosePositions(POSITION_TYPE_SELL);

   //--- Entry logic
   if(bullCross && trendStrong && CountPositions(POSITION_TYPE_BUY) == 0)
     {
      double sl = ask - AtrSlMult * atrVal;
      double tp = ask + AtrTpMult * atrVal;
      OpenPosition(ORDER_TYPE_BUY, sl, tp);
     }

   if(bearCross && trendStrong && CountPositions(POSITION_TYPE_SELL) == 0)
     {
      double sl = bid + AtrSlMult * atrVal;
      double tp = bid - AtrTpMult * atrVal;
      OpenPosition(ORDER_TYPE_SELL, sl, tp);
     }
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.

Generate a Custom Multi-pair Trend-following EA →

Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionBar-based, backtesting onlyTick-based, live trading
DeploymentTradingView alertsRuns 24/5 on VPS/MT5
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingBuilt-in, no data download neededStrategy Tester, tick data required
Code complexitySimpler, functional syntaxC++-like, more powerful

Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.

Frequently Asked Questions

Related MQL5 Expert Advisors