Re: Track Emotional State and Rule Adherence Alongside P&L
Posted: Fri Sep 25, 2026 1:04 pm
Unlike C# in cTrader, MetaTrader 4 and 5 do not expose asynchronous methods for instantly dropping orders from memory, nor can you block the "New Order" terminal button. The circuit breaker must be deployed as an Expert Advisor (EA) running a high-frequency OnTimer() event. This ensures the bot detects off-plan trades even when the market is slow and no new ticks are arriving.
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.
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);
}
}