Why stock and crypto exports need matching

MetaTrader hands you trades. Interactive Brokers, Schwab, Trading 212, Tradovate and every crypto exchange hand you something else entirely — a list of buys and sells that has to be paired up before it means anything.

Two completely different kinds of file

Position exports — MetaTrader, cTrader. One row is a completed trade: opened here, closed there, this is the result. Nothing to reconstruct.

Ledger exports — Interactive Brokers, Schwab, Trading 212, Tradovate, and every crypto exchange. One row is a single fill: you bought 100 shares at this price at this time. Whether that was an entry, an addition to a position, or half of an exit is not stated anywhere in the row.

If you have ever wondered why one journal shows 40 trades from a file and another shows 96, this is the entire reason. The second one counted fills.

What has to happen before the file means anything

The fills must be paired into round trips, and the standard way is FIFO — first in, first out. Buy 100, buy 100 more, then sell 150: the sale closes the first hundred entirely and half of the second.

Everything downstream is produced by that pairing rather than read from the file:

  • the trade count — how many round trips your fills form
  • the entry price — an average across however many fills opened the position
  • the holding time — from the first opening fill to the closing one
  • whether anything is still open — leftover lots with no matching sale

None of these are facts in the export. They are the output of an algorithm, and different algorithms give different answers on the same file.

Where the P&L comes from

Two sources, and the choice matters for anyone trading outside their account currency.

Computed from prices(exit − entry) × quantity. Correct for a single-currency account, such as most Schwab histories.

Taken from the broker's own realised figure — Trading 212 gives a Result column, IBKR a Realized P/L. Where present, this is the better source, because it already accounts for the currency conversion applied at the time of each fill. A price-based calculation on a multi-currency account silently drifts from the account's actual movement.

Our implementation prefers the broker's realised figure when the export carries one, and falls back to prices when it does not.

Rows that are not trades

Ledger exports mix in everything else the account did: deposits, withdrawals, dividends, interest, fees, currency conversions, and corporate actions.

Several of these have a value in the amount column, which is exactly why naive imports go wrong — a dividend read as a trade becomes a small mysterious winner, and a deposit becomes an enormous one. Rows are selected by their action type, never by whether a number is present.

Futures need one more thing

For futures, price difference alone is not money. (exit − entry) × quantity gives you points, and points become currency only through the contract's point value — $50 for an E-mini S&P contract, $20 for a Micro, and so on per instrument.

A ledger that omits it produces P&L figures that look plausible and are wrong by a constant multiple. This is why our Tradovate handling carries a contract specification table rather than treating the price difference as a result.

What can go wrong, and what to check

Partial history. FIFO needs the opening fills. Export three months and the positions opened in month zero have no entry to match against — they appear as unexplained sales or get dropped. If your trade count looks low, export a longer range.

Short positions. Selling first and buying back is the mirror case, and a matcher that assumes buys come first mishandles it.

Symbol changes. A ticker rename splits one position into two unmatched halves.

The quickest sanity check is the trade count. If a tool reports far more trades than you remember taking, it is counting fills. If it reports far fewer, the matching is dropping unmatched fills — and both are worth knowing before you read any statistic built on top.

More in How to export your trades