Problem Description
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
- Indicator: Standard MACD (Fast EMA 12, Slow EMA 26, Signal SMA 9).
- Buy Signal: The MACD Main line crosses above the Signal line.
- Sell Signal: The MACD Main line crosses below the Signal line.
- 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.
- 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
-
Shift Logic (No Repainting):
The code usesiMACD(..., 1)andiMACD(..., 2).Shift 0is the current candle (still moving).Shift 1is 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.
-
Pip Multiplier:
TheOnInitfunction calculatespipMultiplier. This automatically adjusts the Stop Loss and Take Profit calculations for 5-digit brokers (where 1 point = 0.1 pip) and 4-digit brokers. -
Reversal Logic:
InsideOnTick, before opening a Buy, the EA callsClosePositions(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. -
Magic Number:
TheInpMagicNumberensures 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
- Open MetaTrader 4.
- Press F4 to open the MetaEditor.
- Click New -> Expert Advisor (template) -> Name it "MACD_Crossover".
- Delete everything in the new file and paste the code provided above.
- Press F7 or click Compile. Ensure there are no errors in the "Errors" tab at the bottom.
- Go back to the main MT4 terminal.
- Open the Strategy Tester (Ctrl+R).
- Select "MACD_Crossover.ex4", choose a symbol (e.g., EURUSD), and a timeframe (e.g., H1).
- 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:
- ADX Filter: Only trade if ADX > 25 (indicating a strong trend).
- 200 EMA Filter: Only Buy if price is above the 200 EMA, only Sell if below.
- Zero Line Filter: Only Buy if the crossover happens below the zero line (oversold territory), and Sell if above.