
# EA Health Monitor (MetaTrader 5 and MetaTrader 4)
A free indicator that shows **one table row per Expert Advisor** on the account, found by magic number, plus a row for manual
trades and a total: closed trades, net result, win rate, profit factor, max drawdown of the realized result, today's result,
open positions and their floating P/L. It reads the account history and the open positions only; it never trades. Source for both
editions, a Python reference of the statistics, and the tests that were used to check that MT5, MT4 and Python agree.
**Website:** [allanninal.dev/mql5 (utilities)](https://www.allanninal.dev/mql5/#utilities) · [MT4 editions](https://www.allanninal.dev/mql5/#mt4) · [allanninal.dev](https://www.allanninal.dev)
· **Contact:** message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).

> It reports what the account did. It does not judge whether an EA will work. Nothing here is a profit claim
> (see [Honest limits](#honest-limits)).
## Get it on the MQL5 Market
| Edition | Platform | Price | Link |
|---|---|---|---|
| EA Health Monitor | MT5 | free | |
| EA Health Monitor MT4 | MT4 | free | |
The Market builds are the same calculation as the source in this repository.
## Contents
1. [What it is and who it is for](#what-it-is-and-who-it-is-for)
2. [Why it was built](#why-it-was-built)
3. [How it works](#how-it-works)
4. [How it was tested](#how-it-was-tested)
5. [Architecture](#architecture)
6. [Inputs, install and compile](#inputs-install-and-compile)
7. [Run the Python reference and the tests](#run-the-python-reference-and-the-tests)
8. [Honest limits](#honest-limits)
9. [Licence](#licence)
## What it is and who it is for
**What:** a panel of chart objects, refreshed every two seconds. Each row is one magic number (one Expert Advisor), in the order
each first traded; magic 0 is "Manual"; the last row, "All", adds them up. Columns: Trades, Net, Win %, PF, Max DD, Today, Open,
Floating, Last deal. Hover a cell for gross profit, gross loss, wins, losses and expectancy per trade. A manual close of an EA's
trade stays with that EA.
**For:** traders who run several Expert Advisors on one account and want to see which one is doing what, not just one combined
statement. Also for developers: the statistics are a small, tested core you can read.
**Not for:** judging an EA from a few trades, or anyone who wants alarms, period filters or curves.
| | |
|---|---|
|  |  |
Image notes: the screenshots use an example account with made-up data (five magic numbers, names typed in the Names input);
they are illustrations, not terminal captures, and not trading results. MT4 versions are in [`docs/images/mt4/`](docs/images/mt4).
## Why it was built
- **A combined statement hides which EA carries the others.** A per-magic table answers it at a glance.
- **Weak, scattered demand, stated plainly.** Our October 2026 research found a Myfxbook reply listing checks people skip for bots
(open-basket floating loss, restart during an open trade), a Programming-forum thread on EA portfolio analysis (235 views) and a
handful of equity and drawdown panels on the Market. That is a small signal, not proof of mass demand.
- **So it is free and its value is that the numbers are checked.** The statistics follow common definitions and are compared with an
independent Python version on thousands of deals.
```mermaid
mindmap
root((EA Health Monitor))
Problem
One statement for many EAs
Manual closes blur whose trade it was
Partial closes counted as several trades
Design rules
A trade belongs to the EA that opened it
Partial closes are one trade
Costs included, entry commission too
Proof
Python reference
Synthetic histories compared exactly
MT4 tester with real orders
```
## How it works
### The rules, step by step
For each deal, in time order (MT4: orders turned into deals, see below):
1. **Position and owner.** An opening (IN) deal creates a position owned by the deal's magic number. Every later deal of that
position (partial closes, the final close, even a manual close with magic 0) belongs to the same owner. If the opening deal is
not in the history, the first deal seen gives the owner.
2. **Realized curve.** Each deal adds `profit + swap + commission + fee` to its owner's curve at its time (the commission charged at
opening included).
3. **Max drawdown.** `peak = max(peak, curve)`, `maxDD = max(maxDD, peak - curve)`; the peak starts at 0.
4. **A trade** is a position whose closed volume has reached its opened volume. Its net is the sum over all its deals. A reversing
deal (IN/OUT) closes the trade and opens the next one with the volume beyond.
5. **Statistics per owner.** Trades `N`; wins `W` (net > 0) and losses `L` (net < 0); gross profit `GP` and gross loss `GL`;
- win rate `= W / N`
- profit factor `= GP / GL` (shown as "no loss" when `GL = 0` and `GP > 0`)
- expectancy `= (GP - GL) / N`.
6. **Today** is the owner's realized result since server midnight. **Open** and **Floating** come from the open positions (floating
= profit + swap; MT4 adds commission, which MT4 books at the close).
7. **Names.** The Names input (`magic=name,...`), else the comment of the EA's first opening deal plus the magic, else `#magic`.
**MT4** keeps orders, not deals. Each order becomes an opening deal (open time, lots) and, once closed, a closing deal (close time,
lots, profit, swap, commission). A partial close leaves the rest open under a new ticket whose comment is `from #`; all
parts are one position. The book is rebuilt whenever the orders change.
### Per-deal algorithm
```mermaid
flowchart TD
A[Next deal in time order] --> B{Opening deal?}
B -- yes --> C[Create or extend the position
owner = deal's magic]
B -- no --> D{Position known?}
D -- no --> E[Owner = this deal's magic]
D -- yes --> F[Owner = the position's magic]
C --> G[Add profit + swap + commission + fee
to the owner's curve]
E --> G
F --> G
G --> H[Update peak and max drawdown]
H --> I{Closed volume reached opened volume?}
I -- no --> Z[Next deal]
I -- yes --> J[One trade: net = sum of its deals
win, loss, gross profit or loss]
J --> K[Forget the position]
K --> Z
```
### A position's life
```mermaid
stateDiagram-v2
[*] --> Open: opening deal (owner = its magic)
Open --> Open: partial close (same owner, still one trade)
Open --> Closed: closed volume reaches opened volume
Open --> Reversed: IN/OUT deal
Reversed --> Open: the volume beyond opens the other way
Reversed --> Closed
Closed --> [*]: counted as one trade, then forgotten
```
### Refresh flow
```mermaid
sequenceDiagram
participant T as Terminal
participant I as EA Health Monitor
participant B as Book (statistics per magic)
participant P as Panel
T->>I: timer every 2 s (and on ticks)
I->>I: new deals since the last refresh? new day?
alt a new day, or older deals synced in
I->>B: start the book again from the whole history
else only new deals
I->>B: add them
end
I->>B: open positions: count and floating per magic
B-->>I: rows and total
I->>P: update only the cells whose text changed
```
## How it was tested
**1. The core against Python, on synthetic histories.** [`tests/reference.py`](tests/reference.py) follows the rules operation by
operation. [`tests/check_core.py`](tests/check_core.py) writes seeded synthetic deal lists (hedging-style positions with partial
closes, manual closes, reversals, entry commissions, some positions left open). The test script
[`tests/mql5/Scripts/EAHealthDump`](tests/mql5/Scripts) runs them through the MT5 core in the MetaTrader 5 terminal and writes the
statistics, and the checker compares every number (counts exactly, money within 1e-9 relative).
| History | Deals | Magics | Trades | Result |
|---|---|---|---|---|
| seed 1 | 900 | 5 | 346 | pass |
| seed 2 (2,000 positions) | 4,496 | 5 | 1,817 | pass |
| seed 3 (manual only) | 905 | 1 | 355 | pass |
| seed 4 (50 positions) | 117 | 5 | 46 | pass |
| seed 5 (two EAs) | 1,799 | 2 | 708 | pass |
| seed 6 (30 magics) | 6,839 | 30 | 2,732 | pass |
| **Total** | **15,056** | | **6,004** | **ALL PASS** |
Source: [`tests/results/core-parity-mt5.txt`](tests/results/core-parity-mt5.txt).
```mermaid
xychart-beta
title "Deals and trades compared per synthetic history (all 0 differences)"
x-axis ["seed 1", "seed 2", "seed 3", "seed 4", "seed 5", "seed 6"]
y-axis "count" 0 --> 7500
bar [900, 4496, 905, 117, 1799, 6839]
line [346, 1817, 355, 46, 708, 2732]
```
Bars: deals. Line: trades.
**2. The MT4 edition in the MetaTrader 4 Strategy Tester.** [`tests/mt4/HealthTrader.mq4`](tests/mt4/HealthTrader.mq4) trades a seeded
random mix (several magic numbers and manual orders, comments, stop loss and take profit, partial closes, full closes, close-by) and,
every few hundred bars and at the end, writes the raw orders and its own book. [`tests/mt4/check_mt4.py`](tests/mt4/check_mt4.py)
turns the raw orders into deals on its own and compares every statistic, open count, floating and row name with the Python
reference.
| | Result |
|---|---|
| Runs | 9 (2 seeds x 4 symbols + one every-tick run) |
| Snapshots compared | 146 |
| Trade operations made by the test | 25,220 |
| Trades in the final snapshots | 7,972 |
| Result | ALL PASS |
Source: [`tests/results/parity-mt4.txt`](tests/results/parity-mt4.txt).
```mermaid
flowchart LR
R[reference.py
Python, rules as code] --> C1
G[check_core.py write
seeded synthetic deals] --> D5[EAHealthDump script
MT5 core, statistics to CSV]
D5 --> C1[check_core.py check
exact counts, 1e-9 money]
T4[HealthTrader EA
MT4 tester trades] --> O[raw orders + book per snapshot]
O --> C2[check_mt4.py
orders to deals on its own]
R --> C2
C1 --> V{all pass?}
C2 --> V
V -- yes --> M[Market validation]
V -- no --> F[fix, rerun everything]
```
**3. Market validation.** Both Market builds passed the MQL5 Market's automated product validation before publication (the Market's
own check, not part of this repository).
**What the tests do not cover.** The panel text and layout (the MT4 tester gives an indicator run through `iCustom` no chart, so only
the book the panel shows is compared; the indicator is loaded in every run as a smoke test); the live history of a real account
(only synthetic histories and tester trades were compared); the MT4 test account is a cent account, so many orders were refused for
margin and some runs ended early.
## Architecture
```mermaid
flowchart TB
subgraph MT5["mql5"]
A5[Indicators/EAHealth/EAHealthMonitor.mq5
inputs, panel rows, timer]
L5[Include/EAHealth/HealthLive.mqh
refresh, names, formatting]
C5[Include/EAHealth/HealthCore.mqh
deals to statistics]
P5[Include/EAHealth/Panel.mqh
grid of chart objects]
A5 --> L5
A5 --> P5
L5 --> C5
end
subgraph MT4["mql4/Indicators/EAHealth"]
A4[EAHealthMonitor.mq4]
L4[HealthLive.mqh
orders-based refresh]
C4[HealthCore.mqh
same statistics + orders to deals]
P4[Panel.mqh
exact copy]
A4 --> L4
A4 --> P4
L4 --> C4
end
subgraph TESTS[tests]
R[reference.py]
K1[check_core.py]
D5[mql5/Scripts/EAHealthDump]
K2[mt4/check_mt4.py]
D4[mt4/HealthTrader.mq4]
end
D5 -. includes .-> C5
D4 -. includes .-> L4
K1 --> R
K2 --> R
```
```mermaid
classDiagram
class EAHealthMonitor {
+InpNames string
+InpManual bool
+InpHideIdle bool
+OnInit()
+OnTimer()
+Show()
}
class EHLive {
+EHBook b
+EHRefresh()
+EHName()
}
class EHBook {
+EHStat st[]
+EHPos ps[]
}
class EHStat {
+magic long
+n, w, l int
+gp, gl, net double
+maxdd, today double
+open int
+floating double
}
class CSDPanel {
+Setup()
+Layout()
+SetCell()
+Destroy()
}
EAHealthMonitor --> EHLive
EAHealthMonitor --> CSDPanel
EHLive --> EHBook
EHBook --> EHStat
```
The indicator has no buffers and no `iCustom` interface: it is a panel.
## Inputs, install and compile
| Input | Default | Meaning |
|---|---|---|
| Names: magic=name, comma separated | empty | e.g. `771500=Trend EA,0=Manual` |
| Show manual trades (magic 0) | true | The "Manual" row |
| Hide EAs with nothing to show | false | Hide rows with no trade and nothing open |
| Panel corner, distance from the side and the top, font size | top left, 10, 20, 9 | Position and size |
| Colours: panel, cell, text, header, positive, negative | dark theme | Appearance |
| Debug log for bug reports | false | Writes extra lines to the Experts tab, to attach when you report a problem |
**Install from source.** MT5: copy `mql5/Indicators/EAHealth/` into `/MQL5/Indicators/` and
`mql5/Include/EAHealth/` into `/MQL5/Include/`, open `EAHealthMonitor.mq5` in MetaEditor and press Compile (F7).
MT4: copy `mql4/Indicators/EAHealth/` (it holds all four files) into `/MQL4/Indicators/` and compile
`EAHealthMonitor.mq4`. Both compile with 0 errors and 0 warnings (checked with the MetaEditor of a MetaTrader 5 and a MetaTrader 4
build on a Windows machine; 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
```bash
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
```
```python
import sys; sys.path.insert(0, "tests")
from reference import compute, synth
deals = synth(seed=1) # seeded random history, deals in time order
stats = compute(deals, day_start=0) # per magic: n, w, l, gp, gl, net, curve, peak, maxdd, today, last, deals
```
To repeat the MT5 core check: `python3 tests/check_core.py write `, copy `/deals_*.csv` to the terminal's common
`Files\ehtest\` folder, compile and run `tests/mql5/Scripts/EAHealthDump` (it writes `stats_.csv` there), copy those back to
`` and run `python3 tests/check_core.py check `. For MT4: compile `tests/mt4/HealthTrader.mq4`, run it in the Strategy
Tester (open prices) with the inputs in `tests/mt4/configs.tsv`, then `python3 tests/mt4/check_mt4.py `.
## Honest limits
- **It reports history; it does not predict.** A few trades prove little. No profit or accuracy claim is made.
- **Max drawdown is of the realized result**, from the first deal in the history, not of the account's equity. In the "All" row it is
the largest of the rows, not the account's drawdown.
- **History must be available.** The panel can only count what the terminal's account history holds; if you cut the history
range in the terminal, the numbers change.
- **A bug found on the way.** MQL4's `StringSubstr(s, 0, 0)` returns the whole string, so a comment that was only a server-close marker such as `[sl]` was read as a name; fixed in `EHCleanComment` (MT4 core) before the published test run.
- **MT4 books the commission at the close**, so an open trade's floating P/L includes it there; MT5 books it at the entry deal.
- **Scope of the tests.** Synthetic histories and tester trades, not a real account; the panel text is not covered in the MT4 tester.
## Licence
Everything is © 2026 Allan Niñal under the
[PolyForm Noncommercial License 1.0.0](LICENSES/PolyForm-Noncommercial-1.0.0.md): source-available, free for personal and other
noncommercial use, not for commercial use. Each source file has an `SPDX-License-Identifier` line; the text is in
[`LICENSE`](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](https://www.allanninal.dev/mql5/#utilities) · [MT4](https://www.allanninal.dev/mql5/#mt4) · [allanninal.dev](https://www.allanninal.dev)
· Questions: message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).