Session-based max trades cap: stopping the overtrading loop
-
LondonScalper
- Posts: 701
- Joined: Sat Sep 05, 2026 7:54 am
Session-based max trades cap: stopping the overtrading loop
Session trade caps beat pep talks when the loop starts.
I set a max trades per session window before the open -- not a target, a ceiling. London might be four to six depending on day type; the overlap is fewer if London already worked. The number is on the sticky next to max risk. Both are binding.
When I hit the cap, I am done even if flat on P&L. That hurts the ego and saves the account. Most of my worst tickets were numbers seven and eight when I was hunting to "use the morning."
What counts
A filled ticket counts. A cancelled working order does not. A revenge re-entry counts twice in my head even if the sheet only shows one -- I still deduct from the cap.
Do you cap count, only R, or both?
Caps scale with day type. News-heavy days get a lower ceiling even if I feel "ready." Green mornings also get a lower remaining cap -- protecting the day is part of the count. The spreadsheet shows tickets remaining, not inspiration. When the number hits zero, the session role becomes reviewer, not hunter.
I set a max trades per session window before the open -- not a target, a ceiling. London might be four to six depending on day type; the overlap is fewer if London already worked. The number is on the sticky next to max risk. Both are binding.
When I hit the cap, I am done even if flat on P&L. That hurts the ego and saves the account. Most of my worst tickets were numbers seven and eight when I was hunting to "use the morning."
What counts
A filled ticket counts. A cancelled working order does not. A revenge re-entry counts twice in my head even if the sheet only shows one -- I still deduct from the cap.
Do you cap count, only R, or both?
Caps scale with day type. News-heavy days get a lower ceiling even if I feel "ready." Green mornings also get a lower remaining cap -- protecting the day is part of the count. The spreadsheet shows tickets remaining, not inspiration. When the number hits zero, the session role becomes reviewer, not hunter.
Re: Session-based max trades cap: stopping the overtrading loop
Hi LondonScalper,LondonScalper wrote: Mon Sep 14, 2026 8:16 pm Session trade caps beat pep talks when the loop starts.
I set a max trades per session window before the open -- not a target, a ceiling. London might be four to six depending on day type; the overlap is fewer if London already worked. The number is on the sticky next to max risk. Both are binding.
When I hit the cap, I am done even if flat on P&L. That hurts the ego and saves the account. Most of my worst tickets were numbers seven and eight when I was hunting to "use the morning."
What counts
A filled ticket counts. A cancelled working order does not. A revenge re-entry counts twice in my head even if the sheet only shows one -- I still deduct from the cap.
Do you cap count, only R, or both?
Caps scale with day type. News-heavy days get a lower ceiling even if I feel "ready." Green mornings also get a lower remaining cap -- protecting the day is part of the count. The spreadsheet shows tickets remaining, not inspiration. When the number hits zero, the session role becomes reviewer, not hunter.
I completely get the logic behind using a hard cap to break the revenge-trading loop, but I actually do not use a maximum trade count.
For me, capping the number of trades artificially limits the upside on outlier days. I’ve found that several times a year, the market microstructure and price action just perfectly align with my edge. When the market is moving clean and every setup goes exactly my way, it’s highly profitable to keep pressing that advantage. Those specific days are when you make the outsized returns that significantly grow the account.
Instead of capping the number of tickets, I only cap the risk. If I hit a hard daily drawdown limit or take a string of consecutive losses, I walk away—that protects the ego and saves the account from the tilt loop. But if I'm deep in the green and the 1-minute or 5-minute setups keep presenting themselves perfectly in line with the daily and 15-minute structure, I keep hunting.
When the market is handing out A+ setups, I want to be executing, not forced into the reviewer role just because I hit an arbitrary number on a sticky note. I let the market flow dictate the volume, while the hard stop on risk (R) protects the downside.
Re: Session-based max trades cap: stopping the overtrading loop
Even if you leave your manual trading uncapped to maximize those outlier days when your 1-minute and 15-minute price action setups align perfectly, hard-coding a daily limit into an automated strategy is a crucial guardrail. Bots don't have the intuition to know when the market is handing out A+ setups versus when it's chopping into a tight range.
To control the maximum number of trades per day in Pine Script v5, you need to set up a variable that counts your entries and resets at the start of every new daily session.
To control the maximum number of trades per day in Pine Script v5, you need to set up a variable that counts your entries and resets at the start of every new daily session.
Code: Select all
//@version=5
strategy("Max Trades Per Day Control", overlay=true)
// --- User Inputs ---
maxTrades = input.int(5, title="Max Trades Per Day", minval=1)
// --- Daily Trade Counter ---
var int tradesToday = 0
// Detect a new day and reset the counter
if dayofweek != dayofweek[1]
tradesToday := 0
// --- Dummy Strategy Conditions ---
// (Replace these with your actual price action / scalping logic)
fastMA = ta.sma(close, 9)
slowMA = ta.sma(close, 21)
longCondition = ta.crossover(fastMA, slowMA)
shortCondition = ta.crossunder(fastMA, slowMA)
// --- Trade Execution ---
// Check if the daily cap has been reached
canTrade = tradesToday < maxTrades
// Only enter if the condition is met AND we are under the daily limit
if longCondition and canTrade
strategy.entry("Long", strategy.long)
tradesToday += 1 // Increment the counter
if shortCondition and canTrade
strategy.entry("Short", strategy.short)
tradesToday += 1 // Increment the counter
// --- Exit Logic ---
if ta.crossunder(fastMA, slowMA)
strategy.close("Long")
if ta.crossover(fastMA, slowMA)
strategy.close("Short")
// --- Visuals ---
// Highlights the background in red when your bot is locked out for the day
bgcolor(not canTrade ? color.new(color.red, 90) : na, title="Daily Cap Reached")Re: Session-based max trades cap: stopping the overtrading loop
How the logic works:
var int tradesToday = 0: The var keyword ensures the variable isn't reset on every single candlestick. It persists across the chart.
dayofweek != dayofweek[1]: This efficiently detects the first candlestick of a new daily session. When a new day begins, it resets tradesToday back to 0.
canTrade = tradesToday < maxTrades: This acts as the gatekeeper. You append and canTrade to your standard entry conditions so the strategy.entry() command simply won't fire once the limit is hit.
tradesToday += 1: The counter only goes up when a trade is actively triggered.
If you are using this alongside your usual price action strategies and want to track closed trades rather than just entries, you could alternatively use strategy.closedtrades combined with the daily reset, but tracking the raw entries (tradesToday += 1) prevents the bot from opening multiple rapid-fire positions if it gets caught in a whipsaw before a trade officially closes.
var int tradesToday = 0: The var keyword ensures the variable isn't reset on every single candlestick. It persists across the chart.
dayofweek != dayofweek[1]: This efficiently detects the first candlestick of a new daily session. When a new day begins, it resets tradesToday back to 0.
canTrade = tradesToday < maxTrades: This acts as the gatekeeper. You append and canTrade to your standard entry conditions so the strategy.entry() command simply won't fire once the limit is hit.
tradesToday += 1: The counter only goes up when a trade is actively triggered.
If you are using this alongside your usual price action strategies and want to track closed trades rather than just entries, you could alternatively use strategy.closedtrades combined with the daily reset, but tracking the raw entries (tradesToday += 1) prevents the bot from opening multiple rapid-fire positions if it gets caught in a whipsaw before a trade officially closes.
Re: Session-based max trades cap: stopping the overtrading loop
Unlike Pine Script, you cannot rely on an in-memory variable (like int tradesToday = 0;) in MetaTrader. If your MT4/MT5 terminal restarts, the connection drops, or you recompile the Expert Advisor, in-memory variables reset to zero, and your EA will bypass your daily cap.
To make this robust, the EA must scan the actual broker account history to count how many trades it has opened since midnight (server time).
Here is the exact implementation for both platforms.
MQL4 Implementation
In MQL4, open trades and closed trades are stored in two separate pools. The function checks both to accurately count how many tickets your EA opened today.
To make this robust, the EA must scan the actual broker account history to count how many trades it has opened since midnight (server time).
Here is the exact implementation for both platforms.
MQL4 Implementation
In MQL4, open trades and closed trades are stored in two separate pools. The function checks both to accurately count how many tickets your EA opened today.
Code: Select all
// --- User Inputs ---
input int MaxTradesPerDay = 5;
input int MagicNumber = 12345;
// --- Trade Counting Function ---
int GetTradesOpenedToday() {
int count = 0;
// Get the timestamp for midnight of the current day
datetime startOfDay = iTime(Symbol(), PERIOD_D1, 0);
// 1. Check currently open/pending orders
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
// Only count if it was opened today
if (OrderOpenTime() >= startOfDay) {
count++;
}
}
}
}
// 2. Check closed orders
for (int i = OrdersHistoryTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
if (OrderOpenTime() >= startOfDay) {
count++;
}
}
}
}
return count;
}Re: Session-based max trades cap: stopping the overtrading loop
MQL5 Implementation
In MQL5, every execution is a "Deal." When a position opens, a DEAL_ENTRY_IN is immediately written to the history pool. We only need to request today's history and count how many "IN" deals belong to your EA.
In MQL5, every execution is a "Deal." When a position opens, a DEAL_ENTRY_IN is immediately written to the history pool. We only need to request today's history and count how many "IN" deals belong to your EA.
Code: Select all
// --- User Inputs ---
input int MaxTradesPerDay = 5;
input ulong MagicNumber = 12345;
// --- Trade Counting Function ---
int GetTradesOpenedToday() {
int count = 0;
// Get the timestamp for midnight of the current day
datetime startOfDay = iTime(_Symbol, PERIOD_D1, 0);
// Request the history pool from midnight to current server time
if (HistorySelect(startOfDay, TimeCurrent())) {
int dealsTotal = HistoryDealsTotal();
for (int i = 0; i < dealsTotal; i++) {
ulong dealTicket = HistoryDealGetTicket(i);
if (dealTicket > 0) {
// We only care about entries (tickets that opened or added to a position)
long dealEntry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if (dealEntry == DEAL_ENTRY_IN) {
string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
if (dealSymbol == _Symbol && dealMagic == MagicNumber) {
count++;
}
}
}
}
}
return count;
}Re: Session-based max trades cap: stopping the overtrading loop
How to use it in OnTick() (Both Platforms)
You use this as a gatekeeper immediately before your OrderSend() execution block. It works perfectly with your rule that "a filled ticket counts"—if you scale into a trade, it will deduct a ticket from the daily cap.
You use this as a gatekeeper immediately before your OrderSend() execution block. It works perfectly with your rule that "a filled ticket counts"—if you scale into a trade, it will deduct a ticket from the daily cap.
Code: Select all
void OnTick() {
// 1. Check how many trades have fired today
bool canTrade = (GetTradesOpenedToday() < MaxTradesPerDay);
// 2. Dummy entry logic
bool longCondition = /* Your price action logic */;
// 3. Execution (Cap enforced)
if (longCondition && canTrade) {
// Execute trade (OrderSend logic)
}
}Re: Session-based max trades cap: stopping the overtrading loop
Because cTrader (cAlgo) is built on C#, you can bypass the clunky for loops required in MQL and use LINQ to query the account history.
In cTrader, active trades are stored in the Positions collection, and closed trades are stored in the History collection. Pending orders are in a separate PendingOrders collection, which naturally fulfills your rule that "cancelled working orders do not count." Furthermore, cTrader uses a string Label instead of an integer Magic Number to identify which bot opened the trade.
In cTrader, active trades are stored in the Positions collection, and closed trades are stored in the History collection. Pending orders are in a separate PendingOrders collection, which naturally fulfills your rule that "cancelled working orders do not count." Furthermore, cTrader uses a string Label instead of an integer Magic Number to identify which bot opened the trade.
Re: Session-based max trades cap: stopping the overtrading loop
Here is the implementation.
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MaxTradesPerDayBot : Robot
{
// --- User Inputs ---
[Parameter("Max Trades Per Day", DefaultValue = 5, MinValue = 1)]
public int MaxTradesPerDay { get; set; }
[Parameter("Bot Label", DefaultValue = "PriceActionScalper")]
public string BotLabel { get; set; }
// --- Trade Counting Function ---
private int GetTradesOpenedToday()
{
// ServerTime.Date automatically strips the time, giving us midnight of the current day
DateTime startOfDay = ServerTime.Date;
// 1. Count open positions that were executed today
int openPositionsToday = Positions.Count(p =>
p.SymbolName == SymbolName &&
p.Label == BotLabel &&
p.EntryTime >= startOfDay);
// 2. Count closed positions (History) that were executed today
int closedPositionsToday = History.Count(h =>
h.SymbolName == SymbolName &&
h.Label == BotLabel &&
h.EntryTime >= startOfDay);
return openPositionsToday + closedPositionsToday;
}
protected override void OnTick()
{
// 1. Check if the daily cap has been reached
bool canTrade = GetTradesOpenedToday() < MaxTradesPerDay;
// 2. Dummy price action condition
bool longCondition = Bars.ClosePrices.Last(1) > Bars.OpenPrices.Last(1); // Example condition
// 3. Execution logic
if (longCondition && canTrade)
{
// The label passed here is crucial so the counter only tracks this specific bot
ExecuteMarketOrder(TradeType.Buy, SymbolName, 1000, BotLabel, 10, 20);
}
}
}
}Re: Session-based max trades cap: stopping the overtrading loop
Key Differences from MQL:
ServerTime.Date: You do not need to convert timeframes or use iTime() to find the start of the day. ServerTime.Date natively drops the hours/minutes/seconds to evaluate trades strictly from 00:00:00 server time.
EntryTime tracking: We query EntryTime (when the order was actually filled in the market) rather than ClosingTime. This ensures a trade opened at 23:55 yesterday but closed at 01:00 today does not deduct from today's cap.
Instance Isolation: By passing p.SymbolName == SymbolName && p.Label == BotLabel, the LINQ queries ensure you can run this bot on your daily or 15-minute charts across multiple pairs (like gold, silver, or spot forex) without the trade counters interfering with one another.
ServerTime.Date: You do not need to convert timeframes or use iTime() to find the start of the day. ServerTime.Date natively drops the hours/minutes/seconds to evaluate trades strictly from 00:00:00 server time.
EntryTime tracking: We query EntryTime (when the order was actually filled in the market) rather than ClosingTime. This ensures a trade opened at 23:55 yesterday but closed at 01:00 today does not deduct from today's cap.
Instance Isolation: By passing p.SymbolName == SymbolName && p.Label == BotLabel, the LINQ queries ensure you can run this bot on your daily or 15-minute charts across multiple pairs (like gold, silver, or spot forex) without the trade counters interfering with one another.