Trabajando con los precios en la biblioteca DoEasy (Parte 60): Lista de serie de datos de tick del símbolo
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Clase del objeto de la serie de datos de tick
- Simulando la creación de la lista y obtención de datos
- ¿Qué es lo próximo?
Concepto
En el artículo anterior, empezamos a desarrollar la funcionalidad para trabajar con datos de tick. Creamos la clase del objeto de datos de tick. Ahora vamos a crear una lista para almacenar estos objetos. Estas listas estarán disponibles para cada uno de los símbolos utilizados en el programa. Por defecto, el tamaño de las listas de datos de tick va a cubrir el día actual. Lo único es que tendremos la posibilidad de definir la cantidad de días durante los cuales el programa va a poseer el conjunto de datos de tick.
Surge la pregunta, ¿para qué necesitamos nuestras propias listas de datos de tick si MQL5 permite obtenerlos en cualquier momento? La cosa es que si tenemos estas listas de datos de tick a mano, podremos buscar cualquier información en ellas de forma cómoda y rápida, así como comparar y obtener datos. Esta posibilidad se provee por el mismo concepto de la construcción de las listas en la biblioteca y del trabajo con ellas.
Si tenemos a mano estas listas creadas para cada símbolo que se utiliza en el programa, las uniremos en una colección de datos de tick. Esta colección nos permitirá hacer el trabajo con datos de tick más conveniente y realizar cualquier análisis con flujos de tick de símbolos diferentes.
Mejorando las clases de la biblioteca
En primer lugar, vamos a añadir nuevos mensajes de la biblioteca en el archivo \MQL5\Include\DoEasy\Data.mqh. Añadimos los índices de mensajes nuevos:
//--- CTick MSG_TICK_TEXT_TICK, // Tick MSG_TICK_TIME_MSC, // Time of the last update of prices in milliseconds MSG_TICK_TIME, // Time of the last update of prices MSG_TICK_VOLUME, // Volume for the current Last price MSG_TICK_FLAGS, // Flags MSG_TICK_VOLUME_REAL, // Volume for the current Last price with greater accuracy MSG_TICK_SPREAD, // Spread MSG_LIB_TEXT_TICK_CHANGED_DATA, // Changed data on tick: MSG_LIB_TEXT_TICK_FLAG_BID, // Bid price change MSG_LIB_TEXT_TICK_FLAG_ASK, // Ask price change MSG_LIB_TEXT_TICK_FLAG_LAST, // Last deal price change MSG_LIB_TEXT_TICK_FLAG_VOLUME, // Volume change //--- CTickSeries MSG_TICKSERIES_TEXT_TICKSERIES, // Tick series MSG_TICKSERIES_ERR_GET_TICK_DATA, // Failed to get tick data MSG_TICKSERIES_FAILED_CREATE_TICK_DATA_OBJ, // Failed to create tick data object MSG_TICKSERIES_FAILED_ADD_TO_LIST, // Failed to add tick data object to list MSG_TICKSERIES_TEXT_IS_NOT_USE, // Tick series not used. Set the flag using SetAvailable() MSG_TICKSERIES_REQUIRED_HISTORY_DAYS, // Requested number of days }; //+------------------------------------------------------------------+
y añadimos los mensajes de texto correspondientes a los índices nuevamente añadidos:
//--- CTick {"Тик","Tick"}, {"Время последнего обновления цен в миллисекундах","Last price update time in milliseconds"}, {"Время последнего обновления цен","Last price update time"}, {"Объем для текущей цены Last","Volume for the current Last price"}, {"Флаги","Flags"}, {"Объем для текущей цены Last c повышенной точностью","Volume for the current \"Last\" price with increased accuracy"}, {"Спред","Spread"}, {"Изменённые данные на тике:","Changed data on a tick:"}, {"Изменение цены Bid","Bid price change"}, {"Изменение цены Ask","Ask price change"}, {"Изменение цены последней сделки","Last price change"}, {"Изменение объема","Volume change"}, //--- TickSeries {"Тиковая серия","Tickseries"}, {"Ошибка получения тиковых данных","Error getting tick data"}, {"Не удалось создать объект тиковых данных","Failed to create tick data object"}, {"Не удалось добавить объект тиковых данных в список","Failed to add tick data object to the list"}, {"Тиковая серия не используется. Нужно установить флаг использования при помощи SetAvailable()","Tick series are not used. Need to set the use flag using SetAvailable()"}, {"Запрошенное количество дней: ","Number of days requested: "}, }; //+---------------------------------------------------------------------+
Por defecto, los datos de tick se guardarán para el día actual. Para indicar la cantidad de días durante los cuales la biblioteca va a guardar los ticks, introduciremos una constante nueva (macrosustitución) en el archivo \MQL5\Include\DoEasy\Defines.mqh:
//--- Timeseries parameters #define SERIES_DEFAULT_BARS_COUNT (1000) // Required default amount of timeseries data #define PAUSE_FOR_SYNC_ATTEMPTS (16) // Amount of pause milliseconds between synchronization attempts #define ATTEMPTS_FOR_SYNC (5) // Number of attempts to receive synchronization with the server //--- Tick series parameters #define TICKSERIES_DEFAULT_DAYS_COUNT (1) // Required number of days for tick data in default series
Cuando volví a analizar el código de la clase de la serie temporal en el archivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh, me fijé en un error que había cometido: si el objeto de barra creado no ha sido añadido por alguna razón a la lista, entonces, no se elimina. Eso puede provocar una fuga de memoria. Por eso, en el método de la creación de la lista de serie temporal, añadiremos la eliminación del objeto en el caso si no ha sido añadido a la lista:
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeriesDE::Create(const uint required=0) { //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, //--- while being lower than the available bar number, //--- set the new value of the required history depth for the list else if(required>0 && this.m_amount!=required && required<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetRequiredUsedData(required,0)) return 0; } //--- For the rates[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the bar object list and set the flag of sorting by bar index MqlRates rates[]; ::ArraySetAsSeries(rates,true); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); ::ResetLastError(); //--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,(uint)this.m_amount,rates),err=ERR_SUCCESS; if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- Historical data is received in the rates[] array //--- In the rates[] array loop, for(int i=0; i<copied; i++) { //--- create a new bar object out of the current MqlRates structure by the loop index ::ResetLastError(); CBar* bar=new CBar(this.m_symbol,this.m_timeframe,rates[i]); if(bar==NULL) { ::Print ( DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ)," ",this.Header()," ",::TimeToString(rates[i].time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError()) ); continue; } //--- If failed to add bar object to the list, //--- display the appropriate message with the error description in the journal //--- and remove the newly created object if(!this.m_list_series.Add(bar)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header()," ",::TimeToString(rates[i].time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); delete bar; } } //--- Return the size of the created bar object list return this.m_list_series.Total(); } //+------------------------------------------------------------------+
Para tener la posibilidad de buscar, ordenar y seleccionar objetos de tick en la lista creada, tenemos que completar el archivo
\MQL5\Include\DoEasy\Services\Select.mqh con los métodos para manejar esta lista y los datos de tick.
Incluimos el archivo de la clase del objeto de datos de tick en la clase.
//+------------------------------------------------------------------+ //| Select.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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" #include "..\Objects\Symbols\Symbol.mqh" #include "..\Objects\PendRequest\PendRequest.mqh" #include "..\Objects\Series\SeriesDE.mqh" #include "..\Objects\Indicators\Buffer.mqh" #include "..\Objects\Indicators\IndicatorDE.mqh" #include "..\Objects\Indicators\DataInd.mqh" #include "..\Objects\Ticks\DataTick.mqh" //+------------------------------------------------------------------+
Añadimos las declaraciones de los métodos del trabajo con datos de tick al final del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Methods of work with indicator data | //+------------------------------------------------------------------+ //--- Return the list of indicator data with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByIndicatorDataProperty(CArrayObj *list_source,ENUM_IND_DATA_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByIndicatorDataProperty(CArrayObj *list_source,ENUM_IND_DATA_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByIndicatorDataProperty(CArrayObj *list_source,ENUM_IND_DATA_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the indicator data index in the list with the maximum value of (1) integer, (2) real and (3) string property of data static int FindIndDataMax(CArrayObj *list_source,ENUM_IND_DATA_PROP_INTEGER property); static int FindIndDataMax(CArrayObj *list_source,ENUM_IND_DATA_PROP_DOUBLE property); static int FindIndDataMax(CArrayObj *list_source,ENUM_IND_DATA_PROP_STRING property); //--- Return the indicator data index in the list with the minimum value of (1) integer, (2) real and (3) string property of data static int FindIndDataMin(CArrayObj *list_source,ENUM_IND_DATA_PROP_INTEGER property); static int FindIndDataMin(CArrayObj *list_source,ENUM_IND_DATA_PROP_DOUBLE property); static int FindIndDataMin(CArrayObj *list_source,ENUM_IND_DATA_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with tick data | //+------------------------------------------------------------------+ //--- Return the list of tick data with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the tick data index in the list with the maximum value of (1) integer, (2) real and (3) string property of data static int FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_INTEGER property); static int FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_DOUBLE property); static int FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_STRING property); //--- Return the tick data index in the list with the minimum value of (1) integer, (2) real and (3) string property of data static int FindTickDataMin(CArrayObj *list_source,ENUM_TICK_PROP_INTEGER property); static int FindTickDataMin(CArrayObj *list_source,ENUM_TICK_PROP_DOUBLE property); static int FindTickDataMin(CArrayObj *list_source,ENUM_TICK_PROP_STRING property); //--- }; //+------------------------------------------------------------------+
Implementamos fuera del cuerpo de la clase los métodos declarados:
//+------------------------------------------------------------------+ //| Methods of working with tick data lists | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of tick data with one of integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); int total=list_source.Total(); for(int i=0; i<total; i++) { CDataTick *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of tick data with one of real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CDataTick *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of tick data with one of string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByTickDataProperty(CArrayObj *list_source,ENUM_TICK_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CDataTick *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_INTEGER property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDataTick *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_DOUBLE property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDataTick *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMax(CArrayObj *list_source,ENUM_TICK_PROP_STRING property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CDataTick *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMin(CArrayObj* list_source,ENUM_TICK_PROP_INTEGER property) { int index=0; CDataTick *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMin(CArrayObj* list_source,ENUM_TICK_PROP_DOUBLE property) { int index=0; CDataTick *min_obj=NULL; int total=list_source.Total(); if(total== 0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the tick data in the list | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindTickDataMin(CArrayObj* list_source,ENUM_TICK_PROP_STRING property) { int index=0; CDataTick *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CDataTick *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+
Ya consideramos el funcionamiento de estos métodos más de una vez. Para más información, consulte el artículo 3.
Clase del objeto de la serie de datos de tick
Ahora, vamos a escribir la clase del objeto de lista de datos de tick. La lista, como otras tantas en la biblioteca, será representada por una matriz creada a base de la clase de una matriz dinámica de punteros a las instancias de la clase CObject y sus herederos.
Vamos a calcular el comienzo del día necesario dependiendo de la cantidad de días establecida, para los cuales será necesario guardar la historia de ticks. A partir de este día, vamos a introducir en la lista todos los ticks existentes usando CopyTicksRange(). En el siguiente artículo, organizaremos la actualización de estas listas en tiempo real para poder disponer siempre en nuestro programa de una base de datos de tick actual en la colección.
La estructura de la clase del objeto de la lista de datos de tick es muy parecida a la clase de la lista de serie temporal del símbolo. La única diferencia es que ahí la unidad mínima para almacenar datos es un objeto de barra, mientras que aquí es un objeto de tick. La composición de la clase será estándar para la biblioteca. Por eso, analizaremos su cuerpo íntegramente, y luego aclararemos algunos detalles y métodos:
//+------------------------------------------------------------------+ //| TickSeries.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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewTickObj.mqh" #include "DataTick.mqh" //+------------------------------------------------------------------+ //| "Tick data series" class | //+------------------------------------------------------------------+ class CTickSeries : public CBaseObj { private: string m_symbol; // Symbol uint m_amount; // Amount of applied tick series data uint m_required; // Required number of days for tick series data CArrayObj m_list_ticks; // List of tick data CNewTickObj m_new_tick_obj; // "New tick" object public: //--- Return (1) itself, (2) list of tick data and (3) "New tick" object of the tick series CTickSeries *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &m_list_ticks; } CNewTickObj *GetNewTickObj(void) { return &this.m_new_tick_obj;} //--- Return the list of tick objects by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj *GetList(ENUM_TICK_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByTickDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_TICK_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByTickDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_TICK_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByTickDataProperty(this.GetList(),property,value,mode); } //--- Return the object of tick data by (1) index in the list, (2) time and (4) list size CDataTick *GetTickByListIndex(const uint index); CDataTick *GetTick(const datetime time); CDataTick *GetTick(const ulong time_msc); int DataTotal(void) const { return this.m_list_ticks.Total(); } //--- The comparison method for searching identical tick series objects by a symbol virtual int Compare(const CObject *node,const int mode=0) const { const CTickSeries *compared_obj=node; return(this.Symbol()==compared_obj.Symbol() ? 0 : this.Symbol()>compared_obj.Symbol() ? 1 : -1); } //--- Return the tick series name string Header(void); //--- Display (1) the tick series description and (2) the tick series short description in the journal void Print(void); void PrintShort(void); //--- Constructors CTickSeries(void){;} CTickSeries(const string symbol,const uint required=0); //+------------------------------------------------------------------+ //| Methods of working with objects and accessing their properties | //+------------------------------------------------------------------+ //--- Set (1) a symbol and (2) a number of used tick series data void SetSymbol(const string symbol); void SetRequiredUsedBars(const uint required=0); //--- Return (1) symbol, (2) number of used, (3) requested tick data and (4) new tick flag string Symbol(void) const { return this.m_symbol; } ulong AvailableUsedData(void) const { return this.m_amount; } ulong RequiredUsedDays(void) const { return this.m_required; } bool IsNewTick(void) { return this.m_new_tick_obj.IsNewTick(); } //--- Return (1) Bid, (2) Ask, (3) Last, (4) volume with increased accuracy, //--- (5) spread, (6) volume, (7) tick flags, (8) time, (9) time in milliseconds by index in the list double Bid(const uint index); double Ask(const uint index); double Last(const uint index); double VolumeReal(const uint index); double Spread(const uint index); long Volume(const uint index); uint Flags(const uint index); datetime Time(const uint index); long TimeMSC(const uint index); //--- Return (1) Bid, (2) Ask, (3) Last, (4) volume with increased accuracy, //--- (5) spread, (6) volume, (7) tick flags by tick time in milliseconds double Bid(const ulong time_msc); double Ask(const ulong time_msc); double Last(const ulong time_msc); double VolumeReal(const ulong time_msc); double Spread(const ulong time_msc); long Volume(const ulong time_msc); uint Flags(const ulong time_msc); //--- Return (1) Bid, (2) Ask, (3) Last, (4) volume with increased accuracy, //--- (5) spread, (6) volume and (7) tick flags by tick time double Bid(const datetime time); double Ask(const datetime time); double Last(const datetime time); double VolumeReal(const datetime time); double Spread(const datetime time); long Volume(const datetime time); uint Flags(const datetime time); //--- (1) Create and (2) update the timeseries list int Create(const uint required=0); void Refresh(void); }; //+------------------------------------------------------------------+
La clase tiene incluidos los listados de las clases del objeto «Nuevo tick» y «Datos de tick» que hemos escrito antes. El objeto «Nuevo tick» será necesario cuando vamos a actualizar las listas de datos de tick en el siguiente artículo, el objeto «Datos de tick» es la clase de los objetos que serán colocados en la lista.
En la sección privada de la clase, han sido declaradas todas las variables de miembros de clase necesarias para almacenar valores de los parámetros del objeto, el objeto de la clase de la lista de objetos (el que será representado por la lista de ticks) y el objeto de nuevo tick que será necesario en el siguiente artículo para actualizar la lista en tiempo real.
La sección pública de la clase contiene los métodos para el funcionamiento del objeto que son estándar para los objetos de la biblioteca, métodos para acceder a las propiedades del objeto de lista de datos de tick, métodos para un acceso simplificado a las propiedades del objeto indicado de datos de tick que se encuentra en la lista y métodos para crear y actualizar la lista.
Vamos a analizar los métodos del objeto.
En el constructor paramétrico dela clase, limpiamos la lista, establecemos la bandera de una lista ordenada para ella según el tiempo del tick en milisegundos y definimos la cantidad necesaria de días para los cuales necesitamos tener la historia en la lista, usando para ello el método SetRequiredUsedDays().
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTickSeries::CTickSeries(const string symbol,const uint required=0) : m_symbol(symbol) { this.m_list_ticks.Clear(); this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC); this.SetRequiredUsedDays(required); } //+------------------------------------------------------------------+
El método para establecer el símbolo de la lista de ticks:
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CTickSeries::SetSymbol(const string symbol) { if(this.m_symbol==symbol) return; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } //+------------------------------------------------------------------+
Si el símbolo transferido al método ya está definido, salimos del método; de lo contrario, si ha sido transferido el valor NULL o una cadena vacía, definimos el símbolo actual. En caso contrario, definimos el símbolo transferido al método.
El método para definir la cantidad de días para los que se requieren los datos:
//+------------------------------------------------------------------+ //| Set the number of required tick data | //+------------------------------------------------------------------+ void CTickSeries::SetRequiredUsedDays(const uint required=0) { this.m_required=(required<1 ? TICKSERIES_DEFAULT_DAYS_COUNT : required); } //+------------------------------------------------------------------+
Si el método ha recibido el valor cero o menos de cero, establecemos la cantidad de días predefinida; de lo contrario, definimos la cantidad transferida.
El método que devuelve el objeto de tick desde la lista según el índice de la lista:
//+------------------------------------------------------------------+ //| Return the tick object by its index in the list | //+------------------------------------------------------------------+ CDataTick *CTickSeries::GetTickByListIndex(const uint index) { return this.m_list_ticks.At(index); } //+------------------------------------------------------------------+
Simplemente devolvemos el objeto que se encuentra en la lista según el índice transferido al método. Hay que tener en cuenta que el método At() devolverá NULL si el índice indicado es incorrecto o la lista está vacía.
El método que devuelve el objeto de tick desde la lista según su hora:
//+------------------------------------------------------------------+ //| Return the last tick object by its time | //+------------------------------------------------------------------+ CDataTick *CTickSeries::GetTick(const datetime time) { CArrayObj *list=GetList(TICK_PROP_TIME,time,EQUAL); if(list==NULL) return NULL; return list.At(list.Total()-1); } //+------------------------------------------------------------------+
Obtenemos la lista de los objetos de tick cuya hora corresponde a la hora transferida al método, y devolvemos el último de ellos.
Es que diferentes ticks pueden tener la hora igual, por tanto, aquí se devuelve el último de ellos.
El método que devuelve el objeto de tick desde la lista según su hora en milisegundos:
//+------------------------------------------------------------------+ //| Return the last tick object by its time in milliseconds | //+------------------------------------------------------------------+ CDataTick *CTickSeries::GetTick(const ulong time_msc) { CArrayObj *list=GetList(TICK_PROP_TIME_MSC,time_msc,EQUAL); if(list==NULL) return NULL; return list.At(list.Total()-1); } //+------------------------------------------------------------------+
Obtenemos la lista de los objetos de tick cuya hora en milisegundos corresponde a la hora transferida al método, y devolvemos el último de ellos.
Igual como en el método que acabamos de considerar, aquí los ticks diferentes pueden tener la misma hora en milisegundos. Por eso, en este caso, se devuelve el último de ellos ya que se considera el más relevante.
Los métodos subsiguientes para obtener los objetos de tick serán idénticos, y van a tener tres sobrecargas: obtención según el índice de la lista, obtención según la hora y obtención según la hora en milisegundos. Vamos a analizar estos tres métodos para obtener el precio Bid del tick. Para los demás métodos idénticos, simplemente vamos a mostrar su listado.
El método que retorna el precio Bid del objeto de tick según su índice en la lista:
//+------------------------------------------------------------------+ //| Return tick's Bid by index in the list | //+------------------------------------------------------------------+ double CTickSeries::Bid(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Bid() : 0); } //+------------------------------------------------------------------+
Obtenemos de la lista el objeto según el índice transferido al método y devolvemos Bid desde el objeto obtenido o 0 (si la obtención del objeto ha fallado).
El método que retorna el precio Bid del último objeto de tick en la lista según su hora en milisegundos:
//+------------------------------------------------------------------+ //| Return tick's Bid by time in milliseconds | //+------------------------------------------------------------------+ double CTickSeries::Bid(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Bid() : 0); } //+------------------------------------------------------------------+
Obtenemos de la lista el último objeto según la hora en milisegundos transferida al método (el método de la obtención ha sido considerado más arriba) y devolvemos Bid desde el objeto obtenido o 0 (si la obtención del objeto ha fallado).
El método que retorna el precio Bid del último objeto de tick en la lista según su hora:
//+------------------------------------------------------------------+ //| Return tick's Bid by time | //+------------------------------------------------------------------+ double CTickSeries::Bid(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Bid() : 0); } //+------------------------------------------------------------------+
Obtenemos de la lista el último objeto según la hora transferida al método (el método de la obtención ha sido considerado más arriba) y devolvemos Bid desde el objeto obtenido o 0 (si la obtención del objeto ha fallado).
Los demás métodos para obtener los valores de las propiedades de objetos de tick desde la lista son idénticos a estos tres métodos considerados. Puede analizarlos por sí mismo:
//+------------------------------------------------------------------+ //| Return tick's Ask by index in the list | //+------------------------------------------------------------------+ double CTickSeries::Ask(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Ask() : 0); } //+------------------------------------------------------------------+ //| Return tick's Ask by time in milliseconds | //+------------------------------------------------------------------+ double CTickSeries::Ask(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Ask() : 0); } //+------------------------------------------------------------------+ //| Return tick's Ask by time | //+------------------------------------------------------------------+ double CTickSeries::Ask(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Ask() : 0); } //+------------------------------------------------------------------+ //| Return tick's Last by index in the list | //+------------------------------------------------------------------+ double CTickSeries::Last(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Last() : 0); } //+------------------------------------------------------------------+ //| Return tick's Last by time in milliseconds | //+------------------------------------------------------------------+ double CTickSeries::Last(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Last() : 0); } //+------------------------------------------------------------------+ //| Return tick's Last by time | //+------------------------------------------------------------------+ double CTickSeries::Last(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Last() : 0); } //+-------------------------------------------------------------------------+ //| Return the volume with the increased tick accuracy by index in the list | //+-------------------------------------------------------------------------+ double CTickSeries::VolumeReal(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.VolumeReal() : 0); } //+--------------------------------------------------------------------------+ //|Return the volume with the increased tick accuracy by time in milliseconds| //+--------------------------------------------------------------------------+ double CTickSeries::VolumeReal(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.VolumeReal() : 0); } //+------------------------------------------------------------------+ //| Return the volume with the increased tick accuracy by time | //+------------------------------------------------------------------+ double CTickSeries::VolumeReal(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.VolumeReal() : 0); } //+------------------------------------------------------------------+ //| Return the tick spread by index in the list | //+------------------------------------------------------------------+ double CTickSeries::Spread(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Spread() : 0); } //+------------------------------------------------------------------+ //| Return tick's spread by time in milliseconds | //+------------------------------------------------------------------+ double CTickSeries::Spread(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Spread() : 0); } //+------------------------------------------------------------------+ //| Return tick's spread by time | //+------------------------------------------------------------------+ double CTickSeries::Spread(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Spread() : 0); } //+------------------------------------------------------------------+ //| Return the tick volume by index in the list | //+------------------------------------------------------------------+ long CTickSeries::Volume(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Volume() : 0); } //+------------------------------------------------------------------+ //| Return tick's volume by time in milliseconds | //+------------------------------------------------------------------+ long CTickSeries::Volume(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Volume() : 0); } //+------------------------------------------------------------------+ //| Return tick's volume by time | //+------------------------------------------------------------------+ long CTickSeries::Volume(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Volume() : 0); } //+------------------------------------------------------------------+ //| Return the tick flags by index in the list | //+------------------------------------------------------------------+ uint CTickSeries::Flags(const uint index) { CDataTick *tick=this.GetTickByListIndex(index); return(tick!=NULL ? tick.Flags() : 0); } //+------------------------------------------------------------------+ //| Return the tick flags by time in milliseconds | //+------------------------------------------------------------------+ uint CTickSeries::Flags(const ulong time_msc) { CDataTick *tick=this.GetTick(time_msc); return(tick!=NULL ? tick.Flags() : 0); } //+------------------------------------------------------------------+ //| Return the tick flags by time | //+------------------------------------------------------------------+ uint CTickSeries::Flags(const datetime time) { CDataTick *tick=this.GetTick(time); return(tick!=NULL ? tick.Flags() : 0); } //+------------------------------------------------------------------+
Cada uno de los métodos representa tres métodos sobrecargados iguales que permiten obtener el objeto de tick según su índice en la lista y según el tiempo en forma de la fecha, o bien en milisegundos.
El método que devuelve el nombre de cadena de la serie de tick:
//+------------------------------------------------------------------+ //| Return the tick series name | //+------------------------------------------------------------------+ string CTickSeries::Header(void) { return CMessage::Text(MSG_TICKSERIES_TEXT_TICKSERIES)+" \""+this.m_symbol+"\""; } //+------------------------------------------------------------------+
Devuelve la cadena en el formato
Tickseries "symbol name"
Por ejemplo:
Tick series "EURUSD"
El método que retorna al diario la descripción completa de la serie de tick:
//+------------------------------------------------------------------+ //| Display the tick series description in the journal | //+------------------------------------------------------------------+ void CTickSeries::Print(void) { string txt= ( CMessage::Text(MSG_TICKSERIES_REQUIRED_HISTORY_DAYS)+(string)this.RequiredUsedDays()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA)+(string)this.DataTotal() ); ::Print(this.Header(),": ",txt); } //+------------------------------------------------------------------+
En el método, se crea una cadena que muestra la descripción de la cantidad de días durante los cuales se almacenan los ticks en la serie, y la cantidad real de los ticks almacenados en la serie.
Luego se muestra el encabezado de la serie de tick y la cadena creada. Por ejemplo:
Tick series "EURUSD": Requested number of days: 1, Historical data created: 256714
El método que muestra en el diario la descripción breve de la serie de tick:
//+------------------------------------------------------------------+ //| Display the brief tick series description in the journal | //+------------------------------------------------------------------+ void CTickSeries::PrintShort(void) { ::Print(this.Header()); } //+------------------------------------------------------------------+
Muestra en el diario el nombre de cadena de la serie de tick.
Métodos para crear la serie de tick:
//+------------------------------------------------------------------+ //| Create the series list of tick data | //+------------------------------------------------------------------+ int CTickSeries::Create(const uint required=0) { //--- If the tick series is not used, inform of that and exit if(!this.m_available) { ::Print(DFUN,this.m_symbol,": ",CMessage::Text(MSG_TICKSERIES_TEXT_IS_NOT_USE)); return false; } //--- Declare the ticks[] array we are to receive historical data to, //--- clear the list of tick data objects and set the flag of sorting by time in milliseconds MqlTick ticks_array[]; this.m_list_ticks.Clear(); this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC); ::ResetLastError(); int err=ERR_SUCCESS; //--- Calculate the day start time in milliseconds the ticks should be copied from MqlDateTime date_str={0}; datetime date=::iTime(m_symbol,PERIOD_D1,this.m_required); ::TimeToStruct(date,date_str); date_str.hour=date_str.min=date_str.sec=0; date=::StructToTime(date_str); long date_from=(long)date*1000; if(date_from<1) date_from=1; //--- Get historical data of the MqlTick structure to the tick[] array //--- from the calculated date to the current time and save the obtained number in m_amount. //--- If failed to get data, display the appropriate message and return zero this.m_amount=::CopyTicksRange(m_symbol,ticks_array,COPY_TICKS_ALL,date_from); if(this.m_amount<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_ERR_GET_TICK_DATA),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- Historical data is received in the rates[] array //--- In the ticks[] array loop for(int i=0; i<(int)this.m_amount; i++) { //--- create a new object of tick data out of the current MqlTick structure data from the ticks[] array by the loop index ::ResetLastError(); CDataTick* tick=new CDataTick(this.m_symbol,ticks_array[i]); if(tick==NULL) { ::Print ( DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_CREATE_TICK_DATA_OBJ)," ",this.Header()," ",::TimeMSCtoString(ticks_array[i].time_msc),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError()) ); continue; } //--- If failed to add a new tick data object to the list, //--- display the appropriate message with the error description in the journal //--- and remove the newly created object if(!this.m_list_ticks.Add(tick)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_ADD_TO_LIST)," ",tick.Header()," ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); delete tick; } } //--- Return the size of the created bar object list return this.m_list_ticks.Total(); } //+------------------------------------------------------------------+
El funcionamiento del método ha sido detalladamente comentado en el listado del código. En pocas palabras:
calculamos la hora del inicio del día a partir del cual tenemos que copiar los ticks, solicitamos los ticks a partir de la fecha calculada para la matriz. Si los ticks han sido copiados con éxito, obtenemos el siguiente tick en el formato de la estructura MqlTick repasando en el ciclo la matriz de ticks obtenida. Creamos a base de esta estructura un nuevo objeto de tick que será colocado en la lista en caso de su creación con éxito. Una vez finalizado el ciclo, devolvemos el número de los ticks colocados en la lista de datos de tick.
Con esto, damos por finalizada la creación de la lista de datos de tick.
Simulando la creación de la lista y obtención de datos
Para realizar la prueba, simplemente vamos a crear una lista de objetos de tick para el símbolo actual durante el día actual al iniciar el programa. En la lista obtenida, encontraremos el tick con el valor máximo del precio Ask y el valor mínimo del precio Bid, y mostraremos los datos de los objetos de tick encontrados en el diario. Para eso, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part60\ con el nombre nuevo TestDoEasyPart60.mq5
.
Puesto que es una simple prueba de la lista de datos de tick y no hay acceso directo desde la biblioteca, vamos a incluir la clase del objeto de lista de datos de tick en el archivo del asesor experto:
//| TestDoEasyPart60.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> #include <DoEasy\Objects\Ticks\TickSeries.mqh> //--- enums
Declaramos el objeto de lista de datos de tick en el área de variables globales del 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; //--- "New tick" object CNewTickObj check_tick; //--- Object of the current symbol tick series data CTickSeries tick_series; //+------------------------------------------------------------------+
En OnTick() eliminamos el bloque del código para trabajar con objetos de datos de tick. Eso se ha quedado desde el artículo anterior. Aquí, no vamos a crear ningunos objetos:
//--- Create a temporary list for storing “Tick data” objects,
//--- a variable for obtaining tick data and
//--- a variable for calculating incoming ticks
static int tick_count=0;
CArrayObj list;
MqlTick tick_struct;
//--- Check a new tick on the current symbol
if(check_tick.IsNewTick())
{
//--- If failed to get the price - exit
if(!SymbolInfoTick(Symbol(),tick_struct))
return;
//--- Create a new tick data object
CDataTick *tick_obj=new CDataTick(Symbol(),tick_struct);
if(tick_obj==NULL)
return;
//--- Increase tick counter (simply to display on the screen, no other purpose is provided)
tick_count++;
//--- Limit the number of ticks in the counting as one hundred thousand (again, no purpose is provided)
if(tick_count>100000) tick_count=1;
//--- In the comment on the chart display the tick number and its short description
Comment("--- #",IntegerToString(tick_count,5,'0'),": ",tick_obj.Header());
//--- If this is the first tick (which follows the first launch of EA) display its full description in the journal
if(tick_count==1)
tick_obj.Print();
//--- Remove if failed to put the created tick data object in the list
if(!list.Add(tick_obj))
delete tick_obj;
}
De esta manera, el manejador OnTick() tendrá el siguiente aspecto:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Handle the NewTick event in the library engine.OnTick(rates_data); //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the timer PressButtonsControl(); // Button pressing control engine.EventsHandling(); // Working with events } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing pending orders } } //+------------------------------------------------------------------+
La lista será creada en el manejador OnInit() dentro de la función de la inicialización de la biblioteca DoEasy, en el bloque del código:
//+------------------------------------------------------------------+ //| 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,SYMBOLS_COMMON_TOTAL); 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 engine.SeriesCreateAll(array_used_periods); //--- Check created timeseries - display descriptions of all created timeseries in the journal //--- (true - only created ones, false - created and declared ones) engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //engine.GetTimeSeriesCollection().Print(true); // Full descriptions //--- Code block for checking the tick list creation and working with it Print(""); //--- Since the tick series object is created with the default constructor, //--- set a symbol, usage flag and the number of days (the default is 1) to copy the ticks //--- Create the tick series and printed data in the journal tick_series.SetSymbol(Symbol()); tick_series.SetAvailable(true); tick_series.SetRequiredUsedDays(); tick_series.Create(); tick_series.Print(); Print(""); //--- Get and display in the journal the data of an object with the highest Ask price in the daily price range int index_max=CSelect::FindTickDataMax(tick_series.GetList(),TICK_PROP_ASK); CDataTick *tick_max=tick_series.GetList().At(index_max); if(tick_max!=NULL) tick_max.Print(); //--- Get and display in the journal the data of an object with the lowest Bid price in the daily price range int index_min=CSelect::FindTickDataMin(tick_series.GetList(),TICK_PROP_BID); CDataTick *tick_min=tick_series.GetList().At(index_min); if(tick_min!=NULL) tick_min.Print(); //--- 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 main library class engine.CollectionOnInit(); //--- 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)); } //+------------------------------------------------------------------+
El bloque del código para crear una serie de tick del símbolo actual para el día actual y para buscar dos objetos de tick necesarios con el fin de mostrarlos en el diario ha sido comentado muy detalladamente. Creo que no habrá preguntas a la hora de estudiarlo. En cualquier caso, podrán escribir cualquier duda en los comentarios al artículo.
Esta función se invoca desde el manejador OnInit(). Por eso, la lista se crea sólo una vez cuando se inicia el programa. Dos objetos de datos de tick serán encontrados de inmediato (con Ask máximo y Bid mínimo durante el día actual). La visualización de datos requiere algo de tiempo: si no hay datos de tick de forma local, se activará su carga.
Compilamos el EA y lo iniciamos en el gráfico de cualquier símbolo, estableciendo previamente en los ajustes el uso del símbolo y marco temporal actuales. Durante la inicialización del asesor, se mostrarán los datos sobre los parámetros del asesor, series temporales creadas, y después de un rato, se mostrarán los datos sobre la serie de tick creada. A continuación, se mostrarán los datos sobre dos ticks encontrados: con Ask máximo y con Bid mínimo durante el día:
Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10426.13 USD, 1:100, Hedge, MetaTrader 5 demo --- 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: 6336 Tick series "EURUSD": Requested number of days: 1, Historical data created: 276143 ============= Beginning of parameter list (Tick "EURUSD" 2021.01.06 14:25:32.156) ============= Last price update time in milliseconds: 2021.01.06 14:25:32.156 Last price update time: 2021.01.06 14:25:32 Volume for the current Last price: 0 Flags: 134 Changed data on the tick: - Ask price change - Bid price change ------ Bid price: 1.23494 Ask price: 1.23494 Last price: 0.00000 Volume for the current Last price with greater accuracy: 0.00 Spread: 0.00000 ------ Symbol: "EURUSD" ============= End of parameter list (Tick "EURUSD" 2021.01.06 14:25:32.156) ============= ============= Beginning of parameter list (Tick "EURUSD" 2021.01.07 12:51:40.632) ============= Last price update time in milliseconds: 2021.01.07 12:51:40.632 Last price update time: 2021.01.07 12:51:40 Volume for the current Last price: 0 Flags: 134 Changed data on the tick: - Ask price change - Bid price change ------ Bid price: 1.22452 Ask price: 1.22454 Last price: 0.00000 Volume for the current Last price with greater accuracy: 0.00 Spread: 0.00002 ------ Symbol: "EURUSD" ============= End of parameter list (Tick "EURUSD" 2021.01.07 12:51:40.632) ============= Library initialization time: 00:00:12.828
La inicialización ha tardado 12,8 segundos —el tiempo para cargar los datos de tick de la historia.
¿Qué es lo próximo?
En el siguiente artículo, crearemos la clase de colección de datos de tick para todos los símbolos utilizados en el programa, e implementaremos la actualización de todas las listas creadas en tiempo real.
Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Cabe mencionar que las clases de datos de tick se encuentran en el proceso de desarrollo. Por tanto, no se recomienda usarlas en sus programas en esta fase.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
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
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
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 39): Indicadores basados en la biblioteca - Preparación de datos y eventos de la series temporales
Trabajando con las series temporales en la biblioteca DoEasy (Parte 40): Indicadores basados en la biblioteca - actualización de datos en tiempo real
Trabajando con las series temporales en la biblioteca DoEasy (Parte 41): Ejemplo de indicador de símbolo y periodo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 42): La clase del objeto de búfer de indicador abstracto
Trabajando con las series temporales en la biblioteca DoEasy (Parte 43): Las clases de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 44): Las clases de colección de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple
Trabajando con las series temporales en la biblioteca DoEasy (Parte 46): Búferes de indicador de periodo y símbolos múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 47): Indicadores estándar de periodo y símbolo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana
Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de periodo, símbolo y búfer múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 50): Indicadores estándar de periodo y símbolo múltiples con desplazamiento
Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 52): Concepto multiplataforma de indicadores estándar de periodo y símbolo múltiples de búfer único
Trabajando con las series temporales en la biblioteca DoEasy (Parte 53): Clase del indicador abstracto básico
Trabajando con las series temporales en la biblioteca DoEasy (Parte 54): Clases herederas del indicador abstracto básico
Trabajando con las series temporales en la biblioteca DoEasy (Parte 55): Clase de colección de indicadores
Trabajando con las series temporales en la biblioteca DoEasy (Parte 56): Objeto del indicador personalizado, obtención de datos de parte de los objetos de indicador en la colección
Trabajando con las series temporales en la biblioteca DoEasy (Parte 57): Objeto de datos del búfer de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 58): Series temporales de los datos de búferes de indicadores
Trabajando con los precios en la biblioteca DoEasy (Parte 59): Objeto para almacenar los datos de un tick
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/8912
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso