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
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
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-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.