<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omais Ali</title>
    <description>The latest articles on DEV Community by Omais Ali (@omaisali).</description>
    <link>https://dev.to/omaisali</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4173524%2F25bad014-e6df-4e8d-ad5f-db874f3b23fc.png</url>
      <title>DEV Community: Omais Ali</title>
      <link>https://dev.to/omaisali</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omaisali"/>
    <language>en</language>
    <item>
      <title>Building a news filter for an MQL5 EA with the built in economic calendar</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Fri, 30 Oct 2026 06:33:11 +0000</pubDate>
      <link>https://dev.to/omaisali/building-a-news-filter-for-an-mql5-ea-with-the-built-in-economic-calendar-3cj5</link>
      <guid>https://dev.to/omaisali/building-a-news-filter-for-an-mql5-ea-with-the-built-in-economic-calendar-3cj5</guid>
      <description>&lt;p&gt;Many Expert Advisors should not open new trades around major economic releases. Spreads widen, prices jump and stops slip. The usual fix is a news filter, and a lot of EAs build one by downloading a calendar from a website with WebRequest and parsing it. That works until the site changes its layout.&lt;/p&gt;

&lt;p&gt;MetaTrader 5 already has an economic calendar inside the terminal, with an MQL5 API. This tutorial uses it to block new entries for a few minutes before and after important events for the currencies of the chart symbol.&lt;/p&gt;

&lt;h1&gt;
  
  
  The two functions we need
&lt;/h1&gt;

&lt;p&gt;CalendarValueHistory() returns the event values in a time range, optionally filtered by country code and currency. Each value is an MqlCalendarValue with an event_id and a time. It returns the number of values found, or minus one on failure.&lt;/p&gt;

&lt;p&gt;CalendarEventById() turns an event_id into an MqlCalendarEvent, which holds the name, the importance and the time mode of the event.&lt;/p&gt;

&lt;p&gt;One detail matters a lot: all calendar functions work in trade server time. The time field in MqlCalendarValue is server time, so compare it with TimeTradeServer(), not with your local clock.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deciding whether an event matters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input int  InpSecondsBefore     = 1800;  // block entries 30 minutes before an event
input int  InpSecondsAfter      = 900;   // and 15 minutes after it
input bool InpIncludeModerate   = false; // also block on moderate importance events
input bool InpBlockIfNoCalendar = true;  // block when the calendar cannot be read

bool gNewsBlock = false;
bool gFilterOn  = true;

bool EventMatters(const ulong event_id, string &amp;amp;name)
{
   MqlCalendarEvent ev;
   if(!CalendarEventById(event_id, ev))
      return false;

   // all day and tentative events have no exact time to block around
   if(ev.time_mode != CALENDAR_TIMEMODE_DATETIME)
      return false;

   bool important = (ev.importance == CALENDAR_IMPORTANCE_HIGH) ||
                    (InpIncludeModerate &amp;amp;&amp;amp; ev.importance == CALENDAR_IMPORTANCE_MODERATE);
   if(important)
      name = ev.name;
   return important;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Checking one currency
&lt;/h1&gt;

&lt;p&gt;An event at time T should block entries from T minus InpSecondsBefore until T plus InpSecondsAfter. Turned around, the EA is blocked right now if any important event falls between now minus InpSecondsAfter and now plus InpSecondsBefore. The inputs are in seconds because datetime values in MQL5 are counted in seconds, so no conversion is needed. That is the range we ask the calendar for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1 = inside a news window, 0 = clear, -1 = calendar could not be read
int CheckCurrency(const string currency, const datetime now, string &amp;amp;why)
{
   MqlCalendarValue values[];
   datetime from = now - InpSecondsAfter;
   datetime to   = now + InpSecondsBefore;

   ResetLastError();
   int n = CalendarValueHistory(values, from, to, NULL, currency);
   if(n &amp;lt; 0)
   {
      PrintFormat("Calendar read failed for %s, error %d", currency, GetLastError());
      return -1;
   }

   for(int i = 0; i &amp;lt; n; i++)
   {
      string name = "";
      if(EventMatters(values[i].event_id, name))
      {
         why = StringFormat("%s: %s at %s", currency, name,
                            TimeToString(values[i].time, TIME_DATE | TIME_MINUTES));
         return 1;
      }
   }
   return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering by currency rather than country is deliberate. EUR events come from the euro area and from Germany, France, Italy and others. Asking for currency "EUR" catches all of them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Updating the block once a minute
&lt;/h1&gt;

&lt;p&gt;There is no need to query the calendar on every tick. A one minute timer is plenty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void UpdateNewsBlock()
{
   datetime now    = TimeTradeServer();
   string   base   = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE);
   string   profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
   string   why    = "";

   int a = CheckCurrency(base, now, why);
   int b = (profit != base) ? CheckCurrency(profit, now, why) : 0;

   bool block;
   if(a &amp;lt; 0 || b &amp;lt; 0)
   {
      block = InpBlockIfNoCalendar;
      why   = "calendar unavailable";
   }
   else
      block = (a == 1 || b == 1);

   if(block != gNewsBlock)
   {
      if(block)
         Print("News block ON, ", why);
      else
         Print("News block OFF");
      gNewsBlock = block;
   }
}

int OnInit()
{
   if(MQLInfoInteger(MQL_TESTER))
   {
      // the calendar API is not available in the Strategy Tester
      Print("News filter disabled in the tester");
      gFilterOn = false;
      return INIT_SUCCEEDED;
   }
   EventSetTimer(60);
   UpdateNewsBlock();
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}

void OnTimer()
{
   if(gFilterOn)
      UpdateNewsBlock();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Using it in OnTick
&lt;/h1&gt;

&lt;p&gt;The filter should only stop new entries. Trailing stops, breakeven moves and exits keep running during news, because that is exactly when they are needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void OnTick()
{
   ManageOpenPositions();      // always runs

   if(gFilterOn &amp;amp;&amp;amp; gNewsBlock)
      return;                  // no new entries inside a news window

   CheckForEntry();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ManageOpenPositions() and CheckForEntry() stand for your own EA logic.&lt;/p&gt;

&lt;h1&gt;
  
  
  Things to know before relying on it
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;The calendar functions do not work in the Strategy Tester. A backtest of this EA runs without the filter, which is why OnInit() says so in the log. If the filter matters to the strategy, export the event times to a file and read that file in the tester instead.&lt;/li&gt;
&lt;li&gt;A fresh terminal may need a little time to download calendar data. The InpBlockIfNoCalendar input decides whether a failed read blocks trading or lets it continue. Blocking is the safer default.&lt;/li&gt;
&lt;li&gt;Symbols such as XAUUSD have a base currency with no events, so only USD events will count. Index CFDs often report the same currency for base and profit. Check what SymbolInfoString() returns on your broker before trusting the filter on non forex symbols.&lt;/li&gt;
&lt;li&gt;Importance levels are set by the calendar provider. Before going live, print the events your filter would have blocked over the past month and check that the list matches what you expect.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step is cheap. Run CalendarValueHistory() over the previous 30 days in a script, pass every value through EventMatters(), and print the result. If an event you care about is missing, or the list is full of minor releases, adjust the importance setting before the EA ever sees a real release.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adding a pause button to an MQL5 Expert Advisor with OnChartEvent</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Thu, 29 Oct 2026 06:29:09 +0000</pubDate>
      <link>https://dev.to/omaisali/adding-a-pause-button-to-an-mql5-expert-advisor-with-onchartevent-2k27</link>
      <guid>https://dev.to/omaisali/adding-a-pause-button-to-an-mql5-expert-advisor-with-onchartevent-2k27</guid>
      <description>&lt;p&gt;Sooner or later every trader running an Expert Advisor wants to stop it from opening new trades for a while. A news release is coming, the spread looks strange, or they simply want to watch for an hour. The usual options are not great. Removing the EA from the chart also stops it managing the positions it already has. Turning off Algo Trading in the terminal stops every EA on every chart.&lt;/p&gt;

&lt;p&gt;A pause button on the chart solves this. It blocks new entries for this EA only, while the EA keeps managing its open positions. This tutorial builds one with OBJ_BUTTON and OnChartEvent(), and makes the paused state survive a restart.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the button
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input long InpMagic = 7310;

const string BTN = "ctb_pause_btn";
bool gPaused = false;

string PauseKey()
{
   // global variable names are limited to 63 characters
   return StringFormat("ctb_paused_%I64d_%s_%I64d",
                       AccountInfoInteger(ACCOUNT_LOGIN), _Symbol, InpMagic);
}

void UpdateButton()
{
   ObjectSetInteger(0, BTN, OBJPROP_STATE, gPaused);
   ObjectSetString(0, BTN, OBJPROP_TEXT, gPaused ? "Resume entries" : "Pause entries");
   ObjectSetInteger(0, BTN, OBJPROP_BGCOLOR, gPaused ? clrOrange : clrSilver);
   ChartRedraw(0);
}

void CreateButton()
{
   if(ObjectFind(0, BTN) &amp;lt; 0)
      ObjectCreate(0, BTN, OBJ_BUTTON, 0, 0, 0);

   ObjectSetInteger(0, BTN, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
   ObjectSetInteger(0, BTN, OBJPROP_XDISTANCE, 150);  // at least the width
   ObjectSetInteger(0, BTN, OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0, BTN, OBJPROP_XSIZE, 140);
   ObjectSetInteger(0, BTN, OBJPROP_YSIZE, 28);
   ObjectSetInteger(0, BTN, OBJPROP_SELECTABLE, false);
   UpdateButton();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here.&lt;/p&gt;

&lt;p&gt;With CORNER_RIGHT_UPPER the distances are measured from the right edge of the chart to the left edge of the button, so OBJPROP_XDISTANCE has to be at least as large as OBJPROP_XSIZE or the button ends up partly off screen.&lt;/p&gt;

&lt;p&gt;The key for the saved state includes the account number, the symbol and the magic number. If the same EA runs on two charts, each copy gets its own pause switch.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reacting to clicks
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void SetPaused(const bool paused)
{
   gPaused = paused;
   GlobalVariableSet(PauseKey(), paused ? 1.0 : 0.0);
   GlobalVariablesFlush();
   PrintFormat("New entries %s", paused ? "paused" : "resumed");
   UpdateButton();
}

void OnChartEvent(const int id, const long &amp;amp;lparam,
                  const double &amp;amp;dparam, const string &amp;amp;sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK &amp;amp;&amp;amp; sparam == BTN)
   {
      // the terminal has already flipped OBJPROP_STATE for us
      SetPaused((bool)ObjectGetInteger(0, BTN, OBJPROP_STATE));
      return;
   }

   if(id == CHARTEVENT_OBJECT_DELETE &amp;amp;&amp;amp; sparam == BTN)
      CreateButton();   // someone deleted it by hand, put it back
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A button object toggles its own OBJPROP_STATE when clicked, so the handler reads the state instead of keeping a separate toggle. That way the variable and what the trader sees cannot drift apart.&lt;/p&gt;

&lt;p&gt;The delete event is not sent by default. You turn it on with ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true), which is done in OnInit() below.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wiring it into the EA
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int OnInit()
{
   ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);

   if(GlobalVariableCheck(PauseKey()))
      gPaused = GlobalVariableGet(PauseKey()) &amp;gt; 0.5;

   CreateButton();
   if(gPaused)
      Print("Starting with new entries paused");
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   ObjectDelete(0, BTN);
}

void OnTick()
{
   ManageOpenPositions();   // stops, trailing, exits: always runs

   if(gPaused)
      return;               // no new entries while paused

   CheckForEntry();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ManageOpenPositions() and CheckForEntry() stand for whatever your EA already does. The order inside OnTick() is the important part. Position management runs first and always. The pause only skips the entry logic. A pause button that also stopped trailing stops and exits would be more dangerous than no button at all.&lt;/p&gt;

&lt;p&gt;The state is stored in a terminal global variable, so it survives a timeframe change, a change of inputs and a terminal restart. GlobalVariablesFlush() writes it to disk straight away instead of waiting for the terminal to close. Global variables that are not accessed are deleted after four weeks, which is fine for a pause flag.&lt;/p&gt;

&lt;p&gt;Deleting the button in OnDeinit() keeps the chart clean when the EA is removed. On a timeframe change OnInit() runs again and recreates it with the saved state.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing it
&lt;/h1&gt;

&lt;p&gt;Chart events are not delivered in the Strategy Tester, so try the button on a demo chart.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attach the EA, click Pause and check the Experts log for the message.&lt;/li&gt;
&lt;li&gt;Change the timeframe and confirm the button comes back still paused.&lt;/li&gt;
&lt;li&gt;Restart the terminal and check again.&lt;/li&gt;
&lt;li&gt;Open a position, pause, and confirm the trailing stop or exit still works while no new trades open.&lt;/li&gt;
&lt;li&gt;Delete the button from the object list (Ctrl+B) and confirm it reappears.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A pause switch is a small feature, but it gives the trader a safe middle ground between leaving the EA alone and pulling it off the chart.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Sending push notifications from an MQL5 EA without getting them switched off</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Wed, 28 Oct 2026 06:30:35 +0000</pubDate>
      <link>https://dev.to/omaisali/sending-push-notifications-from-an-mql5-ea-without-getting-them-switched-off-11h2</link>
      <guid>https://dev.to/omaisali/sending-push-notifications-from-an-mql5-ea-without-getting-them-switched-off-11h2</guid>
      <description>&lt;p&gt;A push notification to your phone is the simplest way to know what an Expert Advisor is doing while you are away from the terminal. MQL5 has one function for it, SendNotification(), and most EAs call it directly wherever something interesting happens.&lt;/p&gt;

&lt;p&gt;That works until the day something goes wrong. A disconnect, a loop of rejected orders or a burst of partial fills makes the EA call SendNotification() many times in a few seconds. The terminal has strict limits on this function, and if they are broken it can disable notifications for the program. So the one moment you most need the alert is the moment it stops arriving.&lt;/p&gt;

&lt;p&gt;This tutorial builds a small notifier that respects the limits, skips duplicates and still logs everything.&lt;/p&gt;

&lt;h1&gt;
  
  
  The rules from the documentation
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;The message can be at most 255 characters.&lt;/li&gt;
&lt;li&gt;No more than 2 calls per second and no more than 10 calls per minute. The terminal monitors this dynamically and can disable the function when the limit is broken.&lt;/li&gt;
&lt;li&gt;SendNotification() does not work in the Strategy Tester.&lt;/li&gt;
&lt;li&gt;Notifications must be enabled in the terminal, with your MetaQuotes ID entered under Tools, Options, Notifications. You can check this from code with TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED).&lt;/li&gt;
&lt;li&gt;On failure, GetLastError() returns one of 4515 (ERR_NOTIFICATION_SEND_FAILED), 4516 (ERR_NOTIFICATION_WRONG_PARAMETER), 4517 (ERR_NOTIFICATION_WRONG_SETTINGS) or 4518 (ERR_NOTIFICATION_TOO_FREQUENT).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The design follows from those rules: never send from the place where the event happens. Put the message in a queue and send from a timer at a pace the terminal accepts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Queueing messages
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define NOTIFY_MAX_PER_MIN 8     // stay below the limit of 10
#define NOTIFY_MAX_QUEUE   50

string   gQueue[];
ulong    gSent[];                // GetTickCount64() of recent sends
string   gLastText     = "";
datetime gLastTextTime = 0;

bool NotifyAvailable()
{
   if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION))
      return false;
   return (bool)TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED);
}

void Notify(const string text, const int dedupeSeconds = 300)
{
   Print("NOTIFY: ", text);      // the log always gets the message

   if(!NotifyAvailable())
      return;

   // the same text again within a few minutes adds nothing
   if(text == gLastText &amp;amp;&amp;amp; TimeLocal() - gLastTextTime &amp;lt; dedupeSeconds)
      return;
   gLastText     = text;
   gLastTextTime = TimeLocal();

   string msg = StringFormat("%s %I64d: %s", _Symbol,
                             AccountInfoInteger(ACCOUNT_LOGIN), text);
   if(StringLen(msg) &amp;gt; 255)
      msg = StringSubstr(msg, 0, 255);

   int n = ArraySize(gQueue);
   if(n &amp;gt;= NOTIFY_MAX_QUEUE)
   {
      ArrayRemove(gQueue, 0, 1); // drop the oldest, keep the newest
      n--;
   }
   ArrayResize(gQueue, n + 1);
   gQueue[n] = msg;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few choices here are deliberate.&lt;/p&gt;

&lt;p&gt;The symbol and account number go in front of every message. If you run several EAs or several accounts, a bare "Daily loss limit reached" on a phone screen is not enough to act on.&lt;/p&gt;

&lt;p&gt;Duplicates are dropped by text. An EA that fails to modify a stop on every tick would otherwise produce the same alert hundreds of times. Use TimeLocal() for this check rather than TimeCurrent(), because server time stops moving when no quotes arrive, for example over the weekend.&lt;/p&gt;

&lt;p&gt;The queue has a cap. If the terminal cannot send for a long time, you do not want a backlog of stale alerts arriving an hour later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sending at a safe pace
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void FlushNotifications()
{
   if(ArraySize(gQueue) == 0)
      return;

   // forget sends older than 60 seconds
   ulong now  = GetTickCount64();
   int   keep = 0;
   for(int i = 0; i &amp;lt; ArraySize(gSent); i++)
      if(now - gSent[i] &amp;lt; 60000)
         gSent[keep++] = gSent[i];
   ArrayResize(gSent, keep);

   if(keep &amp;gt;= NOTIFY_MAX_PER_MIN)
      return;                    // wait until the window moves on

   ResetLastError();
   if(SendNotification(gQueue[0]))
   {
      ArrayResize(gSent, keep + 1);
      gSent[keep] = now;
      ArrayRemove(gQueue, 0, 1);
      return;
   }

   int err = GetLastError();
   PrintFormat("SendNotification failed, error %d", err);

   // these will never succeed, so do not retry them forever
   if(err == ERR_NOTIFICATION_WRONG_PARAMETER ||
      err == ERR_NOTIFICATION_WRONG_SETTINGS)
      ArrayRemove(gQueue, 0, 1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from a one second timer. That alone keeps you at no more than one message per second, and the rolling window keeps you under the per minute limit with some room to spare.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int OnInit()
{
   EventSetTimer(1);
   if(!NotifyAvailable())
      Print("Push notifications are off or not configured, alerts go to the log only");
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}

void OnTimer()
{
   FlushNotifications();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your EA already uses a timer for other work, call FlushNotifications() at the end of the existing OnTimer() instead of creating a second one. An EA can only have one timer.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is worth a notification
&lt;/h1&gt;

&lt;p&gt;The limit of 10 per minute is a good reminder that a phone alert should mean "look at this". Some events that usually earn one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A position opened or closed, with volume, price and the reason. OnTradeTransaction() with TRADE_TRANSACTION_DEAL_ADD is the reliable place to catch this.&lt;/li&gt;
&lt;li&gt;A trade request rejected with a retcode the EA cannot handle on its own.&lt;/li&gt;
&lt;li&gt;A risk rule triggered, such as a daily loss limit or a maximum drawdown lock.&lt;/li&gt;
&lt;li&gt;Lost connection that lasts longer than a minute. Check TerminalInfoInteger(TERMINAL_CONNECTED) in the timer and only alert when the state has been bad for a while, then send one more message when it recovers.&lt;/li&gt;
&lt;li&gt;The EA being removed or the terminal closing. In OnDeinit() the timer is already gone, so call SendNotification() directly there, once, with the reason code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else belongs in the log. Normal events like a new bar or a trailing stop step will drown out the alerts that matter.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing it
&lt;/h1&gt;

&lt;p&gt;SendNotification() does nothing in the Strategy Tester, so test on a demo account. A quick check is a script or a temporary input that queues 30 messages at once. You should see them arrive on the phone at a steady pace over a few minutes, and no 4518 errors in the Experts log. Then send the same text 30 times and confirm only one arrives.&lt;/p&gt;

&lt;p&gt;A notifier that slows down under pressure is far more useful than one that goes silent exactly when the EA is in trouble.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Validating Expert Advisor inputs in OnInit so bad settings never reach the market</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Tue, 27 Oct 2026 06:23:58 +0000</pubDate>
      <link>https://dev.to/omaisali/validating-expert-advisor-inputs-in-oninit-so-bad-settings-never-reach-the-market-40j1</link>
      <guid>https://dev.to/omaisali/validating-expert-advisor-inputs-in-oninit-so-bad-settings-never-reach-the-market-40j1</guid>
      <description>&lt;p&gt;Most Expert Advisors trust their inputs completely. Whatever the user types in the settings dialog goes straight into the trading logic. A risk of 10 instead of 1.0, a stop loss of 0, a fast moving average longer than the slow one: the EA starts, runs and trades on settings that make no sense.&lt;/p&gt;

&lt;p&gt;MQL5 gives you a clean place to stop that. OnInit() runs once when the EA is attached, when inputs change and at the start of every Strategy Tester pass. Its return value decides whether the EA is allowed to run at all.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to check inputs there, how to choose the right return code, and how the same checks speed up optimization.&lt;/p&gt;

&lt;h1&gt;
  
  
  The return codes
&lt;/h1&gt;

&lt;p&gt;OnInit() returns an int, normally one of these values from ENUM_INIT_RETCODE:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;INIT_SUCCEEDED means everything is fine and the EA starts.&lt;/li&gt;
&lt;li&gt;INIT_FAILED means initialization failed for a reason outside the inputs, such as an indicator handle that could not be created.&lt;/li&gt;
&lt;li&gt;INIT_PARAMETERS_INCORRECT means the inputs themselves are wrong.&lt;/li&gt;
&lt;li&gt;INIT_AGENT_NOT_SUITABLE is for tester agents that cannot run the pass, and rarely matters in normal EAs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any value other than INIT_SUCCEEDED stops the EA. On a live chart it is removed, and OnDeinit() runs with the reason REASON_INITFAILED. The user sees nothing unless you tell them, so always log why.&lt;/p&gt;

&lt;h1&gt;
  
  
  A small rejection helper
&lt;/h1&gt;

&lt;p&gt;Keeping the message and the return code in one place makes the checks short and consistent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int Reject(const string why)
{
   PrintFormat("Input error, EA not started: %s", why);
   if(!MQLInfoInteger(MQL_TESTER))
      Alert("EA not started: ", why);
   return INIT_PARAMETERS_INCORRECT;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Alert is skipped in the tester, because a popup for every rejected optimization pass would be unbearable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Checking the inputs themselves
&lt;/h1&gt;

&lt;p&gt;These are rules that hold on any account and any symbol. Write them as plainly as you can.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input double InpRiskPercent = 1.0;    // risk per trade, percent of balance
input int    InpStopPoints  = 300;    // stop loss distance in points
input int    InpTakePoints  = 600;    // take profit distance in points
input int    InpFastPeriod  = 12;     // fast moving average period
input int    InpSlowPeriod  = 26;     // slow moving average period
input int    InpStartHour   = 8;      // trading window start, server time
input int    InpEndHour     = 20;     // trading window end, server time
input long   InpMagic       = 240101; // magic number

int OnInit()
{
   if(InpRiskPercent &amp;lt;= 0.0 || InpRiskPercent &amp;gt; 5.0)
      return Reject("InpRiskPercent must be above 0 and no more than 5");

   if(InpStopPoints &amp;lt;= 0)
      return Reject("InpStopPoints must be positive, the EA never trades without a stop");

   if(InpTakePoints &amp;lt; 0)
      return Reject("InpTakePoints cannot be negative, use 0 for no take profit");

   if(InpFastPeriod &amp;lt; 1 || InpSlowPeriod &amp;lt; 1)
      return Reject("moving average periods must be at least 1");

   if(InpFastPeriod &amp;gt;= InpSlowPeriod)
      return Reject("InpFastPeriod must be shorter than InpSlowPeriod");

   if(InpStartHour &amp;lt; 0 || InpStartHour &amp;gt; 23 || InpEndHour &amp;lt; 0 || InpEndHour &amp;gt; 23)
      return Reject("trading hours must be between 0 and 23");

   if(InpMagic &amp;lt;= 0)
      return Reject("InpMagic must be a positive number");

   return CheckEnvironment();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upper limit on risk is a deliberate guard against typing 10 when you meant 1.0. Pick a ceiling that suits the strategy and say so in the message.&lt;/p&gt;

&lt;h1&gt;
  
  
  Checking inputs against the symbol
&lt;/h1&gt;

&lt;p&gt;Some inputs are only valid on certain symbols or accounts. A stop of 30 points is fine on one broker and below the minimum distance on another. These checks use symbol properties, so they belong in a second function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int CheckEnvironment()
{
   long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   if(stopsLevel &amp;gt; 0 &amp;amp;&amp;amp; InpStopPoints &amp;lt;= stopsLevel)
      return Reject(StringFormat("InpStopPoints %d is inside the broker minimum of %d points on %s",
                                 InpStopPoints, (int)stopsLevel, _Symbol));

   if(InpTakePoints &amp;gt; 0 &amp;amp;&amp;amp; stopsLevel &amp;gt; 0 &amp;amp;&amp;amp; InpTakePoints &amp;lt;= stopsLevel)
      return Reject(StringFormat("InpTakePoints %d is inside the broker minimum of %d points on %s",
                                 InpTakePoints, (int)stopsLevel, _Symbol));

   double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(minVol &amp;lt;= 0.0)
   {
      PrintFormat("Could not read volume limits for %s", _Symbol);
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the different return code for the volume check. If the terminal cannot give us symbol data, the inputs are not the problem, so INIT_FAILED is the honest answer.&lt;/p&gt;

&lt;p&gt;A stops level of 0 usually means the broker uses a floating minimum distance, so the startup check cannot catch everything. Keep checking the distance again just before each order or modify.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why this matters in optimization
&lt;/h1&gt;

&lt;p&gt;Optimization is where input validation pays for itself. Suppose you optimize the fast period from 5 to 50 and the slow period from 10 to 100. A large share of those combinations has the fast period equal to or longer than the slow one. Without validation, every one of them runs a full test that produces nonsense.&lt;/p&gt;

&lt;p&gt;With the check above, those passes return INIT_PARAMETERS_INCORRECT and stop immediately. The tester marks them in the results instead of simulating them, so the optimization finishes faster and the results table only ranks combinations that make sense.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use enums instead of magic integers
&lt;/h1&gt;

&lt;p&gt;The cheapest validation is the one the user cannot get wrong. If an input chooses between modes, declare an enum and the settings dialog will show a drop down list instead of a free number field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum ENUM_EXIT_MODE
{
   EXIT_FIXED_TP  = 0,  // Fixed take profit
   EXIT_TRAILING  = 1,  // Trailing stop
   EXIT_OPPOSITE  = 2   // Opposite signal
};

input ENUM_EXIT_MODE InpExitMode = EXIT_FIXED_TP;  // exit method
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment after each enum value is what the user sees in the list, which doubles as documentation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Practical notes
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Input variables are read only inside the EA. If you need to adjust a value, for example rounding a lot size to the symbol step, copy it into a global variable in OnInit() and use the copy.&lt;/li&gt;
&lt;li&gt;Print the final accepted settings once in OnInit(). When someone sends you a log, you will know exactly what the EA was running with.&lt;/li&gt;
&lt;li&gt;Write the reason in the message, not just the input name. "InpFastPeriod must be shorter than InpSlowPeriod" saves a support question. "Invalid input" creates one.&lt;/li&gt;
&lt;li&gt;Validation in OnInit() runs again whenever the user changes inputs on a running EA, so the same rules protect live edits too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An EA that refuses to start with a clear message is far better than one that starts happily and trades on a typo.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Writing a custom optimization criterion in MQL5 with OnTester()</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:27:55 +0000</pubDate>
      <link>https://dev.to/omaisali/writing-a-custom-optimization-criterion-in-mql5-with-ontester-kgf</link>
      <guid>https://dev.to/omaisali/writing-a-custom-optimization-criterion-in-mql5-with-ontester-kgf</guid>
      <description>&lt;p&gt;The MetaTrader 5 Strategy Tester can rank optimization passes by balance, profit factor, expected payoff, drawdown, recovery factor or Sharpe ratio. Each of those built in criteria has the same weakness: it will happily rank a pass with 14 trades and one lucky winner above a pass with 400 steady trades.&lt;/p&gt;

&lt;p&gt;The fix is to write your own criterion. MQL5 lets an Expert Advisor define an OnTester() function that runs once at the end of every test pass and returns a single number. If you choose "Custom max" as the optimization criterion, the tester ranks passes by that number.&lt;/p&gt;

&lt;p&gt;This tutorial builds a criterion that rejects passes you would never trade, and then scores the rest in a way that rewards robustness instead of luck.&lt;/p&gt;

&lt;h1&gt;
  
  
  The basics
&lt;/h1&gt;

&lt;p&gt;OnTester() takes no parameters and returns a double. Inside it, TesterStatistics() gives you the same figures you see in the tester report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double OnTester()
{
   double trades = TesterStatistics(STAT_TRADES);
   double profit = TesterStatistics(STAT_PROFIT);
   return profit;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning plain profit like this reproduces the default balance criterion, which is not very useful. The value comes from what you add.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step one: hard filters
&lt;/h1&gt;

&lt;p&gt;Some passes should never win, however good their headline number. Return 0 for them so they sink to the bottom of the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input int    InpMinTrades     = 100;   // minimum trades for a pass to count
input double InpMaxDDPercent  = 25.0;  // maximum relative equity drawdown

double OnTester()
{
   double trades     = TesterStatistics(STAT_TRADES);
   double lossTrades = TesterStatistics(STAT_LOSS_TRADES);
   double profit     = TesterStatistics(STAT_PROFIT);
   double ddPercent  = TesterStatistics(STAT_EQUITY_DDREL_PERCENT);

   if(trades &amp;lt; InpMinTrades)        return 0.0;  // too few trades to judge
   if(lossTrades &amp;lt; 1)               return 0.0;  // no losers usually means a broken test
   if(profit &amp;lt;= 0.0)                return 0.0;
   if(ddPercent &amp;gt; InpMaxDDPercent)  return 0.0;

   return profit;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The minimum trade count is the most important line. Small samples produce extreme statistics in both directions, and the optimizer is very good at finding them.&lt;/p&gt;

&lt;p&gt;A pass with zero losing trades is also worth rejecting. In real strategies it almost always means the stop loss was never hit because of a modelling problem, a grid or martingale that has not met its bad day yet, or a test period that is far too short.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step two: score what is left
&lt;/h1&gt;

&lt;p&gt;Profit on its own ignores how much pain it took to earn it. Recovery factor, which is net profit divided by maximum drawdown, is a better starting point because it rewards return per unit of drawdown.&lt;/p&gt;

&lt;p&gt;On top of that, it helps to shrink the score for passes that only just cleared the trade count filter. A simple way to do that without any fancy statistics is to divide by a factor that approaches 1 as the trade count grows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double OnTester()
{
   double trades     = TesterStatistics(STAT_TRADES);
   double lossTrades = TesterStatistics(STAT_LOSS_TRADES);
   double profit     = TesterStatistics(STAT_PROFIT);
   double ddPercent  = TesterStatistics(STAT_EQUITY_DDREL_PERCENT);
   double recovery   = TesterStatistics(STAT_RECOVERY_FACTOR);

   if(trades &amp;lt; InpMinTrades)        return 0.0;
   if(lossTrades &amp;lt; 1)               return 0.0;
   if(profit &amp;lt;= 0.0)                return 0.0;
   if(ddPercent &amp;gt; InpMaxDDPercent)  return 0.0;

   // 100 trades divides the score by 1.5, 400 trades by 1.125
   double sampleFactor = 1.0 + 50.0 / trades;

   return recovery / sampleFactor;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact constant is a judgement call. The point is that two passes with the same recovery factor should not be ranked equally when one is based on four times as many trades.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step three: check what the optimizer is really choosing
&lt;/h1&gt;

&lt;p&gt;Once you switch to a custom criterion, look at the top 20 passes rather than only the best one. You want to see:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Neighbouring parameter values scoring similarly. If the best pass is surrounded by zeros, it is a spike, not a plateau.&lt;/li&gt;
&lt;li&gt;Trade counts well above your minimum, not clustered right on it.&lt;/li&gt;
&lt;li&gt;Drawdown comfortably under your limit, not just under it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the top of the list is dominated by passes that barely clear the filters, the filters are doing their job and the strategy may simply not have enough edge.&lt;/p&gt;

&lt;h1&gt;
  
  
  Practical notes
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;OnTester() also runs in a single test, not only in optimization. The value is shown in the report as the OnTester result, which is handy for checking your formula on one known pass.&lt;/li&gt;
&lt;li&gt;Keep the criterion inputs in the EA so you can change thresholds without recompiling, but exclude them from the optimization itself.&lt;/li&gt;
&lt;li&gt;If you use forward testing in the optimizer, compare the custom score of the backtest and forward parts of the same pass. A large drop is the clearest warning sign you will get.&lt;/li&gt;
&lt;li&gt;Do not divide by a statistic that can be zero. Recovery factor and profit factor need care when there are no losing trades, which is one more reason for the filter above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A custom criterion does not make a strategy better. It stops the optimizer from rewarding the passes most likely to fall apart live.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Moving a stop to breakeven in MQL5 without rejected modifies or hidden losses</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:30:03 +0000</pubDate>
      <link>https://dev.to/omaisali/moving-a-stop-to-breakeven-in-mql5-without-rejected-modifies-or-hidden-losses-3o73</link>
      <guid>https://dev.to/omaisali/moving-a-stop-to-breakeven-in-mql5-without-rejected-modifies-or-hidden-losses-3o73</guid>
      <description>&lt;p&gt;Moving the stop loss to breakeven once a trade is in profit is one of the most requested Expert Advisor features. It also produces a surprising number of bug reports: the stop never moves, the journal fills with errors, or the trade closes "at breakeven" and still shows a small loss.&lt;/p&gt;

&lt;p&gt;This tutorial builds a breakeven routine that measures profit from the right price, respects the broker's distance rules, only ever tightens the stop, and covers trading costs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Measure profit from the price you would close at
&lt;/h1&gt;

&lt;p&gt;A buy position closes at the bid. A sell position closes at the ask. If you measure a buy against the ask, or a sell against the bid, the spread gets counted as profit and the stop moves too early. On a wide spread symbol that can mean the stop is moved before the trade is really in profit at all.&lt;/p&gt;

&lt;h1&gt;
  
  
  Breakeven is not the entry price
&lt;/h1&gt;

&lt;p&gt;If the stop sits exactly at the open price, a trade that comes back and hits it still loses the commission and any swap it has collected. So "breakeven" should usually be the entry plus a small offset in your favour, large enough to cover commission and a little slippage. For a buy that is above the entry, for a sell below it.&lt;/p&gt;

&lt;p&gt;On netting accounts there is one more catch. If the EA adds to a position, POSITION_PRICE_OPEN becomes the volume weighted average price of the whole position, so the breakeven level moves with every addition. That is usually what you want, but test it on purpose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Helpers
&lt;/h1&gt;

&lt;p&gt;The two helpers convert points to a price distance and round a price to the symbol's tick size. Rounding to the tick size rather than to the number of digits matters on indices and metals where the tick is larger than one point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Trade\Trade.mqh&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;CTrade&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;   &lt;span class="n"&gt;InpTriggerPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// profit in points before the stop moves&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;   &lt;span class="n"&gt;InpOffsetPoints&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// points beyond entry to lock in, covers costs&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;InpMagic&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20261025&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;PointsToPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SymbolInfoDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SYMBOL_POINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// pts times point&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;RoundToTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SymbolInfoDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SYMBOL_TRADE_TICK_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SYMBOL_DIGITS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NormalizeDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NormalizeDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MathRound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Respect the stops level and the freeze level
&lt;/h1&gt;

&lt;p&gt;Two broker settings decide whether a modify request is even allowed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SYMBOL_TRADE_STOPS_LEVEL is the minimum distance in points between the current price and a new stop. Place the stop closer and the request is rejected with invalid stops.&lt;/li&gt;
&lt;li&gt;SYMBOL_TRADE_FREEZE_LEVEL forbids modifying a position whose existing stop loss or take profit is already within that distance of the current price. The position is frozen until price moves away.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Checking both before sending saves a rejected request and a noisy log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ModifyAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;newSl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;curSl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;curTp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MqlTick&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;stopsDist&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PointsToPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SYMBOL_TRADE_STOPS_LEVEL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;freezeDist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PointsToPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SYMBOL_TRADE_FREEZE_LEVEL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;POSITION_TYPE_BUY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// the new stop must be far enough from the closing price&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MathAbs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;newSl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;stopsDist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;// existing levels inside the freeze distance block any modify&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;freezeDist&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curSl&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;MathAbs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;curSl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;freezeDist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curTp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;MathAbs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;curTp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;freezeDist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The breakeven routine
&lt;/h1&gt;

&lt;p&gt;The loop walks backwards through positions, skips anything that does not carry the EA's magic number, and only sends a modify when the stop would actually improve. That last check is what stops the journal from filling with TRADE_RETCODE_NO_CHANGES on every tick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ManageBreakeven&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionsTotal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PositionGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_MAGIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;InpMagic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;long&lt;/span&gt;   &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_TYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_PRICE_OPEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_SL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_TP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;MqlTick&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PointsToPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InpTriggerPoints&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PointsToPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InpOffsetPoints&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;POSITION_TYPE_BUY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                              &lt;span class="c1"&gt;// not far enough in profit&lt;/span&gt;
         &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RoundToTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                              &lt;span class="c1"&gt;// already at or past breakeven&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RoundToTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ModifyAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                 &lt;span class="c1"&gt;// try again on a later tick&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositionModify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Breakeven modify failed on %I64u: %u %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultRetcode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultRetcodeDescription&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
         &lt;span class="nf"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Breakeven set on %I64u at %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DoubleToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Digits&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnTick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;ManageBreakeven&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details worth calling out.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The take profit is passed back unchanged. PositionModify sets both levels, so sending 0 for the take profit would silently remove it.&lt;/li&gt;
&lt;li&gt;The comparison against the existing stop works in one direction only. The stop can move toward profit, never back toward risk.&lt;/li&gt;
&lt;li&gt;If the trigger is smaller than the offset plus the stops level, the routine will keep finding the move not allowed. Keep the trigger comfortably larger than the offset.&lt;/li&gt;
&lt;li&gt;PrintFormat uses _Digits for display only. If the EA trades several symbols, read SYMBOL_DIGITS for each one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Choosing the offset
&lt;/h1&gt;

&lt;p&gt;A reasonable starting point is the commission for a round trip expressed in points, plus a few points for slippage. On a raw spread account charging a fixed commission per lot, work out how many points of price movement that commission equals for your typical lot size, and use that as the floor. On a commission free account the offset can be small, but rarely zero.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing it
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Run the Strategy Tester in visual mode on a volatile day and watch the stop jump exactly once per position.&lt;/li&gt;
&lt;li&gt;Check the journal for repeated modify errors. One or two invalid stops messages near news are normal; hundreds mean a check is missing.&lt;/li&gt;
&lt;li&gt;Test a sell as carefully as a buy. Most breakeven bugs live in the side the developer tested less.&lt;/li&gt;
&lt;li&gt;Try a symbol with a nonzero freeze level, or temporarily force one in code, to make sure frozen positions are skipped instead of retried in a tight loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Breakeven sounds like one line of logic. Done properly it is a small set of rules about prices, distances and costs, and getting them right is what makes the feature trustworthy.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Closing every position your MQL5 EA owns without skipping any</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:30:13 +0000</pubDate>
      <link>https://dev.to/omaisali/closing-every-position-your-mql5-ea-owns-without-skipping-any-2jf6</link>
      <guid>https://dev.to/omaisali/closing-every-position-your-mql5-ea-owns-without-skipping-any-2jf6</guid>
      <description>&lt;p&gt;Sooner or later every Expert Advisor needs a "close everything" routine. A daily loss limit is hit, the weekend is coming, or the trader presses a panic button. It sounds like five lines of code, and that is exactly why it so often ships with bugs that only show up when real money is on the line.&lt;/p&gt;

&lt;p&gt;This tutorial builds a close routine that does not skip positions, only touches positions the EA owns, retries sensibly and tells you when something is left open.&lt;/p&gt;

&lt;h1&gt;
  
  
  The classic bug: looping forward while closing
&lt;/h1&gt;

&lt;p&gt;Here is the version many people write first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;PositionsTotal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositionClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Say there are four positions. The loop closes the one at index 0. The list shrinks, so the position that was at index 1 is now at index 0. The loop moves on to index 1 and closes what used to be index 2. You end up closing roughly half of the positions and leaving the rest open, with no error at all.&lt;/p&gt;

&lt;p&gt;A common fix is to count down from the last index. That works, but there is a cleaner pattern that also helps with retries: take a snapshot of the tickets first, then close them one by one by ticket.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: collect the tickets this EA owns
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Trade\Trade.mqh&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;InpMagic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20261024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;CTrade&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;CollectMyTickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;ArrayResize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionsTotal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// also selects the position&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PositionGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_MAGIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;InpMagic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                           &lt;span class="c1"&gt;// not ours, leave it alone&lt;/span&gt;
      &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArraySize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;ArrayResize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ArraySize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is closed inside this loop, so the index shifting problem cannot happen. The magic number filter matters: a trader may run several EAs or trade manually on the same account, and a panic button that closes someone else's positions is its own kind of disaster. Add a symbol filter here too if the EA should only manage its own chart symbol.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: decide which errors are worth retrying
&lt;/h1&gt;

&lt;p&gt;Not every failure deserves another attempt. A requote or a moved price usually succeeds a moment later. A closed market or disabled trading will not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_REQUOTE&lt;/span&gt;       &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_PRICE_CHANGED&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_PRICE_OFF&lt;/span&gt;     &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_TIMEOUT&lt;/span&gt;       &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 3: close one ticket with limited retries
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CloseTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PositionSelectByTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// already closed, by us or by a stop&lt;/span&gt;

      &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PositionGetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTypeFillingBySymbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositionClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultRetcode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_DONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TRADE_RETCODE_DONE_PARTIAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                           &lt;span class="c1"&gt;// remainder is still open, go again&lt;/span&gt;

      &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Close %I64u attempt %d failed: %u %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultRetcodeDescription&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;IsRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PositionSelectByTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details are doing real work here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The position is selected again on every attempt. Between attempts it may have been closed by its own stop loss or take profit, and trying to close a ticket that no longer exists only produces confusing errors.&lt;/li&gt;
&lt;li&gt;CTrade reads a fresh price on every PositionClose call, so a retry after a requote uses the current Bid or Ask rather than the stale one.&lt;/li&gt;
&lt;li&gt;The filling mode is set per symbol, because a multi symbol EA can hold positions on symbols with different filling rules.&lt;/li&gt;
&lt;li&gt;A partial fill does not count as success. The loop goes around again and closes whatever volume is left.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Step 4: close everything and report what is left
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;CloseAllMine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CollectMyTickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CloseTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CloseAllMine: %d of %d positions still open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetExpertMagicNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InpMagic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetDeviationInPoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;INIT_SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return value is the important part. The caller now knows whether the account is actually flat. A daily loss guard, for example, should keep blocking new entries and try again on the next tick or timer event if anything is still open, instead of assuming the job is done.&lt;/p&gt;

&lt;h1&gt;
  
  
  Do not forget pending orders
&lt;/h1&gt;

&lt;p&gt;Closing positions while leaving pending orders in place often means the EA is back in the market a few minutes later. Use the same snapshot idea with OrdersTotal, OrderGetTicket and ORDER_MAGIC, and remove each one with trade.OrderDelete(ticket). Usually it is best to delete pending orders first, so nothing new can fill while you are closing.&lt;/p&gt;

&lt;h1&gt;
  
  
  A note on hedging accounts
&lt;/h1&gt;

&lt;p&gt;On a hedging account, if the EA holds a buy and a sell of the same symbol, trade.PositionCloseBy(ticket, oppositeTicket) closes one against the other and saves paying the spread twice. It is an optimisation, not a requirement, but worth knowing when an EA builds hedged baskets.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to test it
&lt;/h1&gt;

&lt;p&gt;On a demo account, open four or five positions with the EA's magic number and one manual position. Run the routine and check that every EA position is closed and the manual one is untouched. Then run it again when the market is closed and confirm that it stops quickly, logs TRADE_RETCODE_MARKET_CLOSED and reports the positions as still open rather than retrying forever.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Detecting stale quotes in an MQL5 Expert Advisor before you trade on them</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Fri, 23 Oct 2026 06:22:22 +0000</pubDate>
      <link>https://dev.to/omaisali/detecting-stale-quotes-in-an-mql5-expert-advisor-before-you-trade-on-them-435h</link>
      <guid>https://dev.to/omaisali/detecting-stale-quotes-in-an-mql5-expert-advisor-before-you-trade-on-them-435h</guid>
      <description>&lt;p&gt;An Expert Advisor only ever sees the last price the terminal received. Most of the time that is fine. But when the feed freezes, the market closes for the night, or the symbol has no liquidity for a few minutes, your EA keeps reading the same old Bid and Ask and happily calculates signals and stop distances from a price that no longer exists.&lt;/p&gt;

&lt;p&gt;This article shows a small, reusable check that tells your EA how old the current quote is and whether it is safe to act on.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where the quote time comes from
&lt;/h1&gt;

&lt;p&gt;SymbolInfoTick fills an MqlTick structure. Its time field is the server time of the last price update, and time_msc gives the same moment in milliseconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;MqlTick&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SymbolInfoTick failed, error "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GetLastError&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Last quote at "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TIME_DATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;TIME_SECONDS&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To know how old that quote is, compare it with the current trade server time. TimeTradeServer is estimated from your local clock and the known server offset, so it keeps moving even when no ticks arrive. That is exactly what we need.&lt;/p&gt;

&lt;h1&gt;
  
  
  A quote age function
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Returns the age of the last quote in seconds, or a negative value on failure.&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;QuoteAgeSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;MqlTick&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SymbolInfoTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;TimeTradeServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use TimeCurrent for this. TimeCurrent returns the time of the last tick received for any symbol in Market Watch, so on a frozen feed it can freeze too, and the age will always look close to zero.&lt;/p&gt;

&lt;h1&gt;
  
  
  Is the market actually open?
&lt;/h1&gt;

&lt;p&gt;A quote that is two hours old on Saturday is normal. The same age on Tuesday afternoon is a problem. Before raising an alarm, check whether the symbol has a trade session right now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;InTradeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;serverNow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeTradeServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="n"&gt;MqlDateTime&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;TimeToStruct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;secondsToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serverNow&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// seconds since midnight, server time&lt;/span&gt;

   &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;SymbolInfoSessionTrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENUM_DAY_OF_WEEK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day_of_week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secondsToday&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;secondsToday&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SymbolInfoSessionTrade returns the session start and end as seconds from the start of the day, and you loop over the session index until it returns false. Some symbols have two or three sessions per day with a break in between.&lt;/p&gt;

&lt;h1&gt;
  
  
  Putting it together
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;InpMaxQuoteAgeSec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// treat quotes older than this as stale&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;QuoteIsFresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;InTradeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="c1"&gt;// market closed, do not trade&lt;/span&gt;

   &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QuoteAgeSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="c1"&gt;// could not read the tick&lt;/span&gt;

   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;InpMaxQuoteAgeSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;lastWarn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeTradeServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastWarn&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s quote is %d seconds old, skipping signals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="n"&gt;lastWarn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeTradeServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnTick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;QuoteIsFresh&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="c1"&gt;// signal and order logic here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warning is rate limited so a long freeze does not flood the Experts log.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why OnTick is not enough on its own
&lt;/h1&gt;

&lt;p&gt;If the feed is completely dead, OnTick will not fire at all, so the check above never runs. For EAs that manage open positions, also run the same check from OnTimer and log or alert when the quote has been stale for longer than your threshold during a trading session. That way you hear about a frozen feed while it is happening, not the next morning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;EventSetTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;INIT_SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InTradeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;QuoteAgeSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;InpMaxQuoteAgeSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Feed looks frozen on "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Symbol&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDeinit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;EventKillTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Picking the threshold
&lt;/h1&gt;

&lt;p&gt;There is no single right number. Major forex pairs tick several times a second during the London and New York sessions, so 30 seconds without a quote is already suspicious. An exotic pair or a thin CFD during the Asian session can legitimately go a minute or more without a tick. Measure the normal gaps on your broker for the symbols you trade and set the threshold a few times above that.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing it
&lt;/h1&gt;

&lt;p&gt;The Strategy Tester generates ticks continuously, so you will rarely see a stale quote there. To test the logic, run the EA on a demo chart for a symbol whose session ends while you watch, or temporarily lower InpMaxQuoteAgeSec to a few seconds on a quiet symbol and confirm the skip message appears.&lt;/p&gt;

&lt;p&gt;A stale quote check is a few dozen lines of code, and it prevents a whole class of strange trades that are very hard to explain after the fact.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Knowing why a position closed in MQL5 with OnTradeTransaction</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Thu, 22 Oct 2026 06:21:27 +0000</pubDate>
      <link>https://dev.to/omaisali/knowing-why-a-position-closed-in-mql5-with-ontradetransaction-5fkf</link>
      <guid>https://dev.to/omaisali/knowing-why-a-position-closed-in-mql5-with-ontradetransaction-5fkf</guid>
      <description>&lt;p&gt;A common question in Expert Advisor code is "did my trade just hit its stop loss, its take profit, or did someone close it by hand?" Many EAs answer it by comparing the list of open positions on every tick and guessing. That works until two things happen on the same tick, or the EA restarts in the middle.&lt;/p&gt;

&lt;p&gt;MQL5 gives you a better tool: &lt;code&gt;OnTradeTransaction&lt;/code&gt;. The terminal calls it for every change on the account, and once a deal is added to history you can read exactly why it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The event you care about
&lt;/h2&gt;

&lt;p&gt;A trade request produces several transactions: the order is added, updated, removed, a deal is added, the position changes. For "why did this position close", the one to watch is &lt;code&gt;TRADE_TRANSACTION_DEAL_ADD&lt;/code&gt;. A deal is the actual execution, and it carries a reason code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;InpMagic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;240101&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// the magic number this EA trades with&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnTradeTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MqlTradeTransaction&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MqlTradeRequest&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MqlTradeResult&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;TRADE_TRANSACTION_DEAL_ADD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;deal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;HistoryDealSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Deal %I64u not in history yet, will reconcile later"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HistoryDealGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_MAGIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;InpMagic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="n"&gt;ENUM_DEAL_ENTRY&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENUM_DEAL_ENTRY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;HistoryDealGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_ENTRY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;DEAL_ENTRY_OUT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;DEAL_ENTRY_OUT_BY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;DEAL_ENTRY_INOUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// an opening deal, not a close&lt;/span&gt;

   &lt;span class="n"&gt;ENUM_DEAL_REASON&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENUM_DEAL_REASON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;HistoryDealGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;ulong&lt;/span&gt;  &lt;span class="n"&gt;positionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_POSITION_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_PROFIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_SWAP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;HistoryDealGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_COMMISSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Position %I64u closed by %s, volume %.2f, net %.2f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;positionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReasonText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
               &lt;span class="n"&gt;HistoryDealGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEAL_VOLUME&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="n"&gt;OnPositionClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes on that code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The magic number filter matters. Without it, a manual trade or another EA on the same account will run through your logic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEAL_ENTRY_OUT_BY&lt;/code&gt; covers close by an opposite position, and &lt;code&gt;DEAL_ENTRY_INOUT&lt;/code&gt; covers a reversal on netting accounts, where one deal closes and opens in the same step.&lt;/li&gt;
&lt;li&gt;Net result is profit plus swap plus commission. Using &lt;code&gt;DEAL_PROFIT&lt;/code&gt; alone makes a scratch trade look like a small win.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Turning the reason code into something readable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReasonText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENUM_DEAL_REASON&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_SL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"stop loss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_TP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"take profit"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_SO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"stop out"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_EXPERT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"expert advisor"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_CLIENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"manual (desktop)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_MOBILE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"manual (mobile)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;DEAL_REASON_WEB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"manual (web)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default:&lt;/span&gt;                 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"other ("&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;IntegerToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;")"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the event pays for itself. Once you know the reason, the EA can behave differently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;After a stop loss, start a cooldown before the next entry.&lt;/li&gt;
&lt;li&gt;After a take profit, allow the next signal as normal.&lt;/li&gt;
&lt;li&gt;After a stop out, stop trading completely and alert, because the risk model has already failed.&lt;/li&gt;
&lt;li&gt;After a manual close, respect it. Do not reopen the same trade on the next tick just because the signal is still true.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is the bug I see most often. The trader closes a position on their phone, and the EA immediately opens it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial closes
&lt;/h2&gt;

&lt;p&gt;If a position is partly closed, you still get a &lt;code&gt;DEAL_ENTRY_OUT&lt;/code&gt; deal, but the position stays open with less volume. Check whether the position still exists before you treat it as finished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnPositionClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ulong&lt;/span&gt; &lt;span class="n"&gt;positionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ENUM_DEAL_REASON&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PositionSelectByTicket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positionId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;PrintFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Position %I64u partly closed, %.2f lots remain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;positionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PositionGetDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POSITION_VOLUME&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="c1"&gt;// fully closed: update stats, cooldowns, daily loss counters here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On hedging accounts the position ticket equals the position identifier, so &lt;code&gt;PositionSelectByTicket&lt;/code&gt; works with the value from &lt;code&gt;DEAL_POSITION_ID&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits you must design around
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;OnTradeTransaction&lt;/code&gt; is not a guaranteed delivery system, and two cases break EAs that trust it blindly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The EA was not running. If the terminal was closed, or the EA was removed and attached again, every close in that time happened without an event. On startup, read recent history with &lt;code&gt;HistorySelect&lt;/code&gt; and rebuild your state from deals instead of from memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The queue can overflow. Transactions are queued for the EA, and if the EA is busy for a long time, old events can be dropped. Keep the handler short, and do heavy work elsewhere.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the pattern that holds up is: react to events for speed, and reconcile from history on start and every few minutes for correctness. If the two disagree, history wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;The Strategy Tester calls &lt;code&gt;OnTradeTransaction&lt;/code&gt; too, so stop loss and take profit reasons can be checked there. Manual reasons cannot, so test those on a demo account: open a position with the EA, close it from the mobile app, and confirm the journal shows "manual (mobile)" and that the EA does not reopen it.&lt;/p&gt;

&lt;p&gt;Once the EA knows why each trade ended, a lot of guesswork in the rest of the code goes away.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why your MQL5 EA reads zero or garbage from indicators on the first ticks</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:22:35 +0000</pubDate>
      <link>https://dev.to/omaisali/why-your-mql5-ea-reads-zero-or-garbage-from-indicators-on-the-first-ticks-602</link>
      <guid>https://dev.to/omaisali/why-your-mql5-ea-reads-zero-or-garbage-from-indicators-on-the-first-ticks-602</guid>
      <description>&lt;p&gt;A bug I see in many MT5 Expert Advisors looks like a strategy problem at first. The EA opens a trade right after it is attached to a chart, or right after the terminal restarts, and the entry makes no sense. The journal shows a moving average of 0, or a value like 1.7976931348623157e+308. A few minutes later everything works normally.&lt;/p&gt;

&lt;p&gt;The cause is almost always the way indicator data is read. In MQL5, indicators are not functions that return a value. They are separate calculation objects that you create once and read from later, and they need time to calculate. Here is how I structure indicator access in every EA now.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create handles once, in OnInit
&lt;/h2&gt;

&lt;p&gt;Functions like &lt;code&gt;iMA&lt;/code&gt;, &lt;code&gt;iRSI&lt;/code&gt; and &lt;code&gt;iATR&lt;/code&gt; return a handle, not a value. The terminal starts calculating the indicator in the background when you create it.&lt;/p&gt;

&lt;p&gt;If you call &lt;code&gt;iMA&lt;/code&gt; inside &lt;code&gt;OnTick&lt;/code&gt;, the terminal usually hands back the same handle for identical parameters, so it may look harmless, but you pay a lookup on every tick, you make it easy to leak handles when parameters change, and you hide the fact that the first read after creation can fail. Create every handle in &lt;code&gt;OnInit&lt;/code&gt;, check it, and fail the initialization if it is invalid. An EA that cannot build its indicators should not run at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check that the data is ready before reading it
&lt;/h2&gt;

&lt;p&gt;Right after creation, the indicator may not have calculated any bars yet. &lt;code&gt;BarsCalculated(handle)&lt;/code&gt; returns how many bars are ready, or a negative value while it is still working. &lt;code&gt;CopyBuffer&lt;/code&gt; will return a negative value or fewer values than you asked for, and &lt;code&gt;GetLastError&lt;/code&gt; often reports 4806 (&lt;code&gt;ERR_INDICATOR_DATA_NOT_FOUND&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The wrong reaction is to ignore the return value and use whatever is in the array. The array then holds zeros from initialization or leftovers from a previous tick, and your crossover logic happily compares them.&lt;/p&gt;

&lt;p&gt;The right reaction is to treat "not ready" as a normal state: skip this tick and try again on the next one.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Read closed bars, and know your array direction
&lt;/h2&gt;

&lt;p&gt;Bar 0 is the bar that is still forming. Its indicator value changes with every tick, so a signal on bar 0 can appear and vanish within the same minute. For most strategies, read from bar 1, the last closed bar.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CopyBuffer&lt;/code&gt; fills the array oldest first unless the array is set as a series. I always call &lt;code&gt;ArraySetAsSeries(array, true)&lt;/code&gt; before copying, so index 0 means the most recent value I requested. Mixing the two conventions is a classic source of signals that fire one bar late or in the wrong direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Handle empty values
&lt;/h2&gt;

&lt;p&gt;Some indicators, especially custom ones that draw arrows or gaps, write &lt;code&gt;EMPTY_VALUE&lt;/code&gt; on bars where they have nothing to say. That is the huge number from the journal above. Compare against &lt;code&gt;EMPTY_VALUE&lt;/code&gt; before using a value, otherwise a stop loss or a threshold check can be calculated from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Release handles in OnDeinit
&lt;/h2&gt;

&lt;p&gt;Call &lt;code&gt;IndicatorRelease&lt;/code&gt; for every handle when the EA is removed or reinitialized. It keeps the terminal clean when you switch timeframes or change inputs during testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input int InpFastPeriod = 20;
input int InpSlowPeriod = 50;

int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;

int OnInit()
{
   fastHandle = iMA(_Symbol, PERIOD_CURRENT, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowHandle = iMA(_Symbol, PERIOD_CURRENT, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);

   if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
   {
      Print("Could not create indicator handles, error ", GetLastError());
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
   if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}

// Copies 'count' closed bar values (starting at bar 1) into out[].
// out[0] is the last closed bar. Returns false if data is not ready.
bool ReadClosedBars(const int handle, const int count, double &amp;amp;out[])
{
   int ready = BarsCalculated(handle);
   if(ready &amp;lt; count + 1)
      return false;

   ArraySetAsSeries(out, true);
   int copied = CopyBuffer(handle, 0, 1, count, out);
   if(copied != count)
   {
      PrintFormat("CopyBuffer returned %d of %d values, error %d",
                  copied, count, GetLastError());
      return false;
   }

   for(int i = 0; i &amp;lt; count; i++)
      if(out[i] == EMPTY_VALUE || out[i] == 0.0)
         return false;

   return true;
}

void OnTick()
{
   double fast[], slow[];

   if(!ReadClosedBars(fastHandle, 2, fast) ||
      !ReadClosedBars(slowHandle, 2, slow))
      return;   // indicator not ready yet, try on the next tick

   bool crossUp   = fast[1] &amp;lt;= slow[1] &amp;amp;&amp;amp; fast[0] &amp;gt; slow[0];
   bool crossDown = fast[1] &amp;gt;= slow[1] &amp;amp;&amp;amp; fast[0] &amp;lt; slow[0];

   if(crossUp)
      Print("Fast EMA crossed above slow EMA on the last closed bar");
   if(crossDown)
      Print("Fast EMA crossed below slow EMA on the last closed bar");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zero check in the loop is a deliberate choice for a moving average, which can never be zero on a real price series. For an oscillator where zero is a legitimate value, remove it and rely on &lt;code&gt;BarsCalculated&lt;/code&gt; and the copied count.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on other timeframes and symbols
&lt;/h2&gt;

&lt;p&gt;If the handle is created on another timeframe or symbol, that history may not be loaded yet when the EA starts. &lt;code&gt;BarsCalculated&lt;/code&gt; still protects you, but it can take longer. I also check &lt;code&gt;SeriesInfoInteger(symbol, timeframe, SERIES_SYNCHRONIZED)&lt;/code&gt; before trusting a higher timeframe value, and log when the EA is waiting, so "no trades for the first two minutes" is explained in the journal instead of looking like a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;The Strategy Tester rarely shows this problem, because history is prepared before the test starts. To reproduce it, attach the EA to a chart of a symbol you have not opened recently, or restart the terminal with the EA running, and watch the first lines of the Experts tab. With the guard in place you should see the EA wait rather than act on empty data.&lt;/p&gt;

&lt;p&gt;Most "random bad trades after a restart" I have traced came down to one unchecked &lt;code&gt;CopyBuffer&lt;/code&gt; call. It is a few lines of code to fix, and it removes a whole class of bugs that only appear on live charts.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why your multi symbol MQL5 EA misses trades on every symbol except the chart one</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Tue, 20 Oct 2026 06:17:17 +0000</pubDate>
      <link>https://dev.to/omaisali/why-your-multi-symbol-mql5-ea-misses-trades-on-every-symbol-except-the-chart-one-1nh9</link>
      <guid>https://dev.to/omaisali/why-your-multi-symbol-mql5-ea-misses-trades-on-every-symbol-except-the-chart-one-1nh9</guid>
      <description>&lt;p&gt;A common request I get is "one EA, attached to one chart, trading eight pairs". It is a good design, because one EA is easier to manage and can control total risk in one place. But the first version almost always has the same bug: it trades the chart symbol fine and misses or delays signals on the others.&lt;/p&gt;

&lt;p&gt;Here is why, and how I structure these EAs now.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. OnTick only fires for the chart symbol
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;OnTick&lt;/code&gt; runs when the symbol of the chart the EA is attached to gets a new tick. If the EA sits on EURUSD and GBPJPY moves, nothing happens until EURUSD ticks again. In a quiet EURUSD hour that can be many seconds.&lt;/p&gt;

&lt;p&gt;The fix is to drive the logic from a timer instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string Symbols[] = {"EURUSD", "GBPJPY", "XAUUSD"};

int OnInit()
{
   for(int i = 0; i &amp;lt; ArraySize(Symbols); i++)
      if(!SymbolSelect(Symbols[i], true))
         Print("Cannot select ", Symbols[i]);
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}

void OnTimer()
{
   for(int i = 0; i &amp;lt; ArraySize(Symbols); i++)
      ProcessSymbol(Symbols[i]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SymbolSelect&lt;/code&gt; matters too. A symbol that is not in Market Watch does not receive quotes, so the EA reads stale prices without any error.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. History for other symbols may not be ready
&lt;/h2&gt;

&lt;p&gt;For the chart symbol, the terminal keeps history loaded. For the others, the first &lt;code&gt;CopyRates&lt;/code&gt; call can return fewer bars than you asked for, or fail, while the terminal downloads data. Check it before using the numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool DataReady(const string sym, ENUM_TIMEFRAMES tf, int need)
{
   if(!SeriesInfoInteger(sym, tf, SERIES_SYNCHRONIZED))
      return false;
   MqlRates r[];
   return CopyRates(sym, tf, 0, need, r) == need;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it returns false, skip that symbol this cycle and try again on the next timer event. Do not fall back to zeros.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. New bar detection has to be per symbol
&lt;/h2&gt;

&lt;p&gt;A single &lt;code&gt;lastBar&lt;/code&gt; variable is the classic mistake. Keep one per symbol and timeframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datetime LastBar[];

bool IsNewBar(int idx, const string sym, ENUM_TIMEFRAMES tf)
{
   datetime t = iTime(sym, tf, 0);
   if(t == 0) return false;
   if(t == LastBar[idx]) return false;
   LastBar[idx] = t;
   return true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resize &lt;code&gt;LastBar&lt;/code&gt; to the number of symbols in &lt;code&gt;OnInit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Symbol properties differ, so read them per symbol
&lt;/h2&gt;

&lt;p&gt;Gold, an index and a forex pair have different digits, tick sizes, minimum lots and stops levels. Any code that uses &lt;code&gt;_Point&lt;/code&gt;, &lt;code&gt;_Digits&lt;/code&gt; or &lt;code&gt;Symbol()&lt;/code&gt; inside the loop is reading the chart symbol, not the one being traded. Use &lt;code&gt;SymbolInfoDouble(sym, SYMBOL_POINT)&lt;/code&gt;, &lt;code&gt;SYMBOL_VOLUME_MIN&lt;/code&gt;, &lt;code&gt;SYMBOL_VOLUME_STEP&lt;/code&gt; and friends for every calculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Control risk across all symbols, not per symbol
&lt;/h2&gt;

&lt;p&gt;The advantage of one EA is that it can see total exposure. Before opening a new position, sum the risk of everything the EA already holds (by magic number) and refuse the trade if the total would cross your limit. Correlated pairs like EURUSD and GBPUSD can turn three "small" trades into one big one.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test it like it will run
&lt;/h2&gt;

&lt;p&gt;The Strategy Tester does support multi symbol EAs, but only the symbols you actually request get history. Run at least one test in "Every tick based on real ticks" mode, and compare the trade list per symbol with what you expect from the chart. Missing trades on the non chart symbols are the first sign that one of the issues above is still there.&lt;/p&gt;

&lt;p&gt;Built this way, one EA can handle a whole basket reliably. Built the quick way, it trades one pair well and the rest by luck.&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Detecting a new bar in MQL5 without missing one or firing twice</title>
      <dc:creator>Omais Ali</dc:creator>
      <pubDate>Mon, 19 Oct 2026 06:17:13 +0000</pubDate>
      <link>https://dev.to/omaisali/detecting-a-new-bar-in-mql5-without-missing-one-or-firing-twice-2bim</link>
      <guid>https://dev.to/omaisali/detecting-a-new-bar-in-mql5-without-missing-one-or-firing-twice-2bim</guid>
      <description>&lt;p&gt;Most Expert Advisors make their decisions once per bar, not on every tick. That sounds simple, and the usual snippet looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datetime lastBar = 0;

void OnTick()
{
   datetime current = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(current == lastBar) return;
   lastBar = current;
   // new bar logic
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works on a chart in a quiet market. It breaks in four situations I see again and again in client code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. iTime can return 0
&lt;/h2&gt;

&lt;p&gt;Right after the terminal starts, or when history for the symbol is still loading, &lt;code&gt;iTime&lt;/code&gt; returns 0. The code above then stores 0, and on the next tick it sees a "new bar" that is really just the data arriving. Guard against it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datetime current = iTime(_Symbol, PERIOD_CURRENT, 0);
if(current == 0) return;   // no data yet, try again on the next tick
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The first tick after attaching
&lt;/h2&gt;

&lt;p&gt;When you attach the EA, &lt;code&gt;lastBar&lt;/code&gt; is 0, so the very first tick counts as a new bar even if the bar is already half finished. If your entry logic assumes it is running at the open of a bar, it will trade on a partial candle. Decide what you want explicitly. Usually that means recording the current bar in &lt;code&gt;OnInit&lt;/code&gt; and waiting for the next one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int OnInit()
{
   lastBar = iTime(_Symbol, PERIOD_CURRENT, 0);
   return(INIT_SUCCEEDED);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Gaps and quiet symbols
&lt;/h2&gt;

&lt;p&gt;New bar logic is driven by ticks. If a symbol has no ticks for twenty minutes, an M5 EA skips several bars and then wakes up once. Code that counts bars ("exit after 6 bars") will be wrong. Count time instead, by comparing the bar open time of the entry with the current one using &lt;code&gt;iBarShift&lt;/code&gt;, so skipped bars are still counted.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Multiple timeframes
&lt;/h2&gt;

&lt;p&gt;An EA that trades M15 but filters on H1 often reuses one &lt;code&gt;lastBar&lt;/code&gt; variable for both, or checks H1 with &lt;code&gt;iTime&lt;/code&gt; on every M15 bar and assumes it changed. Keep one tracker per timeframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CNewBar
{
   ENUM_TIMEFRAMES m_tf;
   datetime        m_last;
public:
   CNewBar(ENUM_TIMEFRAMES tf) : m_tf(tf), m_last(0) {}
   bool Check()
   {
      datetime t = iTime(_Symbol, m_tf, 0);
      if(t == 0 || t == m_last) return false;
      bool first = (m_last == 0);
      m_last = t;
      return !first;   // the first call only primes the tracker
   }
};

CNewBar barM15(PERIOD_M15), barH1(PERIOD_H1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;Run the EA in the Strategy Tester with "Every tick based on real ticks" on a symbol with weekend gaps, and print a line every time &lt;code&gt;Check()&lt;/code&gt; returns true. Then count the lines against the number of bars in the test window. If the numbers do not match, you have found the bug before your account did.&lt;/p&gt;

&lt;p&gt;How do you handle new bar detection in your own EAs?&lt;/p&gt;

</description>
      <category>mql5</category>
      <category>programming</category>
      <category>algotrading</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
