How to run an Expert Advisor on a prop firm account without breaking the daily loss rule
Of the firms I checked in September 2026, FTMO resets at 00:00 Central European time, Goat Funded at 5 PM New York, Maven at 00:00 UTC, and FundedNext, The5ers, FundingPips and Alpha Capital at 00:00 on their own platform server. If your EA runs on the firm's own MT5 account, server midnight is exact and needs no offset. If you run somewhere else, a hardcoded GMT offset is wrong for two weeks every spring and autumn, because the EU switches on the last Sunday of March and October while the US switches on the second Sunday of March and the first Sunday of November. Derive the reset from those dates. Central European version below; the US rule is the same loop with other bounds.
datetime EuSwitchUtc(const int year,const int month) { MqlDateTime t = {}; t.year = year; t.mon = month; t.hour = 1; // 01:00 UTC for(int day=31; day>=25; day--) // March and October have 31 days { t.day = day; datetime candidate = StructToTime(t); MqlDateTime c; TimeToStruct(candidate,c); if(c.day_of_week==0) return candidate; } return 0; } int EuOffsetHours(const datetime utcNow) { MqlDateTime n; TimeToStruct(utcNow,n); bool summer = (utcNow >= EuSwitchUtc(n.year,3) && utcNow < EuSwitchUtc(n.year,10)); return summer ? 2 : 1; // CEST or CET } datetime LastResetUtc(const datetime utcNow) { long offset = (long)EuOffsetHours(utcNow)*3600; long local = (long)utcNow + offset; return (datetime)(local - (local % 86400) - offset); }
3. Put the limit check in one account level utility, not inside every EA.
The firm measures the account, so the thing watching the limit has to measure the account. Per EA budgets look tidy on paper and come apart the first time two EAs lose in the same hour. I run one utility on one chart per account. It reads equity on every tick and on a one second timer, because a tick only check goes quiet on a slow chart at the wrong moment. The trading EAs ask that utility for a risk multiplier before they size. DayStartAnchor is in step 5, LockDay and IsDayLocked in step 6.
input double InpDailyPct = 4.5; // armed below the firm's 5 percent void CheckDailyLimit() { datetime reset = LastResetUtc(TimeGMT()); double anchor = DayStartAnchor(reset); double limit = anchor * InpDailyPct / 100.0; double used = anchor - AccountInfoDouble(ACCOUNT_EQUITY); if(used >= limit && !IsDayLocked()) LockDay(); } int OnInit() { EventSetTimer(1); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { EventKillTimer(); } void OnTick() { CheckDailyLimit(); } void OnTimer() { CheckDailyLimit(); // ticks stop, the clock does not }
4. Arm below the published limit.
Detection is tick based, so the close executes past the level you configured. On a demo test with a fixed limit of 250 my close fired at a loss of 267.19. The journal line, with the account tag stripped:
DAILY LIMIT BREACHED: loss 267.19 EUR >= limit 250.00 EUR. Closing all positions.
I arm 0.5 percentage points below the daily limit and 1 point below the max drawdown, so a firm rule of 5% and 10% becomes 4.5% and 9%.
5. Persist the state so a reboot does not reset your protection.
Most guards count from the moment you attached them, so a VPS reboot at 11:00 hands you a fresh day with a fresh allowance. Write the day start anchor, the used percentage and the lock flag into terminal global variables with the account login in the name (https://www.mql5.com/en/docs/globals). They are stored on disk, they survive a terminal restart, and they are removed automatically after four weeks without access. One trap: GlobalVariableTime() returns the last access, and your own check accesses the variable every tick, so keep the reset stamp in a second variable.
string GvName(const string key) { return StringFormat("DLG_%I64d_%s",AccountInfoInteger(ACCOUNT_LOGIN),key); } double DayStartAnchor(const datetime lastReset) { string gvValue = GvName("anchor"); string gvStamp = GvName("anchor_time"); // GlobalVariableTime() is the time of the last ACCESS, not of the last // write, so the reset stamp is kept in a second variable. if(GlobalVariableCheck(gvValue) && GlobalVariableCheck(gvStamp) && (datetime)GlobalVariableGet(gvStamp) >= lastReset) return GlobalVariableGet(gvValue); double anchor = MathMax(AccountInfoDouble(ACCOUNT_BALANCE), AccountInfoDouble(ACCOUNT_EQUITY)); GlobalVariableSet(gvValue,anchor); GlobalVariableSet(gvStamp,(double)lastReset); GlobalVariablesFlush(); return anchor; }
6. Lock the day, and deal with whatever opens during the lock.
Closing everything is only half of it. A Market product cannot switch AutoTrading off without a DLL, so any EA still attached keeps firing signals into your locked day. My utility subscribes to trade transactions and closes anything that appears during the lock within milliseconds, measured at 148 ms for three positions on a demo account. That costs one spread per attempt, a price I will pay over a breach. The tidier route is to detach the EAs yourself as soon as you see the lock.
#include <Trade\Trade.mqh> CTrade trade; bool IsDayLocked() { string gv = GvName("locked"); return (GlobalVariableCheck(gv) && GlobalVariableGet(gv) > 0.0); } void LockDay() { GlobalVariableSet(GvName("locked"),1.0); GlobalVariablesFlush(); for(int i=PositionsTotal()-1; i>=0; i--) { ulong ticket = PositionGetTicket(i); if(ticket>0) trade.PositionClose(ticket); } Print("DAILY LIMIT BREACHED. All positions closed, day locked."); } void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { if(!IsDayLocked()) return; if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return; if(trans.position>0 && PositionSelectByTicket(trans.position)) trade.PositionClose(trans.position); }
7. Rehearse a losing day before you have one.
Open a demo account on the same server, set the daily limit to something tiny, and trade into it by hand. You want to see the warning, the close all, the lock, a blocked trade and the reset in your journal while nothing is at stake. I later packaged my own guard as a Market utility, but the seven steps above are the substance.
What this does not cover. The max drawdown rule is a separate measurement and needs separate code: static from the initial balance at most firms, trailing at some, trailing on end of day values at others. Consistency scores, news windows, minimum trading days and strike systems are rules you keep yourself, because no terminal side tool can see them. And none of this helps a strategy with negative expectancy. A guard turns a rule breach into a bad day, it does not turn a losing system into a winning one. The firm rules above were collected on 3 September 2026 and firms revise them, so verify yours before you configure anything.


