MACD Wasn't the Problem. My M15 Logic Was.

MACD Wasn't the Problem. My M15 Logic Was.

21 June 2026, 12:28
Hoai Nam Trinh
0
24

In 2022 I nearly pulled MACD from one of my gold EAs entirely.

The setup was M15 crossover logic on XAUUSD. Backtests didn't look exciting but they looked stable enough to run on demo. Then I put it on a live feed with EC Markets RAW and watched it eat 14 consecutive losers across one flat Asian session. Spreads were all over the place. On the chart the histogram looked like it was moving - but on tick-level data it was flipping back and forth so fast that my EA was treating every tiny intrabar fluctuation as a new crossover. No new-bar guard. Reading current bar. The EA was just firing on noise.

I disabled it and blamed MACD for about two months.

That was the wrong call.

The indicator was not the main problem. The M15-only implementation was.

🌟 What I Got Wrong

The indicator didn't fail. My implementation was reading the current unclosed M15 bar and reacting to every state change as though it meant something. Every tick that nudged the histogram positive triggered the buy logic. Then three ticks later it flipped. Then back again.

MACD on M15 in a flat Asian session is noise. That's not the indicator's fault - it's the natural behavior of a momentum tool in a market with no momentum. The fix wasn't replacing MACD. It was understanding what context the signal needed before it meant anything.

Gold moves in layers. There's the macro structure visible on H4 or D1. There's the session-level momentum on H1 - whether money is actually coming in or just shuffling around. And then there's the M15 and M5 level where most people try to trade. A crossover on M15 in the wrong context isn't a setup. It's just a signal that a short-term average temporarily crossed a longer one in a market that doesn't care.

I started reading MACD across all three at the same time and only taking trades when they all pointed the same direction. Simple idea. The implementation was less clean.

🌟 The Change I Made in the Code

The first version of this logic was broken in a way I didn't catch immediately.

// Wrong — iMACD() returns a handle, not a value
double macd_h4 = iMACD(NULL, PERIOD_H4, 12, 26, 9, PRICE_CLOSE);

That doesn't work in MQL5. iMACD() returns an indicator handle - an integer reference. You have to retrieve buffer values separately using CopyBuffer(). More importantly, handles should be created once during initialization, not recreated on every tick.

The correct structure:

// OnInit — create handles once
int g_macd_h4  = INVALID_HANDLE;
int g_macd_h1  = INVALID_HANDLE;
int g_macd_m15 = INVALID_HANDLE;

int OnInit()
{
   g_macd_h4  = iMACD(_Symbol, PERIOD_H4,  12, 26, 9, PRICE_CLOSE);
   g_macd_h1  = iMACD(_Symbol, PERIOD_H1,  12, 26, 9, PRICE_CLOSE);
   g_macd_m15 = iMACD(_Symbol, PERIOD_M15, 12, 26, 9, PRICE_CLOSE);

   if(g_macd_h4  == INVALID_HANDLE ||
      g_macd_h1  == INVALID_HANDLE ||
      g_macd_m15 == INVALID_HANDLE)
   {
      Print("MACD handle creation failed");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

// Helper — read main line from closed bar
bool GetMACDMain(const int handle, const int shift, double &value)
{
   double buf[1];
   if(CopyBuffer(handle, 0, shift, 1, buf) != 1)
      return false;

   value = buf[0];
   return true;
}

Then the confluence check using confirmed closed bars (shift 1 and 2):

double h4_val1, h4_val2;
double h1_val1, h1_val2;
double m15_val1, m15_val2;

bool ok =  GetMACDMain(g_macd_h4,  1, h4_val1)  && GetMACDMain(g_macd_h4,  2, h4_val2)
        && GetMACDMain(g_macd_h1,  1, h1_val1)  && GetMACDMain(g_macd_h1,  2, h1_val2)
        && GetMACDMain(g_macd_m15, 1, m15_val1) && GetMACDMain(g_macd_m15, 2, m15_val2);

if(!ok) return; // CopyBuffer failed — connection drop, history not loaded, etc.

bool h4_rising  = (h4_val1  > h4_val2);
bool h1_rising  = (h1_val1  > h1_val2);
bool m15_rising = (m15_val1 > m15_val2) && (m15_val1 > 0.0);

bool entry_allowed = h4_rising && h1_rising && m15_rising;

A few things worth noting about this structure:

CopyBuffer() can fail. After a connection drop or if the timeframe history hasn't loaded yet, it returns -1. If you don't guard for that and just proceed with uninitialized values, you'll fire trades on garbage data. I've seen this cause spurious entries during VPS reconnects after a network blip at 3 AM.

Also - "rising" is not the same as "bullish." A rising MACD main line could still be below zero and below the signal line. My rule became: H4 and H1 need to be rising and above zero before the M15 trigger is considered. The code above is the skeleton - the real version has the zero-line check built in. I'm keeping this example minimal to show the structure without burying it.

The new-bar guard matters. Without it, the confluence check runs on every tick and you get multiple identical entry signals in the same bar. Standard approach: check IsNewBar() before running the full evaluation.

🌟 What It Actually Filtered Out

The version with H4/H1/M15 confluence produced far fewer signals than the original. Not 2–3 per session on average - more like 3–4 per week during typical market conditions. During strong directional moves, alignment appears more often. During choppy, range-bound weeks or pre-FOMC drift, you can go multiple sessions without a valid signal.

Most traders I've talked to find this frustrating. They interpret low signal count as the filter not working. Usually it's the opposite - the filter is working and the market genuinely doesn't have a clean trend context that day.

The losing trades I did take with the multi-timeframe version were mostly cleaner losses: I was positioned in the right general direction and got stopped out by a rotation or news spike. Not "fighting the trend from the wrong side while spreads widened" - which was the main character of the original M15-only losses.

🌟 Where It Still Got Wrecked

None of this stops a news spike.

Gold can drop a significant amount in under two minutes on Fed commentary or CPI surprises. A perfect alignment across all three timeframes at 13:58 UTC is irrelevant if a macro event fires at 14:00. I've had sessions where the confluence logic looked exactly right, the entry was placed, and then the position was immediately underwater $8–10 before the spread even normalized. The MACD wasn't wrong - the timing was wrong, and that's a different problem that requires a different solution (hard session lockout, news guard, time filter).

Lag is the other unavoidable one. MACD is a lagging indicator built on lagging moving averages. Waiting for H4, H1, and M15 to agree means you're entering after the move is already underway. During strong trend days, that's usually fine - there's often enough continuation. On days where the move was thin to begin with, you enter near the end and get stopped out near breakeven. I've accepted this as part of the approach. The alternative is earlier entries with higher false positive rates.

Over-optimization is a trap I fell into early. I spent a few weeks trying different parameter sets per timeframe - shorter period on M15, standard on H1, longer on H4 - trying to get the signals to line up more cleanly on historical data. The backtests looked better. The forward period looked like the parameters had never been tested. I went back to standard 12/26/9 across all three and accepted that the method isn't about finding the perfect settings. It's about using the same consistent measurement across different time horizons.

🚀 Adding a Faster Timing Layer

One structural problem with MACD confluence is that it tells you direction but not whether you're entering at the beginning or end of a local swing. H4 and H1 can be cleanly bullish while M15 has already run 60 points from the session low.

I started using a faster oscillator - in my case WaveTrend - specifically as a timing check on the lower timeframe. Not as a trend indicator. Just to answer one question: is this a fresh entry point or is the M15 already stretched?

MACD provides directional context. WaveTrend helps evaluate lower-timeframe entry timing.

When H4 and H1 MACD are rising and above zero, but WaveTrend on M15 is deep in overbought, I wait. I want to see WaveTrend pull back and reset before looking for the entry. If MACD confluence is still intact when it does, then I consider the trade. If MACD has started rolling over by the time WaveTrend resets, I skip it.

This doesn't always work. Strong trends stay overbought for a long time and waiting for a reset means missing a lot of the move. It's a trade-off I made toward entry quality over trade frequency. On gold at current price levels, a bad entry is expensive even on small lot sizes.

🔍 Execution Still Matters More Than You'd Think

The analytical part of this - reading three MACD buffers, checking confluence, waiting for WaveTrend reset - is actually the simpler part to get right once the code is solid.

What's harder to control is everything after the signal.

Technical alignment does not override poor execution conditions.

I run on a dedicated Windows VPS, roughly 15ms to my broker. Even at that latency, during active sessions I've gotten fills that came back noticeably later and at meaningfully different prices than expected. Not every trade, but enough that it matters on positions that are hunting a small move. If spread is running elevated at the moment of entry - which happens frequently during London open and before major data releases - the trade needs to overcome that cost before it's in profit. A signal that looks valid in an analytical sense can still be a net loser because execution cost destroyed the math.

This is why I track spread at entry, not just during backtesting. A position entered with a 3.5 pip spread is a fundamentally different trade than the same signal entered at 1.2 pips. The MACD said the same thing. The P&L outcome will often be different.

📢 Bottom Line

The "MACD is dead" take exists because a lot of people ran single-timeframe crossover bots and lost money. That's a reasonable observation about the method, not about the indicator.

What's inside the MACD - momentum direction, histogram acceleration, zero-line position - is still readable information about what's happening in the market. The problem is using it without context. An M15 signal with H4 momentum pointing the opposite direction isn't a signal you should be trading. The multi-timeframe approach mostly just enforces that.

It produces fewer trades. Some of the best sessions I've had were days when the filter kept me completely flat because market structure wasn't clean. Flat is not a loss.

Eight years in, including periods where I was fixing mistakes that had cost real money, the tools that stayed were the ones with clear rules for what they don't do. Multi-timeframe MACD filtering doesn't guarantee anything. What it does is give me a structured reason to stay out when conditions aren't right - and on gold, that's most of the time.

If the multi-timeframe logic above interests you: MACD Ultimate MTF PRO handles the cross-timeframe reads and closed-bar confirmation automatically. WaveTrend Clarity Pro covers the timing layer on the lower timeframe.

On broker execution behavior and spread realities: Understanding Gold Spread & Execution

Gold Algo Lab builds practical, risk-first MT5 tools for serious XAUUSD traders - shaped by 8 years of building and trading real systems, with no hype, no profit guarantees, and no unrealistic promises.