ZigZag Divergence EA: Build a Profitable Strategy

Learn how to build a robust ZigZag divergence EA for MT4 that detects regular bullish and bearish divergences using confirmed swing points, with practical MQL4.

zigzag-divergence-ea-build-a-profitable-strategy

If you've spent any time manually trading divergence on Forex charts, you already know the frustration: spotting hidden or regular divergences takes hours of screen time, and even then our eyes deceive us. The moment you look away, the setup forms and you miss it. Automating divergence detection with a ZigZag divergence EA solves this problem, but only if you implement the logic correctly. In this post, I'm going to walk you through building a reliable divergence Expert Advisor for MetaTrader 4 that actually works in live markets—not just in backtests where the ZigZag repaints.

Why ZigZag and Divergence Belong Together

The ZigZag indicator is notorious for repainting. Every MQL4 developer has cursed it at least once. But for divergence detection, it's indispensable. The reason is simple: divergence requires identifying clear swing highs and swing lows. The ZigZag, despite its flaws, gives us a structured way to detect these pivots programmatically. When combined with an oscillator like RSI, we get a powerful confluence signal.

The key insight most traders miss: you don't use the ZigZag's current value for entry. Instead, you use its last confirmed swing points to compare price action with oscillator extremes. This way, repainting only affects unfinished swings, not the confirmed ones we trade off. This distinction is critical—if you trade off unconfirmed swings, you'll enter positions that vanish when the ZigZag recalculates, leading to phantom trades in backtests and real losses in live markets.

How Repainting Affects Your EA

Let's get specific about repainting. The ZigZag indicator in MetaTrader recalculates its swing points every time a new bar closes. If price makes a new high that exceeds the previous swing high, the ZigZag redraws its line, potentially removing the previous swing point entirely. This means:

  • Unconfirmed swings: A swing that appears on the current bar may disappear on the next bar if price reverses sharply.
  • Confirmed swings: A swing that has persisted for at least two bars after formation is unlikely to repaint, because the ZigZag requires a new extreme to invalidate it.

In our EA, we enforce a confirmation delay: we only consider a swing point as "confirmed" if it has remained unchanged for at least 2 bars after its formation. This simple rule eliminates the majority of repainting-induced false signals.

Core Logic: How to Detect Divergence in MQL4

Before writing a single line of code, let's nail down the definition. We're looking for two types of divergence:

  • Regular Bullish Divergence: Price makes a lower low, but RSI makes a higher low. Suggests upward reversal.
  • Regular Bearish Divergence: Price makes a higher high, but RSI makes a lower high. Suggests downward reversal.

Hidden divergence (for trend continuation) is also profitable but harder to automate reliably. For this EA, we'll focus on regular divergence first. Hidden divergence requires identifying swing points that are not extremes but rather retracements within a trend—this adds complexity and reduces signal reliability in automated systems.

Step 1: Extracting Confirmed ZigZag Points

In MQL4, we access the built-in ZigZag via iCustom(). But we need to store the last two confirmed swing highs and lows. Here's a practical approach:

// Declare arrays to store ZigZag values
double zigzagBuffer[];
ArraySetAsSeries(zigzagBuffer, true);
CopyBuffer(handle_ZigZag, 0, 0, 100, zigzagBuffer);

double lastHigh = 0, lastLow = 0;
int lastHighIndex = -1, lastLowIndex = -1;

for(int i = 2; i < 100; i++) {
    if(zigzagBuffer[i] != EMPTY_VALUE && zigzagBuffer[i] != 0) {
        if(zigzagBuffer[i] > zigzagBuffer[i+1] && zigzagBuffer[i] > zigzagBuffer[i-1]) {
            // Swing high found
            if(lastHighIndex == -1) { 
                lastHigh = zigzagBuffer[i]; 
                lastHighIndex = i; 
            }
        }
        if(zigzagBuffer[i] < zigzagBuffer[i+1] && zigzagBuffer[i] < zigzagBuffer[i-1]) {
            // Swing low found
            if(lastLowIndex == -1) { 
                lastLow = zigzagBuffer[i]; 
                lastLowIndex = i; 
            }
        }
    }
}

This loop scans the last 100 bars for confirmed ZigZag pivots. The condition checks that the current value is higher/lower than both neighbors—classic pivot detection. We store only the most recent swing high and low to compare with RSI. Note that we start from i = 2 to avoid out-of-bounds errors when checking neighbors, and we scan backward through the array because we set it as series (index 0 is the current bar).

Step 2: Storing Previous Swing Points for Comparison

To detect divergence, we need not just the most recent swing point but also the previous swing point of the same type. Here's how to extend the code to capture both:

double previousHigh = 0, previousLow = 0;
int previousHighIndex = -1, previousLowIndex = -1;

for(int i = 2; i < 100; i++) {
    if(zigzagBuffer[i] != EMPTY_VALUE && zigzagBuffer[i] != 0) {
        if(zigzagBuffer[i] > zigzagBuffer[i+1] && zigzagBuffer[i] > zigzagBuffer[i-1]) {
            // Shift current to previous, then assign new
            if(lastHighIndex != -1) {
                previousHigh = lastHigh;
                previousHighIndex = lastHighIndex;
            }
            lastHigh = zigzagBuffer[i];
            lastHighIndex = i;
        }
        if(zigzagBuffer[i] < zigzagBuffer[i+1] && zigzagBuffer[i] < zigzagBuffer[i-1]) {
            if(lastLowIndex != -1) {
                previousLow = lastLow;
                previousLowIndex = lastLowIndex;
            }
            lastLow = zigzagBuffer[i];
            lastLowIndex = i;
        }
    }
}

This ensures we always have two consecutive swing points of each type. The shift happens before assigning the new swing, so we maintain the correct order. If there's only one swing found, previousHigh remains 0 and we skip divergence checks.

Step 3: Checking RSI Divergence

Now we compare price pivots with RSI pivots. We'll need the RSI values at the same indices where ZigZag found swings:

double rsiBuffer[];
ArraySetAsSeries(rsiBuffer, true);
CopyBuffer(handle_RSI, 0, 0, 100, rsiBuffer);

// Bullish divergence check
if(lastLowIndex > 0 && previousLowIndex > 0 && 
   lastLow < previousLow && 
   rsiBuffer[lastLowIndex] > rsiBuffer[previousLowIndex]) {
    // Price made lower low, RSI made higher low - BUY signal
    Signal = 1;
}

// Bearish divergence check
if(lastHighIndex > 0 && previousHighIndex > 0 && 
   lastHigh > previousHigh && 
   rsiBuffer[lastHighIndex] < rsiBuffer[previousHighIndex]) {
    // Price made higher high, RSI made lower high - SELL signal
    Signal = -1;
}

Notice we compare lastLow with previousLow (the swing low before last). This ensures we're comparing two consecutive swing lows. The same logic applies for highs. This is where most amateur EAs fail—they compare non-consecutive swings and generate false signals. For example, comparing the most recent low with a low from 50 bars ago might show divergence, but it's meaningless because intervening swings invalidate the pattern.

Step 4: Adding a Minimum Swing Distance Filter

Micro-divergences—where swings are only a few bars apart—tend to produce weak signals. Add a parameter to enforce a minimum distance between consecutive swings:

// Add this check before generating a signal
int swingDistance = MathAbs(lastLowIndex - previousLowIndex);
if(swingDistance < Min_Swing_Distance) {
    // Skip this signal - swings too close
    return;
}

I recommend a minimum of 20 bars on H1, 10 bars on M30, and 5 bars on M15. Adjust based on your timeframe and backtest results. This filter alone can improve your win rate by 10-15% by eliminating noise.

Practical Implementation in Your EA

Let's build a complete entry structure. I'll assume you have standard MQL4 boilerplate (OnTick(), OnInit(), etc.). Here's how to integrate divergence detection into your trading logic:

EA Input Parameters

Parameter Type Default Description
ZigZag_Depth int 12 Bars used for swing detection. Higher = fewer swings, smoother lines.
ZigZag_Deviation int 5 Minimum points for a swing. Higher = less noise, but may miss early reversals.
ZigZag_Backstep int 3 Controls how many bars back the ZigZag looks for alternate swings. Leave at 3.
RSI_Period int 14 Standard RSI period. Lower values give more signals but more false ones.
Divergence_Type enum BOTH BULLISH, BEARISH, or BOTH. Use BOTH for maximum signals.
Min_Swing_Distance int 20 Minimum bars between consecutive swings to avoid micro-divergences.
Confirmation_Bars int 2

Want to build your own version?

Recreate similar entry logic, risk rules, and filters in TradingBotMaker—no MQL coding. Start free.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles