Summary: A practical guide to coding a candlestick pattern scanner in MQL5. Covers pattern logic, ATR filters, and chart visualization, with a focus on avoiding common detection pitfalls.




I've always found it frustrating to manually scan charts for candlestick patterns. My eyes would start playing tricks on me after staring at screens for hours, and I'd inevitably miss signals or see things that weren't there. That's why I finally decided to code my own pattern recognition tool in MQL5. What I learned along the way surprised me—most of the "standard" definitions you find online are actually too rigid to work in real market conditions.

The Problem with "Textbook" Pattern Definitions



Most trading websites define a Doji as a candle where open and close are equal. But in reality, how often does that actually happen? On a typical EURUSD chart with 5-digit pricing, you might see a perfect Doji once in a blue moon. According to the MQL5 documentation on time series and price data, you need to work with the Open[], High[], Low[], and Close[] arrays to access historical candle data . But the docs don't tell you how to handle the "close enough" problem.

Here's the approach that actually works:

  • <strong>Define a tolerance range for Doji detection.</strong> Instead of checking for exact equality, I use the Average True Range (ATR) to set a dynamic threshold. The MQL5 article on candlestick programming suggests using ATR for adaptive thresholds because a fixed point value won't work across different assets or timeframes . My code looks like this:


  • ``cpp
    double atrValue = iATR(_Symbol, PERIOD_CURRENT, 14, 0);
    double dojiThreshold = atrValue * 0.1;

    if(MathAbs(Close[1] - Open[1]) <= dojiThreshold)
    {
    // We have a Doji
    Print("Doji detected on bar 1");
    }
    `

  • <strong>Implement the engulfing pattern with proper indexing.</strong> The index [0] refers to the current (most recent) candle, [1] is the previous one, and so on . For a bullish engulfing pattern:


  • `cpp
    // Bullish Engulfing: previous candle is bearish, current is bullish and engulfs it
    if(Close[1] < Open[1] && // previous candle is bearish
    Close[0] > Open[0] && // current candle is bullish
    Open[0] <= Close[1] && // current opens below previous close
    Close[0] >= Open[1]) // current closes above previous open
    {
    Print("Bullish Engulfing detected");
    }
    `

    The key insight I discovered through trial and error: the visual appearance of a pattern on the chart doesn't always match the textbook definition. For example, a bullish engulfing can still be valid even if the second candle doesn't perfectly close above the first candle's open—especially on higher timeframes where spreads and gaps come into play.

    The Missing Piece: ATR-Based Scaling



    Here's where my experience diverges from most online tutorials. Every pattern definition needs to be scaled to current volatility. A "small body" on a 1-minute chart during the Asian session might be 2 pips, but during London/NY overlap, that same 2-pip body is meaningless.

    I set up my pattern detection with ATR-based multipliers:

    `cpp
    double atr = iATR(_Symbol, PERIOD_CURRENT, 14, 0);
    double minBodySize = atr 0.15; // Minimum body size
    double maxBodySize = atr
    0.35; // Maximum for Doji/hammer

    // Hammer detection with dynamic thresholds
    if(MathAbs(Close[0] - Open[0]) <= maxBodySize &&
    (Low[0] - MathMin(Open[0], Close[0])) >= 2 MathAbs(Close[0] - Open[0]) &&
    (High[0] - MathMax(Open[0], Close[0])) <= minBodySize)
    {
    Print("Hammer detected with ATR-adjusted thresholds");
    }
    `

    Visualizing Patterns on the Chart



    I wanted the scanner to actually mark patterns on the chart so I could visually verify the detection. Using chart objects, I create labels above each detected pattern :

    `cpp
    string labelName = "Pattern_" + TimeToString(Time[0]);
    ObjectCreate(0, labelName, OBJ_TEXT, 0, Time[0], High[0] + (10
    _Point));
    ObjectSetText(labelName, "HAMMER", 10, "Arial", clrGreen);
    ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
    `

    This approach has been a game-changer for me. Instead of second-guessing whether I'm seeing a real pattern, I let the code do the heavy lifting and use my judgment to filter the signals against broader market context.

    A Warning About Backtesting



    One thing I've learned the hard way: never trust a backtest that doesn't account for pattern confirmation. A pattern appearing at the close of a candle might look great, but you need to wait for confirmation. I add a simple filter:

    `cpp
    // Only trigger if the next candle confirms the pattern
    if(Close[1] > Open[1] && // bullish confirmation
    Close[0] < Open[0]) // pattern candle is bearish
    {
    // Don't trigger on the pattern candle itself
    // Wait for confirmation on the next bar
    }
    ``

    Reference: MQL5 Documentation on Time Series and Candlestick Patterns (docs.mql5.com); MQL5 Articles on Candlestick Pattern Programming (mql5.com) .

    本文首发于FXEAR.com,原创内容,未经授权禁止转载。