Query MT5 P&L from an encrypted mt5-pnl-exporter snapshot. One static binary β no daemon, no database, no third-party service.
ββββββββββββββββ writes ββββββββββββββββββ reads ββββββββββββββββ
β mt5-pnl- β βββββββββΊ β snapshot.json β ββββββββΊ β mt5-pnl-cli β
β exporter β β .gz.age β β (this repo) β
β Windows host β β synced via β β macOS, Linux β
ββββββββββββββββ β Dropbox etc. β β or Windows β
ββββββββββββββββββ ββββββββββββββββ
The exporter runs on the Windows host where MT5 lives and writes one encrypted file. This CLI runs wherever you are, decrypts that file in memory, and prints P&L and account tables β or JSON for scripts and AI agents.
- Why
- Install
- Quick start
- Demo
- Commands
- How it works
- Schema compatibility
- Threat model
- Contributing
- Licence
- Self-hosted. Your trading data never touches a third-party dashboard. The snapshot is yours; this binary reads it locally.
- One file in, answers out. No config file. Point it at the snapshot
once (env var or flag) and
mt5-pnl-cli pnljust works. - Agent- and script-friendly.
--format jsonemits stable machine-readable output, and warnings go to stderr so they never corrupt a pipeline. An agent like Claude Code can turn "show me monthly P&L for Q1" intomt5-pnl-cli pnl --from 2026-01-01 --to 2026-03-31 --by month --format json. Colour is auto-disabled when output is piped or redirected, so pipelines stay clean without any extra flags. - Secrets stay in the keychain. The decryption passphrase lives in the OS keychain only β there is deliberately no env var or flag for it.
Download a binary from
Releases
(darwin/linux/windows, amd64/arm64, with checksums.txt), or build with
Go:
go install github.com/tanem/mt5-pnl-cli@latest# once: store the snapshot passphrase in the OS keychain. Use the SAME
# passphrase you gave mt5-pnl-exporter's set-encryption-passphrase.
mt5-pnl-cli set-passphrase
# once: tell the CLI where the synced snapshot lives
export MT5_PNL_SNAPSHOT=~/snapshots/mt5.json.gz.age
mt5-pnl-cli pnl # last 30 days, grouped by week
mt5-pnl-cli accounts # balances, equity, freshnessPer-account and combined (ALL) rows per period, with a summary block:
$ mt5-pnl-cli pnl --from 2026-01-01 --to 2026-01-31
PERIOD ACCOUNT P&L TRADES WINS LOSSES
2026-01-05 Trend EA 5.00 2 1 1
2026-01-05 Scalper EA 0.00 1 0 0
2026-01-05 ALL 5.00 3 1 1
2026-01-12 Trend EA 5.00 1 1 0
2026-01-12 ALL 5.00 1 1 0
Summary
Performance
Trades 4
Win rate 50.0%
Profit factor 3.50
Expectancy 2.50
Avg win 7.00
Avg loss -4.00
Largest win 9.00
Largest loss -4.00
Max drawdown -4.00
Gross profit 14.00
Gross loss -4.00
P&L breakdown
Trade profit 13.00
Commission -2.00
Swap -1.00
Fee 0.00
Net P&L 10.00 USD
$ mt5-pnl-cli accounts
LOGIN LABEL CURRENCY BALANCE EQUITY LAST SUCCESS LAST ERROR
111 Trend EA USD 1000.00 1010.50 2026-06-13T00:00:00Z -
222 Scalper EA USD 500.00 500.00 - login failed
Snapshot generated: 2026-06-13T00:00:00Z
--format json emits the same data for machines
("account": null is the combined row):
$ mt5-pnl-cli pnl --from 2026-01-01 --to 2026-01-31 --by month --accounts "Trend EA" --format json
{
"rows": [
{
"period": "2026-01-01",
"account": 111,
"pnl": 10,
"trade_profit": 13,
"commission": -2,
"swap": -1,
"fee": 0,
"trades": 3,
"wins": 2,
"losses": 1,
"gross_profit": 14,
"gross_loss": -4
},
{
"period": "2026-01-01",
"account": null,
"pnl": 10,
"trade_profit": 13,
"commission": -2,
"swap": -1,
"fee": 0,
"trades": 3,
"wins": 2,
"losses": 1,
"gross_profit": 14,
"gross_loss": -4
}
],
"summary": {
"total_pnl": 10,
"total_trades": 3,
"win_rate_pct": 66.7,
"profit_factor": 3.5,
"expectancy": 3.33,
"avg_win": 7,
"avg_loss": -4,
"largest_win": 9,
"largest_loss": -4,
"max_drawdown": -4,
"gross_profit": 14,
"gross_loss": -4,
"trade_profit": 13,
"commission": -2,
"swap": -1,
"fee": 0
}
}
--format csv emits header + rows for spreadsheets (no summary):
$ mt5-pnl-cli pnl --from 2026-01-01 --to 2026-01-31 --by month --accounts "Trend EA" --format csv
period,account_login,account_label,pnl,trade_profit,commission,swap,fee,trades,wins,losses,gross_profit,gross_loss
2026-01-01,111,Trend EA,10.00,13.00,-2.00,-1.00,0.00,3,2,1,14.00,-4.00
2026-01-01,,ALL,10.00,13.00,-2.00,-1.00,0.00,3,2,1,14.00,-4.00
pnlβ P&L over a date range.- Range:
--last Nd|Nw|Nm|Ny(default30d; months and years are calendar-accurate) or--from YYYY-MM-DD [--to YYYY-MM-DD](--todefaults to today).--lastruns from N units ago through today inclusive, so30dcovers 31 calendar days. Dates,--lastand "today" are all interpreted in UTC, and each deal is bucketed by its UTC day β so from a far-east timezone (e.g., UTC+12), the UTC day can differ from your local day near midnight. - Deal times and broker months. Each deal's
timeis the value MT5 records β on most brokers the server's local clock stored as a Unix timestamp. The CLI buckets by the UTC day/week/month of that value, so monthly and weekly figures line up with what your broker statement shows; there is no timezone skew to correct for. --by day|week|month(defaultweek; weeks start Monday, dates are UTC).--accounts "Trend EA,Scalper EA"filters by account label (case-insensitive; default all).--format table|json|csv(defaulttable). The table footer is a Summary block in two groups β Performance (trades, win rate, profit factor, expectancy, average and largest win/loss, max drawdown, gross profit/loss) and P&L breakdown (trade profit, commission, swap, fee, and the net). JSON carries the same fields per row and in the summary; CSV is header + rows only (no summary), with the component columnspnl,trade_profit,commission,swap,feeso a--by monthexport drops straight into a spreadsheet or tax register. The summary footer shows the account currency when all in-scope accounts share one (e.g.Net P&L 10.00 USD).- P&L components. Net P&L is
trade_profit + commission + swap + fee. Keeping the parts separate shows where a result came from β trading versus broker costs β which the net alone hides. Many tax regimes treat realised trade profit as income and commission/swap/fee as deductible expenses, sopnl --by month --format csvgives per-account, per-month component columns ready for a return; figures are always in the account currency (no home-currency conversion β see Mixed currencies). - Max drawdown is the largest peak-to-trough decline of the realised P&L curve over the selected deals (ordered by time, accumulated from zero), reported signed-negative. It is not account-equity drawdown β it excludes deposits, open positions and starting balance, so it will not match a broker's equity-drawdown figure.
--color auto|always|never(defaultauto): colourise P&L cells and the summary total by sign (green for profit, red for loss).autoenables colour only when writing to an interactive terminal and honours theNO_COLORandTERM=dumbenvironment conventions; output is never coloured when piped or redirected.alwaysforces ANSI codes regardless;neverdisables them unconditionally. On Windows the terminal must already have virtual-terminal processing enabled (Windows Terminal does; oldercmd.exemay not).- Mixed currencies. If the accounts in scope span more than one
currency, combined
ALLrows and the summary are suppressed (n/ain tables,nullin JSON, omitted from CSV) and a warning goes to stderr β the tool never silently sums across currencies. Narrow--accountsto one currency for combined totals.
- Range:
accountsβ balances, equity and freshness per account, plus the snapshot'sgenerated_at.--format table|json|csv(defaulttable).
set-passphraseβ store the snapshot decryption passphrase in the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service). Prompted twice, never echoed.versionβ binary version and supported snapshot schema (also available asmt5-pnl-cli --version).
Both query commands accept --snapshot PATH (overrides
MT5_PNL_SNAPSHOT) and --stale-after (default 2h) β when the
snapshot is older than that, a warning goes to stderr, never stdout,
so machine-output pipelines stay clean. Pass --quiet (-q) to silence
warnings for scripted use; errors still print.
snapshot.json.gz.age βββΊ age decrypt βββΊ gunzip βββΊ JSON βββΊ aggregate βββΊ table / JSON
(ciphertext on disk) passphrase (in memory only)
from keychain
The snapshot is never written to disk decrypted. Per closed deal, net
P&L = profit + swap + commission + fee. A deal with net > 0 is a win,
net < 0 a loss; a breakeven deal counts toward trades but neither
bucket. Sums accumulate at full precision and round only for display.
Profit factor = gross profit / |gross loss|.
What counts as a trade is decided upstream. This CLI sums every deal
in the snapshot's closed_deals and trusts
mt5-pnl-exporter to have
emitted only closing deals β entry legs and cash flows (deposits,
withdrawals) are filtered out there, not here β using MT5's native
signs, where commission, swap and fees are already negative for costs.
The CLI does not re-check this, so if you change that filtering or those
signs in the exporter, the P&L here changes with no error. Keep the two
in step.
schema/snapshot.schema.json is vendored
from the exporter release this build supports. The CLI accepts the same
major schema version and any minor at or below its own, and refuses
anything else with a message naming both versions and which side to
upgrade. mt5-pnl-cli version prints the supported schema version.
The trust boundary is your OS user session β the same as the exporter's.
- The snapshot at rest stays
age-encrypted; this CLI never writes a decrypted copy. Sync services and backups only ever see ciphertext. - The passphrase lives in the OS keychain. There is deliberately no
env var or flag for it: env vars leak via dotfiles, shell history, and
child processes, and scripts don't need one β the binary reads the
keychain itself. (
MT5_PNL_SNAPSHOTcarries only a file path.)
- A compromised user session. Anyone with your OS account can read the keychain entry and run this CLI. The exporter's threat model draws the same line.
- Whatever you do with the output. Tables and JSON contain balances and trade history; treat redirected output accordingly.
See SECURITY.md for scope and private vulnerability reporting.
Issues and PRs welcome β see CONTRIBUTING.md.