MQL4/MQL5 Code Optimization: Speed Up Your EAs & Indicators

Learn how to optimize MQL4/MQL5 code for faster backtesting and live trading. Covers profiling, loop optimization, array handling, and preventing CPU spikes.

mql4-mql5-code-optimization-speed-up-your-eas

Why Code Optimization Matters for Your EAs and Indicators

You've spent hours coding your Expert Advisor or custom indicator. It compiles without errors. It even produces trades. But when you run a backtest over five years of EURUSD M1 data, the Strategy Tester crawls. Or worse — your EA works fine in testing but causes visible lag on a live chart, delaying price updates and missing entries.

This is the reality of unoptimized MQL code. Every unnecessary loop iteration, every redundant RefreshRates() call, and every unmanaged array allocation eats CPU cycles. On a retail VPS with limited resources, these inefficiencies compound into missed ticks, delayed signals, and even platform freezes.

In this guide, you will learn practical, battle-tested techniques to optimize MQL4 and MQL5 code. We will cover profiling your code to find bottlenecks, writing faster loops, minimizing indicator recalculations, and managing memory properly. These techniques apply to both platforms, with specific MT5 notes where relevant. By the end, your EAs will backtest faster and run smoother in live markets.

Prerequisites

  • MetaTrader 4 or 5 installed (any build). MT5 users benefit from additional optimization tools.
  • MetaEditor (comes with the platform).
  • An existing EA or indicator with performance issues — or a willingness to test the examples.
  • Basic MQL knowledge: variables, loops, functions, and array operations.
  • Optional but recommended: A demo account with at least 6 months of tick history for realistic backtesting.

Step 1: Profile Your Code to Find Bottlenecks

Before optimizing, you must measure. Without profiling, you are guessing. In MQL5, use the built-in Profiling feature. In MQL4, you must rely on manual timing with GetTickCount().

Using the MQL5 Profiler (MetaTrader 5 Only)

  1. Open your EA or indicator in MetaEditor.
  2. Go to Tools > Profiler (or press Ctrl+Shift+P).
  3. In the profiler window, select your file and choose Start Profiling.
  4. Run the EA on a chart in the Strategy Tester or on a live chart for a few minutes.
  5. Stop the profiler. You will see a table showing each function, the number of calls, and the time spent (in microseconds).

The profiler highlights the functions consuming the most CPU time. Focus your optimization efforts there. Common hotspots include OnTick(), custom indicator calculation loops, and file I/O operations.

Manual Timing in MQL4 (and MQL5 Alternative)

MQL4 lacks a built-in profiler. Use GetTickCount() to measure execution time of specific code blocks:

// MQL4 example
uint start = GetTickCount();
// Code block to measure
for(int i = 0; i < 10000; i++) {
    // Some operation
}
uint elapsed = GetTickCount() - start;
Print("Loop took ", elapsed, " ms");

Wrap this around suspect sections — indicator loops, order scanning, or file writes. Run the EA for several ticks and note the average elapsed time. This gives you a baseline to compare after optimization.

Step 2: Optimize Loops and Array Operations

Loops are the most common performance killers. Every tick, your EA might iterate over hundreds or thousands of bars. Small inefficiencies multiply.

Loop Unrolling and Pre-calculation

Avoid recalculating the same values inside a loop. Move invariant calculations outside:

// SLOW
for(int i = 0; i < rates_total; i++) {
    double ma = iMA(NULL, 0, 20, 0, MODE_SMA, PRICE_CLOSE, i);
    // Use ma
}

// FASTER
double ma_array[];
ArraySetAsSeries(ma_array, true);
CopyBuffer(ma_handle, 0, 0, rates_total, ma_array);
for(int i = 0; i < rates_total; i++) {
    double ma = ma_array[i];
    // Use ma
}

In MQL5, use CopyBuffer() to fetch indicator data once into an array, then loop through the array. This avoids repeated iMA() calls, each of which recalculates the indicator internally.

Use Local Variables and Avoid Repeated Function Calls

Calling ArraySize() or OrdersTotal() inside a loop condition re-evaluates every iteration. Cache the value:

// SLOW
for(int i = 0; i < ArraySize(arr); i++) { ... }

// FASTER
int size = ArraySize(arr);
for(int i = 0; i < size; i++) { ... }

Similarly, avoid calling Symbol(), Period(), or Point inside loops. Store them in local variables at the start of OnTick() or OnCalculate().

Array Copying vs. Direct Access

In MQL5, copying entire arrays with ArrayCopy() is expensive. If you only need a subset, copy just that portion. Better yet, use ArraySetAsSeries() and access elements directly without copying.

Step 3: Minimize Indicator Recalculations

Custom indicators in OnCalculate() run on every tick. If your indicator calculates all bars every time, it wastes CPU. Use the prev_calculated parameter to only process new bars:

// MQL5 example
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
    // Start from the first uncalculated bar
    int start = prev_calculated - 1;
    if(start < 0) start = 0;
    if(start > rates_total - 1) start = rates_total - 1;

    for(int i = start; i < rates_total; i++) {
        // Calculate only new bars
    }
    return(rates_total);
}

In MQL4, the equivalent is checking prev_calculated in start():

int start() {
    int counted_bars = IndicatorCounted();
    if(counted_bars < 0) return(-1);
    int limit = Bars - counted_bars - 1;
    for(int i = limit; i >= 0; i--) {
        // Calculate only new bars
    }
    return(0);
}

This simple change can reduce CPU usage by 90% on historical backtests.

Step 4: Optimize Order Scanning and Trade Operations

Scanning all open orders or history on every tick is a major bottleneck. Use selective filtering and caching.

Selective Order Selection

Instead of looping through all orders, use OrderSelect() with specific ticket numbers from a cached list. For MQL5, use PositionSelect() with the symbol to get only relevant positions:

// MQL5 - only check positions for current symbol
if(PositionSelect(_Symbol)) {
    ulong ticket = PositionGetInteger(POSITION_TICKET);
    // Process one position
}

Cache Order Information

In MQL4, store open order tickets in an array at the start of OnTick(). Then iterate only that array:

int tickets[];
int total = OrdersTotal();
ArrayResize(tickets, total);
for(int i = 0; i < total; i++) {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
        tickets[i] = OrderTicket();
    }
}
// Later, use cached tickets
for(int i = 0; i < total; i++) {
    if(OrderSelect(tickets[i], SELECT_BY_TICKET)) {
        // Process
    }
}

This avoids repeated OrdersTotal() and OrderSelect() calls when checking conditions. Note: only cache at the start of each tick; do not cache across ticks because order state changes.

Step 5: Manage Memory and Avoid Leaks

Memory mismanagement causes gradual slowdowns and crashes, especially in long-running EAs.

Dynamic Array Sizing

Use ArrayResize() sparingly. Frequent resizing fragments memory. Pre-allocate the maximum expected size once:

// Pre-allocate once in OnInit()
double myArray[];
ArrayResize(myArray, 1000, 1000); // Reserve 1000 elements, with 1000 expansion step

Release Unused Resources

If your EA creates indicator handles, chart objects, or file handles, release them in OnDeinit():

void OnDeinit(const int reason) {
    IndicatorRelease(ma_handle);
    ObjectDelete(0, "myLine");
    FileClose(file_handle);
}

In MQL4, use IndicatorBuffers() and SetIndexBuffer() carefully — do not allocate more buffers than needed.

Avoid Global Variable Overuse

Global variables persist in memory. Use local variables inside functions where possible. If you must use globals, group related ones into structures.

Tips and Best Practices from Experience

  • Test on a clean chart: When profiling, remove other EAs and indicators. They add noise to your measurements.
  • Use tick-by-tick backtesting for optimization: In MT5, select "Every tick" in the Strategy Tester. In MT4, use "Every tick" (available with tick data). This reveals real-world performance.
  • Prefer integer math over double: Integer operations are faster. Use int for counters and indices. Avoid unnecessary type conversions.
  • Batch file writes: If you log trade data, write to a string buffer and flush to file every N ticks, not every tick.
  • Use #property strict in MQL4: It forces type checking and catches subtle bugs that cause runtime slowdowns.
  • MQL5 specific: Enable #property tester_everytick_calculation to test optimization gains in the Strategy Tester.

Common Mistakes and Troubleshooting

Mistake Symptom Fix
Not caching prev_calculated Indicator recalculates all bars on every tick Use prev_calculated (MQL5) or IndicatorCounted() (MQL4) to limit calculations to new bars.
Repeated ArrayResize() in loops Memory fragmentation, gradual slowdown over hours Pre-allocate once with ArrayResize() at initialization. Use a maximum expected size.
Calling RefreshRates() inside tight loops EA skips ticks, delayed execution Call RefreshRates() once per tick, or only before critical price checks.
Not releasing indicator handles (MQL5) Handle leak, eventually "too many handles" error Call IndicatorRelease() in OnDeinit() for every handle created with iCustom() or IndicatorCreate().

Summary / Recap and Next Steps

Code optimization in MQL4 and MQL5 is not about writing clever one-liners — it is about systematic profiling, eliminating redundant calculations, and managing resources consciously. The five steps covered here — profiling, loop optimization, minimizing indicator recalculations, efficient order scanning, and memory management — will reduce your EA's CPU usage by 50-90% in most cases.

Next steps:

  1. Profile your current EA or indicator using the techniques above. Identify the top three time-consuming functions.
  2. Apply the loop and array optimizations to those functions first. Measure the improvement.
  3. Test the optimized version in the Strategy Tester with "Every tick" mode. Compare backtest speed.
  4. Run on a demo account for 24 hours to verify no

Community

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

0 claps0 comments

Related articles