Trabajando con las series temporales en la biblioteca DoEasy (Parte 37): Colección de series temporales - Base de datos de series temporales según el símbolo y el periodo

25 junio 2020, 13:03
Artyom Trishkin
0
706

Contenido


Concepto

Hoy, vamos a crear un objeto de colección con las series temporales de los símbolos usados en el programa. Cada una de ellas contendrá los datos de los marcos temporales establecidos de un símbolo. Como resultado, obtendremos un objeto que contendrá todos los datos sobre el número de barras establecido para cada serie temporal de cada símbolo.

La colección de series temporales guardará todos los datos históricos necesarios de cada uno de los símbolos utilizados en el programa y de todos los marcos temporales que también se establecerán en los ajustes del programa.
Además de esto, la colección permitirá establecer los datos necesarios para cada marco temporal de cada símbolo de forma individual.

Debido a que la descripción de la funcionalidad creada para la colección de series temporales es muy voluminosa, implementaremos las actualizaciones en tiempo real de los datos de la colección y la obtención de todos los datos posibles de la colección en el próximo artículo.


Mejorando los objetos de series temporales creados anteriormente

La mayoría de los objetos de la biblioteca son herederos del objeto básico de todos los objetos de la biblioteca, que a su vez es heredado de la clase básica para construir la Biblioteca estándar MQL5.
A medida que han aumentado las demandas de la biblioteca, ha crecido también la clase CBaseObj del objeto básico de la biblioteca, y ahora, si queremos heredar nuevos objetos de él, estos obtendrán métodos adicionales, que no siempre resultan necesarios.
Para resolver este problema, dividiremos la clase del objeto básico en dos:

  • la primera (CBaseObj) contendrá el conjunto mínimo imprescindible de propiedades y métodos para cada objeto de la biblioteca,
  • la segunda será ampliada (CBaseObjExt), descendiente de CBaseObj, y contendrá las propiedades y métodos para actuar de forma interactiva con el usuario y la funcionalidad de eventos de los objetos herederos.

De esta forma, los objetos que necesiten las propiedades y métodos básicos, los heredaremos de CBaseObj, mientras que los objetos que necesiten la funcionalidad de eventos, los heredaremos de CBaseObjExt.

¿Cómo lo haremos? Para empezar, solo tenemos que renombrar la clase del objeto básico CBaseObj que se encuentra en el archivo \MQL5\Include\DoEasy\Objects\BaseObj.mqh como la clase con el nombre CBaseObjExt y compilar el archivo del objeto principal de la biblioteca CEngine que se ubica en la dirección \MQL5\Include\DoEasy\Engine.mqh, lo cual provocará una larga lista de errores de compilación (hemos renombrado la clase del objeto básico de la biblioteca).

Simplemente iteraremos por la lista con todos los errores que indican la ausencia de la clase CBaseObj, y sustituiremos en los listados de las clases todas las entradas de las líneas "CBaseObj" por "CBaseObjExt". La nueva compilación con las denominaciones de la clase del objeto básico ya corregidas deberá realizarse con éxito.

Ahora, en el listado de la clase del objeto básico, añadimos una nueva clase que llamaremos CBaseObj, heredamos esta del objeto básico de la biblioteca MQL5, y después trasladamos desde la clase CBaseObjExt todas las variables y métodos que deben estar en la nueva clase del objeto básico, mientras que la clase CBaseObjExt la heredamos de CBaseObj.

Parece complicado, pero si echamos un vistazo al listado de las clases, todo resultará comprensible (no tiene sentido mostrar el listado completo, el lector podrá estudiarlo en los archivos adjuntos al artículo):

//+------------------------------------------------------------------+
//| Base object class for all library objects                        |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   ENUM_LOG_LEVEL    m_log_level;                              // Logging level
   ENUM_PROGRAM_TYPE m_program;                                // Program type
   bool              m_first_start;                            // First launch flag
   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   int               m_global_error;                           // Global error code
   long              m_chart_id_main;                          // Control program chart ID
   long              m_chart_id;                               // Chart ID
   string            m_name;                                   // Object name
   string            m_folder_name;                            // Name of the folder storing CBaseObj descendant objects 
   string            m_sound_name;                             // Object sound file name
   int               m_type;                                   // Object type (corresponds to the collection IDs)

public:
//--- (1) Set, (2) return the error logging level
   void              SetLogLevel(const ENUM_LOG_LEVEL level)         { this.m_log_level=level;                 }
   ENUM_LOG_LEVEL    GetLogLevel(void)                         const { return this.m_log_level;                }
//--- (1) Set and (2) return the chart ID of the control program
   void              SetMainChartID(const long id)                   { this.m_chart_id_main=id;                }
   long              GetMainChartID(void)                      const { return this.m_chart_id_main;            }
//--- (1) Set and (2) return chart ID
   void              SetChartID(const long id)                       { this.m_chart_id=id;                     }
   long              GetChartID(void)                          const { return this.m_chart_id;                 }
//--- (1) Set the sub-folder name, (2) return the folder name for storing descendant object files
   void              SetSubFolderName(const string name)             { this.m_folder_name=DIRECTORY+name;      }
   string            GetFolderName(void)                       const { return this.m_folder_name;              }
//--- (1) Set and (2) return the name of the descendant object sound file
   void              SetSoundName(const string name)                 { this.m_sound_name=name;                 }
   string            GetSoundName(void)                        const { return this.m_sound_name;               }
//--- (1) Set and (2) return the flag of playing descendant object sounds
   void              SetUseSound(const bool flag)                    { this.m_use_sound=flag;                  }
   bool              IsUseSound(void)                          const { return this.m_use_sound;                }
//--- (1) Set and (2) return the flag of using the descendant object in the program
   void              SetAvailable(const bool flag)                   { this.m_available=flag;                  }
   bool              IsAvailable(void)                         const { return this.m_available;                }
//--- Return the global error code
   int               GetError(void)                            const { return this.m_global_error;             }
//--- Return the object name
   string            GetName(void)                             const { return this.m_name;                     }
//--- Return an object type
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Constructor
                     CBaseObj() : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)),
                                  m_global_error(ERR_SUCCESS),
                                  m_log_level(LOG_LEVEL_ERROR_MSG),
                                  m_chart_id_main(::ChartID()),
                                  m_chart_id(::ChartID()),
                                  m_folder_name(DIRECTORY),
                                  m_sound_name(""),
                                  m_name(__FUNCTION__),
                                  m_type(0),
                                  m_use_sound(false),
                                  m_available(true),
                                  m_first_start(true) {}
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Extended base object class for all library objects               |
//+------------------------------------------------------------------+
#define  CONTROLS_TOTAL    (10)
class CBaseObjExt : public CBaseObj
  {
private:
   int               m_long_prop_total;
   int               m_double_prop_total;
   //--- Fill in the object property array
   template<typename T> bool  FillPropertySettings(const int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],int &event_id);
protected:
   CArrayObj         m_list_events_base;                       // Object base event list
   CArrayObj         m_list_events;                            // Object event list
   MqlTick           m_tick;                                   // Tick structure for receiving quote data
   double            m_hash_sum;                               // Object data hash sum
   double            m_hash_sum_prev;                          // Object data hash sum during the previous check
   int               m_digits_currency;                        // Number of decimal places in an account currency
   bool              m_is_event;                               // Object event flag
   int               m_event_code;                             // Object event code
   int               m_event_id;                               // Event ID (equal to the object property value)

//--- Data for storing, controlling and returning tracked properties:
//--- [Property index][0] Controlled property increase value
//--- [Property index][1] Controlled property decrease value
//--- [Property index][2] Controlled property value level
//--- [Property index][3] Property value
//--- [Property index][4] Property value change
//--- [Property index][5] Flag of a property change exceeding the increase value
//--- [Property index][6] Flag of a property change exceeding the decrease value
//--- [Property index][7] Flag of a property increase exceeding the control level
//--- [Property index][8] Flag of a property decrease being less than the control level
//--- [Property index][9] Flag of a property value being equal to the control level
   long              m_long_prop_event[][CONTROLS_TOTAL];         // The array for storing object's integer properties values and controlled property change values
   double            m_double_prop_event[][CONTROLS_TOTAL];       // The array for storing object's real properties values and controlled property change values
   long              m_long_prop_event_prev[][CONTROLS_TOTAL];    // The array for storing object's controlled integer properties values during the previous check
   double            m_double_prop_event_prev[][CONTROLS_TOTAL];  // The array for storing object's controlled real properties values during the previous check

//--- Return (1) time in milliseconds, (2) milliseconds from the MqlTick time value
   long              TickTime(void)                            const { return #ifdef __MQL5__ this.m_tick.time_msc #else this.m_tick.time*1000 #endif ;  }
   ushort            MSCfromTime(const long time_msc)          const { return #ifdef __MQL5__ ushort(this.TickTime()%1000) #else 0 #endif ;              }
//--- return the flag of the event code presence in the event object
   bool              IsPresentEventFlag(const int change_code) const { return (this.m_event_code & change_code)==change_code; }
//--- Return the number of decimal places of the account currency
   int               DigitsCurrency(void)                      const { return this.m_digits_currency; }
//--- Returns the number of decimal places in the 'double' value
   int               GetDigits(const double value)             const;

//--- Set the size of the array of controlled (1) integer and (2) real object properties
   bool              SetControlDataArraySizeLong(const int size);
   bool              SetControlDataArraySizeDouble(const int size);
//--- Check the array size of object properties
   bool              CheckControlDataArraySize(bool check_long=true);
   
//--- Check the list of object property changes and create an event
   void              CheckEvents(void);
   
//--- (1) Pack a 'ushort' number to a passed 'long' number
   long              UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value);
   
protected:
//--- (1) convert a 'ushort' value to a specified 'long' number byte
   long              UshortToByte(const ushort value,const uchar to_byte)  const;
   
public:
//--- Set the value of the pbject property controlled (1) increase, (2) decrease, (3) control level
   template<typename T> void  SetControlledValueINC(const int property,const T value);
   template<typename T> void  SetControlledValueDEC(const int property,const T value);
   template<typename T> void  SetControlledValueLEVEL(const int property,const T value);

//--- Return the set value of the controlled (1) integer and (2) real object properties increase
   long              GetControlledLongValueINC(const int property)      const { return this.m_long_prop_event[property][0];                           }
   double            GetControlledDoubleValueINC(const int property)    const { return this.m_double_prop_event[property-this.m_long_prop_total][0];  }
//--- Return the set value of the controlled (1) integer and (2) real object properties decrease
   long              GetControlledLongValueDEC(const int property)      const { return this.m_long_prop_event[property][1];                           }
   double            GetControlledDoubleValueDEC(const int property)    const { return this.m_double_prop_event[property-this.m_long_prop_total][1];  }
//--- Return the specified control level of object's (1) integer and (2) real properties
   long              GetControlledLongValueLEVEL(const int property)    const { return this.m_long_prop_event[property][2];                           }
   double            GetControlledDoubleValueLEVEL(const int property)  const { return this.m_double_prop_event[property-this.m_long_prop_total][2];  }

//--- Return the current value of the object (1) integer and (2) real property
   long              GetPropLongValue(const int property)               const { return this.m_long_prop_event[property][3];                           }
   double            GetPropDoubleValue(const int property)             const { return this.m_double_prop_event[property-this.m_long_prop_total][3];  }
//--- Return the change value of the controlled (1) integer and (2) real object property
   long              GetPropLongChangedValue(const int property)        const { return this.m_long_prop_event[property][4];                           }
   double            GetPropDoubleChangedValue(const int property)      const { return this.m_double_prop_event[property-this.m_long_prop_total][4];  }
//--- Return the flag of an (1) integer and (2) real property value change exceeding the increase value
   long              GetPropLongFlagINC(const int property)             const { return this.m_long_prop_event[property][5];                           }
   double            GetPropDoubleFlagINC(const int property)           const { return this.m_double_prop_event[property-this.m_long_prop_total][5];  }
//--- Return the flag of an (1) integer and (2) real property value change exceeding the decrease value
   long              GetPropLongFlagDEC(const int property)             const { return this.m_long_prop_event[property][6];                           }
   double            GetPropDoubleFlagDEC(const int property)           const { return this.m_double_prop_event[property-this.m_long_prop_total][6];  }
//--- Return the flag of an (1) integer and (2) real property value increase exceeding the control level
   long              GetPropLongFlagMORE(const int property)            const { return this.m_long_prop_event[property][7];                           }
   double            GetPropDoubleFlagMORE(const int property)          const { return this.m_double_prop_event[property-this.m_long_prop_total][7];  }
//--- Return the flag of an (1) integer and (2) real property value decrease being less than the control level
   long              GetPropLongFlagLESS(const int property)            const { return this.m_long_prop_event[property][8];                           }
   double            GetPropDoubleFlagLESS(const int property)          const { return this.m_double_prop_event[property-this.m_long_prop_total][8];  }
//--- Return the flag of an (1) integer and (2) real property being equal to the control level
   long              GetPropLongFlagEQUAL(const int property)           const { return this.m_long_prop_event[property][9];                           }
   double            GetPropDoubleFlagEQUAL(const int property)         const { return this.m_double_prop_event[property-this.m_long_prop_total][9];  }

//--- Reset the variables of (1) tracked and (2) controlled object data (can be reset in the descendants)
   void              ResetChangesParams(void);
   virtual void      ResetControlsParams(void);
//--- Add the (1) object event and (2) the object event reason to the list
   bool              EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam);
   bool              EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value);
//--- Set/return the occurred event flag to the object data
   void              SetEvent(const bool flag)                       { this.m_is_event=flag;                   }
   bool              IsEvent(void)                             const { return this.m_is_event;                 }
//--- Return (1) the list of events, (2) the object event code and (3) the global error code
   CArrayObj        *GetListEvents(void)                             { return &this.m_list_events;             }
   int               GetEventCode(void)                        const { return this.m_event_code;               }
//--- Return (1) an event object and (2) a base event by its number in the list
   CEventBaseObj    *GetEvent(const int shift=WRONG_VALUE,const bool check_out=true);
   CBaseEvent       *GetEventBase(const int index);
//--- Return the number of (1) object events
   int               GetEventsTotal(void)                      const { return this.m_list_events.Total();      }
//--- Update the object data to search for changes (Calling from the descendants: CBaseObj::Refresh())
   virtual void      Refresh(void);
//--- Return an object event description
   string            EventDescription(const int property,
                                      const ENUM_BASE_EVENT_REASON reason,
                                      const int source,
                                      const string value,
                                      const string property_descr,
                                      const int digits);

//--- Data location in the magic number int value
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------
      
//--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value
   void              SetGroupID1(const uchar group,uint &magic)            { magic &=0xFFF0FFFF; magic |= uint(this.ConvToXX(group,0)<<16);  }
   void              SetGroupID2(const uchar group,uint &magic)            { magic &=0xFF0FFFFF; magic |= uint(this.ConvToXX(group,1)<<16);  }
   void              SetPendReqID(const uchar id,uint &magic)              { magic &=0x00FFFFFF; magic |= (uint)id<<24;                      }
//--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones)
   uchar             ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));   }
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(const uint magic)                    const { return ushort(magic & 0xFFFF);                                  }
   uchar             GetGroupID1(const uint magic)                   const { return uchar(magic>>16) & 0x0F;                                 }
   uchar             GetGroupID2(const uint magic)                   const { return uchar((magic>>16) & 0xF0)>>4;                            }
   uchar             GetPendReqID(const uint magic)                  const { return uchar(magic>>24) & 0xFF;                                 }

//--- Constructor
                     CBaseObjExt();
  };
//+------------------------------------------------------------------+

En la clase del nuevo objeto básico de todos los objetos de la biblioteca, ahora se declaran tres nuevas variables de miembro de clase:

   bool              m_use_sound;                              // Flag of playing the sound set for an object
   bool              m_available;                              // Flag of using a descendant object in the program
   string            m_sound_name;                             // Object sound file name

y se han añadido los métodos correspondientes para establecer y retornar los valores de estas variables:

//--- (1) Set and (2) return the name of the descendant object sound file
   void              SetSoundName(const string name)                 { this.m_sound_name=name;                 }
   string            GetSoundName(void)                        const { return this.m_sound_name;               }
//--- (1) Set and (2) return the flag of playing descendant object sounds
   void              SetUseSound(const bool flag)                    { this.m_use_sound=flag;                  }
   bool              IsUseSound(void)                          const { return this.m_use_sound;                }
//--- (1) Set and (2) return the flag of using the descendant object in the program
   void              SetAvailable(const bool flag)                   { this.m_available=flag;                  }
   bool              IsAvailable(void)                         const { return this.m_available;                }

El nombre del archivo de sonido para el objeto heredero permitirá establecer (SetSoundName()) u obtener (GetSoundName()) el nombre del archivo de sonido del objeto, que podrá ser reproducido al darse la condición de la propiedad de control de este objeto.

Además de poder asignar un nombre al objeto, podremos activar/desactivar (SetUseSound()) el permiso de reproducción de este archivo y obtener la bandera de permiso establecido para la reproducción del archivo (IsUseSound()).

Qué indica la "bandera de uso del objeto heredero en el programa", y para qué necesitamos establecerla y obtenerla...
Por ejemplo, tenemos los objetos de las series temporales del símbolo para los periodos М5, М30, Н1 y D1. Pero, en algún momento, no queremos procesar la serie temporal М5. Así, estableciendo/quitando esta bandera, podremos gestionar la necesidad de controlar la biblioteca de eventos, por ejemplo, de una nueva barra para la serie temporal М5.
La presencia de esta bandera en el objeto básico de todos los objetos de la biblioteca nos permitirá gestionar de forma flexible la necesidad de procesar los estados de las propiedades de estos objetos. En otras palabras: si debemos controlar y usar un objeto en el programa, estableceremos la bandera; si no tenemos esa necesidad, quitaremos la bandera.

Naturalmente, también han sido modificados los constructores de las clases: hemos eliminado las variables trasladadas a la nueva clase, al tiempo que inicializamos todas las variables en la nueva clase. El lector podrá familiarizarse con los cambios estudiando los archivos adjuntos al artículo.

Qué clases se han heredado ahora del objeto básico ampliado CBaseObjExt:

  • CAccountsCollection en el archivo \MQL5\Include\DoEasy\Collections\AccountsCollection.mqh
  • CEventsCollection en el archivo \MQL5\Include\DoEasy\Collections\EventsCollection.mqh
    (se han sustituido las líneas "CBaseObj::EventAdd" por "CBaseObjExt::EventAdd")
  • CSymbolsCollection en el archivo \MQL5\Include\DoEasy\Collections\SymbolsCollection.mqh
  • CAccount en el archivo \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh
    (se han sustituido las líneas "CBaseObj::Refresh()" por "CBaseObjExt::Refresh()")
  • COrder en el archivo \MQL5\Include\DoEasy\Objects\Orders\Order.mqh
  • CPendRequest en el archivo \MQL5\Include\DoEasy\Objects\PendRequest\PendRequest.mqh
    (se han sustituido las líneas "return CBaseObj::GetMagicID" por "return CBaseObjExt::GetMagicID",
     CBaseObj::GetGroupID1" por "return CBaseObjExt::GetGroupID1" y CBaseObj::GetGroupID2" por "return CBaseObjExt::GetGroupID2")
  • CSymbol en el archivo \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh
    (se han sustituido las líneas "CBaseObj::Refresh()" por "CBaseObjExt::Refresh()")
  • CTradeObj en el archivo \MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh
    (se ha eliminado la variable bool m_use_sound y los métodos SetUseSound() y IsUseSound(), ahora se encuentran en la clase básica)
  • CTrading en el archivo \MQL5\Include\DoEasy\Trading.mqh
    (se ha eliminado la variable bool m_use_sound y el método IsUseSounds(), ahora se encuentran en la clase básica)

Antes de mejorar las clases de los objetos de series temporales ya creadas, vamos a añadir los datos necesarios al archivo Datas.mqh, una nueva macrosustitución que indica el separador en la línea de la lista de los símbolos y marcos temporales utilizados en los parámetros de entrada del programa, la enumeración de los modos de trabajo con los marcos temporales, y los índices de los nuevos mensajes, además de los textos de los mensajes que se corresponden con los índices declarados:

//+------------------------------------------------------------------+
//|                                                        Datas.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
//+------------------------------------------------------------------+
//| Macrosustituciones                                               |
//+------------------------------------------------------------------+
#define INPUT_SEPARATOR                (",")          // Separator in the inputs string
#define TOTAL_LANG                     (2)            // Number of used languages
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work with the current symbol only
   SYMBOLS_MODE_DEFINES,                              // Work with the specified symbol list
   SYMBOLS_MODE_MARKET_WATCH,                         // Work with the Market Watch window symbols
   SYMBOLS_MODE_ALL                                   // Work with the full symbol list
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work with the current timeframe only
   TIMEFRAMES_MODE_LIST,                              // Work with the specified timeframe list
   TIMEFRAMES_MODE_ALL                                // Work with the full timeframe list
  };
//+------------------------------------------------------------------+

   MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING,            // Error. Predefined symbols string empty, to be used
   MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY,        // Failed to prepare array of used symbols. Error 
   MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING,            // Error. The string of predefined periods is empty and is to be used
   MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY,        // Failed to prepare array of used periods. Error 
   MSG_LIB_SYS_INVALID_ORDER_TYPE,                    // Invalid order type:

...

//--- CTimeSeries
   MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL,              // First, set a symbol using SetSymbol()
   MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME,            // Unknown timeframe
   MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ,             // Failed to receive the timeseries object
   MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH,            // Requested history depth
   MSG_LIB_TEXT_TS_ACTUAL_DEPTH,                      // Actual history depth
   MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA,               // Created historical data
   MSG_LIB_TEXT_TS_HISTORY_BARS,                      // Number of history bars on the server
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES,            // Symbol timeseries
   MSG_LIB_TEXT_TS_TEXT_TIMESERIES,                   // Timeseries
   MSG_LIB_TEXT_TS_TEXT_REQUIRED,                     // Requested
   MSG_LIB_TEXT_TS_TEXT_ACTUAL,                       // Actual
   MSG_LIB_TEXT_TS_TEXT_CREATED,                      // Created
   MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS,                 // On the server
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_FIRSTDATE,             // The very first date by a period symbol
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_LASTBAR_DATE,          // Time of opening the last bar by period symbol
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_SERVER_FIRSTDATE,      // The very first date in history by a server symbol
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_TERMINAL_FIRSTDATE,    // The very first date in history by a symbol in the client terminal
  
};
//+------------------------------------------------------------------+

...

   {"Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols is empty, the Symbol will be used: "},
   {"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create an array of used symbols. Error "},
   {"Ошибка. Строка предопределённых периодов пустая, будет использоваться ","Error. String of predefined periods is empty, the Period will be used: "},
   {"Не удалось подготовить массив используемых периодов. Ошибка ","Failed to create an array of used periods. Error "},
   {"Неправильный тип ордера: ","Invalid order type: "},

...
   {"Сначала нужно установить символ при помощи SetSymbol()","First you need to set the Symbol using SetSymbol()"},
   {"Неизвестный таймфрейм","Unknown timeframe"},
   {"Не удалось получить объект-таймсерию ","Failed to get timeseries object "},
   {"Запрошенная глубина истории: ","Required history depth: "},
   {"Фактическая глубина истории: ","Actual history depth: "},
   {"Создано исторических данных: ","Total historical data created: "},
   {"Баров истории на сервере: ","Server history Bars number: "},
   {"Таймсерия символа","Symbol time series"},
   {"Таймсерия","Timeseries"},
   {"Запрошено","Required"},
   {"Фактически","Actual"},
   {"Создано","Created"},
   {"На сервере","On server"},
   {"Самая первая дата по символу-периоду","The very first date for the symbol-period"},
   {"Время открытия последнего бара по символу-периоду","Open time of the last bar of the symbol-period"},
   {"Самая первая дата в истории по символу на сервере","The very first date in the history of the symbol on the server"},
   {"Самая первая дата в истории по символу в клиентском терминале","The very first date in the history of the symbol in the client terminal"},
  
};
//+---------------------------------------------------------------------+

Ya hemos escrito todos los datos necesarios para introducir las mejoras escritas anteriormente (de las clases de las series temporales) y para crear la colección de todas las series temporales.

Vamos a mejorar la clase de objeto temporal del símbolo para un marco temporal de CSeries.
Añadimos a la sección de la clase cuatro nuevas variables y un método para establecer los valores de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeries : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   string            m_symbol;                                          // Symbol
   string            m_period_description;                              // Timeframe string description
   datetime          m_firstdate;                                       // The very first date by a period symbol at the moment
   datetime          m_lastbar_date;                                    // Time of opening the last bar by period symbol
   uint              m_amount;                                          // Amount of applied timeseries data
   uint              m_required;                                        // Required amount of applied timeseries data
   uint              m_bars;                                            // Number of bars in history by symbol and timeframe
   bool              m_sync;                                            // Synchronized data flag
   CArrayObj         m_list_series;                                     // Timeseries list
   CNewBarObj        m_new_bar_obj;                                     // "New bar" object
//--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol
   void              SetServerDate(void)
                       {
                        this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
                        this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
                       }
public:

Vamos a anotar en la variable m_period_description la descripción del periodo del gráfico de la serie temporal justo al construir el objeto de la clase en el constructor y en los métodos que establecen el marco temporal para el objeto de serie temporal. Lo hemos hecho así para no tener que recurrir cada vez a la función TimeframeDescription() desde el archivo de funciones de servicio DELib.mqh de la biblioteca: la función busca la subcadena en la descripción de línea del marco temporal de la enumeración ENUM_TIMEFRAMES, lo cual no posibilita una alta velocidad de ejecución. Por eso, al construir los objetos de la biblioteca, resulta mejor ejecutar directamente las funciones más laboriosas, en el caso de que estos datos no vayan a cambiar, o bien vayan a hacerlo rara vez cuando el programa los solicite.

La variable m_firstdate guardará la primera fecha del símbolo-periodo en este momento, obtenida mediante la función SeriesInfoInteger() con el identificador de propiedad SERIES_FIRSTDATE.
La variable m_lastbar_date guardará la hora de apertura de la última barra del símbolo-periodo, obtenida mediante la función SeriesInfoInteger() con el identificador de propiedad SERIES_LASTBAR_DATE.
Ambas variables se establecerán llamando al método SetServerDate(), solo en el momento en que se cree el objeto de clase o cambien los datos en la nueva barra, así como al asignar un nuevo símbolo o marco temporal al objeto de serie temporal.

La variable m_required guardará el número necesario (el último solicitado) de datos utilizados para la serie temporal. Al momento de solicitar el número necesario de barras de la serie temporal, podría resultar que la cantidad de datos solicitados para crear la serie temporal simplemente no se encuentre en el servidor. En este caso, la serie temporal se creará usando un número de datos igual a la historia disponible en el servidor. Y en esta variable siempre se guardará la última cantidad de datos solicitado, independientemente de cuántos datos hayamos conseguido obtener y crear en realidad. Por cierto, partiendo de esto, utilizamos el concepto "datos solicitados". En la clase ha cambiado el nombre de los métodos en los que existía "Amount" (cantidad): ahora, ha sido sustituido por "Required" (solicitado, requerido).

Asimismo, en la sección pública de la clase se han añadido nuevos métodos:

public:
//--- Return (1) oneself and (2) the timeseries list
   CSeries          *GetObject(void)                                    { return &this;         }
   CArrayObj        *GetList(void)                                      { return &m_list_series;}
//--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition
   CArrayObj        *GetList(ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByBarProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); }

//--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data
   void              SetSymbol(const string symbol);
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe);
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool              SetRequiredUsedData(const uint required,const uint rates_total);

//--- Return (1) symbol, (2) timeframe, number of (3) used and (4) requested timeseries data,
//--- (5) number of bars in the timeseries, (6) the very first date, (7) time of opening the last bar by a symbol period,
//--- new bar flag with (8) automatic and (9) manual time management
   string            Symbol(void)                                          const { return this.m_symbol;                            }
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe;                         }
   ulong             AvailableUsedData(void)                               const { return this.m_amount;                            }
   ulong             RequiredUsedData(void)                                const { return this.m_required;                          }
   ulong             Bars(void)                                            const { return this.m_bars;                              }
   datetime          FirstDate(void)                                       const { return this.m_firstdate;                         }
   datetime          LastBarDate(void)                                     const { return this.m_lastbar_date;                      }
   bool              IsNewBar(const datetime time)                               { return this.m_new_bar_obj.IsNewBar(time);        }
   bool              IsNewBarManual(const datetime time)                         { return this.m_new_bar_obj.IsNewBarManual(time);  }
//--- Return the bar object by index (1) in the list and (2) in the timeseries, as well as (3) the real list size
   CBar             *GetBarByListIndex(const uint index);
   CBar             *GetBarBySeriesIndex(const uint index);
   int               DataTotal(void)                                       const { return this.m_list_series.Total();               }
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index
   double            Open(const uint index,const bool from_series=true);
   double            High(const uint index,const bool from_series=true);
   double            Low(const uint index,const bool from_series=true);
   double            Close(const uint index,const bool from_series=true);
   datetime          Time(const uint index,const bool from_series=true);
   long              TickVolume(const uint index,const bool from_series=true);
   long              RealVolume(const uint index,const bool from_series=true);
   int               Spread(const uint index,const bool from_series=true);

//--- (1) Set and (2) return the sound of a sound file of the "New bar" timeseries event
   void              SetNewBarSoundName(const string name)                       { this.m_new_bar_obj.SetSoundName(name);           }
   string            NewBarSoundName(void)                                 const { return this.m_new_bar_obj.GetSoundName();        }

//--- Save the new bar time during the manual time management
   void              SaveNewBarTime(const datetime time)                         { this.m_new_bar_obj.SaveNewBarTime(time);         }
//--- Synchronize symbol and timeframe data with server data
   bool              SyncData(const uint required,const uint rates_total);
//--- (1) Create and (2) update the timeseries list
   int               Create(const uint required=0);
   void              Refresh(const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
                             
//--- Return the timeseries name
   string            Header(void);
//--- Display (1) the timeseries description and (2) the brief timeseries description in the journal
   void              Print(void);
   void              PrintShort(void);


//--- Constructors
                     CSeries(void);
                     CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
  };
//+------------------------------------------------------------------+

El método GetObject() retorna al programa de control el puntero al objeto de serie temporal completo. Este nos permite obtener al completo un objeto de serie temporal y trabajar con él en nuestro programa.

El método RequiredUsedData() retorna al programa que realiza la llamada el valor de la variable m_required, que hemos analizado anteriormente.

Los métodos FirstDate() y LastBarDate() retornan respectivamente los valores de las variables m_firstdate y m_lastbar_date, que hemos analizado con anterioridad.  

El método SetNewBarSoundName() establece el nombre del archivo de sonido para el objeto CNewBarObj "Nueva barra", incluido en el objeto de serie temporal.
El método NewBarSoundName() retorna el nombre del archivo de sonido designado para el objeto CNewBarObj "Nueva barra", incluido en el objeto de serie temporal.
Los métodos permiten asignar un sonido a cualquier objeto de serie temporal que se reproduzca al detectarse el evento "Nueva barra".

El método Header() crea y retorna la denominación breve de un objeto de serie temporal:

//+------------------------------------------------------------------+
//| Return the timeseries name                                       |
//+------------------------------------------------------------------+
string CSeries::Header(void)
  {
   return CMessage::Text(MSG_LIB_TEXT_TS_TEXT_TIMESERIES)+" \""+this.m_symbol+"\" "+this.m_period_description;
  }
//+------------------------------------------------------------------+

Desde el método, se retorna la descripción de línea de una serie temporal en forma de

Timeseries "SYMBOL" TIMEFRAME_DESCRIPTION

Por ejemplo:

Timeseries "AUDUSD" M15

El método Print() muestra en el diario la descripción completa de una serie temporal:

//+------------------------------------------------------------------+
//| Display the timeseries description in the journal                |
//+------------------------------------------------------------------+
void CSeries::Print(void)
  {
   string txt=
     (
      CMessage::Text(MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH)+(string)this.RequiredUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_ACTUAL_DEPTH)+(string)this.AvailableUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA)+(string)this.DataTotal()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_HISTORY_BARS)+(string)this.Bars()
     );
   ::Print(this.Header(),": ",txt);
  }
//+------------------------------------------------------------------+

Imprime en el diario los datos de una serie temporal en forma de

HEADER: HISTORY_DEPTH: XXXX, ACTUAL_DEPTH: XXXX, AMOUNT_HISTORY_DATA: XXXX, HISTORY_BARS: XXXX

Por ejemplo:

Timeseries "AUDUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1400

Timeseries "AUDUSD" MN1: Requested history depth: 1000, Actual history depth: 322, Historical data created: 322, History bars on the server: 322

El método PrintShort() muestra en el diario la descripción breve de una serie temporal:

//+------------------------------------------------------------------+
//| Display a short timeseries description in the journal            |
//+------------------------------------------------------------------+
void CSeries::PrintShort(void)
  {
   string txt=
     (
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_REQUIRED)+": "+(string)this.RequiredUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_ACTUAL)+": "+(string)this.AvailableUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED)+": "+(string)this.DataTotal()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS)+": "+(string)this.Bars()
     );
   ::Print(this.Header(),": ",txt);
  }
//+------------------------------------------------------------------+

Imprime en el diario los datos de una serie temporal en forma de

HEADER: REQUIRED: XXXX, ACTUAL: XXXX, CREATED: XXXX, HISTORY_BARS: XXXX

Por ejemplo:

Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562

Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589

En ambos constructores de la clase, añadimos el guardado de la descripción del marco temporal y el establecimiento de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Constructor 1 (current symbol and period timeseries)             |
//+------------------------------------------------------------------+
CSeries::CSeries(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false)
  {
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period());
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+
//| Constructor 2 (specified symbol and period timeseries)           |
//+------------------------------------------------------------------+
CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false)
  {
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetRequiredUsedData(required,0);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+

En el método para establecer el símbolo, añadimos la comprobación del mismo símbolo y el establecimiento de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Set a symbol                                                     |
//+------------------------------------------------------------------+
void CSeries::SetSymbol(const string symbol)
  {
   if(this.m_symbol==symbol)
      return;
   this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);
   this.m_new_bar_obj.SetSymbol(this.m_symbol);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+

Aquí, si se ha transmitido al método un símbolo ya utilizado en el objeto, no necesitamos establecer nada: salimos del método.

En el método para establecer el marco temporal, añadimos la comprobación del mismo marco temporal y el establecimiento de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Set a timeframe                                                  |
//+------------------------------------------------------------------+
void CSeries::SetTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_timeframe==timeframe)
      return;
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe);
   this.m_new_bar_obj.SetPeriod(this.m_timeframe);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+

Aquí, si se ha transmitido al método un marco temporal ya utilizado en el objeto, no necesitamos establecer nada: salimos del método.

Cambiamos el método para establecer el símbolo y el marco temporal:

//+------------------------------------------------------------------+
//| Set a symbol and timeframe                                       |
//+------------------------------------------------------------------+
void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_symbol==symbol && this.m_timeframe==timeframe)
      return;
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
  }
//+------------------------------------------------------------------+

Aquí, si se han transmitido al método el mismo símbolo y marco temporal, no tenemos que cambiar nada, salimos del método.
A continuación, llamamos los métodos para establecer el símbolo y el marco temporal.

En el método para establecer la profundidad de la historia de la serie temporal, guardamos el valor solicitado de profundidad de la historia:

//+------------------------------------------------------------------+
//| Set the number of required data                                  |
//+------------------------------------------------------------------+
bool CSeries::SetRequiredUsedData(const uint required,const uint rates_total)
  {
   this.m_required=(required==0 ? SERIES_DEFAULT_BARS_COUNT : required);
//--- Set the number of available timeseries bars
   this.m_bars=(uint)
     (
      //--- If this is an indicator and the work is performed on the current symbol and timeframe,
      //--- add the rates_total value passed to the method,
      //--- otherwise, get the number from the environment
      this.m_program==PROGRAM_INDICATOR && 
      this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? rates_total : 
      ::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT)
     );
//--- If managed to set the number of available history, set the amount of data in the list:
   if(this.m_bars>0)
     {
      //--- if zero 'required' value is passed,
      //--- use either the default value (1000 bars) or the number of available history bars - the least one of them
      //--- if non-zero 'required' value is passed,
      //--- use either the 'required' value or the number of available history bars - the least one of them
      this.m_amount=(required==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(required,this.m_bars));
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Si se ha transmitido un valor required igual a cero, solicitaremos la historia en la cantidad por defecto (1000 barras), establecida con la macrosustitución SERIES_DEFAULT_BARS_COUNT en el archivo Define.mqh, de lo contrario, el valor transmitido será required.

Añadimos al método de actualización de la serie temporal la comprobación del uso de esta serie temporal en el programa
si la serie temporal no se usa, no deberemos actualizar nada:

//+------------------------------------------------------------------+
//| Update timeseries list and data                                  |
//+------------------------------------------------------------------+
void CSeries::Refresh(const datetime time=0,
                      const double open=0,
                      const double high=0,
                      const double low=0,
                      const double close=0,
                      const long tick_volume=0,
                      const long volume=0,
                      const int spread=0)
  {
//--- If the timeseries is not used, exit
   if(!this.m_available)
      return;
   MqlRates rates[1];
//--- Set the flag of sorting the list of bars by index
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
//--- If a new bar is present on a symbol and period,
   if(this.IsNewBarManual(time))
     {
      //--- create a new bar object and add it to the end of the list
      CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,0);
      if(new_bar==NULL)
         return;
      if(!this.m_list_series.Add(new_bar))
        {
         delete new_bar;
         return;
        }
      //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol 
      this.SetServerDate();
      //--- if the timeseries exceeds the requested number of bars, remove the earliest bar
      if(this.m_list_series.Total()>(int)this.m_required)
         this.m_list_series.Delete(0);
      //--- save the new bar time as the previous one for the subsequent new bar check
      this.SaveNewBarTime(time);
     }
//--- Get the index of the last bar in the list and the object bar by the index
   int index=this.m_list_series.Total()-1;
   CBar *bar=this.m_list_series.At(index);
//--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe,
//--- copy price parameters (passed to the method from the outside) to the bar price structure
   int copied=1;
   if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period())
     {
      rates[0].time=time;
      rates[0].open=open;
      rates[0].high=high;
      rates[0].low=low;
      rates[0].close=close;
      rates[0].tick_volume=tick_volume;
      rates[0].real_volume=volume;
      rates[0].spread=spread;
     }
//--- otherwise, get data to the bar price structure from the environment
   else
      copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates);
//--- If the prices are obtained, set the new properties from the price structure for the bar object
   if(copied==1)
      bar.SetProperties(rates[0]);
  }
//+------------------------------------------------------------------+

Y de manera adicional: si en la serie temporal ha aparecido una nueva barra, actualizaremos las fechas de la serie temporal.

Estos son los cambios principales en esta clase. No vamos a analizar aquí los cambios poco considerables en los nombres de los métodos: el lector podrá familiarizarse con el listado completo de la clase en los archivos adjuntos al final del artículo.

Por el momento, hemos terminado con la clase CSeries.

Vamos a mejorar la clase CTimeSeries, que contine los objetos CSeries para todos los posibles periodos del gráfico de un mismo símbolo.

Para obtener el índice de un marco temporal en la lista que guarda las series temporales de los periodos correspondientes del gráfico y el marco temporal según el índice de la lista, tenemos en el listado de la clase los métodos correspondientes: IndexTimeframe() y TimeframeByIndex(). Los métodos son bastantes específicos, dado que se basan en que el índice del menor marco temporal permitido (PERIOD_M1) está contenido en el índice cero de la lista. No obstante, en la enumeración ENUM_TIMEFRAMES, el índice del periodo М1 será ya igual a la unidad, puesto que en el índice cero se encuentra la constante PERIOD_CURRENT. Es decir, todos los índices han sido desplazados en 1 con respecto al valor cero.
Tras reflexionar un poco, hemos decidido que podríamos necesitar algunas funciones que retornen el índice de la constante del periodo del gráfico dentro de la enumeración ENUM_TIMEFRAMES, y al contrario: que retornen según el periodo del gráfico su constante desde la enumeración. Por eso, hemos creado funciones que ejecuten estas tareas: en el archivo de funciones de servicio, IndexEnumTimeframe() y TimeframeByEnumIndex(), así como en el listado de la clase CTimeSeries, eliminamos la implementación de los métodos IndexTimeframe() y TimeframeByIndex(), añadiendo al cuerpo de la clase la implementación: la llamada de las funciones IndexEnumTimeframe() y TimeframeByEnumIndex() con desplazamiento de una unidad.

Bien, en el archivo de funciones de servicio DELib.mqh, escribimos tres funciones:

//+------------------------------------------------------------------+
//| Return the timeframe index in the ENUM_TIMEFRAMES enumeration    |
//+------------------------------------------------------------------+
char IndexEnumTimeframe(ENUM_TIMEFRAMES timeframe)
  {
   int statement=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   switch(statement)
     {
      case PERIOD_M1    :  return 1;
      case PERIOD_M2    :  return 2;
      case PERIOD_M3    :  return 3;
      case PERIOD_M4    :  return 4;
      case PERIOD_M5    :  return 5;
      case PERIOD_M6    :  return 6;
      case PERIOD_M10   :  return 7;
      case PERIOD_M12   :  return 8;
      case PERIOD_M15   :  return 9;
      case PERIOD_M20   :  return 10;
      case PERIOD_M30   :  return 11;
      case PERIOD_H1    :  return 12;
      case PERIOD_H2    :  return 13;
      case PERIOD_H3    :  return 14;
      case PERIOD_H4    :  return 15;
      case PERIOD_H6    :  return 16;
      case PERIOD_H8    :  return 17;
      case PERIOD_H12   :  return 18;
      case PERIOD_D1    :  return 19;
      case PERIOD_W1    :  return 20;
      case PERIOD_MN1   :  return 21;
      default           :  Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Return the timeframe by the ENUM_TIMEFRAMES enumeration index    |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TimeframeByEnumIndex(const uchar index)
  {
   if(index==0) return(ENUM_TIMEFRAMES)Period();
   switch(index)
     {
      case 1   :  return PERIOD_M1;
      case 2   :  return PERIOD_M2;
      case 3   :  return PERIOD_M3;
      case 4   :  return PERIOD_M4;
      case 5   :  return PERIOD_M5;
      case 6   :  return PERIOD_M6;
      case 7   :  return PERIOD_M10;
      case 8   :  return PERIOD_M12;
      case 9   :  return PERIOD_M15;
      case 10  :  return PERIOD_M20;
      case 11  :  return PERIOD_M30;
      case 12  :  return PERIOD_H1;
      case 13  :  return PERIOD_H2;
      case 14  :  return PERIOD_H3;
      case 15  :  return PERIOD_H4;
      case 16  :  return PERIOD_H6;
      case 17  :  return PERIOD_H8;
      case 18  :  return PERIOD_H12;
      case 19  :  return PERIOD_D1;
      case 20  :  return PERIOD_W1;
      case 21  :  return PERIOD_MN1;
      default  :  Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_DATAS),"... ",CMessage::Text(MSG_SYM_STATUS_INDEX),": ",(string)index); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Return the timeframe by its description                          |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TimeframeByDescription(const string timeframe)
  {
   return
     (
      timeframe=="M1"   ?  PERIOD_M1   :
      timeframe=="M2"   ?  PERIOD_M2   :
      timeframe=="M3"   ?  PERIOD_M3   :
      timeframe=="M4"   ?  PERIOD_M4   :
      timeframe=="M5"   ?  PERIOD_M5   :
      timeframe=="M6"   ?  PERIOD_M6   :
      timeframe=="M10"  ?  PERIOD_M10  :
      timeframe=="M12"  ?  PERIOD_M12  :
      timeframe=="M15"  ?  PERIOD_M15  :
      timeframe=="M20"  ?  PERIOD_M20  :
      timeframe=="M30"  ?  PERIOD_M30  :
      timeframe=="H1"   ?  PERIOD_H1   :
      timeframe=="H2"   ?  PERIOD_H2   :
      timeframe=="H3"   ?  PERIOD_H3   :
      timeframe=="H4"   ?  PERIOD_H4   :
      timeframe=="H6"   ?  PERIOD_H6   :
      timeframe=="H8"   ?  PERIOD_H8   :
      timeframe=="H12"  ?  PERIOD_H12  :
      timeframe=="D1"   ?  PERIOD_D1   :
      timeframe=="W1"   ?  PERIOD_W1   :
      timeframe=="MN1"  ?  PERIOD_MN1  :
      PERIOD_CURRENT
     );
  }
//+------------------------------------------------------------------+

En el archivo de la clase CTimeSeries, eliminamos del listado de la clase la implementación de los métodos IndexTimeframe() y TimeframeByIndex():

//+------------------------------------------------------------------+
//| Return the timeframe index in the list                           |
//+------------------------------------------------------------------+
char CTimeSeries::IndexTimeframe(ENUM_TIMEFRAMES timeframe) const
  {
   int statement=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   switch(statement)
     {
      case PERIOD_M1    :  return 0;
      case PERIOD_M2    :  return 1;
      case PERIOD_M3    :  return 2;
      case PERIOD_M4    :  return 3;
      case PERIOD_M5    :  return 4;
      case PERIOD_M6    :  return 5;
      case PERIOD_M10   :  return 6;
      case PERIOD_M12   :  return 7;
      case PERIOD_M15   :  return 8;
      case PERIOD_M20   :  return 9;
      case PERIOD_M30   :  return 10;
      case PERIOD_H1    :  return 11;
      case PERIOD_H2    :  return 12;
      case PERIOD_H3    :  return 13;
      case PERIOD_H4    :  return 14;
      case PERIOD_H6    :  return 15;
      case PERIOD_H8    :  return 16;
      case PERIOD_H12   :  return 17;
      case PERIOD_D1    :  return 18;
      case PERIOD_W1    :  return 19;
      case PERIOD_MN1   :  return 20;
      default           :  ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Return a timeframe by index                                      |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CTimeSeries::TimeframeByIndex(const uchar index) const
  {
   switch(index)

     {
      case 0   :  return PERIOD_M1;
      case 1   :  return PERIOD_M2;
      case 2   :  return PERIOD_M3;
      case 3   :  return PERIOD_M4;
      case 4   :  return PERIOD_M5;
      case 5   :  return PERIOD_M6;
      case 6   :  return PERIOD_M10;
      case 7   :  return PERIOD_M12;
      case 8   :  return PERIOD_M15;
      case 9   :  return PERIOD_M20;
      case 10  :  return PERIOD_M30;
      case 11  :  return PERIOD_H1;
      case 12  :  return PERIOD_H2;
      case 13  :  return PERIOD_H3;
      case 14  :  return PERIOD_H4;
      case 15  :  return PERIOD_H6;
      case 16  :  return PERIOD_H8;
      case 17  :  return PERIOD_H12;
      case 18  :  return PERIOD_D1;
      case 19  :  return PERIOD_W1;
      case 20  :  return PERIOD_MN1;
      default  :  ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_DATAS),"... ",CMessage::Text(MSG_SYM_STATUS_INDEX),": ",(string)index); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+

En lugar de estos métodos, ahora llamaremos las funciones del archivo de funciones de servicio DELib.mqh:

//--- Return (1) the timeframe index in the list and (2) the timeframe by the list index
   char              IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1;                            }
   ENUM_TIMEFRAMES   TimeframeByIndex(const uchar index)             const { return TimeframeByEnumIndex(uchar(index+1));                       }

Dado que lista de series temporales de la clase contiene todos los posibles periodos de los gráficos, en el índice cero de la lista se contiene la serie temporal del periodo del gráfico М1. En el índice cero de la enumeración ENUM_TIMEFRAMES, en cambio, se contiene PERIOD_CURRENT, mientras que М1 está en el primero, por eso, para obtener el ínidice correcto en la lista, necesitamos desplazar el valor del índice en 1, lo que precisamente hacemos aquí.

Hemos añadido a la sección privada de la clase dos variables de miembro de clase para establecer la primera fecha en la historia en el servidor y el terminal, y un método para asignar los valores de estas fechas a estas variables:

//+------------------------------------------------------------------+
//| Symbol timeseries class                                          |
//+------------------------------------------------------------------+
class CTimeSeries : public CBaseObj
  {
private:
   string            m_symbol;                                             // Timeseries symbol
   CArrayObj         m_list_series;                                        // List of timeseries by timeframes
   datetime          m_server_firstdate;                                   // The very first date in history by a server symbol
   datetime          m_terminal_firstdate;                                 // The very first date in history by a symbol in the client terminal
//--- Return (1) the timeframe index in the list and (2) the timeframe by the list index
   char              IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1;                            }
   ENUM_TIMEFRAMES   TimeframeByIndex(const uchar index)             const { return TimeframeByEnumIndex(uchar(index+1));                       }
//--- Set the very first date in history by symbol on the server and in the client terminal
   void              SetTerminalServerDate(void)
                       {
                        this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE);
                        this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE);
                       }
public:

En la variable m_server_firstdate se guardará la primera fecha en la historia según el símbolo en el servidor. Esta se obtiene mediante la función SeriesInfoInteger() con el identificador de propiedad SERIES_SERVER_FIRSTDATE.

En la variable m_terminal_firstdate se guardará la primera fecha en la historia según el símbolo en el terminal. Esta se obtiene mediante la función SeriesInfoInteger() con el identificador de propiedad SERIES_TERMINAL_FIRSTDATE.

En la sección pública de la clase, añadimos seis nuevos métodos y un constructor paramétrico:

public:
//--- Return (1) oneself, (2) the full list of timeseries, (3) specified timeseries object and (4) timeseries object by index
   CTimeSeries      *GetObject(void)                                       { return &this;                                                      }
   CArrayObj        *GetListSeries(void)                                   { return &this.m_list_series;                                        }
   CSeries          *GetSeries(const ENUM_TIMEFRAMES timeframe)            { return this.m_list_series.At(this.IndexTimeframe(timeframe));      }
   CSeries          *GetSeriesByIndex(const uchar index)                   { return this.m_list_series.At(index);                               }
//--- Set/return timeseries symbol
   void              SetSymbol(const string symbol)                        { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);  }
   string            Symbol(void)                                    const { return this.m_symbol;                                              }
//--- Set the history depth (1) of a specified timeseries and (2) of all applied symbol timeseries
   bool              SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SetRequiredAllUsedData(const uint required=0,const int rates_total=0);
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries, (2) all timeseries
   bool              SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SyncAllData(const uint required=0,const int rates_total=0);
//--- Return the very first date in history by symbol (1) on the server and (2) in the client terminal
   datetime          ServerFirstDate(void)                           const { return this.m_server_firstdate;                                    }
   datetime          TerminalFirstDate(void)                         const { return this.m_terminal_firstdate;                                  }
//--- Create (1) the specified timeseries list and (2) all timeseries lists
   bool              Create(const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool              CreateAll(const uint required=0);
//--- Update (1) the specified timeseries list and (2) all timeseries lists
   void              Refresh(const ENUM_TIMEFRAMES timeframe,
                             const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
   void              RefreshAll(const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
//--- Compare CTimeSeries objects (by symbol)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Display (1) description and (2) short symbol timeseries description in the journal
   void              Print(const bool created=true);
   void              PrintShort(const bool created=true);
   
//--- Constructors
                     CTimeSeries(void){;}
                     CTimeSeries(const string symbol);
  };
//+------------------------------------------------------------------+

El método GetObject() retorna el puntero al objeto de la clase. Este nos permite obtener el objeto de clase de la serie temporal del símbolo y trabajar con él en nuestro programa.

Los métodos ServerFirstDate() y TerminalFirstDate() retornan el valor de las variables m_server_firstdate y m_terminal_firstdate respectivamente, que hemos analizado con anterioridad.

El método virtual Compare() permite comparar dos objetos de series temporales según la denomiación del símbolo de la serie temporal:

//+------------------------------------------------------------------+
//| Compare CTimeSeries objects                                      |
//+------------------------------------------------------------------+
int CTimeSeries::Compare(const CObject *node,const int mode=0) const
  {
   const CTimeSeries *compared_obj=node;
   return(this.Symbol()>compared_obj.Symbol() ? 1 : this.Symbol()<compared_obj.Symbol() ? -1 : 0);
  }
//+------------------------------------------------------------------+

El método retorna cero en el caso de que los símbolos de los dos objetos de series temporales comparados sean iguales. De lo contrario, se retornará +/- 1. El método ha sido declarado en la clase CObject de la biblioteca estándar, y debe ser redefinido en sus herederos.

El método Print() muestra en el diario las descripciones completas de todas las series temporales del símbolo:

//+------------------------------------------------------------------+
//| Display descriptions of all symbol timeseries in the journal     |
//+------------------------------------------------------------------+
void CTimeSeries::Print(const bool created=true)
  {
   ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": ");
   for(int i=0;i<this.m_list_series.Total();i++)
     {
      CSeries *series=this.m_list_series.At(i);
      if(series==NULL || (created && series.DataTotal()==0))
         continue;
      series.Print();
     }
  }
//+------------------------------------------------------------------+

En el diario, se muestra una lista de todos las series temporales (created=true) creadas o todos las series temporales(created=false) creadas y declaradas del símbolo, en un formato determinado, por ejemplo:

created=true:

GBPUSD symbol timeseries: 
Timeseries "GBPUSD" M1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6296
Timeseries "GBPUSD" M5: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3921
Timeseries "GBPUSD" M15: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3227
Timeseries "GBPUSD" M30: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3053
Timeseries "GBPUSD" H1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6187
Timeseries "GBPUSD" H4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5298
Timeseries "GBPUSD" D1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5288
Timeseries "GBPUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1398
Timeseries "GBPUSD" MN1: Requested history depth: 1000, Actual history depth: 321, Historical data created: 321, History bars on the server: 321

created=false:

GBPUSD symbol timeseries: 
Timeseries "GBPUSD" M1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6296
Timeseries "GBPUSD" M2: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5483
Timeseries "GBPUSD" M3: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 4616
Timeseries "GBPUSD" M4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 4182
Timeseries "GBPUSD" M5: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3921
Timeseries "GBPUSD" M6: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3748
Timeseries "GBPUSD" M10: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3401
Timeseries "GBPUSD" M12: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3314
Timeseries "GBPUSD" M15: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3227
Timeseries "GBPUSD" M20: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3140
Timeseries "GBPUSD" M30: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3053
Timeseries "GBPUSD" H1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6187
Timeseries "GBPUSD" H2: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5047
Timeseries "GBPUSD" H3: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5031
Timeseries "GBPUSD" H4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5298
Timeseries "GBPUSD" H6: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 6324
Timeseries "GBPUSD" H8: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 6301
Timeseries "GBPUSD" H12: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5762
Timeseries "GBPUSD" D1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5288
Timeseries "GBPUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1398
Timeseries "GBPUSD" MN1: Requested history depth: 1000, Actual history depth: 321, Historical data created: 321, History bars on the server: 321

El método PrintShort() muestra en el diario las descripciones breves de todas las series temporales del símbolo:

//+-------------------------------------------------------------------+
//| Display short descriptions of all symbol timeseries in the journal|
//+-------------------------------------------------------------------+
void CTimeSeries::PrintShort(const bool created=true)
  {
   ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": ");
   for(int i=0;i<this.m_list_series.Total();i++)
     {
      CSeries *series=this.m_list_series.At(i);
      if(series==NULL || (created && series.DataTotal()==0))
         continue;
      series.PrintShort();
     }
  }
//+------------------------------------------------------------------+

En el diario, se muestra una lista de todos las series temporales (created=true) creadas o todos las series temporales(created=false) creadas y declaradas del símbolo, en un formato determinado, por ejemplo:

created=true:

USDJPY symbol timeseries: 
Timeseries "USDJPY" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2880
Timeseries "USDJPY" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3921
Timeseries "USDJPY" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3227
Timeseries "USDJPY" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3053
Timeseries "USDJPY" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5095
Timeseries "USDJPY" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5023
Timeseries "USDJPY" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5305
Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562
Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589

created=false:

USDJPY symbol timeseries: 
Timeseries "USDJPY" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2880
Timeseries "USDJPY" M2: Requested: 1000, Actual: 1000, Created: 0, On the server: 3608
Timeseries "USDJPY" M3: Requested: 1000, Actual: 1000, Created: 0, On the server: 4616
Timeseries "USDJPY" M4: Requested: 1000, Actual: 1000, Created: 0, On the server: 4182
Timeseries "USDJPY" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3921
Timeseries "USDJPY" M6: Requested: 1000, Actual: 1000, Created: 0, On the server: 3748
Timeseries "USDJPY" M10: Requested: 1000, Actual: 1000, Created: 0, On the server: 3401
Timeseries "USDJPY" M12: Requested: 1000, Actual: 1000, Created: 0, On the server: 3314
Timeseries "USDJPY" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3227
Timeseries "USDJPY" M20: Requested: 1000, Actual: 1000, Created: 0, On the server: 3140
Timeseries "USDJPY" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3053
Timeseries "USDJPY" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5095
Timeseries "USDJPY" H2: Requested: 1000, Actual: 1000, Created: 0, On the server: 5047
Timeseries "USDJPY" H3: Requested: 1000, Actual: 1000, Created: 0, On the server: 5031
Timeseries "USDJPY" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5023
Timeseries "USDJPY" H6: Requested: 1000, Actual: 1000, Created: 0, On the server: 6390
Timeseries "USDJPY" H8: Requested: 1000, Actual: 1000, Created: 0, On the server: 6352
Timeseries "USDJPY" H12: Requested: 1000, Actual: 1000, Created: 0, On the server: 5796
Timeseries "USDJPY" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5305
Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562
Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589

Hemos añadido en el constructor de la clase, el establecimiento de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort();
   for(int i=0;i<21;i++)
     {
      ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i);
      CSeries *series_obj=new CSeries(this.m_symbol,timeframe);
      this.m_list_series.Add(series_obj);
     }
   this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

En el método de adición de la serie temporal indicada, hemos añadido el establecimiento de las fechas de la serie temporalal detectarse el evento "Nueva barra" de la serie temporal actualizada:

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeries::Refresh(const ENUM_TIMEFRAMES timeframe,
                          const datetime time=0,
                          const double open=0,
                          const double high=0,
                          const double low=0,
                          const double close=0,
                          const long tick_volume=0,
                          const long volume=0,
                          const int spread=0)
  {
   CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0)
      return;
   series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
   if(series_obj.IsNewBar(time))
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

En el método de actualización de series temporales, también hemos añadido la actualización de las fechas de la serie temporal:

//+------------------------------------------------------------------+
//| Update all timeseries lists                                      |
//+------------------------------------------------------------------+
void CTimeSeries::RefreshAll(const datetime time=0,
                          const double open=0,
                          const double high=0,
                          const double low=0,
                          const double close=0,
                          const long tick_volume=0,
                          const long volume=0,
                          const int spread=0)
  {
   bool upd=false;
   for(int i=0;i<21;i++)
     {
      CSeries *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || series_obj.DataTotal()==0)
         continue;
      series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
      if(series_obj.IsNewBar(time))
         upd &=true;
     }
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

No obstante, aquí deberemos actualizar las fechas solo una vez al encontrar el evento "Nueva barra" en cualquiera de las series temporales actualizadas presentes en la lista (ya que hay 21 series temporales, y no tiene sentido establecer 21 veces la misma fecha). Por eso, registramos en la bandera que indica la necesidad de actualizar las fechas el valor true al encontrar el evento "Nueva barra" en el ciclo por todas las series temporales, y, al finalizar el ciclo, si la bandera ha sido establecida, actualizamos las fechas.

Ya hemos finalizado las mejoras de la clase CTimeSeries. No vamos a describir aquí las correcciones poco significativas: el lector podrá verlo todo en los archivos adjuntos al final del artículo.

En estos momentos, tenemos tres clases que contienen toda la información necesaria para crear las colecciones de series temporales:

  1. La "barra de un periodo de un símbolo" CBar, incluye los datos de una barra del símbolo establecido del periodo establecido;
  2. La "serie temporal de un periodo de un símbolo" CSeries, incluye la lista de colección de barras (1) de un periodo de un símbolo;
  3. Las "series temporales de todos los periodos de un símbolo" CTimeSeries, incluyen la lista de series temporales (2) para cada periodo de un símbolo;

Ahora, vamos a crear una colección de series temporales que supone una lista de colección de series temporales (3) para cada símbolo usado en el programa.

La clase de colección de los objetos de series temporales de los símbolos y periodos

La colección de series temporales es una matriz dinámica de punteros a los ejemplares de la clase CObject y sus herederos (punteros a los objetos de clase CTimeSeries).

Creamos en la carpeta de la biblioteca \MQL5\Include\DoEasy\Collections\ el nuevo archivo TimeSeriesCollection.mqh de la clase CTimeSeriesCollection.
El objeto básico de la clase será el objeto básico para construir la biblioteca estándar CObject.

A continuación, veremos con mayor detalle el listado de la clase, y también analizaremos poco a poco su construcción:

//+------------------------------------------------------------------+
//|                                         TimeSeriesCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                            |
//+------------------------------------------------------------------+
#include "..\Objects\Series\TimeSeries.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries collection                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CObject
  {
private:
   CArrayObj               m_list;                    // List of applied symbol timeseries
//--- Return the timeseries index by symbol name
   int                     IndexTimeSeries(const string symbol);
public:
//--- Return (1) oneself and (2) the timeseries list
   CTimeSeriesCollection  *GetObject(void)            { return &this;         }
   CArrayObj              *GetList(void)              { return &this.m_list;  }
   
//--- Create the symbol timeseries list collection
   bool                    CreateCollection(const CArrayObj *list_symbols);
//--- Set the flag of using (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   void                    SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true);
   void                    SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true);
   void                    SetAvailable(const string symbol,const bool flag=true);
   void                    SetAvailable(const bool flag=true);
//--- Get the flag of using (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                    IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool                    IsAvailable(const ENUM_TIMEFRAMES timeframe);
   bool                    IsAvailable(const string symbol);
   bool                    IsAvailable(void);

//--- Set the history depth of (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                    SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const uint required=0,const int rates_total=0);
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries of the specified symbol,
//--- (2) the specified timeseries of all symbols, (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                    SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const string symbol,const uint required=0,const int rates_total=0);
   bool                    SyncData(const uint required=0,const int rates_total=0);
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                    CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool                    CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool                    CreateSeries(const string symbol,const uint required=0);
   bool                    CreateSeries(const uint required=0);
//--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   void                    Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                   const datetime time=0,
                                   const double open=0,
                                   const double high=0,
                                   const double low=0,
                                   const double close=0,
                                   const long tick_volume=0,
                                   const long volume=0,
                                   const int spread=0);
   void                    Refresh(const ENUM_TIMEFRAMES timeframe,
                                   const datetime time=0,
                                   const double open=0,
                                   const double high=0,
                                   const double low=0,
                                   const double close=0,
                                   const long tick_volume=0,
                                   const long volume=0,
                                   const int spread=0);
   void                    Refresh(const string symbol,
                                   const datetime time=0,
                                   const double open=0,
                                   const double high=0,
                                   const double low=0,
                                   const double close=0,
                                   const long tick_volume=0,
                                   const long volume=0,
                                   const int spread=0);
   void                    Refresh(const datetime time=0,
                                   const double open=0,
                                   const double high=0,
                                   const double low=0,
                                   const double close=0,
                                   const long tick_volume=0,
                                   const long volume=0,
                                   const int spread=0);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(const bool created=true);
   void                    PrintShort(const bool created=true);
   
   
//--- Constructor
                           CTimeSeriesCollection();
  };
//+------------------------------------------------------------------+

En esencia, en estos momentos, la clase supone una lista de objetos de series temporales y métodos de creación, de establecimiento de parámetros y de actualización de las series temporales necesarias según el símbolo y el periodo:

La matriz de punteros a los objetos de la clase CObject m_list contendrá los punteros a los objetos de clase CTimeSeries. Con la ayuda de esta lista, podremos obtener los datos de las series temporales necesarias y trabajar con ellos.

El método IndexTimeSeries(), que retorna el índice de una serie temporal según el nombre del símbolo, permite obtener acceso al objeto CTimeSeries necesario según el nombre del símbolo:

//+------------------------------------------------------------------+
//| Return the timeseries index by symbol name                       |
//+------------------------------------------------------------------+
int CTimeSeriesCollection::IndexTimeSeries(const string symbol)
  {
   CTimeSeries *tmp=new CTimeSeries(symbol);
   if(tmp==NULL)
      return WRONG_VALUE;
   this.m_list.Sort();
   int index=this.m_list.Search(tmp);
   delete tmp;
   return index;
  }
//+------------------------------------------------------------------+

Creamos un nuevo objeto temporal de serie temporal con el valor del símbolo transmitido al método,
asignamos a la lista m_list la bandera de lista clasificada
y,
con la ayuda del método Searsh(), obtenemos el índice de ese objeto en la lista.
Eliminamos necesariamente el objeto temporal de serie temporal y
retornamos el índice obtenido
.
Si en la lista no se encuentra el objeto con el índice establecido, el método retornará -1, de lo contrario, se retornará el valor del índice del objeto encontrado.

El método GetObject() retorna al programa de control el puntero al objeto de colección de series temporales, y permite obtener el objeto de colección completo, para trabajar con él en nuestro programa.

El método GetList() retorna el puntero a la lista de colección de series temporales CTimeSeries, y permite obtener la lista con todas las series temporales de los símbolos, para trabajar con ella en nuestro programa.

El método CreateCollection() crea una lista vacía de objetos de series temporales:

//+------------------------------------------------------------------+
//| Create the symbol timeseries collection list                     |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateCollection(const CArrayObj *list_symbols)
  {
//--- If an empty list of symbol objects is passed, exit
   if(list_symbols==NULL)
      return false;
//--- Get the number of symbol objects in the passed list
   int total=list_symbols.Total();
//--- Clear the timeseries collection list
   this.m_list.Clear();
//--- In a loop by all symbol objects
   for(int i=0;i<total;i++)
     {
      //--- get the next symbol object
      CSymbol *symbol_obj=list_symbols.At(i);
      //--- if failed to get a symbol object, move on to the next one in the list
      if(symbol_obj==NULL)
         continue;
      //--- Create a new timeseries object with the current symbol name
      CTimeSeries *timeseries=new CTimeSeries(symbol_obj.Name());
      //--- If failed to create the timeseries object, move on to the next symbol in the list
      if(timeseries==NULL)
         continue;
      //--- Set the sorted list flag for the timeseries collection list
      this.m_list.Sort();
      //--- If the object with the same symbol name is already present in the timeseries collection list, remove the timeseries object
      if(this.m_list.Search(timeseries)>WRONG_VALUE)
         delete timeseries;
      //--- if failed to add the timeseries object to the collection list, remove the timeseries object
      else 
         if(!this.m_list.Add(timeseries))
            delete timeseries;
     }
//--- Return the flag indicating that the created collection list has a size greater than zero
   return this.m_list.Total()>0;
  }
//+-----------------------------------------------------------------------+

Cada línea del método ha sido comentada en su listado.
Al método se transmite la lista de todos los símbolos utilizados en el programa (creada anteriormente), y luego, en un ciclo por dicha lista, se crean los objetos de series temporales CTimeSeries, para las cuales se indica de inmediato el nombre del símbolo durante su creación. De esta forma, obtendremos un lista de colección de series temporales según la cantidad de símbolos utilizados en el programa. Todos los demás datos de los objetos de series temporales creados permanecerán vacíos: habrá que establecerlos por separado.
Hemos hecho esto así para que en la biblioteca siempre haya una lista de colección de series vacías en una cantidad igual a la cantidad de datos establecidos para el funcionamiento de los símbolos. En cuanto a los marcos temporales, y, propiamente, los objetos de series temporales con los que deberemos trabajar en el programa, se establecen ya en el siguiente paso, o a medida que resulten necesarios.
Pero es mejor crearlos en el manejador OnInit() del programa, o bien justo después de que comience a funcionar, dado que la creación de un gran número de series temporales necesita tiempo, sobre todo en un inicio "en frío" del programa.

Los cuatro métodos SetAvailable() sobrecargados sirven para establecer la bandera que indica la necesidad de trabajar en el programa con las series temporales establecidas.

Método para establecer la bandera de uso de la serie temporal indicada del símbolo establecido:

//+-----------------------------------------------------------------------+
//|Set the flag of using the specified timeseries of the specified symbol |
//+-----------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return;
   series.SetAvailable(flag);
  }
//+------------------------------------------------------------------+

Transmitimos al método el símbolo, el marco temporal y la propia bandera que se debe establecer para la serie temporal que se corresponde con símbolo de marco temporal.

Primero obtenemos el índice de las series temporales CTimeSeries en la lista m_list según el símbolo con la ayuda del método IndexTimeSeries(), que hemos analizado anteriormente, y obtenemos según este índice la serie temporal de la lista. A partir del objeto de serie temporal, obtenemos la serie temporal CSeries necesaria del periodo que hemos indicado para el gráfico mediante el método GetSeries(), que analizamos en el anterior artículo, y establecemos para ella la bandera transmitida al método mediante el método SetAvailable() de la clase CBaseObj.

Método para establecer la bandera de uso de la serie temporal indicada para todos los símbolos:

//+------------------------------------------------------------------+
//|Set the flag of using the specified timeseries of all symbols     |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      series.SetAvailable(flag);
     }
  }
//+------------------------------------------------------------------+

Transmitimos al método el marco temporal y la bandera que debemos establecer para la serie temporal indicada de todos los símbolos.

En un ciclo por la lista de todas las series temporales de los símbolos, obtenemos la siguiente serie temporal CTimeSeries según el índice del ciclo, luego, a partir del objeto de serie temporal, obtenemos la serie temporal CSeries indicada del periodo que hemos establecido para el gráfico mediante el método GetSeries(), que analizamos en el anterior artículo, y establecemos para ella la bandera transmitida al método mediante el método SetAvailable() de la clase CBaseObj.

Método para establecer la bandera de uso de todas las series temporales para el símbolo indicado:

//+------------------------------------------------------------------+
//|Set the flag of using all timeseries of the specified symbol      |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const string symbol,const bool flag=true)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      series.SetAvailable(flag);
     }
  }
//+------------------------------------------------------------------+

Transmitimos al método el símbolo y la bandera que se debe establecer para todas las series temporales del símbolo indicado.

Primero obtenemos el índice de las series temporales CTimeSeries en la lista m_list según el símbolo con la ayuda del método IndexTimeSeries(), que hemos analizado anteriormente, y obtenemos según este índice la serie temporal CTimeSeries de la lista. Del objeto de serie temporal obtenido, obtenemos con la ayuda de GetListSeries() la lista completa de todas las series temporales CSeries. En un ciclo por la lista obtenida, obtenemos la siguiente serie temporal CSeries y establecemos para ella la bandera transmitida al método mediante el método SetAvailable() de la clase CBaseObj.

Método para establecer la bandera de uso de todas las series temporales de todos los símbolos:

//+------------------------------------------------------------------+
//| Set the flag of using all timeseries of all symbols              |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const bool flag=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         series.SetAvailable(flag);
        }
     }
  }
//+--------------------------------------------------------------------+

Transmitimos al método la bandera que debemos establecer para todas las series temporales de todos los símbolos.

En un ciclo por la lista de las series temporales, obtenemos el siguiente objeto de serie temporal CTimeSeries según el índice del ciclo; después, del objeto obtenido mediante el método GetListSeries(), obtenemos la lista con todas las series temporales CSeries. A continuación, en un ciclo por la lista de series temporales CSeries, obtenemos la siguiente serie temporal según el índice del ciclo y establecemos para ella la bandera transmitida al método mediante el método SetAvailable() de la clase CBaseObj.

Los cuatro métodos encargados retornar la bandera de uso de todas las series temporales, o solo de las indicadas, son:

//+-------------------------------------------------------------------------+
//|Return the flag of using the specified timeseries of the specified symbol|
//+-------------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.IsAvailable();
  }
//+------------------------------------------------------------------+
//| Return the flag of using the specified timeseries of all symbols |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const ENUM_TIMEFRAMES timeframe)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.IsAvailable();
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Return the flag of using all timeseries of the specified symbol  |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const string symbol)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.IsAvailable();
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Return the flag of using all timeseries of all symbols           |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(void)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.IsAvailable();
        }
     }
   return res;
  }
//+--------------------------------------------------------------------+

Los métodos funcionan de forma casi igual a los métodos para asignar la bandera a las series temporales, que acabamos de ver. La única diferencia es el uso de la variable local res, que tiene el estado inicial true. Para obtener la bandera "colectiva" de todas las series temporales en los métodos que retornan la bandera general de uso de multitud de series temporales. En un ciclo por las series temporales CSeries, registramos en la variable el estado de la bandera de cada serie temporal comprobada, y si, aunque sea una sola de ellas tiene el estado false, en la variable se registrará la bandera false. El valor de esta variable se retorna desde el método al finalizar todos los ciclos por todas las series temporales.

Los cuatro métodos para establecer la profundidad necesaria de la historia, ya sea para las series temporales indicadas, ya sea para todas las series temporales, es el siguiente:

//+--------------------------------------------------------------------------+
//|Set the history depth for the specified timeseries of the specified symbol|
//+--------------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.SetRequiredUsedData(required,rates_total);
  }
//+------------------------------------------------------------------+
//| Set the history depth of the specified timeseries of all symbols |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.SetRequiredUsedData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Set the history depth for all timeseries of the specified symbol |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.SetRequiredUsedData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Set the history depth for all timeseries of all symbols          |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.SetRequiredUsedData(required,rates_total);
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

Los métodos funcionan exactamente igual que los métodos que retornan las banderas de uso de las series temporales. Retornan el resultado de la asignación a los objetos de series temporales CSeries de la cantidad de datos solicitada mediante los métodos SetRequiredUsedData(), que retornan un valor booleano. Por eso, aquí también se usa la bandera general al establecer la profundidad de la historia para multitud de series temporales CSeries.

Los cuatro métodos encargados retornar la bandera de sincronización, o bien de la serie temporal indicada, o bien de todas, son:

//+------------------------------------------------------------------+
//| Return the flag of data synchronization with the server data     |
//| for a specified timeseries of a specified symbol                 |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.SyncData(required,rates_total);
  }
//+------------------------------------------------------------------+
//| Return the flag of data synchronization with the server data     |
//| for a specified timeseries of all symbols                        |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.SyncData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Return the flag of data synchronization with the server data     |
//| for all timeseries of a specified symbol                         |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const string symbol,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.SyncData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Return the flag of data synchronization with the server data     |
//| for all timeseries of all symbols                                |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.SyncData(required,rates_total);
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

El funcionamiento de los métodos es idéntico al que hemos visto más arriba. Se retorna el resultado de la comprobación de la sincronización de las series temporales con el método SyncData() de la clase CSeries.

Hay cuatro métodos para crear las series temporales indicadas, o bien todas ellas.

Método para crear la serie temporal indicada del símbolo indicado:

//+------------------------------------------------------------------+
//| Create the specified timeseries of the specified symbol          |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   return timeseries.Create(timeframe,required);
  }
//+------------------------------------------------------------------+

Transmitimos al método el símbolo para el que debemos crear la serie temporal cuyo periodo también se transmite al método.

Obtenemos el índice de la serie temporal en la lista según la denomiación del símbolo mediante el método IndexTimeSeries(); según el índice obtenido, obtenemos la serie temporal CTimeSeries de la lista y retornamos el resultado de la creación de la serie temporal indicada mediante el método Create() de la clase CTimeSeries.

Método para crear la serie temporal indicada de todos los símbolos:

//+------------------------------------------------------------------+
//| Create the specified timeseries of all symbols                   |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      res &=timeseries.Create(timeframe,required);
     }
   return res;
  }
//+------------------------------------------------------------------+

Transmitimos al método el marco temporal cuya serie temporal debemos crear para todos los símbolos.

En un ciclo por todos los objetos de series temporales de todos los símbolos, obtenemos el siguiente objeto de serie temporal CTimeSeries según el índice del ciclo, añadiendo luego a la variable res el resultado de la creación de la serie temporal indicada con el método Create() de la clase CTimeSeries. Al terminar el ciclo, retornamos el resultado de la creación de la serie temporal indicada para todos los símbolos. Con que solo una serie temporal no haya sido creada, el resultado será false.

Método para crear todas las series temporales del símbolo indicado:

//+------------------------------------------------------------------+
//| Create all timeseries of the specified symbol                    |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const string symbol,const uint required=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   return timeseries.CreateAll(required);
  }
//+------------------------------------------------------------------+

Transmitimos al método el símbolo para el que debemos crear todas las series temporales.

Obtenemos el índice de la serie temporal en la lista según la denomiación del símbolo mediante el método IndexTimeSeries(); según el índice obtenido, obtenemos la serie temporal CTimeSeries de la lista y retornamos el resultado de la creación de todas las series temporales mediante el método CreateAll() de la clase CTimeSeries.

Método para crear todas las series temporales de todos los símbolos:

//+------------------------------------------------------------------+
//| Create all timeseries of all symbols                             |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const uint required=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      res &=timeseries.CreateAll(required);
     }
   return res;
  }
//+------------------------------------------------------------------+

Transmitimos al método solo la cantidad de la profundidad creada de la historia (como en todos los métodos analizados anteriormente). Por defecto, se transmite cero, lo que indica una profundidad de la historia igual a 1000 barras, establecida con la macrosustitución SERIES_DEFAULT_BARS_COUNT en el archivo Defines.mqh.

En un ciclo por la lista de series temporales, obtenemos el siguiente objeto de serie temporal CTimeSeries según el índice del ciclo, añadiendo luego a la variable res el resultado de todas las series temporales para el símbolo del objeto CTimeSeries actual mediante el método CreateAll(), que retorna la bandera de creación de todas las series temporales del símbolo del objeto CTimeSeries.
Al terminar el ciclo, retornamos el resultado de la creación de todas las series temporales para todos los símbolos. Con que solo una serie temporal no haya sido creada, el resultado será false.

Los cuatro métodos para actualizar las series temporales indicadas del símbolo indicado, o bien todas ellas, son:

//+------------------------------------------------------------------+
//| Update the specified timeseries of the specified symbol          |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Update the specified timeseries of all symbols                   |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const ENUM_TIMEFRAMES timeframe,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+
//| Update all timeseries of the specified symbol                    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,
                                    const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Update all timeseries of all symbols                             |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const datetime time=0,
                                    const double open=0,
                                    const double high=0,
                                    const double low=0,
                                    const double close=0,
                                    const long tick_volume=0,
                                    const long volume=0,
                                    const int spread=0)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+

Aquí, en todos los métodos, obtenemos de una forma u otra (hemos analizado todas las maneras en los métodos anteriores) el objeto de serie temporal CTimeSeries indicado y llamamos los métodos de actualización de una serie temporal indicada Refresh(), o de todas las series temporales, RefreshAll(), de la clase CTimeSeries.

Transmitimos a todos los métodos los datos actuales de las matrices de series temporales. Esto es necesario para trabajar en los indicadores en el periodo actual del símbolo actual. En el resto de los casos, los valores transmitidos no son importantes, por eso, se les asigna por defecto 0.

Método que retorna al diario la descripción completa de la colección:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Print(const bool created=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.Print(created);
     }
  }
//+------------------------------------------------------------------+

Al método se le transmite la bandera que indica la necesidad de mostrar en el diario solo las series temporales creadas.

En un ciclo por la lista de objetos de series temporales, obtenemos el siguiente objeto de la serie temporal CTimeSeries y llamamos su método homónimo. Ya hemos visto los resultados de su trabajo anteriormente. Finalmente, se imprimirán en el diario los datos de todas las series temporales disponibles de todos los símbolos de la colección.

Método que muestra en el diario la descripción breve de la colección:

//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::PrintShort(const bool created=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.PrintShort(created);
     }
  }
//+------------------------------------------------------------------+

Al método se le transmite la bandera que indica la necesidad de mostrar en el diario solo las series temporales creadas.

En un ciclo por la lista de objetos de series temporales, obtenemos el siguiente objeto de la serie temporal CTimeSeries y llamamos su método homónimo. Ya hemos visto los resultados de su trabajo anteriormente. Finalmente, se imprimirán en el diario los datos de todas las series temporales disponibles de todos los símbolos de la colección, de forma breve.

La clase de colección de series temporales en su versión actual está preparada. En los archivos adjuntos al final del artículo, podemos ver y analizar el listado completo de la clase.

Ahora, debemos organizar el acceso desde el exterior a la colección de series temporales creada, y también implementar un establecimiento cómodo de los parámetros para la series creadas.

Cualquier programa obtiene acceso a los métodos de la biblioteca desde el objeto principal de la biblioteca CEngine.
Vamos a añadir al archivo de la clase CEngine el acceso al trabajo con la colección de series temporales.
Abrimos el archivo Engine.mqh del directorio de la biblioteca \MQL5\Include\DoEasy\ e introducimos en él los cambios necesarios.

Dado que en la versión anterior incluimos en la clase CEngine el archivo de clase CTimeSeries solo para comprobar su funcionamiento, eliminaremos este de la lista de archivos de inclusión

//+------------------------------------------------------------------+
//| Archivos de inclusión                                            |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "TradingControl.mqh"
#include "Objects\Series\TimeSeries.mqh"
//+------------------------------------------------------------------+

e incluiremos el archivo de la clase de colección de series temporales:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                            |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+

En la sección privada de la clase, declaramos la variable con el tipo de la clase de la colección de series temporales:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_series;                       // Timeseries collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CArrayObj            m_list_counters;                 // List of timer counters

En la sección pública de la clase, cambiamos la implementación del método para establecer la lista de símbolos utilizados:

//--- Set the list of (1) used symbols
   bool                 SetUsedSymbols(const string &array_symbols[])   { return this.m_symbols.SetUsedSymbols(array_symbols);}

En esta versión, dicho método llama al método homónimo para establecer la lista de símbolos para la clase de colección de símbolos. Y precisamente en este lugar, justo después del establecimiento de la lista de símbolos en la colección de símbolos, será cómodo para nosotros crear también la colección de series temporales basadas en la lista creada de colección de símbolos.

Solo dejaremos aquí la declaración del método:

//--- Set the list of used symbols in the symbol collection and create the collection of symbol timeseries
   bool                 SetUsedSymbols(const string &array_symbols[]);

y la implementaremos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Set the list of used symbols in the symbol collection            |
//| and create the symbol timeseries collection                      |
//+------------------------------------------------------------------+
bool CEngine::SetUsedSymbols(const string &array_symbols[])
  {
   bool res=this.m_symbols.SetUsedSymbols(array_symbols);
   CArrayObj *list=this.GetListAllUsedSymbols();
   if(list==NULL)
      return false;
   res&=this.m_series.CreateCollection(list);
   return res;
  }
//+------------------------------------------------------------------+

Aquí, declaramos la variableres y la inicializamos con el resultado del funcionamiento del método para establecer la lista de símbolos en la colección de símbolos.

A continuación, obtenemos la lista de símbolos utilizados de la clase de colección de símbolos; si la lista no ha sido establecida, retornaremos false,
de lo contrario, añadiremos a la variable res el resultado de la creación de la colección de series temporales, usando como base la lista de la colección de símbolos.
Retornamos el resultado total desde el método
.

Añadimos a la sección pública de la clase los métodos para trabajar con la colección de series temporales:

//--- Return the list of pending requests
   CArrayObj           *GetListPendingRequests(void)                          { return this.m_trading.GetListRequests();                        }

//--- Return (1) the timeseries collection and (2) the list of timeseries from the timeseries collection
   CTimeSeriesCollection *GetTimeSeriesCollection(void)                       { return &this.m_series;                                          }
   CArrayObj           *GetListTimeSeries(void)                               { return this.m_series.GetList();                                 }
//--- Set the flag of using (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   void                 SeriesSetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true)
                          { this.m_series.SetAvailable(symbol,timeframe,flag);}
   void                 SeriesSetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true)
                          { this.m_series.SetAvailable(timeframe,flag);       }
   void                 SeriesSetAvailable(const string symbol,const bool flag=true)
                          { this.m_series.SetAvailable(symbol,flag);          }
   void                 SeriesSetAvailable(const bool flag=true)              
                          { this.m_series.SetAvailable(flag);                 }
//--- Set the history depth of (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                 SeriesSetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(symbol,timeframe,required,rates_total);}
   bool                 SeriesSetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(timeframe,required,rates_total);       }
   bool                 SeriesSetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(symbol,required,rates_total);          }
   bool                 SeriesSetRequiredUsedData(const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(required,rates_total);                 }
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries of the specified symbol,
//--- (2) the specified timeseries of all symbols, (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                 SeriesSyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(symbol,timeframe,required,rates_total);  }
   bool                 SeriesSyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(timeframe,required,rates_total);         }
   bool                 SeriesSyncData(const string symbol,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(symbol,required,rates_total);            }
   bool                 SeriesSyncData(const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(required,rates_total);                   }
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   bool                 SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,timeframe,required);          }
   bool                 SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(timeframe,required);                 }
   bool                 SeriesCreate(const string symbol,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,required);                    }
   bool                 SeriesCreate(const uint required=0)
                          { return this.m_series.CreateSeries(required);                           }
//--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols,
//--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
   void                 SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const datetime time=0,
                                      const double open=0,
                                      const double high=0,
                                      const double low=0,
                                      const double close=0,
                                      const long tick_volume=0,
                                      const long volume=0,
                                      const int spread=0)
                          { this.m_series.Refresh(symbol,timeframe,time,open,high,low,close,tick_volume,volume,spread); }
   void                 SeriesRefresh(const ENUM_TIMEFRAMES timeframe,
                                      const datetime time=0,
                                      const double open=0,
                                      const double high=0,
                                      const double low=0,
                                      const double close=0,
                                      const long tick_volume=0,
                                      const long volume=0,
                                      const int spread=0)
                          { this.m_series.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);        }
   void                 SeriesRefresh(const string symbol,
                                      const datetime time=0,
                                      const double open=0,
                                      const double high=0,
                                      const double low=0,
                                      const double close=0,
                                      const long tick_volume=0,
                                      const long volume=0,
                                      const int spread=0)
                          { this.m_series.Refresh(symbol,time,open,high,low,close,tick_volume,volume,spread);           }
   void                 SeriesRefresh(const datetime time=0,
                                      const double open=0,
                                      const double high=0,
                                      const double low=0,
                                      const double close=0,
                                      const long tick_volume=0,
                                      const long volume=0,
                                      const int spread=0)
                          { this.m_series.Refresh(time,open,high,low,close,tick_volume,volume,spread);                  }

El método GetTimeSeriesCollection() retorna al programa de control el puntero al objeto de colección de series temporales. Permite obtener en el programa el puntero a la colección y trabajar con ella de forma plenamente operativa.

El método GetListTimeSeries() retorna al programa de control el puntero a la lista de series temporales de la colección. Permite obtener en el programa el puntero a la lista de objetos de series temporales CTimeSeries y trabajar con ella de forma plenamente operativa.

Los métodos sobrecargados SeriesSetAvailable() dan acceso a los métodos SetAvailable() de la clase de colección de series temporales CTimeSeriesCollection(), que ya hemos analizado anteriormente.

Los métodos sobrecargados SeriesSetRequiredUsedData() dan acceso a los métodos SetRequiredUsedData() de la clase de colección de series temporales CTimeSeriesCollection(), que ya hemos analizado anteriormente.

Los métodos sobrecargados SeriesSyncData() dan acceso a los métodos SyncData() de la clase de colección de series temporales CTimeSeriesCollection(), que ya hemos analizado anteriormente.

Los métodos sobrecargados SeriesCreate() dan acceso a los métodos CreateSeries() de la clase de colección de series temporales CTimeSeriesCollection(), que ya hemos analizado anteriormente.

Los métodos sobrecargados SeriesRefresh() dan acceso a los métodos Refresh() de la clase de colección de series temporales CTimeSeriesCollection(), que ya hemos analizado anteriormente.

Y estas son todas las tareas necesarias por hoy en cuanto a la mejora de las clases para la simulación de la nueva clase de colección de series temporales.

Simulación

Para realizar la simulación de la creación y el rellenado de la colección de series temporales, tomaremos el asesor del artículo anterior
y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part37\ con el nuevo nombre TestDoEasyPart37.mq5.

Para seleccionar el modo en que el programa trabajará con los símbolos, tenemos la enumeración Datas.mqh

//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work with the current symbol only
   SYMBOLS_MODE_DEFINES,                              // Work with the specified symbol list
   SYMBOLS_MODE_MARKET_WATCH,                         // Work with the Market Watch window symbols
   SYMBOLS_MODE_ALL                                   // Work with the full symbol list
  };
//+------------------------------------------------------------------+

Y en los parámetros de entrada del asesor, tenemos una variable que permite seleccionar los símbolos para trabajar con ellos:

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list

Usando como base los valores de esta variable, se crea una matriz de símbolos que se transmite a la biblioteca al inicializarse esta en la función del asesor OnInitDoEasy(), y ya después en la biblioteca se crea la lista de símbolos de trabajo.

Las mismas operaciones debemos realizar para seleccionar y crear la lista de marcos temporales de trabajo del asesor.
Vamos a crear una nueva enumeración en el archivo Datas.mqh para seleccionar los modos de trabajo con los periodos de los gráficos de los símbolos:

//+------------------------------------------------------------------+
//|                                                        Datas.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
//+------------------------------------------------------------------+
//| Macrosustituciones                                               |
//+------------------------------------------------------------------+
#define INPUT_SEPARATOR                (",")          // Separator in the inputs string
#define TOTAL_LANG                     (2)            // Number of used languages
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Modes of working with symbols                                    |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Work with the current symbol only
   SYMBOLS_MODE_DEFINES,                              // Work with the specified symbol list
   SYMBOLS_MODE_MARKET_WATCH,                         // Work with the Market Watch window symbols
   SYMBOLS_MODE_ALL                                   // Work with the full symbol list
  };
//+------------------------------------------------------------------+
//| Mode of working with timeframes                                  |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Work with the current timeframe only
   TIMEFRAMES_MODE_LIST,                              // Work with the specified timeframe list
   TIMEFRAMES_MODE_ALL                                // Work with the full timeframe list
  };
//+------------------------------------------------------------------+

En el archivo de funciones de servicio, tenemos una función para preparar la matriz de símbolos utilizados para la biblioteca:

//+------------------------------------------------------------------+
//| Prepare the symbol array for a symbol collection                 |
//+------------------------------------------------------------------+
bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[])
  {
   //--- When working with the current symbol
   if(mode_used_symbols==SYMBOLS_MODE_CURRENT)
     {
      //--- Write the name of the current symbol to the only array cell
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=Symbol();
      return true;
     }
   //--- If working with a predefined symbol set (from the defined_used_symbols string)
   else if(mode_used_symbols==SYMBOLS_MODE_DEFINES)
     {
      //--- Set a comma as a separator
      string separator=",";
      //--- Replace erroneous separators with correct ones
      if(StringFind(defined_used_symbols,";")>WRONG_VALUE)  StringReplace(defined_used_symbols,";",separator);   
      if(StringFind(defined_used_symbols,":")>WRONG_VALUE)  StringReplace(defined_used_symbols,":",separator); 
      if(StringFind(defined_used_symbols,"|")>WRONG_VALUE)  StringReplace(defined_used_symbols,"|",separator);   
      if(StringFind(defined_used_symbols,"/")>WRONG_VALUE)  StringReplace(defined_used_symbols,"/",separator); 
      if(StringFind(defined_used_symbols,"\\")>WRONG_VALUE) StringReplace(defined_used_symbols,"\\",separator);  
      if(StringFind(defined_used_symbols,"'")>WRONG_VALUE)  StringReplace(defined_used_symbols,"'",separator); 
      if(StringFind(defined_used_symbols,"-")>WRONG_VALUE)  StringReplace(defined_used_symbols,"-",separator);   
      if(StringFind(defined_used_symbols,"`")>WRONG_VALUE)  StringReplace(defined_used_symbols,"`",separator);
      //--- Delete as long as there are spaces
      while(StringFind(defined_used_symbols," ")>WRONG_VALUE && !IsStopped()) 
         StringReplace(defined_used_symbols," ","");
      //--- As soon as there are double separators (after removing spaces between them), replace them with a separator
      while(StringFind(defined_used_symbols,separator+separator)>WRONG_VALUE && !IsStopped())
         StringReplace(defined_used_symbols,separator+separator,separator);
      //--- If a single separator remains before the first symbol in the string, replace it with a space
      if(StringFind(defined_used_symbols,separator)==0) 
         StringSetCharacter(defined_used_symbols,0,32);
      //--- If a single separator remains after the last symbol in the string, replace it with a space
      if(StringFind(defined_used_symbols,separator)==StringLen(defined_used_symbols)-1)
         StringSetCharacter(defined_used_symbols,StringLen(defined_used_symbols)-1,32);
      //--- Remove all redundant things to the left and right
      #ifdef __MQL5__
         StringTrimLeft(defined_used_symbols);
         StringTrimRight(defined_used_symbols);
      //---  __MQL4__
      #else 
         defined_used_symbols=StringTrimLeft(defined_used_symbols);
         defined_used_symbols=StringTrimRight(defined_used_symbols);
      #endif 
      //--- Prepare the array 
      ArrayResize(used_symbols_array,0);
      ResetLastError();
      //--- divide the string by separators (comma) and add all found substrings to the array
      int n=StringSplit(defined_used_symbols,StringGetCharacter(separator,0),used_symbols_array);
      //--- if nothing is found, display the appropriate message (working with the current symbol is selected automatically)
      if(n<1)
        {
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_STRING)+Symbol() :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)GetLastError()
           );
         Print(err);
         return false;
        }
     }
   //--- If working with the Market Watch window or the full list
   else
     {
      //--- Add the (mode_used_symbols) working mode to the only array cell
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=EnumToString(mode_used_symbols);
     }
   return true;
  }
//+------------------------------------------------------------------+

Tenemos que hacer una función semejante para preparar la matriz de marcos temporales utilizados. Y aquí entendemos que vamos a tener el mismo bloque de código en dos funciones análogas (marcado a color en el listado presentado).
Sacamos este bloque de código a una función aparte:

//+------------------------------------------------------------------+
//| Prepare the passed string of parameters                          |
//+------------------------------------------------------------------+
int StringParamsPrepare(string defined_used,string separator,string &array[])
  {
//--- Replace erroneous separators with correct ones
   if(separator!=";" && StringFind(defined_used,";")>WRONG_VALUE)  StringReplace(defined_used,";",separator);   
   if(separator!=":" && StringFind(defined_used,":")>WRONG_VALUE)  StringReplace(defined_used,":",separator); 
   if(separator!="|" && StringFind(defined_used,"|")>WRONG_VALUE)  StringReplace(defined_used,"|",separator);   
   if(separator!="/" && StringFind(defined_used,"/")>WRONG_VALUE)  StringReplace(defined_used,"/",separator); 
   if(separator!="\\"&& StringFind(defined_used,"\\")>WRONG_VALUE) StringReplace(defined_used,"\\",separator);  
   if(separator!="'" && StringFind(defined_used,"'")>WRONG_VALUE)  StringReplace(defined_used,"'",separator); 
   if(separator!="-" && StringFind(defined_used,"-")>WRONG_VALUE)  StringReplace(defined_used,"-",separator);   
   if(separator!="`" && StringFind(defined_used,"`")>WRONG_VALUE)  StringReplace(defined_used,"`",separator);
//--- Delete as long as there are spaces
   while(StringFind(defined_used," ")>WRONG_VALUE && !IsStopped()) 
      StringReplace(defined_used," ","");
//--- As soon as there are double separators (after removing spaces between them), replace them with a separator
   while(StringFind(defined_used,separator+separator)>WRONG_VALUE && !IsStopped())
      StringReplace(defined_used,separator+separator,separator);
//--- If a single separator remains before the first symbol in the string, replace it with a space
   if(StringFind(defined_used,separator)==0) 
      StringSetCharacter(defined_used,0,32);
//--- If a single separator remains after the last symbol in the string, replace it with a space
   if(StringFind(defined_used,separator)==StringLen(defined_used)-1)
      StringSetCharacter(defined_used,StringLen(defined_used)-1,32);
//--- Remove all redundant things to the left and right
   #ifdef __MQL5__
      StringTrimLeft(defined_used);
      StringTrimRight(defined_used);
//---  __MQL4__
   #else 
      defined_used=StringTrimLeft(defined_used);
      defined_used=StringTrimRight(defined_used);
   #endif 
//--- Prepare the array 
   ArrayResize(array,0);
   ResetLastError();
//--- divide the string by separators (comma), write all detected substrings into the array and return the number of obtained substrings
   return StringSplit(defined_used,StringGetCharacter(separator,0),array);
  }
//+------------------------------------------------------------------+

Y entonces, la función para crear la matriz de símbolos utilizados adoptará el aspecto que sigue:

//+------------------------------------------------------------------+
//| Prepare the symbol array for a symbol collection                 |
//+------------------------------------------------------------------+
bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[])
  {
//--- When working with the current symbol
   if(mode_used_symbols==SYMBOLS_MODE_CURRENT)
     {
      //--- Write the name of the current symbol to the only array cell
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=Symbol();
      return true;
     }
//--- If working with a predefined symbol set (from the defined_used_symbols string)
   else if(mode_used_symbols==SYMBOLS_MODE_DEFINES)
     {
      //--- Set comma as a separator (defined in the Datas.mqh file, page 11)
      string separator=INPUT_SEPARATOR;
      int n=StringParamsPrepare(defined_used_symbols,separator,used_symbols_array);
      //--- if nothing is found, display the appropriate message (working with the current symbol is selected automatically)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING)+Symbol() :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         return false;
        }
     }
//--- If working with the Market Watch window or the full list
   else
     {
      //--- Add the (mode_used_symbols) working mode to the only array cell
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=EnumToString(mode_used_symbols);
     }
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+

El bloque de código sacado a una función aparte ahora ha sido reemplazado por la llamada de la función de preparación de una línea de parámetros.

Ahora, creamos exactamente de la misma forma la función para preparar la matriz de marcos temporales utilizados en el programa:

//+------------------------------------------------------------------+
//| Prepare the array of timeframes for the timeseries collection    |
//+------------------------------------------------------------------+
bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[])
  {
//--- If working with the current chart period, set the current timeframe flag
   if(mode_used_periods==TIMEFRAMES_MODE_CURRENT)
     {
      ArrayResize(used_periods_array,1,21);
      used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
      return true;
     }
//--- If working with a predefined set of chart periods (from the defined_used_periods string)
   else if(mode_used_periods==TIMEFRAMES_MODE_LIST)
     {
      //--- Set comma as a separator (defined in the Datas.mqh file, page 11)
      string separator=INPUT_SEPARATOR;
      //--- Fill in the array of parameters from the string with predefined timeframes
      int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array);
      //--- if nothing is found, display the appropriate message (working with the current period is selected automatically)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         //--- Set the current period to the array
         ArrayResize(used_periods_array,1,21);
         used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         return false;
        }
     }
//--- If working with the full list of timeframes, fill in the array with strings describing all timeframes
   else
     {
      ArrayResize(used_periods_array,21,21);
      for(int i=0;i<21;i++)
         used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1)));
     }
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+

Aquí, llamamos exactamente de la misma manera la función para preparar la línea de parámetros: estos códigos son idénticos en las dos funciones CreateUsedSymbolsArray() y CreateUsedTimeframesArray().

En el mismo lugar, en el archivo DELib.mqh, tenemos una función que muestra la hora con milisegundos:

//+------------------------------------------------------------------+
//| Retorna la hora con milisegundos                                 |
//+------------------------------------------------------------------+
string TimeMSCtoString(const long time_msc)
  {
   return TimeToString(time_msc/1000,TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"."+IntegerToString(time_msc%1000,3,'0');
  }
//+------------------------------------------------------------------+

La función siempre muestra la fecha y la hora en el formato YYYY.MM.DD HH:MM:SS.MSC

Creamos la posibilidad de seleccionar el formato para mostrar la hora:

//+------------------------------------------------------------------+
//| Retorna la hora con milisegundos                                 |
//+------------------------------------------------------------------+
string TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS)
  {
   return TimeToString(time_msc/1000,flags)+"."+IntegerToString(time_msc%1000,3,'0');
  }
//+------------------------------------------------------------------+

Ahora, será posible establecer el formato de muestra de hora + milisegundos.

Añadimos al bloque de parámetros de entrada del archivo del asesor la selección de los marcos temporales utilizados:

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds

InpModeUsedTFs permite seleccionar el modo de uso de los marcos temporales

  • Trabajar solo con el marco temporal actual
  • Trabajar con la lista de marcos temporales establecida
  • Trabajar con la lista de marcos temporales completa

Al seleccionar el segundo punto, en el programa se usará la lista de marcos temporales descritos en la línea establecida por el parámetro de entrada de la variable InpUsedTFs.

Ahora, no vamos a eliminar del bloque de variables globales del asesor la variable necesaria que declara el objeto de clase CTimeSeries, ya que el acceso a las series temporales se realiza recurriendo al objeto de la biblioteca engine.

//--- global variables
CEngine        engine;
CTimeSeries    timeseries;
SDataButt      butt_data[TOTAL_BUTT];

En este mismo bloque de variables globales, añadimos una nueva matriz en la que se registrarán las denominaciones de los marcos temporales utilizados por el programa:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pressed_pending_buy;
bool           pressed_pending_buy_limit;
bool           pressed_pending_buy_stop;
bool           pressed_pending_buy_stoplimit;
bool           pressed_pending_close_buy;
bool           pressed_pending_close_buy2;
bool           pressed_pending_close_buy_by_sell;
bool           pressed_pending_sell;
bool           pressed_pending_sell_limit;
bool           pressed_pending_sell_stop;
bool           pressed_pending_sell_stoplimit;
bool           pressed_pending_close_sell;
bool           pressed_pending_close_sell2;
bool           pressed_pending_close_sell_by_buy;
bool           pressed_pending_delete_all;
bool           pressed_pending_close_all;
bool           pressed_pending_sl;
bool           pressed_pending_tp;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         array_used_symbols[];
string         array_used_periods[];
bool           testing;
uchar          group1;
uchar          group2;
double         g_point;
int            g_digits;
//+------------------------------------------------------------------+

Eliminamos del manejador OnInit() del asesor el código para crear las dos series temporales que han quedado de las anteriores simulaciones:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq);
   bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq);
   g_point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
//--- Initialize random group numbers
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- Check and remove remaining EA graphical objects
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Set trailing activation button status
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- Reset states of the buttons for working using pending requests
   for(int i=0;i<14;i++)
     {
      ButtonState(butt_data[i].name+"_PRICE",false);
      ButtonState(butt_data[i].name+"_TIME",false);
     }

//--- Check playing a standard sound by macro substitution and a custom sound by description
   engine.PlaySoundByDescription(SND_OK);
   Sleep(600);
   engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2"));

//--- Set a symbol for created timeseries
   timeseries.SetSymbol(Symbol());
//#define TIMESERIES_ALL
//--- Create two timeseries
   #ifndef TIMESERIES_ALL
      timeseries.SyncData(PERIOD_CURRENT,10);
      timeseries.Create(PERIOD_CURRENT);
      timeseries.SyncData(PERIOD_M15,2);
      timeseries.Create(PERIOD_M15);
//--- Create all timeseries
   #else 
      timeseries.SyncAllData();
      timeseries.CreateAll();
   #endif 
//--- Check created timeseries
   CArrayObj *list=timeseries.GetList();
   Print(TextByLanguage("Данные созданных таймсерий:","Data of created timeseries:"));
   for(int i=0;i<list.Total();i++)
     {
      CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i);
      if(series_obj==NULL || series_obj.AmountUsedData()==0 || series_obj.DataTotal()==0)
         continue;
      Print(
            DFUN,i,": ",series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe()),
            ": AmountUsedData=",series_obj.AmountUsedData(),", DataTotal=",series_obj.DataTotal(),", Bars=",series_obj.Bars()
           );
     }
   Print("");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Y también eliminamos del manejador OnTick() del aseasor el código de actualización de las series temporales creadas (la actualización de las series temporales la implementaremos en el próximo artículo):

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();       // Working in the timer
      PressButtonsControl();  // Button pressing control
      EventsHandling();       // Working with events
     }
//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();    // Trailing positions
      TrailingOrders();       // Trailing of pending orders
     }
//--- Update created timeseries
   CArrayObj *list=timeseries.GetList();
   for(int i=0;i<list.Total();i++)
     {
      CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i);
      if(series_obj==NULL || series_obj.DataTotal()==0)
         continue;
      series_obj.Refresh();
      if(series_obj.IsNewBar(0))
        {
         Print(TextByLanguage("Новый бар на ","New bar on "),series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe())," ",TimeToString(series_obj.Time(0)));
         if(series_obj.Timeframe()==Period())
            engine.PlaySoundByDescription(SND_NEWS);
        }
     }
  }
//+------------------------------------------------------------------+

A continuación, añadimos a la función de inicialización de la biblioteca OnInitDoEasy() la creación y representación de la lista de marcos temporales utilizados en el asesor, así como la muestra de la fecha y la hora de inicialización de la biblioteca en el diario.
Veamos el listado completo. Para comprender mejor qué hemos hecho exactamente, hemos destacado a color los cambios y hemos comentado las líneas:

//+------------------------------------------------------------------+
//| Initializing DoEasy library                                      |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Check if working with the full list is selected
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Set the counter start point to measure the approximate library initialization time
   ulong begin=GetTickCount();
   Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---"));
//--- Fill in the array of used symbols
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols);
//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries
   engine.SetUsedSymbols(array_used_symbols);
//--- Displaying the selected mode of working with the symbol object collection in the journal
   string num=
     (
      used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : 
      TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal()
     );
   Print(engine.ModeSymbolsListDescription(),num);
//--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,1000);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   
//--- Set used timeframes
   CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods);
//--- Display the selected mode of working with the timeseries object collection
   string mode=
     (
      InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT   ? 
         TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period())   :
      InpModeUsedTFs==TIMEFRAMES_MODE_LIST      ? 
         TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:")                                              :
      TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:")
     );
   Print(mode);
//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- Create timeseries of all used symbols
   CArrayObj *list_timeseries=engine.GetListTimeSeries();
   int total=list_timeseries.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=list_timeseries.At(i);
      if(list_timeseries==NULL)
         continue;
      int total_periods=ArraySize(array_used_periods);
      for(int j=0;j<total_periods;j++)
        {
         ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]);
         engine.SeriesSyncData(timeseries.Symbol(),timeframe);
         engine.SeriesCreate(timeseries.Symbol(),timeframe);
        }
     }
//--- Check created timeseries - display descriptions of all created timeseries in the journal
//--- (true - only created ones, false - created and declared ones)
   engine.GetTimeSeriesCollection().PrintShort(true); // Short descriptions
   //engine.GetTimeSeriesCollection().Print(false);   // Full descriptions

//--- Create resource text files
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Pass all existing collections to the trading class
   engine.TradingOnInit();

//--- Set the default magic number for all used symbols
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Set synchronous passing of orders for all used symbols
   engine.TradingSetAsyncMode(false);
//--- Set the number of trading attempts in case of an error
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Set correct order expiration and filling types to all trading objects
   engine.TradingSetCorrectTypeExpiration();
   engine.TradingSetCorrectTypeFilling();

//--- Set standard sounds for trading objects of all used symbols
   engine.SetSoundsStandart();
//--- Set the general flag of using sounds
   engine.SetUseSounds(InpUseSounds);
//--- Set the spread multiplier for symbol trading objects in the symbol collection
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Set controlled values for symbols
   //--- Get the list of all collection symbols
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- In a loop by the list, set the necessary values for tracked symbol properties
      //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" 
      //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Set control of the symbol price increase by 100 points
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Set control of the symbol price decrease by 100 points
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Set control of the symbol spread increase by 40 points
         symbol.SetControlSpreadInc(400);
         //--- Set control of the symbol spread decrease by 40 points
         symbol.SetControlSpreadDec(400);
         //--- Set control of the current spread by the value of 40 points
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Set controlled values for the current account
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Set control of the profit increase to 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Set control of the funds increase to 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Set profit control level to 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
//--- Get the end of the library initialization time counting and display it in the journal
   ulong end=GetTickCount();
   Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS));
  }
//+------------------------------------------------------------------+

Estas son todas las mejoras del asesor de prueba.
Lo compilamos y lo iniciamos, estableciendo en los parámetros el uso del símbolo y el marco temporal actual.
En el diario podremos ver los mensajes:

--- Initializing "DoEasy" library ---
Working with the current symbol only: "EURUSD"
Working with the current timeframe only: H4
EURUSD symbol timeseries: 
Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5330
Library initialization time: 00:00:00.141

Establecemos en los ajustes el uso del símbolo actual y la lista de marcos temporales establecida (en la lista se indican los principales marcos temporales).
En el diario podremos ver los mensajes:

--- Initializing "DoEasy" library ---
Working with the current symbol only: "EURUSD"
Working with the specified timeframe list:
"M1"  "M5"  "M15" "M30" "H1"  "H4"  "D1"  "W1"  "MN1"
EURUSD symbol timeseries: 
Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3286
Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3566
Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3109
Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2894
Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5505
Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5330
Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087
Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564
Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590
Library initialization time: 00:00:00.032

Establecemos en los ajustes el uso del símbolo actual y la lista completa de marcos temporales.
En el diario podremos ver los mensajes:

--- Initializing "DoEasy" library ---
Working with the current symbol only: "EURUSD"
Working with the full list of timeframes:
"M1"  "M2"  "M3"  "M4"  "M5"  "M6"  "M10" "M12" "M15" "M20" "M30" "H1"  "H2"  "H3"  "H4"  "H6"  "H8"  "H12" "D1"  "W1"  "MN1"
EURUSD symbol timeseries: 
Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3390
Timeseries "EURUSD" M2: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5626
Timeseries "EURUSD" M3: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4713
Timeseries "EURUSD" M4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4254
Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3587
Timeseries "EURUSD" M6: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4805
Timeseries "EURUSD" M10: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4035
Timeseries "EURUSD" M12: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3842
Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3116
Timeseries "EURUSD" M20: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3457
Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2898
Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5507
Timeseries "EURUSD" H2: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6303
Timeseries "EURUSD" H3: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6263
Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5331
Timeseries "EURUSD" H6: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5208
Timeseries "EURUSD" H8: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5463
Timeseries "EURUSD" H12: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5205
Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087
Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564
Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590
Library initialization time: 00:00:00.094

Establecemos en los ajustes el uso de la lista de símbolos establecida e indicamos en la lista los tres símbolos EURUSD,AUDUSD,EURAUD, así como el uso de la lista de marcos temporales establecida (en la lista se indican los principales marcos temporales).
En el diario podremos ver los mensajes:

--- Initializing "DoEasy" library ---
Working with predefined symbol list. The number of used symbols: 3
"AUDUSD" "EURUSD" "EURAUD"
Working with the specified timeframe list:
"M1"  "M5"  "M15" "M30" "H1"  "H4"  "D1"  "W1"  "MN1"
AUDUSD symbol timeseries: 
Timeseries "AUDUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3394
Timeseries "AUDUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4024
Timeseries "AUDUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3262
Timeseries "AUDUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3071
Timeseries "AUDUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5104
Timeseries "AUDUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5026
Timeseries "AUDUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5289
Timeseries "AUDUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 1401
Timeseries "AUDUSD" MN1: Requested: 1000, Actual: 323, Created: 323, On the server: 323
EURAUD symbol timeseries: 
Timeseries "EURAUD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3393
Timeseries "EURAUD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4025
Timeseries "EURAUD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3262
Timeseries "EURAUD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3071
Timeseries "EURAUD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5104
Timeseries "EURAUD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5026
Timeseries "EURAUD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4071
Timeseries "EURAUD" W1: Requested: 1000, Actual: 820, Created: 820, On the server: 820
Timeseries "EURAUD" MN1: Requested: 1000, Actual: 189, Created: 189, On the server: 189
EURUSD symbol timeseries: 
Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3394
Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3588
Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3116
Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2898
Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5507
Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5331
Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087
Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564
Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590
Library initialization time: 00:00:00.266

Bien, podemos ver que, dependiendo de los símbolos y los marcos temporales establecidos en los ajustes del asesor, podemos crear las series temporales necesarias. La fecha y la hora de creación de las series temporales depende del inicio del asesor (en frío o en caliente), y de si se han utilizado anteriormente los símbolos seleccionados y sus marcos temporales.

¿Qué es lo próximo?

En el próximo artículo, crearemos la funcionalidad para actualizar en tiempo real las series temporales creadas, enviar mensajes al programa sobre los eventos de "Nueva barra" para todas las series temporales utilizadas, y también para obtener los datos necesarios de las series temporales disponibles.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Trabajando con las series temporales en la biblioteca DoEasy (Parte 35): El objeto "Barra" y la lista de serie temporal del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 36): El objeto de series temporales de todos los periodos utilizados del símbolo


Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/7663

Archivos adjuntos |
MQL5.zip (3719.35 KB)
MQL4.zip (3719.35 KB)
El lenguaje MQL como medio de marcado de la interfaz gráfica de programas MQL. Parte 1 El lenguaje MQL como medio de marcado de la interfaz gráfica de programas MQL. Parte 1

En el presente artículo, presentamos un nuevo concepto para la descripción de la interfaz de ventana de los programas MQL con la ayuda de las construcciones del lenguaje MQL. Las clases especiales transforman el marcado visual de MQL en elementos de GUI, permitiendo de controlarlos, ajustar sus propiedades y procesar eventos de forma unificada. Asimismo, mostraremos ejemplos de uso del marcado en las ventanas de diálogo y los elementos de la biblioteca estándar.

Optimización móvil continua (Parte 5): Panorámica del proyecto del optimizador automático, creación de la interfaz gráfica Optimización móvil continua (Parte 5): Panorámica del proyecto del optimizador automático, creación de la interfaz gráfica

Continuamos con la descripción de la optimización móvil en el terminal MetaTrader 5. Tras analizar en los artículos anteriores los métodos de formación del informe de optimización y su método de filtrado, hemos procedido a describir la estructura interna de la aplicación encargada del propio proceso de optimización. El optimizador automático, ejecutado como una aplicación en C#, tiene su propia interfaz gráfica. Este artículo está dedicado precisamente a esta interfaz gráfica.

Trabajando con las series temporales en la biblioteca DoEasy (Parte 38): Colección de series temporales - Actualización en tiempo real y acceso a los datos desde el programa Trabajando con las series temporales en la biblioteca DoEasy (Parte 38): Colección de series temporales - Actualización en tiempo real y acceso a los datos desde el programa

En el artículo, analizaremos la actualización en tiempo real de los datos de las series temporales, así como el envío de mensajes sobre el evento "Nueva barra" al gráfico del programa de control de todas las series temporales de todos los símbolos para poder procesar estos eventos en nuestros propgramas. Para determinar la necesidad de actualizar las series temporales para el símbolo y los periodos del gráfico no actuales, usaremos la clase "Nuevo tick".

Una rápida inmersión en MQL5 Una rápida inmersión en MQL5

¿Ha decidido estudiar el lenguaje de programación de estrategias de trading MQL5 pero no sabe nada sobre él? Hemos intentado describir el terminal de MQL5 y Meta Trader 5 desde el punto de vista de una persona iniciada y para ello hemos escrito este corto artículo introductorio. En este artículo encontrará una breve descripción de las posibilidades de este lenguaje, así como algunos consejos sobre cómo trabajar con MetaEditor 5 y el terminal.