Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Artyom Trishkin | 11 agosto, 2020
Sumário
- Ideia
- Modificando classes de séries temporais
- Criando e testando o indicador multiperíodo
- O que vem agora?
Ideia
No artigo anterior começamos a aprender a trabalhar com a Biblioteca DoEasy como parte de indicadores. Este tipo de programa requer uma abordagem um pouco diferente na hora de construir e atualizar séries temporais, uma vez que existem algumas particularidades ao realizar cálculos de maneira reduzida em indicadores, bem como algumas limitações ao obter dados sobre o símbolo e período gráfico atual do indicador em execução.
Nós construímos uma consulta e um carregamento de dados históricos, tudo da maneira correta, e agora precisamos criar uma funcionalidade que permita atualizar em tempo real todos os dados vindos de todas as séries temporais usadas no indicador (assumimos que o indicador é multiperíodo e recebe dados a partir dos períodos gráficos especificados).
Ao construir os dados do buffer do indicador, executamos um loop desde a barra que tem uma determinada profundidade de dados históricos até a barra atual (zero). Em tal caso, o mais simples é pegar os dados pelo índice do loop, uma vez que esses dados já estão criados no objeto-série temporal da biblioteca, e ninguém nos impede de obtê-los de acordo com o índice. Porém, fazemos isso apenas ao criar dados estáticos. Ademais, enfrentamos problemas de indexação por número de barra ao tentar atualizar dados em tempo real nas listas-séries temporais. Quando uma nova barra é adicionada à lista-série temporal, tal barra possui o índice 0: afinal, é a nova barra que se torna zero (atual) e os índices de todas as barras construídas anteriormente na série temporal aumentam em 1. Assim, sempre que uma nova barra é aberta no gráfico, precisamos adicioná-la à lista-série temporal e aumentar em 1 os números de todas as outras barras na lista-série temporal atualizada.
Isso é extremamente irracional. Por esse motivo, vamos modificar a maneira de fazer/obter a indexação das barras na/da lista para ser como a usada ao trabalhar com o tempo das barras na lista-série temporal: o tempo de abertura de cada barra na lista-série temporal permanece sempre inalterado, e o que necessitamos é usar como ponto de referência o tempo da barra ao acessar qualquer uma delas na coleção de séries temporais da biblioteca. Contudo, manteremos a indexação por número de barra, mas não obteremos o número da barra a partir de propriedades: ao consultar a barra pelo índice da série temporal, calcularemos o tempo da barra segundo o índice solicitado, e segundo esse cálculo teremos a barra necessária desde a lista-série temporal, barra essa que usaremos no futuro.
Modificando classes de séries temporais
No arquivo \MQL5\Include\DoEasy\Defines.mqh, da enumeração de propriedades inteiras do objeto-barra removemos a propriedade índice de barra:
//+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_INDEX = 0, // Bar index in timeseries BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)
Em seu lugar colocamos a propriedade hora da barra e reduzimos em 1 a quantidade de propriedades inteiras do objeto-barra (de 14 para 13):
//+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_TIME = 0, // Bar period start time BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration) BAR_PROP_PERIOD, // Bar period (timeframe) BAR_PROP_SPREAD, // Bar spread BAR_PROP_VOLUME_TICK, // Bar tick volume BAR_PROP_VOLUME_REAL, // Bar exchange volume BAR_PROP_TIME_DAY_OF_YEAR, // Bar day serial number in a year BAR_PROP_TIME_YEAR, // A year the bar belongs to BAR_PROP_TIME_MONTH, // A month the bar belongs to BAR_PROP_TIME_DAY_OF_WEEK, // Bar week day BAR_PROP_TIME_DAY, // Bar day of month (number) BAR_PROP_TIME_HOUR, // Bar hour BAR_PROP_TIME_MINUTE, // Bar minute }; #define BAR_PROP_INTEGER_TOTAL (13) // Total number of integer bar properties #define BAR_PROP_INTEGER_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+
Assim, na lista de possíveis critérios para classificação de barras, precisamos da mesma maneira remover a classificação por índice e em seu lugar colocar a classificação por tempo de barra:
//+------------------------------------------------------------------+ //| Possible bar sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BAR_DBL_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP) #define FIRST_BAR_STR_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP) enum ENUM_SORT_BAR_MODE { //--- Sort by integer properties SORT_BY_BAR_TIME = 0, // Sort by bar period start time SORT_BY_BAR_TYPE, // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration) SORT_BY_BAR_PERIOD, // Sort by bar period (timeframe) SORT_BY_BAR_SPREAD, // Sort by bar spread SORT_BY_BAR_VOLUME_TICK, // Sort by bar tick volume SORT_BY_BAR_VOLUME_REAL, // Sort by bar exchange volume SORT_BY_BAR_TIME_DAY_OF_YEAR, // Sort by bar day number in a year SORT_BY_BAR_TIME_YEAR, // Sort by a year the bar belongs to SORT_BY_BAR_TIME_MONTH, // Sort by a month the bar belongs to SORT_BY_BAR_TIME_DAY_OF_WEEK, // Sort by a bar week day SORT_BY_BAR_TIME_DAY, // Sort by a bar day SORT_BY_BAR_TIME_HOUR, // Sort by a bar hour SORT_BY_BAR_TIME_MINUTE, // Sort by a bar minute //--- Sort by real properties SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP, // Sort by bar open price SORT_BY_BAR_HIGH, // Sort by the highest price for the bar period SORT_BY_BAR_LOW, // Sort by the lowest price for the bar period SORT_BY_BAR_CLOSE, // Sort by a bar close price SORT_BY_BAR_CANDLE_SIZE, // Sort by a candle price SORT_BY_BAR_CANDLE_SIZE_BODY, // Sort by a candle body size SORT_BY_BAR_CANDLE_BODY_TOP, // Sort by a candle body top SORT_BY_BAR_CANDLE_BODY_BOTTOM, // Sort by a candle body bottom SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP, // Sort by candle upper wick size SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN, // Sort by candle lower wick size //--- Sort by string properties SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP, // Sort by a bar symbol }; //+------------------------------------------------------------------+
Reconstruímos a classe CBar no arquivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh de modo que trabalhe com o tempo da barra.
Anteriormente, o método SetSymbolPeriod() definia o símbolo especificado, período gráfico e índice de barra, para o objeto-barra. Agora, em vez do índice, definiremos o tempo da barra:
//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetProperties(const MqlRates &rates);
Consertamos a implementação do método:
//+------------------------------------------------------------------+ //| Set bar symbol, timeframe and index | //+------------------------------------------------------------------+ void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { this.SetProperty(BAR_PROP_TIME,time); this.SetProperty(BAR_PROP_SYMBOL,symbol); this.SetProperty(BAR_PROP_PERIOD,timeframe); this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); this.m_period_description=TimeframeDescription(timeframe); } //+------------------------------------------------------------------+
Em vez do índice de barra, agora vamos transferir para o primeiro construtor paramétrico da classe o tempo da barra, e para obter mais informações de onde é chamado o construtor da classe CBar, adicionamos uma variável por meio da qual transferiremos ao construtor a descrição do método de classe no qual é chamada a criação do objeto-barra:
//--- Constructors CBar(){;} CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source); CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates);
Corrigimos a implementação do construtor: em vez do índice, agora usamos o tempo da barra, e ao texto que descreve o erro de obtenção de dados históricos adicionamos uma variável que aponta para o método de classe desde o qual é chamado o construtor:
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source) { this.m_type=COLLECTION_SERIES_ID; MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,time); ::ResetLastError(); //--- If failed to get the requested data by time and write bar data to the MqlRates array, //--- display an error message, create and fill the structure with zeros, and write it to the rates_array array if(::CopyRates(symbol,timeframe,time,1,rates_array)<1) { int err_code=::GetLastError(); ::Print ( DFUN,"(1)-> ",source,symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); //--- Set the requested bar time to the structure with zero fields MqlRates err={0}; err.time=time; rates_array[0]=err; } ::ResetLastError(); //--- If failed to set time to the time structure, display the error message if(!::TimeToStruct(rates_array[0].time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print ( DFUN,"(1) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); } //--- Set the bar properties this.SetProperties(rates_array[0]); } //+------------------------------------------------------------------+
Após adicionar o valor da variável source à mensagem de erro de histórico, conseguiremos encontrar a classe e o método desde o qual é realizada a tentativa de criar o novo objeto-barra que provoca o falha ao obter o histórico.
O segundo construtor paramétrico agora, em vez de índice de barra, também opera com o tempo de barra:
//+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates) { this.m_type=COLLECTION_SERIES_ID; this.SetSymbolPeriod(symbol,timeframe,rates.time); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print ( DFUN,"(2) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(rates.time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); //--- Set the requested bar time to the structure with zero fields MqlRates err={0}; err.time=rates.time; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
À seção pública da classe, no bloco de métodos de acesso simplificado às propriedades do objeto-barra renomeamos o método Period() para Timeframe() e removemos o método Index(), que retorna esta propriedade (já removida) da barra:
//+------------------------------------------------------------------+ //| Methods of simplified access to bar object properties | //+------------------------------------------------------------------+ //--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume, //--- (6) bar period start time, (7) year, (8) month the bar belongs to //--- (9) week number since the year start, (10) week number since the month start //--- (11) day, (12) hour, (13) minute ENUM_BAR_BODY_TYPE TypeBody(void) const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD); } int Spread(void) const { return (int)this.GetProperty(BAR_PROP_SPREAD); } long VolumeTick(void) const { return this.GetProperty(BAR_PROP_VOLUME_TICK); } long VolumeReal(void) const { return this.GetProperty(BAR_PROP_VOLUME_REAL); } datetime Time(void) const { return (datetime)this.GetProperty(BAR_PROP_TIME); } long Year(void) const { return this.GetProperty(BAR_PROP_TIME_YEAR); } long Month(void) const { return this.GetProperty(BAR_PROP_TIME_MONTH); } long DayOfWeek(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK); } long DayOfYear(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR); } long Day(void) const { return this.GetProperty(BAR_PROP_TIME_DAY); } long Hour(void) const { return this.GetProperty(BAR_PROP_TIME_HOUR); } long Minute(void) const { return this.GetProperty(BAR_PROP_TIME_MINUTE); } long Index(void) const { return this.GetProperty(BAR_PROP_INDEX); }
Agora o método Index() o valor calculado com base no tempo da barra:
//--- Return bar symbol string Symbol(void) const { return this.GetProperty(BAR_PROP_SYMBOL); } //--- Return bar index on the specified timeframe the bar time falls into int Index(const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT) const { return ::iBarShift(this.Symbol(),(timeframe>PERIOD_CURRENT ? timeframe : this.Timeframe()),this.Time()); } //+------------------------------------------------------------------+
O método retorna o índice da barra da série temporal atual para timeframe especificado no parâmetro de entrada do método calculado pela função iBarShift().
No método que retorna o nome abreviado do objeto-barra, agora chamamos o método recém visto com o valor padrão PERIOD_CURRENT, que retorna o índice para a série temporal à qual pertence o objeto-barra:
//+------------------------------------------------------------------+ //| Return the bar object short name | //+------------------------------------------------------------------+ string CBar::Header(void) { return ( CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+ TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.Index()+"]" ); } //+------------------------------------------------------------------+
Do método que retorna uma descrição da propriedade inteira do objeto-barra removemos o bloco que retorna a descrição do índice da barra:
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_INDEX ? CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+
Em vez dele colocamos um bloco de código que retorna o tempo da barra (listagem completa do método):
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.BodyTypeDescription() ) : property==BAR_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==BAR_PROP_SPREAD ? CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_TICK ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_REAL ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TIME_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.Year() ) : property==BAR_PROP_TIME_MONTH ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+MonthDescription((int)this.Month()) ) : property==BAR_PROP_TIME_DAY_OF_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.DayOfYear(),3,'0') ) : property==BAR_PROP_TIME_DAY_OF_WEEK ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek()) ) : property==BAR_PROP_TIME_DAY ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Day(),2,'0') ) : property==BAR_PROP_TIME_HOUR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Hour(),2,'0') ) : property==BAR_PROP_TIME_MINUTE ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Minute(),2,'0') ) : "" ); } //+------------------------------------------------------------------+
Isso conclui as mudanças na classe do objeto-barra.
Se dermos uma olhada nas listas de classes da biblioteca padrão, no caminho MQL5\Include\Indicators\ veremos dois arquivos: Series.mqh e TimeSeries.mqh.
Também temos na biblioteca arquivos de classe com o mesmo nome. Isso não é correto. Renomeamos nossas duas classes, para isso, ao seu nome e ao dos seus arquivos atribuiremos a abreviação DE (de DoEasy), modificando todos os nomes, quando encontremos uma chamada para esses arquivos e classes. Essas alterações afetaram três arquivos: Series.mqh (agora renomeado para SeriesDE.mqh e classe CSeriesDE), TimeSeries.mqh (agora renomeado para TimeSeriesDE.mqh e classe CTimeSeriesDE) e TimeSeriesCollection.mqh (usa as duas classes renomeadas). Vamos examinar todos esses arquivos e suas classes em ordem.
O arquivo Series.mqh agora está salvo com o novo nome \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh, e agora o nome da classe corresponde:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private:
Assim, o método que retorna um objeto desta classe agora tem um novo tipo de classe:
public: //--- Return (1) oneself and (2) the timeseries list CSeriesDE *GetObject(void) { return &this; }
Agora renomeamos o método público, que retorna o objeto-barra pelo índice como na série temporal GetBarBySeriesIndex, para apenas GetBar(), e adicionamos um método semelhante, para retornar um objeto-barra por tempo de abertura na série temporal:
//--- Return the bar object by (1) a real index in the list, (2) an index as in the timeseries, (3) time and (4) the real list size CBar *GetBarByListIndex(const uint index); CBar *GetBar(const uint index); CBar *GetBar(const datetime time); int DataTotal(void) const { return this.m_list_series.Total(); }
Assim, agora teremos dois métodos sobrecarregados para retornar o objeto-barra (por tempo e por índice).
Implementação do método para retornar um objeto-barra por seu tempo de abertura:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(this.m_symbol,this.m_timeframe,time,DFUN_ERR_LINE); if(obj==NULL) return NULL; this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; CBar *bar=this.m_list_series.At(index); return bar; } //+------------------------------------------------------------------+
Ao método é transferido o tempo pelo qual precisamos encontrar e retornar o objeto-barra correspondente.
Criamos um objeto-barra temporário para a série temporal atual com uma propriedade de tempo igual àquela passada para o método.
Definimos o sinalizador para classificar a lista de objetos-barra por tempo e pesquisamos na lista o objeto-barra com uma propriedade de tempo igual àquela passada para o método.
No final da pesquisa, será retornado o índice da barra na lista, se ele for encontrado, caso contrário, será devolvido - 1.
Removemos o objeto-barra e obtemos a devida barra desde a lista pelo índice recebido. Se o índice for menor que zero, o método At() da classe CArrayObj retornará NULL.
Se o objeto for encontrado com base no tempo, retornamos do método o objeto-barra, caso contrário, NULL.
Implementação do método para retornar um objeto-barra por índice:
//+------------------------------------------------------------------+ //| Return the bar object by index as in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const uint index) { datetime time=::iTime(this.m_symbol,this.m_timeframe,index); if(time==0) return NULL; return this.GetBar(time); } //+------------------------------------------------------------------+
Ao método é transferido o índice da barra procurada.
Através da função iTime() obtemos o tempo da barra pelo índice e retornamos o resultado do trabalho do método GetBar(), discutido acima, que retorna o objeto-barra pelo tempo recebido.
Na seção pública da classe, junto com os métodos que retornam as propriedades principais da barra por índice, declaramos os métodos que retornam as mesmas propriedades com base no tempo da barra:
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const uint index,const bool from_series=true); double High(const uint index,const bool from_series=true); double Low(const uint index,const bool from_series=true); double Close(const uint index,const bool from_series=true); datetime Time(const uint index,const bool from_series=true); long TickVolume(const uint index,const bool from_series=true); long RealVolume(const uint index,const bool from_series=true); int Spread(const uint index,const bool from_series=true); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const datetime time); double High(const datetime time); double Low(const datetime time); double Close(const datetime time); datetime Time(const datetime time); long TickVolume(const datetime time); long RealVolume(const datetime time); int Spread(const datetime time);
Consideraremos a implementação dos métodos declarados um pouco mais tarde.
No mesmo local dessa classe, declaramos o método que permite gravar os dados especificados do objeto-série temporal na matriz passada para o método:
//--- (1) Create and (2) update the timeseries list int Create(const uint required=0); void Refresh(SDataCalculate &data_calculate); //--- Copy the specified double property of the timeseries to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE); //--- Create and send the "New bar" event to the control program chart void SendEvent(void);
Digamos que precisamos gravar os dados da série temporal no buffer do indicador de uma vez. Nosso objeto-barra pode conter muitas propriedades diferentes (inteiras e reais). Podemos escrever qualquer uma das propriedades reais do objeto-barra numa matriz usando este método. Neste caso, todos os dados serão gravados na matriz como na matriz-série temporal: os dados da barra atual armazenados no objeto-série temporal no final da lista serão gravados no índice zero da matriz de destino, ou seja, a gravação será feita de trás e para frente.
Consideremos sua implementação:
//+------------------------------------------------------------------+ //| Copy the specified double property of the timeseries to the array| //+------------------------------------------------------------------+ bool CSeriesDE::CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE) { //--- Get the number of bars in the timeseries list int total=this.m_list_series.Total(); if(total==0) return false; //--- If a dynamic array is passed to the method and its size is not equal to that of the timeseries list, //--- set the new size of the passed array equal to that of the timeseries list if(::ArrayIsDynamic(array) && ::ArraySize(array)!=total) if(::ArrayResize(array,total,this.m_required)==WRONG_VALUE) return false; //--- In the loop from the very last timeseries list element (from the current bar) int n=0; for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--) { //--- get the next bar object by the loop index, CBar *bar=this.m_list_series.At(i); //--- calculate the index, based on which the bar property is saved to the passed array n=total-1-i; //--- write the value of the obtained bar property using the calculated index //--- if the bar is not received or the property is equal to zero, write the value passed to the method as "empty" to the array array[n]=(bar==NULL ? empty : (bar.GetProperty(property)>0 && bar.GetProperty(property)<EMPTY_VALUE ? bar.GetProperty(property) : empty)); } return true; } //+------------------------------------------------------------------+
Como podemos ver, para o último valor da matriz de origem caia na célula zero da matriz de destino, aqui é calculado o índice desta última. Assim, nossa lista-série temporal (propriedade da barra solicitada) será gravada na matriz (por exemplo, o buffer de indicador) de acordo com a ordem de numeração no gráfico do símbolo, enquanto os objetos-barras na lista-série temporal estarão localizados na ordem inversa — barra com o tempo mais recente (barra atual) estará localizada no final da lista. Isso nos permitirá copiar rapidamente as propriedades de todas as barras desde a lista-série temporal no buffer de indicador se o timeframe da série temporal copiada (para o buffer por meio deste método) coincidir com o timeframe do gráfico.
Em ambos os construtores da classe definimos o sinalizador para classificar a lista-séries temporal com base no tempo das barras:
//+------------------------------------------------------------------+ //| Constructor 1 (current symbol and period timeseries) | //+------------------------------------------------------------------+ CSeriesDE::CSeriesDE(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false) { this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period()); this.m_period_description=TimeframeDescription(this.m_timeframe); } //+------------------------------------------------------------------+ //| Constructor 2 (specified symbol and period timeseries) | //+------------------------------------------------------------------+ CSeriesDE::CSeriesDE(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false) { this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetRequiredUsedData(required,0); this.m_period_description=TimeframeDescription(this.m_timeframe); } //+------------------------------------------------------------------+
No método para criar uma lista-série temporal alteramos a classificação por índice para a classificação por tempo e adicionamos o texto exibido para erros ao criar um objeto-barra e o adicionamos à lista-série temporal:
//+------------------------------------------------------------------+ //| 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 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)); } } //--- Return the size of the created bar object list return this.m_list_series.Total(); } //+------------------------------------------------------------------+
O método para atualizar a lista e os dados da série temporal também foi ligeiramente aprimorado:
//+------------------------------------------------------------------+ //| Update timeseries list and data | //+------------------------------------------------------------------+ void CSeriesDE::Refresh(SDataCalculate &data_calculate) { //--- If the timeseries is not used, exit if(!this.m_available) return; MqlRates rates[1]; //--- Set the flag of sorting the list of bars by time this.m_list_series.Sort(SORT_BY_BAR_TIME); //--- If a new bar is present on a symbol and period, if(this.IsNewBarManual(data_calculate.rates.time)) { //--- create a new bar object and add it to the end of the list CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,this.m_new_bar_obj.TimeNewBar(),DFUN_ERR_LINE); if(new_bar==NULL) return; if(!this.m_list_series.InsertSort(new_bar)) { delete new_bar; return; } //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol this.SetServerDate(); //--- if the timeseries exceeds the requested number of bars, remove the earliest bar if(this.m_list_series.Total()>(int)this.m_required) this.m_list_series.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(data_calculate.rates.time); } //--- Get the bar index with the maximum time (zero bar) and bar object from the list by the obtained index int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME); CBar *bar=this.m_list_series.At(index); if(bar==NULL) return; //--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe, //--- copy price parameters (passed to the method from the outside) to the bar price structure int copied=1; if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==(ENUM_TIMEFRAMES)::Period()) { rates[0].time=data_calculate.rates.time; rates[0].open=data_calculate.rates.open; rates[0].high=data_calculate.rates.high; rates[0].low=data_calculate.rates.low; rates[0].close=data_calculate.rates.close; rates[0].tick_volume=data_calculate.rates.tick_volume; rates[0].real_volume=data_calculate.rates.real_volume; rates[0].spread=data_calculate.rates.spread; } //--- otherwise, get data to the bar price structure from the environment else copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates); //--- If the prices are obtained, set the new properties from the price structure for the bar object if(copied==1) bar.SetProperties(rates[0]); } //+------------------------------------------------------------------+
Aqui também agora a classificação da lista é definida pelo tempo, assim, ao criar um novo objeto-barra transferimos ao construtor da classe o tempo da barra desde o objeto "Nova barra" — afinal, o que estamos fazendo é adicionar uma nova barra à lista somente quando definimos a abertura de uma nova barra, e o objeto "Nova barra" já contém o tempo de abertura desta barra, portanto, vamos passá-lo para o construtor. E, além disso, passamos ao construtor a descrição do método, em que é criado o novo objeto-barra. Se houver uma falha ao criar um novo objeto-barra, uma mensagem gerada desde seu construtor aparecerá no log, em que será gravado o método CSeriesDE::Refresh e a string desde a qual é chamado o construtor da classe CBar.
Para obter desde a lista-série temporal a barra mais recente (atual), vamos encontrá-la pelo tempo máximo de todos os objetos-barras na lista-série temporal. Para fazer isso, primeiro encontramos o índice do objeto-barra com o tempo máximo por meio do método FindBarMax() da classe CSelect, e pelo índice obtido, da lista pegamos na barra mais recente (a atual). Se por algum motivo não obtivermos o índice da barra atual, o valor do índice será -1, e ao obter um item de lista usando o método At() com um índice negativo, retornaremos o valor NULL, se estiver, simplesmente saímos do método de atualização.
Métodos para retornar as propriedades principais de um objeto-barra com base no tempo:
//+------------------------------------------------------------------+ //| Return bar's Open by time | //+------------------------------------------------------------------+ double CSeriesDE::Open(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Open() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's High by time | //+------------------------------------------------------------------+ double CSeriesDE::High(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.High() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Low by time | //+------------------------------------------------------------------+ double CSeriesDE::Low(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Low() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Close by time | //+------------------------------------------------------------------+ double CSeriesDE::Close(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Close() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar time by time | //+------------------------------------------------------------------+ datetime CSeriesDE::Time(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return bar tick volume by time | //+------------------------------------------------------------------+ long CSeriesDE::TickVolume(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar real volume by time | //+------------------------------------------------------------------+ long CSeriesDE::RealVolume(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar spread by time | //+------------------------------------------------------------------+ int CSeriesDE::Spread(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Spread() : WRONG_VALUE); } //+------------------------------------------------------------------+
Todos eles funcionam da mesma maneira:
obtemos o objeto-barra desde a lista-série temporal com base no tempo e retornamos o valor da devida propriedade levando em consideração o erro de obtenção do objeto-barra.
O método para criar e enviar o evento "Nova barra" para o gráfico do programa de controle também foi aprimorado levando em consideração a necessidade de receber o objeto-barra atual com base no tempo:
//+------------------------------------------------------------------+ //| Create and send the "New bar" event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeriesDE::SendEvent(void) { int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME); CBar *bar=this.m_list_series.At(index); if(bar==NULL) return; ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol()); } //+------------------------------------------------------------------+
Aqui, exatamente da mesma maneira que no método Refresh(), obtemos o objeto-barra atual desde a lista-série temporal, e transferimos o tempo desta barra ao parâmetro lparam ao enviar o evento personalizado para o gráfico do programa de controle.
Assim concluímos a série temporal. Agora modificamos a classe de todas as séries temporais de um símbolo.
Conforme mencionado anteriormente, esta classe CTimeSerirs pode entrar em conflito com a classe de mesmo nome na biblioteca padrão. Por isso, já a renomeamos para CTimeSerirsDE. Assim, dentro da listagem da classe são substituídas todas as ocorrências da linha "CTimeSerirs" pela linha "CTimeSerirsDE" e "CSerirs" por "CSerirsDE", e aqui não consideraremos essas substituições. Apenas como exemplo:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "SeriesDE.mqh" #include "..\Ticks\NewTickObj.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeriesDE : public CBaseObjExt { private:
Na seção pública da classe declaramos um método para copiar na matriz transferida a propriedade real das barras da série temporal especificada:
//--- Copy the specified double property of the specified timeseries to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //--- Compare CTimeSeriesDE objects (by symbol) virtual int Compare(const CObject *node,const int mode=0) const; //--- Display (1) description and (2) short symbol timeseries description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructors CTimeSeriesDE(void){;} CTimeSeriesDE(const string symbol); }; //+------------------------------------------------------------------+
Consideramos esse método acima ao finalizar a classe CSeriesDE. Vejamos a implementação do método:
//+------------------------------------------------------------------+ //| Copy the specified double property of the specified timeseries | //+------------------------------------------------------------------+ bool CTimeSeriesDE::CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE) { CSeriesDE *series=this.GetSeries(timeframe); if(series==NULL) return false; return series.CopyToBufferAsSeries(property,array,empty); } //+------------------------------------------------------------------+
Neste caso, tudo é simples: primeiro, obtemos a série temporal do timeframe especificado, e depois retornamos o resultado da chamada deste método desde o objeto-série temporal resultante.
No método que retorna o índice da série temporal na lista contendo todas as séries temporais do símbolo de acordo com o timeframe, introduzimos uma verificação de timeframe especificado para pesquisa:
//+------------------------------------------------------------------+ //| Return the timeframe index in the list | //+------------------------------------------------------------------+ int CTimeSeriesDE::IndexTimeframe(const ENUM_TIMEFRAMES timeframe) { const CSeriesDE *obj=new CSeriesDE(this.m_symbol,(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe)); if(obj==NULL) return WRONG_VALUE; this.m_list_series.Sort(); int index=this.m_list_series.Search(obj); delete obj; return index; } //+------------------------------------------------------------------+
Ao criar um objeto temporário para pesquisa, verificamos o valor do timeframe inserido e se inserido CURRENT_PERIOD, para realizar a pesquisa usamos o timeframe atual.
No método para atualizar a lista-série temporal especificada ao adicionar um novo evento à lista de eventos usaremos o tempo de abertura da nova barra desde a estrutura data_calculate como valor do parâmetro lparam:
//+------------------------------------------------------------------+ //| Update a specified timeseries list | //+------------------------------------------------------------------+ void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { //--- Reset the timeseries event flag and clear the list of all timeseries events this.m_is_event=false; this.m_list_events.Clear(); //--- Get the timeseries from the list by its timeframe CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable()) return; //--- Update the timeseries list series_obj.Refresh(data_calculate); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(data_calculate.rates.time)) { //--- send the "New bar" event to the control program chart series_obj.SendEvent(); //--- set the values of the first date in history on the server and in the terminal this.SetTerminalServerDate(); //--- add the "New bar" event to the list of timeseries events //--- in case of successful addition, set the event flag for the timeseries if(this.EventAdd(SERIES_EVENTS_NEW_BAR,series_obj.Time(data_calculate.rates.time),series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; } } //+------------------------------------------------------------------+
Isso conclui a classe CTimeSeriesDE. Passamos para a classe de objeto-coleção de objetos de todas as séries temporais de todos os símbolos CTimeSeriesCollection.
No momento, renomeamos duas classes: CSeriesDE e CTimeSerirsDE. Assim, dentro da listagem da classe CTimeSeriesCollection substituímos todas as ocorrências da linha "CTimeSerirs" pela linha "CTimeSerirsDE", e "CSerirs" por "CSerirsDE".
Neste caso, não consideraremos essas substituições. Apenas como exemplo:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Objects\Series\TimeSeriesDE.mqh" #include "..\Objects\Symbols\Symbol.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt { private: CListObj m_list; // List of applied symbol timeseries //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public: //--- Return (1) oneself and (2) the timeseries list CTimeSeriesCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } //--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period CTimeSeriesDE *GetTimeseries(const string symbol); CSeriesDE *GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Create the symbol timeseries list collection
Na seção pública da classe, declararemos três novos métodos:
um método que retorna um objeto-barra da série temporal do símbolo com base no tempo de abertura da barra,
e dois métodos que retornam um objeto-barra de uma série temporal correspondendo ao tempo de abertura desta barra em outra série temporal de acordo com o índice e com o tempo da barra:
//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time //--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true); CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
Além disso, declaramos na seção pública mais dois métodos: um para atualizar todas as séries temporais de um símbolo e outro que copie para a matriz a propriedade double da série temporal de um símbolo:
//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols void Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate); void Refresh(const string symbol,SDataCalculate &data_calculate); void Refresh(SDataCalculate &data_calculate); //--- Get events from the timeseries object and add them to the list bool SetEvents(CTimeSeriesDE *timeseries); //--- Display (1) the complete and (2) short collection description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //--- Constructor CTimeSeriesCollection(); }; //+------------------------------------------------------------------+
Implementação de um método que retorna um objeto-barra de uma série temporal do símbolo da posição especificada com base no tempo:
//+------------------------------------------------------------------+ //| Return the bar object of the specified timeseries | //| of the specified symbol of the specified position by time | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time) { CSeriesDE *series=this.GetSeries(symbol,timeframe); if(series==NULL) return NULL; return series.GetBar(bar_time); } //+------------------------------------------------------------------+
Ao método é transferido o símbolo e o timeframe da série temporal, desde a qual é necessário obter a barra com o tempo de abertura especificado.
Obtemos o objeto-série temporal com o símbolo e o timeframe especificados e retornamos o objeto-barra obtido da série temporal resultante com base no tempo da barra.
Se a barra não puder ser recebida, ela retornará NULL.
Implementação do método que retorna o objeto-barra (da primeira série temporal com base no índice) que corresponde ao tempo de abertura da barra na segunda série temporal:
//+------------------------------------------------------------------+ //| Return the bar object of the first timeseries by index | //| corresponding to the bar open time on the second timeseries | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { CBar *bar_first=this.GetBar(symbol_first,timeframe_first,index); if(bar_first==NULL) return NULL; CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time()); return bar_second; } //+------------------------------------------------------------------+
Ao método são transferidos o símbolo e o timeframe do primeiro gráfico, o índice da barra no primeiro gráfico, o símbolo e o período do segundo gráfico.
Obtemos o primeiro objeto-barra desde a série temporal do primeiro símbolo-período com base no índice,
obtemos e retornamos o segundo objeto-barra do segundo símbolo-período com base no tempo da primeira barra obtida.
O método permite obter a posição da barra com base no índice no primeiro período-símbolo especificado, posição essa cujo tempo coincide com o da posição da barra no segundo período-símbolo.
O que isso faz? Como exemplo, podemos marcar rapidamente no gráfico M15 todas as barras H1.
Basta ao método transferir o símbolo atual, o período gráfico М15, a posição da barra com base no seu índice no gráfico (digamos que seja o índice do loop do cálculo do indicador), o símbolo atual e o período Н1. E o método retornará o objeto-barra desde o gráfico do símbolo atual e o período H1, cujo tempo de abertura inclui o da primeira barra especificada.
Implementação do método que retorna a barra-objeto da primeira série temporal com base no tempo correspondente ao tempo de abertura da barra na segunda série temporal:
//+------------------------------------------------------------------+ //| Return the bar object of the first timeseries by time | //| corresponding to the bar open time on the second timeseries | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { CBar *bar_first=this.GetBar(symbol_first,timeframe_first,first_bar_time); if(bar_first==NULL) return NULL; CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time()); return bar_second; } //+------------------------------------------------------------------+
O método é idêntico ao método de obtenção de um objeto-barra com base no índice que acabamos de considerar. Aqui, em vez do índice da barra na série temporal, é indicada a sua hora de abertura na primeira série temporal especificada.
Como podemos ver, não apenas os períodos dos dois gráficos, mas também seus símbolos são transferidos para ambos os métodos. Isso significa que esses métodos podem retornar um objeto-barra (de qualquer símbolo-período) correspondente ao do primeiro símbolo-período com sua posição especificada na série temporal. Isso torna mais fácil comparar qualquer uma das propriedades de duas barras de qualquer símbolo-período.
Ao método para atualizar a série temporal do símbolo especificado adicionamos uma verificação de "símbolo não nativo":
//+------------------------------------------------------------------+ //| Update the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { //--- Reset the flag of an event in the timeseries collection and clear the event list this.m_is_event=false; this.m_list_events.Clear(); //--- Get the object of all symbol timeseries by a symbol name CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return; //--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit if(symbol!=::Symbol() && !timeseries.IsNewTick()) return; //--- Update the required object timeseries of all symbol timeseries timeseries.Refresh(timeframe,data_calculate); //--- If the timeseries has the enabled event flag, //--- get events from symbol timeseries, write them to the collection event list //--- and set the event flag in the collection if(timeseries.IsEvent()) this.m_is_event=this.SetEvents(timeseries); } //+------------------------------------------------------------------+
Por que isso é necessário? Acontece que atualizamos todas as séries temporais que não pertencem ao símbolo-período atual no temporizador da biblioteca. E a atualização das séries temporais pertencentes ao símbolo no qual o programa está sendo executado deve ser feita a partir do manipulador de eventos Start, NewTick ou Calculate do programa. Por isso, para não verificar no temporizador o novo evento de tick para o símbolo atual (as séries temporais do símbolo atual são atualizadas com base no tick), comparamos o símbolo da série temporal com o símbolo atual e verificamos o evento de série temporal "novo tick" apenas se as séries temporais não pertencerem ao símbolo atual.
Implementação do método para atualizar todas as séries temporais do símbolo especificado:
//+------------------------------------------------------------------+ //| Update all timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,SDataCalculate &data_calculate) { //--- Reset the flag of an event in the timeseries collection and clear the event list this.m_is_event=false; this.m_list_events.Clear(); //--- Get the object of all symbol timeseries by a symbol name CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return; //--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit if(symbol!=::Symbol() && !timeseries.IsNewTick()) return; //--- Update all object timeseries of all symbol timeseries timeseries.RefreshAll(data_calculate); //--- If the timeseries has the enabled event flag, //--- get events from symbol timeseries, write them to the collection event list //--- and set the event flag in the collection if(timeseries.IsEvent()) this.m_is_event=this.SetEvents(timeseries); } //+------------------------------------------------------------------+
Aqui, o código é comentado e, portanto, espero que tudo esteja claro.
Implementação do método que grava os dados reais especificados da barra do objeto-série temporal especificado na matriz passada para o método:
//+------------------------------------------------------------------+ //| Copy the specified double property to the array | //| for a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE) { CSeriesDE *series=this.GetSeries(symbol,timeframe); if(series==NULL) return false; return series.CopyToBufferAsSeries(property,array,empty); } //+------------------------------------------------------------------+
Já vimos a operação do método ao modificar a classe CSeriesDE.
Aqui apenas obtemos o devido objeto-série temporal com base no símbolo e período especificados e retornamos o resultado da chamada do método com o mesmo nome da série temporal obtida.
Assim concluímos a classe-coleção de séries temporais.
Agora precisamos fornecer acesso aos métodos recém-criados desde os programas baseados na biblioteca. O principal objeto da biblioteca CEngine é responsável por isso.
Abrimos o arquivo em \MQL5\Include\DoEasy\Engine.mqh e substituímos todas as ocorrências da string "CSerirs" por "CSerirsDE" e todas as ocorrências da string "CTimeSerirs" por "CTimeSerirsDE".
Na seção privada da classe declaramos uma variável-membro de classe para armazenar o nome do programa:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CTimeSeriesCollection m_time_series; // Timeseries collection CResourceCollection m_resource; // Resource list CTradingControl m_trading; // Trading management object CPause m_pause; // Pause object CArrayObj m_list_counters; // List of timer counters int m_global_error; // Global error code bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag bool m_is_account_event; // Account change event flag bool m_is_symbol_event; // Symbol change event flag ENUM_TRADE_EVENT m_last_trade_event; // Last account trading event int m_last_account_event; // Last event in the account properties int m_last_symbol_event; // Last event in the symbol properties ENUM_PROGRAM_TYPE m_program; // Program type string m_name; // Program name
No construtor da classe atribuímos a esta variável o valor do nome do programa:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name=::MQLInfoString(MQL_PROGRAM_NAME); ...
Na seção pública da classe, escrevemos um método que retorna um objeto-barra da série temporal do símbolo da posição especificada com base no tempo da barra,
dois métodos que retornam o objeto-barra da primeira série temporal correspondente ao tempo de abertura da barra na segunda série temporal com base no índice e com base no tempo,
um método que atualiza todas as séries temporais do símbolo especificado,
métodos que retornam propriedades básicas da barra com base no tempo,
um método para copiar a propriedade double da série temporal do símbolo especificado numa matriz e
um método que retorna o nome do programa que trabalha com base na biblioteca.
//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { return this.m_time_series.GetBar(symbol,timeframe,index,from_series); } CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { return this.m_time_series.GetBar(symbol,timeframe,time); } //--- Return the bar object of the first timeseries corresponding to the bar open time on the second timeseries (1) by index, (2) by time CBar *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,index,symbol_second,timeframe_second); } CBar *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,time,symbol_second,timeframe_second); } //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { return this.m_time_series.IsNewBar(symbol,timeframe,time); } //--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols void SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { this.m_time_series.Refresh(symbol,timeframe,data_calculate); } void SeriesRefresh(const string symbol,SDataCalculate &data_calculate) { this.m_time_series.Refresh(symbol,data_calculate); } void SeriesRefresh(SDataCalculate &data_calculate) { this.m_time_series.Refresh(data_calculate); } //--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period CTimeSeriesDE *SeriesGetTimeseries(const string symbol) { return this.m_time_series.GetTimeseries(symbol); } CSeriesDE *SeriesGetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe) { return this.m_time_series.GetSeries(symbol,timeframe); } //--- Return (1) an empty, (2) partially filled timeseries CSeriesDE *SeriesGetSeriesEmpty(void) { return this.m_time_series.GetSeriesEmpty(); } CSeriesDE *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted(); } //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the bar, specified by index, of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the bar, specified by time, of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); //--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property, double &array[],const double empty=EMPTY_VALUE) { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}
...
//--- Return the program name string Name(void) const { return this.m_name; }
Todos os métodos, cuja implementação está escrita no corpo da classe, retornam o resultado da chamada dos métodos com o mesmo nome da coleção de séries temporais TimeSeriesCollection discutidas acima.
Implementação de métodos que retornam propriedades básicas de barras com base no tempo:
//+------------------------------------------------------------------+ //| Return Open of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Open() : 0); } //+------------------------------------------------------------------+ //| Return High of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.High() : 0); } //+------------------------------------------------------------------+ //| Return Low of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Low() : 0); } //+------------------------------------------------------------------+ //| Return Close of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Close() : 0); } //+------------------------------------------------------------------+ //| Return Time of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return TickVolume of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return RealVolume of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return Spread of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Spread() : INT_MIN); } //+------------------------------------------------------------------+
Aqui tudo é simples:
obtemos o objeto-barra desde a classe-coleção de séries temporais por meio do método GetBar() especificando o símbolo e o período da série temporal e o tempo de abertura da barra solicitada nesta série temporal, e retornamos o valor da devida propriedade da barra obtida, levando em consideração o erro de falha ao obter a barra desde a série temporal.
No manipulador de eventos NewTick do símbolo atual escrevemos a atualização de todas as séries temporais do símbolo atual:
//+------------------------------------------------------------------+ //| NewTick event handler | //+------------------------------------------------------------------+ void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0) { //--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) return; //--- Re-create empty timeseries and update the current symbol timeseries this.SeriesSync(data_calculate,required); this.SeriesRefresh(NULL,data_calculate); //--- end } //+------------------------------------------------------------------+
Isso permitirá imediatamente, após uma tentativa de sincronização, atualizar nos EAs todas as séries temporais do símbolo atual, de modo a não esperar por tal atualização no temporizador da biblioteca, uma vez que isso às vezes leva à dessincronização dos dados, quando a atualização de dados no temporizador é chamada após a chegada de um novo tick com base no símbolo atual.
No manipulador de eventos Calculate do símbolo atual escrevemos a atualização de todas as séries temporais do símbolo atual após sua sincronização:
//+------------------------------------------------------------------+ //| Calculate event handler | //+------------------------------------------------------------------+ int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0) { //--- If this is not an indicator, exit if(this.m_program!=PROGRAM_INDICATOR) return data_calculate.rates_total; //--- Re-create empty timeseries //--- If at least one of the timeseries is not synchronized, return zero if(!this.SeriesSync(data_calculate,required)) { return 0; } //--- Update the timeseries of the current symbol and return rates_total this.SeriesRefresh(NULL,data_calculate); return data_calculate.rates_total; } //+------------------------------------------------------------------+
Neste caso, existem diferenças do manipulador OnTick(): até que todas as séries temporais usadas do símbolo atual estejam sincronizadas, o método retornará zero, que por sua vez informará o manipulador OnCalculate() do indicador sobre a necessidade de recalcular completamente os dados históricos.
Conseqüentemente, o método para sincronizar dados de todas as séries temporais agora deve retornar valores booleanos:
//+------------------------------------------------------------------+ //| Synchronize timeseries data with the server | //+------------------------------------------------------------------+ bool CEngine::SeriesSync(SDataCalculate &data_calculate,const uint required=0) { //--- If the timeseries data is not calculated, try re-creating the timeseries //--- Get the pointer to the empty timeseries CSeriesDE *series=this.SeriesGetSeriesEmpty(); //--- If there is an empty timeseries if(series!=NULL) { //--- Display the empty timeseries data as a chart comment and try synchronizing the timeseries with the server data ::Comment(series.Header(),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_WAIT_FOR_SYNC)); ::ChartRedraw(::ChartID()); //--- if the data has been synchronized if(series.SyncData(required,data_calculate.rates_total)) { //--- if managed to re-create the timeseries if(this.m_time_series.ReCreateSeries(series.Symbol(),series.Timeframe(),data_calculate.rates_total)) { //--- display the chart comment and the journal entry with the re-created timeseries data ::Comment(series.Header(),": OK"); ::ChartRedraw(::ChartID()); Print(series.Header()," ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED_OK),":"); series.PrintShort(); return true; } } //--- Data is not yet synchronized or failed to re-create the timeseries return false; } //--- There are no empty timeseries - all is synchronized, delete all comments else { ::Comment(""); ::ChartRedraw(::ChartID()); return true; } return false; } //+------------------------------------------------------------------+
Assim fica concluída a classe CEngine, por enquanto.
Agora vamos tentar verificar como funciona tudo nos indicadores. Como usamos séries temporais diferentes num indicador e podemos receber dados de uma barra que corresponde aos de outra com o tempo caindo nos limites da primeira barra, mas de outras séries temporais, a primeira coisa que vem à mente é criar um indicador exibindo linhas OHLC de barras de outros timeframes no gráfico atual.
Criando e testando o indicador multiperíodo
Para realizar o teste, vamos pegar o indicador que criamos no último artigo
e vamos salvá-lo na nova pasta \MQL5\Indicators\TestDoEasy\Part40\ com o novo nome TestDoEasyPart40.mq5.
Como proceder...? Podemos usar 21 séries temporais, de acordo com o número de timeframes padrão disponíveis. Nas configurações, haverá uma seleção padrão dos timeframes usados para a biblioteca, e já no gráfico exibiremos os botões correspondentes aos timeframes nas configurações. Para não limitar demais as configurações e, consequentemente, o código de manutenção dos buffers, simplesmente vinculamos os buffers do indicador a cada um dos timeframes disponíveis no terminal usando uma matriz de estruturas.
Vamos ativar/desativar a visibilidade da linha de buffer no gráfico e seus dados na janela de dados do indicador, ativando/desativando o botão correspondente. Para cada timeframe, teremos dois buffers: um a ser desenhado e outro a ser calculado. No buffer de cálculo, será possível armazenar dados intermediários das séries temporais correspondentes. Mas, nesta versão, não usaremos o buffer de cálculo. Para não escrever todos os 42 buffers (21 plotados e 21 calculados), criaremos uma estrutura na qual serão armazenados os parâmetros para cada um dos timeframes:
- Matriz atribuída pelo buffer indicador plotado
- Matriz atribuída pelo buffer indicador calculado
- Identificador de buffer (timeframe da série temporal cujos dados o buffer processará)
- Índice do buffer de indicador associado à matriz do buffer plotado
- Índice do buffer do indicador associado à matriz do buffer de cálculo
- Sinalizador de uso de buffer no indicador (botão pressionado/não pressionado)
- Sinalizador de exibição de buffer no indicador antes de ativar/desativar a exibição do buffer através do botão no gráfico
Não faremos a escolha de timeframe(s) ou séries temporais nas configurações do indicador. Habilitaremos/desabilitaremos a exibição dos devidos buffers do indicador no gráfico usando os botões que serão plotados de acordo com as séries temporais usadas. Precisamos do sinalizador de exibição de buffer no indicador antes de ativá-lo/desativá-lo com o botão, porque necessitamos tomar uma decisão sobre excluir ou exibir dados do buffer no gráfico apenas ao pressionar o devido botão.
Escrevemos todos os parâmetros de cada buffer do indicador (poderia ter sido definido programaticamente, mas é mais rápido desta forma):
//+------------------------------------------------------------------+ //| TestDoEasyPart40.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/pt/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- properties #property indicator_chart_window #property indicator_buffers 43 #property indicator_plots 21 //--- plot M1 #property indicator_label1 " M1" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGray #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot M2 #property indicator_label2 " M2" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGray #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- plot M3 #property indicator_label3 " M3" #property indicator_type3 DRAW_LINE #property indicator_color3 clrGray #property indicator_style3 STYLE_SOLID #property indicator_width3 1 //--- plot M4 #property indicator_label4 " M4" #property indicator_type4 DRAW_LINE #property indicator_color4 clrGray #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- plot M5 #property indicator_label5 " M5" #property indicator_type5 DRAW_LINE #property indicator_color5 clrGray #property indicator_style5 STYLE_SOLID #property indicator_width5 1 //--- plot M6 #property indicator_label6 " M6" #property indicator_type6 DRAW_LINE #property indicator_color6 clrGray #property indicator_style6 STYLE_SOLID #property indicator_width6 1 //--- plot M10 #property indicator_label7 " M10" #property indicator_type7 DRAW_LINE #property indicator_color7 clrGray #property indicator_style7 STYLE_SOLID #property indicator_width7 1 //--- plot M12 #property indicator_label8 " M12" #property indicator_type8 DRAW_LINE #property indicator_color8 clrGray #property indicator_style8 STYLE_SOLID #property indicator_width8 1 //--- plot M15 #property indicator_label9 " M15" #property indicator_type9 DRAW_LINE #property indicator_color9 clrGray #property indicator_style9 STYLE_SOLID #property indicator_width9 1 //--- plot M20 #property indicator_label10 " M20" #property indicator_type10 DRAW_LINE #property indicator_color10 clrGray #property indicator_style10 STYLE_SOLID #property indicator_width10 1 //--- plot M30 #property indicator_label11 " M30" #property indicator_type11 DRAW_LINE #property indicator_color11 clrGray #property indicator_style11 STYLE_SOLID #property indicator_width11 1 //--- plot H1 #property indicator_label12 " H1" #property indicator_type12 DRAW_LINE #property indicator_color12 clrGray #property indicator_style12 STYLE_SOLID #property indicator_width12 1 //--- plot H2 #property indicator_label13 " H2" #property indicator_type13 DRAW_LINE #property indicator_color13 clrGray #property indicator_style13 STYLE_SOLID #property indicator_width13 1 //--- plot H3 #property indicator_label14 " H3" #property indicator_type14 DRAW_LINE #property indicator_color14 clrGray #property indicator_style14 STYLE_SOLID #property indicator_width14 1 //--- plot H4 #property indicator_label15 " H4" #property indicator_type15 DRAW_LINE #property indicator_color15 clrGray #property indicator_style15 STYLE_SOLID #property indicator_width15 1 //--- plot H6 #property indicator_label16 " H6" #property indicator_type16 DRAW_LINE #property indicator_color16 clrGray #property indicator_style16 STYLE_SOLID #property indicator_width16 1 //--- plot H8 #property indicator_label17 " H8" #property indicator_type17 DRAW_LINE #property indicator_color17 clrGray #property indicator_style17 STYLE_SOLID #property indicator_width17 1 //--- plot H12 #property indicator_label18 " H12" #property indicator_type18 DRAW_LINE #property indicator_color18 clrGray #property indicator_style18 STYLE_SOLID #property indicator_width18 1 //--- plot D1 #property indicator_label19 " D1" #property indicator_type19 DRAW_LINE #property indicator_color19 clrGray #property indicator_style19 STYLE_SOLID #property indicator_width19 1 //--- plot W1 #property indicator_label20 " W1" #property indicator_type20 DRAW_LINE #property indicator_color20 clrGray #property indicator_style20 STYLE_SOLID #property indicator_width20 1 //--- plot MN1 #property indicator_label21 " MN1" #property indicator_type21 DRAW_LINE #property indicator_color21 clrGray #property indicator_style21 STYLE_SOLID #property indicator_width21 1 //--- classes
Como podemos ver, nós temos que o número total de buffers é definido como 43, enquanto os buffers plotados são 21. Como concordamos em adicionar um calculado a cada um dos buffers exibidos, então 21+21=42. Por que vem um extra? Ele é necessário para armazenar dados de tempo da matriz time[] OnCalculate(). Como em algumas funções precisamos do tempo da barra segundo o índice, e a matriz time[] existe apenas no escopo do manipulador OnCalculate(), a solução mais simples para ter dados de tempo do timeframe atual é salvar a matriz time[] num dos buffers de cálculo do indicador. É por isso que definimos mais um buffer.
No indicador, poderemos e exibir os quatro preços da barra: Open, High, Low e Close. O objeto-barra tem mais propriedades reais:
- Preço de abertura da barra (Open)
- Preço mais alto durante o período (High)
- Preço mais baixo durante o período (Low)
- Preço da fechamento da barra (Close)
- Tamanho da vela
- Tamanho do corpo da vela
- Parte superior do corpo da vela
- Parte inferior do corpo da vela
- Tamanho de sombra superior da vela
- Tamanho da sombra inferior da vela
Por isso, não podemos usar o valor desta enumeração (ENUM_BAR_PROP_DOUBLE) nas configurações, e criamos mais uma enumeração, na qual escreveremos as propriedades necessárias, equiparadas às propriedades da enumeração das propriedades reais do objeto-barra ENUM_BAR_PROP_DOUBLE, que pode ser selecionado nas configurações de exibição e defini,os uma substituição de macro com o número total de períodos gráficos disponíveis:
//--- classes //--- enums enum ENUM_BAR_PRICE { BAR_PRICE_OPEN = BAR_PROP_OPEN, // Bar Open BAR_PRICE_HIGH = BAR_PROP_HIGH, // Bar High BAR_PRICE_LOW = BAR_PROP_LOW, // Bar Low BAR_PRICE_CLOSE = BAR_PROP_CLOSE, // Bar Close }; //--- defines #define PERIODS_TOTAL (21) // Total amount of available chart periods //--- structures
Agora vamos criar uma estrutura de dados de um buffer plotado e de um de cálculo, atribuídos a uma série temporal (a um período gráfico):
//--- structures struct SDataBuffer { private: int m_buff_id; // Buffer ID (timeframe) int m_buff_data_index; // The index of the indicator buffer related to the Data[] array int m_buff_tmp_index; // The index of the indicator buffer related to the Temp[] array bool m_used; // The flag of using the buffer in the indicator bool m_show_data; // The flag of displaying the buffer on the chart before enabling/disabling its display public: double Data[]; // The array assigned as INDICATOR_DATA by the indicator buffer double Temp[]; // The array assigned as INDICATOR_CALCULATIONS by the indicator buffer //--- Set indices for the drawn and calculated buffers assigned to the timeframe void SetIndex(const int index) { this.m_buff_data_index=index; this.m_buff_tmp_index=index+PERIODS_TOTAL; } //--- Methods of setting and returning values of the private structure members void SetID(const int id) { this.m_buff_id=id; } void SetUsed(const bool flag) { this.m_used=flag; } void SetShowData(const bool flag) { this.m_show_data=flag; } int IndexDataBuffer(void) const { return this.m_buff_data_index; } int IndexTempBuffer(void) const { return this.m_buff_tmp_index; } int ID(void) const { return this.m_buff_id; } bool IsUsed(void) const { return this.m_used; } bool GetShowDataFlag(void) const { return this.m_show_data; } void Print(void); }; //--- Display structure data to the journal void SDataBuffer::Print(void) { ::Print ( "Buffer[",this.IndexDataBuffer(),"], ID: ",(string)this.ID(), " (",TimeframeDescription((ENUM_TIMEFRAMES)this.ID()), "), temp buffer index: ",(string)this.IndexTempBuffer(), ", used: ",this.IsUsed() ); } //--- input variables
Essa estrutura armazenará todos os dados para trabalhar com um timeframe. Cada um dos timeframes do indicador usado terá sua própria estrutura. A melhor solução para isso é a matriz dessas estruturas. Vamos criá-la no bloco para definir os buffers do indicador.
Escrevemos os parâmetros de entrada do indicador:
//--- input variables /*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 ENUM_BAR_PRICE InpBarPrice = BAR_PRICE_OPEN; // Applied bar price sinput bool InpShowBarTimes = false; // Show bar time comments sinput uint InpControlBar = 1; // Control bar sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers
Tudo é padrão aqui, como em todos os EA de teste e indicadores que fazemos para cada artigo. Como hoje testaremos apenas com base no símbolo atual, comentaremos os modificadores sinput nas configurações do símbolo indicando que a variável é um parâmetro de entrada do indicador (modificador sinput indica que a otimização dos parâmetros desta variável é proibida). Assim, estes parâmetros não podem ser selecionados nas configurações uma vez que estão ausentes, e à variável InpModeUsedSymbols será atribuído o valor SYMBOLS_MODE_CURRENT (trabalho apenas com o símbolo atual).
A variável InpShowBarTimes permite ativar/desativar a exibição de comentários no gráfico — exibição da correspondência entre a barra do timeframe atual e a das séries temporais testadas. Já a variável InpControlBar serve para especificar o número da barra, cujo valor pode ser controlado nos comentários do gráfico.
Finalmente, escrevemos os buffers do indicador e as variáveis globais:
//--- indicator buffers SDataBuffer Buffers[PERIODS_TOTAL]; // Array of the indicator buffer data structures assigned to the timeseries double BufferTime[]; // The calculated buffer for storing and passing data from the time[] array //--- global variables CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names bool testing; // Flag of working in the tester int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // Array of used symbols string array_used_periods[]; // Array of used timeframes //+------------------------------------------------------------------+
Como podemos ver, como setup de buffers de indicador, definimos uma matriz de estruturas, discutida acima. Ao inicializar o indicador, atribuiremos dados às propriedades da estrutura e vincularemos as matrizes da estrutura aos buffers de indicador. Aqui é definido o buffer calculado para armazenar e transmitir o tempo na função do indicador.
As variáveis globais do indicador são todas assinadas, e acho que não suscitam dúvidas.
No manipulador OnInit() do indicador, primeiro criamos um painel com botões que corresponda aos timeframes selecionados nas configurações, em seguida, atribuímos todos os buffers do indicador e definimos todos os seus parâmetros para estruturas localizadas na sua matriz de estruturas:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set indicator global variables prefix=engine.Name()+"_"; testing=engine.IsTester(); ZeroMemory(rates_data); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- indicator buffers mapping //--- In the loop by the total number of available timeframes, for(int i=0;i<PERIODS_TOTAL;i++) { //--- get the next timeframe ENUM_TIMEFRAMES timeframe=TimeframeByEnumIndex(uchar(i+1)); //--- Bind the drawn indicator buffer by the buffer index equal to the loop index with the structure Data[] array SetIndexBuffer(i,Buffers[i].Data); //--- set "the empty value" for the Data[] buffer, //--- set the name of the graphical series displayed in the data window for the Data[] buffer //--- set the direction of indexing the Data[] drawn buffer as in the timeseries PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetString(i,PLOT_LABEL,"Buffer "+TimeframeDescription(timeframe)); ArraySetAsSeries(Buffers[i].Data,true); //--- Setting the drawn buffer according to the button status bool state=false; //--- Set the name of the button correspondign to the buffer with the loop index and its timeframe string name=prefix+"BUTT_"+TimeframeDescription(timeframe); //--- If not in the tester, while the chart features the button with the specified name, if(!engine.IsTester() && ObjectFind(ChartID(),name)==0) { //--- set the name of the terminal global variable for storing the button status string name_gv=(string)ChartID()+"_"+name; //--- if no global variable with such a name is found, create it set to 'false', if(!GlobalVariableCheck(name_gv)) GlobalVariableSet(name_gv,false); //--- get the button status from the terminal global variable state=GlobalVariableGet(name_gv); } //--- Set the values for all structure fields Buffers[i].SetID(timeframe); Buffers[i].SetIndex(i); Buffers[i].SetUsed(state); Buffers[i].SetShowData(state); //--- Set the button status ButtonState(name,state); //--- Depending on the button status, specify whether the buffer data should be displayed should be displayed in the data window PlotIndexSetInteger(i,PLOT_SHOW_DATA,state); //--- Bind the calculated indicator buffer by the buffer index from IndexTempBuffer() with the Temp[] array of the structure SetIndexBuffer(Buffers[i].IndexTempBuffer(),Buffers[i].Temp,INDICATOR_CALCULATIONS); //--- set the direction of indexing the Temp[] calculated buffer as in the timeseries ArraySetAsSeries(Buffers[i].Temp,true); } //--- Bind the calculated indicator buffer by the PERIODS_TOTAL*2 buffer index with the BufferTime[] array of the indicator SetIndexBuffer(PERIODS_TOTAL*2,BufferTime,INDICATOR_CALCULATIONS); //--- set the direction of indexing the BufferTime[] calculated buffer as in the timeseries ArraySetAsSeries(BufferTime,true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Aqui são comentadas todas as strings do loop em que são vinculados os buffers do indicador ao índice do loop com base na matriz de estrutura e são definidos os parâmetros restantes para cada estrutura armazenada na célula seguinte. Se você tiver alguma dúvida, pode sempre perguntar na discussão do artigo.
Funções para trabalhar com botões:
//+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=20,const int shift_y=0) { int total=ArraySize(array_used_periods); uint w=30,h=20,x=InpButtShiftX+1, y=InpButtShiftY+h+1; //--- In the loop by the amount of used timeframes for(int i=0;i<total;i++) { //--- create the name of the next button string butt_name=prefix+"BUTT_"+array_used_periods[i]; //--- create a new button with the offset by ((button width + 1) * loop index) if(!ButtonCreate(butt_name,x+(w+1)*i,y,w,h,array_used_periods[i],clrGray)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),array_used_periods[i]); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Create the button | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Set the terminal's global variable value | //+------------------------------------------------------------------+ bool SetGlobalVariable(const string gv_name,const double value) { //--- If the variable name length exceeds 63 symbols, return 'false' if(StringLen(gv_name)>63) return false; return(GlobalVariableSet(gv_name,value)>0); } //+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Return the button status by the timeframe name | //+------------------------------------------------------------------+ bool ButtonState(const ENUM_TIMEFRAMES timeframe) { string name=prefix+"BUTT_"+TimeframeDescription(timeframe); return ButtonState(name); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } //+------------------------------------------------------------------+ //| Track the buttons' status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0,0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Create the button name for the terminal's global variable string name_gv=(string)ChartID()+"_"+prefix+button; //--- Get the button status (pressed/released). If not in the tester, //--- write the status to the button global variable (1 or 0) bool state=ButtonState(button_name); if(!engine.IsTester()) SetGlobalVariable(name_gv,state); //--- Get the timeframe from the button string ID and //--- the drawn buffer index by timeframe ENUM_TIMEFRAMES timeframe=TimeframeByDescription(StringSubstr(button,5)); int buffer_index=IndexBuffer(timeframe); //--- Set the button color depending on its status, //--- write its status to the buffer structure depending on the button status (used/not used) //--- initialize the buffer corresponding to the button timeframe by the buffer index received earlier ButtonState(button_name,state); Buffers[buffer_index].SetUsed(state); if(Buffers[buffer_index].GetShowDataFlag()!=state) { InitBuffer(buffer_index); BufferFill(buffer_index); Buffers[buffer_index].SetShowData(state); } //--- Here you can add additional handling of button pressing: //--- If the button is pressed if(state) { //--- If M1 button is pressed if(button=="BUTT_M1") { } //--- If button M2 is pressed else if(button=="BUTT_M2") { } //--- // Remaining buttons ... //--- } //--- Not pressed else { //--- M1 button if(button=="BUTT_M1") { } //--- M2 button if(button=="BUTT_M2") { } //--- // Remaining buttons ... //--- } //--- re-draw the chart ChartRedraw(); } //+------------------------------------------------------------------+
Todas essas funções são bastante simples e claras, além disso, algumas de suas strings são comentadas, e creio que não geraram dúvidas.
Vejamos o manipulador OnCalculate() do indicador:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure CopyData(rates_data,rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Handle the Calculate event in the library engine.OnCalculate(rates_data); //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the timer PressButtonsControl(); // Button pressing control EventsHandling(); // Working with events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Set OnCalculate arrays as timeseries ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(time,true); ArraySetAsSeries(tick_volume,true); ArraySetAsSeries(volume,true); ArraySetAsSeries(spread,true); //--- Setting buffer arrays as timeseries //--- Check for the minimum number of bars for calculation if(rates_total<2 || Point()==0) return 0; //--- Display reference data on bar open time if(InpShowBarTimes) { string txt=""; int total=ArraySize(array_used_periods); //--- In the loop by the amount of used timeframes for(int i=0;i<total;i++) { //--- get the next timeframe, buffer index and timeseries object by timeframe ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[i]); int buffer_index=IndexBuffer(timeframe); CSeriesDE *series=engine.SeriesGetSeries(NULL,timeframe); //--- If failed to get the timeseries or the buffer is not used (the button is released), move on to the next one if(series==NULL || !Buffers[buffer_index].IsUsed()) continue; //--- Get the reference bar from the timeseries list CBar *bar=series.GetBar(InpControlBar); if(bar==NULL) continue; //--- Collect data for the comment text string t1=TimeframeDescription((ENUM_TIMEFRAMES)Period()); string t2=TimeframeDescription(bar.Timeframe()); string t3=(string)InpControlBar; string t4=TimeToString(bar.Time()); string t5=(string)bar.Index((ENUM_TIMEFRAMES)Period()); //--- Set the comment text depending on the terminal language string tn=TextByLanguage ( "Бар на "+t1+", соответствующий бару "+t2+"["+t3+"] со временеи открытия "+t4+", расположен на баре "+t5, "The bar on "+t1+", corresponding to the "+t2+"["+t3+"] bar since the opening time of "+t4+", is located on bar "+t5 ); txt+=tn+"\n"; } //--- Display the comment on the chart Comment(txt); } //--- Check and calculate the number of calculated bars int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; InitBuffersAll(); } //--- Prepare data //--- Calculate the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { BufferTime[i]=(double)time[i]; CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,time[i]); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Se nas configurações o parâmetro "Show bar time comments" (variável InpShowBarTimes) estiver definido como true, este bloco de código exibirá no gráfico informações sobre a barra especificada na variável InpControlBar ("ControlBar"), bem como sobre sua correspondência com a barra nos timeframes de todas as séries temporais usadas.
Se o valor calculado limit for mais de um (o que significa que é necessário redesenhar todo o histórico, uma vez que houve mudanças nele), definiremos limit como igual ao início do histórico no gráfico atual e chamaremos a função de inicialização de todos os buffers do indicador.
O indicador é calculado desde o valor limit (em condições normais, seu valor é 1 (nova barra) ou zero (é calculada a barra atual)) até zero.
No ciclo principal do cálculo do indicador preenchemos o buffer de tempo calculado desde a matriz time[] (precisamos de um buffer de tempo em outras funções do indicador, onde é necessário obter o tempo por índice e a matriz time[] não está disponível), e chamamos a função para calcular uma barra para todos os buffers do indicador usados.
Funções de inicialização para buffers do indicador:
//+------------------------------------------------------------------+ //| Initialize the timeseries and the appropriate buffers by index | //+------------------------------------------------------------------+ bool InitBuffer(const int buffer_index) { //--- Leave if the wrong index is passed if(buffer_index==WRONG_VALUE) return false; //--- Initialize the variables using the "Not rendered" drawing style and disable the display in the data window int draw_type=DRAW_NONE; bool show_data=false; //--- If the buffer is used (button pressed) //--- Set the "Line" drawing style for variables and enable display in the data window if(Buffers[buffer_index].IsUsed()) { draw_type=DRAW_LINE; show_data=true; } //--- Set the drawing style and display in the data window for the buffer by its index PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_DRAW_TYPE,draw_type); PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_SHOW_DATA,show_data); //--- Initialize the calculated buffer using zero, while the drawn one is initialized using the "empty" value ArrayInitialize(Buffers[buffer_index].Temp,0); ArrayInitialize(Buffers[buffer_index].Data,EMPTY_VALUE); return true; } //+------------------------------------------------------------------+ //|Initialize the timeseries and the appropriate buffers by timeframe| //+------------------------------------------------------------------+ bool InitBuffer(const ENUM_TIMEFRAMES timeframe) { return InitBuffer(IndexBuffer(timeframe)); } //+------------------------------------------------------------------+ //| Initialize all timeseries and the appropriate buffers | //+------------------------------------------------------------------+ void InitBuffersAll(void) { //--- Initialize the next buffer in the loop by the total number of chart periods for(int i=0;i<PERIODS_TOTAL;i++) if(!InitBuffer(i)) continue; } //+------------------------------------------------------------------+
Função de cálculo de uma barra especificada para todos os buffers do indicador usados (para os quais o botão é pressionado):
//+------------------------------------------------------------------+ //| Calculating a single bar of all active buffers | //+------------------------------------------------------------------+ void CalculateSeries(const ENUM_BAR_PROP_DOUBLE property,const int index,const datetime time) { //--- Get the next buffer in the loop by the total number of chart periods for(int i=0;i<PERIODS_TOTAL;i++) { //--- if the buffer is not used (the button is released), move on to the next one if(!Buffers[i].IsUsed()) continue; //--- get the timeseries object by the buffer timeframe CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[i].ID()); //--- if the timeseries is not received //--- or the bar index passed to the function is beyond the total number of bars in the timeseries, move on to the next buffer if(series==NULL || index>series.GetList().Total()-1) continue; //--- get the bar object from the timeseries corresponding to the one passed to the bar time function on the current chart CBar *bar=engine.SeriesGetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,time,NULL,series.Timeframe()); if(bar==NULL) continue; //--- get the specified property from the obtained bar and //--- call the function of writing the value to the buffer by i index double value=bar.GetProperty(property); SetBufferData(i,value,index,bar); } } //+------------------------------------------------------------------+
Função de registro do valor da propriedade de um objeto-barra no buffer do indicador com base em vários índices de barra no gráfico atual:
//+------------------------------------------------------------------+ //| Write data on a single bar to the specified buffer | //+------------------------------------------------------------------+ void SetBufferData(const int buffer_index,const double value,const int index,const CBar *bar) { //--- Get the bar index by its time falling within the time limits on the current chart int n=iBarShift(NULL,PERIOD_CURRENT,bar.Time()); //--- If the passed index on the current chart (index) is less than the calculated time of bar start on another timeframe if(index<n) //--- in the loop from the n bar on the current chart to zero while(n>WRONG_VALUE && !IsStopped()) { //--- fill in the n index buffer with the 'value' passed to the function (0 - EMPTY_VALUE) //--- and decrease the n value Buffers[buffer_index].Data[n]=(value>0 ? value : EMPTY_VALUE); n--; } //--- If the passed index on the current chart (index) is not less than the calculated time of bar start on another timeframe //--- Set 'value' for the buffer by the 'index' passed to the function (0 - EMPTY_VALUE) else Buffers[buffer_index].Data[index]=(value>0 ? value : EMPTY_VALUE); } //+------------------------------------------------------------------+
Para exibir corretamente os dados de uma barra de um timeframe diferente no gráfico atual, precisamos encontrar o início do período da vela especificada (barra) no gráfico atual e preencher todos os índices de buffer com o valor da barra no outro período gráfico. E é esta função que toma conta disso.
Quando pressionamos o botão para ativar qualquer período gráfico, precisamos preencher o devido buffer exibido com um valor vazio (se o botão for liberado) ou recalcular todos os dados no buffer especificado pelo botão (se o botão for pressionado). A função de inicialização do buffer é responsável por apagar os dados, enquanto a função a seguir é responsável por preencher o buffer com os dados da série temporal especificada:
//+------------------------------------------------------------------+ //| Fill in the entire buffer with historical data | //+------------------------------------------------------------------+ void BufferFill(const int buffer_index) { //--- Leave if the wrong index is passed if(buffer_index==WRONG_VALUE) return; //--- Leave if the buffer is not used (the button is released) if(!Buffers[buffer_index].IsUsed()) return; //--- Get the timeseries object by the buffer timeframe CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[buffer_index].ID()); if(series==NULL) return; //--- If the buffer belongs to the current chart, copy the bar data from the timeseries to the buffer if(Buffers[buffer_index].ID()==Period()) series.CopyToBufferAsSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,Buffers[buffer_index].Data,EMPTY_VALUE); //--- Otherwise, calculate each next timeseries bar and write it to the buffer in the loop by the number of the current chart bars else for(int i=rates_data.rates_total-1;i>WRONG_VALUE && !IsStopped();i--) CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,(datetime)BufferTime[i]); } //+------------------------------------------------------------------+
O código completo do indicador pode ser encontrado nos arquivos anexados ao artigo.
Gostaria de salientar que este indicador de teste foi desenvolvido em MQL5. Ele também funciona em MQL4 sem necessidade de modificações, mas não totalmente da maneira correta, já que o período gráfico atual não é exibido quando o devido botão é pressionado, em vez disso, começa a ser exibido quando outro timeframe é ativado. Se definirmos nas configurações não padrão para os períodos gráficos do MetaTrader 4, o indicador sempre aguardará sua sincronização.
Além disso, os dados na janela de dados do terminal são exibidos incorretamente, mostrando absolutamente todos os buffers do indicador, mesmo os calculados, o que é natural, porque nem todas as funções MQL5 funcionam em MQL4, e é por isso que precisamos substituí-las por análogos MQL4.
Além disso, o indicador no MetaTrader 5 nem sempre processa corretamente as alterações nos dados históricos, o que é natural, pois esta é apenas uma versão de teste para verificar a operação no modo multiperíodo. Iremos resolver gradualmente todas as deficiências identificadas nos próximos artigos. E só quando tudo estiver funcionando corretamente no MetaTrader 5, corrigiremos o trabalho da biblioteca em indicadores no MetaTrader 4.
Compilamos o indicador e o iniciamos no gráfico do terminal:
Pode-se ver que no gráfico M15, o buffer de dados de M5 mostra os preços de fechamento das barras M5 num terço das velas no gráfico atual, o que é natural, porque numa barra M15 temos três barras M5, e é o preço de fechamento da barra M5 que é exibido na barra M15.
Iniciamos o indicador no testador com o parâmetro definido para exibir os dados das séries temporais no período gráfico atual:
O que vem agora?
No próximo artigo, continuaremos a desenvolver o tópico acerca do trabalho com objetos-séries temporais da biblioteca aplicado a indicadores.
Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
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