Sticky-note daily loss limit that faces the screen
Re: Sticky-note daily loss limit that faces the screen
To translate the "digital sticky note" philosophy to MetaTrader, we must use an Expert Advisor (EA) rather than an indicator. Indicators cannot close trades.
By running these as EAs on a dedicated chart, they act as active background enforcers. If you hit your daily limit, the EA immediately flattens the account. More importantly, if you try to "negotiate" by opening a new ticket manually, the EA will instantly detect the breach and close the new trade within milliseconds. It acts as an aggressive circuit breaker.
Here are the basic versions for both MQL4 and MQL5. Both use a 1-second timer to ensure enforcement happens even in low-liquidity/low-tick conditions.
By running these as EAs on a dedicated chart, they act as active background enforcers. If you hit your daily limit, the EA immediately flattens the account. More importantly, if you try to "negotiate" by opening a new ticket manually, the EA will instantly detect the breach and close the new trade within milliseconds. It acts as an aggressive circuit breaker.
Here are the basic versions for both MQL4 and MQL5. Both use a 1-second timer to ensure enforcement happens even in low-liquidity/low-tick conditions.
Re: Sticky-note daily loss limit that faces the screen
MQL4: Digital Sticky Note EA
Code: Select all
//+------------------------------------------------------------------+
//| DigitalStickyNote.mq4 |
//+------------------------------------------------------------------+
#property copyright "Risk Enforcer"
#property strict
input double MaxDailyLoss = 500.0; // Daily Loss Limit (Local Currency)
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
EventSetTimer(1);
DrawUI();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
EventKillTimer();
ObjectsDeleteAll(0, "Sticky_");
}
//+------------------------------------------------------------------+
//| Timer function (Runs every second, independent of ticks) |
//+------------------------------------------------------------------+
void OnTimer() {
CheckRiskLimit();
}
void OnTick() {
CheckRiskLimit();
}
//+------------------------------------------------------------------+
//| Core Logic |
//+------------------------------------------------------------------+
void CheckRiskLimit() {
double dailyPnL = CalculateDailyPnL();
bool limitHit = (dailyPnL <= -MaxDailyLoss);
UpdateUI(dailyPnL, limitHit);
if (limitHit) {
FlattenAccount();
}
}
double CalculateDailyPnL() {
double pnl = 0.0;
datetime startOfDay = iTime(Symbol(), PERIOD_D1, 0);
// 1. Sum Closed Trades Today
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
if(OrderCloseTime() >= startOfDay) {
pnl += OrderProfit() + OrderCommission() + OrderSwap();
}
}
}
// 2. Sum Open Trades (Floating PnL)
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
pnl += OrderProfit() + OrderCommission() + OrderSwap();
}
}
return pnl;
}
void FlattenAccount() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderType() == OP_BUY) {
OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), 3);
} else if(OrderType() == OP_SELL) {
OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_ASK), 3);
} else {
OrderDelete(OrderTicket()); // Delete pending orders
}
}
}
}
//+------------------------------------------------------------------+
//| UI Rendering |
//+------------------------------------------------------------------+
void DrawUI() {
ObjectCreate(0, "Sticky_Bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_XSIZE, 200);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_YSIZE, 70);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_BGCOLOR, clrYellow);
ObjectCreate(0, "Sticky_Limit", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_XDISTANCE, 190);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_YDISTANCE, 60);
ObjectSetString(0, "Sticky_Limit", OBJPROP_FONT, "Arial");
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_FONTSIZE, 10);
ObjectCreate(0, "Sticky_PnL", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_XDISTANCE, 190);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_YDISTANCE, 35);
ObjectSetString(0, "Sticky_PnL", OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_FONTSIZE, 12);
}
void UpdateUI(double pnl, bool limitHit) {
color bgColor = limitHit ? clrRed : clrYellow;
color txtColor = limitHit ? clrWhite : clrBlack;
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_COLOR, txtColor);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_COLOR, txtColor);
ObjectSetString(0, "Sticky_Limit", OBJPROP_TEXT, "MAX LOSS: -$" + DoubleToStr(MaxDailyLoss, 2));
ObjectSetString(0, "Sticky_PnL", OBJPROP_TEXT, "TODAY: $" + DoubleToStr(pnl, 2));
ChartRedraw();
}Re: Sticky-note daily loss limit that faces the screen
MQL5: Digital Sticky Note EA
MQL5 architecture separates orders, deals, and positions. This version uses the standard #include <Trade\Trade.mqh> library for robust execution.
MQL5 architecture separates orders, deals, and positions. This version uses the standard #include <Trade\Trade.mqh> library for robust execution.
Code: Select all
//+------------------------------------------------------------------+
//| DigitalStickyNote.mq5 |
//+------------------------------------------------------------------+
#property copyright "Risk Enforcer"
#property version "1.00"
#include <Trade\Trade.mqh>
CTrade trade;
input double MaxDailyLoss = 500.0; // Daily Loss Limit (Local Currency)
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
EventSetTimer(1);
DrawUI();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
EventKillTimer();
ObjectsDeleteAll(0, "Sticky_");
}
void OnTimer() {
CheckRiskLimit();
}
void OnTick() {
CheckRiskLimit();
}
//+------------------------------------------------------------------+
//| Core Logic |
//+------------------------------------------------------------------+
void CheckRiskLimit() {
double dailyPnL = CalculateDailyPnL();
bool limitHit = (dailyPnL <= -MaxDailyLoss);
UpdateUI(dailyPnL, limitHit);
if (limitHit) {
FlattenAccount();
}
}
double CalculateDailyPnL() {
double pnl = 0.0;
// Get start of today
MqlDateTime dt;
TimeCurrent(dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
datetime startOfDay = StructToTime(dt);
// 1. Sum Closed Deals Today (Realized)
if(HistorySelect(startOfDay, TimeCurrent())) {
int dealsTotal = HistoryDealsTotal();
for(int i = 0; i < dealsTotal; i++) {
ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket > 0) {
pnl += HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION)
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP)
+ HistoryDealGetDouble(dealTicket, DEAL_FEE);
}
}
}
// 2. Sum Open Positions (Floating)
int posTotal = PositionsTotal();
for(int i = 0; i < posTotal; i++) {
ulong posTicket = PositionGetTicket(i);
if(posTicket > 0) {
pnl += PositionGetDouble(POSITION_PROFIT)
+ PositionGetDouble(POSITION_SWAP);
}
}
return pnl;
}
void FlattenAccount() {
// Close Open Positions
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong posTicket = PositionGetTicket(i);
if(posTicket > 0) {
trade.PositionClose(posTicket);
}
}
// Delete Pending Orders
for(int i = OrdersTotal() - 1; i >= 0; i--) {
ulong orderTicket = OrderGetTicket(i);
if(orderTicket > 0) {
trade.OrderDelete(orderTicket);
}
}
}
//+------------------------------------------------------------------+
//| UI Rendering |
//+------------------------------------------------------------------+
void DrawUI() {
ObjectCreate(0, "Sticky_Bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_XSIZE, 200);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_YSIZE, 70);
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_BGCOLOR, clrYellow);
ObjectCreate(0, "Sticky_Limit", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_XDISTANCE, 190);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_YDISTANCE, 60);
ObjectSetString(0, "Sticky_Limit", OBJPROP_FONT, "Arial");
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_FONTSIZE, 10);
ObjectCreate(0, "Sticky_PnL", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_XDISTANCE, 190);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_YDISTANCE, 35);
ObjectSetString(0, "Sticky_PnL", OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_FONTSIZE, 12);
}
void UpdateUI(double pnl, bool limitHit) {
color bgColor = limitHit ? clrRed : clrYellow;
color txtColor = limitHit ? clrWhite : clrBlack;
ObjectSetInteger(0, "Sticky_Bg", OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, "Sticky_Limit", OBJPROP_COLOR, txtColor);
ObjectSetInteger(0, "Sticky_PnL", OBJPROP_COLOR, txtColor);
ObjectSetString(0, "Sticky_Limit", OBJPROP_TEXT, "MAX LOSS: -$" + DoubleToString(MaxDailyLoss, 2));
ObjectSetString(0, "Sticky_PnL", OBJPROP_TEXT, "TODAY: $" + DoubleToString(pnl, 2));
ChartRedraw();
}Re: Sticky-note daily loss limit that faces the screen
Operational Deployment
To run these properly alongside manual scalping:
1.) Open a blank chart (e.g., EURUSD on a daily timeframe so ticks don't distract you) and attach this EA. Keep this chart minimized or on a secondary screen.
2.) Ensure "Allow Auto Trading" / "Algo Trading" is enabled in the terminal platform settings.
3.) Because both scripts utilize OnTimer() running at 1-second intervals, they will enforce the limit during quiet market hours, weekend spikes, or illiquid periods where ticks aren't registering fast enough.
To run these properly alongside manual scalping:
1.) Open a blank chart (e.g., EURUSD on a daily timeframe so ticks don't distract you) and attach this EA. Keep this chart minimized or on a secondary screen.
2.) Ensure "Allow Auto Trading" / "Algo Trading" is enabled in the terminal platform settings.
3.) Because both scripts utilize OnTimer() running at 1-second intervals, they will enforce the limit during quiet market hours, weekend spikes, or illiquid periods where ticks aren't registering fast enough.
Re: Sticky-note daily loss limit that faces the screen
To build the "Pro" Risk Desk in MQL4 and MQL5, we have to translate Pine Script’s native tracking into custom historical array parsing. Since MT4/MT5 don't have built-in behavior tracking, the EA must manually iterate through your closed deals to calculate your daily realization, R-multiples, and consecutive losing streaks in real-time.
Here is the institutional-grade architecture for both platforms. It features a dark-mode HUD, background environment overriding (flashing the chart red/purple on a breach), and aggressive sub-second circuit breakers.
Here is the institutional-grade architecture for both platforms. It features a dark-mode HUD, background environment overriding (flashing the chart red/purple on a breach), and aggressive sub-second circuit breakers.
Re: Sticky-note daily loss limit that faces the screen
MQL4: Pro Risk Desk EA
Code: Select all
//+------------------------------------------------------------------+
//| ProRiskDesk_MT4.mq4 |
//+------------------------------------------------------------------+
#property copyright "Risk Enforcer Pro"
#property strict
// --- Sunday Prep Inputs ---
input double MaxDailyLoss = 500.0; // Daily Loss Limit ($)
input double R_Value = 100.0; // Value of 1R ($)
input int MaxTrades = 10; // Max Trades Per Day
input int MaxStreak = 3; // Max Consecutive Losses
input bool FlashChart = true; // Override Chart Background on Halt
// --- State Variables ---
double startOfDayEquity = 0;
double peakDailyEquity = 0;
bool isHalted = false;
bool alertFired = false;
string haltReason = "";
color originalBgColor;
//+------------------------------------------------------------------+
//| Initialization |
//+------------------------------------------------------------------+
int OnInit() {
originalBgColor = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
peakDailyEquity = startOfDayEquity;
EventSetTimer(1);
DrawUI();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
EventKillTimer();
ObjectsDeleteAll(0, "RD_");
ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBgColor);
}
void OnTimer() { CheckRiskLimits(); }
void OnTick() { CheckRiskLimits(); }
//+------------------------------------------------------------------+
//| Core Logic |
//+------------------------------------------------------------------+
void CheckRiskLimits() {
datetime startOfDay = iTime(Symbol(), PERIOD_D1, 0);
// Reset state on new day
static datetime currentDay = 0;
if (startOfDay != currentDay) {
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
peakDailyEquity = startOfDayEquity;
isHalted = false;
alertFired = false;
haltReason = "";
currentDay = startOfDay;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBgColor);
}
// Update Peak Equity
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
if (currentEquity > peakDailyEquity) peakDailyEquity = currentEquity;
double peakDrawdown = peakDailyEquity - currentEquity;
// Calculate Stats
int tradesToday = 0;
int consecLosses = 0;
double realizedPnL = 0;
// Track History
for(int i = 0; i < OrdersHistoryTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
if(OrderCloseTime() >= startOfDay && OrderType() <= OP_SELL) {
tradesToday++;
realizedPnL += OrderProfit() + OrderCommission() + OrderSwap();
}
}
}
// Track Consecutive Losses (Reverse loop)
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
if(OrderCloseTime() < startOfDay) break;
if(OrderType() <= OP_SELL) {
double prf = OrderProfit() + OrderCommission() + OrderSwap();
if(prf < 0) consecLosses++;
else break; // Streak broken
}
}
}
// Calculate Floating PnL
double floatingPnL = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
floatingPnL += OrderProfit() + OrderCommission() + OrderSwap();
}
}
double totalPnL = realizedPnL + floatingPnL;
double safeR = R_Value > 0 ? R_Value : 1;
double pnlInR = totalPnL / safeR;
// Check Halt Conditions
bool limitHit = (totalPnL <= -MaxDailyLoss);
bool tiltHit = (consecLosses >= MaxStreak);
bool overtradeHit = (tradesToday >= MaxTrades);
if (limitHit || tiltHit || overtradeHit) {
isHalted = true;
if(limitHit) haltReason = "HARD LIMIT HIT";
else if(tiltHit) haltReason = "TILT: MAX LOSS STREAK";
else haltReason = "OVERTRADING CAP HIT";
FlattenAccount();
if (!alertFired) {
Alert("TRADING HALTED: ", haltReason);
alertFired = true;
if (FlashChart) {
color bg = limitHit ? clrMaroon : clrPurple;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, bg);
}
}
}
UpdateUI(totalPnL, pnlInR, peakDrawdown, tradesToday, consecLosses);
}
//+------------------------------------------------------------------+
//| Execution |
//+------------------------------------------------------------------+
void FlattenAccount() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderType() == OP_BUY) {
OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), 3);
} else if(OrderType() == OP_SELL) {
OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_ASK), 3);
} else {
OrderDelete(OrderTicket());
}
}
}
}
//+------------------------------------------------------------------+
//| UI Engine |
//+------------------------------------------------------------------+
void DrawUI() {
CreateRect("RD_Bg", 20, 20, 220, 140, clrBlack);
CreateText("RD_Title", 130, 125, "RISK DESK: ACTIVE", clrWhite, 10, true);
CreateText("RD_L_PnL", 220, 100, "Daily PnL:", clrSilver, 9);
CreateText("RD_V_PnL", 40, 100, "$0.00", clrLime, 9);
CreateText("RD_L_R", 220, 80, "Target (R):", clrSilver, 9);
CreateText("RD_V_R", 40, 80, "0.00 R", clrLime, 9);
CreateText("RD_L_DD", 220, 60, "Drawdown:", clrSilver, 9);
CreateText("RD_V_DD", 40, 60, "$0.00", clrGray, 9);
CreateText("RD_L_Trd", 220, 40, "Trades:", clrSilver, 9);
CreateText("RD_V_Trd", 40, 40, "0 / 0", clrWhite, 9);
CreateText("RD_L_Str", 220, 20, "Loss Streak:", clrSilver, 9);
CreateText("RD_V_Str", 40, 20, "0 / 0", clrWhite, 9);
}
void CreateRect(string name, int x, int y, int w, int h, color bg) {
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrDimGray);
}
void CreateText(string name, int x, int y, string text, color clr, int size, bool bold=false) {
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_FONT, bold ? "Courier New Bold" : "Courier New");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
void UpdateUI(double pnl, double r, double dd, int trd, int str) {
ObjectSetString(0, "RD_Title", OBJPROP_TEXT, isHalted ? haltReason : "RISK DESK: ACTIVE");
ObjectSetInteger(0, "RD_Title", OBJPROP_COLOR, isHalted ? clrWhite : clrAqua);
ObjectSetInteger(0, "RD_Bg", OBJPROP_BGCOLOR, isHalted ? clrMaroon : clrBlack);
ObjectSetString(0, "RD_V_PnL", OBJPROP_TEXT, (pnl >= 0 ? "+$" : "-$") + DoubleToStr(MathAbs(pnl), 2));
ObjectSetInteger(0, "RD_V_PnL", OBJPROP_COLOR, pnl >= 0 ? clrLime : clrRed);
ObjectSetString(0, "RD_V_R", OBJPROP_TEXT, (r >= 0 ? "+" : "") + DoubleToStr(r, 2) + " R");
ObjectSetInteger(0, "RD_V_R", OBJPROP_COLOR, r >= 0 ? clrLime : clrRed);
ObjectSetString(0, "RD_V_DD", OBJPROP_TEXT, "$" + DoubleToStr(dd, 2));
ObjectSetString(0, "RD_V_Trd", OBJPROP_TEXT, IntegerToString(trd) + " / " + IntegerToString(MaxTrades));
ObjectSetInteger(0, "RD_V_Trd", OBJPROP_COLOR, trd >= MaxTrades ? clrRed : clrWhite);
ObjectSetString(0, "RD_V_Str", OBJPROP_TEXT, IntegerToString(str) + " / " + IntegerToString(MaxStreak));
ObjectSetInteger(0, "RD_V_Str", OBJPROP_COLOR, str >= MaxStreak ? clrRed : clrWhite);
ChartRedraw();
}Re: Sticky-note daily loss limit that faces the screen
MQL5: Pro Risk Desk EA
In MQL5, we must filter history deals specifically by DEAL_ENTRY_OUT or DEAL_ENTRY_INOUT to accurately count round-trip executions and streaks without counting deposits/withdrawals.
In MQL5, we must filter history deals specifically by DEAL_ENTRY_OUT or DEAL_ENTRY_INOUT to accurately count round-trip executions and streaks without counting deposits/withdrawals.
Code: Select all
//+------------------------------------------------------------------+
//| ProRiskDesk_MT5.mq5 |
//+------------------------------------------------------------------+
#property copyright "Risk Enforcer Pro"
#property version "1.00"
#include <Trade\Trade.mqh>
CTrade trade;
// --- Sunday Prep Inputs ---
input double MaxDailyLoss = 500.0;
input double R_Value = 100.0;
input int MaxTrades = 10;
input int MaxStreak = 3;
input bool FlashChart = true;
// --- State Variables ---
double startOfDayEquity = 0;
double peakDailyEquity = 0;
bool isHalted = false;
bool alertFired = false;
string haltReason = "";
color originalBgColor;
//+------------------------------------------------------------------+
//| Initialization |
//+------------------------------------------------------------------+
int OnInit() {
originalBgColor = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
peakDailyEquity = startOfDayEquity;
EventSetTimer(1);
DrawUI();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
EventKillTimer();
ObjectsDeleteAll(0, "RD_");
ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBgColor);
}
void OnTimer() { CheckRiskLimits(); }
void OnTick() { CheckRiskLimits(); }
//+------------------------------------------------------------------+
//| Core Logic |
//+------------------------------------------------------------------+
void CheckRiskLimits() {
MqlDateTime dt;
TimeCurrent(dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
datetime startOfDay = StructToTime(dt);
static datetime currentDay = 0;
if (startOfDay != currentDay) {
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
peakDailyEquity = startOfDayEquity;
isHalted = false;
alertFired = false;
haltReason = "";
currentDay = startOfDay;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBgColor);
}
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
if (currentEquity > peakDailyEquity) peakDailyEquity = currentEquity;
double peakDrawdown = peakDailyEquity - currentEquity;
int tradesToday = 0;
int consecLosses = 0;
double realizedPnL = 0;
if(HistorySelect(startOfDay, TimeCurrent())) {
int dealsTotal = HistoryDealsTotal();
// Count total trades & realized PnL
for(int i = 0; i < dealsTotal; i++) {
ulong ticket = HistoryDealGetTicket(i);
long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT) {
tradesToday++;
realizedPnL += HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ HistoryDealGetDouble(ticket, DEAL_COMMISSION)
+ HistoryDealGetDouble(ticket, DEAL_SWAP)
+ HistoryDealGetDouble(ticket, DEAL_FEE);
}
}
// Calculate Streak (Reverse loop)
for(int i = dealsTotal - 1; i >= 0; i--) {
ulong ticket = HistoryDealGetTicket(i);
long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT) {
double prf = HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ HistoryDealGetDouble(ticket, DEAL_COMMISSION)
+ HistoryDealGetDouble(ticket, DEAL_SWAP)
+ HistoryDealGetDouble(ticket, DEAL_FEE);
if(prf < 0) consecLosses++;
else break;
}
}
}
double floatingPnL = 0;
for(int i = 0; i < PositionsTotal(); i++) {
ulong posTicket = PositionGetTicket(i);
if(posTicket > 0) {
floatingPnL += PositionGetDouble(POSITION_PROFIT)
+ PositionGetDouble(POSITION_SWAP);
}
}
double totalPnL = realizedPnL + floatingPnL;
double safeR = R_Value > 0 ? R_Value : 1;
double pnlInR = totalPnL / safeR;
bool limitHit = (totalPnL <= -MaxDailyLoss);
bool tiltHit = (consecLosses >= MaxStreak);
bool overtradeHit = (tradesToday >= MaxTrades);
if (limitHit || tiltHit || overtradeHit) {
isHalted = true;
if(limitHit) haltReason = "HARD LIMIT HIT";
else if(tiltHit) haltReason = "TILT: MAX LOSS STREAK";
else haltReason = "OVERTRADING CAP HIT";
FlattenAccount();
if (!alertFired) {
Alert("TRADING HALTED: ", haltReason);
alertFired = true;
if (FlashChart) {
color bg = limitHit ? clrMaroon : clrPurple;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, bg);
}
}
}
UpdateUI(totalPnL, pnlInR, peakDrawdown, tradesToday, consecLosses);
}
//+------------------------------------------------------------------+
//| Execution |
//+------------------------------------------------------------------+
void FlattenAccount() {
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong posTicket = PositionGetTicket(i);
if(posTicket > 0) trade.PositionClose(posTicket);
}
for(int i = OrdersTotal() - 1; i >= 0; i--) {
ulong orderTicket = OrderGetTicket(i);
if(orderTicket > 0) trade.OrderDelete(orderTicket);
}
}
//+------------------------------------------------------------------+
//| UI Engine |
//+------------------------------------------------------------------+
void DrawUI() {
CreateRect("RD_Bg", 20, 20, 220, 140, clrBlack);
CreateText("RD_Title", 130, 125, "RISK DESK: ACTIVE", clrWhite, 10, true);
CreateText("RD_L_PnL", 220, 100, "Daily PnL:", clrSilver, 9);
CreateText("RD_V_PnL", 40, 100, "$0.00", clrLime, 9);
CreateText("RD_L_R", 220, 80, "Target (R):", clrSilver, 9);
CreateText("RD_V_R", 40, 80, "0.00 R", clrLime, 9);
CreateText("RD_L_DD", 220, 60, "Drawdown:", clrSilver, 9);
CreateText("RD_V_DD", 40, 60, "$0.00", clrGray, 9);
CreateText("RD_L_Trd", 220, 40, "Trades:", clrSilver, 9);
CreateText("RD_V_Trd", 40, 40, "0 / 0", clrWhite, 9);
CreateText("RD_L_Str", 220, 20, "Loss Streak:", clrSilver, 9);
CreateText("RD_V_Str", 40, 20, "0 / 0", clrWhite, 9);
}
void CreateRect(string name, int x, int y, int w, int h, color bg) {
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrDimGray);
}
void CreateText(string name, int x, int y, string text, color clr, int size, bool bold=false) {
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_FONT, bold ? "Courier New Bold" : "Courier New");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
void UpdateUI(double pnl, double r, double dd, int trd, int str) {
ObjectSetString(0, "RD_Title", OBJPROP_TEXT, isHalted ? haltReason : "RISK DESK: ACTIVE");
ObjectSetInteger(0, "RD_Title", OBJPROP_COLOR, isHalted ? clrWhite : clrAqua);
ObjectSetInteger(0, "RD_Bg", OBJPROP_BGCOLOR, isHalted ? clrMaroon : clrBlack);
ObjectSetString(0, "RD_V_PnL", OBJPROP_TEXT, (pnl >= 0 ? "+$" : "-$") + DoubleToString(MathAbs(pnl), 2));
ObjectSetInteger(0, "RD_V_PnL", OBJPROP_COLOR, pnl >= 0 ? clrLime : clrRed);
ObjectSetString(0, "RD_V_R", OBJPROP_TEXT, (r >= 0 ? "+" : "") + DoubleToString(r, 2) + " R");
ObjectSetInteger(0, "RD_V_R", OBJPROP_COLOR, r >= 0 ? clrLime : clrRed);
ObjectSetString(0, "RD_V_DD", OBJPROP_TEXT, "$" + DoubleToString(dd, 2));
ObjectSetString(0, "RD_V_Trd", OBJPROP_TEXT, IntegerToString(trd) + " / " + IntegerToString(MaxTrades));
ObjectSetInteger(0, "RD_V_Trd", OBJPROP_COLOR, trd >= MaxTrades ? clrRed : clrWhite);
ObjectSetString(0, "RD_V_Str", OBJPROP_TEXT, IntegerToString(str) + " / " + IntegerToString(MaxStreak));
ObjectSetInteger(0, "RD_V_Str", OBJPROP_COLOR, str >= MaxStreak ? clrRed : clrWhite);
ChartRedraw();
}Re: Sticky-note daily loss limit that faces the screen
Institutional Deployment
1.) Run this on a completely isolated, blank chart (like a Daily timeframe so ticks aren't distracting).
2.) The UI is built programmatically using modular CreateRect and CreateText functions to keep the architecture clean (and offload rendering overhead).
3.) The Lockout: Because the execution block (FlattenAccount) is tied directly to the OnTimer() function that fires every 1000ms, if you hit a behavioral limit (e.g., 3 consecutive losses) and attempt to bypass it by manually submitting a new order, the EA will detect the isHalted state and instantly close the trade in the next sub-second cycle.
1.) Run this on a completely isolated, blank chart (like a Daily timeframe so ticks aren't distracting).
2.) The UI is built programmatically using modular CreateRect and CreateText functions to keep the architecture clean (and offload rendering overhead).
3.) The Lockout: Because the execution block (FlattenAccount) is tied directly to the OnTimer() function that fires every 1000ms, if you hit a behavioral limit (e.g., 3 consecutive losses) and attempt to bypass it by manually submitting a new order, the EA will detect the isHalted state and instantly close the trade in the next sub-second cycle.
Re: Sticky-note daily loss limit that faces the screen
Because cTrader is built on C# and .NET, we can build the UI using its native, WPF-style control elements and use LINQ to calculate the historical PnL cleanly.
As a cBot, this runs in the background. It uses a 1-second timer to ensure the circuit breaker functions perfectly even during illiquid market moments when ticks aren't registering fast enough.
Here is the basic "Digital Sticky Note" translated for cTrader.
As a cBot, this runs in the background. It uses a 1-second timer to ensure the circuit breaker functions perfectly even during illiquid market moments when ticks aren't registering fast enough.
Here is the basic "Digital Sticky Note" translated for cTrader.
Re: Sticky-note daily loss limit that faces the screen
cTrader: Digital Sticky Note (cBot)
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Models;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class DigitalStickyNote : Robot
{
[Parameter("Daily Loss Limit ($)", DefaultValue = 500.0, MinValue = 0, Step = 50)]
public double MaxDailyLoss { get; set; }
private Border _stickyBorder;
private TextBlock _limitText;
private TextBlock _pnlText;
protected override void OnStart()
{
// 1. Initialize the UI (The Digital Sticky Note)
DrawUI();
// 2. Start the 1-second timer to enforce risk regardless of tick volume
Timer.Start(TimeSpan.FromSeconds(1));
}
protected override void OnTick()
{
CheckRiskLimit();
}
protected override void OnTimer()
{
CheckRiskLimit();
}
private void CheckRiskLimit()
{
double dailyPnL = CalculateDailyPnL();
bool limitHit = dailyPnL <= -MaxDailyLoss;
UpdateUI(dailyPnL, limitHit);
if (limitHit)
{
FlattenAccount();
}
}
private double CalculateDailyPnL()
{
// Get the start of the current trading day
DateTime startOfDay = Server.Time.Date;
// Sum Realized PnL (Closed trades today). NetProfit includes commissions and swaps.
double realizedPnL = History
.Where(trade => trade.ClosingTime >= startOfDay)
.Sum(trade => trade.NetProfit);
// Sum Unrealized PnL (Open positions floating)
double unrealizedPnL = Positions.Sum(pos => pos.NetProfit);
return realizedPnL + unrealizedPnL;
}
private void FlattenAccount()
{
// Close all open positions asynchronously to avoid blocking the thread
foreach (var position in Positions)
{
ClosePositionAsync(position);
}
// Cancel all pending orders
foreach (var order in PendingOrders)
{
CancelPendingOrderAsync(order);
}
}
private void DrawUI()
{
// Define the text elements
_limitText = new TextBlock
{
Text = $"MAX LOSS: -${MaxDailyLoss:F2}",
ForegroundColor = Color.Black,
FontWeight = FontWeight.Normal,
FontSize = 11
};
_pnlText = new TextBlock
{
Text = "TODAY: $0.00",
ForegroundColor = Color.Black,
FontWeight = FontWeight.ExtraBold,
FontSize = 14,
Margin = new Thickness(0, 5, 0, 0)
};
// Wrap them in a StackPanel
var stackPanel = new StackPanel
{
Orientation = Orientation.Vertical,
Margin = new Thickness(15)
};
stackPanel.AddChild(_limitText);
stackPanel.AddChild(_pnlText);
// Create the Yellow Sticky Note background
_stickyBorder = new Border
{
BackgroundColor = Color.Yellow,
BorderColor = Color.Black,
BorderThickness = new Thickness(2),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(20),
Child = stackPanel
};
// Render to the chart
Chart.AddControl(_stickyBorder);
}
private void UpdateUI(double pnl, bool limitHit)
{
// Visual interrupt: Switch to Red/White if the limit is breached
_stickyBorder.BackgroundColor = limitHit ? Color.Red : Color.Yellow;
_limitText.ForegroundColor = limitHit ? Color.White : Color.Black;
_pnlText.ForegroundColor = limitHit ? Color.White : Color.Black;
_pnlText.Text = $"TODAY: ${(pnl > 0 ? "+" : "")}{pnl:F2}";
}
}
}