Why Automate Your Trading?
Manual trading is emotional. You hesitate, you revenge-trade, you close winners too early. A well-coded Expert Advisor (EA) follows rules without flinching.
In this guide, I'll walk through building a simple but functional moving average crossover EA in MQL4 — the language behind MetaTrader 4, still the most widely used retail forex platform.
The Strategy
We'll implement a dual moving average crossover:
- Buy when the fast MA crosses above the slow MA
- Sell when the fast MA crosses below the slow MA
- One position at a time, with a fixed stop loss and take profit
Simple? Yes. But this foundation teaches you 90% of what you need for more complex strategies.
The Code
//+------------------------------------------------------------------+
//| MA_Cross_EA.mq4 |
//| A simple moving average crossover Expert Advisor |
//+------------------------------------------------------------------+
#property strict
// Input parameters
input int FastMA_Period = 10; // Fast MA period
input int SlowMA_Period = 50; // Slow MA period
input double LotSize = 0.01; // Position size
input int StopLoss = 100; // Stop loss in points
input int TakeProfit = 200; // Take profit in points
input int MagicNumber = 12345; // Unique EA identifier
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Only trade on new bar
static datetime lastBar = 0;
if (Time[0] == lastBar) return;
lastBar = Time[0];
// Calculate moving averages (using previous closed bar)
double fastMA_now = iMA(NULL, 0, FastMA_Period, 0, MODE_SMA, PRICE_CLOSE, 1);
double fastMA_prev = iMA(NULL, 0, FastMA_Period, 0, MODE_SMA, PRICE_CLOSE, 2);
double slowMA_now = iMA(NULL, 0, SlowMA_Period, 0, MODE_SMA, PRICE_CLOSE, 1);
double slowMA_prev = iMA(NULL, 0, SlowMA_Period, 0, MODE_SMA, PRICE_CLOSE, 2);
// Detect crossover
bool bullishCross = (fastMA_prev <= slowMA_prev) && (fastMA_now > slowMA_now);
bool bearishCross = (fastMA_prev >= slowMA_prev) && (fastMA_now < slowMA_now);
// Count open positions for this EA
int openPositions = CountPositions();
// Close opposite positions and open new ones
if (bullishCross)
{
ClosePositions(OP_SELL);
if (openPositions == 0 || CountPositions() == 0)
OpenTrade(OP_BUY);
}
else if (bearishCross)
{
ClosePositions(OP_BUY);
if (openPositions == 0 || CountPositions() == 0)
OpenTrade(OP_SELL);
}
}
//+------------------------------------------------------------------+
//| Open a new trade |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
double price, sl, tp;
double point = MarketInfo(Symbol(), MODE_POINT);
if (type == OP_BUY)
{
price = Ask;
sl = price - StopLoss * point;
tp = price + TakeProfit * point;
}
else
{
price = Bid;
sl = price + StopLoss * point;
tp = price - TakeProfit * point;
}
int ticket = OrderSend(
Symbol(), type, LotSize, price, 3, sl, tp,
"MA Cross EA", MagicNumber, 0, type == OP_BUY ? clrBlue : clrRed
);
if (ticket < 0)
Print("OrderSend failed: ", GetLastError());
}
//+------------------------------------------------------------------+
//| Close all positions of a given type |
//+------------------------------------------------------------------+
void ClosePositions(int type)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderMagicNumber() != MagicNumber) continue;
if (OrderType() != type) continue;
double closePrice = (type == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), closePrice, 3);
}
}
//+------------------------------------------------------------------+
//| Count open positions for this EA |
//+------------------------------------------------------------------+
int CountPositions()
{
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (!OrderSelect(i, SELECT_BY_POS)) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderMagicNumber() != MagicNumber) continue;
if (OrderType() <= OP_SELL) count++;
}
return count;
}
Key Concepts Explained
1. New Bar Detection
static datetime lastBar = 0;
if (Time[0] == lastBar) return;
lastBar = Time[0];
Without this, your EA fires on every tick (multiple times per second). We only want to evaluate once per candle close.
2. Using Bar Index 1, Not 0
We calculate MAs on bar[1] (the last closed bar), not bar[0] (the current forming bar). This prevents signals that appear and disappear as the current candle moves — a common beginner mistake.
3. Magic Number
This is your EA's fingerprint. If you run multiple EAs on the same pair, the magic number ensures each EA only manages its own trades.
4. Iterating Orders in Reverse
When closing orders, always loop from OrdersTotal() - 1 down to 0. Closing an order shifts the index of all orders above it. Forward iteration skips orders.
Before You Go Live
- Backtest first — Use MetaTrader's Strategy Tester with at least 2 years of data
- Demo account — Run for at least 1 month on demo before real money
- Risk management — Never risk more than 1-2% of your account per trade
- Spread awareness — This strategy works best on major pairs (EURUSD, USDJPY) with tight spreads
- Time filter — Consider adding a time filter to avoid low-liquidity hours
What's Next?
This is a starting point. In future posts, I'll cover:
- Adding RSI or Bollinger Band filters to reduce false signals
- Multi-timeframe confirmation
- Dynamic position sizing based on ATR
- Walk-forward optimization
The full source code is ready to compile in MetaEditor. Drop it in your MQL4/Experts folder and start testing.
Building trading systems one function at a time.
Top comments (0)