A trade copier's hardest job is deciding that a trade has closed
You come back to two terminals and the books do not match. The sending account is in three positions, the receiving account is in two, and the copier's panel has been showing a green light the whole time.
Opening a copy is the easy half. When an open fails it fails loudly: a return code, a log line, a position that is not there. Closing is where copiers fail quietly, and a copier that fails quietly is worse than no copier at all, because you stop checking.
Why event streaming drifts
Most copiers stream events. The master detects a trade, sends OPEN, the receiver applies it. Master closes, sends CLOSE, receiver applies it. Simple, fast, and it has one structural flaw: nothing in the design ever compares the two books.
Lose one message and the divergence is permanent, and losing one is ordinary. The file was locked for a moment, the terminal froze mid-write, the receiving EA reinitialised because you changed the chart timeframe. There is no later pass that notices, because there is no later pass at all.
The two directions of drift are not equally bad. A missed OPEN leaves you under-exposed, which is an annoyance. A missed CLOSE leaves a live position on the receiver that nothing is managing any more, and whatever stop it happens to carry is now the only thing between you and the account.
Publish the state, not the change
The correct model is declarative. The master publishes its entire book every poll, and the receiver compares its own live positions against that picture and moves toward it. Nothing is a message. Everything is a description of what is true right now.
That buys three properties. Applying the same snapshot twice does nothing, so the receiver can read it a hundred times and stay correct. A dropped snapshot costs latency and nothing else. And a restarted receiver rebuilds its state from the live accounts and its own persisted map, so a crash does not duplicate anything.
The snapshot has to be self-validating, because it is written and read concurrently with no lock between the two processes. A header line carries the sequence number, the account, a wall-clock stamp and the number of position rows to expect. Then one line per position. Then a footer that repeats the sequence number.
That shape does the work. A reader that opens the file mid-write finds a footer that disagrees with the header, or a body count that does not match what the header promised, and discards the whole thing. The last good snapshot stands. The wall-clock stamp means a file left over from last week's test cannot be mistaken for a live link.
The attachment at the end of this post is a compiling example that writes such a snapshot and reads it straight back, printing the verdict. It places no orders.
The trap that the fix introduces
Now the receiver has one instruction the event model never had: if a position I am tracking is absent from the master's book, close my copy.
That line is correct, and it is the most dangerous line in the product. To a comparison, "absent" and "the whole book is empty" are the same thing, and there are several perfectly ordinary ways for a master book to read empty while the account is fully in the market.
There are five of these and they are worth naming separately, because each has its own fix.
A terminal that is open while the connection is down reports an empty book quite happily, and a naive reader concludes everything closed. The master must not publish at all in that state. A file read halfway through a write looks like the missing lines closed; reject the snapshot and keep the last good one. A book walked forwards while it mutates loses the skipped position, which reads as closed; walk it backwards. A master that has just restarted may publish before the terminal has finished syncing; hold for several consistent snapshots. And a position that genuinely closed should indeed close the copy, but only after the absence repeats.
Disconnected is not flat. A terminal that is open but not connected reports an empty book quite happily. So the master refuses to publish while the terminal is disconnected, and re-arms a few seconds of grace on every reconnection before it publishes again. Measure that grace on the monotonic tick counter, not on server time. Server time is frozen in precisely the situation this guard exists for.
The forward walk. This is the one a competent developer writes on the first attempt, and it is nearly invisible in review. The terminal's position list is index-addressed and it compacts when a position closes. Say there are four positions and the walk is at index one. The position at index zero closes. The list compacts, so what used to be index two is now index one. The walk advances to index two and never sees it.

In an ordinary EA that skip is a missed pass. In a copier the skipped position is omitted from the snapshot, and omission means closed. Walking from PositionsTotal()-1 down to zero cannot skip: at worst it emits the same position twice, and a declarative receiver treats a duplicate line as nothing at all.
A restart is not a flat book. The master carries a boot id in every snapshot. When that id changes, the receiver holds for a few consistent snapshots before it acts on any absence, because the first book published after a restart may be assembled before the terminal has finished syncing.
Make the absence repeat, and make the repeat real
None of the above is sufficient on its own, so the last rule is simply that one absence is not proof. A tracked ticket has to be missing from two consecutive snapshots before the copy is closed.
There is a subtlety here that quietly defeats the rule. The receiver polls faster than the master publishes, by design, so it reads the same snapshot more than once. If the absence counter advances once per poll, it reaches two off a single reading of the same bytes, and the whole guard collapses into no guard. The counter has to be gated on the sequence number so that it advances once per distinct snapshot. Then two absences really do mean two independent looks at the master's book.
The mirror image of the same problem
A receiver that restarts mid-position finds positions it does not remember opening. Adopt too eagerly and it claims a trade belonging to another EA on that account and closes it later. Adopt too reluctantly and it opens a second copy of something it already holds.
The rule that survives both is to adopt only on proof: its own recorded ticket, its own comment token naming the master ticket, or a single candidate on that symbol and direction whose open time falls inside the window when the request was sent. Anything else holds, counts, and says so on the panel. A missing copy that is reported is a problem you fix in ten seconds. A duplicate is exposure you did not choose and did not size.
If you are writing your own copier, those are the five places to look before you trust it with a funded account: the publish gate, the torn read, the walk direction, the restart grace, and the repeat requirement. None of them show up in a quiet test. All of them show up on a Monday morning.
The limits, out loud. Both terminals sit on the same machine, with no DLL and no network. The receiver has to be a hedging account. A copier is only as disciplined as the account it reads, so it inherits whatever the sender does. No martingale. No grid. No averaging down.
Copier Relay is my implementation of the above: a local MT5 to MT5 copier for two terminals on one machine, with the risk sizing and the receiver protection layer on top. Copier Relay Core is the free version and runs the same copy engine without them. https://www.mql5.com/en/market/product/188445


