Problem Description
Solution
Here is a complete, robust, and fully commented MQL4 Expert Advisor (trading bot) based on the Moving Average Crossover strategy.
Strategy Logic
- Buy Signal: The Fast Moving Average crosses above the Slow Moving Average.
- Sell Signal: The Fast Moving Average crosses below the Slow Moving Average.
- Execution: The bot checks values on closed bars (Shift 1 and Shift 2) to prevent "repainting" or flickering signals.
- 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
- Open MetaTrader 4.
- Press
F4to open the MetaEditor. - Click New -> Expert Advisor (template) -> Name it
MA_Crossover. - Delete everything in the new file and paste the code above.
- Press
F7or click Compile. Ensure there are no errors in the "Errors" tab at the bottom. - Go back to the main MT4 window.
- Find
MA_Crossoverin the Navigator panel under Expert Advisors. - Drag it onto a chart.
- 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.