Five Trading Days, One Valid Measurement: Auditing a Live Forward Test
Over the next several months I am going to publish the full incubation record of one Expert Advisor — a session breakout system on gold, M15 — from validation through live forward testing. Not the marketing version. The actual record, including the parts where nothing worked.
This is the first entry, and it is not about performance. It is about a much more boring question that turns out to dominate everything else in the early weeks: did the forward test actually record anything?
Five trading days in, the honest answer is that 16 of 80 scheduled decision points produced a valid live measurement. Twenty percent. The other eighty percent were lost to infrastructure faults, none of them related to the strategy — and I did not fully understand the largest one until I sat down to write this post.
The system, in one paragraph
A Donchian-style breakout on GOLD#, M15, restricted to a single session window: 10:00–13:45 true UTC. That window contains exactly sixteen M15 bars. On each of those sixteen bars the EA cancels and re-places a buy stop at the upper channel and a sell stop at the lower channel, with an ATR-scaled protective stop and a trailing exit. Sixteen bars, thirty-two order operations, every trading day, with no discretion anywhere in the loop. That regularity is the whole reason this audit is possible: I know exactly how many log lines a healthy day should produce, so a missing line is evidence rather than a feeling.
Everything below is from a demo account on a live feed. No real money has been committed, and no live results are being claimed. The point of this stage is not to find out whether the edge is real — that takes months. The point is to find out whether my measurement apparatus works at all. It did not.
The scoreboard
Here is what the session window actually recorded, reconstructed from the terminal's expert log and journal files:
| Date | Decision bars | Order attempts | Accepted | Rejected (10027) | Usable? |
|---|---|---|---|---|---|
| Aug 10 (Mon) | 16 / 16 | 32 | 32 | 0 | Yes |
| Aug 11 (Tue) | 16 / 16 | 32 | 0 | 32 | No |
| Aug 12 (Wed) | 0 / 16 | 0 | 0 | 0 | No |
| Aug 13 (Thu) | 16 / 16 | 32 | 0 | 32 | No |
| Aug 14 (Fri) | 16 / 16 | 32 | 0 | 32 | No |
| Total | 64 / 80 | 128 | 32 | 96 | 1 of 5 days |
Note the gap between the third and fourth columns. On three of the five days the EA looked perfectly healthy: it woke up, it computed the channel, it printed its decision line, it called OrderSend . Anyone skimming the expert log would have seen an EA running normally.
Ninety-six order attempts came back with retcode=10027 , and that code deserves a moment of attention, because it does not mean what a rejection usually means. It is the client terminal refusing to send. The orders never left the machine. There is no broker involved in this failure at all — which is exactly why it took me so long to look in the right place.
And the one day that did work — August 10 — produced zero fills. Price never touched either side of the channel. So after a full trading week, the number of executed trades in the record is zero.
There were ten deals on August 11, and they are not in that table on purpose. They belong to a second session leg that I retired after a corrected simulator showed it was worth approximately nothing. A settings file I wrote to fix an unrelated problem silently switched it back on, it traded five round trips overnight, and those five round trips are excluded from the record. A strategy you have already killed does not get to contribute to your track record just because it happened to fire.
Failure 1: seventy-eight rejections nobody read
The first failure predates this window. Between August 6 and August 8, the EA attempted seventy-eight orders and all seventy-eight were rejected with the same code. Every rejection was written to the log, in plain text, with a human-readable reason. I did not notice for more than two days, because nothing reads the log except a person who decides to open it.
This is the failure mode that matters most for anyone building a track record. It is not a crash. A crash is loud. This is an EA that runs, prints, calculates, and sends — and produces no record. If I had discovered it three months later, I would have discovered simultaneously that I had three months of nothing.
What I built in response
I wrote an external watchdog: a Python script on a fifteen-minute Windows scheduled task that reads the terminal's own log files and pushes alerts to Discord. Three design decisions in it are worth stating, because two of them were wrong the first time.
It watches from outside the EA. A heartbeat emitted by the EA cannot report that the terminal is gone. The monitor has to be a separate process reading files on disk, and it must require no modification to the EA whatsoever.
Silence is measured in tradeable minutes, not wall-clock minutes. The first version alerted whenever the EA had been quiet for twenty-five minutes inside the session window. Over the first weekend it sent twelve alerts, all of them false: the market was closed, or the window overlapped the broker's daily quote break. A monitor that cries wolf twelve times is functionally identical to no monitor. The fix was to derive the broker's server offset from the EU daylight-saving rule, subtract weekends and the 23:55–01:05 quote break, and measure the silence only across minutes when the symbol was actually quoting.
def market_open(when_utc):
srv = when_utc + timedelta(hours=broker_offset_hours(when_utc))
if srv.weekday() >= 5: # broker weekend
return False
hm = (srv.hour, srv.minute)
if hm >= (23, 55) or hm < (1, 5): # daily quote break
return False
return True It says something when things are working. A monitor that only speaks during failures cannot be distinguished from a monitor that has died. Once per session, on first confirmed activity, it sends a positive notice — but only if no alert is currently raised, because "the EA is talking" is not evidence that the EA is trading. I learned that by replaying the August 8 logs through the monitor and watching it cheerfully report healthy activity for a period in which every single order had been refused.
Failure 2: the terminal that did not come back
On August 12 the journal shows one line at 05:44:11 — terminal stopped due to system shutdown — and then nothing until 23:47. A Windows restart bounced the machine before dawn. The PC itself was back within minutes, and three other MetaTrader instances on it restarted automatically at 05:48–05:49. This one stayed down for eighteen hours, because it was the only one without a startup entry.
The watchdog had a process check for exactly this. It did not fire, and the reason is a good lesson in how monitors quietly rot. The check ran tasklist /FI IMAGENAME eq terminal64.exe and asked whether any MetaTrader was running. Three others were. So the process check reported healthy all day, while a different alert — "session window, but the EA is silent" — fired four times that evening without ever naming the cause.
The fix was to compare full executable paths with exact string equality, because C:\Program Files\XM Trading MT5 is a prefix of C:\Program Files\XM Trading MT5 2 , and a substring match would have reintroduced the same bug in a subtler form. The alert body now names the specific executable and the specific recovery step.
Failure 3: one flag, made permanent by a power cycle
I restarted the terminal on the night of August 12, confirmed the EA loaded, confirmed the account was in demo mode, and confirmed the terminal's Algo Trading button was on. I considered the incident closed.
Then I pulled the logs for this post and found thirty-two rejections on August 13 and thirty-two more on August 14. Same code. After the fix.
The watchdog did detect them, both days, and the alerts went out. I did not act on them, and part of the reason is that the alert text told me to do something I could see was already true: it said to enable Algo Trading in the terminal. The terminal-level button was on. Here is the proof, from the same terminal and the same account, on the same evenings:
logs\20260813.log (terminal journal) 01:00:02 accepted market buy 0.12 USDJPY#, ... done in 148.973 ms 23:45:00 accepted market sell 0.12 USDJPY# sl: 159.389 ... done in 145.766 ms MQL5\Logs\20260813.log (expert log) 19:00:00 [BAR] UTC10 sess=30070 up=4449.74 dn=4363.96 atr=9.256 sp=0.240 pos=0 19:00:00 [ERR] BuyStop retcode=10027 auto trading disabled by client 19:00:00 [ERR] SellStop retcode=10027 auto trading disabled by client
A second EA on the same terminal was placing and filling market orders on both of those days. Automated trading was unambiguously enabled at the terminal level. Only this one EA was blocked — which is the clue I should have followed on day one, and did not.
The trail starts two days earlier. On August 11, at 12:01 in the middle of the day, the journal records this:
12:01:16 Automated Trading for XAUUSD_SessionBreak_M15_EA (GOLD#,M15)
is disabled because chart symbol or period has been changed I had changed the chart's timeframe to look at something. MetaTrader responds to a symbol or period change by disabling automated trading for that expert, and it is a per-chart state, not a terminal one. Seven hours later, that evening's session produced its thirty-two rejections. At 13:52 I had toggled the terminal's Algo Trading button three times, ending in the enabled state, which is why I believed the problem was solved. It was the wrong switch. The per-EA permission was untouched by it.
Then the power cycle turned a temporary condition into a permanent one. MetaTrader stores per-chart expert settings in MQL5\Profiles\Charts\<profile>\chartNN.chr , and each carries an expertmode flag:
chart02.chr | USDJPY_FixFlow_EA | expertmode=1 <- trading normally chart03.chr | XAUUSD_SessionBreak_M15_EA | expertmode=4 <- every order refused
Both files were last written at 05:44 on August 12 — the exact moment of the system shutdown. MetaTrader persists chart state on exit, so it captured the disabled permission as it stood and committed it to disk. When the terminal came back that night it read the file, restored the EA with algo trading switched off for that chart, and reported loaded successfully while doing it.
So this is not three unrelated failures. August 11, 13 and 14 are one root cause — a careless timeframe change — that survived a full restart because a crash wrote it down. One flag, in one file, cost three of five trading days.
Why my own self-test said "OK"
The EA runs a startup self-test and prints a verdict. On the night of August 12, immediately after the restart, it printed this:
23:47:09 [INIT SELFTEST] Donchian(128 bars) up=4441.07 dn=4356.61
width=84.46 (10.0 ATR) -> buy lot=0.010 sell lot=0.010 OK: can place orders At the very next session window, every order it placed was refused. The self-test was not lying so much as answering a different question than its own output implied. Reading the source, the verdict reduces to (buy_lot > 0 && sell_lot > 0) . It checks that position sizing produces a non-zero lot. It never checks whether this EA is permitted to trade.
MQL5 exposes three distinct permissions, and I was checking none of them:
- MQLInfoInteger(MQL_TRADE_ALLOWED) — whether this program, on this chart, may trade. The one that was off.
- TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) — the terminal-level button. On the whole time.
- AccountInfoInteger(ACCOUNT_TRADE_EXPERT) — whether the server permits expert trading on the account.
A self-test that prints "can place orders" without reading any of them is worse than no self-test, because it manufactures confidence. That is the same category of error as a backtest that reports a Sharpe ratio without reporting how many independent bets produced it.
What changes
Four things, in order of how much they hurt to admit. The second shipped while I was writing this post; the first and third are queued for the next build; the fourth is already true.
The self-test will have to earn its verdict. All three permission flags get read and printed individually at startup, and the summary line says "cannot place orders" if any one of them is false.
Alerts must name the correct knob, and arrive before the window opens. Waiting for a 10027 means waiting until the session has already begun, by which point the day is spent. The monitor now reads expertmode straight out of the chart file ahead of the window, and if bit 0 is clear it says so in as many words: that this is the per-EA checkbox and not the toolbar button, that the other EA on the same terminal is sitting at expertmode=1 , and that the startup self-test will cheerfully print OK anyway. A monitor that detects the symptom but prescribes the wrong fix is a monitor you learn to ignore — which is precisely what happened to me on August 13.
Repeated rejections will escalate instead of settling into a rhythm. The sixty-minute cooldown that stops a burst of ninety-six errors from becoming ninety-six notifications also made a two-day outage look like routine background noise.
The recorded start of the forward test is August 10, and it currently contains one usable day and zero executed trades. I am not backdating it to the day the EA was installed. If I did that here, in the middle of a series about validation transparency, nothing else I publish would be worth reading.
Why I am publishing this
The market for Expert Advisors is full of equity curves and short on evidence about how those curves were produced. There are articles on this very site teaching buyers how to recognise manipulated backtests; the problem is well enough known that it has a literature. What I have not seen much of is a seller showing the unglamorous machinery underneath: how the record is kept, what happens when it breaks, and what gets excluded and why.
So that is what this series will be. Coming entries cover the places where my Python research code and my MQL5 implementation disagreed, why adding a fixed take-profit made results worse in testing, and why counting non-overlapping trades honestly pushes statistical significance months further out than the raw trade count suggests. The EA itself is not for sale and will not be for months. This is a build log, not a sales page.
The one thing I would offer to anyone running a live forward test right now: go and count the log lines. Not the equity curve — the log lines. Work out how many your system should emit on a healthy day, then check whether it emitted them. I thought I was five days into a forward test. I was one day in, and it took writing this post to find that out.
My free gold panel is here if you want to see what I have already published. Next entry: where the research code and the implementation disagreed.


