Complete .mq5 source code

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.

Historical test results

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, save it in the MQL5/Experts folder, and compile with F7.

Generate a custom Multi-pair scalping EA

Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-like, more powerful

The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

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.