Memory plus an expiry policy: what a restarted guard is allowed to assume
Persisting state is the easy half. Every interesting bug I have hit in restart-safe guards was in the second half: deciding what the resumed process is allowed to assume about a world that kept moving while it was gone.
The resumed process is not the same process resumed. It is a new process that happens to share a file.
1. A counter without a date is not state
Restore the value and forget which day it belongs to, and you get one of two failures, both of which look like success:
- the restored counter is ignored (so the guard silently starts the day fresh);
- the restored counter is applied forever (so last Tuesday's "blocked" keeps you flat all week).
Neither throws. Both are the persistence "working".
2. Write ordering is a real decision
Persist before acting and a crash in between lets the action through twice. Persist after acting and the crash window is genuinely there - an action that happened and was not recorded. Only the second ordering cannot double-act, and choosing it means accepting an unbounded-by-default window instead of pretending the window does not exist. Bound it deliberately (the action is idempotent, the state is reconciled from the broker on start) rather than hoping.
3. Staleness must be a first-class state
The resumed guard inherits a world model that was true four minutes ago. If it cannot distinguish "my memory is old" from "my memory is current", it will act on a stale premise with complete confidence - which is exactly the shape of every silent failure in this series.
So the question worth designing against is not "did the memory survive?" but:
"Does the system know how old its own memory is?"
If the answer is no, then everything downstream is unfalsifiable.
4. The unit is a record, not a value
| Field | Why it must be persisted together with the value |
|---|---|
| the value | obviously |
| its day / period stamp | otherwise the record cannot be scoped to a period, only applied or discarded |
| when it was written | so the next process can measure its own staleness instead of guessing |
| which clock it was written against | the broker's day is not the host's day (see the trading-day note) |
| the decision it encodes | state that records inputs without the verdict can be re-derived wrongly |
What this looks like in practice
{
"day": 20261004, // broker trading day this record scopes to
"anchor": 10000.00, // equity at the start of that day
"used": 412.50, // measured drawdown/usage inside the day
"blocked": true, // the verdict, persisted, not recomputed
"written_at": 1759555200, // so a later process can compute its own age
"clock": "trade_server"
}
On start: compare day with the current broker day. Equal -> restore. Different -> reset, and say so
in the log. Measure now - written_at: if it exceeds the policy, treat the record as untrusted and say so.
Two comparisons, and the whole class of "it restored, therefore it is protecting me" disappears.
The uncomfortable summary
Persistence answers "did the memory survive?". It does not answer "is the memory still true?", "is it still mine?", or "is it still today?". Those are policy questions, and they have to be written down next to the data - because the process reading them later cannot reconstruct the context it was written in.
Related notes
- Your daily loss limit resets when MetaTrader restarts. Here is the fix.
- Detection is not resolution: the gap that makes guards decorative
- What a kill switch should actually do (and the four ways they fail)
- Silent failures in trading automation: the three that cost the most
- Which day is it? Broker time, host time, and the trading-day boundary
Get the guard
Free MT5 daily-loss guard, source included: https://xuks124.github.io/vigildesk/free.html
Risk disclosure
Algorithmic trading carries both technical and market risk. No tool eliminates the possibility of loss. Nothing on this page is investment advice, and no performance is implied or promised.