The strategy report said +$14,200 for the quarter. The balance had gone up by
$11,900. Nobody had withdrawn anything. The missing $2,300 was commission and
swap, and the report was summing profit on exit deals and nothing else.
That gap is the most common accounting error in MT5 tooling, and it always errs in the flattering direction. The fix is knowing where MT5 books each cost, which turns out to be three separate fields in three separate places.
The cost fields on a deal
Every deal in MT5 history carries four money fields, all in the account currency, all signed (a charge is negative):
profit: the price P&L of that deal. Zero on an entry deal, the realised gain or loss on an exit deal.commission: the broker's per-trade charge. Where it lands is up to the broker.swap: the overnight financing charge or credit accumulated while the position was open.fee: an extra charge some brokers and exchanges apply. Usually zero on retail forex, present on exchange-traded and some crypto instruments.
Spread isn't on the list. It's the fourth cost and the invisible one: you pay
it by buying at the ask and marking to the bid, so it is already inside
profit and never appears as a line of its own.
The rule that follows from the fields is short:
trade net = Σ over the position's deals of (profit + commission + swap + fee)
Sum across every deal that shares the position id, not just the exit. The orders, deals and positions post covers why grouping by position id is the first step in any history code; this post is about what to sum once you've grouped.
Commission is booked where the broker feels like it
There is no MT5 standard for this. Common patterns on the brokers we've seen:
- Split. Half on the entry deal, half on the exit deal. A $7-per-lot
round-turn appears as
commission: -3.5on theindeal and-3.5on theoutdeal. The most common raw-spread setup. - All on entry. The full round-turn is charged when the position opens, and
the exit deal shows
commission: 0. This is the one that makes a still-open position look like it has already lost $7. - Zero. Spread-only accounts. The cost is real, it's just hidden in the fill price.
The split pattern is where dashboards go wrong. Sum commission from exit deals only and you've halved the cost. Sum from entry deals only and a strategy that's open across a month boundary gets its cost booked in the wrong month.
Partial closes make it messier: on a split-commission broker each partial
out deal carries commission proportional to its volume, so a position closed
in three chunks has four commission entries.
Swap accrues on the position and lands on the exit
Swap is the financing cost of holding a leveraged position overnight: the interest differential between the two currencies, plus the broker's markup. Long a high-yield currency against a low-yield one and it can be a credit. Most of the time, for most retail pairs, it's a charge.
MT5 applies it at the broker's daily rollover (server midnight on most
brokers, which usually lines up with 5pm New York; the
server time post explains why that's
not midnight anywhere you live). Each rollover adds that night's swap to the
open position. You can watch it grow: the swap field on an open position in
/v1/accounts/{id}/positions is the running total.
When the position closes, that accumulated total is written onto the exit
deal's swap. Entry deals have swap: 0. Close in parts and each out deal
takes the share belonging to the volume it closed, with the rest staying on the
open remainder.
Then there's Wednesday. Spot forex settles two business days after the trade, so a position held over Wednesday night is financed across the weekend, and the broker charges three nights' swap in one go. Triple swap Wednesday is why a Monday-to-Thursday hold costs more than a Thursday-to-Monday hold, despite the latter spanning the weekend. For index and crypto CFDs many brokers triple on Friday instead; the symbol specification says which day, per symbol.
A worked example: three nights in EURUSD
Buy 1.00 lot EURUSD at 1.0850 on Monday 10:00, close Thursday 10:00 at 1.0910. Broker charges $3.50 per lot per side, and long EURUSD swap is $7.20 per lot per night, tripled on Wednesday.
| deal | entry | profit | commission | swap | fee |
|---|---|---|---|---|---|
| Mon 10:00 buy 1.00 @ 1.0850 | in | 0.00 | -3.50 | 0.00 | 0 |
| Thu 10:00 sell 1.00 @ 1.0910 | out | +600.00 | -3.50 | -36.00 | 0 |
Sixty pips on one lot is $600 of gross profit. Swap is Monday $7.20, Tuesday $7.20, Wednesday $21.60, total $36.00. Net:
gross = 600.00
commission = -7.00
swap = -36.00
net = 557.00
Costs are $43, which is 7.2% of the gross. The dashboard that reads only exit
profit reports $600, and the one that reads exit profit + commission + swap
reports $560.50 because it missed the entry-side commission. Both are wrong,
one of them subtly.
Now shrink the trade. A scalper taking 6 pips on the same lot has $60 of gross against the same $7 of commission: 11.7% before spread, and with a 0.3-pip spread already inside the price it's closer to 16%. Same broker, same instrument, and the cost ratio is more than double.
Swap-free accounts aren't free
Islamic or swap-free accounts set swap to zero on every deal, because
charging interest is the thing being avoided. The broker still needs to be paid
for carrying the position, so most replace it with an administration fee after
some number of nights, and how that fee is booked varies: some brokers put it
in commission on the exit deal, some add a separate balance-type deal to the
account, some widen the spread on those accounts instead. Read the terms, then
check the history after your first multi-day hold to see which one you got.
The practical consequence for cost analysis: on a swap-free account a zero
swap column tells you nothing about carrying costs. Look at commission and
at non-trade deals too.
Measuring whether costs are eating the edge
The number to track is costs as a share of gross winnings: total commission, swap, and fee across all closed trades, divided by the gross profit of the winning trades. It answers the question "of what the strategy actually makes when it's right, how much goes to the broker?"
import os
from collections import defaultdict
import requests
BASE = "https://api.metakit.cloud"
HEADERS = {"Authorization": f"Bearer {os.environ['METAKIT_KEY']}"}
ACCOUNT_ID = 2
def all_deals(account_id: int, since: str) -> list[dict]:
page, out = 1, []
while True:
res = requests.get(
f"{BASE}/v1/accounts/{account_id}/deals",
params={"from": since, "page": page, "limit": 100},
headers=HEADERS,
).json()
out += res["data"]
if page >= res["total_pages"]:
return out
page += 1
# Balance operations (deposits, withdrawals) are deals too. Skip them.
deals = [d for d in all_deals(ACCOUNT_ID, "2026-01-01") if d["type"] in ("buy", "sell")]
trades = defaultdict(lambda: {"gross": 0.0, "costs": 0.0, "closed": False})
for d in deals:
t = trades[d["positionId"]]
t["gross"] += d["profit"]
t["costs"] += d["commission"] + d["swap"] + (d.get("fee") or 0)
if d["entry"] in ("out", "out_by"):
t["closed"] = True
closed = [t for t in trades.values() if t["closed"]]
gross_wins = sum(t["gross"] for t in closed if t["gross"] > 0)
total_costs = -sum(t["costs"] for t in closed)
net = sum(t["gross"] + t["costs"] for t in closed)
print(f"closed trades {len(closed)}")
print(f"gross on winners {gross_wins:>12,.2f}")
print(f"costs {total_costs:>12,.2f} ({total_costs / gross_wins:.1%} of gross winnings)")
print(f"net {net:>12,.2f}")There's no universal threshold, but a strategy handing a third of its gross winnings to the broker is running on a knife-edge: a small widening of spread or a swap-rate change flips it negative with no change in the trader's decisions. Under 10% and costs are a rounding error on the edge. In between, it depends on how consistent the edge is, which is what the profit factor post is about.
Reading this over the API
The deals endpoint returns each deal with profit, commission, swap, and
fee exactly as MT5 records them, plus entry (in, out, in_out,
out_by) and reason so you can tell a stop-loss exit from a manual one. Note
the position id on a deal comes back as positionId; the rest of the deal is
as shown above. The endpoint is paginated with a 100-row limit per page, and
history is cached for about a minute server-side, so a loop like the one above
is the right shape and hammering it faster gains nothing.
If you don't want to do the arithmetic yourself, /v1/accounts/{id}/performance
already sums all four fields per position group before computing net_profit,
profit_factor, and the rest. It does not report the cost ratio separately,
which is why the script exists. Field-level detail is in the
API reference.
Pull one month of your own deals, run the script, and compare the net to your balance change over the same window. If they don't match to the cent, something between the terminal and your dashboard is dropping a field.