MQL4 helps you structure nothing. You get OnTick(), global variables and one file. The path of least resistance is a long procedure where the entry signal, position management and risk controls interleave until nobody can touch one without breaking another.
That interleaving has a concrete cost: when the strategy logic is wrong, the protection is wrong with it, because they share state and share conditions.
The rule we imposed on ourselves
The guardian may not read anything the strategy decides.
One constraint, and it orders everything else. The guardian does not know the current bias, whether the signal is active, or which grid level we are on. It only reads terminal facts: open positions, their floating P&L, account equity, the clock.
This means the guardian polices every position in the terminal — including other robots' and the ones you open by hand. That is not a side effect, it is the consequence of not coupling it to our strategy.
What it looks like in practice
The EA runs two independent blocks on every tick:
OnTick()
Guardian_Check() // first, always, no matter what
Strategy_Step() // only if the guardian has not paused
Guardian_Check() calls nothing from the strategy. Strategy_Step() calls nothing from the guardian: it reads a pause flag and that is it.
That asymmetry is deliberate. The guardian can stop the strategy. The strategy cannot soften the guardian. The day someone writes if (signal_is_great) skip_the_stop(), the separation is over.
Two magic numbers, not one
An MQL4 detail that saves pain: we use a distinct magic number per direction — one for sell positions and one for buy positions.
With a single identifier, grouping positions to compute a set's floating P&L forces you to filter by direction in every loop, and those filters get copied wrong. With two, OrdersTotal() filtered by magic is the set. Grouping stops being code and becomes a property of the data.
State: the less, the better
The previous version had an adaptive layer with its own state: current regime, brake counter, inverted bias. When we measured it and saw it added nothing, we deleted the whole thing.
What remains is that the bias function is literally return -raw; — one line, no state. And the panel stopped publishing two diagnostic blocks that existed only to debug that layer.
Less state is not just elegance. In an EA that runs unattended for days, every persistent variable is a place where the system can end up in a state you never anticipated after a reconnect, a symbol change or a terminal restart.
What the guardian cannot do
This part belongs in the documentation, not the code, and it is the most important one:
The guardian can cut a set of positions, flatten the account and stop the day. It cannot predict a move, cannot execute when the broker rejects orders, cannot close a gap it was already holding through, cannot watch with the terminal off, and cannot turn a negative-expectancy strategy into a positive one.
A well-separated protection layer gives you bounded guarantees. Confusing "well designed" with "always covers you" is how people lose money on correctly programmed systems.
From building Cerberus at Tuurt Labs. High-risk instrument: trading leveraged CFDs can cost you your entire capital. This is not investment advice.