How every number on this site was taken
Every figure here comes off a measurement rig we built and run ourselves. Bid and ask are sampled from our own accounts, continuously, and stored raw. A published figure is an aggregate over the stated window — nothing is smoothed, nothing is rounded in our favour, and nothing is copied from a broker's marketing page and dressed up as a finding.
This page is longer than a methodology note usually is, on purpose. A comparison table is only worth the method behind it, and the parts of a method that matter are the parts where it can go wrong. Those are written out below, including the mistakes we made and what they cost us.
- Instrument
- EUR/USD
- Window
- 2026-10-12T19:08:17Z → 2026-10-13T17:13:27Z, continuous, UTC
- Sampling
- 1 second, MT5 tick stream
- Accounts
- our own, opened the same week
- Profile
- 10 lots a month
The rig
Two legs feed one store.
The API leg. Where a broker exposes a programmatic price feed, we subscribe to it directly and receive every quote the server sends, each one stamped by the server as it is produced. There is no polling interval to miss a tick between, and no chart to read numbers off. The connection is held open by a service that reconnects with backoff when the line drops and renews its own credentials before they expire, so a quiet night is a quiet night rather than a gap nobody noticed.
The terminal leg. Most brokers do not expose an API, so for those we run their own MetaTrader 5 terminal on a dedicated machine and read the tick stream out of it locally. That machine does not sleep, does not hibernate, and does not stop when it is running on battery — all three of which we had to turn off explicitly, because the defaults would have cut exactly the overnight hours this site exists to measure.
Both legs write into the same store in the same shape, so the aggregation that produces a published number is written once and tested once, rather than once per broker.
What a tick is here, and the three unit traps
A tick is a bid and an ask with a timestamp. That sounds like it cannot be got wrong. It can, and we got it wrong three times in the first fortnight — each time producing numbers that looked entirely plausible.
Pip size. A pip is 0.0001 on most currency pairs and 0.01 on yen crosses. Our first conversion derived it from the symbol's digit count with an off-by-one exponent, which scaled every published spread by a factor of a thousand. It is caught now by a conversion that is checked against known answers on five-digit and three-digit symbols before it is allowed to run, and the broker's own declared pip position is compared with ours — when the two disagree, the symbol is skipped rather than published.
Price scale belongs to the wire, not to the symbol. One feed sends every quote as an integer scaled by the same power of ten regardless of how many digits the symbol quotes. Storing the symbol's digit count alongside those integers made a yen pair read a hundred pips wide. What catches this now is not a rule but a sanity corridor on the result, plus a stored live quote in the test suite as a regression.
Two numbers in one record can be in different units. An execution price returned by an order is a plain decimal; the quotes streaming alongside it are scaled integers. An order probe therefore compares its own fill against the quote stream and, when the two disagree by more than a couple of per cent, records the result as suspect instead of printing a converted figure.
Metals, indices and crypto have no settled pip convention, so the conversion refuses them outright rather than quietly applying the currency rule. Their ticks are still collected; only the published conversion is withheld until a convention is fixed deliberately.
Clocks
A terminal stamps each tick with the broker server's time, not UTC. One broker in this set runs three hours ahead. Mix that into a table of spread by hour of day and every row is three hours out of place while every number in it stays perfectly believable — the illiquid rollover window lands in the middle of the London session and nobody can see anything wrong.
The offset is therefore measured at every start, from fresh ticks, rather than written down as a constant: it follows the broker's own daylight-saving switch, not ours. Three guards sit around that measurement, because measuring it is the dangerous part:
- The market calendar. Outside trading hours nothing is measured and nothing is written. With the market shut, every quote a terminal holds is the Friday close, so what a naive measurement returns is not a time zone at all — it is the age of a frozen quote.
- A plausible range. No broker server sits more than fourteen hours from UTC. A reading beyond that is refused rather than used.
- Tick freshness. After the offset is subtracted, a tick we are about to store must be seconds old. This is the guard that holds whatever the offset says, and it is the one that finally closed the hole.
That third guard exists because the first two were not enough. One Saturday the rig measured a frozen Friday quote, got a figure that happened to round to exactly sixteen hours, passed the whole-hour check on the way through, and stamped a Friday price with a Saturday time. The age of a stale quote lands on a round hour roughly once an hour, so this was not bad luck — it was a hole waiting for the clock. Four rows were affected and are quarantined. A test reproducing that exact reading now runs on every change.
Spread, and why zero can be true
Spread is the ask minus the bid, converted to pips at the symbol's own scale, averaged over the hour it falls in. Ticks sometimes carry one side alone, so each side is carried forward and a spread is only computed once both have been seen.
On a raw or commission-based account the spread is genuinely zero a great deal of the time: the broker is not paid through the spread there, it is paid through a per-lot commission. Our first sanity corridor had a lower bound and would have thrown that away as a units error — it would have rejected the truth and kept nothing. The lower bound is gone. What remains is an upper bound and a refusal of negative spreads, which are impossible rather than merely surprising.
This is also why a table of raw spread alone is close to useless for deciding where to trade, and why this site leads with cost of ownership instead. A zero spread with a commission attached can cost more over a year than a wider spread with none.
Is the feed even real?
This is the question that changed the shape of the project, and it is the one most comparison sites never ask.
A demo feed is not obliged to be the market. It can be a simulator that emits a fixed spread, or one that steps between two or three values, and from a chart it is indistinguishable from a live quote. We found exactly that on one of our own accounts: across hundreds of thousands of observations, one symbol produced a single spread value, another produced two, with nothing in between. A morning's worth of comparison built on that feed was wrong, and we had already written it down before we checked.
So every series is now audited before any figure from it is published, over the whole population of observations rather than a sample. The test is not how many distinct values appear — our first attempt used that and would have condemned a genuine raw feed that sits at zero most of the time. The test is continuity: a real spread passes through neighbouring values on its way between levels; a fixture jumps. A series that fails is not published at all, and the broker it belongs to is not ranked.
We would rather show a table with fewer brokers in it than a table where one column is a simulator.
The hour of the day, and why it is a separate column
An advertised spread is a statement about one part of the day. The same account at the same broker is a different product at 22:00 UTC, when the New York session has closed, the Asian session has not opened and the banks quoting the interbank market step back while positions roll to the next value date.
So the headline spread on this site is the liquid session only, 07:00–20:00 UTC, and the rollover band is measured and printed beside it instead of being folded in. The reason is arithmetic rather than taste: on a raw account the session spread sits near zero and the rollover band is one to two pips, so an average over the whole clock is dominated by three hours nobody trades in. Measured on this rig, that single choice moved one account's figure by a factor of a hundred and reordered the table — a comparison can be wrong without a single number in it being wrong.
Each hour is time-weighted: a quote counts for as long as it stood, capped at a few seconds, so a gap in the recording cannot vote and a burst of ticks in one volatile minute cannot either. Hours are then averaged with equal weight, and only the hours every published account recorded are used — otherwise the comparison is between collection schedules rather than between brokers.
What a broker does with an actual order — the fill, the rejection, the slippage in the seconds around a release — is measurable on the same accounts, and when this rig produces those figures they will appear as their own columns with their own windows. There is no column for them today.
Cost of ownership
The headline figure is not a spread. It is what a stated trading profile costs over a year on each account: the spread paid across the traded volume plus the commission on that volume. The profile is printed on the page that uses it, so the number can be recomputed by anyone who disagrees with our assumptions, and the two components are printed separately so the sum can be taken apart.
Commission is taken from the broker's published schedule and verified against an actual fill where we have one — on our own FxPro account the schedule implies a per-million rate, and a real trade confirmed it to the cent. Where a schedule and a fill disagree, the fill wins and the discrepancy is noted.
Where every number comes from
Not every figure on this site is a measurement, and the ones that are not say so at the number, not in a footnote. There are three classes, and brokers are only ranked against others in the same class — a broker we measured and a broker quoted from its own price list are not competing in one column.
- our measurementour own account, sampled on our rig
- third partypublic live spreads from a third party
- stated by the brokerthe broker's own published terms
- derivedcomputed from the spread we measured and the commission its source states - never a figure of its own
A figure with no declared class is not printed as a number at all: the build refuses to publish it. That rule exists because an undeclared source silently reads as “we measured this”, which is the most expensive kind of quiet mistake a site like this can make.
Coverage, gaps and bad readings
A month of data with a hole in it is not a month of data. Coverage is therefore reported as the share of calendar market hours in the window that actually hold samples — not as the count of hours of the day we have ever seen, which is a number that can only go up and which hid a multi-day outage from us for exactly as long as we trusted it.
When a reading turns out to be wrong, it is flagged rather than deleted, and every query that feeds a published figure excludes flagged rows. Two reasons: a tick cannot be re-collected, so throwing one away destroys evidence permanently; and a record of what the rig did wrong is worth keeping, because the next fault usually rhymes with the last one.
An hourly watchdog compares the age of the newest row in each series against the market calendar and raises an alarm when a series goes quiet during trading hours. It found its first real outage within an hour of being switched on — a collector that had been dead for three and a half days while every service around it reported itself healthy.
What is on this site, and what is therefore absent
The site carries two kinds of figure and no third: what our rig measured, and what a named source states with the page and date it was read from. Withdrawal times, support response, execution quality — measuring those honestly means funding an account at every broker and placing real orders. Until we do, they are not here. An empty column is not published, and a broker's own claim is never promoted into the gap.
The same rule governs the table itself. A broker enters it when our own accounts produced a spread that moves; one whose demo feed turned out to quote a fixed price does not get a row, however large its name. And nothing is averaged across account types: a raw account and a standard account at the same broker are different products at different prices, and a comparison that mixes them is not a comparison.
Where the money comes from
FxPro pays us a commission when someone opens an account through our link, and it is the only such arrangement on this site. It is not in the comparison tables. We opened demo accounts on both of its platforms and sampled them exactly as we sample everyone else, and the price we recorded never moved: on cTrader EUR/USD sat at exactly 0.4 pips through 255,304 samples of the London session, on MT5 at exactly 0.2 pips through 123,356. A price that never changes is a fixture inside a demo server rather than a market, so there is no FxPro figure of ours to print, and we do not reprint its marketing numbers to fill the hole. It appears as a sponsor card that says this, and the brokers in the tables are measured and unlinked.
This is the whole reason the method above is written out at this length. A site with a commercial interest in one of the names it compares has exactly one way to be worth reading, and that is to publish how the numbers were taken, in enough detail that a sceptical reader can find the places we could have cheated and check that we did not.