Advertisement IC Markets

Build In a Maximum Trades Per Day Rule

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

2. MetaTrader 5 (MQL5) - Risk Desk EA (Pro)

Code: Select all

//+------------------------------------------------------------------+
//|                                            RiskDesk_Cap_Pro.mq5  |
//|                        Institutional Risk Desk with Persistence  |
//+------------------------------------------------------------------+
#property copyright "Risk Desk Professional"

#include <Trade\Trade.mqh>
CTrade trade;

// --- Risk Desk Inputs ---
input group "--- Risk Enforcement ---"
input int      MaxIdeas         = 3;       // Daily Idea Cap
input int      MaxClicks        = 10;      // Daily Execution Cap
input double   DailyMaxLossUSD  = 500.0;   // Daily Drawdown Hard Stop ($)
input bool     NewsOverride     = false;   // Pre-Planned News Exemption
input ulong    MagicNumber      = 77777;

input group "--- Strategy ---"
input double   LotSize          = 0.1;
input int      RsiPeriod        = 14;

// --- State Variables ---
color originalBG;
string gvIdeas, gvClicks, gvDay;
int dailyIdeas = 0;
int dailyClicks = 0;
double dailyPnL = 0;
int rsiHandle;
double rsiBuffer[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(MagicNumber);
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    
    // Global Variable Names Setup
    string prefix = "RiskDesk_" + _Symbol + "_" + IntegerToString(MagicNumber) + "_";
    gvIdeas = prefix + "Ideas";
    gvClicks = prefix + "Clicks";
    gvDay = prefix + "Day";

    MqlDateTime dt; TimeCurrent(dt);
    
    // State Recovery
    if(GlobalVariableCheck(gvDay) && GlobalVariableGet(gvDay) == dt.day) {
        if(GlobalVariableCheck(gvIdeas)) dailyIdeas = (int)GlobalVariableGet(gvIdeas);
        if(GlobalVariableCheck(gvClicks)) dailyClicks = (int)GlobalVariableGet(gvClicks);
    } else {
        ResetDailyState(dt.day);
    }

    rsiHandle = iRSI(_Symbol, _Period, RsiPeriod, PRICE_CLOSE);
    ArraySetAsSeries(rsiBuffer, true);

    CreateBlotter();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBG);
    ObjectsDeleteAll(0, "DeskBlotter_");
}

void ResetDailyState(int currentDay) {
    dailyIdeas = 0; dailyClicks = 0;
    GlobalVariableSet(gvDay, currentDay);
    GlobalVariableSet(gvIdeas, 0);
    GlobalVariableSet(gvClicks, 0);
}

//+------------------------------------------------------------------+
//| Daily PnL Calculation (MT5 Deal History)                         |
//+------------------------------------------------------------------+
void CalculateDailyPnL() {
    dailyPnL = 0;
    
    // 1. Fetch Closed Deals for Today
    datetime startOfDay = iTime(_Symbol, PERIOD_D1, 0);
    datetime endOfDay = TimeCurrent() + 86400;
    
    if(HistorySelect(startOfDay, endOfDay)) {
        for(int i=0; i<HistoryDealsTotal(); i++) {
            ulong dealTicket = HistoryDealGetTicket(i);
            if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == MagicNumber && HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol) {
                dailyPnL += HistoryDealGetDouble(dealTicket, DEAL_PROFIT) + HistoryDealGetDouble(dealTicket, DEAL_COMMISSION) + HistoryDealGetDouble(dealTicket, DEAL_SWAP);
            }
        }
    }

    // 2. Add Floating PnL
    for(int i = PositionsTotal()-1; i >= 0; i--) {
        ulong posTicket = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
            dailyPnL += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
        }
    }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    MqlDateTime dt; TimeCurrent(dt);
    if(GlobalVariableGet(gvDay) != dt.day) ResetDailyState(dt.day);
    CalculateDailyPnL();

    int openBuys = 0, openSells = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) openBuys++;
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) openSells++;
        }
    }
    bool isFlat = (openBuys == 0 && openSells == 0);

    bool lossLimitHit = (dailyPnL <= -MathAbs(DailyMaxLossUSD));
    bool capReached = (dailyIdeas >= MaxIdeas) || (dailyClicks >= MaxClicks) || lossLimitHit;
    bool warningLevel = (dailyIdeas == MaxIdeas - 1) || (dailyPnL <= -(MathAbs(DailyMaxLossUSD) * 0.8));
    
    bool isHardStop = capReached && !NewsOverride;
    bool isSoftWarning = warningLevel && !capReached;
    
    bool canTakeNewIdea = !isHardStop;
    bool canScaleIn = (dailyClicks < MaxClicks && !lossLimitHit) || NewsOverride;

    UpdateBlotter(isHardStop, isSoftWarning, lossLimitHit);

    if(CopyBuffer(rsiHandle, 0, 0, 2, rsiBuffer) <= 0) return;
    bool longSignal = (rsiBuffer[1] < 30 && rsiBuffer[0] >= 30);
    bool shortSignal = (rsiBuffer[1] > 70 && rsiBuffer[0] <= 70);
    bool flatSignal = ( (openBuys > 0 && rsiBuffer[0] >= 50) || (openSells > 0 && rsiBuffer[0] <= 50) );

    // FORCE CLOSE IF LIMIT HIT OR EXIT SIGNAL
    if(flatSignal || (lossLimitHit && !isFlat)) {
        for(int i = PositionsTotal() - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
                trade.PositionClose(ticket);
            }
        }
    }

    if(isFlat && canTakeNewIdea) {
        if(longSignal && trade.Buy(LotSize, _Symbol, 0, 0, 0, "New Idea")) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && trade.Sell(LotSize, _Symbol, 0, 0, 0, "New Idea")) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
    else if(!isFlat && canScaleIn) {
        if(longSignal && openBuys > 0 && trade.Buy(LotSize, _Symbol, 0, 0, 0, "Scale")) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && openSells > 0 && trade.Sell(LotSize, _Symbol, 0, 0, 0, "Scale")) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
}

//+------------------------------------------------------------------+
//| UI GUI Panel Functions                                           |
//+------------------------------------------------------------------+
void CreateBlotter() {
    ObjectCreate(0, "DeskBlotter_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XSIZE, 180);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YSIZE, 110);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BGCOLOR, clrBlack);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BORDER_TYPE, BORDER_FLAT);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_COLOR, clrDimGray);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BACK, true);

    string labels[5] = {"Header", "Ideas", "Clicks", "PnL", "Status"};
    for(int i=0; i<5; i++) {
        string name = "DeskBlotter_" + labels[i];
        ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 20);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 90 - (i * 20));
        ObjectSetString(0, name, OBJPROP_FONT, "Trebuchet MS");
        ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
    }
}

void UpdateBlotter(bool isHardStop, bool isSoftWarning, bool lossLimitHit) {
    color bg = NewsOverride ? clrMidnightBlue : (isHardStop ? clrMaroon : (isSoftWarning ? clrSaddleBrown : originalBG));
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, bg);

    string status = NewsOverride ? "NEWS EXEMPTION" : (lossLimitHit ? "MAX LOSS HIT" : (isHardStop ? "LOCKED (Walk Away)" : (isSoftWarning ? "A+ SETUPS ONLY" : "ACTIVE")));
    color statClr = NewsOverride ? clrDeepSkyBlue : (isHardStop ? clrRed : (isSoftWarning ? clrOrange : clrLimeGreen));
    
    ObjectSetString(0, "DeskBlotter_Header", OBJPROP_TEXT, "--- RISK DESK ---");
    ObjectSetInteger(0, "DeskBlotter_Header", OBJPROP_COLOR, clrSilver);
    ObjectSetString(0, "DeskBlotter_Ideas", OBJ, PROP_TEXT, "Ideas: " + IntegerToString(dailyIdeas) + " / " + IntegerToString(MaxIdeas));
    ObjectSetInteger(0, "DeskBlotter_Ideas", OBJPROP_COLOR, (dailyIdeas >= MaxIdeas) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_Clicks", OBJPROP_TEXT, "Clicks: " + IntegerToString(dailyClicks) + " / " + IntegerToString(MaxClicks));
    ObjectSetInteger(0, "DeskBlotter_Clicks", OBJPROP_COLOR, (dailyClicks >= MaxClicks) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_PnL", OBJPROP_TEXT, "Daily PnL: $" + DoubleToString(dailyPnL, 2));
    ObjectSetInteger(0, "DeskBlotter_PnL", OBJPROP_COLOR, (dailyPnL < 0) ? clrRed : clrLimeGreen);
    ObjectSetString(0, "DeskBlotter_Status", OBJPROP_TEXT, status);
    ObjectSetInteger(0, "DeskBlotter_Status", OBJPROP_COLOR, statClr);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Breakdown of the Pro Architecture

Terminal Global Variables (GV_): Standard variables get wiped if you switch from a 15-minute to a 5-minute chart, ruining your daily count. This setup intercepts OnTick(), detects execution success, and immediately writes the incremented counters to the terminal's global memory database. The OnInit() block pulls them back up if the terminal is restarted.

The Daily Max Loss Killswitch: Euphoria often comes right after an agonizing loss when revenge trading takes over. Tracking clicks isn't enough if you double your lot size. The CalculateDailyPnL() function iterates through both your closed deals for the day and your floating PnL. If the combined total breaches DailyMaxLossUSD, the EA forces a flat state, prevents new orders, and locks the chart red.

The Graphical Panel (OBJ_RECTANGLE_LABEL): Price action moving behind text labels makes them illegible. We render a solid black background box (BORDER_FLAT) pinned to the corner, turning it into a dedicated risk dashboard separated from the chart noise.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

If we are building an EA around the philosophy of "hyper-selectivity under euphoria" and only taking A+ setups, the strategy cannot be a simple, twitchy crossover that fires 20 times a day. It needs to be a high-probability, multi-factor model.

I have designed a strategy for this EA called the "Triple-Sync Value Pullback." Professional desks don't usually chase breakouts; they wait for the market to establish a strong trend, wait for retail traders to panic during a pullback, and step in when institutional momentum resumes.

The A+ Setup Rules:

The Macro Filter (The Tide): The 50 EMA must be stacked appropriately with the 200 EMA. We only trade with the dominant market tide.

The Value Zone (The Pullback): Price must pull back to touch or cross the 20 EMA. We refuse to buy at the absolute top; we only buy at a discount.

The Trigger (The Shift): The MACD (12, 26, 9) Signal line must cross, indicating that the short-term pullback is over and momentum is snapping back in the direction of the macro trend.

The Exit (Thesis Invalidation): If price closes strongly on the wrong side of the 50 EMA, the trend thesis is wrong. The EA closes all positions.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Here is the fully integrated, Pro-grade code for both platforms.

1. MetaTrader 4 (MQL4) - Triple-Sync Pro

Save as RiskDesk_TripleSync.mq4.

Code: Select all

//+------------------------------------------------------------------+
//|                                        RiskDesk_TripleSync.mq4   |
//|                        Institutional Risk Desk with Persistence  |
//+------------------------------------------------------------------+
#property copyright "Risk Desk Professional"
#property strict

// --- Risk Desk Inputs ---
input string   Grp1 = "--- Risk Enforcement ---";
input int      MaxIdeas         = 3;       // Daily Idea Cap
input int      MaxClicks        = 10;      // Daily Execution Cap
input double   DailyMaxLossUSD  = 500.0;   // Daily Drawdown Hard Stop ($)
input bool     NewsOverride     = false;   // Pre-Planned News Exemption
input int      MagicNumber      = 77777;

// --- Strategy Inputs (Triple-Sync) ---
input string   Grp2 = "--- Strategy Parameters ---";
input double   LotSize          = 0.1;
input int      FastEma          = 20;
input int      MedEma           = 50;
input int      SlowEma          = 200;

// --- State Management ---
color originalBG;
string gvIdeas, gvClicks, gvDay;
int dailyIdeas = 0;
int dailyClicks = 0;
double dailyPnL = 0;

int OnInit() {
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    string prefix = "RiskDesk_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_";
    gvIdeas = prefix + "Ideas"; gvClicks = prefix + "Clicks"; gvDay = prefix + "Day";

    if(GlobalVariableCheck(gvDay) && GlobalVariableGet(gvDay) == Day()) {
        if(GlobalVariableCheck(gvIdeas)) dailyIdeas = (int)GlobalVariableGet(gvIdeas);
        if(GlobalVariableCheck(gvClicks)) dailyClicks = (int)GlobalVariableGet(gvClicks);
    } else { ResetDailyState(); }

    CreateBlotter();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBG);
    ObjectsDeleteAll(0, "DeskBlotter_");
}

void ResetDailyState() {
    dailyIdeas = 0; dailyClicks = 0;
    GlobalVariableSet(gvDay, Day());
    GlobalVariableSet(gvIdeas, 0); GlobalVariableSet(gvClicks, 0);
}

void CalculateDailyPnL() {
    dailyPnL = 0;
    for(int i = OrdersHistoryTotal()-1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
            if(TimeDay(OrderCloseTime()) == Day() && TimeMonth(OrderCloseTime()) == Month()) {
                dailyPnL += OrderProfit() + OrderCommission() + OrderSwap();
            }
        }
    }
    for(int i = OrdersTotal()-1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
            dailyPnL += OrderProfit() + OrderCommission() + OrderSwap();
        }
    }
}

void OnTick() {
    if(!IsNewBar()) return; // Execute logic on bar close to prevent repainting noise
    
    if(GlobalVariableGet(gvDay) != Day()) ResetDailyState();
    CalculateDailyPnL();

    int openBuys = 0, openSells = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
            if(OrderType() == OP_BUY) openBuys++;
            if(OrderType() == OP_SELL) openSells++;
        }
    }
    bool isFlat = (openBuys == 0 && openSells == 0);

    bool lossLimitHit = (dailyPnL <= -MathAbs(DailyMaxLossUSD));
    bool capReached = (dailyIdeas >= MaxIdeas) || (dailyClicks >= MaxClicks) || lossLimitHit;
    bool warningLevel = (dailyIdeas == MaxIdeas - 1) || (dailyPnL <= -(MathAbs(DailyMaxLossUSD) * 0.8));
    
    bool isHardStop = capReached && !NewsOverride;
    bool isSoftWarning = warningLevel && !capReached;
    
    bool canTakeNewIdea = !isHardStop;
    bool canScaleIn = (dailyClicks < MaxClicks && !lossLimitHit) || NewsOverride;

    UpdateBlotter(isHardStop, isSoftWarning, lossLimitHit);

    // --- TRIPLE-SYNC LOGIC ---
    double ema200 = iMA(NULL, 0, SlowEma, 0, MODE_EMA, PRICE_CLOSE, 1);
    double ema50  = iMA(NULL, 0, MedEma, 0, MODE_EMA, PRICE_CLOSE, 1);
    double ema20  = iMA(NULL, 0, FastEma, 0, MODE_EMA, PRICE_CLOSE, 1);
    
    double macdMain1 = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 1);
    double macdSig1  = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 1);
    double macdMain2 = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 2);
    double macdSig2  = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 2);
    
    double close1 = iClose(NULL, 0, 1);
    double low1   = iLow(NULL, 0, 1);
    double high1  = iHigh(NULL, 0, 1);

    // Long A+ Setup
    bool bullTrend = (ema50 > ema200);
    bool bullPullback = (low1 <= ema20); // Price touched value zone
    bool bullTrigger = (macdMain1 > macdSig1 && macdMain2 <= macdSig2); // Momentum crossover up
    bool longSignal = bullTrend && bullPullback && bullTrigger && (close1 > ema50); // Ensure we haven't broken structure

    // Short A+ Setup
    bool bearTrend = (ema50 < ema200);
    bool bearPullback = (high1 >= ema20); // Price touched value zone
    bool bearTrigger = (macdMain1 < macdSig1 && macdMain2 >= macdSig2); // Momentum crossover down
    bool shortSignal = bearTrend && bearPullback && bearTrigger && (close1 < ema50);

    // Exits: Invalidation of Thesis
    bool exitLong = (close1 < ema50);
    bool exitShort = (close1 > ema50);

    // Execution
    if((openBuys > 0 && exitLong) || (openSells > 0 && exitShort) || (lossLimitHit && !isFlat)) { 
        for(int i = OrdersTotal() - 1; i >= 0; i--) {
            if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
                if(OrderType() == OP_BUY) OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrWhite);
                if(OrderType() == OP_SELL) OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrWhite);
            }
        }
    }

    if(isFlat && canTakeNewIdea) {
        if(longSignal && OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "A+ Long Idea", MagicNumber, 0, clrGreen) > 0) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "A+ Short Idea", MagicNumber, 0, clrRed) > 0) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
    else if(!isFlat && canScaleIn) {
        if(longSignal && openBuys > 0 && OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "Scale Long", MagicNumber, 0, clrGreen) > 0) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && openSells > 0 && OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "Scale Short", MagicNumber, 0, clrRed) > 0) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
}

// Volume/Bar checking helper to prevent mid-bar noise
bool IsNewBar() {
    static datetime lastBar = 0;
    datetime currentBar = iTime(NULL, 0, 0);
    if(lastBar != currentBar) { lastBar = currentBar; return true; }
    return false;
}

// UI functions remain identical to the previous Pro version.
void CreateBlotter() {
    ObjectCreate(0, "DeskBlotter_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XSIZE, 180);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YSIZE, 110);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BGCOLOR, clrBlack);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BORDER_TYPE, BORDER_FLAT);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_COLOR, clrDimGray);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BACK, true);

    string labels[5] = {"Header", "Ideas", "Clicks", "PnL", "Status"};
    for(int i=0; i<5; i++) {
        string name = "DeskBlotter_" + labels[i];
        ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 20);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 90 - (i * 20));
        ObjectSetString(0, name, OBJPROP_FONT, "Trebuchet MS");
        ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
    }
}

void UpdateBlotter(bool isHardStop, bool isSoftWarning, bool lossLimitHit) {
    color bg = NewsOverride ? clrMidnightBlue : (isHardStop ? clrMaroon : (isSoftWarning ? clrSaddleBrown : originalBG));
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, bg);

    string status = NewsOverride ? "NEWS EXEMPTION" : (lossLimitHit ? "MAX LOSS HIT" : (isHardStop ? "LOCKED (Walk Away)" : (isSoftWarning ? "A+ SETUPS ONLY" : "ACTIVE")));
    color statClr = NewsOverride ? clrDeepSkyBlue : (isHardStop ? clrRed : (isSoftWarning ? clrOrange : clrLimeGreen));
    
    ObjectSetString(0, "DeskBlotter_Header", OBJPROP_TEXT, "--- RISK DESK ---");
    ObjectSetInteger(0, "DeskBlotter_Header", OBJPROP_COLOR, clrSilver);
    ObjectSetString(0, "DeskBlotter_Ideas", OBJPROP_TEXT, "Ideas: " + IntegerToString(dailyIdeas) + " / " + IntegerToString(MaxIdeas));
    ObjectSetInteger(0, "DeskBlotter_Ideas", OBJPROP_COLOR, (dailyIdeas >= MaxIdeas) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_Clicks", OBJPROP_TEXT, "Clicks: " + IntegerToString(dailyClicks) + " / " + IntegerToString(MaxClicks));
    ObjectSetInteger(0, "DeskBlotter_Clicks", OBJPROP_COLOR, (dailyClicks >= MaxClicks) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_PnL", OBJPROP_TEXT, "Daily PnL: $" + DoubleToString(dailyPnL, 2));
    ObjectSetInteger(0, "DeskBlotter_PnL", OBJPROP_COLOR, (dailyPnL < 0) ? clrRed : clrLimeGreen);
    ObjectSetString(0, "DeskBlotter_Status", OBJPROP_TEXT, status);
    ObjectSetInteger(0, "DeskBlotter_Status", OBJPROP_COLOR, statClr);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

2. MetaTrader 5 (MQL5) - Triple-Sync Pro

Save as RiskDesk_TripleSync.mq5.

Code: Select all

//+------------------------------------------------------------------+
//|                                        RiskDesk_TripleSync.mq5   |
//|                        Institutional Risk Desk with Persistence  |
//+------------------------------------------------------------------+
#property copyright "Risk Desk Professional"
#include <Trade\Trade.mqh>
CTrade trade;

// --- Risk Desk Inputs ---
input group "--- Risk Enforcement ---"
input int      MaxIdeas         = 3;       
input int      MaxClicks        = 10;      
input double   DailyMaxLossUSD  = 500.0;   
input bool     NewsOverride     = false;   
input ulong    MagicNumber      = 77777;

// --- Strategy Inputs ---
input group "--- Strategy Parameters ---"
input double   LotSize          = 0.1;
input int      FastEma          = 20;
input int      MedEma           = 50;
input int      SlowEma          = 200;

// --- State Variables ---
color originalBG;
string gvIdeas, gvClicks, gvDay;
int dailyIdeas = 0; int dailyClicks = 0; double dailyPnL = 0;

// Indicator Handles
int hEma20, hEma50, hEma200, hMacd;
double bufEma20[], bufEma50[], bufEma200[], bufMacdMain[], bufMacdSig[];

int OnInit() {
    trade.SetExpertMagicNumber(MagicNumber);
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    
    string prefix = "RiskDesk_" + _Symbol + "_" + IntegerToString(MagicNumber) + "_";
    gvIdeas = prefix + "Ideas"; gvClicks = prefix + "Clicks"; gvDay = prefix + "Day";

    MqlDateTime dt; TimeCurrent(dt);
    if(GlobalVariableCheck(gvDay) && GlobalVariableGet(gvDay) == dt.day) {
        if(GlobalVariableCheck(gvIdeas)) dailyIdeas = (int)GlobalVariableGet(gvIdeas);
        if(GlobalVariableCheck(gvClicks)) dailyClicks = (int)GlobalVariableGet(gvClicks);
    } else { ResetDailyState(dt.day); }

    // Init Indicators
    hEma20  = iMA(_Symbol, _Period, FastEma, 0, MODE_EMA, PRICE_CLOSE);
    hEma50  = iMA(_Symbol, _Period, MedEma, 0, MODE_EMA, PRICE_CLOSE);
    hEma200 = iMA(_Symbol, _Period, SlowEma, 0, MODE_EMA, PRICE_CLOSE);
    hMacd   = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE);
    
    ArraySetAsSeries(bufEma20, true); ArraySetAsSeries(bufEma50, true); ArraySetAsSeries(bufEma200, true);
    ArraySetAsSeries(bufMacdMain, true); ArraySetAsSeries(bufMacdSig, true);

    CreateBlotter();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBG);
    ObjectsDeleteAll(0, "DeskBlotter_");
}

void ResetDailyState(int currentDay) {
    dailyIdeas = 0; dailyClicks = 0;
    GlobalVariableSet(gvDay, currentDay);
    GlobalVariableSet(gvIdeas, 0); GlobalVariableSet(gvClicks, 0);
}

void CalculateDailyPnL() {
    dailyPnL = 0;
    datetime startOfDay = iTime(_Symbol, PERIOD_D1, 0);
    datetime endOfDay = TimeCurrent() + 86400;
    
    if(HistorySelect(startOfDay, endOfDay)) {
        for(int i=0; i<HistoryDealsTotal(); i++) {
            ulong deal = HistoryDealGetTicket(i);
            if(HistoryDealGetInteger(deal, DEAL_MAGIC) == MagicNumber && HistoryDealGetString(deal, DEAL_SYMBOL) == _Symbol) {
                dailyPnL += HistoryDealGetDouble(deal, DEAL_PROFIT) + HistoryDealGetDouble(deal, DEAL_COMMISSION) + HistoryDealGetDouble(deal, DEAL_SWAP);
            }
        }
    }
    for(int i = PositionsTotal()-1; i >= 0; i--) {
        ulong pos = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
            dailyPnL += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
        }
    }
}

void OnTick() {
    if(!IsNewBar()) return; // Wait for bar close
    
    MqlDateTime dt; TimeCurrent(dt);
    if(GlobalVariableGet(gvDay) != dt.day) ResetDailyState(dt.day);
    CalculateDailyPnL();

    int openBuys = 0, openSells = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) openBuys++;
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) openSells++;
        }
    }
    bool isFlat = (openBuys == 0 && openSells == 0);

    bool lossLimitHit = (dailyPnL <= -MathAbs(DailyMaxLossUSD));
    bool capReached = (dailyIdeas >= MaxIdeas) || (dailyClicks >= MaxClicks) || lossLimitHit;
    bool warningLevel = (dailyIdeas == MaxIdeas - 1) || (dailyPnL <= -(MathAbs(DailyMaxLossUSD) * 0.8));
    
    bool isHardStop = capReached && !NewsOverride;
    bool isSoftWarning = warningLevel && !capReached;
    bool canTakeNewIdea = !isHardStop;
    bool canScaleIn = (dailyClicks < MaxClicks && !lossLimitHit) || NewsOverride;

    UpdateBlotter(isHardStop, isSoftWarning, lossLimitHit);

    // Get Indicator Data
    if(CopyBuffer(hEma20, 0, 1, 1, bufEma20) <= 0 || CopyBuffer(hEma50, 0, 1, 1, bufEma50) <= 0 || CopyBuffer(hEma200, 0, 1, 1, bufEma200) <= 0) return;
    if(CopyBuffer(hMacd, 0, 1, 2, bufMacdMain) <= 0 || CopyBuffer(hMacd, 1, 1, 2, bufMacdSig) <= 0) return;
    
    double close1 = iClose(_Symbol, _Period, 1);
    double low1 = iLow(_Symbol, _Period, 1);
    double high1 = iHigh(_Symbol, _Period, 1);

    // --- TRIPLE-SYNC LOGIC ---
    bool bullTrend = (bufEma50[0] > bufEma200[0]);
    bool bullPullback = (low1 <= bufEma20[0]);
    bool bullTrigger = (bufMacdMain[0] > bufMacdSig[0] && bufMacdMain[1] <= bufMacdSig[1]);
    bool longSignal = bullTrend && bullPullback && bullTrigger && (close1 > bufEma50[0]);

    bool bearTrend = (bufEma50[0] < bufEma200[0]);
    bool bearPullback = (high1 >= bufEma20[0]);
    bool bearTrigger = (bufMacdMain[0] < bufMacdSig[0] && bufMacdMain[1] >= bufMacdSig[1]);
    bool shortSignal = bearTrend && bearPullback && bearTrigger && (close1 < bufEma50[0]);

    bool exitLong = (close1 < bufEma50[0]);
    bool exitShort = (close1 > bufEma50[0]);

    // Executions
    if((openBuys > 0 && exitLong) || (openSells > 0 && exitShort) || (lossLimitHit && !isFlat)) {
        for(int i = PositionsTotal() - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) trade.PositionClose(ticket);
        }
    }

    if(isFlat && canTakeNewIdea) {
        if(longSignal && trade.Buy(LotSize, _Symbol, 0, 0, 0, "A+ Long")) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && trade.Sell(LotSize, _Symbol, 0, 0, 0, "A+ Short")) {
            dailyIdeas++; dailyClicks++;
            GlobalVariableSet(gvIdeas, dailyIdeas); GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
    else if(!isFlat && canScaleIn) {
        if(longSignal && openBuys > 0 && trade.Buy(LotSize, _Symbol, 0, 0, 0, "Scale Long")) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && openSells > 0 && trade.Sell(LotSize, _Symbol, 0, 0, 0, "Scale Short")) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
    }
}

bool IsNewBar() {
    static datetime lastBar = 0;
    datetime currentBar = iTime(_Symbol, _Period, 0);
    if(lastBar != currentBar) { lastBar = currentBar; return true; }
    return false;
}

// UI functions identical to MQL4 block
void CreateBlotter() {
    ObjectCreate(0, "DeskBlotter_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YDISTANCE, 10);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_XSIZE, 180);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_YSIZE, 110);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BGCOLOR, clrBlack);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BORDER_TYPE, BORDER_FLAT);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_COLOR, clrDimGray);
    ObjectSetInteger(0, "DeskBlotter_BG", OBJPROP_BACK, true);

    string labels[5] = {"Header", "Ideas", "Clicks", "PnL", "Status"};
    for(int i=0; i<5; i++) {
        string name = "DeskBlotter_" + labels[i];
        ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 20);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 90 - (i * 20));
        ObjectSetString(0, name, OBJPROP_FONT, "Trebuchet MS");
        ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
    }
}

void UpdateBlotter(bool isHardStop, bool isSoftWarning, bool lossLimitHit) {
    color bg = NewsOverride ? clrMidnightBlue : (isHardStop ? clrMaroon : (isSoftWarning ? clrSaddleBrown : originalBG));
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, bg);

    string status = NewsOverride ? "NEWS EXEMPTION" : (lossLimitHit ? "MAX LOSS HIT" : (isHardStop ? "LOCKED (Walk Away)" : (isSoftWarning ? "A+ SETUPS ONLY" : "ACTIVE")));
    color statClr = NewsOverride ? clrDeepSkyBlue : (isHardStop ? clrRed : (isSoftWarning ? clrOrange : clrLimeGreen));
    
    ObjectSetString(0, "DeskBlotter_Header", OBJPROP_TEXT, "--- RISK DESK ---");
    ObjectSetInteger(0, "DeskBlotter_Header", OBJPROP_COLOR, clrSilver);
    ObjectSetString(0, "DeskBlotter_Ideas", OBJPROP_TEXT, "Ideas: " + IntegerToString(dailyIdeas) + " / " + IntegerToString(MaxIdeas));
    ObjectSetInteger(0, "DeskBlotter_Ideas", OBJPROP_COLOR, (dailyIdeas >= MaxIdeas) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_Clicks", OBJPROP_TEXT, "Clicks: " + IntegerToString(dailyClicks) + " / " + IntegerToString(MaxClicks));
    ObjectSetInteger(0, "DeskBlotter_Clicks", OBJPROP_COLOR, (dailyClicks >= MaxClicks) ? clrRed : clrWhite);
    ObjectSetString(0, "DeskBlotter_PnL", OBJPROP_TEXT, "Daily PnL: $" + DoubleToString(dailyPnL, 2));
    ObjectSetInteger(0, "DeskBlotter_PnL", OBJPROP_COLOR, (dailyPnL < 0) ? clrRed : clrLimeGreen);
    ObjectSetString(0, "DeskBlotter_Status", OBJPROP_TEXT, status);
    ObjectSetInteger(0, "DeskBlotter_Status", OBJPROP_COLOR, statClr);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Key Mechanical Changes for "Pro" Quality:

IsNewBar() Implementation: Retail EAs calculate on every single tick. This causes them to trigger false entries when an indicator briefly flashes mid-bar. A professional strategy waits for the candle to close to confirm the setup. I added the IsNewBar() boolean; the strategy only evaluates its logic at the exact opening of a new candle.

Structural Integrity Check: Even if the MACD crosses and price touches the 20 EMA, the EA checks (close1 > ema50) for longs. This prevents buying into a pullback that was so violent it broke the medium-term structure.

Thesis-Based Exits: There are no arbitrary stop-loss or take-profit pip counts. If you buy a pullback above the 50 EMA, and the price subsequently closes below the 50 EMA, the trend thesis is objectively wrong. The EA immediately flattens the position, takes the loss, and preserves your remaining idea cap.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Moving this architecture to cTrader (cAlgo) is a massive upgrade in code quality. Because cTrader uses modern C# (.NET) and features a WPF-style UI framework, we can build a significantly cleaner, faster, and more robust Risk Desk.

Instead of floating text objects (MT4) or hacky rectangle drawings, we can construct a true docked institutional dashboard that dynamically colors itself, calculates PnL using LINQ, and uses cTrader's native LocalStorage to survive platform crashes.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Here is the complete C# cBot for the Risk Desk: Triple-Sync Pro.

cTrader cBot (C#)
Open cTrader Automate.

Create a New cBot and name it RiskDesk_TripleSync.

Paste the following code, replacing everything.

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class RiskDeskTripleSync : Robot
    {
        // ==============================================================================
        // 1. RISK DESK INPUTS
        // ==============================================================================
        [Parameter("Daily Idea Cap", Group = "Risk Enforcement", DefaultValue = 3)]
        public int MaxIdeas { get; set; }

        [Parameter("Daily Execution Cap", Group = "Risk Enforcement", DefaultValue = 10)]
        public int MaxClicks { get; set; }

        [Parameter("Daily Max Loss (USD)", Group = "Risk Enforcement", DefaultValue = 500.0)]
        public double DailyMaxLossUSD { get; set; }

        [Parameter("Pre-Planned News Exemption", Group = "Risk Enforcement", DefaultValue = false)]
        public bool NewsOverride { get; set; }

        // ==============================================================================
        // 2. STRATEGY INPUTS
        // ==============================================================================
        [Parameter("Lot Size", Group = "Strategy", DefaultValue = 0.1)]
        public double LotSize { get; set; }

        [Parameter("Fast EMA", Group = "Strategy", DefaultValue = 20)]
        public int FastEma { get; set; }

        [Parameter("Med EMA", Group = "Strategy", DefaultValue = 50)]
        public int MedEma { get; set; }

        [Parameter("Slow EMA", Group = "Strategy", DefaultValue = 200)]
        public int SlowEma { get; set; }

        // --- State Management & Indicators ---
        private ExponentialMovingAverage _ema20, _ema50, _ema200;
        private MacdCrossOver _macd;
        
        private int _dailyIdeas = 0;
        private int _dailyClicks = 0;
        private double _dailyPnL = 0;
        private string _botLabel = "RiskDesk";
        
        private string _gvDate, _gvIdeas, _gvClicks;
        private Color _originalBgColor;

        // --- UI Elements ---
        private Border _blotterBorder;
        private TextBlock _tbIdeas, _tbClicks, _tbPnL, _tbStatus;

        // ==============================================================================
        // INITIALIZATION
        // ==============================================================================
        protected override void OnStart()
        {
            _originalBgColor = Chart.ColorSettings.BackgroundColor;
            
            // Persistence Keys (Unique per symbol and timeframe)
            string prefix = $"RiskDesk_{SymbolName}_{TimeFrame}_";
            _gvDate = prefix + "Date";
            _gvIdeas = prefix + "Ideas";
            _gvClicks = prefix + "Clicks";

            LoadState();

            // Initialize Indicators
            _ema20 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastEma);
            _ema50 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, MedEma);
            _ema200 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowEma);
            _macd = Indicators.MacdCrossOver(Bars.ClosePrices, 26, 12, 9); // Long, Short, Signal

            BuildInstitutionalUI();
        }

        protected override void OnStop()
        {
            Chart.ColorSettings.BackgroundColor = _originalBgColor;
            Chart.RemoveControl(_blotterBorder);
        }

        // ==============================================================================
        // STATE PERSISTENCE
        // ==============================================================================
        private void LoadState()
        {
            string storedDate = LocalStorage.GetString(_gvDate);
            string today = Server.Time.Date.ToString("yyyyMMdd");

            if (storedDate == today)
            {
                _dailyIdeas = int.Parse(LocalStorage.GetString(_gvIdeas) ?? "0");
                _dailyClicks = int.Parse(LocalStorage.GetString(_gvClicks) ?? "0");
            }
            else
            {
                ResetDailyState(today);
            }
        }

        private void ResetDailyState(string todayStr)
        {
            _dailyIdeas = 0;
            _dailyClicks = 0;
            LocalStorage.SetString(_gvDate, todayStr);
            SaveState();
        }

        private void SaveState()
        {
            LocalStorage.SetString(_gvIdeas, _dailyIdeas.ToString());
            LocalStorage.SetString(_gvClicks, _dailyClicks.ToString());
        }

        // ==============================================================================
        // TICK LOGIC (Risk Management & UI)
        // ==============================================================================
        protected override void OnTick()
        {
            // 1. Day Roll-over Check
            if (LocalStorage.GetString(_gvDate) != Server.Time.Date.ToString("yyyyMMdd"))
            {
                ResetDailyState(Server.Time.Date.ToString("yyyyMMdd"));
            }

            // 2. Real-time Daily PnL Calculation (LINQ is vastly superior to MT4 loops)
            double realizedPnL = History.Where(h => h.EntryTime.Date == Server.Time.Date && h.Label == _botLabel && h.SymbolName == SymbolName).Sum(h => h.NetProfit);
            double floatingPnL = Positions.FindAll(_botLabel, SymbolName).Sum(p => p.NetProfit);
            _dailyPnL = realizedPnL + floatingPnL;

            // 3. Desk Enforcement Rules
            bool lossLimitHit = _dailyPnL <= -Math.Abs(DailyMaxLossUSD);
            bool capReached = (_dailyIdeas >= MaxIdeas) || (_dailyClicks >= MaxClicks) || lossLimitHit;
            bool warningLevel = (_dailyIdeas == MaxIdeas - 1) || (_dailyPnL <= -(Math.Abs(DailyMaxLossUSD) * 0.8)); // 80% loss warning
            
            bool isHardStop = capReached && !NewsOverride;
            bool isSoftWarning = warningLevel && !capReached;

            // 4. Force Liquidate on Max Loss
            if (lossLimitHit && Positions.FindAll(_botLabel, SymbolName).Length > 0)
            {
                foreach (var pos in Positions.FindAll(_botLabel, SymbolName))
                {
                    ClosePosition(pos);
                }
            }

            UpdateUI(isHardStop, isSoftWarning, lossLimitHit);
        }

        // ==============================================================================
        // BAR LOGIC (Strategy Execution)
        // ==============================================================================
        protected override void OnBar()
        {
            var openPositions = Positions.FindAll(_botLabel, SymbolName);
            bool isFlat = openPositions.Length == 0;
            bool openBuys = openPositions.Count(p => p.TradeType == TradeType.Buy) > 0;
            bool openSells = openPositions.Count(p => p.TradeType == TradeType.Sell) > 0;

            bool lossLimitHit = _dailyPnL <= -Math.Abs(DailyMaxLossUSD);
            bool isHardStop = ((_dailyIdeas >= MaxIdeas) || (_dailyClicks >= MaxClicks) || lossLimitHit) && !NewsOverride;
            
            bool canTakeNewIdea = !isHardStop;
            bool canScaleIn = (_dailyClicks < MaxClicks && !lossLimitHit) || NewsOverride;

            // Indicator Values (1 bar ago, properly locked in)
            double ema20 = _ema20.Result.Last(1);
            double ema50 = _ema50.Result.Last(1);
            double ema200 = _ema200.Result.Last(1);

            double macdMain1 = _macd.MACD.Last(1);
            double macdSig1 = _macd.Signal.Last(1);
            double macdMain2 = _macd.MACD.Last(2);
            double macdSig2 = _macd.Signal.Last(2);

            double close1 = Bars.ClosePrices.Last(1);
            double low1 = Bars.LowPrices.Last(1);
            double high1 = Bars.HighPrices.Last(1);

            // --- A+ Setups ---
            bool bullTrend = ema50 > ema200;
            bool bullPullback = low1 <= ema20;
            bool bullTrigger = macdMain1 > macdSig1 && macdMain2 <= macdSig2;
            bool longSignal = bullTrend && bullPullback && bullTrigger && (close1 > ema50);

            bool bearTrend = ema50 < ema200;
            bool bearPullback = high1 >= ema20;
            bool bearTrigger = macdMain1 < macdSig1 && macdMain2 >= macdSig2;
            bool shortSignal = bearTrend && bearPullback && bearTrigger && (close1 < ema50);

            // --- Exits ---
            bool exitLong = close1 < ema50;
            bool exitShort = close1 > ema50;

            if ((openBuys && exitLong) || (openSells && exitShort))
            {
                foreach (var pos in openPositions) ClosePosition(pos);
                isFlat = true;
            }

            double volume = Symbol.QuantityToVolumeInUnits(LotSize);

            // --- Entries ---
            if (isFlat && canTakeNewIdea)
            {
                if (longSignal) ExecuteTrade(TradeType.Buy, volume, true);
                else if (shortSignal) ExecuteTrade(TradeType.Sell, volume, true);
            }
            else if (!isFlat && canScaleIn)
            {
                if (longSignal && openBuys) ExecuteTrade(TradeType.Buy, volume, false);
                else if (shortSignal && openSells) ExecuteTrade(TradeType.Sell, volume, false);
            }
        }

        private void ExecuteTrade(TradeType type, double volume, bool isNewIdea)
        {
            var result = ExecuteMarketOrder(type, SymbolName, volume, _botLabel);
            if (result.IsSuccessful)
            {
                if (isNewIdea) _dailyIdeas++;
                _dailyClicks++;
                SaveState();
            }
        }

        // ==============================================================================
        // INSTITUTIONAL UI DASHBOARD (WPF/Canvas)
        // ==============================================================================
        private void BuildInstitutionalUI()
        {
            var stackPanel = new StackPanel { Orientation = Orientation.Vertical, Margin = 10 };

            var header = new TextBlock { Text = "--- RISK DESK ---", ForegroundColor = Color.Silver, FontWeight = FontWeight.Bold, Margin = new Thickness(0,0,0,5) };
            _tbIdeas = new TextBlock { Text = "Ideas: 0 / " + MaxIdeas, ForegroundColor = Color.White, Margin = new Thickness(0,2,0,2) };
            _tbClicks = new TextBlock { Text = "Clicks: 0 / " + MaxClicks, ForegroundColor = Color.White, Margin = new Thickness(0,2,0,2) };
            _tbPnL = new TextBlock { Text = "Daily PnL: $0.00", ForegroundColor = Color.LimeGreen, Margin = new Thickness(0,2,0,2) };
            _tbStatus = new TextBlock { Text = "STATUS: ACTIVE", ForegroundColor = Color.LimeGreen, FontWeight = FontWeight.Bold, Margin = new Thickness(0,5,0,0) };

            stackPanel.AddChild(header);
            stackPanel.AddChild(_tbIdeas);
            stackPanel.AddChild(_tbClicks);
            stackPanel.AddChild(_tbPnL);
            stackPanel.AddChild(_tbStatus);

            _blotterBorder = new Border
            {
                BackgroundColor = Color.FromHex("#EF1E1E24"), // Slightly transparent dark grey
                BorderColor = Color.FromHex("#555555"),
                BorderThickness = 1,
                CornerRadius = 3,
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                Margin = new Thickness(20),
                Child = stackPanel
            };

            Chart.AddControl(_blotterBorder);
        }

        private void UpdateUI(bool isHardStop, bool isSoftWarning, bool lossLimitHit)
        {
            // Chart Background Alerting
            Color targetBg = originalBgColor;
            if (NewsOverride) targetBg = Color.FromHex("#121b2b"); // Deep Blue
            else if (isHardStop) targetBg = Color.FromHex("#2b1010"); // Deep Red
            else if (isSoftWarning) targetBg = Color.FromHex("#2b1c0b"); // Deep Orange
            
            Chart.ColorSettings.BackgroundColor = targetBg;

            // Update Panel Text
            _tbIdeas.Text = $"Ideas: {_dailyIdeas} / {MaxIdeas}";
            _tbIdeas.ForegroundColor = (_dailyIdeas >= MaxIdeas) ? Color.Red : Color.White;

            _tbClicks.Text = $"Executions: {_dailyClicks} / {MaxClicks}";
            _tbClicks.ForegroundColor = (_dailyClicks >= MaxClicks) ? Color.Red : Color.White;

            _tbPnL.Text = $"Daily PnL: ${_dailyPnL:F2}";
            _tbPnL.ForegroundColor = (_dailyPnL < 0) ? Color.Tomato : Color.LimeGreen;

            // Status Logic
            if (NewsOverride) 
            { 
                _tbStatus.Text = "NEWS EXEMPTION"; _tbStatus.ForegroundColor = Color.DeepSkyBlue; 
            }
            else if (lossLimitHit) 
            { 
                _tbStatus.Text = "MAX LOSS HIT"; _tbStatus.ForegroundColor = Color.Red; 
            }
            else if (isHardStop) 
            { 
                _tbStatus.Text = "LOCKED (Walk Away)"; _tbStatus.ForegroundColor = Color.Red; 
            }
            else if (isSoftWarning) 
            { 
                _tbStatus.Text = "A+ SETUPS ONLY"; _tbStatus.ForegroundColor = Color.Orange; 
            }
            else 
            { 
                _tbStatus.Text = "ACTIVE"; _tbStatus.ForegroundColor = Color.LimeGreen; 
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Build In a Maximum Trades Per Day Rule

Post by PTScalper »

Why this architecture shines in cTrader:

LINQ PnL Scanning: Instead of messy MT4 for() loops iterating through order history indexes, cTrader lets us query the database directly in one line of C# (History.Where(...).Sum(...)). Realized and floating PnL tracking is flawless.

True State Storage: MT4/MT5 Global Variables are exposed to the user in the terminal and can be accidentally deleted by pressing F3. cTrader's LocalStorage writes silently to your machine's app data in the background. It survives terminal crashes and is completely hidden from the front-end user.

Volume Conversion Engine: Symbol.QuantityToVolumeInUnits(LotSize) handles the nightmare of asset-class differences automatically. 0.1 lots on EURUSD behaves radically differently than 0.1 lots on US30. cTrader normalizes this via the API, preventing your scale-ins from blowing the account if you switch symbols.

WPF UI Engine: The dashboard isn't drawn on the chart with coordinates—it's a StackPanel inside a Border. If you resize your chart, zoom in, or scroll back in time, the dashboard stays locked cleanly in the bottom right corner, partially transparent so you can still read the grid beneath it.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
LondonScalper
Posts: 770
Joined: Sat Sep 05, 2026 7:54 am

Re: Build In a Maximum Trades Per Day Rule

Post by LondonScalper »

PTScalper wrote:Your perspective captures one of the most mature and difficult-to-master truths in trading: discipline is arguably more vital during euphoria. You have also translated the desk-enforcement idea into a Pine Script counter with a soft warning at n−1 and a hard stop at n.
That is a useful implementation, PT. The important detail is fixing what counts as a ticket before the session starts. For me, one planned thesis with documented scale-ins is not five fresh ideas; a reversal from long to short is a new decision and should count. Otherwise the code can make activity look more disciplined than the actual risk.

Two checks are worth adding. First, make the reset follow the intended trading session and exchange timezone, rather than assuming a calendar day. Second, the pre-planned news exception in my note is not represented in the hard stop. I would make any override explicit and logged, never a quiet bypass. Forward-test the counter against actual order timestamps and the blotter, especially around reversals and partial fills.

The code should remove discretion after the cap, not give discretion a smarter-looking excuse to click.
Post Reply