Problem Description
Solution
To optimize a complex MQL4 trading algorithm for speed and memory efficiency, you must minimize redundant calculations, manage dynamic memory explicitly, and optimize how you access market history.
Here are the specific strategies and a code template to achieve this.
1. Optimization Strategies
A. Execution Speed (CPU)
- Event-Driven Calculation (The "New Bar" Check):
Most algorithms do not need to recalculate complex logic on every tick. Ticks can arrive milliseconds apart. Restrict heavy calculations to the opening of a new bar. - Limit Loops:
Never loop throughBarsorOrdersHistoryTotal()entirely on every tick. Store the last processed time or index and only process new data. - Optimize Indicator Calls:
iCustomcalls are expensive. Do not call them inside loops if the parameters don't change. Calculate the value once per bar and store it in a variable. - Reduce Visual Updates:
Functions likeComment(),ChartRedraw(), and Object functions are slow because they interact with the terminal's GUI thread. Update them only when necessary (e.g., on a new bar or via a Timer).
B. Memory Management (RAM)
- Array Reservation:
When using dynamic arrays (double arr[]), frequent resizing is memory-intensive. UseArrayResize(arr, size, reserve)to allocate extra memory blocks in advance to prevent memory fragmentation. - Free Unused Memory:
UseArrayFree()ordelete(for class objects) when data is no longer needed. - Limit History Access:
AccessingiOpen,iHigh, etc., for deep history (e.g., index 50,000) forces the terminal to load history files into RAM. Limit your lookback period to what is strictly necessary.
2. Optimized MQL4 Code Template
This template demonstrates a "New Bar" event handler, efficient array memory reservation, and optimized order looping.
//+------------------------------------------------------------------+
//| OptimizedAlgorithm.mq4 |
//| Copyright 2023, Quantitative Trading AI |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input int MagicNumber = 12345;
input int LookBackPeriod = 100; // Limit history processing
//--- Global Variables for Optimization
datetime LastBarTime = 0;
double MyDataArray[];
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Pre-allocate memory to avoid fragmentation during runtime
// We allocate 1000 slots, and reserve 1000 more to prevent frequent resizing
ArrayResize(MyDataArray, 1000, 1000);
// Initialize random seed if using MathRand
MathSrand(GetTickCount());
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Explicitly free memory
ArrayFree(MyDataArray);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. CRITICAL: New Bar Check
// Most logic should only run once per bar to save CPU
if(IsNewBar())
{
// Run heavy calculations here
RunComplexLogic();
// Update visual comments only once per bar
Comment("Last calculation: ", TimeToString(TimeCurrent()));
}
// 2. Trade Management (Trailing stops, etc.)
// This might need to run every tick, but keep it lightweight
ManageOpenOrders();
}
//+------------------------------------------------------------------+
//| Check for a new bar opening |
//+------------------------------------------------------------------+
bool IsNewBar()
{
if(LastBarTime != Time[0])
{
LastBarTime = Time[0];
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Heavy Calculation Logic |
//+------------------------------------------------------------------+
void RunComplexLogic()
{
// OPTIMIZATION: Only process the required lookback period
// Do NOT use Bars or iBars(Symbol(), Period()) in the loop limit
int limit = MathMin(LookBackPeriod, Bars - 1);
// Resize array only if strictly necessary
if(ArraySize(MyDataArray) < limit)
ArrayResize(MyDataArray, limit, 500); // Reserve 500 extra slots
for(int i = 0; i < limit; i++)
{
// Example: Simple Moving Average calculation
// Using iMA directly is faster than manual loops for standard indicators
MyDataArray[i] = iMA(NULL, 0, 14, 0, MODE_SMA, PRICE_CLOSE, i);
}
// Example: Check signal on the just closed bar (index 1)
if(MyDataArray[1] > MyDataArray[2])
{
// Signal Logic
}
}
//+------------------------------------------------------------------+
//| Efficient Order Management |
//+------------------------------------------------------------------+
void ManageOpenOrders()
{
// OPTIMIZATION: Loop backwards when dealing with orders
// (especially if deleting/closing) to avoid index shifting issues.
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
// Select by Position is faster than Ticket for general looping
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
// Filter early to save CPU cycles
if(OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
// Perform lightweight checks
if(OrderType() == OP_BUY)
{
// Trailing stop logic example
// ...
}
}
}
3. Key Optimization Techniques Explained
The ArrayResize Reserve Parameter
In the code above, ArrayResize(MyDataArray, limit, 500) is used. The third parameter (500) is the reserve.
- Without Reserve: If you resize an array from 100 to 101, the OS finds a new memory block of size 101, copies the old data, and deletes the old block. This is very slow.
- With Reserve: The OS allocates memory for 600 slots immediately. Resizing from 100 to 101 happens instantly inside the reserved block without moving data in physical memory.
Avoiding iCustom Memory Leaks
If you use iCustom, ensure you are not creating new handles (in MQL5) or causing excessive recalculations (in MQL4) inside loops.
- Bad: Calling
iCustominside aforloop that iterates 1000 times per tick. - Good: Call
iCustomfor specific indices (e.g.,iandi+1) only when a new bar arrives.
Disable Print in Optimization
The Print() function writes to the hard drive (log files). This is the single biggest bottleneck during Strategy Tester optimization.
// Use a macro to disable printing during optimization
if(!IsOptimization())
{
Print("Current Equity: ", AccountEquity());
}
Q&A: MQL4 Optimization
Q: Does ArraySetAsSeries affect performance?
A: ArraySetAsSeries is very efficient. It does not physically move data in memory; it only changes the indexing logic (reverses the access order). It is preferred over ArrayCopy if you just need to read data backwards.
Q: Why is my backtest consuming gigabytes of RAM?
A: This usually happens if you access deep history (e.g., iClose(Symbol(), Period(), 100000)). MetaTrader 4 loads history data into RAM on demand. If your code requests bar #100,000, MT4 loads all 100,000 bars into memory. Restrict your loops to a defined LookBackPeriod.
Q: Is it faster to use built-in indicators (iMA) or calculate them manually in a loop?
A: Built-in functions (iMA, iRSI, etc.) are highly optimized in C++ within the terminal core. They are generally faster than writing the equivalent math loop in MQL4, unless you need a highly specific variation of the calculation.
Q: How do I profile my MQL4 code to find bottlenecks?
A: MQL4 does not have a built-in profiler like MQL5. You must use GetTickCount() or GetMicrosecondCount() to measure execution time manually.
- Example:
ulong start = GetMicrosecondCount(); RunComplexLogic(); ulong end = GetMicrosecondCount(); Print("Logic took: " + IntegerToString(end - start) + " microseconds");