Trabalhando com séries temporais na biblioteca DoEasy (Parte 56): objeto de indicador personalizado, obtenção de dados a partir de objetos-indicadores numa coleção
Sumário
Ideia
Continuamos a criar funcionalidades para trabalhar com indicadores em Expert Advisors com base nesta biblioteca.
Nós já criamos classes de objetos-indicadores padrão e começamos a colocá-los numa lista-coleção. Para um "sortimento" completo, precisamos apenas de um objeto-indicador personalizado. Hoje vamos criar um objeto desse tipo. O conceito por trás da sua construção será diferente do de criação de objetos de indicadores padrão.
Visto que temos um conjunto finito de indicadores padrão no terminal, podemos criar objetos de indicadores padrão com base em dados conhecidos com antecedência para cada um dos indicadores, e para fazer isso, indicamos um conjunto estritamente especificado de parâmetros que correspondem ao indicador criado. Embora um indicador personalizado possa ter qualquer conjunto de parâmetros, não há como saber isso com antecedência.
Por esse motivo, o conceito de criação de objeto-indicador personalizado será diferente da criação de um objeto-indicador padrão.
Se, para criar um objeto-indicador padrão, basta gerar para cada um deles um método de criação próprio cujos parâmetros de entrada conterão todas as propriedades necessárias, então para criar um indicador personalizado com um número e tipo de parâmetros previamente desconhecidos, teremos que passar para o método de criação deste a matriz de estruturas de parâmetros de entrada do indicador pré-preenchida, matriz essa que permitirá retirar todos os parâmetros e propriedades necessários para criar o indicador. Assim, infelizmente, o usuário da biblioteca terá que preencher por contra própria essa matriz de estruturas de parâmetros de entrada para criar um indicador personalizado em seu programa.
Como estamos tentando criar uma biblioteca para simplificar a criação de nossos programas, dentre várias opções de acesso aos indicadores criados em meu programa, escolhi a seguinte: cada indicador criado será marcado com seu próprio identificador. Por este identificador podemos nos acessar cada um dos indicadores criados. Felizmente, nós mesmos os criamos, atribuímos identificadores e sabemos perfeitamente bem qual dos identificadores atribuídos corresponde a um ou outro indicador criado no programa.
Além do identificador, também haverá métodos para acessar o indicador com base em todos os seus parâmetros. Quer dizer, será possível obter o objeto do indicador, especificando os mesmos parâmetros com os quais este objeto do indicador foi criado, para logo trabalhar com o objeto resultante - receber dados do indicador (iremos criar métodos para receber dados hoje) e copiar em matrizes para pesquisa estatística (isso já estará em artigos posteriores).
Aprimorando as classes da biblioteca
Como de costume, em primeiro lugar, vamos adicionar novas mensagens de biblioteca ao arquivo \MQL5\Include\DoEasy\Data.mqh.
Adicionamos os índices das novas mensagens:
MSG_LIB_TEXT_IND_TEXT_GROUP, // Indicator group MSG_LIB_TEXT_IND_TEXT_GROUP_TREND, // Trend indicator MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR, // Oscillator MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES, // Volumes MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS, // Arrow indicator MSG_LIB_TEXT_IND_TEXT_ID, // Indicator ID MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE, // Empty value for plotting where nothing will be drawn MSG_LIB_TEXT_IND_TEXT_SYMBOL, // Indicator symbol MSG_LIB_TEXT_IND_TEXT_NAME, // Indicator name MSG_LIB_TEXT_IND_TEXT_SHORTNAME, // Indicator short name MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS, // Indicator parameters MSG_LIB_TEXT_IND_TEXT_APPLIED_VOLUME, // Volume type for calculation MSG_LIB_TEXT_IND_TEXT_PERIOD, // Averaging period MSG_LIB_TEXT_IND_TEXT_FAST_PERIOD, // Fast MA period MSG_LIB_TEXT_IND_TEXT_SLOW_PERIOD, // Slow MA period MSG_LIB_TEXT_IND_TEXT_SIGNAL, // Difference averaging period MSG_LIB_TEXT_IND_TEXT_TENKAN_PERIOD, // Tenkan-sen period MSG_LIB_TEXT_IND_TEXT_KIJUN_PERIOD, // Kijun-sen period MSG_LIB_TEXT_IND_TEXT_SPANB_PERIOD, // Senkou Span B period MSG_LIB_TEXT_IND_TEXT_JAW_PERIOD, // Period for jaw line calculation MSG_LIB_TEXT_IND_TEXT_TEETH_PERIOD, // Period for teeth line calculation MSG_LIB_TEXT_IND_TEXT_LIPS_PERIOD, // Period for lips line calculation MSG_LIB_TEXT_IND_TEXT_JAW_SHIFT, // Horizontal shift of jaws line MSG_LIB_TEXT_IND_TEXT_TEETH_SHIFT, // Horizontal shift of teeth line MSG_LIB_TEXT_IND_TEXT_LIPS_SHIFT, // Horizontal shift of lips line MSG_LIB_TEXT_IND_TEXT_SHIFT, // Horizontal shift of the indicator MSG_LIB_TEXT_IND_TEXT_MA_METHOD, // Smoothing type MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE, // Price type or handle MSG_LIB_TEXT_IND_TEXT_STD_DEVIATION, // Number of standard deviations MSG_LIB_TEXT_IND_TEXT_DEVIATION, // Deviation of channel borders from the central line MSG_LIB_TEXT_IND_TEXT_STEP, // Price change step — acceleration factor MSG_LIB_TEXT_IND_TEXT_MAXIMUM, // Maximum step MSG_LIB_TEXT_IND_TEXT_KPERIOD, // K period (number of bars for calculation) MSG_LIB_TEXT_IND_TEXT_DPERIOD, // D period (primary smoothing period) MSG_LIB_TEXT_IND_TEXT_SLOWING, // Final smoothing MSG_LIB_TEXT_IND_TEXT_PRICE_FIELD, // Stochastic calculation method MSG_LIB_TEXT_IND_TEXT_CMO_PERIOD, // Chande Momentum period MSG_LIB_TEXT_IND_TEXT_SMOOTHING_PERIOD, // Smoothing factor period MSG_LIB_TEXT_IND_TEXT_CUSTOM_PARAM, // Input parameter //--- CIndicatorsCollection MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST, // Error. Failed to add indicator object to list MSG_LIB_SYS_INVALID_IND_POINTER, // Error. Invalid pointer to indicator object is passed MSG_LIB_SYS_IND_ID_EXIST, // Error. The indicator object with the ID already exists }; //+------------------------------------------------------------------+
e as mensagens de texto correspondentes aos índices adicionados recentemente:
{"Стрелочный индикатор","Arrow indicator"}, {"Идентификатор индикатора","Indicator ID"}, {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"}, {"Символ индикатора","Indicator symbol"}, {"Имя индикатора","Indicator name"}, {"Короткое имя индикатора","Indicator shortname"}, {"Параметры индикатора","Indicator parameters"}, {"Тип объема для расчета","Volume type for calculation"}, {"Период усреднения","Averaging period"}, {"Период быстрой скользящей","Fast MA period"}, {"Период медленной скользящей","Slow MA period"}, {"Период усреднения разности","Averaging period for their difference"}, {"Период Tenkan-sen","Tenkan-sen period"}, {"Период Kijun-sen","Kijun-sen period"}, {"Период Senkou Span B","Senkou Span B period"}, {"Период для расчета челюстей","Period for the calculation of jaws"}, {"Период для расчета зубов","Period for the calculation of teeth"}, {"Период для расчета губ","Period for the calculation of lips"}, {"Смещение челюстей по горизонтали","Horizontal shift of jaws"}, {"Смещение зубов по горизонтали","Horizontal shift of teeth"}, {"Смещение губ по горизонтали","Horizontal shift of lips"}, {"Смещение индикатора по горизонтали","Horizontal shift of the indicator"}, {"Тип сглаживания","Smoothing type"}, {"Тип цены или handle","Price type or handle"}, {"Количество стандартных отклонений","Number of standard deviations"}, {"Отклонение границ от средней линии","Deviation of boundaries from the midline"}, {"Шаг изменения цены - коэффициент ускорения","Price increment step - acceleration factor"}, {"Максимальный шаг","Maximum value of step"}, {"K-период (количество баров для расчетов)","K-period (number of bars for calculations)"}, {"D-период (период первичного сглаживания)","D-period (period of first smoothing)"}, {"Окончательное сглаживание","Final smoothing"}, {"Способ расчета стохастика","Stochastic calculation method"}, {"Период Chande Momentum","Chande Momentum period"}, {"Период фактора сглаживания","Smoothing factor period"}, {"Входной параметр","Input parameter"}, {"Ошибка. Не удалось добавить объект-индикатор в список","Error. Failed to add indicator object to list"}, {"Ошибка. Передан неверный указатель на объект-индикатор","Error. Invalid pointer to indicator object passed"}, {"Ошибка. Уже существует объект-индикатор с идентификатором","Error. There is already exist an indicator object with ID"}, }; //+---------------------------------------------------------------------+
Quando criamos um objeto-indicador padrão, logo em seu construtor definimos para ele o grupo que corresponde ao tipo de indicador: tendência, seta, oscilador ou indicador de volume. Para indicadores padrão, podemos fazer isso, pois sabemos com antecedência o tipo de indicador criado. Para um indicador personalizado, não podemos saber de que tipo é com antecedência (o grupo ao qual o indicador pertence) e defini-lo no construtor da classe do objeto. Por isso, precisamos criar mais um grupo de indicadores "qualquer", que será automaticamente atribuído ao objeto recém-criado, e permitir que o usuário indique independentemente o grupo ao qual o indicador personalizado pertence após criar seu objeto.
No arquivo \MQL5\Include\DoEasy\Defines.mqh na enumeração do grupo do indicador adicionamos uma nova constante:
//+------------------------------------------------------------------+ //| Indicator group | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_GROUP { INDICATOR_GROUP_TREND, // Trend indicator INDICATOR_GROUP_OSCILLATOR, // Oscillator INDICATOR_GROUP_VOLUMES, // Volumes INDICATOR_GROUP_ARROWS, // Arrow indicator INDICATOR_GROUP_ANY, // Any indicator }; //+------------------------------------------------------------------+
Visto que concordamos em acessar aos indicadores criados por identificadores únicos, precisamos adicionar uma nova propriedade à enumeração de propriedades inteiras do indicador:
//+------------------------------------------------------------------+ //| Indicator integer properties | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_PROP_INTEGER { INDICATOR_PROP_STATUS = 0, // Indicator status (from enumeration ENUM_INDICATOR_STATUS) INDICATOR_PROP_TYPE, // Indicator type (from enumeration ENUM_INDICATOR) INDICATOR_PROP_TIMEFRAME, // Indicator timeframe INDICATOR_PROP_HANDLE, // Indicator handle INDICATOR_PROP_GROUP, // Indicator group INDICATOR_PROP_ID, // Indicator ID }; #define INDICATOR_PROP_INTEGER_TOTAL (6) // Total number of indicator integer properties #define INDICATOR_PROP_INTEGER_SKIP (0) // Number of indicator properties not used in sorting //+------------------------------------------------------------------+
e, consequentemente, aumentamos o número de propriedades inteiras do indicador de 5 para 6.
Para poder pesquisar um indicador na lista por identificador, vamos introduzir um novo critério para classificar indicadores por identificadores:
//+------------------------------------------------------------------+ //| Possible indicator sorting criteria | //+------------------------------------------------------------------+ #define FIRST_INDICATOR_DBL_PROP (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP) #define FIRST_INDICATOR_STR_PROP (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP+INDICATOR_PROP_DOUBLE_TOTAL-INDICATOR_PROP_DOUBLE_SKIP) enum ENUM_SORT_INDICATOR_MODE { //--- Sort by integer properties SORT_BY_INDICATOR_INDEX_STATUS = 0, // Sort by indicator status SORT_BY_INDICATOR_TYPE, // Sort by indicator type SORT_BY_INDICATOR_TIMEFRAME, // Sort by indicator timeframe SORT_BY_INDICATOR_HANDLE, // Sort by indicator handle SORT_BY_INDICATOR_GROUP, // Sort by indicator group SORT_BY_INDICATOR_ID, // Sort by indicator ID //--- Sort by real properties SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,// Sort by the empty value for plotting where nothing will be drawn //--- Sort by string properties SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP, // Sort by indicator symbol SORT_BY_INDICATOR_NAME, // Sort by indicator name SORT_BY_INDICATOR_SHORTNAME, // Sort by indicator short name }; //+------------------------------------------------------------------+
Como novas propriedades foram adicionadas ao objeto indicador, agora é necessário modificar ligeiramente a classe do objeto indicador abstrato no arquivo \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh.
Na seção pública da classe, vamos escrever dois métodos para definire obter propriedades "identificador do indicador":
//--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name, (5) indicator ID void SetGroup(const ENUM_INDICATOR_GROUP group) { this.SetProperty(INDICATOR_PROP_GROUP,group); } void SetEmptyValue(const double value) { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value); } void SetName(const string name) { this.SetProperty(INDICATOR_PROP_NAME,name); } void SetShortName(const string shortname) { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname); } void SetID(const int id) { this.SetProperty(INDICATOR_PROP_ID,id); } //--- Return (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID, //--- (7) empty value of buffers, (8) name, (9) short name, (10) indicator symbol ENUM_INDICATOR_STATUS Status(void) const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);} ENUM_INDICATOR_GROUP Group(void) const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME); } ENUM_INDICATOR TypeIndicator(void) const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE); } int Handle(void) const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE); } int ID(void) const { return (int)this.GetProperty(INDICATOR_PROP_ID); } double EmptyValue(void) const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE); } string Name(void) const { return this.GetProperty(INDICATOR_PROP_NAME); } string ShortName(void) const { return this.GetProperty(INDICATOR_PROP_SHORTNAME); } string Symbol(void) const { return this.GetProperty(INDICATOR_PROP_SYMBOL); }
Nos objetos de indicadores padrão, poderíamos escrever métodos para exibir a descrição de cada parâmetro do indicador, uma vez que sabemos exatamente qual parâmetro de que indicador específico estamos descrevendo. Para exibir uma descrição dos parâmetros de um indicador personalizado, não podemos saber exatamente a finalidade de cada parâmetro de um indicador que não conhecemos de antemão. Por isso, aqui só temos que exibir as descrições de cada parâmetro subsequente da matriz de parâmetros do indicador MqlParam.
Na seção pública da classe do objeto do indicador abstrato declaramos um método para exibir a descrição dos parâmetros da estrutura MqlParam, bem como mais dois métodos para receber dados do objeto-indicador por índice e hora da barra:
//--- Return description of (1) type, (2) status, (3) group, (4) timeframe, (5) empty value of indicator, (6) parameter of m_mql_param array string GetTypeDescription(void) const { return m_ind_type_description; } string GetStatusDescription(void) const; string GetGroupDescription(void) const; string GetTimeframeDescription(void) const; string GetEmptyValueDescription(void) const; string GetMqlParamDescription(const int index) const; //--- Display the description of indicator object properties in the journal (full_prop=true - all properties, false - supported ones only) void Print(const bool full_prop=false); //--- Display (1) a short description, (2) description of indicator object parameters in the journal (implementation in the descendants) virtual void PrintShort(void) {;} virtual void PrintParameters(void) {;} //--- Return data of specified buffer from specified bar (1) by index, (2) by bar time double GetDataBuffer(const int buffer_num,const int index); double GetDataBuffer(const int buffer_num,const datetime time); }; //+------------------------------------------------------------------+
Fora do corpo da classe, vamos escrever a implementação desses métodos:
Método que retorna uma descrição de um parâmetro da matriz de estruturas MqlParam:
//+------------------------------------------------------------------+ //| Return the description of parameter of m_mql_param array | //+------------------------------------------------------------------+ string CIndicatorDE::GetMqlParamDescription(const int index) const { return "["+(string)index+"] "+MqlParameterDescription(this.m_mql_param[index]); } //+------------------------------------------------------------------+
Ao método é transferido o índice do parâmetro na matriz e, logo, é criada um string a partir do índice da matriz e da descrição do parâmetro em conformidade com os dados armazenados na estrutura de acordo com o índice da matriz. Vamos escrever a função MqlParameterDescription() para retornar a descrição dos dados da estrutura MqlParam logo abaixo.
Métodos que retornam dados do indicador por índice e tempo de barra:
//+------------------------------------------------------------------+ //| Return data of specified buffer from specified bar by index | //+------------------------------------------------------------------+ double CIndicatorDE::GetDataBuffer(const int buffer_num,const int index) { double array[1]={EMPTY_VALUE}; int copied=::CopyBuffer(this.Handle(),buffer_num,index,1,array); return(copied==1 ? array[0] : this.EmptyValue()); } //+------------------------------------------------------------------+ //| Return data of specified buffer from specified bar by time | //+------------------------------------------------------------------+ double CIndicatorDE::GetDataBuffer(const int buffer_num,const datetime time) { double array[1]={EMPTY_VALUE}; int copied=::CopyBuffer(this.Handle(),buffer_num,time,1,array); return(copied==1 ? array[0] : this.EmptyValue()); } //+------------------------------------------------------------------+
Aos métodos são passados o índice ou o tempo da barra cujos dados devem ser obtidos a partir do indicador. Usamos a função CopyBuffer() para solicitar uma barra de acordo com o índice ou tempo, e retornamos o resultado registrado na matriz. Se por algum motivo os dados não podem ser recebidos, os métodos retornam um valor vazio definido para o objeto-indicador.
No construtor da classe definimos o identificador do indicador por padrão (-1):
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CIndicatorDE::CIndicatorDE(ENUM_INDICATOR ind_type, string symbol, ENUM_TIMEFRAMES timeframe, ENUM_INDICATOR_STATUS status, ENUM_INDICATOR_GROUP group, string name, string shortname, MqlParam &mql_params[]) { //--- Set collection ID to the object this.m_type=COLLECTION_INDICATORS_ID; //--- Write description of indicator type this.m_ind_type_description=IndicatorTypeDescription(ind_type); //--- If parameter array value passed to constructor is more than zero //--- fill in the array of object parameters with data from the array passed to constructor int count=::ArrayResize(this.m_mql_param,::ArraySize(mql_params)); for(int i=0;i<count;i++) { this.m_mql_param[i].type = mql_params[i].type; this.m_mql_param[i].double_value = mql_params[i].double_value; this.m_mql_param[i].integer_value= mql_params[i].integer_value; this.m_mql_param[i].string_value = mql_params[i].string_value; } //--- Create indicator handle int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,this.m_mql_param); //--- Save integer properties this.m_long_prop[INDICATOR_PROP_STATUS] = status; this.m_long_prop[INDICATOR_PROP_TYPE] = ind_type; this.m_long_prop[INDICATOR_PROP_GROUP] = group; this.m_long_prop[INDICATOR_PROP_TIMEFRAME] = timeframe; this.m_long_prop[INDICATOR_PROP_HANDLE] = handle; this.m_long_prop[INDICATOR_PROP_ID] = WRONG_VALUE; //--- Save real properties this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE; //--- Save string properties this.m_string_prop[this.IndexProp(INDICATOR_PROP_SYMBOL)] = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_string_prop[this.IndexProp(INDICATOR_PROP_NAME)] = name; this.m_string_prop[this.IndexProp(INDICATOR_PROP_SHORTNAME)]= shortname; } //+------------------------------------------------------------------+
Como cada indicador tem um identificador único (que nós mesmos iremos atribuir), ao comparar a identidade de dois indicadores por parâmetros, precisamos pular os dois parâmetros mencionados acima, caso contrário, cada indicador será definido como único, e não poderemos compará-los corretamente.
Para evitar isso, ignoramos as propriedades "handle" e "identificador" no método para comparar dois objetos-indicadores:
//+------------------------------------------------------------------+ //| Compare CIndicatorDE objects with each other by all properties | //+------------------------------------------------------------------+ bool CIndicatorDE::IsEqual(CIndicatorDE *compared_obj) const { if(!IsEqualMqlParamArrays(compared_obj.m_mql_param)) return false; int beg=0, end=INDICATOR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_INTEGER prop=(ENUM_INDICATOR_PROP_INTEGER)i; if(prop==INDICATOR_PROP_HANDLE || prop==INDICATOR_PROP_ID) continue; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } beg=end; end+=INDICATOR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_DOUBLE prop=(ENUM_INDICATOR_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } beg=end; end+=INDICATOR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_STRING prop=(ENUM_INDICATOR_PROP_STRING)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } return true; } //+------------------------------------------------------------------+
No método que retorna a descrição da propriedade inteira do indicador, incorporamos a saída da descrição da propriedade do indicador "identificador":
//+------------------------------------------------------------------+ //| Return description of indicator's integer property | //+------------------------------------------------------------------+ string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property) { return ( property==INDICATOR_PROP_STATUS ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetStatusDescription() ) : property==INDICATOR_PROP_TYPE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTypeDescription() ) : property==INDICATOR_PROP_GROUP ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetGroupDescription() ) : property==INDICATOR_PROP_ID ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==INDICATOR_PROP_TIMEFRAME ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTimeframeDescription() ) : property==INDICATOR_PROP_HANDLE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
Agora ao arquivo de funções de serviço \MQL5\Include\DoEasy\Services\DELib.mqh adicionamos a função para exibir a descrição da estrutura MqlParam:
//+------------------------------------------------------------------+ //| Return the description of parameter MqlParam array | //+------------------------------------------------------------------+ string MqlParameterDescription(const MqlParam &mql_param) { int type=mql_param.type; string res=CMessage::Text(MSG_ORD_TYPE)+" "+typename(type)+": "; //--- string parameter type if(type==TYPE_STRING) res+=mql_param.string_value; //--- datetime parameter type else if(type==TYPE_DATETIME) res+=TimeToString(mql_param.integer_value,TIME_DATE|TIME_MINUTES|TIME_SECONDS); //--- color parameter type else if(type==TYPE_COLOR) res+=ColorToString((color)mql_param.integer_value,true); //--- bool parameter type else if(type==TYPE_BOOL) res+=(string)(bool)mql_param.integer_value; //--- integer types else if(type>TYPE_BOOL && type<TYPE_FLOAT) res+=(string)mql_param.integer_value; //--- real types else res+=DoubleToString(mql_param.double_value,8); return res; } //+------------------------------------------------------------------+
Na estrutura MqlParam existem alguns campos. Um deles contém o tipo de dados armazenados na estrutura. Por este tipo de dado, podemos entender de qual campo da estrutura receber dados e qual função representando dados como string usar para enviar dados para o log.
Obtemos o tipo de dados e começamos a gerar uma string consistindo na palavra "Tipo " +" "+ descrição de string do tipo de dados + ": ".
Em seguida, dependendo do tipo de dados, adicionamos à string já criada a descrição do valor que armazena no campo as estruturas, que correspondem ao tipo, com ajuda de funções padrão para exibir como uma string o tipo necessário de dados.
Como resultado, ao usar o método de saída de parâmetros da estrutura MqlParam da classe do objeto indicador abstrato e a função de serviço acima no log do terminal, teremos uma descrição dos parâmetros de um indicador personalizado com quatro parâmetros assim:
--- Indicator parameters --- - [1] int type: 13 - [2] int type: 0 - [3] int type: 0 - [4] int type: 2
Como adicionamos a nova propriedade para o objeto-indicador (isto é, seu identificador), em todas as classes de todos os objetos dos indicadores, alteramos ligeiramente os arquivos que estão localizados na pasta \MQL5\Include\DoEasy\Objects\Indicators\Standart\, isto é, simplesmente alteramos o nome curto do valor do identificador:
//+------------------------------------------------------------------+ //| Display a short description of indicator object in the journal | //+------------------------------------------------------------------+ void CIndAC::PrintShort(void) { string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]"); ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id); } //+------------------------------------------------------------------+
Aqui, criamos a descrição do identificador. Além disso, se o valor do identificador for maior que -1, o identificador será exibido, caso contrário, se não houver identificador (se seu valor for -1), ele não será exibido na descrição (simplesmente aparecerá um colchete de fechamento). Em seguida, adicionamos a linha resultante à descrição breve do indicador.
Essas modificações foram feitas em todas as classes de objetos-indicadores.
Objeto do indicador personalizado
Agora vamos escrever a classe do objeto-indicador personalizado. Vamos colocá-lo na pasta de indicadores padrão da biblioteca \MQL5\Include\DoEasy\Objects\Indicators\Standart\. Simplesmente visto que existe um indicador personalizado na lista de indicadores do terminal, seu local também está nessa lista.
Vamos dar uma olhada em toda a classe:
//+------------------------------------------------------------------+ //| IndCustom.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\\IndicatorDE.mqh" //+------------------------------------------------------------------+ //| Custom indicator | //+------------------------------------------------------------------+ class CIndCustom : public CIndicatorDE { private: public: //--- Constructor CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,MqlParam &mql_param[]) : CIndicatorDE(IND_CUSTOM,symbol,timeframe, INDICATOR_STATUS_CUSTOM, INDICATOR_GROUP_ANY, mql_param[0].string_value, mql_param[0].string_value+"("+(symbol==NULL || symbol=="" ? ::Symbol() : symbol)+ ","+TimeframeDescription(timeframe)+")",mql_param) {} //--- Supported indicator properties (1) real, (2) integer virtual bool SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_INDICATOR_PROP_INTEGER property); //--- Display (1) a short description, (2) description of indicator object parameters in the journal virtual void PrintShort(void); virtual void PrintParameters(void); }; //+------------------------------------------------------------------+ //| Return 'true' if the indicator supports the passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property) { return true; } //+------------------------------------------------------------------+ //| Return 'true' if the indicator supports the passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property) { return true; } //+------------------------------------------------------------------+ //| Display a short description of indicator object in the journal | //+------------------------------------------------------------------+ void CIndCustom::PrintShort(void) { string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]"); ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id); } //+------------------------------------------------------------------+ //| Display parameter description of indicator object in the journal | //+------------------------------------------------------------------+ void CIndCustom::PrintParameters(void) { ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- "); int total=::ArraySize(this.m_mql_param); for(int i=1;i<total;i++) { ::Print(" - ",this.GetMqlParamDescription(i)); } } //+------------------------------------------------------------------+
Todos os métodos já nos são familiares graças às classes de indicadores padrão. Mas ao contrário deles, obtemos no construtor da classe os parâmetros do indicador criado, não nas variáveis de entrada, mas, sim, na matriz de estruturas de parâmetros de entrada MqlParam criada anteriormente. E ao construtor privado da classe do objeto indicador abstrato passamos o status "indicador personalizado", grupo "qualquer indicador", e como nome, passamos o primeiro elemento da matriz de parâmetros que, ao criar um indicador personalizado, deve ter o tipo TYPE_STRING e o valor do campostring_value contém o nome do indicador personalizado. Da mesma forma, nós criamos o nome curto do indicador, mas com a adição de uma descrição do símbolo e de período gráfico. No futuro, o nome e o nome abreviado do indicador podem ser alterados usando os métodos da classe pai SetName() e SetShortName(). Na verdade, o nome do indicador também contém o caminho para ele, portanto (pelo menos neste estágio do desenvolvimento da biblioteca) o nome de um indicador personalizado já criado não pode ser alterado.
No método que registra a descrição dos parâmetros do objeto indicador fazemos literalmente o seguinte: primeiro mostramos o título e, em seguida, num loop percorrendo a matriz de parâmetros do indicador, imprimimos cada parâmetro subsequente usando os métodos considerados anteriormente (em particular, o método da classe pai GetMqlParamDescription()).
Armazenamos todos os objetos-indicadores numa lista-coleção na classe CIndicatorsCollection no arquivo \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh.
Ao adicionar um indicador à lista-coleção, precisamos verificar adicionalmente se seu identificador é único. Além disso, devemos tornar visível a classe do indicador personalizado para que a classe-coleção possa trabalhar com ela.
Anexamos o arquivo da classe do indicador personalizado à classe da coleção de objetos-indicadores:
//+------------------------------------------------------------------+ //| IndicatorsCollection.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Objects\Indicators\Standart\IndAC.mqh" #include "..\Objects\Indicators\Standart\IndAD.mqh" #include "..\Objects\Indicators\Standart\IndADX.mqh" #include "..\Objects\Indicators\Standart\IndADXW.mqh" #include "..\Objects\Indicators\Standart\IndAlligator.mqh" #include "..\Objects\Indicators\Standart\IndAMA.mqh" #include "..\Objects\Indicators\Standart\IndAO.mqh" #include "..\Objects\Indicators\Standart\IndATR.mqh" #include "..\Objects\Indicators\Standart\IndBands.mqh" #include "..\Objects\Indicators\Standart\IndBears.mqh" #include "..\Objects\Indicators\Standart\IndBulls.mqh" #include "..\Objects\Indicators\Standart\IndBWMFI.mqh" #include "..\Objects\Indicators\Standart\IndCCI.mqh" #include "..\Objects\Indicators\Standart\IndChaikin.mqh" #include "..\Objects\Indicators\Standart\IndCustom.mqh" #include "..\Objects\Indicators\Standart\IndDEMA.mqh" #include "..\Objects\Indicators\Standart\IndDeMarker.mqh" #include "..\Objects\Indicators\Standart\IndEnvelopes.mqh" #include "..\Objects\Indicators\Standart\IndForce.mqh" #include "..\Objects\Indicators\Standart\IndFractals.mqh" #include "..\Objects\Indicators\Standart\IndFRAMA.mqh" #include "..\Objects\Indicators\Standart\IndGator.mqh" #include "..\Objects\Indicators\Standart\IndIchimoku.mqh" #include "..\Objects\Indicators\Standart\IndMA.mqh" #include "..\Objects\Indicators\Standart\IndMACD.mqh" #include "..\Objects\Indicators\Standart\IndMFI.mqh" #include "..\Objects\Indicators\Standart\IndMomentum.mqh" #include "..\Objects\Indicators\Standart\IndOBV.mqh" #include "..\Objects\Indicators\Standart\IndOsMA.mqh" #include "..\Objects\Indicators\Standart\IndRSI.mqh" #include "..\Objects\Indicators\Standart\IndRVI.mqh" #include "..\Objects\Indicators\Standart\IndSAR.mqh" #include "..\Objects\Indicators\Standart\IndStDev.mqh" #include "..\Objects\Indicators\Standart\IndStoch.mqh" #include "..\Objects\Indicators\Standart\IndTEMA.mqh" #include "..\Objects\Indicators\Standart\IndTRIX.mqh" #include "..\Objects\Indicators\Standart\IndVIDYA.mqh" #include "..\Objects\Indicators\Standart\IndVolumes.mqh" #include "..\Objects\Indicators\Standart\IndWPR.mqh" //+------------------------------------------------------------------+
Na seção privada da classe declaramos o método para adicionar o objeto-indicador na coleção e o método que verifica a presença na lista do objeto-indicador com o identificador em questão:
//+------------------------------------------------------------------+ //| Indicator collection | //+------------------------------------------------------------------+ class CIndicatorsCollection : public CObject { private: CListObj m_list; // List of indicator objects MqlParam m_mql_param[]; // Array of indicator parameters //--- (1) Create, (2) add to collection list a new indicator object and set an ID for it CIndicatorDE *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT); int AddIndicatorToList(CIndicatorDE *indicator,const int id); //--- Return the indicator index in the list int Index(CIndicatorDE *compared_obj); //--- Check presence of indicator object with specified id in the list bool CheckID(const int id); public:
Na seção pública da classe declaramos o método que retorna o ponteiro para o objeto do indicador personalizado de acordo com seu grupo e parâmetro definido na matriz MqlParam (ao contrário dos indicadores padrão, podemos especificar parâmetros apenas passando-os nessa matriz):
CIndicatorDE *GetIndCCI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price); CIndicatorDE *GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam ¶m[]); CIndicatorDE *GetIndDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price);
No bloco de declaração de métodos para a criação de indicadores, primeiro adicionamos aos parâmetros de cada método uma indicação do identificador do indicador e, em segundo lugar, adicionamos uma declaração do método para criar um indicador personalizado. Não forneceremos uma lista completa de declarações desses métodos, em vez dissos, nos limitaremos a três métodos (todos eles já foram finalizados e podem ser visualizados nos arquivos anexados ao artigo):
int CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, const int ma_period=14, const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL); int CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]); int CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, const int ma_period=14, const int ma_shift=0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
Em todos os métodos, após os parâmetros de entrada do símbolo e período gráfico, foi adicionada uma indicação do identificador do indicador ao criá-lo.
No método de criação do indicador personalizado é adicionalmente especificado o grupo do indicador e é transferida a matriz de parâmetros de indicador previamente criada e preenchida, matriz essa pela qual será criado o indicador personalizado.
No final da listagem do corpo da classe, declaramos o método para definir o identificador para o indicador especificado e o método que retorna um objeto indicador segundo o identificador especificado:
//--- Set ID for the specified indicator void SetID(CIndicatorDE *indicator,const int id); //--- Return indicator object by its ID CIndicatorDE *GetIndByID(const uint id); //--- Display (1) the complete and (2) short collection description in the journal void Print(void); void PrintShort(void); //--- Constructor CIndicatorsCollection(); }; //+------------------------------------------------------------------+
Agora, vamos examinar todos os métodos declarados.
No método privado para criar um novo objeto-indicador adicionamos a criação de um novo objeto-indicador personalizado:
//+------------------------------------------------------------------+ //| Create a new indicator object | //+------------------------------------------------------------------+ CIndicatorDE *CIndicatorsCollection::CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT) { string symbol=(symbol_name==NULL || symbol_name=="" ? ::Symbol() : symbol_name); ENUM_TIMEFRAMES timeframe=(period==PERIOD_CURRENT ? ::Period() : period); CIndicatorDE *indicator=NULL; switch(ind_type) { case IND_AC : indicator=new CIndAC(symbol,timeframe,mql_param); break; case IND_AD : indicator=new CIndAD(symbol,timeframe,mql_param); break; case IND_ADX : indicator=new CIndADX(symbol,timeframe,mql_param); break; case IND_ADXW : indicator=new CIndADXW(symbol,timeframe,mql_param); break; case IND_ALLIGATOR : indicator=new CIndAlligator(symbol,timeframe,mql_param); break; case IND_AMA : indicator=new CIndAMA(symbol,timeframe,mql_param); break; case IND_AO : indicator=new CIndAO(symbol,timeframe,mql_param); break; case IND_ATR : indicator=new CIndATR(symbol,timeframe,mql_param); break; case IND_BANDS : indicator=new CIndBands(symbol,timeframe,mql_param); break; case IND_BEARS : indicator=new CIndBears(symbol,timeframe,mql_param); break; case IND_BULLS : indicator=new CIndBulls(symbol,timeframe,mql_param); break; case IND_BWMFI : indicator=new CIndBWMFI(symbol,timeframe,mql_param); break; case IND_CCI : indicator=new CIndCCI(symbol,timeframe,mql_param); break; case IND_CHAIKIN : indicator=new CIndCHO(symbol,timeframe,mql_param); break; case IND_DEMA : indicator=new CIndDEMA(symbol,timeframe,mql_param); break; case IND_DEMARKER : indicator=new CIndDeMarker(symbol,timeframe,mql_param); break; case IND_ENVELOPES : indicator=new CIndEnvelopes(symbol,timeframe,mql_param); break; case IND_FORCE : indicator=new CIndForce(symbol,timeframe,mql_param); break; case IND_FRACTALS : indicator=new CIndFractals(symbol,timeframe,mql_param); break; case IND_FRAMA : indicator=new CIndFRAMA(symbol,timeframe,mql_param); break; case IND_GATOR : indicator=new CIndGator(symbol,timeframe,mql_param); break; case IND_ICHIMOKU : indicator=new CIndIchimoku(symbol,timeframe,mql_param); break; case IND_MA : indicator=new CIndMA(symbol,timeframe,mql_param); break; case IND_MACD : indicator=new CIndMACD(symbol,timeframe,mql_param); break; case IND_MFI : indicator=new CIndMFI(symbol,timeframe,mql_param); break; case IND_MOMENTUM : indicator=new CIndMomentum(symbol,timeframe,mql_param); break; case IND_OBV : indicator=new CIndOBV(symbol,timeframe,mql_param); break; case IND_OSMA : indicator=new CIndOsMA(symbol,timeframe,mql_param); break; case IND_RSI : indicator=new CIndRSI(symbol,timeframe,mql_param); break; case IND_RVI : indicator=new CIndRVI(symbol,timeframe,mql_param); break; case IND_SAR : indicator=new CIndSAR(symbol,timeframe,mql_param); break; case IND_STDDEV : indicator=new CIndStDev(symbol,timeframe,mql_param); break; case IND_STOCHASTIC : indicator=new CIndStoch(symbol,timeframe,mql_param); break; case IND_TEMA : indicator=new CIndTEMA(symbol,timeframe,mql_param); break; case IND_TRIX : indicator=new CIndTRIX(symbol,timeframe,mql_param); break; case IND_VIDYA : indicator=new CIndVIDYA(symbol,timeframe,mql_param); break; case IND_VOLUMES : indicator=new CIndVolumes(symbol,timeframe,mql_param); break; case IND_WPR : indicator=new CIndWPR(symbol,timeframe,mql_param); break; case IND_CUSTOM : indicator=new CIndCustom(symbol,timeframe,mql_param); break; default: break; } return indicator; } //+------------------------------------------------------------------+
Vamos mudar o método que adiciona um novo objeto-indicador à lista-coleção:
//+------------------------------------------------------------------+ //| Add a new indicator object to collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id) { //--- If invalid indicator is passed to the object - return INVALID_HANDLE if(indicator==NULL) return INVALID_HANDLE; //--- If such indicator is already in the list int index=this.Index(indicator); if(index!=WRONG_VALUE) { //--- Remove the earlier created object, get indicator object from the list and return indicator handle delete indicator; indicator=this.m_list.At(index); } //--- If indicator object is not in the list yet else { //--- If failed to add indicator object to the list - display a corresponding message, //--- remove object and return INVALID_HANDLE if(!this.m_list.Add(indicator)) { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST)); delete indicator; return INVALID_HANDLE; } } //--- If indicator is successfully added to the list or is already there... //--- If indicator with specified ID (not -1) is not in the list - set ID if(id>WRONG_VALUE && !this.CheckID(id)) indicator.SetID(id); //--- Return the handle of a new indicator added to the list return indicator.Handle(); } //+------------------------------------------------------------------+
Agora o identificador do indicador é passado para o método, e se o indicador com este identificador ainda não estiver na lista-coleção, então para o objeto-indicador é definido o identificador especificado. Caso contrário, o identificador do indicador será definido por padrão como -1.
Agora, todos os métodos de criação de objetos-indicadores são mais curtos.
Vejamos o exemplo de criação de objetos dos indicadores AC e Alligator:
//+------------------------------------------------------------------+ //| Create a new indicator object Accelerator Oscillator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id) { //--- AC indicator possesses no parameters - resize the array of parameter structures ::ArrayResize(this.m_mql_param,0); //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Create new indicator object Alligator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, const int jaw_period=13, const int jaw_shift=8, const int teeth_period=8, const int teeth_shift=5, const int lips_period=5, const int lips_shift=3, const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN) { //--- Add required indicator parameters to the array of parameter structures ::ArrayResize(this.m_mql_param,8); this.m_mql_param[0].type=TYPE_INT; this.m_mql_param[0].integer_value=jaw_period; this.m_mql_param[1].type=TYPE_INT; this.m_mql_param[1].integer_value=jaw_shift; this.m_mql_param[2].type=TYPE_INT; this.m_mql_param[2].integer_value=teeth_period; this.m_mql_param[3].type=TYPE_INT; this.m_mql_param[3].integer_value=teeth_shift; this.m_mql_param[4].type=TYPE_INT; this.m_mql_param[4].integer_value=lips_period; this.m_mql_param[5].type=TYPE_INT; this.m_mql_param[5].integer_value=lips_shift; this.m_mql_param[6].type=TYPE_INT; this.m_mql_param[6].integer_value=ma_method; this.m_mql_param[7].type=TYPE_INT; this.m_mql_param[7].integer_value=applied_price; //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_ALLIGATOR,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id); } //+------------------------------------------------------------------+
Agora só precisamos preencher a estrutura dos parâmetros de entrada, criar o indicador e chamar o método para adicioná-lo à lista-coleção. Essas mudanças foram feitas em todos os métodos de criação de objetos-indicadores, e não as analisaremos aqui, com exceção do método para criar um indicador personalizado:
//+------------------------------------------------------------------+ //| Create a new object - custom indicator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]) { //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe); if(indicator==NULL) return INVALID_HANDLE; //--- Set a group for indicator object indicator.SetGroup(group); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id); } //+------------------------------------------------------------------+
Aqui tudo muda um pouco. Para o método de criação é passado, além do identificador, o grupo de indicadores. Todos os parâmetros do indicador criado são imediatamente passados na matriz de parâmetros MqlParam, porque não podemos saber com antecedência sobre os parâmetros do indicador personalizado que está sendo criado.
Absolutamente a todos os métodos de criação de indicadores padrão foram adicionados a valores padrão para cada parâmetro de entrada. Assim, para criar um indicador padrão com parâmetros padrão, bastará especificar o símbolo, período e identificador.
Implementação de um método que retorna um ponteiro para um objeto-indicador personalizado:
//+------------------------------------------------------------------+ //| Return pointer to object- custom indicator | //+------------------------------------------------------------------+ CIndicatorDE *CIndicatorsCollection::GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam ¶m[]) { CIndicatorDE *tmp=new CIndCustom(symbol,timeframe,param); if(tmp==NULL) return NULL; tmp.SetGroup(group); int index=this.Index(tmp); delete tmp; return(index>WRONG_VALUE ? this.m_list.At(index) : NULL); } //+------------------------------------------------------------------+
Aqui: criamos um objeto temporário do indicador para procurar exatamente o mesmo objeto na lista-coleção, definimos um grupo para ele e obter o índice do objeto encontrado na lista-coleção. Em seguida, excluímos o objeto temporário e retornamos um ponteiro para um objeto com base no índice encontrado da lista, ouNULL se nenhum objeto foi encontrado na lista.
Método que verifica se um objeto-indicador existe com o id especificado na lista:
//+------------------------------------------------------------------+ //| Check presence of indicator object with specified id in the list | //+------------------------------------------------------------------+ bool CIndicatorsCollection::CheckID(const int id) { CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL); return(list!=NULL && list.Total()!=0); } //+------------------------------------------------------------------+
Obtemos uma lista de objetos-indicadores com o identificador especificado e retornamos o sinalizador que verifica se a lista é válida e não está vazia (a lista deve conter um objeto).
Método que define o identificador para o indicador especificado:
//+------------------------------------------------------------------+ //| Set ID for the specified indicator | //+------------------------------------------------------------------+ void CIndicatorsCollection::SetID(CIndicatorDE *indicator,const int id) { if(indicator==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_IND_POINTER)); return; } if(id>WRONG_VALUE) { if(CheckID(id)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_IND_ID_EXIST)," #",(string)id); return; } } indicator.SetID(id); } //+------------------------------------------------------------------+
Ao método é transferido o ponteiro para o objeto-indicador, que precisa definir o identificador passado pelo parâmetro para o método.
Se for passado um ponteiro inválido, nós relatamos isso e saímos do método.
Se o valor do identificador for maior que -1, verificamos se existe um objeto-indicador com este identificador e, se já existir, relatamos isso e saímos.
Se todas as verificações forem passadas, definimos o identificador do objeto. Se o valor do identificador passado para o método for menor que zero, esse identificador será definido para o objeto sem verificação, e tal identificador do objeto-indicador indica sua ausência.
Método que retorna um objeto-indicador de acordo com o identificador especificado:
//+------------------------------------------------------------------+ //| Return indicator object by its ID | //+------------------------------------------------------------------+ CIndicatorDE *CIndicatorsCollection::GetIndByID(const uint id) { CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL); return(list==NULL || list.Total()==0 ? NULL : list.At(list.Total()-1)); } //+------------------------------------------------------------------+
Aqui: obtemos uma lista de objetos-indicadores com o identificador especificado e retornamos NULL (se a lista não puder ser recuperada ou se a lista estiver vazia), ou um ponteiro para um objeto com o identificador desejado. Como pode haver apenas um objeto com o identificador especificado, não importa qual índice na lista resultante deve ser indicado - o primeiro ou o último. Aqui indicamos o último.
Ao testar a criação de indicadores no EA houve um problema, quando o intervalo de tempo é alterado, são criados exatamente os mesmos indicadores adicionais, mas com um intervalo de tempo diferente. E isso é certo, afinal, indicadores com os mesmos parâmetros de entrada, mas calculados com base em timeframes diferentes, já são dois indicadores diferentes. Para simplesmente evitar esse problema, basta limpar a lista de indicadores criados ao desinicializar o programa. Para tanto, no objeto principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh declaramos um novo manipulador OnDeinit():
//--- (1) Timer, event handler (2) NewTick, (3) Calculate, Deinit void OnTimer(SDataCalculate &data_calculate); void OnTick(SDataCalculate &data_calculate,const uint required=0); int OnCalculate(SDataCalculate &data_calculate,const uint required=0); void OnDeinit(void);
E fora do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Deinitialize library | //+------------------------------------------------------------------+ void CEngine::OnDeinit(void) { this.m_indicators.GetList().Clear(); } //+------------------------------------------------------------------+
Este método de classe será chamado a partir do manipulador OnDeinit() do programa. Tudo o que é feito aqui é chamar o método para limpar a lista-coleção do objeto-coleção dos indicadores.
Teste
Para testar a criação de diferentes indicadores e obter dados dos objetos-indicadores criados, tomamos este Expert Advisor do artigo anterior.
Vamos salvá-lo na pasta nova \MQL5\Experts\TestDoEasy\Part56\ usando o novo nome TestDoEasyPart56.mq5.
No EA, criaremos dois indicadores de média móvel personalizados, mas com parâmetros diferentes (pegaremos os indicadores na pasta de exemplos de indicadores que vem com o terminal padrão \MQL5\Indicators\Examples\). E criaremos dois indicadores padrão de Adaptive Moving Average com diferentes parâmetros de entrada.
No escopo global definimos substituições de macro para simplificar o acesso aos indicadores por identificador e declaramos duas matrizes de parâmetros para criar dois indicadores personalizados:
//+------------------------------------------------------------------+ //| TestDoEasyPart56.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_PROFIT_WITHDRAWAL, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20) #define MA1 (1) #define MA2 (2) #define AMA1 (3) #define AMA2 (4) //--- structures struct SDataButt { string name; string text; }; //--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpDistancePReq = 50; // Distance for Pending Request's activate (points) input uint InpBarsDelayPReq = 5; // Bars delay for Pending Request's activate (current timeframe) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) 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 //--- 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; //--- Arrays of custom indicator parameters MqlParam param_ma1[]; MqlParam param_ma2[]; //+------------------------------------------------------------------+
Em essência, as substituições de macro declaradas são descrições dos valores numéricos dos identificadores. Será mais fácil acessar o identificador do indicador pelo nome do que por seu valor.
No manipulador OnInit() do Expert Advisor criamos todos os quatro indicadores e imediatamente exibimos os dados de todos os indicadores criados no log:
//+------------------------------------------------------------------+ //| 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(); //--- Create indicators ArrayResize(param_ma1,4); //--- Name of indicator 1 param_ma1[0].type=TYPE_STRING; param_ma1[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma1[1].type=TYPE_INT; param_ma1[1].integer_value=13; //--- Horizontal shift param_ma1[2].type=TYPE_INT; param_ma1[2].integer_value=0; //--- Smoothing method param_ma1[3].type=TYPE_INT; param_ma1[3].integer_value=MODE_SMA; //--- Create indicator 1 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,INDICATOR_GROUP_TREND,param_ma1); ArrayResize(param_ma2,5); //--- Name of indicator 2 param_ma2[0].type=TYPE_STRING; param_ma2[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma2[1].type=TYPE_INT; param_ma2[1].integer_value=13; //--- Horizontal shift param_ma2[2].type=TYPE_INT; param_ma2[2].integer_value=0; //--- Smoothing method param_ma2[3].type=TYPE_INT; param_ma2[3].integer_value=MODE_SMA; //--- Calculation price param_ma2[4].type=TYPE_INT; param_ma2[4].integer_value=PRICE_OPEN; //--- Create indicator 2 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,INDICATOR_GROUP_TREND,param_ma2); //--- Create indicator 3 engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA1); //--- Create indicator 4 engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA2,14); //--- Display descriptions of created indicators engine.GetIndicatorsCollection().Print(); engine.GetIndicatorsCollection().PrintShort(); //--- Check and remove remaining EA graphical objects if(IsPresentObectByPrefix(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); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2")); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Para o segundo indicador MA personalizado, definiremos o preço de cálculo PRICE_OPEN. Se o preço de cálculo não for indicado explicitamente, então por padrão (no primeiro indicador MA), o preço PRICE_CLOSE é usado para calcular o indicador.
Ao criar indicadores AMA, o primeiro é definido para um período de cálculo de 9 (definido por padrão), já o segundo é definido explicitamente para 14.
Assim, todos os quatro indicadores criados têm diferentes valores de entrada a nível de parâmetros.
No manipulador OnDeinit() do Expert Advisor chamamos o manipulador OnDeinit() da biblioteca:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove EA graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); //--- Deinitialize library engine.OnDeinit(); } //+------------------------------------------------------------------+
Isso limpará a lista-coleção dos indicadores durante a desinicialização do EA ao alternar os períodos gráficos, o que nos evitará criar objetos-indicadores desnecessários.
No manipulador OnTick() do Expert Advisor obtemos acesso a cada um dos objetos-indicadores criados e imprimimos os dados da barra atual de cada indicador, exibindo-os nos comentários do gráfico:
//+------------------------------------------------------------------+ //| 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 } //--- Get custom indicator objects CIndicatorDE *ma1=engine.GetIndicatorsCollection().GetIndByID(MA1); CIndicatorDE *ma2=engine.GetIndicatorsCollection().GetIndByID(MA2); CIndicatorDE *ama1=engine.GetIndicatorsCollection().GetIndByID(AMA1); CIndicatorDE *ama2=engine.GetIndicatorsCollection().GetIndByID(AMA2); Comment ( "ma1=",DoubleToString(ma1.GetDataBuffer(0,0),6), ", ma2=",DoubleToString(ma2.GetDataBuffer(0,0),6), "\nama1=",DoubleToString(ama1.GetDataBuffer(0,0),6), ", ama2=",DoubleToString(ama2.GetDataBuffer(0,0),6) ); //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing pending orders } } //+------------------------------------------------------------------+
No último artigo, criamos temporariamente objetos-indicadores na função de inicialização da biblioteca OnInitDoEasy(). Vamos remover essas linhas da função:
//--- 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 //--- Create indicators engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),9,2,30,0,PRICE_CLOSE); engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),10,3,32,5,PRICE_CLOSE); engine.GetIndicatorsCollection().Print(); engine.GetIndicatorsCollection().PrintShort(); //--- Create resource text files
Vamos compilar o Expert Advisor e iniciá-lo no gráfico, tendo previamente definido nas configurações para usar apenas o símbolo e período gráfico atuais.
O log conterá descrições dos parâmetros de todos os indicadores criados:
--- Initializing "DoEasy" library --- Working with the current symbol only: "EURUSD" Working with the current timeframe only: H1 EURUSD symbol timeseries: - Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6284 Library initialization time: 00:00:00.141 ============= Parameter list start: "Custom indicator" ============= Indicator status: Custom indicator Type of indicator: CUSTOM Indicator timeframe: H1 Indicator handle: 10 Indicator group: Trend indicator Indicator ID: 1 ------ Empty value for plotting where nothing will be drawn: EMPTY_VALUE ------ Indicator symbol: EURUSD Indicator name: "Examples\Custom Moving Average.ex5" Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)" --- Indicator parameters --- - [1] int type: 13 - [2] int type: 0 - [3] int type: 0 ================== Parameter list end: "Custom indicator" ================== ============= Parameter list start: "Custom indicator" ============= Indicator status: Custom indicator Type of indicator: CUSTOM Indicator timeframe: H1 Indicator handle: 11 Indicator group: Trend indicator Indicator ID: 2 ------ Empty value for plotting where nothing will be drawn: EMPTY_VALUE ------ Indicator symbol: EURUSD Indicator name: "Examples\Custom Moving Average.ex5" Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)" --- Indicator parameters --- - [1] int type: 13 - [2] int type: 0 - [3] int type: 0 - [4] int type: 2 ================== Parameter list end: "Custom indicator" ================== ============= Parameter list start: "Standard indicator" ============= Indicator status: Standard indicator Type of indicator: AMA Indicator timeframe: H1 Indicator handle: 12 Indicator group: Trend indicator Indicator ID: 3 ------ Empty value for plotting where nothing will be drawn: EMPTY_VALUE ------ Indicator symbol: EURUSD Indicator name: "Adaptive Moving Average" Indicator short name: "AMA(EURUSD,H1)" --- Indicator parameters --- - Averaging period: 9 - Fast MA period: 2 - Slow MA period: 30 - Horizontal shift of the indicator: 0 - Price type or handle: CLOSE ================== Parameter list end: "Standard indicator" ================== ============= Parameter list start: "Standard indicator" ============= Indicator status: Standard indicator Type of indicator: AMA Indicator timeframe: H1 Indicator handle: 13 Indicator group: Trend indicator Indicator ID: 4 ------ Empty value for plotting where nothing will be drawn: EMPTY_VALUE ------ Indicator symbol: EURUSD Indicator name: "Adaptive Moving Average" Indicator short name: "AMA(EURUSD,H1)" --- Indicator parameters --- - Averaging period: 14 - Fast MA period: 2 - Slow MA period: 30 - Horizontal shift of the indicator: 0 - Price type or handle: CLOSE ================== Parameter list end: "Standard indicator" ================== Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 10, id #1] Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 11, id #2] Standard indicator Adaptive Moving Average EURUSD H1 [handle 12, id #3] Standard indicator Adaptive Moving Average EURUSD H1 [handle 13, id #4]
No gráfico do símbolo, serão exibidos os dados dos buffers de todos os indicadores criados:
No gráfico podemos colocar os indicadores necessários que correspondem aos parâmetros criados no EA, e verificar se esses indicadores coincidem nos comentários no gráfico e na janela de dados.
O que vem agora?
No próximo artigo, continuaremos a criar funcionalidade para trabalhar com indicadores em Expert Advisors. Nos próximos artigos, pretende-se criar uma vinculação desses indicadores a barras das classes da série temporal e obter dados de indicadores para vários estudos estatísticos.
Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Quero ressaltar que a classe-coleção de indicadores está atualmente em desenvolvimento, portanto, não é recomendável usá-la em nossos programas.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
Artigos desta série:
Trabalhando com séries temporais na biblioteca DoEasy (Parte 35): Objeto "Barra" e lista-série temporal do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 36): objeto das séries temporais de todos os períodos usados do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa
Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais
Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Trabalhando com séries temporais na biblioteca DoEasy (Parte 41): exemplo de indicador multissímbolo multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 42): classe de um objeto de buffer abstrato de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 43): classes de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 44): classe-coleção de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 46): buffers de indicador multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela
Trabalhando com séries temporais na biblioteca DoEasy (Parte 49): indicadores padrão multiperíodos multissímbolos multibuffer
Trabalhando com séries temporais na biblioteca DoEasy (Parte 50): indicadores padrão multiperíodos multissímbolos com deslocamento
Trabalhando com séries temporais na biblioteca DoEasy (Parte 51): indicadores padrão multiperíodos multissímbolos compostos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 52): natureza multiplataforma de indicadores padrão multiperíodos multissímbolos de buffer único
Trabalhando com séries temporais na biblioteca DoEasy (Parte 53): classe do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 54): classes herdeiras do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 55): classe-coleção de indicadores
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/8646
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso