Master MT5 MQL5 Profiler: Find Slow Code in EAs

Learn to run the MQL5 profiler in MetaEditor, interpret exclusive vs inclusive time, and fix bottlenecks in your Expert Advisor. Step-by-step with real code.

master-mt5-mql5-profiler-find-slow-code-in-eas

You've built an Expert Advisor that works perfectly in the Strategy Tester, but when you run it on a live chart with multiple currency pairs, the terminal starts lagging. Ticks get missed. The EA falls behind. You suspect the code is the bottleneck, but you're not sure where.

The MQL5 profiler in MetaEditor is the tool for this exact situation. It measures how long every function in your program actually takes to execute, showing you call counts, exclusive time (time spent inside the function itself), and inclusive time (time including everything it calls). With that data, you stop guessing and start fixing the real problem lines.

This guide walks you through running the profiler, reading its output, and acting on what you find. I'll cover the specific menus, the compiler switch you need to enable, and the common traps that make your EA slow. By the end, you'll know how to turn a sluggish 500ms-per-tick EA into something that runs in under 50ms.

What You'll Need Before Starting

The profiler is only available in MetaTrader 5 (not MT4) and requires MetaEditor build 2000 or later. If you're still on MT4, you're out of luck — the MT4 compiler has no profiling support. You'll need to upgrade to MT5 or use manual timing with GetTickCount(). I've seen traders try to hack around this with external profilers, but it's never worth the effort; just switch to MT5 for development.

You also need an MQL5 project that compiles cleanly — no syntax errors, no unresolved external functions. The profiler instruments your compiled code, so any build errors will prevent it from running. I recommend using a separate copy of your EA or indicator for profiling, not your live-trading version, because the profiling instrumentation adds overhead that changes timing behavior slightly. On my own EAs, I keep a "dev" copy with #property version bumped and profiling enabled, and a separate "live" copy without profiling.

Make sure your EA doesn't rely on external DLLs or custom includes that aren't available during profiling. The profiler hooks into every function call, including those from included files, but if a DLL function isn't instrumentable, you'll get incomplete results.

Step-by-Step: Running the MQL5 Profiler

Step 1: Enable Profiling in the Compiler

Open MetaEditor (Tools > MetaQuotes Language Editor in MT5, or press F4). Load the MQL5 project you want to profile — typically an EA or custom indicator file with a .mq5 extension. Don't try to profile a script; scripts run once and the profiler won't capture meaningful data.

Go to Tools > Options in MetaEditor (not the MT5 terminal options). Click the Compiler tab. You'll see a checkbox labeled "Generate profiling information". Check it. This tells the compiler to insert instrumentation hooks into every function entry and exit point.

Click OK. Now rebuild your program: File > Rebuild All (or press F7). The output window should show "generating profiling information" during compilation. If you don't see that message, the checkbox didn't take effect — double-check the option. I've had cases where MetaEditor needed a restart after enabling the option; if you don't see the message, close and reopen MetaEditor, then rebuild.

One nuance: the checkbox is per-workspace, not global. If you switch between different EAs, you need to enable profiling for each one separately. That's actually convenient — you won't accidentally profile your live EA.

Step 2: Run Your EA or Indicator in the Strategy Tester

Close MetaEditor and go to the MT5 terminal. Open the Strategy Tester (Ctrl+R). Select your EA or indicator from the Expert Advisor dropdown. Choose a symbol and timeframe you typically trade — don't use a random 1-minute pair if your EA runs on H1. The profiler data is only meaningful when you test under realistic conditions.

Set the testing period. For profiling, a shorter period (say 3–6 months on a daily timeframe) is usually enough to expose the hot functions. Running 10 years of tick data just makes the profiling file huge and slow to analyze. I usually run 3 months of H1 data for a day-trading EA, or 6 months of D1 for a swing trader. That's enough to trigger all code paths without drowning in data.

Important: Enable "Visual Mode" in the tester settings. The profiler only captures data when the tester runs visually. I know it sounds odd, but that's how MetaTrader works — the profiler hooks into the visual tick-processing loop. Without visual mode, you get zero profiling output. You'll find this checkbox under the "Settings" tab in the tester, labeled "Visual Mode". Check it before starting.

Click Start. Let the test run for at least a few hundred ticks or until you see the EA execute a reasonable number of trades. You don't need to complete the entire test — 500–1000 ticks is usually sufficient to spot bottlenecks. For a scalper EA that trades every few ticks, 200 ticks might be enough. For a daily swing trader, you might need 5000 ticks to see the EA open a few positions.

Step 3: Stop the Test and Open the Profiler Report

Once the test has run long enough, close the Strategy Tester visual window (the chart with the EA). The tester will stop. Now switch back to MetaEditor. Go to View > Profiling Results. A new docked window appears, usually at the bottom of MetaEditor, showing a table with columns: Function, Calls, Exclusive Time (ms), Inclusive Time (ms), and Average Time (ms).

This is your profiling report. It lists every function that was called during the test, sorted by exclusive time by default — the most expensive functions at the top. You can click any column header to re-sort. I often sort by "Calls" first to see what's running most frequently, then switch to "Exclusive Time" to find the real CPU hogs.

If the window is empty, don't panic. Check that you ran the test in visual mode and that the test actually executed some code. If the EA never entered OnTick() (e.g., because of a time filter), you'll see nothing.

Step 4: Interpret the Columns

ColumnWhat It MeasuresWhy It Matters
FunctionName of the function or event handler (e.g., OnTick, CalculateIndicators, OrderSend)Tells you which code unit to examine. Library functions like CopyBuffer also appear.
CallsNumber of times the function was invoked during the testA function called 10,000 times with 0.1ms per call adds up to 1 second total. High call count with low per-call cost is still a problem.
Exclusive Time (ms)Total milliseconds spent inside this function, excluding time in any functions it callsThe pure cost of this function's own logic. High exclusive time means the function itself is the bottleneck.
Inclusive Time (ms)Total milliseconds including all sub-functions called from this oneIf inclusive time is high but exclusive is low, the bottleneck is in a child function. Drill down into the call tree.
Average Time (ms)Average time per call (exclusive time / calls)Useful for comparing per-call cost; a function with 1000 calls and 50ms total is cheap per call, but one with 10 calls and 500ms total is a disaster.

Step 5: Identify the Hotspots

Look at the top 3–5 entries by exclusive time. These are your hot functions — the code that consumes the most CPU. Common culprits I've seen in real EAs:

  • Indicator recalculation loops — calling iMA() or iRSI() in a loop for every tick instead of caching the values. This is by far the most common issue I've fixed in client EAs.
  • Excessive OrderSelect() calls — selecting the same order multiple times in a single OnTick() pass. Each call is cheap alone, but 50 calls per tick adds up.
  • File I/O — writing to a CSV file on every tick is a performance killer. I once saw a trader's EA that wrote 5 lines per tick to a log file; the profiler showed FileWrite() taking 70% of the tick time.
  • String operations — concatenating strings in loops (use StringConcatenate() or string.Format instead). String allocation in MQL5 is surprisingly expensive.
  • Unnecessary ChartRedraw() calls — forcing the chart to redraw on every tick when nothing changed visually.

Don't ignore functions with high call counts even if their exclusive time looks low. A function called 100,000 times with 0.01ms per call still costs 1 second total. That's a lot if it's happening every tick.

Fixing the Slow Functions

Case 1: Indicator Recalculation in a Loop

Suppose your profiler shows CalculateIndicators with 800ms exclusive time out of 1000ms total for OnTick. Inside that function, you have something like:

for(int i = 0; i < 100; i++)
{
   double ma = iMA(_Symbol, _Period, 14, 0, MODE_EMA, PRICE_CLOSE, i);
   // do something with ma
}

That's 100 calls to iMA() per tick. Each call recalculates the entire moving average buffer internally. Instead, compute the indicator buffer once and read from it:

double maBuffer[];
ArraySetAsSeries(maBuffer, true);
CopyBuffer(maHandle, 0, 0, 100, maBuffer);
for(int i = 0; i < 100; i++)
{
   double ma = maBuffer[i];
   // do something with ma
}

This cuts the exclusive time from 800ms to maybe 20ms. The key is using CopyBuffer() once and indexing into the array. You'll need to create the indicator handle once in OnInit() with iMA(_Symbol, _Period, 14, 0, MODE_EMA, PRICE_CLOSE) and store it in a global variable.

Case 2: Redundant Order Selection

If OrderSelect() appears high in the profiler, check your code. You might be doing this:

if(OrderSelect(0, SELECT_BY_POS, MODE_TRADES))
{
   double openPrice = OrderOpenPrice();
   // ...
}
if(OrderSelect(0, SELECT_BY_POS, MODE_TRADES))
{
   double profit = OrderProfit();
   // ...
}

Select the order once, store the values in local variables, and reuse them:

if(OrderSelect(0, SELECT_BY_POS, MODE_TRADES))
{
   double openPrice = OrderOpenPrice();
   double profit = OrderProfit();
   // use both values here
}

If you're looping over all open orders, use OrdersTotal() once and store it, rather than calling it inside the loop condition. That alone can cut OrderSelect() calls by half.

Case 3: File Logging on Every Tick

If FileWrite() shows high exclusive time, move file writes to a buffer. Write to the file every 10 ticks or once per bar, not every tick. Here's a pattern I use:

string logBuffer[];
int logCount = 0;

void LogTick(string message)
{
   ArrayResize(logBuffer, logCount + 1);
   logBuffer[logCount] = message;
   logCount++;
   if(logCount >= 10)
   {
      FlushLog();
   }
}

void FlushLog()
{
   int handle = FileOpen("ea_log.csv", FILE_WRITE|FILE_CSV|FILE_READ, ",");
   if(handle != INVALID_HANDLE)
   {
      FileSeek(handle, 0, SEEK_END);
      for(int i = 0; i < logCount; i++)
         FileWrite(handle, logBuffer[i]);
      FileClose(handle);
   }
   logCount = 0;
}</pre

Frequently Asked Questions

Does the MQL5 profiler work in MT4?

No. The profiler is only available in MetaEditor for MetaTrader 5. MT4 uses MQL4, which has no built-in profiling support. For MT4, you'd need to manually instrument your code with GetTickCount() calls.

Can I profile an EA running on a live chart, not in the tester?

No. The profiler

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles