How to Check for a New Bar in MQL5?

Algorithmic trading systems, whether Expert Advisors (EAs) or custom indicators, frequently need to execute logic only when a new price bar has formed. Reacting to every incoming tick can be computationally expensive and often unnecessary for strategies that rely on bar-based patterns or indicators calculated on bar close.

Why Detecting New Bars Matters in Algorithmic Trading

Efficiently detecting a new bar is fundamental for performance and correctness in many automated trading strategies. Strategies often trigger actions based on conditions met at the close of a bar (e.g., indicator crossing, pattern completion) or the open of a new bar. Processing these events on every tick when the bar hasn’t closed yet can lead to premature signals, incorrect indicator calculations, and excessive processing load.

Pinpointing the exact moment a new bar opens allows the EA or indicator to perform calculations, check conditions, or execute trades precisely at the intended time, ensuring the strategy logic aligns with the bar structure of the selected timeframe.

Overview of Common Approaches for New Bar Detection

There are several robust methods to check for a new bar in MQL5, each with its own nuances suitable for different contexts (e.g., OnTick vs OnCalculate, EA vs Indicator). The most common techniques involve comparing time values or utilizing specific MQL functions:

  • Comparing the time of the current bar (Time[0]) with the time of the previous bar (Time[1]).
  • Storing the time of the last processed bar using a static variable and comparing it with the current Time[0]. This is particularly effective in the OnTick function.
  • Using functions like iBarShift to determine bar indices based on time, although this is less a direct ‘new bar detection’ event trigger and more a utility for bar referencing.

We will explore the implementation and considerations for these methods.

Method 1: Comparing Time Values of the Last Bar

The MQL5 language provides built-in series arrays to access historical data. The Time series array (_Symbol, _Period, TIME) contains the open time for each bar.

Explanation of the ‘Time’ series array

Time[0] refers to the open time of the current, incomplete bar. Time[1] refers to the open time of the immediately preceding, completed bar. For any index i > 0, Time[i] is the open time of the bar i bars ago.

Code Example: Comparing current and previous bar times

This method is straightforward and commonly used in indicators within the OnCalculate function, which is called when price or indicator data updates.

// In an OnCalculate function of a custom indicator
// Check if a new bar has formed by comparing the time of the current bar (index 0)
// with the time of the previously completed bar (index 1).
// This check should ideally be done only once per new bar.

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[])
{
    // Check if it's the very first calculation or if new bars have arrived
    if (prev_calculated < rates_total)
    {
        // A simple check: if index 0 time is different from index 1 time
        // (assuming index 1 exists and is valid)
        // This check works reliably in OnCalculate as it's processed after data updates
        if (rates_total > 1 && time[0] != time[1])
        {
            // New bar detected!
            // Execute new bar logic here
            Print("New bar detected in OnCalculate. Time: ", time[0]);

            // Example: Calculate indicator value on the closed bar (index 1)
            // double indicator_value = CalculateMyIndicator(1);
            // ... trading logic based on indicator_value
        }
        // Else: Still on the same bar

        // ... rest of indicator calculations ...

    }

    // Return the number of bars calculated
    return rates_total;
}

Note: While time[0] != time[1] works effectively in OnCalculate (as rates_total updates and time array is synchronized), using Time[0] != Time[1] directly in OnTick can be problematic immediately when a bar opens because Time[1] might not be instantly updated or accessible in the required state. A more robust method for OnTick is comparing the current Time[0] with the Time[0] value from the previous tick.

Handling Time Zone Differences and DST

The Time array values represent the server time of the broker. MQL handles Standard Time and Daylight Saving Time (DST) adjustments automatically based on the server’s configuration. You generally do not need to account for these explicitly when comparing Time values from the same symbol and timeframe within the same trading environment, as the times provided are consistent server times.

Method 2: Using a Static Variable to Store the Last Bar Time

This method is widely considered the most reliable and efficient way to detect a new bar specifically within the OnTick function of an Expert Advisor.

Explanation of static variables in MQL5

Static variables in MQL5 are initialized once when the program starts and retain their value between calls to the function in which they are declared. They exist for the lifetime of the program instance (EA or indicator) on the chart. This makes them ideal for preserving state information, such as the time of the bar that was processed on the previous tick.

Code Example: Implementation of Static Variable Approach

Declare a static variable to store the time of the last processed bar. Initialize it, typically to 0 or the current bar time on the first tick.

// In an Expert Advisor's OnTick function

void OnTick()
{
    // Declare a static variable to store the time of the last processed bar.
    // It retains its value between OnTick calls.
    static datetime last_bar_time = 0;

    // Get the current bar's open time.
    datetime current_bar_time = Time[0];

    // Check if the current bar's time is different from the last processed bar's time.
    // This indicates a new bar has started.
    if (last_bar_time != current_bar_time)
    {
        // --- NEW BAR DETECTED --- //
        Print("New bar detected! Previous bar time: ", last_bar_time, ", Current bar time: ", current_bar_time);

        // --- Update the static variable with the new bar time ---
        last_bar_time = current_bar_time;

        // --- Execute your new bar logic here ---
        // This code block will run only once per new bar.

        // Example: Check conditions on the just-closed bar (index 1)
        // if (Close[1] > Open[1]) // Previous bar was bullish
        // {
        //     // Place a buy order on the new bar?
        //     // Trade.Buy(...);
        // }

        // --- End of new bar logic ---
    }
    else
    {
        // --- STILL ON THE SAME BAR --- //
        // This code block runs on subsequent ticks of the same bar.
        // Use this for logic that should execute on every tick within a bar.
        // Print("Still on the same bar. Time: ", current_bar_time);
    }

    // --- Logic that runs on every tick, regardless of new bar --- //
    // Example: Manage pending orders, check high/low of current bar, etc.
}

// In OnInit, you might initialize last_bar_time if needed, e.g.,
// int OnInit()
// {
//    // Get the time of the current bar when the EA starts
//    datetime current_bar_time = Time[0];
//    // Find the static variable declared in OnTick and set its initial value
//    // Note: Accessing static variables from other functions requires care or alternative storage (class member)
//    // A common pattern is to rely on the first call to OnTick for initialization.
//    return(INIT_SUCCEEDED);
// }

// The initialization `static datetime last_bar_time = 0;` is sufficient for most cases.
// On the first tick, Time[0] will be different from 0, triggering the new bar logic.

Advantages and potential pitfalls of the static variable method

Advantages:

  • Reliable: Works consistently in OnTick to detect the transition to a new bar.
  • Efficient: The check last_bar_time != current_bar_time is very fast.
  • Clean State: Encapsulates the state needed for bar detection within the function using the static keyword.

Potential Pitfalls:

  • Reinitialization: If the EA is stopped and restarted, the static variable is reset. If the chart is closed and reopened, it’s also reset. This is usually desired behavior, but something to be aware of.
  • Multiple Instances: If the same EA is attached to multiple charts, each instance on each chart will have its own separate static variable, ensuring correct operation for each instance.

Method 3: Using the iBarShift Function

While not the primary method for detecting a new bar event on a tick, the iBarShift function is crucial for working with historical bars and can be used in conjunction with time checks.

Explanation of iBarShift Function and its Parameters

iBarShift is used to find the index of a bar whose opening time is equal to or closest to a specified time value. Its signature is:

int iBarShift(string symbol, ENUM_TIMEFRAMES timeframe, datetime time, bool exact=false)

  • symbol: The symbol name (NULL for the current chart symbol).
  • timeframe: The timeframe (0 for the current chart timeframe).
  • time: The datetime value to search for.
  • exact: If true, it searches for a bar with an exact match in open time. If false, it returns the index of the bar whose start time is less than or equal to the specified time (the bar containing or immediately preceding the time). The default is false.

The function returns the index of the bar if successful, and -1 on error.

Code Example: Implementing New Bar Detection with iBarShift

Using iBarShift solely for detecting a new bar on tick is less direct than time comparison, as iBarShift(NULL, 0, Time[0]) will almost always return 0 once the bar data is synchronized. However, it can be used in a stateful way similar to the static variable method, or more typically, to relate a specific time to a bar index.

// Example of using iBarShift to find the index of the current bar's open time
// (less direct for *detecting* a new bar event on tick compared to static time check)

void OnTick()
{
    // Get the current bar's open time
    datetime current_bar_time = Time[0];

    // Find the index of the bar corresponding to the current bar time
    // Using exact=true might fail if the time isn't precisely a bar open time
    // Using exact=false is more robust, finding the containing or preceding bar.
    int bar_index = iBarShift(NULL, 0, current_bar_time, false);

    if (bar_index == 0)
    {
        // This indicates the current Time[0] maps to the bar at index 0.
        // It doesn't inherently mean a *new* bar just formed *this tick*,
        // just that Time[0] is indeed the current bar's time.
        // To use iBarShift for new bar *detection* on tick, you'd need
        // to store the previous tick's Time[0] and check if its iBarShift
        // result is now > 0, meaning it's no longer the current bar (index 0).

        static datetime last_processed_time = 0;
        if (last_processed_time != current_bar_time)
        {
             // This check is effectively the static variable method (Method 2)
             // We can confirm the index if needed:
             int confirmed_index = iBarShift(NULL, 0, last_processed_time, false); // Index of the *previous* bar's time
             if (confirmed_index > 0 || last_processed_time == 0) // last_processed_time is now in the past
             {
                 Print("New bar detected using iBarShift logic. Current index: ", bar_index, ", Previous time index: ", confirmed_index);
                 // New bar logic here
                 last_processed_time = current_bar_time;
             }
             else
             {
                  // This happens if the EA just started, last_processed_time was 0,
                  // or if for some reason iBarShift(last_processed_time) returned 0
                  // even though Time[0] changed.
                  last_processed_time = current_bar_time;
             }
        }
    }
    else if (bar_index > 0)
    {
        // This should not happen frequently for Time[0] on the current chart timeframe,
        // but could if data isn't synchronized or there's an issue.
        Print("Warning: iBarShift(Time[0]) returned index ", bar_index);
    }
    else // bar_index is -1
    {
        Print("Error finding bar index for Time[0]");
    }

    // --- Logic that runs on every tick --- //
}

Self-Correction: The above iBarShift example for detection is overly complex and essentially reimplements the static variable time comparison. The static variable method (Method 2) is cleaner and more direct for tick-based new bar detection. iBarShift‘s strength lies in finding bar indices for arbitrary times or across different timeframes, not as a primary event trigger for a new bar on the current chart/timeframe’s tick.

Let’s refine the iBarShift section to focus on its utility:

// Example demonstrating typical use of iBarShift

void OnTick()
{
    // Suppose we need the Close price of the bar that opened 1 day ago
    datetime time_one_day_ago = Time[0] - 24*60*60; // Approximate time

    // Find the index of the bar corresponding to this time
    int index_one_day_ago = iBarShift(NULL, 0, time_one_day_ago, false);

    if (index_one_day_ago >= 0)
    {
        // Got the index. Now access data for that bar.
        double close_price = Close[index_one_day_ago];
        // Print("Close price from approx. 1 day ago: ", close_price);
    }
    else
    {
        // Handle error
        Print("Could not find bar index for time one day ago.");
    }

    // iBarShift can also be used to find the index of the current bar (Time[0])
    // This will typically return 0, confirming Time[0] is indeed index 0.
    int current_index = iBarShift(NULL, 0, Time[0]);
    // Print("Current bar index from iBarShift: ", current_index);
    // This confirms the mapping but doesn't detect a *new* bar event.

    // To use it for detecting a new bar (less common approach for tick): check if previous Time[0]'s index is now > 0
    static datetime last_known_time = 0;
    if (last_known_time != Time[0]) // Basic time change check first
    {
         if (last_known_time != 0) // Not the very first tick
         {
             // If the time changed, is the old time now located at an index > 0?
             // This implies a new bar pushed the old bar to index 1 or further.
             if (iBarShift(NULL, 0, last_known_time, true) > 0)
             {
                 // New bar detected via iBarShift check
                 Print("New bar detected (via iBarShift check). Old time index: ", iBarShift(NULL, 0, last_known_time, true));
                 // Execute new bar logic
             }
         }
         last_known_time = Time[0]; // Update for the next tick
    }

    // --- Logic that runs on every tick --- //
}

Conclusion on iBarShift for Detection: While possible, using iBarShift to detect a new bar event on tick is less straightforward and less common than the static variable time comparison. Its primary utility is retrieving bar indices for analysis or trading logic based on specific times, not for triggering events on bar formation.*

Advantages and Disadvantages of Using iBarShift

Advantages:

  • Flexibility: Allows finding bar indices for any given time, including historical times or times on different timeframes (with appropriate symbol/timeframe parameters).
  • Precise Indexing: Useful for navigating historical data or aligning data from different periods.

Disadvantages:

  • Not Direct for Tick Events: Not the most intuitive or direct method for simply detecting when a new bar opens on the current chart’s OnTick event.
  • Performance: Repeated calls to iBarShift might involve searching through data, potentially being less performant than a simple time comparison, though typically negligible unless abused.
  • Exactness: Using exact=true can fail if the provided time doesn’t precisely match a bar’s open time.

Best Practices and Optimization

Efficiently handling new bar detection is key to writing performant MQL programs.

Choosing the Right Method for Your Trading Strategy

  • For OnTick in EAs: The static variable method (Method 2) is generally the preferred and most reliable approach for detecting a new bar and executing logic exactly once per bar open.
  • For OnCalculate in Indicators: Comparing time[0] and time[1] (Method 1) is suitable, as OnCalculate is processed with updated data arrays. Ensure rates_total > 1 before accessing time[1]. The static variable method is also valid here if state persistence is needed across OnCalculate calls for some reason other than basic bar detection.
  • Using iBarShift: Best used for specific tasks like finding the bar index for a historical event time, aligning data from different symbols/timeframes, or calculating lookback periods based on time rather than fixed bar counts.

Minimizing CPU Usage when Checking for New Bars

Regardless of the method chosen, place the new bar check at the very beginning of your OnTick or OnCalculate function. If a new bar is not detected and your strategy logic only needs to run on new bars, you can use return; in OnTick or return(prev_calculated); in OnCalculate immediately after the check to exit the function early. This prevents unnecessary recalculations on every single tick.

// Example of early exit in OnTick using static variable method
void OnTick()
{
    static datetime last_bar_time = 0;
    datetime current_bar_time = Time[0];

    if (last_bar_time == current_bar_time) // Check if still on the same bar
    {
        // No new bar, exit early to save CPU cycles
        return;
    }

    // --- New bar detected --- //
    last_bar_time = current_bar_time;
    Print("New bar detected. Executing bar logic...");

    // --- Place all new bar logic here ---
    // ... (trading strategy entry/exit checks, indicator calculations on closed bar, etc.)
    // --- End of new bar logic ---
}

Error Handling and Debugging Tips

  • Print Statements: Use Print() or Comment() liberally during development to show Time[0], the value of your static variable, and when the new bar detection block is executed. This helps visualize the timing.
  • Visual Testing: Run your EA in the Strategy Tester with ‘Visual mode’ enabled. Add Print statements to see the output log and observe when the new bar detection triggers relative to price action and bar formation on the chart.
  • Check Return Values: While the simple time comparison methods rarely fail, always check the return value of functions like iBarShift (>= 0 for success, -1 for error) if you are using them in more complex ways.
  • Data Loading: Ensure sufficient historical data is loaded, especially if your logic depends on accessing older bars via indices found by iBarShift or direct array access like Close[i]. Check Bars() count to ensure data is available.

By implementing reliable new bar detection, your MQL programs will execute logic precisely when intended, leading to more accurate backtesting, efficient resource usage, and better trading performance.


Leave a Reply