The code
A complete Expert Advisor with a deliberately simple demo signal (a close above the previous bar's high) to show the limits at work. It opens real positions: run it on a demo account, and replace the signal with yours.
Save it as MQL5/Experts/limit-trades-per-day.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.
#include <Trade/Trade.mqh>
input int InpMaxPerDay = 3; // Maximum new trades per day
input bool InpOnePerBar = true; // At most one new trade per bar
input int InpResetHour = 0; // Hour the trading day starts, server time
input double InpLots = 0.10; // Volume, lots
input int InpStopPoints = 300; // Stop-loss, points
input ulong InpMagic = 20261112; // Magic number
CTrade trade;
// Start of the current trading day, in server time
datetime TradingDayStart(const datetime now)
{
datetime start = (datetime)((long)now / 86400 * 86400) + InpResetHour * 3600;
return start > now ? start - 86400 : start;
}
// Entries this EA made on this symbol since `from`, read from the deal history (survives restarts).
// `lastEntry` receives the time of the newest one, looked up from `historyFrom`.
int CountEntries(const datetime from, const datetime historyFrom, datetime &lastEntry)
{
lastEntry = 0;
if(!HistorySelect(historyFrom, TimeCurrent() + 86400))
return -1;
int count = 0;
for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
{
ulong deal = HistoryDealGetTicket(i);
if(deal == 0 || HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol || HistoryDealGetInteger(deal, DEAL_MAGIC) != (long)InpMagic)
continue;
long entry = HistoryDealGetInteger(deal, DEAL_ENTRY);
if(entry != DEAL_ENTRY_IN && entry != DEAL_ENTRY_INOUT) // INOUT: a reversal on a netting account
continue;
datetime t = (datetime)HistoryDealGetInteger(deal, DEAL_TIME);
if(t > lastEntry)
lastEntry = t;
if(t >= from)
count++;
}
return count;
}
bool CanOpen()
{
datetime now = TimeCurrent();
datetime dayStart = TradingDayStart(now);
datetime barOpen = iTime(_Symbol, PERIOD_CURRENT, 0);
if(barOpen == 0)
return false;
datetime last;
int today = CountEntries(dayStart, MathMin(dayStart, barOpen), last);
if(today < 0 || today >= InpMaxPerDay)
return false;
if(InpOnePerBar && last >= barOpen)
return false; // already traded on this bar
return true;
}
// Demo signal, to replace with yours: the last closed bar closed above the high of the bar before it
bool BuySignal()
{
return iClose(_Symbol, PERIOD_CURRENT, 1) > iHigh(_Symbol, PERIOD_CURRENT, 2);
}
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic);
trade.SetTypeFillingBySymbol(_Symbol);
return INIT_SUCCEEDED;
}
void OnTick()
{
if(!BuySignal() || !CanOpen())
return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = NormalizeDouble(bid - InpStopPoints * _Point, _Digits);
if(!trade.Buy(InpLots, _Symbol, ask, sl, 0.0, "limited") || trade.ResultRetcode() != TRADE_RETCODE_DONE)
PrintFormat("Buy failed: retcode %u (%s)", trade.ResultRetcode(), trade.ResultRetcodeDescription());
}How it works
TradingDayStart(). Server midnight plusInpResetHour. Prop firms often reset their daily limits at a fixed hour in their own time zone, which is rarely the server's midnight: set the hour to match.HistorySelect(historyFrom, ...). Loads only the deals of the window needed: the trading day, or the current bar if it started earlier. Selecting the whole history on every tick is slow on old accounts.DEAL_ENTRY_INandDEAL_ENTRY_INOUT. Entries are counted, not exits, so a trade stopped out an hour ago still counts for today.INOUTis a reversal on a netting account: one deal that closes a position and opens the opposite one.lastEntry. The time of the newest entry, compared with the bar's open time: equal or later means this bar already has its trade.CanOpen()before sending. The limit is checked against the server's record of what happened, so it holds after a restart, a crash or a move to another VPS.
Pitfalls
- A counter in memory. A
static int tradesTodayresets whenever the EA restarts: a terminal restart, a change of inputs or timeframe, a VPS migration. The EA then trades the day's quota again. - Counting open positions.
PositionsTotal()forgets trades that closed. After a stop-out, a position-based limit lets the EA re-enter on the same signal. - Server midnight as "the day". A prop firm's day that resets at, say, 00:00 in its own time zone can be hours away from the server's. A limit on the wrong day boundary is a limit on the wrong trades: coding a prop firm daily loss limit covers the same question for losses.
- Counting requests instead of deals. A refused order isn't a trade. A pending order counts when it fills, which is when its entry deal appears, possibly hours after it was placed.
- Re-entry on the same bar. A signal that's still true after a quick stop-out opens another trade on the same bar. The one-per-bar check prevents it; a new-bar check alone doesn't survive a restart.
Variations
- Stop after losses. Count closing deals (
DEAL_ENTRY_OUT) with a negativeDEAL_PROFITplus commission and swap, and stop for the day after two of them. - Per week. Use the start of the week (Monday 00:00 server time) as
from. - Open positions at once. For a cap on simultaneous trades rather than daily entries, count
PositionsTotal()filtered by symbol and magic number. - With a session. Combine
CanOpen()with a session filter so the quota is spent in the hours you choose.
Questions
How do I make my EA take only one trade per day?
Set the maximum to 1. Before each entry, count the EA's entry deals since the start of the trading day in the history, filtered by symbol and magic number, and skip the trade when the count is already 1.
Why does my EA open several trades on the same candle?
Because the entry condition stays true for every tick of the candle, and nothing remembers that a trade was already taken, or the memory was lost in a restart. Compare the newest entry deal's time with the candle's open time.
Do trades closed by the stop-loss still count toward the limit?
Yes, in this code: it counts entry deals, and a closed trade's entry deal stays in the history. That's usually what a daily trade limit means; count open positions instead if you want a cap on simultaneous trades.
This program is a minimal example written to show one technique, not a trading system, and none of it is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.