🚀 AI One-Click Generation of mql4 Strategy Code

How to Build a Moving Average Crossover Expert Advisor in Mql4? (Full Code Included)

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

Problem Description

Can you write a trading bot that buys when a fast moving average crosses above a slow one and sells when it crosses below?

Solution

Here is a complete, robust, and fully commented MQL4 Expert Advisor (trading bot) based on the Moving Average Crossover strategy.

Strategy Logic

  1. Buy Signal: The Fast Moving Average crosses above the Slow Moving Average.
  2. Sell Signal: The Fast Moving Average crosses below the Slow Moving Average.
  3. Execution: The bot checks values on closed bars (Shift 1 and Shift 2) to prevent "repainting" or flickering signals.
  4. Position Management: When a signal occurs, the bot closes any opposing trades before opening a new one (Reversal Strategy).

MQL4 Code

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

//--- Input Parameters
input string   Inp_Settings   = "--- Strategy Settings ---"; // .
input int      FastMA_Period  = 12;          // Fast MA Period
input int      SlowMA_Period  = 26;          // Slow MA Period
input int      MA_Method      = MODE_EMA;    // MA Method (0:SMA, 1:EMA, 2:SMMA, 3:LWMA)
input int      MA_Price       = PRICE_CLOSE; // Applied Price (0:Close)

input string   Inp_Risk       = "--- Risk Management ---"; // .
input double   LotSize        = 0.1;         // Fixed Lot Size
input int      StopLoss       = 0;           // Stop Loss in points (0 = No SL)
input int      TakeProfit     = 0;           // Take Profit in points (0 = No TP)
input int      Slippage       = 3;           // Max Slippage in points
input int      MagicNumber    = 123456;      // Magic Number to identify orders

//--- Global Variables
double pPoint; // Adjusted point value for 3/5 digit brokers

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

   // Basic validation
   if(FastMA_Period >= SlowMA_Period)
   {
      Print("Error: Fast MA Period must be smaller than Slow MA Period.");
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // We only trade on the opening of a new bar to ensure the crossover is confirmed
   // However, for simplicity in backtesting, we check the crossover of the *previous* closed bars.
   // This logic runs every tick but acts based on closed bar data (index 1 and 2).

   // 1. Get Moving Average Values
   // Shift 1 = Last closed candle, Shift 2 = The candle before that
   double fastCurr = iMA(NULL, 0, FastMA_Period, 0, MA_Method, MA_Price, 1);
   double slowCurr = iMA(NULL, 0, SlowMA_Period, 0, MA_Method, MA_Price, 1);
   
   double fastPrev = iMA(NULL, 0, FastMA_Period, 0, MA_Method, MA_Price, 2);
   double slowPrev = iMA(NULL, 0, SlowMA_Period, 0, MA_Method, MA_Price, 2);

   // 2. Check for Crossovers
   // Cross Up: Previous Fast was below/equal Slow, Current Fast is above Slow
   bool isCrossUp   = (fastPrev <= slowPrev) && (fastCurr > slowCurr);
   
   // Cross Down: Previous Fast was above/equal Slow, Current Fast is below Slow
   bool isCrossDown = (fastPrev >= slowPrev) && (fastCurr < slowCurr);

   // 3. Execution Logic
   if(isCrossUp)
   {
      // If we have a Buy signal, close existing Sells first
      if(CountOrders(OP_SELL) > 0) CloseOrders(OP_SELL);
      
      // Open Buy if we don't already have one
      if(CountOrders(OP_BUY) == 0) OpenTrade(OP_BUY);
   }
   
   if(isCrossDown)
   {
      // If we have a Sell signal, close existing Buys first
      if(CountOrders(OP_BUY) > 0) CloseOrders(OP_BUY);
      
      // Open Sell if we don't already have one
      if(CountOrders(OP_SELL) == 0) OpenTrade(OP_SELL);
   }
}

//+------------------------------------------------------------------+
//| Helper: Open a Trade                                             |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
   double price, sl, tp;
   color cmdColor;

   if(type == OP_BUY)
   {
      price = Ask;
      sl = (StopLoss > 0) ? price - StopLoss * pPoint : 0;
      tp = (TakeProfit > 0) ? price + TakeProfit * pPoint : 0;
      cmdColor = clrBlue;
   }
   else // OP_SELL
   {
      price = Bid;
      sl = (StopLoss > 0) ? price + StopLoss * pPoint : 0;
      tp = (TakeProfit > 0) ? price - TakeProfit * pPoint : 0;
      cmdColor = clrRed;
   }

   // Normalize prices
   if(sl > 0) sl = NormalizeDouble(sl, Digits);
   if(tp > 0) tp = NormalizeDouble(tp, Digits);

   // Send Order
   int ticket = OrderSend(Symbol(), type, LotSize, price, Slippage, sl, tp, "MA Cross Bot", MagicNumber, 0, cmdColor);
   
   if(ticket < 0)
   {
      Print("OrderSend failed with error #", GetLastError());
   }
}

//+------------------------------------------------------------------+
//| Helper: Close Orders of a specific type                          |
//+------------------------------------------------------------------+
void CloseOrders(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // Check Symbol, Magic Number and Type
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
         {
            bool res = false;
            if(OrderType() == OP_BUY)
               res = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrRed);
            else if(OrderType() == OP_SELL)
               res = OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrBlue);
            
            if(!res) Print("OrderClose failed: ", GetLastError());
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Count 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() == MagicNumber && OrderType() == type)
         {
            count++;
         }
      }
   }
   return(count);
}
//+------------------------------------------------------------------+

How to Install and Run

  1. Open MetaTrader 4.
  2. Press F4 to open the MetaEditor.
  3. Click New -> Expert Advisor (template) -> Name it MA_Crossover.
  4. Delete everything in the new file and paste the code 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 window.
  7. Find MA_Crossover in the Navigator panel under Expert Advisors.
  8. Drag it onto a chart.
  9. Ensure AutoTrading is enabled in the top toolbar.

Q&A

Q: Why does the bot use iMA(..., 1) and iMA(..., 2) instead of 0?
A: Index 0 represents the current forming candle. The price moves constantly, causing the MA to wiggle up and down. If we use 0, the bot might buy and sell multiple times within one minute as the lines touch and separate. Using 1 (the last closed candle) ensures the crossover is confirmed and permanent.

Q: What happens if I set Stop Loss to 0?
A: The bot will run in "Reversal Mode." It will hold a Buy position until a Sell signal occurs, at which point it closes the Buy and opens a Sell.

Q: Does this work on 5-digit brokers?
A: Yes. The code includes a pPoint calculation in OnInit which automatically adjusts the Stop Loss and Take Profit calculations for 3-digit (JPY) and 5-digit brokers.