Sooner or later you need your MT5 trade history in a spreadsheet: for a tax accountant, a prop firm review, a trading journal, or just to answer a question MetaTrader's own report doesn't. There are two ways to get it out, and which one you want depends on how many accounts you have and how often you need it.
Method 1: export from the MT5 terminal
In the desktop terminal:
- Open the Toolbox (Ctrl+T) and go to the History tab.
- Right-click anywhere in the list and set the period: All History, or a custom range.
- Right-click again and choose Report, then Open XML (MS Office Excel 2007) for a spreadsheet, or HTML for a browser report.
- Save the file and open it in Excel, Google Sheets or LibreOffice.
You can also switch the History tab between Positions, Deals and Orders views before exporting. For most purposes the Deals view is the one to export: every execution, with commission and swap on its own row.
This works well for one account, once. It has limits:
- It's manual. Every export is a few clicks per account, per period.
- Desktop only. The MT5 mobile apps can't export history.
- The format is a report, not a dataset. The XML has summary blocks, merged headers and formatting around the trades, so you'll clean it up before you can pivot or chart it.
- Times are the broker's server clock, which usually isn't your timezone or UTC. The server time post has the details.
Method 2: export trade history with a script
If you export regularly, or have more than one account, a script is less work from the second time on. With an account connected to MetaKit, the deals endpoint returns the full history as clean JSON, and turning it into CSV is a few lines. Use a read-only API key; exporting never needs to trade.
"""Export MT5 deal history to CSV (opens directly in Excel)."""
import csv
import os
import sys
import requests
BASE = "https://api.metakit.cloud/v1"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['METAKIT_KEY']}"
FIELDS = ["ticket", "positionId", "order", "time", "symbol", "type", "entry",
"reason", "volume", "price", "profit", "commission", "swap", "fee",
"magic", "comment"]
def deals(account_id: int, date_from: str | None = None, date_to: str | None = None):
"""Every deal for one account, across all pages."""
params = {"limit": 100, **({"from": date_from} if date_from else {}), **({"to": date_to} if date_to else {})}
page = 1
while True:
r = s.get(f"{BASE}/accounts/{account_id}/deals", params={**params, "page": page}, timeout=60)
if not r.ok:
sys.exit(f"account {account_id}: {r.status_code} {r.json()['error']['code']}")
body = r.json()
yield from body["data"]
if page >= body["total_pages"]:
return
page += 1
def export(account_id: int, path: str, **kw):
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=FIELDS, extrasaction="ignore")
w.writeheader()
n = 0
for d in deals(account_id, **kw):
w.writerow(d)
n += 1
print(f"account {account_id}: {n} deals -> {path}")
if __name__ == "__main__":
export(int(sys.argv[1]), f"mt5-history-{sys.argv[1]}.csv")Run python export.py 2 and you get mt5-history-2.csv with one row per
deal. Pass date_from="2026-01-01", date_to="2026-12-31" to export for a
calendar year.
Excel and CSV encoding: if Excel shows broken characters in comments,
change encoding="utf-8" to encoding="utf-8-sig". The extra byte-order
mark tells Excel the file is UTF-8.
Reading the columns
Each deal is one execution. The columns that confuse people:
entry:inopened a position,outclosed it. Realised profit lives onoutdeals;indeals usually show profit 0 and only carry commission.typeis the deal's direction, not the position's. Closing a long is aselldeal.positionIdties the entry and exit deals of one position together. Group by it to get one row per trade.reason: why the deal happened, e.g.stop_loss,take_profit,client(manual),expert(an EA).- Deposits and withdrawals appear as deals too, with no symbol. Filter them out for trading analysis and keep them for growth calculations.
The orders, deals and positions post explains why MT5 splits one trade into several records, and the costs post covers what commission, swap and fee each include.
One row per trade, not per deal
For most analysis you want trades, not executions. In Excel, a pivot table on
positionId with profit, commission and swap summed gets you there. In
pandas it's one groupby:
import pandas as pd
df = pd.read_csv("mt5-history-2.csv", parse_dates=["time"])
df = df[df["symbol"].notna()] # drop deposits / withdrawals
trades = df.groupby("positionId").agg(
symbol=("symbol", "first"),
opened=("time", "min"),
closed=("time", "max"),
volume=("volume", lambda v: v[df.loc[v.index, "entry"] == "in"].sum()),
profit=("profit", "sum"),
commission=("commission", "sum"),
swap=("swap", "sum"),
)
trades["net"] = trades["profit"] + trades["commission"] + trades["swap"]
trades.to_excel("mt5-trades.xlsx") # needs: pip install openpyxlExporting many accounts at once
If you manage several accounts, money manager or prop trader with multiple
challenges, loop over them. GET /v1/accounts lists every account you've
connected:
page = 1
while True:
r = s.get(f"{BASE}/accounts", params={"page": page, "limit": 100}).json()
for a in r["data"]:
export(a["id"], f"mt5-history-{a['account_number']}.csv")
if page >= r["total_pages"]:
break
page += 1One run, one CSV per account, named by MT5 login number. To combine them for a portfolio view instead, the multi-account P&L post builds one table across all of them.
Which method to use
- One account, occasionally: the MT5 terminal's Report export is fine.
- Regularly, or several accounts: script it once and never click through the History tab again.
- Continuously: don't export at all; keep a live journal or dashboard that fills itself.
Whichever you use, keep the raw deals somewhere. Summaries can always be rebuilt from deals; deals can't be rebuilt from summaries.