🚀 AI One-Click Generation of mql4 Strategy Code

How to Build a MACD Crossover Expert Advisor in Mql4?

mql4 | Published on: 2025/11/26
The following content is generated by EasyQuant .

Problem Description

Write a trading algorithm based on the MACD indicator's signal line crossover.

Solution

Here is a complete, robust, and well-structured Expert Advisor (EA) for MetaTrader 4 based on the MACD Signal Line Crossover strategy.

Strategy Logic

  1. Indicator: Standard MACD (Fast EMA 12, Slow EMA 26, Signal SMA 9).
  2. Buy Signal: The MACD Main line crosses above the Signal line.
  3. Sell Signal: The MACD Main line crosses below the Signal line.
  4. Execution: The EA checks for crossovers on closed bars (Shift 1 and Shift 2) to ensure the signal is confirmed and does not "repaint" during the current candle formation.
  5. Risk Management: Includes Stop Loss, Take Profit, and a Magic Number to manage specific trades. It also closes opposite positions when a reversal signal occurs.

MQL4 Code

//+------------------------------------------------------------------+
//|                                                MACD_Crossover.mq4|
//|                        Copyright 2023, Quantitative Trading AI   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input double   InpLots          = 0.1;      // Lot Size
input int      InpStopLoss      = 50;       // Stop Loss in pips (0 = no SL)
input int      InpTakeProfit    = 100;      // Take Profit in pips (0 = no TP)
input int      InpMagicNumber   = 123456;   // Magic Number to identify orders
input int      InpSlippage      = 3;        // Maximum Slippage in pips

//--- MACD Settings
input int      InpMACDFast      = 12;       // Fast EMA Period
input int      InpMACDSlow      = 26;       // Slow EMA Period
input int      InpMACDSignal    = 9;        // Signal SMA Period

//--- Global Variables
double pipMultiplier;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Adjust pip multiplier for 3 or 5 digit brokers (Jpy vs others)
   if(Digits == 3 || Digits == 5) pipMultiplier = 10 * Point;
   else pipMultiplier = Point;

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up if necessary
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Check if we have enough bars to calculate
   if(Bars < 100) return;

   // --- 1. Get MACD Data ---
   // We look at Shift 1 (Previous closed candle) and Shift 2 (Candle before that)
   // This prevents "repainting" signals on the current forming bar.
   
   // Main Line (Mode 0)
   double macdMain1 = iMACD(_Symbol, _Period, InpMACDFast, InpMACDSlow, InpMACDSignal, PRICE_CLOSE, MODE_MAIN, 1);
   double macdMain2 = iMACD(_Symbol, _Period, InpMACDFast, InpMACDSlow, InpMACDSignal, PRICE_CLOSE, MODE_MAIN, 2);

   // Signal Line (Mode 1)
   double macdSig1  = iMACD(_Symbol, _Period, InpMACDFast, InpMACDSlow, InpMACDSignal, PRICE_CLOSE, MODE_SIGNAL, 1);
   double macdSig2  = iMACD(_Symbol, _Period, InpMACDFast, InpMACDSlow, InpMACDSignal, PRICE_CLOSE, MODE_SIGNAL, 2);

   // --- 2. Determine Signals ---
   
   // Buy Cross: Main was below Signal at shift 2, and is above Signal at shift 1
   bool buySignal  = (macdMain2 < macdSig2) && (macdMain1 > macdSig1);
   
   // Sell Cross: Main was above Signal at shift 2, and is below Signal at shift 1
   bool sellSignal = (macdMain2 > macdSig2) && (macdMain1 < macdSig1);

   // --- 3. Execute Trade Logic ---
   
   if(buySignal)
   {
      // Close any existing Sell positions first (Reversal logic)
      ClosePositions(OP_SELL);
      
      // Open Buy if no Buy exists
      if(CountOrders(OP_BUY) == 0)
      {
         OpenTrade(OP_BUY);
      }
   }
   
   if(sellSignal)
   {
      // Close any existing Buy positions first (Reversal logic)
      ClosePositions(OP_BUY);
      
      // Open Sell if no Sell exists
      if(CountOrders(OP_SELL) == 0)
      {
         OpenTrade(OP_SELL);
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Open a Trade                                             |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
   double price, sl, tp;
   color clr;
   
   if(type == OP_BUY)
   {
      price = Ask;
      sl = (InpStopLoss > 0) ? price - InpStopLoss * pipMultiplier : 0;
      tp = (InpTakeProfit > 0) ? price + InpTakeProfit * pipMultiplier : 0;
      clr = clrBlue;
   }
   else // OP_SELL
   {
      price = Bid;
      sl = (InpStopLoss > 0) ? price + InpStopLoss * pipMultiplier : 0;
      tp = (InpTakeProfit > 0) ? price - InpTakeProfit * pipMultiplier : 0;
      clr = clrRed;
   }

   // Normalize prices to prevent Error 130 (Invalid Stops)
   price = NormalizeDouble(price, Digits);
   sl    = NormalizeDouble(sl, Digits);
   tp    = NormalizeDouble(tp, Digits);

   int ticket = OrderSend(_Symbol, type, InpLots, price, InpSlippage, sl, tp, "MACD Cross", InpMagicNumber, 0, clr);
   
   if(ticket < 0)
   {
      Print("OrderSend failed with error #", GetLastError());
   }
}

//+------------------------------------------------------------------+
//| Helper: Close specific position types                            |
//+------------------------------------------------------------------+
void ClosePositions(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
         {
            if(OrderType() == type)
            {
               double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
               bool res = OrderClose(OrderTicket(), OrderLots(), closePrice, InpSlippage, clrGray);
               if(!res) Print("OrderClose failed: ", GetLastError());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Count open orders of a specific type                     |
//+------------------------------------------------------------------+
int CountOrders(int type)
{
   int count = 0;
   for(int i = 0; i < OrdersTotal(); i++)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
         {
            if(OrderType() == type) count++;
         }
      }
   }
   return count;
}
//+------------------------------------------------------------------+

Key Features of This Code

  1. Shift Logic (No Repainting):
    The code uses iMACD(..., 1) and iMACD(..., 2).

    • Shift 0 is the current candle (still moving).
    • Shift 1 is the last completed candle.
    • By comparing Shift 1 and Shift 2, we confirm that the crossover actually finished and locked in. This prevents the EA from opening a trade and then having the signal disappear if the price retraces before the candle closes.
  2. Pip Multiplier:
    The OnInit function calculates pipMultiplier. This automatically adjusts the Stop Loss and Take Profit calculations for 5-digit brokers (where 1 point = 0.1 pip) and 4-digit brokers.

  3. Reversal Logic:
    Inside OnTick, before opening a Buy, the EA calls ClosePositions(OP_SELL). This ensures that if the market reverses, you don't hold a losing Sell position while opening a new Buy position (hedging), unless you specifically remove that line of code.

  4. Magic Number:
    The InpMagicNumber ensures this EA only manages its own trades. You can run this EA on EURUSD and GBPUSD simultaneously (with different Magic Numbers if desired, though usually unique per symbol is automatic in this logic) without them interfering with each other.

How to Install and Test

  1. Open MetaTrader 4.
  2. Press F4 to open the MetaEditor.
  3. Click New -> Expert Advisor (template) -> Name it "MACD_Crossover".
  4. Delete everything in the new file and paste the code provided above.
  5. Press F7 or click Compile. Ensure there are no errors in the "Errors" tab at the bottom.
  6. Go back to the main MT4 terminal.
  7. Open the Strategy Tester (Ctrl+R).
  8. Select "MACD_Crossover.ex4", choose a symbol (e.g., EURUSD), and a timeframe (e.g., H1).
  9. Click Start to backtest.

Q&A: Quantitative Trading with MQL4

Q: Why do we use NormalizeDouble for Stop Loss and Take Profit?
A: MetaTrader 4 requires prices in trade requests to match the exact precision (digits) of the symbol. If a calculated price has floating-point errors (e.g., 1.1234500001), the server may reject the order with "Invalid Price" or "Invalid Stops". NormalizeDouble rounds the value to the correct number of decimal places.

Q: Can this EA run on multiple timeframes simultaneously?
A: Yes. Because the logic uses _Period (current chart period) and _Symbol (current chart symbol), you can attach this EA to an H1 chart and an M15 chart of the same asset simultaneously. However, you should assign a unique InpMagicNumber to each instance to track performance separately.

Q: How can I filter false signals in a ranging market?
A: MACD is a trend-following indicator and often produces losses in sideways markets. To improve this algorithm quantitatively, you could add a filter such as:

  1. ADX Filter: Only trade if ADX > 25 (indicating a strong trend).
  2. 200 EMA Filter: Only Buy if price is above the 200 EMA, only Sell if below.
  3. Zero Line Filter: Only Buy if the crossover happens below the zero line (oversold territory), and Sell if above.