MetaTrader 5 (MQL5) Implementation
MT5 has a structural advantage for this: the OnTradeTransaction event. If you attempt to enter a manual trade while the daily lockout is active, this event intercepts the order placement instantly in the background, bypassing the need to wait for the next price tick.
Code: Select all
#include <Trade\Trade.mqh>
input double MaxDailyLoss = 500.0; // Max Daily Loss (Account Currency)
CTrade trade;
double startOfDayBalance = 0;
int currentDay = -1;
bool isLockedOut = false;
int OnInit() {
EventSetTimer(1); // 1-second timer for off-tick monitoring
UpdateStartOfDayBalance();
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) {
EventKillTimer();
}
void OnTick() {
EvaluateDrawdown();
}
void OnTimer() {
CheckNewDayReset();
EvaluateDrawdown();
// Aggressive fallback sweep
if (isLockedOut && (PositionsTotal() > 0 || OrdersTotal() > 0)) {
LiquidateAccount();
}
}
// Instantly intercepts manual revenge trades
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) {
if (isLockedOut && (PositionsTotal() > 0 || OrdersTotal() > 0)) {
LiquidateAccount();
}
}
void CheckNewDayReset() {
MqlDateTime dt;
TimeCurrent(dt);
if (dt.day_of_year != currentDay) {
UpdateStartOfDayBalance();
isLockedOut = false;
Print("New trading day. Circuit breaker reset.");
}
}
void UpdateStartOfDayBalance() {
MqlDateTime dt;
TimeCurrent(dt);
currentDay = dt.day_of_year;
// Calculate 00:00:00 of the current broker day
dt.hour = 0; dt.min = 0; dt.sec = 0;
datetime startOfDay = StructToTime(dt);
HistorySelect(startOfDay, TimeCurrent());
double dailyClosedPnL = 0;
for(int i = 0; i < HistoryDealsTotal(); i++) {
ulong ticket = HistoryDealGetTicket(i);
dailyClosedPnL += HistoryDealGetDouble(ticket, DEAL_PROFIT) +
HistoryDealGetDouble(ticket, DEAL_COMMISSION) +
HistoryDealGetDouble(ticket, DEAL_SWAP);
}
startOfDayBalance = AccountInfoDouble(ACCOUNT_BALANCE) - dailyClosedPnL;
}
void EvaluateDrawdown() {
if (isLockedOut) return;
double currentLoss = startOfDayBalance - AccountInfoDouble(ACCOUNT_EQUITY);
if (currentLoss >= MaxDailyLoss) {
isLockedOut = true;
Print("CIRCUIT BREAKER TRIGGERED: Daily loss limit reached.");
LiquidateAccount();
}
}
void LiquidateAccount() {
// Loop backward to avoid index shifting during deletions
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if(ticket > 0) trade.PositionClose(ticket);
}
for(int i = OrdersTotal() - 1; i >= 0; i--) {
ulong ticket = OrderGetTicket(i);
if(ticket > 0) trade.OrderDelete(ticket);
}
}