Orders vs deals vs positions in MetaTrader 5

4 min readMetaKit

Every developer who pulls trading history out of MetaTrader 5 hits the same wall: the numbers don't match the terminal. Trade counts are double what they should be, profits attach to the wrong rows, and "history" turns out to mean something different from what the strategy report shows. The cause is almost always the same — MT5's data model has three distinct objects, and they are not interchangeable.

The three objects

An order is an instruction. "Buy 0.5 lots of EURUSD at market" or "sell 1 lot if price reaches 1.1000". Orders can be pending (limits, stops), can be cancelled before ever executing, and can exist without a single cent changing hands. An order is intent.

A deal is an execution. When an order (or part of one) actually fills, MT5 records a deal: a concrete transaction at a concrete price and time. Deals are facts — they cannot be cancelled, only recorded. Balance operations like deposits also appear as deals.

A position is the net result. The open exposure you currently hold in a symbol, created and modified by deals. Close the position and it disappears from the open list — what remains is the trail of deals that built and unwound it.

Order (intent)  --fills-->  Deal (fact)  --creates/modifies-->  Position (state)

The relationship that matters most: one trade = two deals

A simple round-trip trade — open, then close — produces two deals: an entry deal and an exit deal. Both carry the same position identifier, and that identifier is the thread that ties a trade's lifecycle together.

This is the single most common integration bug: treating each deal as a trade. Count deals and every trade is counted twice; average your "trade profit" over deals and entries (which typically carry zero profit) drag it toward zero. The correct reconstruction is:

  1. Pull the deal history.
  2. Group deals by position id.
  3. Each group is one trade: entry price from the entry deal(s), exit from the exit deal(s), profit summed across the group, duration from first to last.

Partial closes make the grouping non-optional: a position closed in three chunks is one trade with four deals, not three trades and certainly not four.

Profit lives on the exit

Deal-level profit is recorded where money is realized — the closing deal. An entry deal's profit is normally zero (commission and swap are their own fields). If your analytics ever shows a "trade" with zero profit next to a "trade" with double profit, you've split a position's deals into separate trades.

Commission and swap deserve their own mention: they attach per deal, so a trade's true cost is the sum across all deals in the position group. Analytics that read only the exit deal's profit and ignore accumulated swap on long-held positions overstate performance — quietly, and always in the flattering direction.

Netting vs hedging changes what "position" means

MT5 accounts run in one of two position-accounting modes, and the same sequence of orders produces different objects in each:

  • Netting — one position per symbol, ever. Buy 1 lot, buy 1 more: you have a single 2-lot position. Sell 1: still one position, now 1 lot. Deals modify the single position.
  • Hedging — every entry opens its own position. Buy 1 lot twice: two separate positions, individually closeable, each with its own position id. You can hold longs and shorts on the same symbol simultaneously.

Retail forex accounts are usually hedging; exchange-style instruments are usually netting. Code that assumes one mode breaks on the other — the classic symptom is analytics that work perfectly on one broker and produce nonsense on another.

What this looks like over an API

When you read an account through MetaKit, the three objects map to three endpoints: open positions from /v1/accounts/{id}/positions, pending orders from /v1/accounts/{id}/orders, and executed history from /v1/accounts/{id}/deals. The computed analytics at /v1/accounts/{id}/performance already do the position-grouping described above — win rate, profit factor, and drawdown are calculated from reconstructed trades, not raw deals, so the double-counting bug can't happen downstream.

If you build your own metrics from deal history instead, the checklist is short: group by position id, sum profit and costs across the group, treat balance-operation deals (deposits, withdrawals) separately from trading deals, and test on both a netting and a hedging account before trusting a number. The metrics post covers what to compute once the trades are reconstructed correctly.