Why don't you have candle timer for MT5? - page 2

 
Conor Mcnamara #:

There's been a few different approaches and they all work just fine. 

OnTick can be precarious because not always do ticks arrive, so OnTimer would be best or a constantly running while loop.

At the end of the day, it all depends upon the level of responsiveness versus the level of resource consumption desired. For example, a scalping trader on an M1 chart would obviously prefer the high level of responsiveness associated with OnTimer()--while a positional trader on a D1 chart could care less and use OnStart(), OnCalculate(), or OnTick().

 
Alain Verleyen #:
Similar bad code.
As a moderator you can remove this code, If its bad.
Alain Verleyen #:
Similar bad code.

Thank you for taking the time to review and comment on the code. I respect your experience and understand that different developers evaluate quality using different standards.

This same question was once asked on the forum to a well-known publisher who has contributed thousands of codes. His response was:
“The code is provided as-is. You are free to modify it yourself if you consider it bad.”
I believe that principle still reflects the spirit of this community.

The code has been downloaded over 11,500 times and received multiple positive ratings, which suggests that many users found it functional and useful for their needs. While it may not align with every developer’s preferred structure or architectural style, it does work as intended, and that is why users chose to rate it positively.

If you feel there are areas that can be improved, constructive suggestions or example fixes in the comments would be extremely valuable for the community. Likewise, if the code does not meet the platform’s standards, you of course have the authority to take appropriate moderation action as you are a moderator.

As contributors, we share code voluntarily and without compensation. Not every contributor is a commercial-grade EA developer, and not every shared script aims to be a fully robust or monolithic solution. In my view, functionality and usefulness to end users are also valid measures of value.

I’ve done my part by sharing working code and making it available to others. 

 
Rajesh Kumar Nait #:
As a moderator you can remove this code, If its bad.

Thank you for taking the time to review and comment on the code. I respect your experience and understand that different developers evaluate quality using different standards.

This same question was once asked on the forum to a well-known publisher who has contributed thousands of codes. His response was:
“The code is provided as-is. You are free to modify it yourself if you consider it bad.”
I believe that principle still reflects the spirit of this community.

The code has been downloaded over 11,500 times and received multiple positive ratings, which suggests that many users found it functional and useful for their needs. While it may not align with every developer’s preferred structure or architectural style, it does work as intended, and that is why users chose to rate it positively.

If you feel there are areas that can be improved, constructive suggestions or example fixes in the comments would be extremely valuable for the community. Likewise, if the code does not meet the platform’s standards, you of course have the authority to take appropriate moderation action as you are a moderator.

As contributors, we share code voluntarily and without compensation. Not every contributor is a commercial-grade EA developer, and not every shared script aims to be a fully robust or monolithic solution. In my view, functionality and usefulness to end users are also valid measures of value.

I’ve done my part by sharing working code and making it available to others. 

There is nothing to moderate here, I give my opinion so people have more information to decide. 

 
I'm fairly new to trading but I've created this indicator which I've found honestly helpful. Feel free to test it and give me the feedback🙏. It continues the countdown even when there are no ticks arriving.
//+------------------------------------------------------------------+
//|                                             Candle_Countdown.mq5 |
//|                                  Copyright 2026, Derick Kibiwott |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Derick Kibiwott"
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Displays a live countdown timer for the active candle, updating every second until the bar closes. The timer follows the current bid price,remains offset from the forming candle for improved visibility, and automatically formats the remaining time based on the active timeframe."

enum DISPLAY_MODE
{
    MODE_DISABLED, // Off
    MODE_MOVING,   // Follow active candle prices
    MODE_FIXED     // Pin chart corner
};

//--- Inputs
input group "Appearance";
input DISPLAY_MODE inp_mode = MODE_MOVING;     // Display Mode
input int inp_font_size = 8;                   // Font size
input color inp_label_color = clrMidnightBlue; // Label color

input group "Moving countdown";
input int inp_x_offset = 5;  // Horizontal offset
input int inp_y_offset = 18; // Vertical offset

input group "Fixed countdown";
input ENUM_BASE_CORNER inp_corner = CORNER_RIGHT_UPPER;  // Chart corner
input ENUM_ANCHOR_POINT inp_anchor = ANCHOR_RIGHT_UPPER; // Text anchor

const long CURRENT_CHART = 0;
const int MAIN_WINDOW = 0;
const int CANDLE_SHIFT = 0;

class CountdownLabel
{
  private:
    string name_;
    DISPLAY_MODE mode_;
    int font_size_;
    int x_offset_;
    int y_offset_;
    color label_color_;
    ENUM_BASE_CORNER corner_;
    ENUM_ANCHOR_POINT anchor_;

  public:
    CountdownLabel(const string &name, DISPLAY_MODE mode, int font_size, int x_offset, int y_offset, color label_color, ENUM_BASE_CORNER corner, ENUM_ANCHOR_POINT anchor) : name_(name), mode_(mode), font_size_(font_size), x_offset_(x_offset), y_offset_(y_offset), label_color_(label_color), corner_(corner), anchor_(anchor)
    {}
    bool create()
    {

        if (mode_ == MODE_DISABLED)
            return true;

        if (!ObjectCreate(CURRENT_CHART, name_, OBJ_LABEL, MAIN_WINDOW, 0, 0)) {
            Print("Failed to create label: ", name_);
            return false;
        }

        ENUM_BASE_CORNER target_corner = (mode_ == MODE_MOVING) ? CORNER_RIGHT_UPPER : corner_;
        ENUM_ANCHOR_POINT target_anchor = (mode_ == MODE_MOVING) ? ANCHOR_RIGHT_UPPER : anchor_;

        ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_FONTSIZE, font_size_);
        ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_COLOR, label_color_);
        ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_CORNER, target_corner);
        ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_ANCHOR, target_anchor);
        ObjectSetString(CURRENT_CHART, name_, OBJPROP_TEXT, "--:--");

        return setInitialCoord();
    }

    bool setInitialCoord()
    {
        if (mode_ == MODE_DISABLED)
            return true;

        int final_x_offset = x_offset_;

        if (mode_ == MODE_MOVING) {
            bool chart_shifted = ChartGetInteger(CURRENT_CHART, CHART_SHIFT, MAIN_WINDOW);
            final_x_offset = !chart_shifted ? x_offset_ * 2 * font_size_ : x_offset_;
        }

        return ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_XDISTANCE, final_x_offset) && ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_YDISTANCE, y_offset_);
    }

    bool trackPrice(datetime last_bar_opening_time)
    {
        if (mode_ != MODE_MOVING)
            return true;

        int x, y;
        double current_bid_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

        if (ChartTimePriceToXY(CURRENT_CHART, MAIN_WINDOW, last_bar_opening_time, current_bid_price, x, y)) {
            return ObjectSetInteger(CURRENT_CHART, name_, OBJPROP_YDISTANCE, y - y_offset_);
        }

        return false;
    }

    bool setText(const string text) const
    {
        if (mode_ == MODE_DISABLED)
            return true;
        return ObjectSetString(CURRENT_CHART, name_, OBJPROP_TEXT, text);
    }

    bool destroy()
    {
        if (ObjectFind(CURRENT_CHART, name_) >= 0) {
            return ObjectDelete(CURRENT_CHART, name_);
        }
        return true;
    }

    ~CountdownLabel()
    {
        destroy();
    }
};

class BrokerClock
{
  private:
    long offset_seconds_;

  public:
    BrokerClock() : offset_seconds_(0)
    {}

    void synchronize()
    {
        offset_seconds_ = (long)(TimeCurrent() - TimeLocal());
    }

    datetime now() const
    {
        return TimeLocal() + offset_seconds_;
    }
};

class CandleCountdown
{
  private:
    CountdownLabel label_;
    BrokerClock broker_clock_;
    datetime close_time_;
    datetime last_bar_opening_time_;

  public:
    CandleCountdown(const string &name, DISPLAY_MODE mode, int font_size, int x_offset, int y_offset, color label_color, ENUM_BASE_CORNER corner, ENUM_ANCHOR_POINT anchor) : label_(name, mode, font_size, x_offset, y_offset, label_color, corner, anchor)
    {}

    bool create()
    {
        broker_clock_.synchronize();
        return label_.create();
    }
    bool update()
    {

        label_.trackPrice(last_bar_opening_time_);

        return label_.setText(formatTime());
    }

    void synchronizeClock(const datetime &time[])
    {
        ArraySetAsSeries(time, true);
        if (last_bar_opening_time_ != time[0]) {
            last_bar_opening_time_ = time[0];
            setCloseTime();
        }

        broker_clock_.synchronize();
    }

    void destroy()
    {
        if (!label_.destroy()) {
            Print("Failed to delete countdown label.");
        }
    }

  private:
    int remainingSeconds() const
    {
        return (int)(close_time_ - broker_clock_.now());
    }

    string formatTime()
    {
        const int remaining_seconds = MathMax(0, remainingSeconds());

        MqlDateTime time = {};

        TimeToStruct(remaining_seconds, time);

        switch (_Period) {

        case PERIOD_MN1: {
            int weeks = time.day / 7;
            int days = time.day % 7;
            return StringFormat("%2dw %2dd %02dh %02dm %02ds", weeks, days, time.hour, time.min, time.sec);
        }

        case PERIOD_D1:
        case PERIOD_W1: {
            return StringFormat("%2dd %02dh %02dm %02ds", time.day, time.hour, time.min, time.sec);
        }

        case PERIOD_H1:
        case PERIOD_H4: {
            return StringFormat("%2dh %02dm %02ds", time.hour, time.min, time.sec);
        }

        default: {
            return StringFormat("%02dm %02ds", time.min, time.sec);
        }
        }
    }

    void setCloseTime()
    {

        close_time_ = last_bar_opening_time_ + PeriodSeconds(_Period);
    }
};

const string label_name = "countdown_label";

CandleCountdown candle_countdown(label_name, inp_mode, inp_font_size, inp_x_offset, inp_y_offset, inp_label_color, inp_corner, inp_anchor);

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
    if (!candle_countdown.create()) {
        return INIT_FAILED;
    }
    EventSetTimer(1);
    return (INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
void OnTimer()
{
    candle_countdown.update();
    ChartRedraw(CURRENT_CHART);
}

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[])
{

    candle_countdown.synchronizeClock(time);
    return (rates_total);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    EventKillTimer();
    candle_countdown.destroy();
}