D -- yes --> E[Insert the day, low = min of floating and 0]
D -- no --> F{floating below the day's low?}
F -- yes --> G[Deepen the low and its percent]
F -- no --> H[Nothing changes]
E --> I{Changed and 10 s since the last write?}
G --> I
I -- yes --> J[Read the file, merge, keep the deeper low per day, rewrite]
I -- no --> K[Keep in memory]
L[Card removed or terminal closed] --> J
```
### Periods, gain and worst drawdown
- **Balance before a period** = the current balance minus every balance-affecting deal at or after the period's first moment
(cash flow included). **Gain %** of a row is its net divided by that balance.
- **Rows:** today; up to four earlier days that had trades; the week (back to Monday); the month; the year. Their days are
server calendar days.
- **Worst DD** of a day is the journal's value when the journal has that day; otherwise the deepest peak-to-trough of that
day's closed-trade curve, shown with `~` and dimmed. A week, month or year row shows its worst day. Days with neither show `-`.
- **Averages:** the mean daily return over days with trades (`100 * day net / balance before the day`), and the mean monthly return
over months with trades (month net / balance before the month's first day).
### Statistics
Let `W` be the winning trades (net above 0), `L` the losing ones (net below 0), `GP` the sum of the wins, `GL` the sum of the
losses as a positive number, `N` the trades.
| Figure | Rule |
|---|---|
| Win rate | `100 * W / N` |
| Profit factor | `GP / GL` (shown as "no losses" when `GL = 0` and `GP > 0`) |
| Expectancy | `(GP - GL) / N` per trade |
| Average win, average loss | `GP / W`, `-GL / L` |
| Reward/risk | `(GP / W) / (GL / L)` |
| Maximum drawdown | on the closed-trade balance curve, starting from the balance before the first trade opened: the largest `peak - curve`, with its percent of that peak and its date |
| Relative drawdown | the largest `100 * (peak - curve) / peak` over the same curve, with its date |
| Sharpe, 60 days | over the last 60 calendar days (today and the 59 before): `sum(daily returns) / stdev(daily returns)`, sample standard deviation, needs 2 or more trading days with a return and a non-zero deviation |
| Best day, worst day | the highest and lowest daily return in percent, with their dates |
| Costs | commission + swap + fees of the counted trades |
The formulas follow the definitions that prop-firm statistics pages publish (the Sharpe line is the one such a page documents: cumulative
return over the standard deviation of daily returns). This project is not affiliated with any firm.
### Open positions, risk and exposure
- **P/L** of a position is profit plus swap. **Pips** are `(current - open) / point / m`, with `m` points per pip chosen from the
symbol's digits and kind (5-digit FX and 1-digit quotes: 10; 3-digit yen or silver and 2-digit gold: 10; one more digit: 100).
Short positions are negated.
- **Risk at the stops** is the loss at the stop-loss price in account money: MT5 asks the terminal for the profit calculation at
that price (`OrderCalcProfit`, which only calculates); MT4 uses `distance / tick size * tick value * lots`, with a documented
fallback when the tester gives a tick value of 0. Positions without a stop are counted and the first is named.
- **Exposure by currency:** each position contributes `+/- notional` in account money (volume x contract size x price x the
profit currency's rate to the account currency, from a cross symbol, else from the terminal's tick value): long adds the base
currency and subtracts the profit currency. When base and profit currency are the same (an index CFD) the symbol is its own
"currency". The six largest nets are shown.
### MT4: orders become deals
MetaTrader 4 keeps orders, not deals. [`VtLive.mqh`](mql4/Include/Vitals/VtLive.mqh) turns them into the same deal list the engine reads on MT5:
each order becomes an opening deal at its open time and, once closed, a closing deal at its close time carrying profit, swap and
commission. A partial close leaves an order for the closed part and opens a new one commented `from #<ticket>`: the chain is one
position, keyed by its first ticket. Balance records are cash flow, credit records are credit. The deals are ordered by time,
then cash before opening before closing, then reading order. The list is rebuilt whenever the history or the open orders change.
```mermaid
flowchart LR
A[Order history and open orders] --> B[Skip pending orders]
B --> C[Follow 'from #ticket' to the first ticket<br/>= the position id]
C --> D[Opening deal at open time]
C --> E{Closed?}
E -- yes --> F[Closing deal at close time<br/>profit, swap, commission]
E -- no --> G[Still open: no closing deal]
A --> H[Type 6 = cash flow, type 7 = credit]
D --> I[Sort by time, then cash, opening, closing]
F --> I
H --> I
I --> J[The same engine as MT5]
```
### One refresh
```mermaid
sequenceDiagram
participant T as Terminal
participant A as Vitals app
participant H as History reader
participant J as Journal
participant E as Engine
participant C as Card
T->>A: timer, every second
A->>A: read the account, then the open positions
A->>H: new deals since the last one?
alt login changed, history rewritten, or first run
H->>E: rebuild from the whole history
else only new deals
H->>E: add them
end
A->>J: sample the floating result
alt history or journal changed
A->>E: recompute rows and statistics
else nothing changed
A->>E: only the open positions
end
A->>A: signature of everything shown
alt signature changed, or a click, or a theme change
A->>C: redraw the card
else same as before
A-->>C: leave it as it is
end
T->>A: chart tick: sample the journal, and refresh if the timer is late
```
### The card
The card is one `CCanvas` bitmap label. Drag it by the header, fold it to the header with the button, click a tab. Chart
scrolling is paused while the mouse button is down on the card. It stays inside the chart, and its position, fold state and tab are kept
per chart in terminal global variables. In the Strategy Tester the card is not drawn; the engine still runs.
```mermaid
stateDiagram-v2
[*] --> Shown
Shown --> Dragging: mouse down on the header
Dragging --> Shown: release (position saved, kept inside the chart)
Shown --> Folded: fold button
Folded --> Shown: fold button
Shown --> Shown: click a tab (Overview, History, Stats)
Shown --> Shown: theme or chart size change
```
## How it was tested
Every number in sections 1 to 3 was produced by a run of this repository's code on 21 October 2026, and its output is in [`tests/results/`](tests/results). Section 4 is clearly marked as a different set of runs.
**1. The engine 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 eleven seeded synthetic cases (hedging and netting positions,
partial closes, reversals, cash flow and credit, commission, swap and fees, the symbol, magic, comment and start-date filters, "now" on a
Monday, on the first of a month, on 1 January and on a Sunday, an empty history, a 3,000-position history), plus open
positions and a floating-drawdown journal. A dump tool runs the MetaTrader engine on them
([`tests/mql5/Scripts/VitalsDump.mq5`](tests/mql5/Scripts) as a script, [`tests/mql4/Experts/VitalsDump4.mq4`](tests/mql4/Experts) in the MT4
Strategy Tester) and writes every number the card shows; the checker compares counts exactly and money and percentages within 1e-9
Bars: deals. Line: trades. Cases in the table's order. The eleven cases are the same for both platforms; the values are the numbers the card shows (rows,
statistics, per-day figures, open positions, exposure).
**2. The journal, through restarts and crashes.** A scripted equity path crosses midnight, restarts cleanly and by crash (no final
write), jumps three days, deposits, changes the balance, has a flat day, and has another chart writing a deeper low into the file. The
journal file after each restart, and the final memory, are compared line by line with a Python model of the same rules.
Result on both platforms: 990 samples, 6 restarts, 6 days, 43 lines compared, **0 mismatches** (same two result files).
**3. The pip table.** 19 symbol and digit pairs (FX, yen, gold, silver, indices, crypto, suffixed names) are compared with the Python
function.
Result on both platforms: 19 of 19 equal (same two result files).
| Engine against tester account histories, MT5 (4 runs plus snapshots) | 21,626 deals, 82,846 values, 0 mismatches |
| Engine against tester order histories, MT4 (4 runs plus snapshots) | 29,590 deals, 107,958 values, 0 mismatches |
| Validator-style runs ($100 and $100,000 accounts, 5 symbols and timeframes, every tick on MT4 EURUSD H1 and XAUUSD M5) | 20 runs per platform, 0 error lines |
| History loader | MT5: 24.9 ms for 10,294 deals, 1.65 s for 69,547 deals; MT4: 16 to 156 ms for 1.5 to 6.7 thousand deals |
| Card refresh with a full redraw | MT5 1.0 to 1.8 ms, MT4 about 12 ms |
| Empty history | the card draws (22 screenshots per platform) |
(The value counts include a few figures from parts of the full build that are not in this repository, so they are not the same as this repository's own run above.)
**Files and settings it writes.**`MQL5/Files/Vitals/FDD_<login>.csv` (MT4: `MQL4/Files/Vitals/FDD_<login>.csv`): the journal, one line per
day. Terminal global variables `VITALS_<chart id>_x`, `_y`, `_fold`, `_tab`: the card's place, fold state and tab for that chart.
Nothing else, and no network access.
**Install from source.** MT5: copy `mql5/Indicators/Vitals/` into `<terminal data folder>/MQL5/Indicators/` and `mql5/Include/Vitals/`
into `<data folder>/MQL5/Include/`, open `Vitals.mq5` in MetaEditor and press Compile (F7). MT4: copy `mql4/Indicators/Vitals/` and
`mql4/Include/Vitals/` into the matching folders under `<data folder>/MQL4/`, open `Vitals.mq4` and compile. The source files need a
UTF-8 byte-order mark (they have one). Both editions 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.
card=e.report(now,balance,journal)# flat dict of every number the card shows
card.update(e.positions(positions,balance))
print(card["st.winrate"],card["row.0.net"])
```
To repeat the engine check: `python3 tests/check_core.py write <dir>`, copy `<dir>/*.csv` to the terminal's `Files/vttest/`
(MT5: the common files folder; MT4: the Strategy Tester's `files` folder), compile and run `tests/mql5/Scripts/VitalsDump.mq5`
(MT5, once with `InpMode=0` and once with `InpMode=1`) or run `tests/mql4/Experts/VitalsDump4.mq4` in the Strategy Tester (MT4,
also once per mode), copy the `out_*.txt` and `journal_out.txt` files it wrote back into `<dir>`, and run
`python3 tests/check_core.py check <dir>`. The test tool includes `tests/mql5/Include/VitalsCheck/VtDump.mqh`: copy it to your
`Include/VitalsCheck/` folder first.
## Honest limits
- **The journal starts when you attach the card.** MetaTrader does not store past floating equity. Before that, the History tab falls
back to the drawdown of the closed-trade curve, marked `~` and dimmed. The journal is also only as complete as the time the terminal was
running with the card on the chart: a day the terminal was off has no recorded low.
- **Drawdown figures are of two kinds.** The floating drawdown is recorded from equity; the maximum and relative drawdown on the Stats tab
come from the closed-trade balance curve and miss what happened inside open trades.
- **It reports history; it does not predict.** No profit or accuracy claim is made, and a handful of trades proves little.
- **Server time.** Days, weeks and months follow the server's calendar. MT4 has no trade-server clock and uses the time of the last tick.
- **History must be available.** The card counts what the terminal's account history holds; if you cut the history range in the terminal,
the numbers change. Very large histories take longer to load the first time.
- **MT4 books the commission at the close**, and its history is orders, not deals: the server's own comment notes (`[sl]`, `[tp]`, `from #...`) are not treated as an EA's comment, and the order-to-deal conversion was
tested only on the tester's account (in the full-build runs) and not on a live one.
- **Exposure and risk need conversion rates.** If no cross symbol exists for a profit currency, the terminal's own tick value is used, and