
Tipo de desenho DRAW_ARROW em indicadores multissímbolos e multiperíodos
Conteúdo
Introdução
Continuamos o tema dos indicadores multissímbolos e multiperíodos. O último artigo desta série foi sobre buffers coloridos em indicadores multissímbolos e multiperíodos. Hoje, vamos aprimorar a classe de multi-indicadores para que ela possa trabalhar com indicadores de seta.
Os indicadores de seta envolvem a ausência constante de dados no seu buffer de desenho. Onde a seta é exibida existe um valor no buffer, enquanto nos outros momentos, o buffer contém um valor vazio estabelecido para ele. Normalmente, isso é EMPTY_VALUE, mas para o buffer é possível definir qualquer valor como "vazio", que não será exibido no gráfico. Isso pode ser feito com a função:
PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);
onde buffer_index é o índice do buffer ao qual se define o valor vazio, e empty_value é o valor "vazio" que será estabelecido para esse buffer.
Nos indicadores multiperíodos, onde o buffer é preenchido com dados de forma intermitente, é necessário considerar a presença de valores vazios onde não há setas, e não inserir esses valores vazios na barra do gráfico onde já há um valor não vazio. Caso contrário, a seta anteriormente colocada será apagada por um novo valor vazio. Isso é verdadeiro se os dados de um indicador calculado em um timeframe menor forem copiados para um timeframe maior.
Vou explicar com um exemplo. Na imagem do gráfico M5 as barras do gráfico M15 são marcadas:
Aqui, vemos fractais do gráfico M5 que precisam ser estabelecidos nas barras do gráfico M15.
- Para a primeira barra M15 (I), é necessário estabelecer o fractal superior localizado na barra 3 — esse fractal será visível em M15, pois é o último dos três.
- Para a segunda barra M15 (II), é necessário estabelecer o fractal localizado na barra 2 — esse fractal será visível em M15 somente se não copiar o valor vazio da barra 3, pois ele substituirá o valor do fractal inferior. A barra no M15 terá dois fractais — superior e inferior, pois no M5 há um fractal inferior na barra 2 e um fractal superior na barra 3.
- Para a terceira barra M15 (III), é necessário estabelecer o fractal superior da barra 1 — esse fractal será visível em M15 apenas se não copiar os valores vazios das barras 2 e 3, pois eles substituirão o valor do fractal.
Assim, é necessário rastrear o valor na barra do gráfico atual e, se for vazio, pode-se copiar qualquer valor do buffer do timeframe menor. Mas se o valor já não for vazio, deve-se copiar apenas o valor também não vazio. Neste caso, na barra do gráfico do timeframe maior será estabelecido o valor do último fractal das barras do timeframe menor, que fazem parte da barra do período maior do gráfico. Por que usar o último valor da barra, e não o primeiro? Considero que este valor é mais relevante, pois está mais próximo do tempo atual.
Ao copiar dados do indicador calculado no timeframe maior para o gráfico do timeframe menor, é necessário considerar que a seta pode ser colocada não na barra zero ou primeira, mas, por exemplo, na segunda, terceira, ou em uma barra ainda mais antiga. Se copiar os dados apenas das barras zero e primeira, como está atualmente na classe de multi-indicadores, nenhuma seta será colocada no gráfico, até porque elas simplesmente estão em barras com índices mais altos e, consequentemente, o algoritmo de cálculo do indicador não pode vê-las:
Aqui vemos que o fractal foi formado na barra com índice 2. Se copiar apenas as barras zero e primeira, esse fractal não será visível para o programa.
Dessa forma, como não podemos saber exatamente em qual barra o indicador coloca suas setas, para a classe de multi-indicadores é necessário introduzir uma variável que indique explicitamente a barra a partir da qual começar a busca e a cópia dos valores. Atualmente, a classe copia apenas as barras zero e primeira no tick atual, portanto, os fractais não podem ser vistos, porque para eles, os valores começam na barra com índice 2.
Se usarmos um indicador personalizado como base, ele pode colocar setas em qualquer outra barra, isto é, não apenas na segunda, como o indicador Fractals. Por exemplo, se for um indicador de fractais personalizados, onde são procurados fractais de outra dimensão, como 3-X-3, a seta pode ser colocada na barra com índice 3 (se o fractal for redesenhado) ou 4 (se o fractal não for redesenhado).
Para indicadores padrão, vamos indicar explicitamente o índice da barra, pois ele é conhecido. Para indicadores personalizados, faremos com que seja possível especificar a barra de início da busca nos parâmetros do construtor da classe.
Aprimoramento das classes de multi-indicadores
Ao copiar dados nos métodos da classe, usamos arrays temporários locais. Vamos torná-los globais para não alocar memória novamente para esses arrays toda vez.
Na seção privada da classe base de indicadores multissímbolos e multiperíodos CIndMSTF declaramos novos arrays, e na seção protegida, uma variável para armazenar o número de barras para calcular o indicador no tick atual:
//+------------------------------------------------------------------+ //| Base class of the multi-symbol multi-period indicator | //+------------------------------------------------------------------+ class CIndMSTF : public CObject { private: ENUM_PROGRAM_TYPE m_program; // Program type ENUM_INDICATOR m_type; // Indicator type ENUM_TIMEFRAMES m_timeframe; // Chart timeframe string m_symbol; // Chart symbol int m_handle; // Indicator handle int m_id; // Identifier bool m_success; // Successful calculation flag ENUM_ERR_TYPE m_type_err; // Calculation error type string m_description; // Custom description of the indicator string m_name; // Indicator name string m_parameters; // Description of indicator parameters double m_array_data[]; // Temporary array for copying data datetime m_array_time[]; // Temporary array for copying time protected: ENUM_IND_CATEGORY m_category; // Indicator category MqlParam m_param[]; // Array of indicator parameters string m_title; // Title (indicator name + description of parameters) SBuffer m_buffers[]; // Indicator buffers int m_digits; // Digits in indicator values int m_limit; // Number of bars required to calculate the indicator on the current tick int m_rates_total; // Number of available bars for indicator calculation int m_prev_calculated; // Number of calculated bars on the previous indicator call uint m_bars_to_calculate; // Number of bars required to calculate the indicator on the current tick
Na seção pública da classe, escreveremos um método que define o número de barras necessárias para calcular o indicador no tick atual:
//--- Returns amount of calculated data int Calculated(void) const { return ::BarsCalculated(this.m_handle); } //--- Set the number of bars required to calculate the indicator on the current tick void SetBarsToCalculate(const int bars) { this.m_bars_to_calculate=bars; if(this.m_array_data.Size()!=this.m_bars_to_calculate) { ::ArrayResize(this.m_array_data,this.m_bars_to_calculate); ::ArrayResize(this.m_array_time,this.m_bars_to_calculate); } } //--- Virtual method returning the type of object (indicator)
O método recebe o número necessário de barras, este valor é armazenado na nova variável e os tamanhos dos arrays são definidos como iguais ao valor dessa variável.
Assim, ao alterar com sucesso os tamanhos dos arrays, não será necessário redefini-los novamente em cada método de cópia de dados. Se não for possível alterar os tamanhos dos arrays aqui, as funções de cópia de dados dentro dos métodos de cópia tentarão alterar o tamanho do array para o número necessário. Se isso também não for possível, os métodos emitirão uma mensagem de erro de cópia.
Dessa forma, definimos uma vez o tamanho necessário dos arrays quer seja no construtor da classe, ou (em caso de falha) nos métodos de cópia, e depois usamos os arrays com o tamanho definido, o que nos livra da necessidade de realocar memória constantemente para os arrays, caso fossem locais.
No construtor da classe chamamos esse método com um valor padrão para a maioria dos indicadores, igual a duas barras, para copiar os dados (primeira e zero):
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe) { //--- Start the timer ::ResetLastError(); if(!::EventSetTimer(1)) ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError()); //--- Set properties to the values passed to the constructor or to default values this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_type=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_handle=INVALID_HANDLE; this.m_digits=::Digits(); this.m_success=true; this.m_type_err=ERR_TYPE_NO_ERROR; //--- Set the size of the buffer structure array to the number of indicator buffers ::ResetLastError(); if(::ArrayResize(this.m_buffers,buffers)!=buffers) ::PrintFormat("%s: Buffers ArrayResize failed. Error %lu"__FUNCTION__,::GetLastError()); //--- Set the "empty" value for each buffer by default (can be changed it later) for(int i=0;i<(int)this.m_buffers.Size();i++) this.SetBufferInitValue(i,EMPTY_VALUE); //--- Set initial values of variables involved in resource-efficient calculation of the indicator this.m_prev_calculated=0; this.m_limit=0; this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe); this.SetBarsToCalculate(2); //--- If the indicator is calculated on non-current chart, request data from the required chart //--- (the first access to data starts data pumping) datetime array[]; if(symbol!=::Symbol() || timeframe!=::Period()) ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array); }
No destrutor da classe liberamos a memória alocada para os arrays temporários:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CIndMSTF::~CIndMSTF() { //--- Delete timer ::EventKillTimer(); //--- Release handle of the indicator ::ResetLastError(); if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle)) ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError()); //--- Free up the memory of buffer arrays for(int i=0;i<(int)this.BuffersTotal();i++) { ::ArrayFree(this.m_buffers[i].array0); ::ArrayFree(this.m_buffers[i].array1); ::ArrayFree(this.m_buffers[i].array2); ::ArrayFree(this.m_buffers[i].array3); ::ArrayFree(this.m_buffers[i].color_indexes); } //--- Free up the memory of temporary arrays ::ArrayFree(this.m_array_data); ::ArrayFree(this.m_array_time); }
No método de cópia de dados do array especificado do buffer especificado agora verificamos a quantidade de dados copiados pelo valor definido na nova variável. Anteriormente, o valor fixo era de duas barras:
//+------------------------------------------------------------------+ //| Copy data of the specified array of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArray(const uint buff_num,const uint array_num,const int to_copy,double &array[]) { ::ResetLastError(); //--- Copy either the last two bars to 'array' or all available historical data from indicator's calculation part array to buffer array of indicator object int copied=0; if(to_copy==this.m_bars_to_calculate) { switch(array_num) { case 0 : case 1 : case 2 : case 3 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,array); break; case 4 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,array); break; default : break; } } else { switch(array_num) { case 0 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0); break; case 1 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1); break; case 2 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2); break; case 3 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3); break; case 4 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes); break; default : break; } } //--- If copied successfully if(copied>0) return true; //--- If not all data is copied //--- If CopyBuffer returned -1, this means the start of historical data downloading //--- print a message about this to the log if(copied==WRONG_VALUE) ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription()); //--- In any other case, not all data has been copied yet //--- print a message about this to the log else ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied); return false; }
No método de cópia de dados de todos os arrays do buffer especificado anteriormente era fixado em duas barras, e era especificado diretamente no código de qual índice do array temporário para qual índice do array-buffer copiar os dados:
//+------------------------------------------------------------------+ //| Copy data of all arrays of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy) { bool res=true; double array[2]; if(to_copy==2) { switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : res=this.CopyArray(buff_num,0,to_copy,array); if(res) { this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-1]=array[1]; this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-2]=array[0]; } return res; //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING :
Agora, o número de dados copiados é desconhecido previamente, pois ele está definido na nova variável. Portanto, vamos verificar o valor dessa variável, e copiar os dados em um loop de acordo com a quantidade de dados copiados.
Os índices dos arrays de origem e de destino serão calculados a partir do índice do loop. Agora temos um array temporário global declarado, então o array local, como era anteriormente, já não é necessário:
//+------------------------------------------------------------------+ //| Copy data of all arrays of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy) { bool res=true; if(to_copy==this.m_bars_to_calculate) { int total=(int)this.m_array_data.Size(); switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } return res; //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } return res; //--- Four buffers case DRAW_BARS : case DRAW_CANDLES : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i]; } return res; //--- One buffer + color buffer case DRAW_COLOR_LINE : case DRAW_COLOR_HISTOGRAM : case DRAW_COLOR_ARROW : case DRAW_COLOR_SECTION : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //--- Two buffers + color buffer case DRAW_COLOR_HISTOGRAM2 : case DRAW_COLOR_ZIGZAG : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //--- Four buffers + color buffer case DRAW_COLOR_BARS : case DRAW_COLOR_CANDLES : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //---DRAW_NONE default: break; } } else { switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : return this.CopyArray(buff_num,0,to_copy,this.m_array_data); //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); return res; //--- Four buffers case DRAW_BARS : case DRAW_CANDLES : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); return res; //--- One buffer + color buffer case DRAW_COLOR_LINE : case DRAW_COLOR_HISTOGRAM : case DRAW_COLOR_ARROW : case DRAW_COLOR_SECTION : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //--- Two buffers + color buffer case DRAW_COLOR_HISTOGRAM2 : case DRAW_COLOR_ZIGZAG : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //--- Four buffers + color buffer case DRAW_COLOR_BARS : case DRAW_COLOR_CANDLES : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //---DRAW_NONE default: break; } } return false; }
No método de preenchimento dos buffers do objeto com dados do buffer de cálculo na seção de código responsável pelo trabalho no tick atual, agora copiamos não duas barras, mas a quantidade de barras definida na variável m_bars_to_calculate:
//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0) //--- In this case, it is necessary to calculate the number of bars specified in the m_bars_to_calculate variable - the current one and the rest if(this.m_limit<=1) { //--- In a loop over the number of indicator buffers for(int i=0;i<total;i++) { //--- If this is the opening of a new bar and resizing the indicator buffer failed, if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total)) { //--- add 'false' to the m_success variable and return 'false' //--- Here, an error message will be printed to log from the BufferResize method this.m_success=false; return false; } //--- If failed to copy bars in the amount of m_bars_to_calculate from the indicator's calculation part buffer, ::ResetLastError(); if(!this.CopyArrays(i,this.m_bars_to_calculate)) { //--- report this via the log, add 'false' to the m_success variable and return 'false' ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError()); this.m_success &=false; } } //--- If the loop is followed by errors, return 'false' if(!this.m_success) { this.m_type_err=ERR_TYPE_NO_DATA; return false; } //--- Success this.m_type_err=ERR_TYPE_NO_ERROR; this.m_success=true; return true; } //--- Undefined 'limit' option - return 'false' return false; }
O método que preenche o array desenhado com dados do buffer da classe foi completamente reescrito. Nele, foram considerados as nuances de copiar dados do timeframe menor para o maior, do maior para o menor e a quantidade de dados copiados no tick atual, conforme discutido no início deste artigo. A lógica deste método foi completamente reescrita e comentada:
//+------------------------------------------------------------------+ //| Fills the passed plot array with data from the class buffer | //+------------------------------------------------------------------+ bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const uint array_num,const int limit,double &buffer[]) { //--- Set the success flag this.m_success=true; //--- Get the indexing direction of the buffer array passed to the method and, //--- if non-timeseries indexing, set timeseries indexing bool as_series=::ArrayGetAsSeries(buffer); if(!as_series) ::ArraySetAsSeries(buffer,true); //--- Set the symbol name and timeframe value passed to the method string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to); ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to); //--- If this is the first launch or history changes, initialize the buffer array passed to the method if(limit>1 && this.m_limit>1) { ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num); ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num)); } //--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart) int count=(limit<=1 ? (int)this.m_bars_to_calculate : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit)); //--- In a loop from the zero bar to the value of the loop counter for(int i=0;i<count;i++) { //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array if(timeframe==::Period() && this.m_timeframe==::Period()) buffer[i]=this.GetData(buffer_num,array_num,i); //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object else { //--- If the chart timeframe is lower than the timeframe of the class object, if(timeframe<this.m_timeframe) { //--- If this is historical data (a bar with an index greater than m_bars_to_calculate-1) if(i>(int)this.m_bars_to_calculate-1) { //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]); if(bar==WRONG_VALUE) { this.m_success &=false; continue; } //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer buffer[i]=this.GetData(buffer_num,array_num,bar); } //--- If this is the current (zero) bar or a bar with an index less than or equal to m_bars_to_calculate-1 else { //--- Get the time of bars by symbol/timeframe of the class object in the amount of m_bars_to_calculate if(::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_bars_to_calculate,this.m_array_time)!=this.m_bars_to_calculate) { this.m_success=false; return false; } //--- Get the number of bars of the current chart period contained in the chart period of the class object int num_bars=::PeriodSeconds(this.m_timeframe)/::PeriodSeconds(timeframe); if(num_bars<1) num_bars=1; //--- In a loop through the array of received bar times for(int j=0;j<(int)this.m_array_time.Size();j++) { //--- Get the corresponding bar on the chart by the time of the bar in the buffer array of the class object int barN=::iBarShift(symbol,timeframe,this.m_array_time[j]); if(barN==WRONG_VALUE) { this.m_success &=false; return false; } //--- Calculate the final bar on the chart to copy data from the buffer of the calculation part of the indicator int end=barN-num_bars; if(end<0) end=-1; //--- In a loop from barN to end, copy data from the buffer array of the calculation part to the indicator buffer being drawn on the chart for(int n=barN;n>end;n--) buffer[n]=this.GetData(buffer_num,array_num,this.m_bars_to_calculate-1-j); } } } //--- If the chart timeframe is higher than the timeframe of the class object, else if(timeframe>this.m_timeframe) { //--- Get the number of bars of the class object chart period contained in the bar of the current chart period int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(this.m_timeframe); //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array //--- This bar will be the first of several bars of the class object in the amount of num_bars included in the chart bar int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]); if(bar==WRONG_VALUE) { this.m_success &=false; continue; } //--- Calculate the index of the last bar to copy in the loop int end=bar-num_bars; if(end<0) end=-1; //--- In a loop from the first to the last bar included in the bar of the older chart period for(int j=bar;j>end;j--) { //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(this.m_symbol,this.m_timeframe,j,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Get the bar number on the chart corresponding to the time of the buffer array of the calculation part by j loop index int barN=::iBarShift(symbol,timeframe,this.m_array_time[0]); //--- Get the data of the j bar in the buffer array of the calculation part and the data of barN contained in the chart indicator buffer double value_data=this.GetData(buffer_num,array_num,j); double value_buff=buffer[barN]; //--- If the bar on the chart has an empty value, then write data from the buffer array of the calculation part into it. Or //--- if there is already data on the chart, then we write new data to this bar only if the data contains a non-empty value. if(value_buff==this.BufferInitValue(buffer_num) || (value_buff!=this.BufferInitValue(buffer_num) && value_data!=this.BufferInitValue(buffer_num))) buffer[barN]=value_data; } } } } //--- Set initial indexing of the buffer array passed to the method ::ArraySetAsSeries(buffer,as_series); //--- Successful return true; }
A classe base do indicador multissímbolo multiperíodo está pronta.
Agora, vamos aprimorar as classes derivadas.
De todos os indicadores padrão, apenas o indicador de fractais de Bill Williams desenha setas com interrupções (Parabolic SAR também desenha setas, mas sem interrupções no gráfico).
E o desenho do indicador Fractals começa na barra com índice 2, ou seja, para calcular o indicador é necessário atualizar em cada tick três barras: 2, 1 e 0.
No construtor da classe desse indicador definimos a quantidade de barras de cálculo igual a três:
//+------------------------------------------------------------------+ //| Fractals indicator class | //+------------------------------------------------------------------+ class CIndFractals : public CIndMSTF { public: //--- Constructor CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe) { // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE //--- Create description of parameters //--- If non-current chart symbol or period, their descriptions are added to parameters bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); string param=(current ? "" : StringFormat("(%s)",symbol_period)); //--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("Fractals"); this.SetDescription("Fractals"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; //--- Description of UPPER_LINE and LOWER_LINE line buffers this.SetBufferDescription(UPPER_LINE,this.m_title+" Up"); this.SetBufferDescription(LOWER_LINE,this.m_title+" Down"); //--- Set drawing style for buffers 0 and 1 and the numbers of calculation part data buffers this.SetBufferDrawType(0,DRAW_ARROW,UPPER_LINE); this.SetBufferDrawType(1,DRAW_ARROW,LOWER_LINE); //--- Set default colors for buffers 0 and 1 this.SetBufferColorToIndex(UPPER_LINE,0,clrGray); this.SetBufferColorToIndex(LOWER_LINE,0,clrGray); //--- Set the number of bars required to calculate the indicator on the current tick this.SetBarsToCalculate(3); } };
Na classe base, essa quantidade é definida por padrão igual a duas barras. Aqui, redefinimos a quantidade para três barras e, consequentemente, aumentamos também as dimensões dos arrays temporários para três.
Na classe de indicador personalizado, não se sabe antecipadamente quantas barras são necessárias para seu cálculo no tick atual. Portanto, não podemos especificar antecipadamente o número necessário de barras, como feito no construtor da classe do indicador de fractais. Aqui, será necessário especificar esse número nos parâmetros do construtor da classe, passando e definindo através de uma variável de entrada:
//+------------------------------------------------------------------+ //| Custom indicator class | //+------------------------------------------------------------------+ class CIndCustom : public CIndMSTF { public: //--- Constructor CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe, const string path, // path to the indicator (for example, "Examples\\MACD.ex5") const string name, // name of the custom indicator const uint buffers, // number of indicator buffers const uint bars_to_calculate, // number of bars required to calculate the indicator on the current tick const MqlParam ¶m[] // array of custom indicator parameters ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe) { //--- If an empty array of parameters is passed, print this to log int total=(int)param.Size(); if(total==0) ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__); //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name) ResetLastError(); if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1) { //--- Reset data in the array and enter name (path to file and name of .ex5 file) ::ZeroMemory(this.m_param); //--- name of the custom indicator this.m_param[0].type=TYPE_STRING; this.m_param[0].string_value=path; //--- fill the array of indicator parameters for(int i=0;i<total;i++) { this.m_param[i+1].type=param[i].type; this.m_param[i+1].double_value=param[i].double_value; this.m_param[i+1].integer_value=param[i].integer_value; this.m_param[i+1].string_value=param[i].string_value; } //--- Create description of parameters //--- If non-current chart symbol or period, their descriptions are added to parameters bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); string param=(current ? "" : StringFormat("(%s)",symbol_period)); //--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName(name); this.SetDescription(name); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_CUSTOM; //--- Write a description of the first line buffer this.SetBufferDescription(0,this.m_title); //--- Set the number of bars required to calculate the indicator on the current tick this.SetBarsToCalculate(bars_to_calculate); } } };
Na classe-coleção de indicadores, no método de criação de um novo objeto de indicador personalizado, também é necessário especificar o número de barras para o cálculo do indicador:
int AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); int AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path, // path to the indicator (for example, "Examples\\MACD.ex5") const string name, // name of custom indicator (for example, "Custom MACD") const uint buffers, // number of buffers const uint bars_to_calculate,// number of bars required to calculate the indicator on the current tick const MqlParam ¶m[]); // Array of parameters //--- Timer
e no código de implementação desse método, passar o valor dessa variável para o construtor da classe do indicador personalizado:
//+------------------------------------------------------------------+ //| Add a custom indicator to the collection | //+------------------------------------------------------------------+ int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const uint bars_to_calculate,const MqlParam ¶m[]) { //--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,bars_to_calculate,param); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name); return INVALID_HANDLE; } //--- Return the result of adding the created indicator object to the collection list return this.AddNewIndicator(ind_obj,__FUNCTION__); }
Agora, as classes de indicadores multissímbolos e multiperíodos devem funcionar com indicadores de seta e com aqueles indicadores cujo buffer contém dados com interrupções — indicadores com estilos de desenho DRAW_ARROW, DRAW_COLOR_ARROW, DRAW_SECTION, DRAW_ZIGZAG, DRAW_COLOR_SECTION, e DRAW_COLOR_ZIGZAG.
Teste
Para testar, usaremos qualquer indicador criado anteriormente, como Parabolic SAR, já que ele também desenha setas. Ele tem um buffer de desenho, enquanto o indicador de fractais possui dois. Um buffer é usado para desenhar os fractais superiores, e o outro — para desenhar os fractais inferiores. Vamos aprimorar o código do indicador para que ele desenhe setas em dois buffers e salvá-lo com o nome TestMSTFFractals.mq5.
O indicador exibirá no gráfico os dados de dois indicadores de fractais ao mesmo tempo, já que um desenhará fractais do período atual do gráfico, e o outro plotará fractais do indicador Fractals, calculado no símbolo e período do gráfico especificados nas configurações. Além disso, para exibir os dados dos indicadores de fractais, será utilizada um painel informativo, que é usado em todos os indicadores dos artigos desta série:
//+------------------------------------------------------------------+ //| TestMSTFFractals.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 4 #property indicator_plots 4 //--- enums //--- plot FractalsUp1 #property indicator_label1 "FractalsUp1" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrGray #property indicator_width1 1 //--- plot FractalsDown1 #property indicator_label2 "FractalsDown1" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrGray #property indicator_width2 1 //--- plot FractalsUp2 #property indicator_label3 "FractalsUp2" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrDodgerBlue #property indicator_width3 1 //--- plot FractalsDown2 #property indicator_label4 "FractalsDown2" #property indicator_type4 DRAW_ARROW #property indicator_color4 clrDodgerBlue #property indicator_width4 1 //--- includes #include <IndMSTF\IndMSTF.mqh> #include <Dashboard\Dashboard.mqh> //--- input parameters input string InpSymbol = NULL; /* Symbol */ // Moving average symbol input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; /* Timeframe */ // Moving average timeframe input uchar InpArrowShift1 = 10; /* Senior period Arrow VShift */ // Shift the arrows vertically for the higher period input uchar InpArrowShift2 = 10; /* Junior period Arrow VShift */ // Shift the arrows vertically for the lower period input bool InpAsSeries = true; /* As Series flag */ // Timeseries flag of indicator buffer arrays //--- indicator buffers double BufferFractalsUp1[]; double BufferFractalsDn1[]; double BufferFractalsUp2[]; double BufferFractalsDn2[]; //--- global variables int handle_fractals1; int handle_fractals2; CMSTFIndicators indicators; // An instance of the indicator collection object //--- variables for the panel CDashboard *panel=NULL; // Pointer to the panel object int mouse_bar_index; // Index of the bar the data is taken from //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set a timer with an interval of 1 second EventSetTimer(1); //--- Assign the BufferFractalsUp1 and BufferFractalsDn1 arrays to the plot buffers 0 and 1, respectively SetIndexBuffer(0,BufferFractalsUp1,INDICATOR_DATA); SetIndexBuffer(1,BufferFractalsDn1,INDICATOR_DATA); //--- Assign the BufferFractalsUp2 and BufferFractalsDn2 arrays to the plot buffers 2 and 3, respectively SetIndexBuffer(2,BufferFractalsUp2,INDICATOR_DATA); SetIndexBuffer(3,BufferFractalsDn2,INDICATOR_DATA); //--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,217); PlotIndexSetInteger(1,PLOT_ARROW,218); PlotIndexSetInteger(2,PLOT_ARROW,217); PlotIndexSetInteger(3,PLOT_ARROW,218); //--- Set the vertical shift of the arrows PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-InpArrowShift1); PlotIndexSetInteger(1,PLOT_ARROW_SHIFT, InpArrowShift1); PlotIndexSetInteger(2,PLOT_ARROW_SHIFT,-InpArrowShift2); PlotIndexSetInteger(3,PLOT_ARROW_SHIFT, InpArrowShift2); //--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference) ArraySetAsSeries(BufferFractalsUp1,InpAsSeries); ArraySetAsSeries(BufferFractalsDn1,InpAsSeries); ArraySetAsSeries(BufferFractalsUp2,InpAsSeries); ArraySetAsSeries(BufferFractalsDn2,InpAsSeries); //--- Create two indicators of the same type //--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings handle_fractals1=indicators.AddNewFractals(NULL,PERIOD_CURRENT); handle_fractals2=indicators.AddNewFractals(InpSymbol,InpTimeframe); //--- If failed to create indicator handles, return initialization error if(handle_fractals1==INVALID_HANDLE || handle_fractals2==INVALID_HANDLE) return INIT_FAILED; //--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators indicators.SetPlotLabelFromBuffer(0,handle_fractals1,0); indicators.SetPlotLabelFromBuffer(1,handle_fractals1,1); indicators.SetPlotLabelFromBuffer(2,handle_fractals2,0); indicators.SetPlotLabelFromBuffer(3,handle_fractals2,1); //--- Dashboard //--- Create the panel int width=311; panel=new CDashboard(1,20,20,width,264); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,width/2-2); //--- Create a table with ID 1 to display the data of indicator 1 panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,2,2,18,width/2-2); //--- Create a table with ID 2 to display the data of indicator 2 panel.CreateNewTable(2); //--- Get the Y2 coordinate of the table with ID 1 and //--- set the Y1 coordinate for the table with ID 2 int y2=panel.TableY2(1)+3; //--- Draw a table with ID 2 on the background of the dashboard panel.DrawGrid(2,2,y2,3,2,18,width/2-2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the timer EventKillTimer(); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; //--- Delete all comments Comment(""); } //+------------------------------------------------------------------+ //| 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[]) { //--- Number of bars for calculation int limit=rates_total-prev_calculated; //--- If limit > 1, then this is the first calculation or change in the history if(limit>1) { //--- specify all the available history for calculation limit=rates_total-1; /* // If the indicator has any buffers that display other calculations (not multi-indicators), // initialize them here with the "empty" value set for these buffers */ } //--- Calculate all created multi-symbol multi-period indicators if(!indicators.Calculate()) return 0; //--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard DrawData(mouse_bar_index,time[mouse_bar_index]); //--- From buffers of calculated indicators, output data to indicator buffers if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,0,0,limit,BufferFractalsUp1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,1,0,limit,BufferFractalsDn1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,0,0,limit,BufferFractalsUp2)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,1,0,limit,BufferFractalsDn2)) return 0; //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Call the indicator collection timer indicators.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } } //+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlRates rates[1]; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Output the data of buffer 0 of indicator 1 from the specified bar into table 1 panel.DrawText(indicators.Title(handle_fractals1)+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value10=indicators.GetData(handle_fractals1,0,0,index); string value_str10=(value10!=EMPTY_VALUE ? DoubleToString(value10,indicators.Digits(handle_fractals1)) : " "); panel.DrawText(value_str10,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110); //--- Output the data of buffer 1 of indicator 1 from the specified bar into table 1 panel.DrawText(indicators.Title(handle_fractals1)+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); double value11=indicators.GetData(handle_fractals1,1,0,index); string value_str11=(value11!=EMPTY_VALUE ? DoubleToString(value11,indicators.Digits(handle_fractals1)) : " "); panel.DrawText(value_str11,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110); //--- Output the data of buffer 0 of indicator 2 from the specified bar into table 2 panel.DrawText(indicators.Title(handle_fractals2)+" Up", panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2); double value20=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,0,0,index); string value_str20=(value20!=EMPTY_VALUE ? DoubleToString(value20,indicators.Digits(handle_fractals2)) : " "); panel.DrawText(value_str20,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110); //--- Output the data of buffer 1 of indicator 2 from the specified bar into table 2 panel.DrawText(indicators.Title(handle_fractals2)+" Down", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2); double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,1,0,index); string value_str21=(value21!=EMPTY_VALUE ? DoubleToString(value21,indicators.Digits(handle_fractals2)) : " "); panel.DrawText(value_str21,panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); } //+------------------------------------------------------------------+
Vamos compilar o indicador e executá-lo no gráfico M1, depois mudar o período do gráfico para M5 e M15:
Vemos como os buffers do indicador no gráfico M1 são preenchidos com dados do indicador calculado em M5.
Ao mesmo tempo, ao mudar o gráfico para M15, vemos como todos os fractais do indicador calculado em M5 são desenhados nas barras de M15. Vemos que em cada barra do gráfico M15, onde há fractais, eles não deveriam estar lá, segundo o gráfico. Mas não é o caso, visto que em cada barra do gráfico M15 há três barras do período M5, e lá esses fractais existem, e é isso que é exibido nas barras do gráfico M15, pois o indicador foi criado para ser um indicador multissímbolo e multiperíodo.
Conclusão
Hoje criamos e testamos o funcionamento das classes de indicadores multissímbolos e multiperíodos com o estilo de desenho DRAW_ARROW. Nos próximos artigos, tentaremos entender os outros estilos de desenho que envolvem interrupções de dados no gráfico e completaremos os demais indicadores padrão ainda não abordados, para, desse modo, fechar o tema dos indicadores multissímbolos e multiperíodos.
Todos os arquivos utilizados no artigo, como classes de multi-indicadores, classe do painel e arquivo do indicador estão anexados ao artigo para teste e uso próprio.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/14105





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso