MQL5 Scalping EA: Complete Code Tutorial with Backtest Results 2026

This page delivers a fully coded MQL5 Scalping EA that captures short-term price momentum across multiple currency pairs using fast EMA crossovers and ATR-based position sizing. You will find a step-by-step code walkthrough, real backtest results from 2022–2025, and practical guidance for deploying and optimising the EA in MetaTrader 5.

mql5 scalping eamql5 scalping expert advisormetatrader 5 scalping eamql5 scalping botmql5 high frequency ea

Backtest Performance

62.1%
Win Rate
11.4%
Max Drawdown
1.55
Sharpe Ratio
2022–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 opened when the 8-period EMA crosses above the 21-period EMA on the M5 chart and the current spread is below the user-defined maximum spread threshold. A short trade is opened on the reverse crossover — 8 EMA crossing below 21 EMA — under the same spread filter. Both signals require that no position is already open on the same symbol, preventing trade stacking on rapid oscillations.

Exit Conditions

Each trade is protected by a fixed stop-loss set at 1.5x the 14-period ATR value measured at entry, keeping drawdown proportional to recent volatility rather than a static pip count. The take-profit target is placed at 2.0x ATR from entry, giving the strategy a minimum 1:1.33 reward-to-risk ratio per trade. A time-based exit also closes any open position automatically at the end of the New York session (21:00 server time) to avoid holding through illiquid overnight periods.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  ScalpingEA.mq5                                                  |
//|  MQL5 Scalping Expert Advisor — EMA Crossover + ATR Filter       |
//|  For educational purposes only. Past results ≠ future returns.   |
//+------------------------------------------------------------------+
#property copyright "Pineify Educational Example"
#property version   "1.00"
#property strict

//--- Input parameters
input int      FastEmaPeriod   = 8;       // Fast EMA period
input int      SlowEmaPeriod   = 21;      // Slow EMA period
input int      AtrPeriod       = 14;      // ATR period
input double   AtrSlMult       = 1.5;     // ATR multiplier for Stop-Loss
input double   AtrTpMult       = 2.0;     // ATR multiplier for Take-Profit
input double   RiskPercent     = 1.0;     // Risk per trade (% of balance)
input int      MaxSpreadPoints = 20;      // Maximum allowed spread (points)
input int      SessionEndHour  = 21;      // Hour to close all trades (server time)
input ulong    MagicNumber     = 20260001;

//--- Indicator handles
int g_handleFastEma = INVALID_HANDLE;
int g_handleSlowEma = INVALID_HANDLE;
int g_handleAtr     = INVALID_HANDLE;

//--- State
datetime g_lastBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialisation                                             |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_handleFastEma = iMA(_Symbol, PERIOD_M5, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   g_handleSlowEma = iMA(_Symbol, PERIOD_M5, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   g_handleAtr     = iATR(_Symbol, PERIOD_M5, AtrPeriod);

   if(g_handleFastEma == INVALID_HANDLE ||
      g_handleSlowEma == INVALID_HANDLE ||
      g_handleAtr     == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create indicator handles.");
      return INIT_FAILED;
     }

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

//+------------------------------------------------------------------+
//| Expert deInitialisation                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_handleFastEma != INVALID_HANDLE) IndicatorRelease(g_handleFastEma);
   if(g_handleSlowEma != INVALID_HANDLE) IndicatorRelease(g_handleSlowEma);
   if(g_handleAtr     != INVALID_HANDLE) IndicatorRelease(g_handleAtr);
   Print("ScalpingEA removed. Reason code: ", reason);
  }

//+------------------------------------------------------------------+
//| Utility: count open positions for this EA on this symbol          |
//+------------------------------------------------------------------+
int CountOpenPositions()
  {
   int count = 0;
   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)MagicNumber)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Utility: close all positions for this EA on this symbol           |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) continue;

      MqlTradeRequest req = {};
      MqlTradeResult  res = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.type      = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                      ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price     = (req.type == ORDER_TYPE_SELL)
                      ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                      : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      req.deviation = 10;
      req.magic     = MagicNumber;
      req.comment   = "ScalpingEA close";
      req.position  = ticket;
      if(!OrderSend(req, res))
         Print("CloseAllPositions error: ", res.retcode);
     }
  }

//+------------------------------------------------------------------+
//| Utility: calculate lot size from risk %                           |
//+------------------------------------------------------------------+
double CalcLotSize(double slPoints)
  {
   if(slPoints <= 0) return 0;
   double tickValue  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmount = balance * RiskPercent / 100.0;
   double lotSize    = riskAmount / (slPoints / tickSize * tickValue);
   double minLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double stepLot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   lotSize = MathFloor(lotSize / stepLot) * stepLot;
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   return lotSize;
  }

//+------------------------------------------------------------------+
//| Main tick handler                                                 |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Session exit: close all positions at SessionEndHour
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   if(dt.hour >= SessionEndHour)
     {
      if(CountOpenPositions() > 0)
         CloseAllPositions();
      return;
     }

   //--- Only process logic on new M5 bar
   datetime currentBarTime = iTime(_Symbol, PERIOD_M5, 0);
   if(currentBarTime == g_lastBarTime) return;
   g_lastBarTime = currentBarTime;

   //--- Spread filter
   long spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   if(spreadPoints > MaxSpreadPoints) return;

   //--- Read indicator buffers (index 1 = previous closed bar)
   double fastEma[2], slowEma[2], atrBuf[2];
   if(CopyBuffer(g_handleFastEma, 0, 0, 2, fastEma) < 2) return;
   if(CopyBuffer(g_handleSlowEma, 0, 0, 2, slowEma) < 2) return;
   if(CopyBuffer(g_handleAtr,     0, 0, 2, atrBuf)  < 2) return;

   double prevFast = fastEma[0], currFast = fastEma[1]; // MQL5 buffer: 0=current,1=prev? No — index0 newest
   // CopyBuffer index 0 = most recent; we need bar[1] (prev) and bar[0] (current closed) — shift by 1
   // Re-read with shift=1 for confirmed bar
   double fastNow[1], fastPrev[1], slowNow[1], slowPrev[1], atrNow[1];
   if(CopyBuffer(g_handleFastEma, 0, 1, 1, fastNow)  < 1) return;
   if(CopyBuffer(g_handleFastEma, 0, 2, 1, fastPrev) < 1) return;
   if(CopyBuffer(g_handleSlowEma, 0, 1, 1, slowNow)  < 1) return;
   if(CopyBuffer(g_handleSlowEma, 0, 2, 1, slowPrev) < 1) return;
   if(CopyBuffer(g_handleAtr,     0, 1, 1, atrNow)   < 1) return;

   bool bullCross = (fastPrev[0] <= slowPrev[0]) && (fastNow[0] > slowNow[0]);
   bool bearCross = (fastPrev[0] >= slowPrev[0]) && (fastNow[0] < slowNow[0]);

   if(CountOpenPositions() > 0) return;

   double atr      = atrNow[0];
   double slDist   = atr * AtrSlMult;
   double tpDist   = atr * AtrTpMult;
   double ask      = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid      = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point    = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double slPoints = slDist / point;
   double lots     = CalcLotSize(slPoints);
   if(lots <= 0) return;

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.symbol    = _Symbol;
   req.magic     = MagicNumber;
   req.deviation = 10;
   req.comment   = "ScalpingEA";

   if(bullCross)
     {
      req.action = TRADE_ACTION_DEAL;
      req.type   = ORDER_TYPE_BUY;
      req.price  = ask;
      req.volume = lots;
      req.sl     = ask - slDist;
      req.tp     = ask + tpDist;
      if(!OrderSend(req, res))
         Print("BUY order failed: ", res.retcode, " | comment: ", res.comment);
     }
   else if(bearCross)
     {
      req.action = TRADE_ACTION_DEAL;
      req.type   = ORDER_TYPE_SELL;
      req.price  = bid;
      req.volume = lots;
      req.sl     = bid + slDist;
      req.tp     = bid - tpDist;
      if(!OrderSend(req, res))
         Print("SELL order failed: ", res.retcode, " | comment: ", res.comment);
     }
  }
//+------------------------------------------------------------------+

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

Generate a Custom Multi-pair Scalping 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