What We Will Build
We will create a complete Expert Advisor that trades a simple moving average crossover strategy: buy when the fast MA crosses above the slow MA, sell when it crosses below. The EA includes stop-loss, take-profit, and only one position at a time.
Complete EA Source Code
//+------------------------------------------------------------------+
//| MACrossoverEA.mq5 |
//| Simple Moving Average Crossover Expert Advisor |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh> // Include the CTrade class
// Input parameters
input int FastMAPeriod = 10; // Fast MA Period
input int SlowMAPeriod = 50; // Slow MA Period
input double LotSize = 0.1; // Lot Size
input int StopLoss = 100; // Stop Loss (points)
input int TakeProfit = 200; // Take Profit (points)
input int MagicNumber = 12345; // Magic Number (unique EA ID)
// Global variables
int fastMAHandle, slowMAHandle;
datetime lastBarTime;
CTrade trade; // Trade execution object
//+------------------------------------------------------------------+
int OnInit()
{
// Validate inputs
if(FastMAPeriod >= SlowMAPeriod)
{
Print("Error: Fast MA must be smaller than Slow MA");
return INIT_PARAMETERS_INCORRECT;
}
// Create indicator handles
fastMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod,
0, MODE_EMA, PRICE_CLOSE);
slowMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod,
0, MODE_EMA, PRICE_CLOSE);
if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE)
{
Print("Error creating MA handles");
return INIT_FAILED;
}
// Set magic number for order identification
trade.SetExpertMagicNumber(MagicNumber);
lastBarTime = 0;
Print("MA Crossover EA initialized. Fast: ", FastMAPeriod,
" Slow: ", SlowMAPeriod);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnTick()
{
// Only trade on new bars (not every tick)
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;
// Get MA values for the last 3 completed bars
double fastMA[], slowMA[];
ArraySetAsSeries(fastMA, true);
ArraySetAsSeries(slowMA, true);
CopyBuffer(fastMAHandle, 0, 1, 3, fastMA);
CopyBuffer(slowMAHandle, 0, 1, 3, slowMA);
// Detect crossover on the LAST COMPLETED bar
bool bullishCross = (fastMA[0] > slowMA[0]) &&
(fastMA[1] <= slowMA[1]);
bool bearishCross = (fastMA[0] < slowMA[0]) &&
(fastMA[1] >= slowMA[1]);
// Check if we have an open position
bool hasPosition = PositionSelect(_Symbol);
// BULLISH CROSSOVER — close sell, open buy
if(bullishCross)
{
if(hasPosition && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
trade.PositionClose(_Symbol);
if(!PositionSelect(_Symbol)) // no position after close
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - StopLoss * _Point;
double tp = ask + TakeProfit * _Point;
trade.Buy(LotSize, _Symbol, ask, sl, tp, "MA Cross Buy");
}
}
// BEARISH CROSSOVER — close buy, open sell
if(bearishCross)
{
if(hasPosition && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
trade.PositionClose(_Symbol);
if(!PositionSelect(_Symbol))
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = bid + StopLoss * _Point;
double tp = bid - TakeProfit * _Point;
trade.Sell(LotSize, _Symbol, bid, sl, tp, "MA Cross Sell");
}
}
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(fastMAHandle);
IndicatorRelease(slowMAHandle);
Print("MA Crossover EA removed. Reason: ", reason);
}
//+------------------------------------------------------------------+
Key Concepts Explained
The CTrade Class
The #include <Trade/Trade.mqh> line imports the standard trade library. The CTrade class provides clean methods for order operations:
trade.Buy(volume, symbol, price, sl, tp, comment);
trade.Sell(volume, symbol, price, sl, tp, comment);
trade.PositionClose(symbol);
trade.PositionModify(ticket, sl, tp);
This is much easier than the raw OrderSend() function.
Magic Number
The Magic Number is a unique identifier for your EA's trades. If you run multiple EAs on the same account, each should have a different Magic Number so they do not interfere with each other's positions.
New Bar Detection
We only check for signals when a new bar opens (not on every tick). This prevents the EA from repeatedly acting on the same signal and ensures we use completed bar data for analysis.
Crossover Detection
A crossover is detected by comparing two consecutive bars: if the fast MA was below the slow MA on bar[1] and is now above on bar[0], that is a bullish crossover. We use bars starting from index 1 (last completed bar) to avoid using the still-forming current bar.
Testing Your EA
In MT5, press Ctrl + R or go to View > Strategy Tester.
Select your EA, choose a symbol (e.g., EURUSD), set the timeframe (e.g., H1), and choose a date range with at least 1 year of data.
Click Start. After the backtest, examine the Results tab (trade list), Graph tab (equity curve), and Report tab (statistics).
Past performance does not predict future results. Backtests use perfect historical data without real-world issues like slippage, requotes, and variable spreads. Always forward-test on a demo account before going live.
Improvements for Production
This EA is educational. A production EA would also need:
- Dynamic lot sizing based on account balance and risk percentage
- Trailing stop-loss to lock in profits
- Trading session filters (avoid low-liquidity hours)
- News event filters
- Error handling and retry logic for failed orders
- Magic number filtering to only manage its own positions
- Logging for diagnostics and auditing