divergence-signal-alerts/README.md
Allan Niñal Allan Niñal 953af40e03 Free source regenerated: Pro-only code removed from the engine, debug log added
Generated by the publish tool from the private source; all publish gates pass, compile 0 errors 0 warnings on MT5 and MT4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:12:09 +08:00

Divergence Signal Alerts logo

Divergence Signal Alerts (MetaTrader 5 and MetaTrader 4)

A free indicator that finds RSI and MACD divergences on the chart, draws a line between the two price pivots and puts an arrow on the bar where the divergence became known, on closed bars only, with an alert for each one. Source for both editions, a Python reference of the maths, and the tests that were used to check that the MT5 and MT4 code, the Python reference and each other all agree.

Website: allanninal.dev/mql5 (indicators) · MT4 editions · allanninal.dev · Contact: message me on LinkedIn.

Divergence Signal Alerts on XAUUSD H1

It is an indicator, not a strategy. It draws lines and arrows and places no trades. Nothing here is a profit claim (see Honest limits).

Get it on the MQL5 Market

Edition Platform Price Link
Divergence Signal Alerts MT5 free https://www.mql5.com/en/market/product/201785
Divergence Signal Alerts MT4 MT4 free https://www.mql5.com/en/market/product/202356

The Market builds are the same calculation as the source in this repository.

Contents

  1. What it is and who it is for
  2. Why it was built
  3. How it works
  4. Non-repainting: the proof and how it was tested
  5. Architecture and buffers
  6. Inputs, alerts, install and compile
  7. Run the Python reference and the tests
  8. Honest limits
  9. Licence and credits

What it is and who it is for

What: classic oscillator divergence as a native MetaTrader indicator. It finds swing lows and highs on price, compares two consecutive swings with the RSI and the MACD histogram, and when price and oscillator disagree it draws a line between the two price pivots and an arrow (bullish below the candle, bearish above) on the bar where the divergence became known. Regular divergence (a reversal hint) is on by default; hidden divergence (a continuation hint) is optional. Alerts: pop-up, sound, push, e-mail.

For: traders who use divergence and want it marked without repainting, and who want to know that the arrow they saw will still be there tomorrow. Also for developers who want a small, tested indicator with documented buffers to read from an EA with iCustom.

Not for: anyone who expects a divergence to call a turn. It marks where price and oscillator disagreed.

how it works regular and hidden
alerts starting settings

Image notes: the charts are drawn by the Python reference on broker history (the same numbers the indicator shows); the alert pop-up is a mock-up around the indicator's real text. They are illustrations, not terminal captures. MT4 versions are in docs/images/mt4/.

Why it was built

Divergence is among the most-used classic methods. The motivations, stated with the evidence we actually have:

  • A native, readable implementation. Hundreds of divergence indicators exist on the MQL5 Market (we counted about 240 MT5 indicators in the October 2026 research), many closed-source, and the most popular single-oscillator ones sell at about 30 USD. A free, documented one is a useful reference.
  • Trust in the arrows. The same research found repeated complaints about arrow indicators that repaint, and a 2026-07 request for "arrows confirmed on bar close". A divergence needs bars after the pivot to exist, so this indicator draws its arrow on the bar that confirms it and never moves anything into the past.
  • Honest scale of demand. Named classic methods are requested only rarely on freelance boards (divergence among them), while forum users do ask for divergence versions of their favourite oscillators. So this is a free tool that builds a track record, not a claim of mass demand.
  • Provable behaviour. Every number in this README comes from an executed check, listed below, not from a promise.
mindmap
  root((Divergence Signal Alerts))
    Problem
      Divergence indicators that repaint or redraw the past
      Alerts that fire twice or during history load
    Design rules
      Arrow on the confirming closed bar
      Nothing drawn into the past, nothing removed
      One alert per bar
      Same maths on MT5 MT4 and Python
    Proof
      Python reference
      Bar by bar parity
      Tester repaint probe
      Market validation

How it works

The maths, step by step

Let H, L, C be a bar's high, low and close, k the index of the bar just closed (oldest = 0), left and right the pivot bars (defaults 5 and 3).

  1. Oscillators, computed on every closed bar:

    • RSI (Wilder, period 14): gains and losses smoothed with an RMA seeded with the simple average of the first 14 changes; RSI = 100 - 100 / (1 + gain / loss) (100 when the loss is 0, 0 when the gain is 0).
    • MACD histogram: EMA(12) - EMA(26) minus the EMA(9) of that line. Every EMA is seeded with the simple average of its first n values, then EMA = EMA + 2/(n+1) * (x - EMA).
  2. Pivots on price. Bar p = k - right is a pivot low if its low is below the low of each of the left bars before it and not above the low of any of the right bars after it. A pivot high mirrors it. A pivot is therefore known right bars after it formed: bar k is its confirmation bar.

  3. Compare with the earlier pivots. The engine remembers the last four pivots of each side. At a new pivot low p, the earlier pivot lows q are tried, nearest first, if they are between min and max bars back (defaults 5 and 60):

    • regular bullish: low[p] < low[q] and osc[p] > osc[q] (price lower low, oscillator higher low)
    • hidden bullish: low[p] > low[q] and osc[p] < osc[q] (price higher low, oscillator lower low)

    A new pivot high mirrors it for bearish divergence (regular: price higher high, oscillator lower high; hidden: price lower high, oscillator higher high).

  4. Clean lines (on by default): no bar strictly between q and p may cross the straight line from q to p, on price (lows must stay above the line for a bullish pair, highs below it for a bearish pair) or on the oscillator being tested. A pair that fails is skipped.

  5. Result. The first q where at least one oscillator (or both RSI and MACD, if Only when RSI and MACD both diverge is on) diverges is the divergence. The arrow goes on the confirmation bar k at its low (bullish) or high (bearish), the line joins the two price pivots, and a tooltip names the oscillators that agreed. A bar can be both a pivot low and a pivot high; the low is tested first.

The forming bar is never fed to the engine, so nothing on a closed bar changes afterwards.

Per-bar algorithm

flowchart TD
    A[Next closed bar k] --> B[Update RSI and MACD histogram]
    B --> C[p = k - right, the bar that could now be confirmed]
    C --> D{p is a pivot low?}
    D -- yes --> E[Try earlier pivot lows q, nearest first,<br/>min to max bars back]
    D -- no --> H{p is a pivot high?}
    E --> F{Regular or hidden pattern on price,<br/>clean line, enough oscillators agree?}
    F -- yes --> G[Bullish divergence found]
    F -- no --> R1[Remember p as a pivot low] --> H
    G --> R2[Remember p as a pivot low] --> I
    H -- yes --> J[Same test mirrored on pivot highs]
    H -- no --> I[No arrow]
    J --> K{Divergence found?}
    K -- yes --> L[Bearish divergence found]
    K -- no --> I
    G --> M[Arrow on bar k, line between the pivots]
    L --> M
    M --> N[Alert once per bar, only when live]

Signal states

stateDiagram-v2
    [*] --> Scanning
    Scanning --> Scanning: no pivot, or pivot without divergence
    Scanning --> Confirmed: a pivot is confirmed and diverges (arrow, line, alert)
    Confirmed --> Scanning: next closed bar (the arrow stays where it is)

Alert flow

sequenceDiagram
    participant T as Terminal
    participant I as Divergence indicator
    participant H as Alert helper
    participant U as You
    T->>I: OnCalculate (history load, prev_calculated = 0)
    I->>I: rebuild the engine from the last InpHistory closed bars
    Note over I,H: no alert during the history load
    T->>I: OnCalculate (new bar closes, prev_calculated > 0)
    I->>I: feed the bar that just closed
    alt it confirmed a divergence and its time is newer than the last alert
        I->>H: SignalAlertMessage(text)
        alt not in the tester
            H->>U: pop-up (optional sound, push, e-mail)
        end
    else nothing confirmed or already alerted
        I-->>T: nothing sent
    end

How often it signals (measured)

Divergence arrows (bullish plus bearish) per month with the default inputs (RSI and MACD, regular, clean lines), on closed bars of broker history, October 2023 to October 2026. Source: docs/data/events-per-month.json. This counts arrows only. It says nothing about profit.

xychart-beta
    title "Divergence arrows per month by timeframe (EURUSD and XAUUSD)"
    x-axis ["M15", "H1", "H4", "D1"]
    y-axis "arrows per month" 0 --> 50
    line [42.17, 9.69, 2.64, 0.39]
    line [36.22, 7.94, 2.44, 0.25]

First line EURUSD, second XAUUSD.

xychart-beta
    title "XAUUSD divergence arrows per month, default inputs"
    x-axis ["M15", "H1", "H4", "D1"]
    y-axis "arrows per month" 0 --> 50
    bar [36.22, 7.94, 2.44, 0.25]

Non-repainting: the proof and how it was tested

"Non-repainting" is easy to claim. Here is what was actually run. Raw result files are in tests/results/. The tests below were re-run on exactly the code in this repository.

1. Three implementations, compared bar by bar. The Python reference (tests/reference.py) is a separate implementation of the same maths. The MT5 and MT4 indicators were dumped bar by bar (arrows, arrow types, RSI, MACD histogram) in the MetaTrader Strategy Tester, and tests/check_parity.py compared each dump with the reference. Pass means: RSI and MACD within 1e-9 relative on every scanned bar, and arrows of the same type on exactly the same bars.

MT5 MT4
dumps compared 36 (4 symbols x M15/H1/H4 x 3 input sets) 37 (same 36 + one every-tick EURUSD H1)
bars scanned 84,000 768,247
arrows compared (bullish + bearish) 3,002 23,757
result ALL PASS ALL PASS

Symbols: EURUSD, XAUUSD, GBPJPY, BTCUSD. Input sets: defaults; hidden divergence on with both oscillators required; RSI only with clean lines off, pivot bars 3 and 2 and a 40-bar maximum distance. Source: tests/results/parity-mt5-and-mt4.txt. The indicator scans the last 2,000 (or 3,000) closed bars of the chart, which is why the MT5 count is small; the MT4 test scans the whole test history.

xychart-beta
    title "Arrows compared, Python vs indicator (all 0 differences)"
    x-axis ["Defaults MT5", "Hidden+both MT5", "RSI only MT5", "Defaults MT4", "Hidden+both MT4", "RSI only MT4"]
    y-axis "arrows compared" 0 --> 18000
    bar [438, 222, 2342, 4924, 2225, 16608]

(MT4 "Defaults" is 4,850 arrows over 12 runs plus 74 in the every-tick run = 4,924.)

2. Repaint probe. An EA records which signal buffers are set on each bar at the moment it closes, then reads the same bars again at the end and counts any value that changed. It also counts any signal ever shown on the still-forming bar.

Run Closed bars checked Signals seen at close Changed afterwards Signals on the live bar
MT5, XAUUSD H1, every tick, 91,052,717 ticks 4,406 75 0 0
MT4, 37 runs combined 755,574 23,429 0 0

Source: tests/results/repaint-summary.md. The MT5 repaint probe was run once (XAUUSD H1, default inputs); the MT4 probe ran on every dump run.

3. Market validation. Both Market builds passed the MQL5 Market's automated product validation before publication (this is the Market's own check, not part of this repository).

flowchart LR
    R[reference.py<br/>Python, same maths] --> P
    S5[MT5 indicator] --> T5[Tester script<br/>DumpIndicator<br/>every bar to CSV]
    S4[MT4 indicator] --> T4[Tester EA<br/>DivergenceDump + Dump4.mqh<br/>CSV + repaint log]
    T5 --> P[check_parity.py<br/>1e-9, same arrows and types]
    T4 --> P
    S5 --> RP[RepaintProbe EA<br/>values at close vs final]
    T4 --> RL[repaint log<br/>per run]
    P --> V{all pass?}
    RP --> V
    RL --> V
    V -- yes --> M[Market validation]
    V -- no --> F[fix, rerun everything]

Why the design cannot repaint

  • The engine is fed closed bars only: the loop stops at rates_total - 2, so the forming bar is never used.
  • A pivot needs right closed bars after it, so the arrow is drawn on the bar that confirms it, never on the pivot bar and never into the past. Lines are created once and never moved or deleted while the indicator runs.
  • Each step depends only on the earlier bars (recursive formulas, no look-ahead).
  • Alerts fire only for the bar that just closed, only after the history load, once per bar, and never in the tester.
  • The MT4 tester recalculates an indicator from scratch at the end of a run, and a fresh calculation covers only the last InpHistory bars; the MT4 test therefore stores each bar's values as the live instance produced them and compares those.

Architecture and buffers

flowchart TB
    subgraph MT5["mql5"]
        A5[Indicators/Divergence/DivergenceSignalAlerts.mq5<br/>inputs, buffers, lines, OnCalculate]
        E5[Include/Divergence/Div.mqh<br/>engine: oscillators, pivots, divergence rule]
        B5[Include/Divergence/SignalAlerts.mqh<br/>alert inputs, SignalAlertMessage]
        A5 -- include --> E5
        A5 -- include --> B5
    end
    subgraph MT4["mql4/Indicators/Divergence"]
        A4[DivergenceSignalAlerts.mq4]
        E4[Div.mqh<br/>same engine as MT5]
        B4[SignalAlerts.mqh<br/>MT4 edition]
        A4 -- include --> E4
        A4 -- include --> B4
    end
    subgraph TESTS[tests]
        R[reference.py]
        C[check_parity.py]
        D5[mql5: DumpIndicator, RepaintProbe, Params.mqh]
        D4[mql4: DivergenceDump.mq4, Dump4.mqh]
        C --> R
    end
    D5 -. iCustom/IndicatorCreate .-> A5
    D4 -. iCustom .-> A4
classDiagram
    class DivergenceSignalAlerts {
        +InpRSI bool
        +InpMACD bool
        +InpRegular bool
        +InpHidden bool
        +InpLeft int
        +InpRight int
        +InpMinDist int
        +InpMaxDist int
        +InpClean bool
        +InpBoth bool
        +InpHistory int
        +OnInit()
        +OnCalculate()
    }
    class Div {
        +DVParams
        +DVState
        +DVInit()
        +DVFeed()
        +DVOsc()
        +DVMaskNames()
    }
    class SignalAlerts {
        +InpAlertPopup bool
        +InpAlertSound bool
        +InpAlertPush bool
        +InpAlertEmail bool
        +SignalAlertMessage()
    }
    class reference {
        +oscillators()
        +check()
        +run()
    }
    DivergenceSignalAlerts --> Div : engine
    DivergenceSignalAlerts --> SignalAlerts : alerts
    reference ..> Div : same maths, compared by check_parity

iCustom buffer table (for EA developers)

Buffer MT5 and MT4
0 Bullish divergence arrow price (bar low), empty otherwise
1 Bearish divergence arrow price (bar high), empty otherwise
2 type of the bullish divergence on that bar (1 regular, 2 hidden)
3 type of the bearish divergence on that bar (1 regular, 2 hidden)
4 RSI
5 MACD histogram

Empty value is EMPTY_VALUE. Read arrows on closed bars (shift 1 and higher). iCustom parameters follow the input order: RSI on, RSI period, MACD on, fast, slow, signal, regular, hidden, pivot left, pivot right, minimum distance, maximum distance, clean lines, both required, closed bars to scan, draw lines, bullish colour, bearish colour, arrow gap (MT4 only), pop-up, sound, sound file, push, e-mail, debug. In MT5 the input group headings also count as string parameters when you build a handle with IndicatorCreate (see tests/mql5/Include/IndTest/Params.mqh).

Inputs, alerts, install and compile

Input Default Meaning
RSI divergence / RSI period true / 14 Use the RSI
MACD histogram divergence true Use the MACD histogram
MACD fast / slow / signal EMA 12 / 26 / 9 MACD periods
Regular divergence (reversal) true Price and oscillator disagree at a new extreme
Hidden divergence (continuation) false Price makes a higher low (or lower high) while the oscillator does the opposite
Pivot: bars on the left / right 5 / 3 Right bars are the confirmation delay
Pivots at least / at most this many bars apart 5 / 60 Distance between the two pivots
Clean lines true No bar crosses the line between the pivots
Only when RSI and MACD both diverge false Needs both oscillators
Closed bars to scan on the chart 2000 100 to 20000
Draw the line between the price pivots true Trend line objects
Bullish / bearish colour green / red Colours of arrows and lines
Arrow distance from the candle (MT4 only) 0.3 x ATR(14) Gap between arrow and candle (MT5 uses a fixed pixel gap)
Pop-up alert true Terminal alert window
Play a sound / sound file false / alert.wav File from the terminal's Sounds folder
Push notification false Needs your MetaQuotes ID in Options
E-mail false Needs e-mail set up in Options
Debug log for bug reports false Writes extra lines to the Experts tab, to attach when you report a problem

Alert text: Regular bullish divergence (RSI+MACD) on EURUSD H1.

Install from source. MT5: copy mql5/Indicators/Divergence/ into <terminal data folder>/MQL5/Indicators/ and mql5/Include/Divergence/ into <data folder>/MQL5/Include/, open DivergenceSignalAlerts.mq5 in MetaEditor and press Compile (F7). MT4: copy mql4/Indicators/Divergence/ into <data folder>/MQL4/Indicators/ (the three files of the edition stay together) and compile DivergenceSignalAlerts.mq4. Both compile with 0 errors and 0 warnings (checked on a Windows machine with MetaEditor; the compiled files are not in the repository). The debug log needs the Chart Kit file as well: copy mql5/Include/ChartKit/ (MT5) or mql4/Include/ChartKit/ (MT4) into the Include folder of the data folder.

Run the Python reference and the tests

python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
import sys; sys.path.insert(0, "tests")
from reference import Params, run
# h, l, c are float sequences of closed bars, oldest first
events, osc = run(h, l, c, Params())   # events keyed by the confirming bar: dir (+1 bull, -1 bear), type, mask, p, q

To repeat the parity check on your own data:

  1. Compile the indicator and the test tool for your platform (tests/mql5/Scripts/DumpIndicator, or tests/mql4/DivergenceDump.mq4 with tests/mql4/Include/IndTest4/Dump4.mqh in your MQL4/Include/IndTest4/; MT5 also needs tests/mql5/Include/IndTest/Params.mqh).
  2. Run it in the Strategy Tester (MT4) or as a script (MT5) for each symbol and timeframe, with the input sets in tests/configs.tsv (MT5) / tests/mql4/configs.tsv (MT4). The tool writes one CSV per symbol and timeframe.
  3. python3 tests/check_parity.py <folder> where the CSVs are in <folder>/dump/ (or directly in <folder>) and named dv_default_*, dv_hidden_both_* or dv_rsi_noclean_* (add --mt4 for MT4 dumps). It prints PASS or FAIL per file and an overall result.
  4. Repaint: run tests/mql5/Experts/RepaintProbe in the MT5 Strategy Tester (every tick); MT4 writes its repaint log with the dump.

The price dumps used for the published numbers are not in this repository (they are broker price data).

Honest limits

  • An indicator signal is not a strategy. No stop-loss, position sizing or exit rules are provided or implied, and the arrows were not optimised or back-tested for profit. Whether a divergence is followed by a turn depends on your market, costs and rules.
  • No profit or accuracy claim is made. The only numbers here are about consistency (the three implementations agree, arrows do not move) and frequency (arrows per month).
  • Late by design. A divergence is only known right closed bars after its pivot formed, so the arrow always comes after the turn it describes.
  • Divergences can persist. Price can keep making lower lows while the oscillator keeps rising; many divergences are not followed by a reversal.
  • Window-dependent. The oscillators are seeded from the first bar the engine sees and it starts Closed bars to scan bars back, so the first arrows of a window can differ from a longer one.
  • Scope of the tests. Four symbols, three timeframes, three input sets, one MT5 repaint run. Other symbols and timeframes are not covered by the published numbers. The MT5 build was not run on a live chart as part of these numbers.

Licence and credits

Origin of the maths. RSI (Wilder), the MACD histogram, swing pivots and oscillator divergence are standard, long-published technical-analysis definitions. This code is an own implementation written from those definitions; it is not a port of any particular script.

Everything in this repository is © 2026 Allan Niñal under the PolyForm Noncommercial License 1.0.0: free for personal and other noncommercial use, not for commercial use (source-available, not OSI open source). Each source file has an SPDX-License-Identifier line. Text: LICENSE. The licence applies to this source, not to anything you build or trade with the indicator. Trading involves risk; the software is provided as is.


Website: allanninal.dev/mql5 · MT4 · allanninal.dev · Questions: message me on LinkedIn.