, custom indicators run in an isolated thread and cannot intercept the manual F9 (New Order) window due to platform architecture.
like the Pine Script version, the cleanest approach is an on-chart HUD / Dashboard indicator that calculates today’s execution stats from midnight broker time (00:00), updates tick-by-tick, displays PERMISSION: GRANTED or PERMISSION: REVOKED, and fires alerts the moment a boundary is breached.
Code: Select all
//+------------------------------------------------------------------+
//| OvertradingController.mq4 |
//| Daily Limit & Permission HUD |
//+------------------------------------------------------------------+
#property copyright "Trading Discipline Tools"
#property link ""
#property version "1.00"
#property indicator_chart_window
//--- Inputs
input group "=== Daily Risk Parameters ==="
input double InpMaxDailyLoss = 500.0; // Max Daily Loss (Account Currency)
input int InpMaxDailyTrades = 3; // Max Trades Opened Today
input bool InpIncludeFloating= true; // Include Floating Drawdown in Loss Limit
input bool InpSendAlerts = true; // Popup & Sound Alerts on Limit Breach
//--- State
datetime glbDayStart = 0;
bool glbAlertSent = false;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
EventSetTimer(1); // 1-second refresh for timer consistency
DrawDashboard("Initializing...", clrGray);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
ObjectDelete(0, "OTC_Panel_BG");
ObjectDelete(0, "OTC_Panel_Title");
ObjectDelete(0, "OTC_Panel_Trades");
ObjectDelete(0, "OTC_Panel_PnL");
ObjectDelete(0, "OTC_Panel_Status");
}
//+------------------------------------------------------------------+
//| Timer & Tick handling |
//+------------------------------------------------------------------+
void OnTimer()
{
RunCheck();
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
RunCheck();
return(rates_total);
}
//+------------------------------------------------------------------+
//| Main calculation and permission evaluation |
//+------------------------------------------------------------------+
void RunCheck()
{
datetime currentDayStart = iTime(_Symbol, PERIOD_D1, 0);
if(currentDayStart != glbDayStart)
{
glbDayStart = currentDayStart;
glbAlertSent = false; // Reset daily alert flag
}
int tradesOpenedToday = 0;
double closedDailyPnL = 0.0;
double floatingPnL = 0.0;
// 1. Scan closed trades from history
int histTotal = OrdersHistoryTotal();
for(int i = 0; i < histTotal; i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
{
if(OrderOpenTime() >= glbDayStart)
tradesOpenedToday++;
if(OrderCloseTime() >= glbDayStart)
closedDailyPnL += (OrderProfit() + OrderSwap() + OrderCommission());
}
}
}
// 2. Scan currently open positions
int openTotal = OrdersTotal();
for(int j = 0; j < openTotal; j++)
{
if(OrderSelect(j, SELECT_BY_POS, MODE_TRADES))
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
{
if(OrderOpenTime() >= glbDayStart)
tradesOpenedToday++;
floatingPnL += (OrderProfit() + OrderSwap() + OrderCommission());
}
}
}
double totalEffectivePnL = closedDailyPnL + (InpIncludeFloating ? floatingPnL : 0.0);
// 3. Permission Evaluation
bool tradeLimitHit = (tradesOpenedToday >= InpMaxDailyTrades);
bool lossLimitHit = (totalEffectivePnL <= -InpMaxDailyLoss);
bool permitted = (!tradeLimitHit && !lossLimitHit);
string statusStr = "";
color statusColor = clrLime;
if(!permitted)
{
statusColor = clrCrimson;
if(tradeLimitHit && lossLimitHit)
statusStr = "REVOKED (Limit & Loss Reached)";
else if(tradeLimitHit)
statusStr = "REVOKED (Max Trades Hit)";
else
statusStr = "REVOKED (Max Loss Hit)";
if(InpSendAlerts && !glbAlertSent)
{
Alert(StringFormat("[Overtrading Controller] %s breached! Trading PERMISSION REVOKED.", _Symbol));
glbAlertSent = true;
}
}
else
{
statusStr = "GRANTED";
}
// 4. Update HUD
UpdateDashboard(tradesOpenedToday, totalEffectivePnL, statusStr, statusColor);
}
//+------------------------------------------------------------------+
//| On-screen HUD visual objects |
//+------------------------------------------------------------------+
void DrawDashboard(string initText, color initCol)
{
CreateLabel("OTC_Panel_Title", 20, 30, "=== PERMISSION CONTROLLER ===", clrSilver, 9, true);
CreateLabel("OTC_Panel_Trades", 20, 50, "Trades Today: --", clrWhite, 9, false);
CreateLabel("OTC_Panel_PnL", 20, 70, "Daily P&L: --", clrWhite, 9, false);
CreateLabel("OTC_Panel_Status", 20, 95, "PERMISSION: " + initText, initCol, 10, true);
}
void UpdateDashboard(int count, double pnl, string status, color col)
{
ObjectSetString(0, "OTC_Panel_Trades", OBJPROP_TEXT, StringFormat("Tickets Opened Today: %d / %d", count, InpMaxDailyTrades));
ObjectSetString(0, "OTC_Panel_PnL", OBJPROP_TEXT, StringFormat("Session Net P&L: %.2f (Limit: -%.2f)", pnl, InpMaxDailyLoss));
ObjectSetString(0, "OTC_Panel_Status", OBJPROP_TEXT, "PERMISSION: " + status);
ObjectSetInteger(0, "OTC_Panel_Status", OBJPROP_COLOR, col);
}
void CreateLabel(string name, int x, int y, string text, color clr, int fontSize, bool bold)
{
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetString(0, name, OBJPROP_FONT, bold ? "Arial Bold" : "Arial");
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}