I was staring at my chart one Tuesday afternoon, watching price action on EURUSD H1. A textbook hammer formed right at a major support level. I knew it was a hammer because it had that long lower shadow, tiny body, virtually no upper shadow. But here's the thing—I've been burned by hammers before. They look great in hindsight, but in real-time trading, half of them are just noise. That's when I decided to stop relying on my eyes and start coding a proper detection system.
The problem with most beginner MQL5 pattern detectors is that they use hard-coded thresholds. Body less than 10 points? Lower shadow twice the body? That works on EURUSD during London session, but completely falls apart on GBPJPY during Asian hours, or on a daily chart versus a 5-minute chart. I learned this the hard way after backtesting a detector I threw together and watching it flag about 40% false positives.
The ATR Epiphany
The official MQL5 documentation (docs.mql5.com) covers the
iATR() function for Average True Range, but what I haven't seen spelled out clearly is how to use it as a dynamic filter for candlestick pattern detection. The docs show you how to calculate ATR, but they don't walk you through using it to replace fixed-point thresholds with volatility-adjusted ones.Here's the logic I settled on after three weeks of trial and error: instead of saying "body < 10 points," I say "body < 0.3 ATR(14)." Instead of "lower shadow > 2 body," I say "lower shadow >= 2 body AND lower shadow > 0.5 ATR(14)." That second condition prevents the detector from flagging hammers in ultra-low volatility environments where even a 2:1 shadow-to-body ratio might only be a few points—which is just market noise.
The Code That Actually Works
Let me walk you through the final version that's been running on my demo account for two months:
``
mql5
//+------------------------------------------------------------------+
//| Hammer Pattern Detection Function |
//+------------------------------------------------------------------+
bool IsHammer(int shift, double atrValue)
{
// Get OHLC data using explicit symbol and period calls
double open = iOpen(_Symbol, _Period, shift);
double high = iHigh(_Symbol, _Period, shift);
double low = iLow(_Symbol, _Period, shift);
double close = iClose(_Symbol, _Period, shift);
double body = MathAbs(close - open);
double lowerShadow = MathMin(open, close) - low;
double upperShadow = high - MathMax(open, close);
double range = high - low;
// Basic structure check
if(body == 0 || range == 0) return false;
// ATR-dynamic conditions
if(body > 0.3 atrValue) return false; // Body too large
if(lowerShadow < 2 body) return false; // Shadow too short
if(lowerShadow < 0.5 atrValue) return false; // Shadow too small in absolute terms
if(upperShadow > 0.3 body) return false; // Upper shadow exists
// Entity must be in the upper part of the candle
double lowerBody = MathMin(open, close);
if(lowerBody > low + 0.3 range) return false;
return true;
}
`
The thing I want to emphasize here is that third condition—lowerShadow < 0.5 atrValue. I added this after reviewing a string of false signals on GBPJPY during low-volatility periods. The official MQL5 documentation on ATR doesn't mention this use case, but in practice, it's the difference between a robust detector and one that fires on every minor wick.
Trend Context: The Dealbreaker
Even with ATR filtering, I was still getting hammers in sideways markets that went nowhere. That's when I added trend validation. According to the MQL5 community article series on candlestick programming, a hammer only has real significance when it appears at the end of a downtrend. A hammer in a ranging market is just a candle with a long shadow.
Here's my trend filter:
`mql5
bool IsDowntrend(int shift)
{
double maCurrent = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, shift);
double maPrev = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE, shift + 5);
return (maCurrent < maPrev);
}
`
The shift + 5 instead of shift + 1 gives a smoother slope reading and reduces noise. I found that using a 5-bar lookback for the MA comparison filters out minor whipsaws that would otherwise generate false signals.
The Reusable Library Approach
One thing I've learned from working on this is that you're better off building a pattern library than writing one-off functions. The MQL5 article "From Novice to Expert: Programming Candlesticks" makes a strong case for this approach. I now have a single include file with functions for hammer, shooting star, engulfing, doji, and morning star. Each function accepts an ATR value as a parameter, so the volatility adjustment is consistent across all patterns.
This matters because when I backtest a strategy, I want to know that the pattern recognition logic is consistent. If I tweak the ATR multiplier for hammers but forget to update the engulfing function, my backtest results become meaningless.
The Hidden Gotcha
Here's something the official documentation doesn't tell you: when you're testing your pattern detector on historical data using the Strategy Tester, the iATR() function behaves differently than it does on a live chart. In the tester, ATR is calculated based on the available history at the time of the test, but if you're running optimization with multiple threads, the ATR values can get desynced. My workaround was to calculate ATR manually using the CopyRates()` function within the same loop that does the pattern detection, ensuring that both sets of calculations use identical data.Reference: MetaQuotes MQL5 Documentation (docs.mql5.com) for iATR and iMA functions; MQL5 Community Article "From Novice to Expert: Programming Candlesticks" (www.mql5.com).
本文首发于FXEAR.com,原创内容,未经授权禁止转载。