Page 2 of 4

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:35 pm
by PTScalper
How this Code Enforces Your Thesis

The Blotter (Table): A small table stays anchored to the bottom right of your screen. It acts as the "counter on the blotter," keeping your current daily ticket count objectively visible in real-time.

The Soft Warning ($n-1$): When your counter reaches one trade away from the cap, the script changes the chart background to a faint orange. This is the visual trigger for your rule: Only A+ setups may compete for the last slot.

The Hard Stop ($n$): Once the cap is hit, the can_trade boolean turns false. The background goes red, and the strategy.entry() function is strictly blocked from taking any more signals until the next day prints on the chart.

Honesty in Measurement: The script increments daily_tickets based on distinct entry signals triggered, honoring the difference between a deliberate "ticket" and scattered noise.

How do you currently differentiate between a "micro-slice" scale-in and a completely new ticket when tracking your daily cap in real-time?

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:40 pm
by PTScalper
To elevate this from a basic script to a professional-grade trading tool, we need to address the nuances you mentioned—specifically, distinguishing between a new thesis and a micro-slice scale-in, and handling the "pre-planned news" exception.

A professional desk doesn't just count clicks; it categorizes them to prevent you from lying to yourself about what you are measuring. If your cap is 3, taking three scale-in entries on a single Euro short shouldn't freeze your platform. But taking three entirely different setups should.

Here is the "Pro" version. It introduces Object-Oriented State Management using Pine Script's type system, handles Pyramiding (micro-slices), includes a News Exemption toggle, and features an institutional-grade UI blotter.

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:40 pm
by PTScalper
Pine Script v5: Institutional Risk Desk Blotter

Code: Select all

//@version=5
strategy("Risk Desk: The Quality Filter (Pro)", overlay=true, pyramiding=5, initial_capital=100000, default_qty_type=strategy.percent_of_equity, default_qty_value=5, calc_on_every_tick=true)

// ==============================================================================
// 1. RISK DESK INPUTS
// ==============================================================================
grp_risk = "Desk Enforcement Parameters"
max_ideas     = input.int(3, title="Daily Idea Cap (The Teeth)", minval=1, group=grp_risk, tooltip="Max number of UNIQUE trade setups allowed per day.")
max_clicks    = input.int(10, title="Daily Execution Cap (Micro-slices)", minval=1, group=grp_risk, tooltip="Max total clicks to prevent hyperactive scale-ins.")
news_override = input.bool(false, title="Pre-Planned News Exemption", group=grp_risk, tooltip="Enable this temporarily if a macro event dictates overriding the cap.")

grp_strat = "Strategy Logic (Placeholder)"
rsi_len = input.int(14, "RSI Length", group=grp_strat)

// ==============================================================================
// 2. STATE MANAGEMENT (Custom Types)
// ==============================================================================
// Using a structured type keeps the state clean and professional
type DeskBlotter
    int  ideas_taken
    int  total_clicks
    bool is_soft_warning
    bool is_hard_stop

var DeskBlotter desk = DeskBlotter.new(0, 0, false, false)

// Reset the blotter at the start of a new trading session/day
if ta.change(time("D"))
    desk.ideas_taken := 0
    desk.total_clicks := 0
    desk.is_soft_warning := false
    desk.is_hard_stop := false

// ==============================================================================
// 3. MARKET CONTEXT & ENFORCEMENT LOGIC
// ==============================================================================
// Are we currently in a trade?
is_flat = strategy.position_size == 0
is_long = strategy.position_size > 0
is_short = strategy.position_size < 0

// Evaluate limits
cap_reached = (desk.ideas_taken >= max_ideas) or (desk.total_clicks >= max_clicks)
warning_level = (desk.ideas_taken == max_ideas - 1)

// Update state
desk.is_soft_warning := warning_level and not cap_reached
desk.is_hard_stop    := cap_reached and not news_override

// Permission to execute
can_take_new_idea = not desk.is_hard_stop
can_scale_in      = (desk.total_clicks < max_clicks) and not news_override or news_override

// ==============================================================================
// 4. STRATEGY EXECUTION (Thesis vs. Micro-Slices)
// ==============================================================================
// Placeholder logic: RSI extremes
rsi = ta.rsi(close, rsi_len)
long_signal = ta.crossover(rsi, 30)
short_signal = ta.crossunder(rsi, 70)

// A. New Thesis (Costs 1 Idea, 1 Click)
if long_signal and is_flat and can_take_new_idea
    strategy.entry("Long Idea", strategy.long)
    desk.ideas_taken += 1
    desk.total_clicks += 1

if short_signal and is_flat and can_take_new_idea
    strategy.entry("Short Idea", strategy.short)
    desk.ideas_taken += 1
    desk.total_clicks += 1

// B. Micro-Slices / Scaling In (Costs 0 Ideas, 1 Click)
// Simulating a scale-in if the signal fires again while already in position
if long_signal and is_long and can_scale_in
    strategy.entry("Scale Long", strategy.long)
    desk.total_clicks += 1

if short_signal and is_short and can_scale_in
    strategy.entry("Scale Short", strategy.short)
    desk.total_clicks += 1

// Standard Exits (RSI Mean Reversion)
if ta.crossover(rsi, 50) and is_long
    strategy.close_all(comment="Flat (TP Long)")
if ta.crossunder(rsi, 50) and is_short
    strategy.close_all(comment="Flat (TP Short)")

// ==============================================================================
// 5. INSTITUTIONAL UI & VISUALS
// ==============================================================================
// Colors
color clr_bg_normal  = color.new(#1e1e24, 100)
color clr_bg_warn    = color.new(#ff9800, 92) // Soft orange for n-1
color clr_bg_stop    = color.new(#f44336, 90) // Red for Hard Stop
color clr_bg_news    = color.new(#2196f3, 92) // Blue for News Exemption

color current_bg = news_override ? clr_bg_news : desk.is_hard_stop ? clr_bg_stop : desk.is_soft_warning ? clr_bg_warn : clr_bg_normal
bgcolor(current_bg, title="Desk Enforcement Background")

// UI Blotter Generation
var table blotter = table.new(position.bottom_right, 2, 4, border_width=2, border_color=#333333, frame_color=#1e1e24, frame_width=2)

if barstate.islast
    // Header
    table.cell(blotter, 0, 0, "RISK DESK BLOTTER", text_color=color.white, bgcolor=#1e1e24, text_size=size.small, text_halign=text.align_left)
    table.cell(blotter, 1, 0, news_override ? "NEWS MODE" : desk.is_hard_stop ? "LOCKED" : "ACTIVE", text_color=color.white, bgcolor=news_override ? #2196f3 : desk.is_hard_stop ? #f44336 : #4caf50, text_size=size.small)
    
    // Idea Cap (The Thesis)
    table.cell(blotter, 0, 1, "New Ideas (Cap: " + str.tostring(max_ideas) + ")", text_color=color.gray, bgcolor=#2b2b36, text_size=size.small, text_halign=text.align_left)
    table.cell(blotter, 1, 1, str.tostring(desk.ideas_taken), text_color=desk.is_soft_warning ? #ff9800 : desk.is_hard_stop ? #f44336 : color.white, bgcolor=#2b2b36, text_size=size.small)

    // Execution Cap (Micro-slices)
    table.cell(blotter, 0, 2, "Total Executions", text_color=color.gray, bgcolor=#2b2b36, text_size=size.small, text_halign=text.align_left)
    table.cell(blotter, 1, 2, str.tostring(desk.total_clicks) + " / " + str.tostring(max_clicks), text_color=color.white, bgcolor=#2b2b36, text_size=size.small)

    // Current State Guidance
    string guidance_text = news_override ? "Cap overridden for pre-planned event." : desk.is_hard_stop ? "Max capacity reached. Walk away." : desk.is_soft_warning ? "Only A+ setups permitted." : "Normal execution rules apply."
    table.cell(blotter, 0, 3, guidance_text, text_color=color.white, bgcolor=#1e1e24, text_size=size.small, text_halign=text.align_left)
    table.merge_cells(blotter, 0, 3, 1, 3)

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:41 pm
by PTScalper
What makes this "Pro"

Thesis vs. Execution Separation:

Notice the pyramiding=5 parameter and the distinct logic blocks for A (New Thesis) and B (Micro-Slices). If you enter a Long and then scale into it three more times, it burns 4 executions but only 1 idea ticket. This honors your philosophy of scaling into a single thesis without triggering the hard stop prematurely, while still capping hyperactive over-trading (total clicks).

The News Override (news_override):

You mentioned "unless a pre-planned news reaction remains". There is now a boolean toggle in the script settings. If CPI or NFP is dropping and you planned to trade it, you check the box. The background turns blue (signaling a conscious state change), the hard stop is lifted, and the UI blotter updates to "NEWS MODE".

State Management via type:

Using type DeskBlotter prevents variable spaghetti. It packages your ideas_taken, total_clicks, and enforcement states into a single clean object. This is how software engineers structure code to prevent logic errors on complex systems.

Institutional UI:

The table is built using a dark-mode palette (#1e1e24, #2b2b36) rather than garish base colors. It provides a distinct dashboard that feeds you the exact psychological cue you need: "Only A+ setups permitted" or "Max capacity reached. Walk away."

To implement this, load it onto a chart and try ticking the "Pre-Planned News Exemption" in the strategy settings. You'll see exactly how the script's strict rigidity yields to your deliberate, pre-planned flexibility.

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:43 pm
by PTScalper
Moving this architecture from TradingView (Pine Script) to MetaTrader (MQL4 and MQL5) requires a structural shift. Pine Script handles charting and execution in one native environment. In MetaTrader, to execute trades automatically and manage state, this must be coded as an Expert Advisor (EA) rather than a simple indicator.

Below are the complete, professional-grade EAs for both MT4 and MT5. They feature the daily state resets, the separation of "Ideas" vs. "Micro-slices" (based on current open positions), the News Override, and an on-chart UI blotter using text labels.

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:43 pm
by PTScalper
1. MetaTrader 4 (MQL4) - Risk Desk EA

Save this file as RiskDesk_Cap.mq4 in your MQL4/Experts folder.

Code: Select all

//+------------------------------------------------------------------+
//|                                                RiskDesk_Cap.mq4  |
//|                                      Desk Enforcement Architecture |
//+------------------------------------------------------------------+
#property copyright "Risk Desk Professional"
#property link      ""
#property version   "1.00"
#property strict

// --- Risk Desk Inputs ---
input string   Grp1 = "--- Desk Enforcement ---";
input int      MaxIdeas      = 3;      // Daily Idea Cap (The Teeth)
input int      MaxClicks     = 10;     // Daily Execution Cap (Micro-slices)
input bool     NewsOverride  = false;  // Pre-Planned News Exemption
input int      MagicNumber   = 77777;

// --- Strategy Inputs ---
input string   Grp2 = "--- Strategy (Placeholder) ---";
input double   LotSize       = 0.1;
input int      RsiPeriod     = 14;

// --- State Variables ---
int dailyIdeas = 0;
int dailyClicks = 0;
int currentDay = -1;
color originalBG;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    // Save the user's original background color to restore later
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    currentDay = Day();
    CreateBlotter();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBG);
    ObjectsDeleteAll(0, "DeskBlotter_");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    // 1. Check for New Trading Day to Reset Counters
    if(Day() != currentDay) {
        dailyIdeas = 0;
        dailyClicks = 0;
        currentDay = Day();
    }

    // 2. Assess Current Positions (Thesis vs Micro-slice context)
    int openBuys = 0;
    int openSells = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
                if(OrderType() == OP_BUY) openBuys++;
                if(OrderType() == OP_SELL) openSells++;
            }
        }
    }
    bool isFlat = (openBuys == 0 && openSells == 0);

    // 3. Desk Enforcement Logic
    bool capReached = (dailyIdeas >= MaxIdeas) || (dailyClicks >= MaxClicks);
    bool warningLevel = (dailyIdeas == MaxIdeas - 1);
    
    bool isHardStop = capReached && !NewsOverride;
    bool isSoftWarning = warningLevel && !capReached;
    
    bool canTakeNewIdea = !isHardStop;
    bool canScaleIn = (dailyClicks < MaxClicks) || NewsOverride;

    // Update UI and Chart Background
    UpdateBlotter(isHardStop, isSoftWarning, isFlat);

    // 4. Strategy Logic (RSI Placeholder)
    double rsi0 = iRSI(NULL, 0, RsiPeriod, PRICE_CLOSE, 0);
    double rsi1 = iRSI(NULL, 0, RsiPeriod, PRICE_CLOSE, 1);
    
    bool longSignal = (rsi1 < 30 && rsi0 >= 30);
    bool shortSignal = (rsi1 > 70 && rsi0 <= 70);
    bool flatSignal = ( (openBuys > 0 && rsi0 >= 50) || (openSells > 0 && rsi0 <= 50) );

    // EXITS
    if(flatSignal) {
        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);
            }
        }
    }

    // ENTRIES (A: New Idea)
    if(isFlat && canTakeNewIdea) {
        if(longSignal) {
            if(OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "New Idea Long", MagicNumber, 0, clrGreen) > 0) {
                dailyIdeas++; dailyClicks++;
            }
        } else if(shortSignal) {
            if(OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "New Idea Short", MagicNumber, 0, clrRed) > 0) {
                dailyIdeas++; dailyClicks++;
            }
        }
    }
    // ENTRIES (B: Micro-slice / Scale-in)
    else if(!isFlat && canScaleIn) {
        if(longSignal && openBuys > 0) {
            if(OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "Scale Long", MagicNumber, 0, clrGreen) > 0) dailyClicks++;
        } else if(shortSignal && openSells > 0) {
            if(OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "Scale Short", MagicNumber, 0, clrRed) > 0) dailyClicks++;
        }
    }
}

//+------------------------------------------------------------------+
//| UI Blotter Functions                                             |
//+------------------------------------------------------------------+
void CreateBlotter() {
    string labels[4] = {"DeskBlotter_Header", "DeskBlotter_Ideas", "DeskBlotter_Clicks", "DeskBlotter_Status"};
    for(int i=0; i<4; i++) {
        ObjectCreate(0, labels[i], OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, labels[i], OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, labels[i], OBJPROP_XDISTANCE, 20);
        ObjectSetInteger(0, labels[i], OBJPROP_YDISTANCE, 20 + (i * 20));
        ObjectSetString(0, labels[i], OBJPROP_FONT, "Arial");
        ObjectSetInteger(0, labels[i], OBJPROP_FONTSIZE, 10);
    }
}

void UpdateBlotter(bool isHardStop, bool isSoftWarning, bool isFlat) {
    // 1. Chart Background Color
    color targetBG = originalBG;
    if(NewsOverride) targetBG = clrDarkBlue;
    else if(isHardStop) targetBG = clrMaroon;
    else if(isSoftWarning) targetBG = clrSaddleBrown;
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, targetBG);

    // 2. Update Text
    string status = NewsOverride ? "STATUS: NEWS OVERRIDE" : (isHardStop ? "STATUS: LOCKED (Walk Away)" : (isSoftWarning ? "STATUS: A+ SETUPS ONLY" : "STATUS: ACTIVE"));
    color statusClr = NewsOverride ? clrDeepSkyBlue : (isHardStop ? clrRed : (isSoftWarning ? clrOrange : clrLimeGreen));
    
    ObjectSetString(0, "DeskBlotter_Header", OBJPROP_TEXT, "--- RISK DESK BLOTTER ---");
    ObjectSetInteger(0, "DeskBlotter_Header", OBJPROP_COLOR, clrWhite);
    
    ObjectSetString(0, "DeskBlotter_Ideas", OBJPROP_TEXT, "Ideas Taken: " + IntegerToString(dailyIdeas) + " / " + IntegerToString(MaxIdeas));
    ObjectSetInteger(0, "DeskBlotter_Ideas", OBJPROP_COLOR, (dailyIdeas >= MaxIdeas) ? clrRed : clrWhite);

    ObjectSetString(0, "DeskBlotter_Clicks", OBJPROP_TEXT, "Executions: " + IntegerToString(dailyClicks) + " / " + IntegerToString(MaxClicks));
    ObjectSetInteger(0, "DeskBlotter_Clicks", OBJPROP_COLOR, (dailyClicks >= MaxClicks) ? clrRed : clrWhite);

    ObjectSetString(0, "DeskBlotter_Status", OBJPROP_TEXT, status);
    ObjectSetInteger(0, "DeskBlotter_Status", OBJPROP_COLOR, statusClr);
}

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:44 pm
by PTScalper
2. MetaTrader 5 (MQL5) - Risk Desk EA

Save this file as RiskDesk_Cap.mq5 in your MQL5/Experts folder. MQL5 uses object-oriented standard libraries (like CTrade), which makes execution cleaner.

Code: Select all

//+------------------------------------------------------------------+
//|                                                RiskDesk_Cap.mq5  |
//|                                      Desk Enforcement Architecture |
//+------------------------------------------------------------------+
#property copyright "Risk Desk Professional"
#property link      ""
#property version   "1.00"

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

// --- Risk Desk Inputs ---
input group "--- Desk Enforcement ---"
input int      MaxIdeas      = 3;      // Daily Idea Cap (The Teeth)
input int      MaxClicks     = 10;     // Daily Execution Cap (Micro-slices)
input bool     NewsOverride  = false;  // Pre-Planned News Exemption
input ulong    MagicNumber   = 77777;

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

// --- State Variables ---
int dailyIdeas = 0;
int dailyClicks = 0;
int currentDay = -1;
color originalBG;
int rsiHandle;
double rsiBuffer[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(MagicNumber);
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    
    MqlDateTime timeStruct;
    TimeCurrent(timeStruct);
    currentDay = timeStruct.day;
    
    rsiHandle = iRSI(_Symbol, _Period, RsiPeriod, PRICE_CLOSE);
    ArraySetAsSeries(rsiBuffer, true);
    
    CreateBlotter();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, originalBG);
    ObjectsDeleteAll(0, "DeskBlotter_");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    // 1. Check for New Trading Day
    MqlDateTime timeStruct;
    TimeCurrent(timeStruct);
    if(timeStruct.day != currentDay) {
        dailyIdeas = 0;
        dailyClicks = 0;
        currentDay = timeStruct.day;
    }

    // 2. Assess Current Positions (Thesis vs Micro-slice)
    int openBuys = 0;
    int 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);

    // 3. Desk Enforcement Logic
    bool capReached = (dailyIdeas >= MaxIdeas) || (dailyClicks >= MaxClicks);
    bool warningLevel = (dailyIdeas == MaxIdeas - 1);
    
    bool isHardStop = capReached && !NewsOverride;
    bool isSoftWarning = warningLevel && !capReached;
    
    bool canTakeNewIdea = !isHardStop;
    bool canScaleIn = (dailyClicks < MaxClicks) || NewsOverride;

    UpdateBlotter(isHardStop, isSoftWarning, isFlat);

    // 4. Strategy Logic (RSI)
    if(CopyBuffer(rsiHandle, 0, 0, 2, rsiBuffer) <= 0) return;
    double rsi0 = rsiBuffer[0];
    double rsi1 = rsiBuffer[1];
    
    bool longSignal = (rsi1 < 30 && rsi0 >= 30);
    bool shortSignal = (rsi1 > 70 && rsi0 <= 70);
    bool flatSignal = ( (openBuys > 0 && rsi0 >= 50) || (openSells > 0 && rsi0 <= 50) );

    // EXITS
    if(flatSignal) {
        for(int i = PositionsTotal() - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
                trade.PositionClose(ticket);
            }
        }
    }

    // ENTRIES (A: New Idea)
    if(isFlat && canTakeNewIdea) {
        if(longSignal) {
            if(trade.Buy(LotSize, _Symbol, 0, 0, 0, "New Idea Long")) { dailyIdeas++; dailyClicks++; }
        } else if(shortSignal) {
            if(trade.Sell(LotSize, _Symbol, 0, 0, 0, "New Idea Short")) { dailyIdeas++; dailyClicks++; }
        }
    }
    // ENTRIES (B: Micro-slice)
    else if(!isFlat && canScaleIn) {
        if(longSignal && openBuys > 0) {
            if(trade.Buy(LotSize, _Symbol, 0, 0, 0, "Scale Long")) dailyClicks++;
        } else if(shortSignal && openSells > 0) {
            if(trade.Sell(LotSize, _Symbol, 0, 0, 0, "Scale Short")) dailyClicks++;
        }
    }
}

//+------------------------------------------------------------------+
//| UI Blotter Functions                                             |
//+------------------------------------------------------------------+
void CreateBlotter() {
    string labels[4] = {"DeskBlotter_Header", "DeskBlotter_Ideas", "DeskBlotter_Clicks", "DeskBlotter_Status"};
    for(int i=0; i<4; i++) {
        ObjectCreate(0, labels[i], OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, labels[i], OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, labels[i], OBJPROP_XDISTANCE, 20);
        ObjectSetInteger(0, labels[i], OBJPROP_YDISTANCE, 20 + (i * 20));
        ObjectSetString(0, labels[i], OBJPROP_FONT, "Arial");
        ObjectSetInteger(0, labels[i], OBJPROP_FONTSIZE, 10);
    }
}

void UpdateBlotter(bool isHardStop, bool isSoftWarning, bool isFlat) {
    color targetBG = originalBG;
    if(NewsOverride) targetBG = clrMidnightBlue;
    else if(isHardStop) targetBG = clrMaroon;
    else if(isSoftWarning) targetBG = clrSaddleBrown;
    ChartSetInteger(0, CHART_COLOR_BACKGROUND, targetBG);

    string status = NewsOverride ? "STATUS: NEWS OVERRIDE" : (isHardStop ? "STATUS: LOCKED (Walk Away)" : (isSoftWarning ? "STATUS: A+ SETUPS ONLY" : "STATUS: ACTIVE"));
    color statusClr = NewsOverride ? clrDeepSkyBlue : (isHardStop ? clrRed : (isSoftWarning ? clrOrange : clrLimeGreen));
    
    ObjectSetString(0, "DeskBlotter_Header", OBJPROP_TEXT, "--- RISK DESK BLOTTER ---");
    ObjectSetInteger(0, "DeskBlotter_Header", OBJPROP_COLOR, clrWhite);
    
    ObjectSetString(0, "DeskBlotter_Ideas", OBJPROP_TEXT, "Ideas Taken: " + IntegerToString(dailyIdeas) + " / " + IntegerToString(MaxIdeas));
    ObjectSetInteger(0, "DeskBlotter_Ideas", OBJPROP_COLOR, (dailyIdeas >= MaxIdeas) ? clrRed : clrWhite);

    ObjectSetString(0, "DeskBlotter_Clicks", OBJPROP_TEXT, "Executions: " + IntegerToString(dailyClicks) + " / " + IntegerToString(MaxClicks));
    ObjectSetInteger(0, "DeskBlotter_Clicks", OBJPROP_COLOR, (dailyClicks >= MaxClicks) ? clrRed : clrWhite);

    ObjectSetString(0, "DeskBlotter_Status", OBJPROP_TEXT, status);
    ObjectSetInteger(0, "DeskBlotter_Status", OBJPROP_COLOR, statusClr);
}

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:44 pm
by PTScalper
Key MetaTrader Adaptations

State Persistence: In MT4/MT5, global variables reset if the EA is recompiled or the timeframe is changed. To counter this for daily resets, the script checks the server's current Day() (MT4) or timeStruct.day (MT5) on every tick. If it changes, your blotter wipes clean for the new session.

Position Counting (The True "Idea" Metric): To differentiate an Idea from a Micro-slice, the EA loops through current open positions matching its MagicNumber. If there are zero positions and a signal fires, it registers as a new Idea (Cost: 1 Idea, 1 Click). If a position already exists and the signal fires again, it registers as a scale-in (Cost: 0 Ideas, 1 Click).

Visual Backgrounds: MetaTrader does not support TradingView's transparent background colors (bgcolor(color.new(...))). To prevent blinding you with a solid bright red screen when locked out, I mapped the states to darker, muted institutional colors: clrMaroon for Hard Stop, clrSaddleBrown for Soft Warning, and clrMidnightBlue for News Mode. It saves your default chart color on initialization and restores it when the EA is removed.

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:46 pm
by PTScalper
To elevate these Expert Advisors to true institutional standards, we must solve the vulnerabilities that plague retail EAs.

A professional desk tool must survive terminal crashes without losing count, track financial risk alongside execution risk, and provide a clean, unreadable-by-candlesticks GUI panel rather than floating text.

Here are the Pro upgrades implemented in both MT4 and MT5:

Crash Resilience (State Persistence): Basic EAs reset their variables if you change the chart timeframe or if the terminal restarts. These versions use Terminal Global Variables to write your counts directly to the hard drive on every tick. If your PC crashes, the EA remembers exactly how many tickets you've burned today.

Daily Drawdown Enforcement: A desk cap isn't just about clicks; it's about capital. Added a Daily Max Loss ($) hard stop.

Institutional GUI Panel: Replaced floating text with a solid graphical HUD (Heads-Up Display) using OBJ_RECTANGLE_LABEL, ensuring price action doesn't obscure your blotter.

Robust History Tracking: Calculates true daily PnL by scanning the account's historical deals/orders for the current day.

Re: Build In a Maximum Trades Per Day Rule

Posted: Wed Sep 16, 2026 8:46 pm
by PTScalper
1. MetaTrader 4 (MQL4) - Risk Desk EA (Pro)

Code: Select all

//+------------------------------------------------------------------+
//|                                            RiskDesk_Cap_Pro.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 ---
input string   Grp2 = "--- Strategy ---";
input double   LotSize          = 0.1;
input int      RsiPeriod        = 14;

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    originalBG = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
    
    // Setup Global Variable Names (Unique to Symbol & Magic Number)
    string prefix = "RiskDesk_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_";
    gvIdeas = prefix + "Ideas";
    gvClicks = prefix + "Clicks";
    gvDay = prefix + "Day";

    // Load State from Hard Drive
    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_");
}

//+------------------------------------------------------------------+
//| Helper: Reset Daily State & Save                                 |
//+------------------------------------------------------------------+
void ResetDailyState() {
    dailyIdeas = 0;
    dailyClicks = 0;
    GlobalVariableSet(gvDay, Day());
    GlobalVariableSet(gvIdeas, 0);
    GlobalVariableSet(gvClicks, 0);
}

//+------------------------------------------------------------------+
//| Helper: Calculate Daily PnL                                      |
//+------------------------------------------------------------------+
void CalculateDailyPnL() {
    dailyPnL = 0;
    // 1. Closed PnL for today
    for(int i = OrdersHistoryTotal()-1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
            if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
                if(TimeDay(OrderCloseTime()) == Day() && TimeMonth(OrderCloseTime()) == Month()) {
                    dailyPnL += OrderProfit() + OrderCommission() + OrderSwap();
                }
            }
        }
    }
    // 2. Floating PnL
    for(int i = OrdersTotal()-1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
                dailyPnL += OrderProfit() + OrderCommission() + OrderSwap();
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    if(GlobalVariableGet(gvDay) != Day()) ResetDailyState();
    CalculateDailyPnL();

    // Position Assessment
    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);

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

    UpdateBlotter(isHardStop, isSoftWarning, lossLimitHit);

    // Strategy Execution (RSI Placeholder)
    double rsi0 = iRSI(NULL, 0, RsiPeriod, PRICE_CLOSE, 0);
    double rsi1 = iRSI(NULL, 0, RsiPeriod, PRICE_CLOSE, 1);
    bool longSignal = (rsi1 < 30 && rsi0 >= 30);
    bool shortSignal = (rsi1 > 70 && rsi0 <= 70);
    bool flatSignal = ( (openBuys > 0 && rsi0 >= 50) || (openSells > 0 && rsi0 <= 50) );

    if(flatSignal || (lossLimitHit && !isFlat)) { // Force close if max loss hit
        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, "New 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, "New 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", MagicNumber, 0, clrGreen) > 0) {
            dailyClicks++; GlobalVariableSet(gvClicks, dailyClicks);
        }
        else if(shortSignal && openSells > 0 && OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "Scale", MagicNumber, 0, clrRed) > 0) {
            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", 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);
}