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.
Backtest Performance
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
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-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.