Server Stop vs Virtual Stop on MT5 Gold: 354 Paired Orders, No Slippage Edge Worth Taking, One Wrong Assumption
This is the tenth entry in the incubation diary for my gold breakout EA. The EA trades XAUUSD on M15 on a demo account at XM: during 10:00 to 13:59 UTC it keeps a buy stop at the highest high and a sell stop at the lowest low of the last 128 closed bars, with an initial stop of 0.5 ATR, a trailing exit and a 480-minute time stop. Its entries are server-side stop orders. Fills dated 7 October or later are still sealed for the pre-registered slippage test described in the seventh entry, so there are no trade results from the EA in this entry either.
Instead, this entry keeps a promise. In the comments on the seventh entry, a reader asked whether I had tried adding a random latency buffer to my backtest to reproduce the cost tail. I answered that I would rather measure the delay than tune one, that a separate demo test was already placing server-side stops and EA-watched virtual stops at the same levels and times, that it was pre-registered with a fixed end date, and that I would write it up when it closed. It closed on 15 October. Here is what it found, including the part where one of my own modelling assumptions turned out to be wrong.
The question
There are two ways to enter on a breakout of a level. A server-side stop order sits on the broker's server and triggers there. A virtual stop is a level the EA keeps in memory: the EA watches the ticks, and when a tick reaches the level it sends a market order. Traders argue about which is better. The case for virtual stops is usually that the broker cannot see your level, and that a market order sent at the touch might fill closer to the level than a stop that the server fills at its own convenience.
My own reason for asking was narrower. For some time my tick replays of gold stop entries at this broker have assumed that a triggered stop fills at the quote about 0.25 seconds after the first tick that touches the level. The only check it had ever had against this broker was six stop fills from an earlier demo pilot, whose slippage looked consistent with it. Nothing else. If the server really waited a quarter of a second, a virtual stop sent from my PC might beat it, and that would be one of the few cheap changes left on the execution side. If the assumption was wrong, the replays that use it would need correcting either way.
The design, fixed before the first order
The test ran as a separate probe EA on a separate XM demo account, on GOLD# with a fixed 0.01 lot. It did not trade a strategy. Each cycle began with a small market order at a random moment, held for ten seconds and closed, as a baseline for plain market-order slippage. The probe then picked a level at a random distance of $0.30 to $2.00 from the current price, in a random direction, and did one of three things with it. In 60% of cycles it placed both a server-side stop and a virtual stop at the same level at the same moment, a pair. In 20% it placed only the server-side stop, and in 20% only the virtual stop. Whatever filled was closed as soon as the cycle was decided, and the probe waited 8 to 28 minutes before the next cycle. It did not avoid news releases.
The solo cycles are there for one specific reason. Both orders in a pair come from the same account. If the server handles one account's orders one at a time, the virtual stop's market order could be queued behind the server stop's fill and look slower and worse than it really is. Comparing the virtual stops from pairs with the solo virtual stops measures that queueing effect directly instead of assuming it away.
Slippage for each order was measured against the level, signed so that a positive number is worse for me. The main statistic was the paired difference: slippage of the server stop minus slippage of the virtual stop, so a positive difference means the virtual stop did better. The decision rule was written down in advance with four possible outcomes. The one that mattered was outcome 1: the virtual stop counts as practically better only if the mean difference is at least $0.15 per ounce and the lower end of its 95% confidence interval is above zero. The confidence interval clusters by UTC day, because orders on the same day are not independent. The $0.15 threshold came from the earlier pilot, whose stop fills slipped by a median of $0.16 and a mean of $0.22; an improvement smaller than that would not be worth a change in how the EA enters.
The stopping rule was 300 complete pairs, at least 100 of each solo arm and at least 10 UTC days with a complete pair, or the Friday close on 16 October, whichever came first. Until then the daily collection script reported counts and sanity checks only, never the slippage of either arm, apart from a first-day check that the first twenty pairs had plausible values in every column, with no comparison between arms. The readout script was hashed when the plan was written, and the next day it was run once, without arguments, after confirming the hash matched.
The data
The probe reached its targets at 16:08 UTC on 15 October, a day before the 16 October deadline I gave in the ninth entry, and stopped itself. It ran 1,035 cycles, 687 of which got as far as placing a level, with complete pairs on 16 separate UTC days (three of those are short Sunday-evening sessions with one to three pairs each). The pair cycles produced 356 complete pairs, 71 levels that were never touched, and no incomplete pairs, meaning no case where one order of a pair filled and the other did not. Two pairs were excluded under a rule written in advance, for a stale quote at the moment of detection, which left 354 pairs. The solo arms gave 101 usable server stops and 100 virtual stops. Over 4,097 order requests there were no failed sends, and every opening and closing deal reconciled against the terminal history.
Readers of the ninth entry will remember the frozen quotes of 12 to 14 October. That incident hit this probe too, and this is where its freshness check paid off: it refused to set a level while the last tick was more than five seconds old, and skipped the stop-placing part of 345 cycles in a row. All 345 fall inside the frozen window, and no pair or solo fill happened inside it. The baseline market orders it still sent in that window were dropped under the pre-registered stale-quote rule. The incident cost the probe sample size and some demo balance, nothing else.
The result
| Comparison | n | Days | Mean difference | 95% CI |
|---|---|---|---|---|
| Complete pairs (primary) | 354 | 16 | -$0.004 | -$0.018 to +$0.010 |
| Pairs, excluding those touched within 2 s of an order from the other probe on the account | 348 | 16 | -$0.005 | -$0.018 to +$0.009 |
| Solo server stops vs solo virtual stops (unpaired) | 101 / 100 | 16 | +$0.019 | -$0.038 to +$0.076 |
Positive would favour the virtual stop. The mean paired difference was minus four tenths of a cent per ounce. The median was exactly zero; 31% of pairs had a difference of exactly zero, and 42% were within one cent. The interval runs from -$0.018 to +$0.010. Its upper end is far below $0.15, so the pre-registered verdict is outcome 3 in its strongest form: no difference that can be distinguished from zero, and an average improvement of $0.15 per ounce or more from switching to virtual stops can be ruled out. The same outcome holds for the sensitivity check, which drops pairs whose level was touched within two seconds of an order from the other probe sharing the account, and for the unpaired solo comparison.
Before the test I had worked out that if the true difference were zero, 300 pairs should give an upper bound of about $0.11. It came out at $0.010, so the pairs were much less noisy than I had allowed for. This is not a null result from too little data.
The queueing check did not trigger. The median delay of the virtual stop was 156 ms in pairs and 153 ms on its own, a 3 ms gap with a Mann-Whitney p of 0.98. The pre-registered threshold for treating the pairs as contaminated was a gap of 50 ms or more.
For scale, both kinds of order slipped by about $0.08 per ounce on average in this sample: $0.077 for the server stops in pairs, with a median of $0.04, and $0.081 for the virtual stops, with a median of $0.05. These are descriptive numbers I added after the readout, not part of the decision. The virtual stops were a little more spread out, with a 90th percentile of $0.24 against $0.19, which is consistent with a market order travelling for about 155 ms while the price keeps moving in both directions.
Faster markets
The obvious objection is that random levels mostly get touched in quiet moments, and the argument for virtual stops is about fast ones. The plan included a look at that, as a secondary and exploratory analysis. Splitting the pairs into thirds by how far the price had moved in the five seconds before the touch:
| Price move in prior 5 s | Pairs | Server stop, mean slip | Virtual stop, mean slip | Mean difference |
|---|---|---|---|---|
| Slow (up to $0.435) | 119 | $0.047 | $0.038 | +$0.010 |
| Middle | 118 | $0.063 | $0.076 | -$0.013 |
| Fast (above $0.76) | 117 | $0.122 | $0.130 | -$0.009 |
Both kinds of order slip more when the price is moving faster, which is what you would expect. The difference between them does not grow with speed: the rank correlation between the speed tertile and the paired difference was -0.013 (p = 0.81). In the fastest tenth of pairs, 36 of them with a move of at least $1.33 in five seconds, the server stops slipped $0.139 on average and the virtual stops $0.130. Thirty-six pairs is not much, and I would not lean on it, but it gives no hint of the advantage the argument predicts.
The assumption that was wrong
This is the part I did not expect. The probe recorded, for every server stop, the time of the first tick at or beyond the level and the time of the fill. Both come from the server's clock, so no clock synchronisation is involved. If my replay assumption were right, the gap should be around 250 ms.
It was a median of 1 millisecond, with a 95% interval of 1 to 2 ms, over 455 server-stop fills from pairs and solo cycles together. Eighty-eight percent filled within 5 ms, and fewer than 2% took 250 ms or more. Every fill matched the quote in force at the time of the fill to within a cent, which in practice is the quote of the tick that touched the level. Only 40% matched the quote 250 ms later. On this demo account, the server fills a triggered stop at the touching tick, not a quarter of a second later.
So the delay in my replays was wrong by two orders of magnitude, and it had been sitting in them with nothing behind it but six fills that did not contradict it. The part that softens this is the price. When I compared the slippage the 250 ms model predicted with the slippage that actually happened, the model was off by -$0.009 on average (the model was slightly pessimistic), with a correlation of 0.71. Gold rarely moves far in a quarter of a second at a random level, so a wrong delay produced nearly the right price here. That is luck in the sample design rather than a property of the model. The EA does not enter at random levels; it enters when price breaks out, which is exactly when a quarter of a second can matter. I have not yet re-run the replays with the corrected rule, so I do not know how much their numbers move, and I will not guess.
The 1 ms result also takes apart my original reason for the test. I had framed it as "is my round trip shorter than the server's 250 ms?" The server's delay turned out to be close to zero and my round trip about 155 ms, so the virtual stop was the slower of the two by a wide margin, and still slipped the same on average. A faster connection or a VPS would shorten the round trip, but it would be racing an order that is already filled at the touching tick. That last point is my inference; I did not measure it.
What I am doing with it
The plan said in advance what each outcome would lead to, so none of this is a judgement call made after seeing the numbers.
The EA keeps its server-side stops. Switching to virtual stops was the cheapest open idea for reducing entry cost, and it is now closed: on this account and at this size, it does not improve average slippage by anything that would matter.
The replays get corrected. A triggered stop will fill at the quote of the touching tick, not the quote 250 ms later. The plan required this whatever the main verdict was. The corrected numbers are not in yet, and when they are I will say which earlier figures changed.
And I am keeping the limits attached. This was one demo account, one symbol, one lot size, levels placed at random, three weeks, and one home connection with a round trip of about 155 ms. A real account routes orders differently and could behave differently in both delay and price; "the server fills a stop at the touching tick within a millisecond" is a fact about this demo, not about real money at this broker. The verdict also covers the average only. Whether virtual stops are worse in the worst cases, or in the specific minutes the EA trades, is outside what this test can say.
What I take from it
Two things. The first is about the question people argue over: on this account, at the level of the average fill, the choice between a server-side stop and a virtual stop made no difference this test could detect; whatever difference exists is within about two cents per ounce either way. That is less exciting than either side of the argument, and it is what 354 matched pairs showed.
The second is about my own process. The 250 ms assumption was never a decision. It went into a replay because a replay needs some number, six fills did not contradict it, and it stayed there because nothing contradicted it. It took a test designed to answer a different question to expose it. If you run tick replays of stop orders, the delay you assume between the touch and the fill is worth measuring once on your own broker, with a few hundred orders and the server's own timestamps. It is cheap to do on a demo account, and it is easy to be wrong about.


