Prop Firm EA Compliance: Four Infrastructure Gaps That Cause Challenge Disqualification
A client asked me to diagnose why his FTMO Phase 1 EA was disqualified on day 9. The strategy was profitable — positive P&L at the point of disqualification. The journal told the story: trades were opening after the daily loss limit had already been breached. The check was in OnTimer() , set to fire every 15 minutes. In the 14-minute window after the breach, three more trades executed. Compliance failure, not strategy failure.
This is the most common prop firm EA failure pattern I see: the strategy is sound, but the compliance layer either doesn't exist or has timing gaps. The trading logic has no awareness of account state — it only knows price moved and a signal fired. Four specific gaps cause the bulk of challenge disqualifications.
Daily Loss Enforcement: OnTick, Not OnTimer
The correct implementation checks total daily P&L on every OnTick() call. Total daily P&L = all floating unrealized P&L plus all closed-trade results since session open, compared against startOfDayBalance × maxDailyLossPercent .
The session-start balance capture matters. If you capture it once at EA initialization, subsequent days calculate against the wrong reference. After a profitable Monday, Tuesday's daily loss limit calculation starts from the wrong point — either allowing more room than it should or triggering too early. Capture it at the first tick of each session using broker server time, not local machine time or UTC.
Trailing Drawdown: Track Peak Equity
Prop firms don't enforce a fixed floor — the allowed drawdown follows your equity peak. An EA monitoring startBalance × 0.90 as a fixed floor gets caught every time the account makes progress.
On a $100,000 account with a 10% trailing limit:
- Account opens: floor is $90,000
- Equity peaks at $103,500: floor moves to $93,150
- Equity peaks at $107,000: floor moves to $96,300
An EA tracking the fixed $90,000 floor believes it has $3,150 more room than it does after the first equity peak.
Initialize peakEquity in OnInit() to the current account balance — not zero. An EA initializing peakEquity = 0 won't correctly track a floor until it first hits a new equity high.
Phase-Aware Position Sizing
Different challenge phases have different risk limits. A 2% risk-per-trade that fits a funded account's relaxed drawdown limits can breach Phase 1's daily loss limit after two consecutive losses. Building phase awareness into the lot size calculation:
Log the active phase in OnInit() . When reviewing a journal, you want to see what configuration was running — not reconstruct it from memory weeks later.
MT5 Netting Mode Guard
Several prop firms have migrated to MT5 accounts in netting mode. An EA built for hedging mode compiles without error on a netting account and behaves incorrectly from the first trade.
On a hedging account, opening a long and then a short gives you two independent positions. On a netting account, the short partially or fully closes the long — there is no separate short position. An EA expecting a hedge now has no position. PositionsTotal() returns 0, the strategy sees no open position, and re-enters — creating an unintended trade loop. No compile-time warning. No runtime error. Silent architectural mismatch.
The ACCOUNT_MARGIN_MODE_RETAIL_NETTING constant is documented in the MQL5 reference under account property enumerations.
Architecture: Compliance Separate from Strategy
These four checks belong in a compliance module that is architecturally separate from the trading logic. No knowledge of the strategy's signal generation, no dependency on strategy variables, no compliance checks interleaved inside strategy functions.
The practical reason: when you update the strategy — new entry logic, revised exit conditions — you don't want to accidentally break the compliance checks. A standalone compliance module can be tested in isolation. Feed it a synthetic equity sequence and verify it fires exactly at the limit: not one trade early, not one trade late.
The trading strategy runs inside the compliance wrapper. RunStrategyLogic() is only called after all compliance checks pass. That's the structural goal.
The mistakes above are preventable. They require building the compliance layer as a first-class engineering deliverable, not an afterthought layered onto a working strategy.


