Self-Programmed Expert Advisor “Moving Average Crossover”
Introduction
This Expert Advisor (EA) “MA_Cross” for MetaTrader 5 automates 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 use MQL5 to:
- 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, runnable EA.
Code Overview
Libraries and Trading Object
#include <Trade\Trade.mqh> // include CTrade class for order functions
CTrade trade; // trading object to send and manage 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; // period for ATR
input double RiskPercent = 1.0; // max risk per trade in % of 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 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 integer variables store references and are later used with CopyBuffer()
to read values.
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);
// maximum amount we are allowed to risk
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 size: 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 calculates the lot size so that, if the stop-loss is hit, the maximum risk amount will not be exceeded.
OnTick()
void OnTick()
{
// 1) current Bid/Ask
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 if a position is already open
bool positionExists = PositionSelect(_Symbol);
// — entries only if no position is open —
if(!positionExists)
{
// long signal: EMA10 crosses EMA50 upward + ATR rising
bool longSignal = (fastEMAold < slowEMAold)
&& (fastEMA > slowEMA)
&& (atrNow > atrOld);
// short signal: EMA10 crosses EMA50 downward
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("Long opened 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("Short opened 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 trailed dynamically if 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
- Handle creation and value access
- iMA() / iATR() each return a handle in
OnInit()
, a numeric reference to the respective indicator. - In OnTick(),
CopyBuffer(handle, 0, shift, count, buffer)
fetches current and previous values into a local array. - Benefit: more efficient than repeated direct calls and enables 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 previous bar’s EMA values; fastEMA and slowEMA are the current ones.
- A long signal occurs when the fast EMA crosses above the slow EMA and ATR indicates rising volatility.
- A short signal occurs when crossing downward, without the 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 targets a 1:2 risk-reward:
- TP distance = 2 × SL distance
CalculateLot()
computes how many lots to place so that, if the SL is hit, at most RiskPercent% of the account is lost.- It uses
SymbolInfoDouble(..., SYMBOL_TRADE_TICK_VALUE)
andSYMBOL_TRADE_TICK_SIZE
to correctly account for the money value per point.
- Stop-loss is set to 1.5 × ATR:
- Order placement
- With
trade.Buy(lot, NULL, price, sl, tp, CommentText)
ortrade.Sell(...)
a market order is sent with defined SL and TP. CTrade
handles internal error processing, slippage, and returns the order ticket.
- With
- Trailing stop (optional)
- Once a position is open and
UseTrailingStop == true
, the EA shifts the SL on every tick by 1 × ATR behind the current market price:- Long: new SL = Bid – ATR
- Short: new SL = Ask + ATR
- This provides a dynamic lock-in of accrued profits.
- 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
- Apply dynamic stop-loss strategies
Summary
This EA demonstrates a complete workflow in MQL5:
- Clean separation between initialization (
OnInit
), main logic (OnTick
), and cleanup (OnDeinit
). - Handle-based indicator usage with
CopyBuffer()
. - Simple trend-following strategy using 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 have a solid foundation to develop your own strategies in MQL5 and understand how to implement professional automated trading systems. 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; // trading object
//—— Input parameters —————————————————————————————————————————————
input int FastEMAPeriod = 10; // fast EMA period
input int SlowEMAPeriod = 50; // slow EMA period
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"; // order comment
//—— Global handles for indicators ————————————————————————————————
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 source
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);
// validate handles
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); // ok
}
//+------------------------------------------------------------------+
//| CalculateLot: lot size based on SL distance |
//+------------------------------------------------------------------+
double CalculateLot(bool isBuy, double stopLossPrice)
{
// 1) 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 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); // return final lot size
}
//+------------------------------------------------------------------+
//| OnTick: main logic each tick |
//+------------------------------------------------------------------+
void OnTick()
{
// 1) 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);
// —— entries when no position is open ————————————————————————————
if(!positionExists)
{
// long when EMA cross up + ATR rising
bool longSignal = (fastEMAold < slowEMAold) && (fastEMA > slowEMA) && (atrNow > atrOld);
// short when 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); // lot sizing
if(trade.Buy(lot, NULL, ask, sl, tp, CommentText)) // send buy order
Print("Long opened 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); // lot sizing
if(trade.Sell(lot, NULL, bid, sl, tp, CommentText)) // send sell order
Print("Short opened 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) // for long
newSL = bid - atrNow; // SL = Bid – 1×ATR
else // for short
newSL = ask + atrNow; // SL = Ask + 1×ATR
trade.PositionModify(ticket, newSL, 0); // modify 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
}
//+------------------------------------------------------------------+