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
- Notes
- 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. Point it at the snapshot once via the
MT5_PNL_SNAPSHOTenv var or--snapshotflag, then runmt5-pnl-cli pnl. - 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
--by symbol aggregates across accounts, one row per symbol:
$ mt5-pnl-cli pnl --from 2026-01-01 --to 2026-12-31 --by symbol
SYMBOL P&L TRADES WINS LOSSES
EURUSD 5.00 2 1 1
XAUUSD 10.00 1 1 0
Summary
Performance
Trades 3
...
$ 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": [
{
"group": "2026-01-01",
"group_by": "month",
"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
},
{
"group": "2026-01-01",
"group_by": "month",
"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
group,group_by,account_login,account_label,pnl,trade_profit,commission,swap,fee,trades,wins,losses,gross_profit,gross_loss
2026-01-01,month,111,Trend EA,10.00,13.00,-2.00,-1.00,0.00,3,2,1,14.00,-4.00
2026-01-01,month,,ALL,10.00,13.00,-2.00,-1.00,0.00,3,2,1,14.00,-4.00
--last Nd|Nw|Nm|Ny(default30d)
Range ending today, inclusive β N units ago through today, so30dcovers 31 calendar days. Months and years are calendar-accurate.--from YYYY-MM-DD [--to YYYY-MM-DD]
Explicit range;--todefaults to today. Use instead of--last.--by day|week|month|symbol|magic(defaultweek, weeks start Monday)
Time cuts (day/week/month) group per period and account, with a combinedALLrow per period.symbol/magicaggregate across all in-scope accounts β one row per symbol or magic number, no per-account orALLrow, first columnSYMBOL/MAGIC. (magicis the raw MT5 magic number, commonly one per strategy/EA.) See Mixed currencies.--accounts "Trend EA,Scalper EA"(default all)
Filter by account label (case-insensitive).--format table|json|csv(defaulttable)
tableprints rows plus a Summary block;jsoncarries the same fields per row and in the summary;csvis header + rows only (no summary). See Output shape.--color auto|always|never(defaultauto)
Colour P&L cells and the summary total by sign.autocolours only on an interactive terminal and honoursNO_COLOR/TERM=dumb; output is never coloured when piped or redirected.alwaysforces ANSI codes;neverdisables them. On Windows the terminal must have virtual-terminal processing enabled (Windows Terminal does; oldercmd.exemay not).
Dates, --last and "today" are all interpreted in UTC.
Per-account balances, equity and freshness, plus the snapshot's
generated_at. Takes --format table|json|csv (default table).
Store the snapshot decryption passphrase in the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service). Prompted twice, never echoed.
Binary version and supported snapshot schema. Also available as
mt5-pnl-cli --version.
Both query commands (pnl, accounts) also accept --snapshot PATH
(overrides MT5_PNL_SNAPSHOT) and --stale-after (default 2h) β when
the snapshot is older, a warning goes to stderr, never stdout, so
machine-output pipelines stay clean. --quiet (-q) silences warnings;
errors still print.
Each deal's time is 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 weekly and monthly figures line up
with what your broker statement shows; there is no timezone skew to
correct for. One edge case: from a far-east timezone (e.g. UTC+12) the
UTC day can differ from your local day near midnight.
Net P&L is trade_profit + commission + swap + fee, using MT5's native
signs where commission, swap and fees are already negative for costs.
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, so pnl --by month --format csv gives per-account, per-month
component columns ready for a return. Figures are always in the account
currency (no home-currency conversion β see
Mixed currencies).
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.
The CLI never silently sums across currencies. If the accounts in scope
span more than one, combined ALL rows and the summary are suppressed
(n/a in tables, null in JSON, omitted from CSV) and a warning goes to
stderr. A symbol/magic cut aggregates across accounts and has no
per-account row to fall back on, so it refuses outright (exit 1). Narrow
--accounts to one currency for combined totals.
Every --by cut emits the same JSON/CSV shape: rows carry group (the
period date, symbol, or magic) and group_by (the --by value);
account is the login for per-account time rows and null (JSON) or
empty (CSV) for the combined time row and for every symbol/magic row. CSV
component columns are pnl,trade_profit,commission,swap,fee, so a
--by month export drops straight into a spreadsheet or tax register.
The table summary footer shows the account currency when all in-scope
accounts share one (e.g. Net P&L 10.00 USD).
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.