Self-coded Expert Advisor “Moving Average Crossover”
Introduction
This Expert Advisor (EA) “MA_Cross” for MetaTrader 5 automates the detection and trading of a trend-following setup based on two Exponential Moving Averages (EMA) and the Average True Range (ATR).
It demonstrates how to do the following in MQL5:
- Create and read indicators
- Detect signals (crossover and volatility filter)
- Implement risk management via stop-loss, take-profit, and lot sizing
- Place and monitor orders
- Dynamically adjust a trailing stop
The goal is to show how to work cleanly with indicator handles in MQL5 and build a complete, running EA.
Code Overview
Libraries and trade object
#include <Trade\Trade.mqh> // Include CTrade class for order functions
CTrade trade; // Trade object for sending and modifying orders
Trade.mqh provides the CTrade class, which makes it easy to place market orders (buy/sell) and modify existing positions.
Input parameters
input int FastEMAPeriod = 10; // Period for fast EMA
input int SlowEMAPeriod = 50; // Period for slow EMA
input int ATRPeriod = 14; // ATR period
input double RiskPercent = 1.0; // Max risk per trade in % of the account
input double RewardRiskRatio = 2.0; // TP-to-SL ratio (e.g., 1:2)
input bool UseTrailingStop = true; // Enable trailing stop?
input string CommentText = "EMA_Cross_ATR";// Comment for all orders
These inputs let you adjust all key parameters (EMA periods, ATR, risk, R:R ratio, trailing stop) directly in the tester or EA properties.
Global handles for indicators
int fastHandle; // Handle for fast EMA (FastEMAPeriod)
int slowHandle; // Handle for slow EMA (SlowEMAPeriod)
int atrHandle; // Handle for ATR (ATRPeriod)
Indicators in MQL5 are created via handles. These integers store references that are later used to read values using CopyBuffer().
OnInit()
int OnInit()
{
// Create EMA handles
fastHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
// Create ATR handle
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);
// Check if all handles are valid
if(fastHandle == INVALID_HANDLE
|| slowHandle == INVALID_HANDLE
|| atrHandle == INVALID_HANDLE)
{
Print("Error creating indicator handles");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
- iMA(…) and iATR(…) return the handles.
- If a handle is INVALID_HANDLE, initialization fails.
CalculateLot()
double CalculateLot(bool isBuy, double stopLossPrice)
{
// Entry price (Ask for buy, Bid for sell)
double entryPrice = isBuy
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Distance Entry ↔ Stop-loss in price units
double distance = MathAbs(entryPrice - stopLossPrice);
// Amount we are allowed to risk at most
double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;
// Tick value and tick size for the symbol
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double pointValue = tickValue / tickSize;
// Raw lot calculation: risk / (distance × point value)
double lots = riskAmount / (distance * pointValue);
// Round to allowed volume steps
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lots = MathFloor(lots / step) * step;
if(lots < minLot) lots = minLot;
if(lots > maxLot) lots = maxLot;
return(lots);
}
This function computes the lot size so that, if the stop-loss is hit, the maximum risk amount is not exceeded.
OnTick()
void OnTick()
{
// 1) Current Bid/Ask prices
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// 2) Buffers for the last 2 values of each indicator
double fastBuf[2], slowBuf[2], atrBuf[2];
// 3) Copy indicator values (current = index 0, previous = index 1)
if(CopyBuffer(fastHandle, 0, 0, 2, fastBuf) <= 0 ||
CopyBuffer(slowHandle, 0, 0, 2, slowBuf) <= 0 ||
CopyBuffer(atrHandle, 0, 0, 2, atrBuf) <= 0)
{
Print("Error copying indicator data");
return;
}
// 4) Assign buffer data
double fastEMA = fastBuf[0], fastEMAold = fastBuf[1];
double slowEMA = slowBuf[0], slowEMAold = slowBuf[1];
double atrNow = atrBuf[0], atrOld = atrBuf[1];
// 5) Check whether a position is already open
bool positionExists = PositionSelect(_Symbol);
// — Enter only if no position is open —
if(!positionExists)
{
// Long signal: EMA10 crosses EMA50 up + ATR rising
bool longSignal = (fastEMAold < slowEMAold)
&& (fastEMA > slowEMA)
&& (atrNow > atrOld);
// Short signal: EMA10 crosses EMA50 down
bool shortSignal = (fastEMAold > slowEMAold)
&& (fastEMA < slowEMA);
// Long entry
if(longSignal)
{
double sl = ask - 1.5 * atrNow;
double tp = ask + RewardRiskRatio * (ask - sl);
double lot = CalculateLot(true, sl);
if(trade.Buy(lot, NULL, ask, sl, tp, CommentText))
Print("Opened long at ", ask, " SL=", sl, " TP=", tp);
}
// Short entry
else if(shortSignal)
{
double sl = bid + 1.5 * atrNow;
double tp = bid - RewardRiskRatio * (sl - bid);
double lot = CalculateLot(false, sl);
if(trade.Sell(lot, NULL, bid, sl, tp, CommentText))
Print("Opened short at ", bid, " SL=", sl, " TP=", tp);
}
}
// — Trailing stop if a position is open and enabled —
else if(UseTrailingStop)
{
ulong ticket = PositionGetInteger(POSITION_TICKET);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double newSL = (type == POSITION_TYPE_BUY)
? bid - atrNow
: ask + atrNow;
trade.PositionModify(ticket, newSL, 0);
}
}
In OnTick(), indicators are evaluated, signals are detected, orders are sent, and the stop-loss is dynamically trailed when needed.
OnDeinit()
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
}
When removing the EA, all indicator handles are released to avoid memory leaks and other issues.
Explanation of the key steps
- Creating handles and reading values
- iMA() / iATR() each return a handle in
OnInit(), i.e., a numeric reference to the indicator. - In OnTick(),
CopyBuffer(handle, 0, shift, count, buffer)fetches current and previous values into a local array. - Advantage: more efficient than repeated direct calls and allows access to historical bars.
- iMA() / iATR() each return a handle in
- Crossover detection
bool longSignal = (fastEMAold < slowEMAold) && (fastEMA > slowEMA) && (atrNow > atrOld); bool shortSignal = (fastEMAold > slowEMAold) && (fastEMA < slowEMA);- fastEMAold and slowEMAold are the EMA values of the previous bar; fastEMA and slowEMA are those of the current bar.
- A long signal occurs when the fast EMA crosses above the slow EMA and ATR shows rising volatility.
- A short signal occurs when crossing from above to below, without an ATR filter.
- Risk management & lot sizing
- Stop-loss is set to 1.5 × ATR:
- Long: SL = Ask – 1.5 × ATR
- Short: SL = Bid + 1.5 × ATR
- Take-profit so that the risk-reward ratio is 1:2:
- TP distance = 2 × SL distance
- In
CalculateLot(), the lot size ensures that, if the stop-loss is hit, at most RiskPercent% of the account balance is lost. SymbolInfoDouble(..., SYMBOL_TRADE_TICK_VALUE)andSYMBOL_TRADE_TICK_SIZEare used to correctly account for the cash value per point.
- Stop-loss is set to 1.5 × ATR:
- Order placement
- With
trade.Buy(lot, NULL, price, sl, tp, CommentText)andtrade.Sell(...)a market order is sent with defined SL and TP. CTradehandles error processing, slippage, and returns the order ticket.
- With
- Trailing stop (optional)
- Once a position is open and
UseTrailingStop == true, the EA moves the stop-loss by 1 × ATR behind current price on every tick:- Long: new SL = Bid – ATR
- Short: new SL = Ask + ATR
- This provides a dynamic lock-in of accrued profit.
- Once a position is open and
- Cleanup in OnDeinit()
- All created indicator handles (
fastHandle,slowHandle,atrHandle) are released withIndicatorRelease(handle)to free resources properly.
- All created indicator handles (
With these clearly structured steps, you learn how to:
- Create and read indicators efficiently
- Detect signals precisely
- Implement responsible risk management
- Manage market orders professionally
- Implement dynamic stop-loss strategies
Summary
This EA demonstrates a complete MQL5 workflow:
- Clean separation between initialization (
OnInit), main logic (OnTick), and cleanup (OnDeinit). - Handle-based indicator usage with
CopyBuffer(). - Simple trend-following strategy based on two EMAs plus a volatility filter.
- Careful risk management using stop-loss, take-profit, and dynamic lot sizing.
- Optional automation of a trailing stop.
With this commented example, you get a solid foundation to develop your own strategies in MQL5 and understand how to implement automated trading systems professionally. Good luck with your next steps in MQL5 programming!
Source Code MA_Cross.mq5
//+------------------------------------------------------------------+
//| MA_Cross.mq5 |
//| EMA Crossover + ATR Filter for MetaTrader 5 (MQL5) |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh> // Include CTrade class for order functions
CTrade trade; // Trade object
//— Input parameters ——————————————————————————————————————————————
input int FastEMAPeriod = 10; // Period for fast EMA
input int SlowEMAPeriod = 50; // Period for slow EMA
input int ATRPeriod = 14; // ATR period
input double RiskPercent = 1.0; // Max risk per trade in % (e.g., 1 = 1%)
input double RewardRiskRatio = 2.0; // Risk–reward ratio (e.g., 1:2)
input bool UseTrailingStop = true; // Enable trailing stop?
input string CommentText = "EMA_Cross_ATR"; // Comment text for orders
//— Global indicator handles ——————————————————————————————————————
int fastHandle; // Handle for fast EMA
int slowHandle; // Handle for slow EMA
int atrHandle; // Handle for ATR
//+------------------------------------------------------------------+
//| OnInit: Initialization when the EA is loaded |
//+------------------------------------------------------------------+
int OnInit()
{
// Create EMA handles with symbol, timeframe, period, shift, method, price
fastHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
// Create ATR handle with symbol, timeframe, period
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);
// Check if handles are valid
if(fastHandle==INVALID_HANDLE || slowHandle==INVALID_HANDLE || atrHandle==INVALID_HANDLE)
{
Print("Error creating indicator handles");
return(INIT_FAILED); // Abort init on error
}
return(INIT_SUCCEEDED); // All good
}
//+------------------------------------------------------------------+
//| CalculateLot: Lot size based on SL distance |
//+------------------------------------------------------------------+
double CalculateLot(bool isBuy, double stopLossPrice)
{
// 1) Determine entry price depending on buy/sell
double entryPrice = isBuy
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
// 2) Distance SL ↔ entry
double distance = MathAbs(entryPrice - stopLossPrice);
// 3) Risk amount (balance × risk %)
double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;
// 4) Point value for symbol (TickValue / TickSize)
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double pointValue = tickValue / tickSize;
// 5) Raw lots = risk / (distance × point value)
double lots = riskAmount / (distance * pointValue);
// 6) Round to minimum lot step
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lots = MathFloor(lots / step) * step;
if(lots < minLot) lots = minLot;
if(lots > maxLot) lots = maxLot;
return(lots); // Return final lot size
}
//+------------------------------------------------------------------+
//| OnTick: Main logic on every tick |
//+------------------------------------------------------------------+
void OnTick()
{
// 1) Get current bid/ask
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// 2) Buffers for current and previous indicator values
double fastBuf[2], slowBuf[2], atrBuf[2];
// 3) Copy data from handles
if(CopyBuffer(fastHandle, 0, 0, 2, fastBuf) <= 0 ||
CopyBuffer(slowHandle, 0, 0, 2, slowBuf) <= 0 ||
CopyBuffer(atrHandle, 0, 0, 2, atrBuf) <= 0)
{
Print("Error copying indicator data");
return; // Abort on error
}
// 4) Assign values: [0] = current, [1] = previous
double fastEMA = fastBuf[0];
double fastEMAold = fastBuf[1];
double slowEMA = slowBuf[0];
double slowEMAold = slowBuf[1];
double atrNow = atrBuf[0];
double atrOld = atrBuf[1];
// 5) Check if a position is already open
bool positionExists = PositionSelect(_Symbol);
// — Entry if no position is open ————————————————————————————————
if(!positionExists)
{
// Long if EMA cross up + ATR rising
bool longSignal = (fastEMAold < slowEMAold) && (fastEMA > slowEMA) && (atrNow > atrOld);
// Short if EMA cross down
bool shortSignal = (fastEMAold > slowEMAold) && (fastEMA < slowEMA);
if(longSignal) // Long entry
{
double sl = ask - 1.5 * atrNow; // SL = entry − 1.5×ATR
double tp = ask + RewardRiskRatio * (ask - sl); // TP = entry + 2×risk
double lot = CalculateLot(true, sl); // Compute lot
if(trade.Buy(lot, NULL, ask, sl, tp, CommentText)) // Send buy order
Print("Opened long at ", ask, " SL=", sl, " TP=", tp);
}
else if(shortSignal) // Short entry
{
double sl = bid + 1.5 * atrNow; // SL = entry + 1.5×ATR
double tp = bid - RewardRiskRatio * (sl - bid); // TP = entry − 2×risk
double lot = CalculateLot(false, sl); // Compute lot
if(trade.Sell(lot, NULL, bid, sl, tp, CommentText)) // Send sell order
Print("Opened short at ", bid, " SL=", sl, " TP=", tp);
}
}
// — Trailing stop if a position is open ——————————————————————————
else if(UseTrailingStop)
{
ulong ticket = PositionGetInteger(POSITION_TICKET); // Position ticket
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double newSL;
if(type == POSITION_TYPE_BUY) // If long
newSL = bid - atrNow; // SL = Bid − 1×ATR
else // If short
newSL = ask + atrNow; // SL = Ask + 1×ATR
trade.PositionModify(ticket, newSL, 0); // Adjust SL
}
}
//+------------------------------------------------------------------+
//| OnDeinit: Release handles |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle); // Release fast EMA
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle); // Release slow EMA
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle); // Release ATR
}
//+------------------------------------------------------------------+
