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>

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.
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
- What it is and who it is for
- Why it was built
- How it works
- Non-repainting: the proof and how it was tested
- Architecture and buffers
- Inputs, alerts, install and compile
- Run the Python reference and the tests
- Honest limits
- 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.
![]() |
![]() |
![]() |
![]() |
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).
-
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 theEMA(9)of that line. Every EMA is seeded with the simple average of its firstnvalues, thenEMA = EMA + 2/(n+1) * (x - EMA).
- RSI (Wilder, period 14): gains and losses smoothed with an RMA seeded with the simple average of the first 14
changes;
-
Pivots on price. Bar
p = k - rightis a pivot low if its low is below the low of each of theleftbars before it and not above the low of any of therightbars after it. A pivot high mirrors it. A pivot is therefore knownrightbars after it formed: barkis its confirmation bar. -
Compare with the earlier pivots. The engine remembers the last four pivots of each side. At a new pivot low
p, the earlier pivot lowsqare tried, nearest first, if they are betweenminandmaxbars back (defaults 5 and 60):- regular bullish:
low[p] < low[q]andosc[p] > osc[q](price lower low, oscillator higher low) - hidden bullish:
low[p] > low[q]andosc[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).
- regular bullish:
-
Clean lines (on by default): no bar strictly between
qandpmay cross the straight line fromqtop, 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. -
Result. The first
qwhere 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 barkat 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
rightclosed 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
InpHistorybars; 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 |
| 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:
- Compile the indicator and the test tool for your platform (
tests/mql5/Scripts/DumpIndicator, ortests/mql4/DivergenceDump.mq4withtests/mql4/Include/IndTest4/Dump4.mqhin yourMQL4/Include/IndTest4/; MT5 also needstests/mql5/Include/IndTest/Params.mqh). - 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. python3 tests/check_parity.py <folder>where the CSVs are in<folder>/dump/(or directly in<folder>) and nameddv_default_*,dv_hidden_both_*ordv_rsi_noclean_*(add--mt4for MT4 dumps). It prints PASS or FAIL per file and an overall result.- Repaint: run
tests/mql5/Experts/RepaintProbein 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
rightclosed 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 scanbars 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.




