MT5 history_deals_get in Python: from deals to positions

Deals are executions; traders think in positions. Here is how to convert one into the other with the Python package, and the traps that give wrong numbers.

What this is for

The official MetaTrader5 Python package lets a script read your account history from a running terminal. The function history_deals_get returns deals, which are individual executions. If you want one row per closed trade, you have to turn deals into positions. This page shows the approach AI Trading Journal itself uses, and the traps along the way.

The basics

Run the MetaTrader 5 terminal on the same Windows PC and log in. In Python: initialise the connection, request deals for a date range, and shut down when finished. Dates can be Python datetime objects.

import MetaTrader5 as mt5
from datetime import datetime

if not mt5.initialize():
    raise SystemExit(f"initialize failed: {mt5.last_error()}")

deals = mt5.history_deals_get(datetime(2026, 1, 1), datetime.now()) or ()
for d in deals[:5]:
    print(d.ticket, d.position_id, d.symbol, d.type, d.entry, d.volume, d.price, d.profit, d.commission, d.swap)

mt5.shutdown()

Gotchas that cause wrong numbers

From deals to one row per position

CLOSING = (mt5.DEAL_ENTRY_OUT, mt5.DEAL_ENTRY_OUT_BY, mt5.DEAL_ENTRY_INOUT)
trades, seen = [], set()

for d in deals:
    if d.type not in (mt5.DEAL_TYPE_BUY, mt5.DEAL_TYPE_SELL) or d.entry not in CLOSING:
        continue
    if d.position_id in seen:
        continue
    seen.add(d.position_id)

    pos = mt5.history_deals_get(position=d.position_id) or ()
    entries = [x for x in pos if x.entry == mt5.DEAL_ENTRY_IN]
    exits = sorted((x for x in pos if x.entry in CLOSING), key=lambda x: x.time)
    if not entries or not exits:
        continue

    trades.append({
        "position": d.position_id,
        "symbol": entries[0].symbol,
        "volume": sum(x.volume for x in entries),
        "open": entries[0].time,
        "close": exits[-1].time,
        "profit": sum(x.profit + x.commission + x.swap for x in exits),
    })

This is a simplified version of the logic, written for explanation. Check field names against the version of the package you have installed, and test on a demo account.

Then what?

Once you have positions, calculating statistics is straightforward; see MT5 trading statistics, and note how hedging and netting change the picture in MT5 hedging vs netting. If you would rather not write and maintain this, AI Trading Journal does it for you and saves each position in your own journal on your PC (with an optional copy in your own Notion workspace).

Prefer not to dig through the History tab? AI Trading Journal reads your closed trades from your own MT5 terminal and saves one entry in your own journal on your PC (and one Notion row, if you connect Notion) per closed position about every 5 minutes, with open and close time, size, prices, stop, target and profit. It is read-only, needs no broker login, and is Windows-only, with a 7-day free trial and a one-time $19.99.

This page is technical education, not trading or investment advice. The MetaTrader5 package is provided by MetaQuotes; we are not affiliated with MetaQuotes.

Include commission on entry deals

The simplified example above sums profit, commission and swap on the exit deals only. Some brokers charge commission on the entry deal as well as, or instead of, the exit. To get the full cost of a position, add up the commission and swap of every deal that belongs to it, not only the exits:

total = sum(x.profit + x.commission + x.swap + getattr(x, "fee", 0.0) for x in pos)

Here pos is the list of all deals returned for the position. The fee field exists in the package for some accounts, so the example reads it defensively. Compare the totals with the Profit, Commission and Swap columns in your terminal for a few trades before you trust the script.

Getting stop loss and take profit

Deals do not carry the stop and target. Orders do. This snippet takes the most recent non-zero stop and target set on the orders of a position:

orders = mt5.history_orders_get(position=position_id) or ()
orders = sorted(orders, key=lambda o: o.time_setup, reverse=True)
sl = next((o.sl for o in orders if o.sl), 0.0)
tp = next((o.tp for o in orders if o.tp), 0.0)

If you moved the stop, this returns the last one set, which may not be the one that was active when the trade closed. For a planned-versus-actual comparison you would also keep the first non-zero stop. Test the result against the trade's detail in the terminal.

Converting server time

The time on a deal is a number of seconds that reflects the broker's server clock. To work in your own time zone, treat the number as UTC to get a datetime, then subtract the server's offset from UTC, which you can read from your broker's documentation or work out from a known trade:

from datetime import datetime, timezone, timedelta
SERVER_OFFSET_HOURS = 3  # example only: check your broker
t = datetime.fromtimestamp(d.time, tz=timezone.utc) - timedelta(hours=SERVER_OFFSET_HOURS)

Some brokers shift their offset for daylight saving, so a fixed number can be wrong for part of the year. If you group trades by session, check a handful against the terminal around the dates when your broker's clocks change.

Troubleshooting

SymptomLikely causeWhat to check
initialize() returns FalseThe terminal is not running, not logged in, or the package cannot find itStart MT5 and log in; print mt5.last_error()
history_deals_get returns None or an empty tupleNo deals in the range, a wrong date range, or an errorWiden the dates; print mt5.last_error()
Positions with no entry dealThe position opened before the date rangeFetch by position ID so the range does not matter
Totals differ from the terminalCommission or swap missing, or balance deals includedSum all deals of each position; filter balance types
Times look hours offServer time not convertedCompare with the History tab for one trade

Test it against the terminal

Before you rely on any script, pick five closed trades and compare symbol, volume, open and close time, prices and net result with the History tab (Positions view). Include one trade with a partial close and one with commission. A script that matches the terminal on those is probably right; one that does not will give you statistics you cannot trust. For what to calculate once the positions are right, see MT5 trading statistics.

To understand the identifiers you group by, read MT5 deal, order and position IDs and the magic number for splitting EA and manual trades.

Frequently asked questions

How do I get MT5 trade history in Python?
Initialise the MetaTrader5 package with a running, logged-in terminal and call history_deals_get with a date range, then group the deals by position_id.
Does the MT5 deal profit include commission and swap?
No. Profit, commission and swap are separate fields; add them for the true result.
How do I get stop loss and take profit in Python?
They are stored on orders, not deals. Use history_orders_get with the position ID.
What is the difference between a deal and a position in MT5?
A deal is one execution. A position is the trade that one or more deals open, change and close. Group deals by position_id to get trades.

Related guides

Skip the code: journal MT5 positions automatically

Free for 7 days, no card required. After that, a one-time $19.99 purchase. No subscription. Also on the Microsoft Store.

One-time $19.99, no monthly fee · your MT5 login never leaves your PC · trades go into your own journal on your PC, not through our servers.

Start free 7-day trial