Automatic back and forth Timeframe change using a global terminal variable

 

Hello everyone,

I'm currently developing several multitimeframe (MTF) indicators for MT5, and I have been dealing with the well-known problem of working with MTF when the market is closed, as I have recently posted here.

I think I have successfully developed a new solution I haven't found in the forum, which consists of automatically switching to a different timeframe, and return back to the original one, using global variables of the client terminal. 

I will try to summarize the problem and my solution, and I hope that it can be useful to some of you working on the same problem. I'm sorry if I make some wrong assumptions, or my explanations are not perfect, since I'm new to MT5 and I have not as much expertise as some other members of the forum. I have been reading and understanding the documentation as much as I could.  


Description of the problem:

Let's say we are in M1 (TF1) but we need to access bars from D1 (TF2). We call iBarShift in TF2, but the market is closed (let's say we're on weekend) so we get an error (WRONG_VALUE or -1). That error means that TF2 bars are still not loaded in the client terminal, and the indicator won't load  either, since we need bars from TF2 but we can't get them. Since it is weekend, there aren't onCalculate events as they would be when the market is open. Thus, the final result is the indicator won't load. 

Some known solutions:

  1. To manually switch to a different timeframe than TF1, let's say to M5 (TF3) and then we switch back to TF1.  Sure enough, the terminal will have had time to load TF2 bars, and the indicator will fully reload. Since TF2 bars are ready, iBarShift won't fail again. 

  2. To use ChartSetSymbolPeriod(0, NULL, 0) as Alain Verleyen has suggested in several posts, like this one for example. This is similar to refresh the chart, but as I explained here, this can lead to some really strange behaviour: iBarShift alternates between failing and not, which doesn't make a lot of sense, really. 
    Another problem with this method is that it's like using a hammer to kill a fly, as Alain cleverly stated, because it will lead to reload all indicators for all charts using the considered symbol.


My solution: automatically switch to TF3 and back to TF1 using global terminal variables

In order to do this switch automatically we set a timer that would generate a OnTimer event. Before the  timer goes by, the client terminal will have some time to load bars in TF2 (and even sometimes it will have to load TF1 bars too).  When OnTimer gets called, we will use ChartSetSymbolPeriod to move to TF3.  

Given that the indicator is fully re-initialized after moving to a different TF, we have to use a global variable of the client terminal to be able to do the trick, because they don't get deleted after the indicator is re-initialized. In that global variable we will store the current Timeframe (TF1), so that we can return to it later.   Normal global variables, or static variables are deleted after an indicator gets re-initialized, so there would be no way to know to which TF to return to. 

After we have moved automatically to TF3, we check whether our global variable exists. If it exist, we don't calculate the indicator, and we just switch back to our original timeframe TF1. When we are again in TF1, we just delete the global terminal variable. 

I have tried this system with a very simple multitimeframe indicator that paints the closing value of the chosen higher timeframe candles. By switching back and forth between timeframes, the problem stated in point 2 above by Alain in the previous section dissapears. Now only the current indicator will be reloaded. I have tried to make the code clear enough so that it can be reusable:

#property copyright "Copyright 2020, Carlos"
#property indicator_chart_window
#property indicator_buffers 1    // buffers used
#property indicator_plots   1    // buffers displayed

//--- Indicator Properties
#property indicator_label1  "Close Value in Other TF"
#property indicator_type1   DRAW_LINE
#property indicator_color1  Blue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#ifndef _VERBOSE_
   #define _VERBOSE_
#endif

#define SECONDS_TO_WAIT 1

//--- input parameters
input ENUM_TIMEFRAMES other_TimeFrame = PERIOD_D1;

//--- Indicator buffer
double otherTF_Buffer[];

int offset;
string gvar_magic_name; 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- indicator buffers mapping
   SetIndexBuffer(0, otherTF_Buffer, INDICATOR_DATA);
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   double tf1 = PeriodSeconds(_Period);
   double tf2 = PeriodSeconds(other_TimeFrame);
   offset = (int)ceil(tf2/tf1);

   // Create a kind of unique variable name for this indicator: (chart ID number + indicator file name)
   gvar_magic_name = IntegerToString(ChartID())+ "_" + __FILE__;
   
   // Check if we have to change timeframes
   check_auto_timeframe_travel(gvar_magic_name);

   return(INIT_SUCCEEDED);
}


//+---------------------------------------------------------------------------------+
//| After SECONDS_TO_WAIT seconds have gone by, the Timer event is generated, and   |
//| we move to a different TF, from which we will be back to the original TF.       |
//+---------------------------------------------------------------------------------+
void OnTimer(void)
{
   move_to_a_different_TF(gvar_magic_name); 
}


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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[])
{
   int    start;
   int    shift;
   double c;     // close value in the other timeframe
   
   #ifdef _VERBOSE_
      Print("On Calculate ==> Rates total = ", rates_total, "; Prev_calculated = ", prev_calculated);
   #endif 
   
   //--- Detect start position
   if(prev_calculated == 0)
   {
      // If our global variable of the client terminal exists, it means that we have switched
      // temporarily to a different timeframe, so we don't need to calculate the indicator now. 
      if(GlobalVariableCheck(gvar_magic_name)) return 0;
      
      // Check if we can start reading bars in the TF we want to. If we can't, auto TF switch
      // will be initiated. 
      if(check_ibarshift_in_Other_TF(time[rates_total-1], other_TimeFrame, SECONDS_TO_WAIT) == false) return 0;

      start = 1;
      ArrayInitialize(otherTF_Buffer,EMPTY_VALUE);
   }
   else
   {
      start = prev_calculated - offset;
   }

   //--- Main loop
   #ifdef _VERBOSE_
      Print("   Before main loop: start = ", start);
   #endif
   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      shift = iBarShift(NULL, other_TimeFrame, time[i], false);
      if(shift < 0)
      {
         #ifdef _VERBOSE_
            Print("iBarShift error #", GetLastError());
         #endif
         return i;
      }

      c = iClose(NULL, other_TimeFrame, shift);
      if(c < 0)
      {
         #ifdef _VERBOSE_
            Print("iClose error #", GetLastError());
         #endif
         return i;
      }

      // Save the close value from the other timeframe
      otherTF_Buffer[i] = c;
   }

   //--- return value of prev_calculated for next call
   #ifdef _VERBOSE_
      Print("   Loop end, returning rates_total = ", rates_total);
   #endif
   return(rates_total);
}

//+------------------------------------------------------------------------------------------------------------+
//| Let's say we are in M1 (TF1) and we want to access bars from D1 (TF2). We try iBarShift in TF2, but we get |  
//| an error. That means TF2 bars are still not loaded in the client terminal, so the indicator doesn't load   |
//| either. What we would do manually, would be to switch to M5 (TF3) and back to TF1 (it could be TF3 = TF2,  |
//| but not necessarily).                                                                                      |
//|                                                                                                            |
//| In order to do this switch automatically we set a timer that would generate a OnTimer event. Before the    |
//| timer goes by, the client terminal will have some time to load bars in TF2 (and even sometimes it will     |
//| have to load TF1 bars too). When OnTimer gets called, we will use ChartSetSymbolPeriod to move to TF3.     | 
//+------------------------------------------------------------------------------------------------------------+
bool check_ibarshift_in_Other_TF(datetime t, ENUM_TIMEFRAMES tf, int seconds)
{
   int res; 
   static bool is_timer_already_set = false;

   // Test iBarShift for date 't' and get the result. If it doesn't work, it will return  WRONG_VALUE
   res = iBarShift(NULL, tf, t);
   
   #ifdef _VERBOSE_
      Print("   check_ibarshift(date = ", t, ") ==> iBarShift = ", res);
   #endif

   // If iBarShift is not ready and returns an error, then set a timer for making an automatic change
   // to another timeframe so that the candles have time to load. 
   if(res == WRONG_VALUE)
   {
      // This function can be entered several times before the timer
      // is called, so we just make sure to set the timer ONCE. 
      if(is_timer_already_set == false)
      {
         #ifdef _VERBOSE_
            Print("      Setting a ", SECONDS_TO_WAIT, " seconds Timer");
         #endif
         EventSetTimer(seconds);
         is_timer_already_set = true;
      }
      
      return false;
   }

   return true;
}


//+------------------------------------------------------------------------------------------------------------+
//| This function let us move to another TF, using ChartSetSymbolPeriod. If we are in TF1, we need to move to  |
//| TF2 (being TF1 != TF2, of course).                                                                         |
//| Given that the indicator is fully re-initialized after moving to a different TF, we have to use a global   |
//| variable of the client terminal to be able to do the trick, because they can stay up even for 4 weeks.     |
//| In that global variable , we store the current Timeframe, so that we can return to it later.               |
//+------------------------------------------------------------------------------------------------------------+
void move_to_a_different_TF(string gvar_name)
{
   ENUM_TIMEFRAMES TF_to_move_to = get_higher_tf(_Period);
   
   #ifdef _VERBOSE_
      Print("=> Moving to ", EnumToString(TF_to_move_to), " using ChartSetSymbolPeriod.");
   #endif
   
   GlobalVariableSet(gvar_name, _Period);
   ChartSetSymbolPeriod(0, NULL, TF_to_move_to);
}



//+------------------------------------------------------------------------------------------------------------+
//| Return the timeframe to the right of a given timeframe, taking into account just the classic set of TFs:   |
//| M1, M5, M15, M30, H1, H4, D1, W1, MN                                                                       |
//+------------------------------------------------------------------------------------------------------------+
ENUM_TIMEFRAMES get_higher_tf(ENUM_TIMEFRAMES tf)
{
   if     (tf < PERIOD_M5)  return PERIOD_M5;
   else if(tf < PERIOD_M15) return PERIOD_M15;
   else if(tf < PERIOD_M30) return PERIOD_M30;
   else if(tf < PERIOD_H1)  return PERIOD_H1;
   else if(tf < PERIOD_H4)  return PERIOD_H4;
   else if(tf < PERIOD_D1)  return PERIOD_D1;
   else if(tf < PERIOD_W1)  return PERIOD_W1;
   else if(tf < PERIOD_MN1) return PERIOD_MN1;
   else                     return PERIOD_W1; //If tf is MN1, we can't switch to the immediately higher timeframe
                                              // so we switch to the immediately lower timeframe instead.
}


//+------------------------------------------------------------------------------------------------------------+
//| If we have set a global variable of the client terminal, it means we are forcing a re-initialization of    |
//| the indicator by switching automatically back and forth to another TimeFrame.                              |
//|                                                                                                            |
//| If a global variable already exists, get its value, which is the original timeframe (TF1) where we have    |  
//| to return to. Here two different scenarios may arise:                                                      |
//|                                                                                                            |
//|    1) The current timeframe (TF2) is different from the original timeframe. That means we have moved to    |
//|       TF2 from TF1, and we have now to return to TF1.                                                      |
//|                                                                                                            |
//|    2) TF2 is the same as TF1. That means we are already back from the TF2. This means that trick of        |
//|       moving back and forth between timeframe is done. We only have to delete the global variable.         |
//+------------------------------------------------------------------------------------------------------------+
void check_auto_timeframe_travel(string gname)
{
   // 
   if(GlobalVariableCheck(gname))
   {
      // Recover the value we stored, which is the timeframe where we have to return to. 
      ENUM_TIMEFRAMES orig_tf = (ENUM_TIMEFRAMES) GlobalVariableGet(gname);
      
      if(_Period != orig_tf)
      {
         // If the current TF is different from the original TF, that means we have temporarily
         // auto-moved to another TF. We have to send the order to move back to the original TF.
         ChartSetSymbolPeriod(0, NULL, orig_tf);
      }
      else
      {
         GlobalVariableDel(gname);
         #ifdef _VERBOSE_
            Print("OnInit: we're back to ", EnumToString(orig_tf), ". Deleting global terminal variable.");
         #endif 
      }
   }
}

//+------------------------------------------------------------------------------------------------------------+
Documentation on MQL5: Global Variables of the Terminal
Documentation on MQL5: Global Variables of the Terminal
  • www.mql5.com
Global variables are kept in the client terminal for 4 weeks since the last access, then they will be deleted automatically. An access to a global variable is not only setting of a new value, but reading of the global variable value, as well.
Reason: