DEV Community

Alex Turner
Alex Turner

Posted on

Building Your First Forex Trading Bot with MQL4: A Practical Guide

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;
}
Enter fullscreen mode Exit fullscreen mode

Key Concepts Explained

1. New Bar Detection

static datetime lastBar = 0;
if (Time[0] == lastBar) return;
lastBar = Time[0];
Enter fullscreen mode Exit fullscreen mode

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

  1. Backtest first — Use MetaTrader's Strategy Tester with at least 2 years of data
  2. Demo account — Run for at least 1 month on demo before real money
  3. Risk management — Never risk more than 1-2% of your account per trade
  4. Spread awareness — This strategy works best on major pairs (EURUSD, USDJPY) with tight spreads
  5. 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)