MT5 execution speed
MT5 execution speed: measuring send-to-fill time
Your EA sent an order. The fill came back late and the price had moved. Before you move VPS or move broker: which parts of that delay can a MetaTrader 5 terminal actually measure, which clock is the number read from, and which parts are invisible from where you stand?
Three different numbers get called "execution speed"
Search for MT5 execution speed and you mostly find descriptions of execution types. Market, Instant, Request and Exchange Execution describe how a price is agreed between you and the trade server, not how long it takes. A broker's advertised average is an aggregate over their whole book, by their definition, in a window they chose. What is left are two numbers you can actually read:
-
Ping to the trade server. Reported continuously as
TERMINAL_PING_LAST, in microseconds. A network round trip with no order attached - a useful check on your connection, and not your fill time. -
Send-to-fill time on your own account. The interval between your EA calling
OrderSendand the server's answer arriving back. The one that is yours, and the one nobody publishes for you.
The MT5 order lifecycle, segment by segment
A market order passes through a fixed sequence. Naming the segments matters, because only some of them leave a timestamp behind - and the ones that do not are the ones people argue about.
-
Your EA decides. A tick arrives,
OnTickruns, anMqlTradeRequestis filled in. All inside your program; MT5 records none of it. - OrderSend is called. The terminal validates the structure locally, serialises the request and hands it to the network stack.
- The request crosses to the trade server. One network leg, whose length depends on where your terminal runs relative to the server.
- The server validates and executes. Session, volume limits, stops level, free margin and filling policy are checked. If the request survives, the server executes it against a price and writes one or more deals.
-
The result crosses back and OrderSend returns. The second network leg carries an
MqlTradeResult: return code, order and deal tickets, filled volume and price, and the server's bid and ask.OrderSendis synchronous - it does not return until that answer arrives or the request times out. -
OnTradeTransaction fires. Every EA on the terminal is notified in stages: the deal
is added, the order moves into history, and a
TRADE_TRANSACTION_REQUESTevent finally delivers the full request and result together.
Steps 3, 4 and 5 - network out, server work, network back - produce no boundary timestamp a terminal can read, because they are separated by events happening somewhere else. What you can observe is where each segment is visible from:
| Segment | Measurable from | Source |
|---|---|---|
| EA decision to the moment of send | Only from inside the EA that sends, and only if it is instrumented | No MT5 field exists |
| Send to result received (whole round trip) | The terminal, as a single interval | GetMicrosecondCount() before and after OrderSend |
| Network out / server work / network back, separately | Not measurable from a terminal at all | No boundary timestamp exists |
| Order accepted on the server | Broker record, broker clock | ORDER_TIME_SETUP_MSC |
| Order completed on the server | Broker record, broker clock | ORDER_TIME_DONE_MSC |
| Deal executed | Broker record, broker clock | DEAL_TIME_MSC |
| Background connection quality | The terminal, continuously | TERMINAL_PING_LAST |
| Confirmation delivered to EAs | The terminal, after the fact | OnTradeTransaction |
Timing methodology, published in full
A latency figure is worth exactly as much as its method. This is the method, in enough detail to reimplement or to disagree with.
Which clock is read
Both timestamps come from GetMicrosecondCount(), the terminal-side monotonic counter
returning microseconds elapsed since the MQL5 program started, read on the machine running the
terminal. It is not a wall clock, it is not synchronised with anything, and it is unaffected by NTP
corrections, daylight-saving changes or the trade server's time. Both readings are taken inside the
same program instance, which is what makes subtracting one from the other meaningful.
Where each timestamp comes from
The capture include, which will be published in full, adds two calls around the
OrderSend you already have:
-
BeforeOrderSend(...)readsGetMicrosecondCount()as its last statement beforeOrderSendis reached, plus the bid and ask fromSymbolInfoDoubleat that instant and the price, volume and stops requested. -
AfterOrderSend(...)readsGetMicrosecondCount()as its first statement afterOrderSendreturns, plus the returnedretcodeand order ticket.
The number
send_to_fill = ea_return_us - ea_send_us (microseconds, reported in ms)
Both readings come from the same counter in the same program, so the subtraction is valid without any clock synchronisation between your terminal and the broker.
What the number includes
Everything between the two readings: the terminal dispatching the request, the outbound network
leg, the server's validation and execution, and the inbound leg carrying the result. Because
OrderSend is synchronous, it is a complete client-observed round trip, not a
hand-off.
What the number does not include
-
Your EA's decision time. Everything your code did before
BeforeOrderSendwas reached sits outside the measurement by design. A fast send-to-fill can still sit behind a slow decision. - Any decomposition. A 180 ms round trip does not say how much was network and how much was the server; no timestamp exists between them. Anyone reporting that split from a terminal is estimating it.
-
Asynchronous sends.
OrderSendAsyncreturns without waiting for the server, so the same subtraction measures only the hand-off. Those values are not comparable to synchronous ones and are not pooled with them. - Server-side executions you never sent. Stop loss, take profit and pending order triggers execute on the server, so no send-to-fill value exists for them at all.
Why two clocks are never subtracted from each other
ORDER_TIME_SETUP_MSC, ORDER_TIME_DONE_MSC and
DEAL_TIME_MSC are recorded too, in milliseconds, from the broker's clock. Differences
between two broker-clock values - order setup to order done - are valid. A difference between a
broker-clock and a terminal-clock value is not, because the offset between those clocks is unknown
and drifts. No figure reported here is built that way.
Rejections are counted, not discarded
A send returning TRADE_RETCODE_REQUOTE or TRADE_RETCODE_PRICE_OFF has a
full round trip and no fill. Those attempts are reported alongside filled ones, because dropping
them quietly removes some of the slowest attempts from the distribution.
Why a tracker EA alone cannot see the pre-send segment
It would be convenient to drop a monitoring EA on a chart and let it time your other EA's orders. You cannot, and it is worth being exact about why.
OnTradeTransaction is the only trade event a separate EA receives, and everything it
carries originates from the trade server. The event fires because the server did something, so by the
time it reaches a bystander EA the order has already left the terminal, crossed the network, been
processed and come back. The earliest observable moment is downstream of the send.
A passive tracker therefore records the aftermath faithfully and the departure not at all. The send
timestamp has to be taken inside the program that calls OrderSend - which means editing
your EA: one #include and two lines around an existing call.
If you cannot modify the EA, you still get every broker-reported field - order and deal timestamps, requested and filled price, spread at the result, commission, fees and return codes. What you lose is the local send timestamp and the pre-send bid and ask, and with them the send-to-fill number itself. That is a real limitation, not one worth papering over with an estimate.
What actually moves the number
Where the terminal runs relative to the trade server
Two of the segments are network legs, and their length follows physical distance and route quality;
TERMINAL_PING_LAST is the cheap proxy for it. Two caveats keep this honest. Shortening
the network legs does not touch the server-side portion, and the round-trip number cannot separate the
two - so whether a move changes your distribution is an empirical question about your account, not a
promise. And stop loss, take profit and pending order triggers already live on the server, where
moving your terminal closer does nothing for them.
Terminal-side delays
An EA runs on a single thread per chart, and the handler must return before the next tick is
processed. A heavy indicator, a wide loop, file I/O, a WebRequest, verbose logging or a
chart carrying thousands of objects all push back the moment OrderSend is reached. None of
it appears in send-to-fill, because it happens before the first timestamp - which is why the two
numbers belong side by side. CPU and memory pressure, several EAs on a small VPS and reconnects are the
same category.
News windows, session boundaries and rollover
The server-side portion is not a constant. Around high-impact releases, order flow spikes, prices move between quote and execution, spread widens, and requotes and off quotes become more frequent - the round trip lengthens for reasons unrelated to your connection. Rollover and session transitions are a smaller version of the same effect, and pooling those windows with quiet mid-session trading gives an average describing neither. Weekends are simpler: with the market closed there is nothing to send, so the Friday close and Sunday reopen each deserve their own bucket.
Order type, filling policy and execution mode
A market order and a pending order are not the same measurement: the pending order's fill is a
server-side trigger with no local send behind it. Under Immediate-or-Cancel or Return filling policies
a request can fill partially, producing several deals with several DEAL_TIME_MSC values -
so "the fill" is not a single moment either.
Execution mode then changes what the same condition looks like. Under Instant and Request Execution
deviation bounds how far price may move before the request returns a requote or rejection;
under Market Execution the price is the broker's, so the same movement arrives as a difference between
requested and filled price instead. Comparing accounts on different modes without saying so is
meaningless.
Establishing a baseline before you blame anything
- Report percentiles, and segment before you summarise. Median, 90th and 95th are the useful shape - one four-second reconnect ruins a mean and barely moves a median. Split by symbol, hour of the server day, order type and volume band, because a single account-wide figure hides every effect described above.
- Carry the sample size and window on every figure. "142 ms" means nothing. "142 ms median over 380 market orders on EURUSD, London session, 6 Jan to 5 Feb" is a claim someone can check.
- Count what did not fill. Requotes, off quotes and other rejections belong in the same report. A distribution built only from successful fills is a survivorship-filtered one.
- Change one variable at a time, and start early. Hold broker, symbol, EA and session constant, then compare the same percentile over a comparable window - a difference measured across two machines in two different weeks is not a broker difference. The comparison you will want is against your own quiet period, and that data exists only if it was recorded before you had a reason to care.
Questions traders ask
Is ping the same as execution speed?
No. Ping is a round trip with no order attached; send-to-fill also contains the server validating and executing your request. Ping is a lower bound, and the gap between the two is where everything interesting lives.
Can I measure send-to-fill without changing my EA's code?
Not the number itself - the send timestamp has to be taken inside the program that calls
OrderSend, and no MT5 event surfaces it to a separate EA. Without instrumentation you
can still record every broker-reported field: order and deal timestamps, requested versus filled
price, and spread at the result.
Does the number include the broker's processing time?
Yes, and it cannot be separated out. The round trip contains both network legs and everything the server did in between, with no timestamp marking the boundaries. Any figure claiming to split them from terminal-side data is inferring the split, not measuring it.
Will a VPS closer to the broker make my fills faster?
It shortens the network legs, which are part of the round trip. It does not change the server-side portion, and it does nothing for stop loss, take profit or pending order triggers, which already execute on the server. Whether your own distribution shifts is something to measure before and after with everything else held constant, rather than to assume.
Related pages
Waitlist
We are building the part that records this
ExecGuard records per-trade execution data on MT5 accounts - send-to-fill time by the method above, requested versus filled price, spread at entry and exit, return codes, commission and fees - and summarises it at the account level. The tracking EA will be published in full, so the method above can be checked line by line rather than taken on trust.
It is not open to everyone yet, and the waitlist is genuinely a waitlist: we are trying to learn which version of this problem people actually have before building more. If you join, tell us what you observed - which symbol, which session, and what you have already ruled out.