Problem Description
Solution
In MQL4, the most efficient way to check for changes and update an indicator without looping through the entire history is to utilize the prev_calculated parameter within the OnCalculate() function (or IndicatorCounted() in older legacy code).
This allows you to calculate only the bars that have changed or are new, rather than the entire history.
The Logic
rates_total: The total number of bars currently on the chart.prev_calculated: The number of bars that were successfully calculated in the previous tick.- The Difference: By subtracting
prev_calculatedfromrates_total, you determine how many bars need updating.
Implementation
Here is the standard, optimized structure for an MQL4 indicator. This code ensures you only process the latest tick and any new bars formed.
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
// --- 1. Check for data errors
if(rates_total < 1) return(0);
// --- 2. Define the calculation limit
// If prev_calculated is 0, we must calculate the whole history.
// Otherwise, we only calculate the difference.
int limit = rates_total - prev_calculated;
// --- 3. Handle the "Current" Bar
// If we have calculated data before, we usually need to re-calculate
// the very last bar (index 0) because it is still forming and changing.
// We might also want to re-calculate the previous bar (index 1) depending on strategy.
if(prev_calculated > 0)
{
limit++;
}
// --- 4. The Optimization Loop
// We loop from the 'limit' down to 0.
// 0 is the current open bar. 'limit' is the oldest bar that needs updating.
for(int i = limit; i >= 0; i--)
{
// --- YOUR INDICATOR LOGIC HERE ---
// Example: Simple Moving Average calculation
// Buffer[i] = iMA(NULL, 0, 14, 0, MODE_SMA, PRICE_CLOSE, i);
// Example: Check if value changed significantly
// if(Buffer[i] != LastValue) { ... }
}
// --- 5. Return the total count for the next tick
return(rates_total);
}
Why this is efficient
- First Run:
prev_calculatedis 0.limitbecomesrates_total. The loop runs through the entire history once. - Subsequent Ticks:
prev_calculatedequalsrates_total.limitbecomes 0 (or 1 due to thelimit++safety check). The loop runs only once for the current bari=0. - New Bar Event:
rates_totalincreases by 1.limitbecomes 1 (plus safety). The loop runs for the new bar and the previous closed bar.
Important Note on Array Indexing
In the example above, it assumes standard MQL4 indicator indexing where 0 is the most recent bar (rightmost) and rates_total-1 is the oldest bar (leftmost).
If you are using arrays passed directly into OnCalculate (like open[], close[]), you should ensure they are set as series if you want [0] to be the current bar:
int OnInit()
{
// Set arrays to index like timeseries (0 = newest)
// This is usually done automatically for Indicator Buffers,
// but good practice for raw arrays in complex calculations.
return(INIT_SUCCEEDED);
}
Q&A
Q: Can I use this in an Expert Advisor (EA)?
A: No, OnCalculate is specific to Indicators. In an EA, you typically check for a New Bar event to avoid recalculating logic on every tick. You can do this by tracking the Time[0] of the current bar.
Q: What happens if history is updated (e.g., download of historical data)?
A: The terminal will reset prev_calculated to 0 automatically. Your code will detect this (if(prev_calculated == 0)) and perform a full recalculation to ensure data integrity.
Q: Why do we do limit++?
A: When a new tick arrives, the current bar (index 0) changes. However, sometimes the calculation of the current bar depends on the previous bar (index 1). If the previous bar was the "current" bar in the last tick, its final closing value might have changed slightly at the very last moment. Recalculating the last known bar ensures your indicator transitions from "open" to "closed" state correctly.