MQL5 Scalping EA: M5 EMA Crossovers and ATR Sizing
Inspect a single-symbol M5 EMA crossover EA with an entry spread filter, ATR-derived initial stop distances and estimated percentage sizing. The tutorial exposes the calculation and execution limits; it does not publish verified backtest results or establish an edge for short-term trading.
Strategy logic
Entry conditions
The fast and slow EMA values from two completed M5 bars define a crossover. The EA checks the spread on the first tick of a new M5 bar and allows one matching symbol-and-magic position. Indicator-data retries are allowed until that bar is processed, but a spread rejection skips the bar. Each chart instance trades its own symbol; there is no portfolio selector.
Exit conditions
Requested SL and TP distances are ATR multiplied by the input factors, fixed when the order is submitted. With the illustrative 1.5 and 2.0 settings, their distance ratio is 1:1.33 before costs and execution differences. At or after SessionEndHour in broker-server time, the EA retries matching-position closure on chart ticks and blocks entries until midnight. This is not an automatic New York timezone conversion or a guarantee of closure.
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()
{
if(FastEmaPeriod<=0 || SlowEmaPeriod<=0 || AtrPeriod<=0 || AtrSlMult<=0
|| AtrTpMult<=0 || RiskPercent<=0 || RiskPercent>100 || MaxSpreadPoints<0
|| SessionEndHour<0 || SessionEndHour>23 || MagicNumber==0) return INIT_PARAMETERS_INCORRECT;
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) || res.retcode!=TRADE_RETCODE_DONE)
Print("CloseAllPositions error: ", res.retcode);
}
}
//+------------------------------------------------------------------+
//| Utility: calculate lot size from risk % |
//+------------------------------------------------------------------+
double CalcLotSize(double slDistance)
{
if(slDistance <= 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;
if(tickSize<=0 || tickValue<=0 || riskAmount<=0) return 0;
double lotSize = riskAmount / (slDistance / tickSize * tickValue);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(stepLot<=0 || minLot<=0 || maxLot<=0) return 0;
lotSize = NormalizeDouble(MathFloor(MathMin(lotSize,maxLot)/stepLot)*stepLot,8);
if(lotSize<minLot) return 0;
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;
if(currentBarTime<=0) return;
//--- Spread filter
long spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(spreadPoints > MaxSpreadPoints) { g_lastBarTime=currentBarTime; return; }
// Two completed bars; each one-element buffer has an explicit shift.
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;
if(atrNow[0]<=0) return;
g_lastBarTime=currentBarTime;
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 lots = CalcLotSize(slDist); // Price distance, not a count of points.
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 = NormalizeDouble(ask - slDist,_Digits);
req.tp = NormalizeDouble(ask + tpDist,_Digits);
if(!OrderSend(req, res) || res.retcode!=TRADE_RETCODE_DONE)
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 = NormalizeDouble(bid + slDist,_Digits);
req.tp = NormalizeDouble(bid - tpDist,_Digits);
if(!OrderSend(req, res) || res.retcode!=TRADE_RETCODE_DONE)
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 entry, exit, position, order, and risk rules 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 | Strategy orders are simulated; live automation needs a separate 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.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.