Most EAs that blow an account do it in one bad day, not over a month. A daily loss guard is the simplest protection against that, and it is also what most prop firm rules require. The tricky part is measuring the day's loss correctly. Two common mistakes:
- Comparing current balance with yesterday's balance. That ignores open trades, so the EA can sit on a large floating loss and keep opening new positions.
- Using the PC clock for the start of the day. The trading day follows the broker server time, which is often two or three hours away from yours.
The approach below records the equity at the start of each server day and blocks new entries once equity falls a set percentage below it. It can also close everything when the limit is hit.
Inputs and state
input double InpMaxDailyLossPct = 3.0; // max loss per day, percent of start equity
input bool InpCloseOnLimit = true; // close open positions when hit
input long InpMagic = 12345; // this EA's magic number
double g_dayStartEquity = 0.0;
datetime g_dayStart = 0;
bool g_blockedToday = false;
Detecting the new server day
datetime ServerDayStart()
{
MqlDateTime t;
TimeToStruct(TimeTradeServer(), t);
t.hour = 0; t.min = 0; t.sec = 0;
return StructToTime(t);
}
void UpdateDay()
{
datetime d = ServerDayStart();
if(d != g_dayStart)
{
g_dayStart = d;
g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
g_blockedToday = false;
PrintFormat("New server day %s, start equity %.2f",
TimeToString(d, TIME_DATE), g_dayStartEquity);
}
}
Taking a snapshot of equity rather than balance means a trade carried overnight is counted from where it stood at midnight, which is what most prop firm rules do.
The check
bool DailyLossHit()
{
if(g_blockedToday) return true;
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
double limit = g_dayStartEquity * (1.0 - InpMaxDailyLossPct / 100.0);
if(eq <= limit)
{
g_blockedToday = true;
PrintFormat("Daily loss limit hit: equity %.2f, limit %.2f", eq, limit);
if(InpCloseOnLimit) CloseAllMine();
return true;
}
return false;
}
CloseAllMine is whatever routine your EA already uses to close its own positions, filtered by magic number and symbol.
Using it in OnTick
void OnTick()
{
UpdateDay();
if(DailyLossHit()) return; // no new entries today
// normal signal and entry logic below
}
Run the check before the signal logic so a blocked day never reaches OrderSend.
Surviving a restart
If the terminal restarts in the middle of the day, g_dayStartEquity resets to the current equity and the EA forgets the morning's losses. Save the two values in terminal global variables so a restart picks them up:
void SaveDay()
{
GlobalVariableSet("DLG_start_" + (string)InpMagic, (double)g_dayStart);
GlobalVariableSet("DLG_eq_" + (string)InpMagic, g_dayStartEquity);
}
void LoadDay()
{
string k1 = "DLG_start_" + (string)InpMagic;
string k2 = "DLG_eq_" + (string)InpMagic;
if(GlobalVariableCheck(k1) && (datetime)GlobalVariableGet(k1) == ServerDayStart())
{
g_dayStart = (datetime)GlobalVariableGet(k1);
g_dayStartEquity = GlobalVariableGet(k2);
}
}
Call LoadDay() in OnInit and SaveDay() whenever a new day starts.
Things to test
- Run the Strategy Tester over a period that includes a sharp drop and confirm the log shows the limit line and no entries after it.
- Test with a position held over midnight and check the start equity includes its floating result.
- Restart the terminal halfway through a losing day and check the EA still remembers the start equity.
A guard like this will not make a strategy profitable, but it caps how much one broken day can cost.
Top comments (0)