You are down 3% on the day, your daily-loss limit is 4%, and you switch the chart from M15 to H1 to look at the higher-timeframe context. Your limit is now 4% measured from where you already are, which is 7% from where the day started, and nothing on the screen told you.
This is not a logic error. The arithmetic is right, but the number it runs on was captured in the wrong place.

OnInit is not "once a day"
Every daily-loss breaker needs one reference number: the balance the day started at. The obvious place to take it is OnInit, because OnInit is where the EA starts.
MT5 calls OnDeinit and then OnInit again on all of the following:
- Change the chart timeframe — yes, many times a day
- Change the chart symbol — yes, many times a day
- Change an input in the EA properties — yes, during any tuning
- Recompile in MetaEditor — yes, during any development
- Restart the terminal, or a VPS reboot — yes, daily, and unscheduled
Only the last one is what people picture. The rest are ordinary chart handling, and each of them silently re-anchors the day and hands you a fresh full allowance. Down 3%, re-anchor, down 4% more, change the symbol, re-anchor, down 4% more again. From every anchor's point of view the limit was never breached. The account is down more than 10%.
Put real numbers on it. A 10,000 EUR account down 3% has lost 300 EUR and sits at 9,700. A timeframe change re-anchors the day-start balance to that 9,700, so the 4% limit is now computed against it: another 388 EUR before the breaker trips, for a running total of 688 EUR. That is 6.88% of the original balance, already past the stated 4%, and the breaker never saw it happen.
Why the obvious fixes do not hold
"Only capture it if it is a new day." That requires remembering whether it already captured today, and a global or static variable in an EA does not survive OnDeinit. You are back where you started.
"Rebuild it from history." Not wrong, just a different problem. Netting the day's deals out of the current balance is the only method available to a tool that was not running at the reset, and it is the right method for anything you attach mid-session. It is also more work than it looks, because deposits, credit adjustments and the postings that land at rollover all sit in the same history and each needs its own rule. An EA that was already attached when the day started needs none of it. It only needs to remember.
"Write it to a file." That works, but MT5 already ships the primitive. Terminal global variables are doubles, they live at the terminal level rather than the chart level, and they are flushed to disk on shutdown. All you have to do is get the key right.
Getting the key right
The day-start balance is anchored under a global variable name built from the account login and the current day's date. The date stamp makes the anchor self-expiring, so tomorrow asks for a key that does not exist yet, captures a fresh balance, and yesterday's value ages out.
The account login stops cross-contamination. Test on demo, switch the same terminal to live, and an unkeyed variable name hands the live account the demo's anchor. It is a five-character fix that you will otherwise find the hard way.
The trip flag needs the same treatment, not just the anchor: a breaker that forgets it fired is a breaker you can restart your way out of. It trips at 11:00, the VPS reboots for maintenance at 11:05, and by 11:06 a day you had already stopped is trading again.
Persisting the trip flag is only half the job. GlobalVariableSet writes to memory immediately, but the value only survives a genuine crash, not a clean shutdown, if it has actually reached disk. Call GlobalVariablesFlush right after setting the flag, not on a timer or at the next OnDeinit, because the whole reason the flag exists is to survive the one event you cannot schedule around.
A short, distinct prefix on the key matters more than it looks once an account runs more than one EA. Terminal global variables are shared across every program on that account, not scoped per chart or per program, so two tools using the same bare key name silently overwrite each other's anchor.
One more collision worth knowing: the same EA on two charts of the same account computes the identical key, since it is built from the login and the date, not from the chart or the symbol. That is not a bug, it is the point. Two instances reading and writing the same anchor and the same trip flag means whichever one gets there first sets it and the other one restores it, and the daily limit is enforced once for the whole account rather than once per chart.
MT5 also offers GlobalVariableTemp, which behaves identically but is deliberately wiped when the terminal closes. That is the wrong primitive here on purpose: the entire design goal is surviving a restart, so the anchor has to use the persistent form, not the temporary one.
The login alone is not a perfect key either. Account numbers are assigned per broker server, not globally, so a demo login on one broker and a live login on another can share the same number by coincidence. Folding the account server name into the key alongside the login closes that gap.
Wiring it into your EA
None of this does anything until it is called from the right place. The anchor function runs once from OnInit, so it captures or restores the day-start balance and the trip flag every time MT5 reinitialises the EA, which is the entire point.
The breaker check itself belongs in OnTick, not OnInit, because OnInit only runs on reinitialisation and a losing position can push the account past the limit at any tick in between. The loss has to be measured against equity, not balance, because balance ignores a floating loss on an open position.
When the check trips, two things have to happen in the same tick, not queued for later. The account gets flattened, and the trip flag is written to the global variable immediately, because the entire reason it is persisted at all is to survive a restart that could happen one second afterward.
The stop has the opposite problem
The day anchor goes wrong because it is captured too often. A stop goes wrong for the reverse reason: it is computed once, correctly, and the server refuses it.
Attaching a stop to a naked position looks like one line. It is four.
Measure the broker's clamp from the market, not from the entry. SYMBOL_TRADE_STOPS_LEVEL is a minimum distance from the current price. A position that has already run against you can carry a perfectly sensible stop below its entry that is now too close to the bid, and the modify comes back rejected.
The extra point past the clamp is not superstition. Price moves between your read and the server's check, and a stop sitting exactly on the boundary loses that race often enough to notice. NormalizeDouble to the symbol's digits is the same class of fix.
Put numbers on the clamp too. A gold position bought at 2400.00 with a planned 3.00 stop distance wants its stop at 2397.00. Price runs against it a little, the bid is now 2397.30, and the broker's 50-point stops level means the stop cannot sit closer than 0.50 to that bid. 2397.00 is only 0.30 away, so the clamp pushes it out to 2396.79. The position's real risk is now 3.21, not the 3.00 the strategy asked for.
Never leave the position naked because an input was unavailable. If the distance comes from ATR and the indicator has not filled its buffer, or from a money amount and the tick value reads zero, the honest fallback is a fixed point distance.
A stop that can loosen is not a stop. Break-even and trailing both propose a new level, and both have to be applied as a ratchet. The comparison has to be one-directional, so a trail that momentarily computes a worse level cannot walk the stop back down.
Stops level is not the only clamp worth knowing. SYMBOL_TRADE_FREEZE_LEVEL is a second, separate distance, and inside it the server refuses to modify or close a position at all, not just refuses a too-tight stop.
A symbol you are not looking at has no price. A symbol absent from Market Watch returns a bid of zero, and arithmetic on it produces a stop at an absurd level. Call SymbolSelect, skip that cycle, pick it up on the next.
Putting it together

Put together, this is the shape of a correct implementation: an anchor that survives every reinitialisation event MT5 can throw at it, and a stop that respects both of the broker's clamps. Neither piece opens a trade or carries a signal of its own.
The caveats are worth stating plainly. An economic-calendar filter needs a connected terminal and returns nothing in the strategy tester. The day boundary is the broker's server midnight, not the trader's own. Reading ATR off the attached chart's timeframe means that timeframe silently sets the stop distance for every symbol in a whole-account setup. None of this needs to be hidden from a broker, and none of it should be. No martingale. No grid. No averaging down.
If you are testing your own version of this, the check is simple: force a loss, note the balance, then change the chart timeframe and read the anchor back. If the number moved, the anchor lives in the wrong place.
It will not turn a losing strategy into a winning one. Done well, its only job is to keep a bad day from becoming a bad month. This is the same pattern behind Risk Sentinel, free on the Market.


