Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Métodos para trabalhar com indicadores padrão
- Teste
- O que vem agora?
Ideia
Acho que hoje não faremos introduções demoradas, já que todo mundo sabe o que é um indicador padrão do terminal. Sabe-se também que esses indicadores exibem no gráfico do símbolo/período atual os dados calculados para o mesmo símbolo/período.
Hoje começaremos a tornar possível criar indicadores personalizados que exibem no gráfico do símbolo/período atual os dados de todos os indicadores padrão disponíveis no terminal calculados para os símbolos/períodos especificados.
Neste artigo veremos como criar os métodos necessários para gerar um indicador personalizado com base no AC padrão (Accelerator Oscillator). Todos os métodos funcionarão para outros indicadores padrão só que com pequenas modificações. Nós os criaremos nos artigos a seguir.
Para criar e identificar objetos-buffers para trabalhar com os dados de indicadores padrão, adicionamos novas propriedades ao objeto-buffer:
- Identificador de conjuntos de buffers de um indicador: permite identificar e selecionar todos os objetos-buffers pertencentes ao indicador padrão que usa esses buffers. Num indicador personalizado podemos usar vários dos mesmos indicadores padrão, mas com parâmetros diferentes (quando criamos um indicador personalizado complexo baseado em alguns indicadores padrão). Este identificador permitirá identificar cada um dos objetos-buffers usados com base em se pertence ao indicador padrão.
- Identificador do indicador que usa um buffer: em cada objeto-buffer usado para calcular um indicador padrão, iremos escrever o identificador do indicador padrão criado a partir de qualquer objeto-buffer pertencente a tal indicador.
- Tipo de indicador que usa um buffer: aqui vamos inserir o tipo de indicador a partir de enumerações de tipos de indicadores ENUM_INDICATOR. Também permitirá identificar e selecionar objetos-buffers com base em se pertence ao tipo de indicador padrão.
- Nome do indicador que usa um buffer, isto é, aqui vamos armazenar o nome de um indicador padrão que usa um objeto-buffer para exibir sua descrição.
Além de criar uma base para trabalhar com os dados dos indicadores padrão, faremos uma pequena modificação na classe do objeto "Nova barra" e nas classes da série temporal para rastrear as barras de histórico ausentes e enviar o evento "Barras ausentes" para o programa.
Em caso de perda de conexão, de entrada e saída do sistema em modo de hibernação e de outros eventos anormais que requerem tempo de recuperação, após a retomada do programa podemos observar os dados ausentes no banco de dados da biblioteca, quer dizer, no momento em que não há conexão com o servidor, as barras de histórico são ignoradas e não são incluídas no banco de dados da biblioteca. Vamos criar métodos que rastreiem o número de barras ausentes e enviaremos o evento "Barras ausentes" para o programa para que o usuário possa tratar esta situação em seu programa.
Aprimorando as classes da biblioteca
Em primeiro lugar, completamos o arquivo \MQL5\Include\DoEasy\Datas.mqh com dados para exibir mensagens.
Inserimos os identificadores das novas mensagens:
MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT, // Index of the next drawn buffer MSG_LIB_TEXT_BUFFER_TEXT_ID, // Indicator buffer ID MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE, // Handle of an indicator using a buffer MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE, // Type of an indicator using a buffer MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME, // Buffer (timeframe) data period MSG_LIB_TEXT_BUFFER_TEXT_STATUS, // Buffer status MSG_LIB_TEXT_BUFFER_TEXT_TYPE, // Buffer type MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE, // Active MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE, // Arrow code MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT, // The vertical shift of the arrows MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN, // The number of initial bars that are not drawn and values in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE, // Graphical construction type MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA, // Display construction values in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_SHIFT, // Indicator graphical construction shift by time axis in bars MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE, // Line style MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH, // Line width MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE, // Arrow size MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM, // Number of colors MSG_LIB_TEXT_BUFFER_TEXT_COLOR, // Drawing color MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE, // Empty value for plotting where nothing will be drawn MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL, // Buffer symbol MSG_LIB_TEXT_BUFFER_TEXT_LABEL, // Name of the graphical indicator series displayed in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME, // Name of an indicator using a buffer
emensagens de texto correspondentes aos IDs recém-adicionados:
{"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"}, {"Идентификатор буферов индикатора","Indicator Buffer Id"}, {"Хэндл индикатора, использующего буфер","Indicator handle that uses buffer"}, {"Тип индикатора, использующего буфер","Indicator type that uses buffer"}, {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"}, {"Статус буфера","Buffer status"}, {"Тип буфера","Buffer type"}, {"Активен","Active"}, {"Код стрелки","Arrow code"}, {"Смещение стрелок по вертикали","Vertical shift of arrows"}, {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in DataWindow"}, {"Тип графического построения","Type of graphical construction"}, {"Отображение значений построения в окне DataWindow","Display construction values in DataWindow"}, {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along time axis in bars"}, {"Стиль линии отрисовки","Drawing line style "}, {"Толщина линии отрисовки","Thickness of drawing line"}, {"Размер значка стрелки","Arrow icon size"}, {"Количество цветов","Number of colors"}, {"Цвет отрисовки","Index of buffer containing drawing color"}, {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"}, {"Символ буфера","Buffer Symbol"}, {"Имя индикаторной графической серии, отображаемое в окне DataWindow","Name of indicator graphical series to display in DataWindow"}, {"Наименование индикатора, использующего буфер","Name of indicator that uses buffer"}, {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"}, {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"}, {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
No arquivo \MQL5\Include\DoEasy\Defines.mqh terminamos de escrever todas as adições necessárias para as tarefas realizadas hoje.
Na seção "Substituições de macro" alteramos o nome da constante que armazena o valor do número de tentativas de negociação por padrão para um mais informativo:
//+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ //--- Describe the function with the error line number #define DFUN_ERR_LINE (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ") #define DFUN (__FUNCTION__+": ") // "Function description" #define COUNTRY_LANG ("Russian") // Country language #define END_TIME (D'31.12.3000 23:59:59') // End date for account history data requests #define TIMER_FREQUENCY (16) // Minimal frequency of the library timer in milliseconds #define TOTAL_TRADE_TRY (5) // Default number of trading attempts #define IND_COLORS_TOTAL (64) // Maximum possible number of indicator buffer colors #define IND_BUFFERS_MAX (512) // Maximum possible number of indicator buffers //--- Standard sounds
Anteriormente, a constante era denominada TOTAL_TRY, o que não era muito informativo. Como no futuro poderemos ter mais algumas constantes indicando o número de tentativas, se adicionarmos ao nome a constante que indica a que ação pertence cada uma das tentativas (neste caso "TRADE" pertencente a uma tentativa de negociação), isso será mais informativo, e não precisaremos alterar o nome desta constante ao adicionar novas para diferentes "números de tentativas"
Na enumeração de possíveis eventos das séries temporais adicionamos um novo evento:
//+------------------------------------------------------------------+ //| List of possible timeseries events | //+------------------------------------------------------------------+ enum ENUM_SERIES_EVENT { SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE, // no event SERIES_EVENTS_NEW_BAR, // "New bar" event SERIES_EVENTS_MISSING_BARS, // "Bars skipped" event }; #define SERIES_EVENTS_NEXT_CODE (SERIES_EVENTS_MISSING_BARS+1) // Code of the next event after the "Bars skipped" event //+------------------------------------------------------------------+
Assim, o código do próximo evento agora será baseado numa nova constante.
Já falamos sobre adicionar novas propriedades ao objeto-buffer. Vamos escrevê-los nas enumerações de propriedades inteiras e de string do objeto-buffer:
//+------------------------------------------------------------------+ //| Buffer integer properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_INTEGER { BUFFER_PROP_INDEX_PLOT = 0, // Plotted buffer serial number BUFFER_PROP_STATUS, // Buffer status (by drawing style) from the ENUM_BUFFER_STATUS enumeration BUFFER_PROP_TYPE, // Buffer type (from the ENUM_BUFFER_TYPE enumeration) BUFFER_PROP_TIMEFRAME, // Buffer period data (timeframe) BUFFER_PROP_ACTIVE, // Buffer usage flag BUFFER_PROP_DRAW_TYPE, // Graphical construction type (from the ENUM_DRAW_TYPE enumeration) BUFFER_PROP_ARROW_CODE, // Arrow code for DRAW_ARROW style BUFFER_PROP_ARROW_SHIFT, // The vertical shift of the arrows for DRAW_ARROW style BUFFER_PROP_LINE_STYLE, // Line style BUFFER_PROP_LINE_WIDTH, // Line width BUFFER_PROP_DRAW_BEGIN, // The number of initial bars that are not drawn and values in DataWindow BUFFER_PROP_SHOW_DATA, // Flag of displaying construction values in DataWindow BUFFER_PROP_SHIFT, // Indicator graphical construction shift by time axis in bars BUFFER_PROP_COLOR_INDEXES, // Number of colors BUFFER_PROP_COLOR, // Drawing color BUFFER_PROP_INDEX_BASE, // Base data buffer index BUFFER_PROP_INDEX_NEXT_BASE, // Index of the array to be assigned as the next indicator buffer BUFFER_PROP_INDEX_NEXT_PLOT, // Index of the next drawn buffer BUFFER_PROP_ID, // ID of multiple buffers of a single indicator BUFFER_PROP_IND_HANDLE, // Handle of an indicator using a buffer BUFFER_PROP_IND_TYPE, // Type of an indicator using a buffer BUFFER_PROP_NUM_DATAS, // Number of data buffers BUFFER_PROP_INDEX_COLOR, // Color buffer index }; #define BUFFER_PROP_INTEGER_TOTAL (23) // Total number of integer bar properties #define BUFFER_PROP_INTEGER_SKIP (2) // Number of buffer properties not used in sorting //+------------------------------------------------------------------+ //| Buffer real properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_DOUBLE { BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL, // Empty value for plotting where nothing will be drawn }; #define BUFFER_PROP_DOUBLE_TOTAL (1) // Total number of real buffer properties #define BUFFER_PROP_DOUBLE_SKIP (0) // Number of buffer properties not used in sorting //+------------------------------------------------------------------+ //| Buffer string properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_STRING { BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Buffer symbol BUFFER_PROP_LABEL, // Name of the graphical indicator series displayed in DataWindow BUFFER_PROP_IND_NAME, // Name of an indicator using a buffer }; #define BUFFER_PROP_STRING_TOTAL (3) // Total number of string buffer properties //+------------------------------------------------------------------+
Aumentamos o número total de propriedades inteiras de 20 para 23 e propriedades de string de 2 para 3.
Consequentemente, como adicionamos novas propriedades, precisamos adicionar a capacidade de classificar e selecionar com base nessas propriedades.
Na enumeração de possíveis critérios de classificação adicionamos novos tipos de classificação para os objetos-buffers:
//+------------------------------------------------------------------+ //| Possible buffer sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BUFFER_DBL_PROP (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP) #define FIRST_BUFFER_STR_PROP (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP) enum ENUM_SORT_BUFFER_MODE { //--- Sort by integer properties SORT_BY_BUFFER_INDEX_PLOT = 0, // Sort by the plotted buffer serial number SORT_BY_BUFFER_STATUS, // Sort by buffer drawing style (status) from the ENUM_BUFFER_STATUS enumeration SORT_BY_BUFFER_TYPE, // Sort by buffer type (from the ENUM_BUFFER_TYPE enumeration) SORT_BY_BUFFER_TIMEFRAME, // Sort by the buffer data period (timeframe) SORT_BY_BUFFER_ACTIVE, // Sort by the buffer usage flag SORT_BY_BUFFER_DRAW_TYPE, // Sort by graphical construction type (from the ENUM_DRAW_TYPE enumeration) SORT_BY_BUFFER_ARROW_CODE, // Sort by the arrow code for DRAW_ARROW style SORT_BY_BUFFER_ARROW_SHIFT, // Sort by the vertical shift of the arrows for DRAW_ARROW style SORT_BY_BUFFER_LINE_STYLE, // Sort by the line style SORT_BY_BUFFER_LINE_WIDTH, // Sort by the line width SORT_BY_BUFFER_DRAW_BEGIN, // Sort by the number of initial bars that are not drawn and values in DataWindow SORT_BY_BUFFER_SHOW_DATA, // Sort by the flag of displaying construction values in DataWindow SORT_BY_BUFFER_SHIFT, // Sort by the indicator graphical construction shift by time axis in bars SORT_BY_BUFFER_COLOR_INDEXES, // Sort by a number of attempts SORT_BY_BUFFER_COLOR, // Sort by the drawing color SORT_BY_BUFFER_INDEX_BASE, // Sort by the basic data buffer index SORT_BY_BUFFER_INDEX_NEXT_BASE, // Sort by the index of the array to be assigned as the next indicator buffer SORT_BY_BUFFER_INDEX_NEXT_PLOT, // Sort by the index of the next drawn buffer SORT_BY_BUFFER_ID, // Sort by ID of multiple buffers of a single indicator SORT_BY_BUFFER_IND_HANDLE, // Sort by handle of an indicator using a buffer SORT_BY_BUFFER_IND_TYPE, // Sort by type of an indicator using a buffer //--- Sort by real properties SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP, // Sort by the empty value for plotting where nothing will be drawn //--- Sort by string properties SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP, // Sort by the buffer symbol SORT_BY_BUFFER_LABEL, // Sort by the name of the graphical indicator series displayed in DataWindow SORT_BY_BUFFER_IND_NAME, // Sort by name of an indicator using a buffer }; //+------------------------------------------------------------------+
Para fixar as barras ausentes em caso de, por exemplo, perda e restauração da conexão, modificaremos ligeiramente a classe do objeto "Nova barra" no arquivo \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh. Tudo o que precisamos fazer é adicionar uma contagem do número barras que passaram entre dois eventos "Nova barra". Se o valor calculado for maior que 1, será porque as barras de histórico foram ignoradas ou não estão presentes no servidor (ainda não consideramos esta situação).
À seção privada da classe adicionamos quatro novas variáveis-membros de classe para armazenar a hora do evento "Nova barra" anterior para gerenciamento de tempo manual e automático, para armazenamento do número de segundos e de barrasentre dois eventos "Nova barra"
//+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj : public CBaseObj { private: string m_symbol; // Symbol ENUM_TIMEFRAMES m_timeframe; // Timeframe datetime m_new_bar_time; // New bar time for auto time management datetime m_prev_time; // Previous time for auto time management datetime m_new_bar_time_manual; // New bar time for manual time management datetime m_prev_time_manual; // Previous time for manual time management datetime m_prev_new_bar_time; // Previous new bar time for auto time management datetime m_prev_new_bar_time_manual; // Previous new bar time for manual time management long m_seconds_between; // Number of seconds between two "New bar" events int m_bars_between; // Number of bars between two "New bar" events //--- Return the current bar data datetime GetLastBarDate(const datetime time); public:
Na seção pública da classe renomeamos os métodos para definir e retornar o período gráfico do objeto (anteriormente era usado "Period", já agora usar "Timeframe" para armazenar o período gráfico é mais informativo), e adicionar métodos para retornar valores de variáveis recém-declaradas:
public: //--- Set (1) symbol and (2) timeframe void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } void SetTimeframe(const ENUM_TIMEFRAMES timeframe){ this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } //--- Return (1) symbol and (2) timeframe string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } //--- Return (1) new bar time, (2) previous new bar time, number of (3) seconds, (4) number of bars between the two last events datetime TimeNewBar(void) const { return this.m_new_bar_time; } datetime TimePrevNewBar(void) const { return this.m_prev_new_bar_time; } long SecondsBetweenNewBars(void) const { return this.m_seconds_between; } int BarsBetweenNewBars(void) const { return this.m_bars_between; } //--- Return the new bar opening flag during the time (1) auto, (2) manual management bool IsNewBar(const datetime time); bool IsNewBarManual(const datetime time); //--- Constructors CNewBarObj(void) : m_symbol(::Symbol()), m_timeframe((ENUM_TIMEFRAMES)::Period()), m_prev_time(0),m_new_bar_time(0), m_prev_time_manual(0),m_new_bar_time_manual(0) {} CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe); }; //+------------------------------------------------------------------+
Na lista de inicialização do construtor paramétrico da classe, definimos os valores de inicialização para o número de segundos e barras,
e inicializamos as novas variáveis restantes com zero no corpo do construtor:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol), m_timeframe(timeframe), m_seconds_between(0), m_bars_between(0) { this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0; } //+------------------------------------------------------------------+
No método que retorna ao sinalizador de abertura de nova barra durante o controle automático de tempo (em particular, quando é gerada uma nova barra), armazenamos o tempo da barra anterior e fazemos o cálculo do número de segundos e barras entre dois eventos "Nova barra":
//+------------------------------------------------------------------+ //| Return new bar opening flag | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBar(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); if(tm<=0) return false; //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time+this.m_new_bar_time==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time=this.m_prev_time=tm; return false; } //--- If the previous time is less than the current bar open time, this is a new bar if(this.m_prev_time>0 && this.m_prev_time<tm) { this.m_prev_new_bar_time=this.m_prev_time; this.m_seconds_between=tm-m_prev_time; this.m_bars_between=int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe)); //--- set the new bar opening time, //--- set the previous time as the current one and return 'true' this.m_new_bar_time=this.m_prev_time=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
Os dados não precisam ser calculados no método que retorna o sinalizador de nova barra durante o controle manual, uma vez que os dados das barras ausentes serão sempre calculados automaticamente. Porém, neste método vamos armazenar o tempo da "Nova barra" anterior durante a controle manual e corrigimos o erro de atribuir o tempo da nova barra (anteriormente na variável era armazenado o tempo para controle automático de tempo):
//+------------------------------------------------------------------+ //| Return the new bar opening flag during the manual management | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBarManual(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); if(tm<=0) return false; //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time_manual+this.m_new_bar_time_manual==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time_manual=this.m_prev_time_manual=tm; return false; } //--- If the previous time is less than the current bar open time, this is a new bar if(this.m_prev_time_manual>0 && this.m_prev_time_manual<tm) { this.m_prev_new_bar_time_manual=this.m_prev_time_manual; //--- set the new bar opening time and return 'true' //--- Save the previous time as the current one from the program using the SaveNewBarTime() method //--- Till the previous time is forcibly set as the current one from the program, //--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar. this.m_new_bar_time_manual=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
Muitas vezes, podemos ver no log do terminal as entradas vindas da biblioteca que indicam um erro sobre o recebimento das barras do histórico. Isso ocorre porque a biblioteca percorre todo o histórico, mesmo quando não há dados históricos de um símbolo específico. É criada uma entrada sobre isso e é realizada a transição para a próxima barra do histórico. É feito assim para permitir a depuração de métodos de biblioteca ao trabalhar com séries temporais. Mas onde definitivamente não for necessário visualizar os erros de obtenção de dados históricos, removeremos esses registros. Para fazer isso, precisamos na classe de objeto "Barra", no arquivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh escrever mais um construtor — sem parâmetros:
//+------------------------------------------------------------------+ //| Bar class | //+------------------------------------------------------------------+ class CBar : public CBaseObj { private: MqlDateTime m_dt_struct; // Date structure int m_digits; // Symbol's digits value string m_period_description; // Timeframe string description long m_long_prop[BAR_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[BAR_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[BAR_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the bar's (1) double and (2) string properties are located at int IndexProp(ENUM_BAR_PROP_DOUBLE property) const { return(int)property-BAR_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_BAR_PROP_STRING property) const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL; } //--- Return the bar type (bullish/bearish/zero) ENUM_BAR_BODY_TYPE BodyType(void) const; //--- Calculate and return the size of (1) candle, (2) candle body, //--- (3) upper, (4) lower candle wick, //--- (5) candle body top and (6) bottom double CandleSize(void) const { return(this.High()-this.Low()); } double BodySize(void) const { return(this.BodyHigh()-this.BodyLow()); } double ShadowUpSize(void) const { return(this.High()-this.BodyHigh()); } double ShadowDownSize(void) const { return(this.BodyLow()-this.Low()); } double BodyHigh(void) const { return ::fmax(this.Close(),this.Open()); } double BodyLow(void) const { return ::fmin(this.Close(),this.Open()); } //--- Return the (1) year and (2) month the bar belongs to, (3) week day, //--- (4) bar serial number in a year, (5) day, (6) hour, (7) minute, int TimeYear(void) const { return this.m_dt_struct.year; } int TimeMonth(void) const { return this.m_dt_struct.mon; } int TimeDayOfWeek(void) const { return this.m_dt_struct.day_of_week; } int TimeDayOfYear(void) const { return this.m_dt_struct.day_of_year; } int TimeDay(void) const { return this.m_dt_struct.day; } int TimeHour(void) const { return this.m_dt_struct.hour; } int TimeMinute(void) const { return this.m_dt_struct.min; } public: //--- Set bar's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string bar properties from the properties array long GetProperty(ENUM_BAR_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_BAR_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_BAR_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of the bar supporting the property virtual bool SupportProperty(ENUM_BAR_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_STRING property) { return true; } //--- Return itself CBar *GetObject(void) { return &this;} //--- 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); //--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CBar objects by all properties (to search for equal bar objects) bool IsEqual(CBar* compared_bar) const; //--- 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); //+------------------------------------------------------------------+
Além disso, ao criar listas de séries temporais segundo símbolos, usaremos esse mesmo construtor para criar um novo objeto-barra pertencente à série de tempo de símbolo especificada. Anteriormente, os construtores paramétricos tentavam obter independentemente os dados necessários do objeto-barra recém-criado no histórico e, se o histórico fosse recuperado do construtor, uma entrada de depuração era gravada no log. Um construtor simples sem parâmetros criará um objeto-barra vazio que precisaremos preencher com os dados após ter sido criado com sucesso. Isso acontecerá nos métodos da classe CSeriesDE.
Vamos considerar as mudanças que precisam ser feitas na listagem desta classe no arquivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh.
À seção pública da classe adicionamos um método que retorna um ponteiro para um objeto da classe "Nova barra"pertencente à série temporal desta classe:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol string m_period_description; // Timeframe string description datetime m_firstdate; // The very first date by a period symbol at the moment datetime m_lastbar_date; // Time of opening the last bar by period symbol uint m_amount; // Amount of applied timeseries data uint m_required; // Required amount of applied timeseries data uint m_bars; // Number of bars in history by symbol and timeframe bool m_sync; // Synchronized data flag CArrayObj m_list_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object //--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol void SetServerDate(void) { this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE); this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE); } public: //--- Return (1) itself, (2) timeseries list, (3) timeseries "New bar" object CSeriesDE *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &m_list_series; } CNewBarObj *GetNewBarObj(void) { return &this.m_new_bar_obj; } //--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition
Como agora temos dois eventos de série temporal (nova barra e barras ausentes), o método de criação e envio de evento de série temporal para o gráfico do programa de controle precisa ser melhorado. Na declaração do método, escreveremos um parâmetro de entrada no qual passaremos o evento de série temporal a ser criado e enviado:
//--- Create and send the timeseries event to the control program chart void SendEvent(ENUM_SERIES_EVENT event);
E vamos modificar o próprio método localizado fora do corpo da classe:
//+------------------------------------------------------------------+ //| Create and send the timeseries event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeriesDE::SendEvent(ENUM_SERIES_EVENT event) { if(event==SERIES_EVENTS_NEW_BAR) { 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()); } else if(event==SERIES_EVENTS_MISSING_BARS) { ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_MISSING_BARS,this.m_new_bar_obj.BarsBetweenNewBars(),this.Timeframe(),this.Symbol()); } } //+------------------------------------------------------------------+
Aqui: dependendo do valor passado para o método criamos o evento necessário e o enviamos para o gráfico do programa de controle. Se o evento "Barras ausentes" for criado, no valor lparam da função EventChartCustom() transferimos a quantidade de barras de histórico ausentes.
Para nos livrarmos de mensagens desnecessárias sobre erros de recebimento de dados históricos no log, precisamos modificar o método que retorna um objeto-barra com base no tempo na série temporal:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(); if(obj==NULL) return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; return this.m_list_series.At(index); } //+------------------------------------------------------------------+
Como agora temos um construtor sem parâmetros na classe CBar, para encontrar a barra necessária usaremos a criação de um novo objeto-barra com ajuda deste construtor.
Aqui apenas criamos um objeto-barra vazio temporário, definimos o símbolo necessário, período gráfico e tempo de barra para este objeto.
Depois, simplesmente classificamos a lista de objetos-barras com base no tempo e procuramos na lista de objetos-narras um objeto cujos dados coincidam com os que definimos para o objeto-barra temporário criado.
O método Search() retorna o índice do objeto encontrado na lista, e o método At() retorna um ponteiro para o objeto de acordo com o índice. Se o objeto não for encontrado, o índice terá um valor de -1, enquanto o método At() retornará o valor NULL.
Pegamos eventos de novas barras e, agora, eventos de barras ausentes nos métodos de atualização de todas as séries temporais disponíveis da classe CTimeSeriesDE
no arquivo \MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh.
Complementamos os dois métodos para atualizar séries temporais com blocos de código para definir os eventos "Barras ausentes":
//+------------------------------------------------------------------+ //| 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); datetime time= ( this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? data_calculate.rates.time : series_obj.LastBarDate() ); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(time)) { //--- send the "New bar" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_NEW_BAR); //--- 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,time,series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; //--- Check skipped bars int missing=series_obj.GetNewBarObj().BarsBetweenNewBars(); if(missing>1) { //--- send the "Bars skipped" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS); //--- add the "Bars skipped" event to the list of timeseries events this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol()); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Update all timeseries lists | //+------------------------------------------------------------------+ void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate) { //--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal //--- and the timeseries event flag, and clear the list of all timeseries events bool upd=false; this.m_is_event=false; this.m_list_events.Clear(); //--- In the loop by the list of all used timeseries, int total=this.m_list_series.Total(); for(int i=0;i<total;i++) { //--- get the next timeseries object by the loop index CSeriesDE *series_obj=this.m_list_series.At(i); if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0) continue; //--- update the timeseries list series_obj.Refresh(data_calculate); datetime time= ( this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? data_calculate.rates.time : series_obj.LastBarDate() ); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(time)) { //--- send the "New bar" event to the control program chart, series_obj.SendEvent(SERIES_EVENTS_NEW_BAR); //--- set the flag indicating the necessity to set the first date in history on the server and in the terminal upd=true; //--- 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,time,series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; //--- Check skipped bars int missing=series_obj.GetNewBarObj().BarsBetweenNewBars(); if(missing>1) { //--- send the "Bars skipped" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS); //--- add the "Bars skipped" event to the list of timeseries events this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol()); } } } //--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled, //--- set the values of the first date in history on the server and in the terminal if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
Aqui:no momento em que o evento "Nova barra" é definido chamamos o método alterado anteriormente para criar um novo evento de série temporal, para o qual transferimos o evento "nova barra", e, em seguida, se houver barras ausentes, criaremos tal evento.
Na seção pública da classe-coleção de objetos de todas as séries temporais CTimeSeriesCollection contida no arquivo \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh, adicionamos a declaração do método para recriar todas as séries temporais:
//--- (1) Create, (2) re-create a specified timeseries of a specified symbol, (3) re-create all timeseries bool CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); bool ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); bool ReCreateSeriesAll(const int rates_total=0,const uint required=0); //--- Return (1) an empty, (2) partially filled timeseries
Fora do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Re-create all timeseries | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::ReCreateSeriesAll(const int rates_total=0,const uint required=0) { //--- In the loop by all symbol timeseries objects in the collection, int total=this.m_list.Total(); for(int i=0;i<total;i++) { //--- get the next symbol timeseries object CTimeSeriesDE *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; //--- Get the list of all symbol timeseries CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; //--- In a loop by all symbol timeseries int total_series=list.Total(); for(int j=0;j<total_series;j++) { //--- Get the next timeseries CSeriesDE *series=list.At(j); if(series==NULL) continue; //--- check timeseries synchronization and re-create it if(!series.SyncData(required,rates_total)) return false; if(series.Create(required)==0) return false; } } return true; } //+------------------------------------------------------------------+
O método simplesmente recria todas as séries temporais disponíveis na coleção. Até agora, esse método não é usado, mas pode ser útil no futuro se for necessário recriar as coleções existentes de séries temporais. Por exemplo, pode ser necessário ao detectar um grande número de barras ausentes quando o programa usa muitos símbolos/pontos. Quando isso acontece, é mais fácil recriar todas as séries temporais da coleção chamando um método, do que determinar o número de barras ausentes em cada série temporal e recriar cada uma separadamente. Além disso, isso só acontecerá quando a conexão com o servidor for restaurada ou quando aparecer uma nova barra.
Concluímos todas as etapas preparatórias, modificamos ligeiramente o trabalho com séries temporais e barras. É hora de começar a criar métodos para trabalhar com indicadores padrão.
Métodos para trabalhar com indicadores padrão
Em primeiro lugar, finalizamos a classe de objeto do buffer abstrato no arquivo \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh.
À seção pública da classe adicionamos os métodos para definição e retorno das quatro novas propriedades do objeto-buffer:
//--- Set (1) the arrow code, (2) vertical shift of arrows, (3) symbol, (4) timeframe, (5) buffer activity flag //--- (6) drawing type, (7) number of initial bars without drawing, (8) flag of displaying construction values in DataWindow, //--- (9) shift of the indicator graphical construction along the time axis, (10) line style, (11) line width, //--- (12) total number of colors, (13) one drawing color, (14) color of drawing in the specified color index, //--- (15) drawing colors from the color array, (16) empty value, (17) name of the graphical series displayed in DataWindow virtual void SetArrowCode(const uchar code) { return; } virtual void SetArrowShift(const int shift) { return; } void SetSymbol(const string symbol) { this.SetProperty(BUFFER_PROP_SYMBOL,symbol); } void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe); } void SetActive(const bool flag) { this.SetProperty(BUFFER_PROP_ACTIVE,flag); } void SetDrawType(const ENUM_DRAW_TYPE draw_type); void SetDrawBegin(const int value); void SetShowData(const bool flag); void SetShift(const int shift); void SetStyle(const ENUM_LINE_STYLE style); void SetWidth(const int width); void SetColorNumbers(const int number); void SetColor(const color colour); void SetColor(const color colour,const uchar index); void SetColors(const color &array_colors[]); void SetEmptyValue(const double value); virtual void SetLabel(const string label); void SetID(const int id) { this.SetProperty(BUFFER_PROP_ID,id); } void SetIndicatorHandle(const int handle) { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle); } void SetIndicatorType(const ENUM_INDICATOR type) { this.SetProperty(BUFFER_PROP_IND_TYPE,type); } void SetIndicatorName(const string name) { this.SetProperty(BUFFER_PROP_IND_NAME,name); } //--- Return (1) the serial number of the drawn buffer, (2) bound array index, (3) color buffer index, //--- (4) index of the first free bound array, (5) index of the next drawn buffer, (6) buffer data period, (7) buffer status, //--- (8) buffer type, (9) buffer usage flag, (10) arrow code, (11) arrow shift for DRAW_ARROW style, //--- (12) number of initial bars that are not drawn and values in DataWindow, (13) graphical construction type, //--- (14) flag of displaying construction values in DataWindow, (15) indicator graphical construction shift along the time axis, //--- (16) drawing line style, (17) drawing line width, (18) number of colors, (19) drawing color, number of buffers for construction //--- (20) set empty value, (21) buffer symbol, (22) name of the indicator graphical series displayed in DataWindow int IndexPlot(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT); } int IndexBase(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE); } int IndexColor(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR); } int IndexNextBaseBuffer(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE); } int IndexNextPlotBuffer(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME); } ENUM_BUFFER_STATUS Status(void) const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS); } ENUM_BUFFER_TYPE TypeBuffer(void) const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE); } bool IsActive(void) const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE); } uchar ArrowCode(void) const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE); } int ArrowShift(void) const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT); } int DrawBegin(void) const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN); } ENUM_DRAW_TYPE DrawType(void) const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE); } bool IsShowData(void) const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA); } int Shift(void) const { return (int)this.GetProperty(BUFFER_PROP_SHIFT); } ENUM_LINE_STYLE LineStyle(void) const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE); } int LineWidth(void) const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH); } int ColorsTotal(void) const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES); } color Color(void) const { return (color)this.GetProperty(BUFFER_PROP_COLOR); } int BuffersTotal(void) const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS); } double EmptyValue(void) const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE); } string Symbol(void) const { return this.GetProperty(BUFFER_PROP_SYMBOL); } string Label(void) const { return this.GetProperty(BUFFER_PROP_LABEL); } int ID(void) const { return (int)this.GetProperty(BUFFER_PROP_ID); } int IndicatorHandle(void) const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE); } ENUM_INDICATOR IndicatorType(void) const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE); } string IndicatorName(void) const { return this.GetProperty(BUFFER_PROP_IND_NAME); } int IndicatorBarsCalculated(void) const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}
No construtor da classe definimos valores padrão para as novas propriedades:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status, ENUM_BUFFER_TYPE buffer_type, const uint index_plot, const uint index_base_array, const int num_datas, const uchar total_arrays, const int width, const string label) { this.m_type=COLLECTION_BUFFERS_ID; this.m_act_state_trigger=true; this.m_total_arrays=total_arrays; //--- Save integer properties this.m_long_prop[BUFFER_PROP_STATUS] = buffer_status; this.m_long_prop[BUFFER_PROP_TYPE] = buffer_type; this.m_long_prop[BUFFER_PROP_ID] = WRONG_VALUE; this.m_long_prop[BUFFER_PROP_IND_HANDLE] = INVALID_HANDLE; this.m_long_prop[BUFFER_PROP_IND_TYPE] = WRONG_VALUE; ENUM_DRAW_TYPE type= ( !this.TypeBuffer() || !this.Status() ? DRAW_NONE : this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING : ENUM_DRAW_TYPE(this.Status()+8) ); this.m_long_prop[BUFFER_PROP_DRAW_TYPE] = type; this.m_long_prop[BUFFER_PROP_TIMEFRAME] = PERIOD_CURRENT; this.m_long_prop[BUFFER_PROP_ACTIVE] = true; this.m_long_prop[BUFFER_PROP_ARROW_CODE] = 0x9F; this.m_long_prop[BUFFER_PROP_ARROW_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_DRAW_BEGIN] = 0; this.m_long_prop[BUFFER_PROP_SHOW_DATA] = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false); this.m_long_prop[BUFFER_PROP_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_LINE_STYLE] = STYLE_SOLID; this.m_long_prop[BUFFER_PROP_LINE_WIDTH] = width; this.m_long_prop[BUFFER_PROP_COLOR_INDEXES] = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0); this.m_long_prop[BUFFER_PROP_COLOR] = clrRed; this.m_long_prop[BUFFER_PROP_NUM_DATAS] = num_datas; this.m_long_prop[BUFFER_PROP_INDEX_PLOT] = index_plot; this.m_long_prop[BUFFER_PROP_INDEX_BASE] = index_base_array; this.m_long_prop[BUFFER_PROP_INDEX_COLOR] = this.GetProperty(BUFFER_PROP_INDEX_BASE)+ (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0); this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE] = index_base_array+this.m_total_arrays; this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot); //--- Save real properties this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0); //--- Save string properties this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)] = ::Symbol(); this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL); this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)] = NULL; //--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string
Esses valores para essas novas propriedades pertencerão a objetos-buffers que não funcionam com indicadores padrão. Se criarmos um objeto-buffer pertencente ao indicador padrão, esses parâmetros serão preenchidos pela biblioteca, faremos isso a seguir.
Num método que retorna uma descrição da propriedade inteira do buffer, adicionamos saída de descrições para novas propriedades inteiras:
//+------------------------------------------------------------------+ //| Return description of a buffer's integer property | //+------------------------------------------------------------------+ string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property) { return ( property==BUFFER_PROP_INDEX_PLOT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_STATUS ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetStatusDescription() ) : property==BUFFER_PROP_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTypeBufferDescription() ) : property==BUFFER_PROP_TIMEFRAME ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTimeframeDescription() ) : property==BUFFER_PROP_ACTIVE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetActiveDescription() ) : property==BUFFER_PROP_DRAW_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetDrawTypeDescription() ) : property==BUFFER_PROP_ARROW_CODE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_ARROW_SHIFT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_LINE_STYLE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetLineStyleDescription() ) : property==BUFFER_PROP_LINE_WIDTH ? (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) : CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_DRAW_BEGIN ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_SHOW_DATA ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetShowDataDescription() ) : property==BUFFER_PROP_SHIFT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_COLOR_INDEXES ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_COLOR ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_BASE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_NEXT_BASE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_NEXT_PLOT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_ID ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_IND_HANDLE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_IND_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_NUM_DATAS ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_COLOR ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetColorsDescription() ) : "" ); } //+------------------------------------------------------------------+
No método que retorna a descrição da propriedade de string do buffer, adicionamos a impressão de descrição para a nova propriedade da string:
//+------------------------------------------------------------------+ //| Return description of a buffer's string property | //+------------------------------------------------------------------+ string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property) { return ( property==BUFFER_PROP_SYMBOL ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.Symbol() ) : property==BUFFER_PROP_LABEL ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"") ) : property==BUFFER_PROP_IND_NAME ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"") ) : "" ); } //+------------------------------------------------------------------+
Fazemos alterações nos métodos para definir o valor vazio e o nome da série gráfica. Anteriormente, esses valores não eram definidos para o buffer calculado. Vamos fazer com que para o buffer calculado os valores sejam definidos apenas nas propriedades do objeto-buffer,
já para o buffer desenhado, isso deve ser feito nas propriedades do objeto e nas propriedades do buffer em si mesmo:
//+------------------------------------------------------------------+ //| Set the "empty" value for construction | //| without drawing | //+------------------------------------------------------------------+ void CBuffer::SetEmptyValue(const double value) { this.SetProperty(BUFFER_PROP_EMPTY_VALUE,value); if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,value); } //+------------------------------------------------------------------+ //| Set the indicator graphical series name | //+------------------------------------------------------------------+ void CBuffer::SetLabel(const string label) { this.SetProperty(BUFFER_PROP_LABEL,label); if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,label); } //+------------------------------------------------------------------+
Aos métodos que retornam valores pelo índice da série temporal, adicionamos controle do valor de índice menor que zero:
//+------------------------------------------------------------------+ //| Return the value from the specified timeseries index | //| of the specified data buffer array | //+------------------------------------------------------------------+ double CBuffer::GetDataBufferValue(const uint buffer_index,const int series_index) const { int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index); int data_total=this.GetDataTotal(correct_buff_index); if(data_total==0 || series_index<0) return this.EmptyValue(); int data_index=((int)series_index<data_total ? (int)series_index : data_total-1); return this.DataBuffer[correct_buff_index].Array[data_index]; } //+------------------------------------------------------------------+ //| Return the color index value from the specified timeseries index | //| of the specified color buffer array | //+------------------------------------------------------------------+ int CBuffer::GetColorBufferValueIndex(const int series_index) const { int data_total=this.GetDataTotal(0); if(data_total==0 || series_index<0) return WRONG_VALUE; int data_index=((int)series_index<data_total ? (int)series_index : data_total-1); return(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]); } //+------------------------------------------------------------------+ //| Return the color value from the specified timeseries index | //| of the specified color buffer array | //+------------------------------------------------------------------+ color CBuffer::GetColorBufferValueColor(const int series_index) const { int data_total=this.GetDataTotal(0); if(data_total==0 || series_index<0) return clrNONE; int color_index=this.GetColorBufferValueIndex(series_index); return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE); } //+------------------------------------------------------------------+
Assim, se para o método for passado um índice incorreto, o método simplesmente sairá com o retorno de um valor "vazio", que é diferente para cada um dos métodos.
Agora modificamos a classe do objeto de buffer calculado no arquivo \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh.
Os métodos - que retornam um sinalizador que indica que o objeto-buffer suporta uma propriedade real e de string - sempre retornavam anteriormente false , ou seja, o buffer calculado não oferece suporte a propriedades desse tipo. Vamos torná-lo compatível com cada uma dessas propriedades. Já no método que retorna um sinalizador que indica que o objeto suporta propriedades inteiras, adicionamos novas propriedades inteiras para suportar seu objeto de buffer calculado:
//+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property) { if( property==BUFFER_PROP_INDEX_PLOT || property==BUFFER_PROP_STATUS || property==BUFFER_PROP_TYPE || property==BUFFER_PROP_INDEX_BASE || property==BUFFER_PROP_ID || property==BUFFER_PROP_IND_HANDLE || property==BUFFER_PROP_IND_TYPE || property==BUFFER_PROP_INDEX_NEXT_BASE ) return true; return false; } //+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property) { return true; } //+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| string property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property) { return true; } //+------------------------------------------------------------------+
O facto de o objeto suportar o buffer calculado de todas as propriedades reais e de string é uma solução temporária rápida para a criação de métodos para trabalhar com buffers que funcionam com indicadores padrão e, posteriormente, removeremos alguns deles da lista de propriedades suportadas.
Realizamos todo o trabalho com buffers de indicador para indicadores padrão na classe-coleção CBuffersCollection de buffers de indicador
no arquivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.
Hoje vamos criar e acompanhar buffers de indicador multiperíodos e multissímbolos para o indicador AC padrão (Accelerator Oscillator). Nos artigos seguintes, com base na funcionalidade testada, adicionaremos a capacidade de criar e trabalhar com outros indicadores padrão.
Todos os objetos-buffers que trabalham com indicadores padrão necessariamente receberão um identificador ao serem criados, e por meio deste último podemos encontrar os buffers necessários.
Na seção pública da classe declaramos o método que retorna uma lista de objetos-buffers que tem este identificador:
//+------------------------------------------------------------------+ //| Collection of indicator buffers | //+------------------------------------------------------------------+ class CBuffersCollection : public CObject { private: CListObj m_list; // Buffer object list CTimeSeriesCollection *m_timeseries; // Pointer to the timeseries collection object //--- Return the index of the (1) last, (2) next drawn and (3) basic buffer int GetIndexLastPlot(void); int GetIndexNextPlot(void); int GetIndexNextBase(void); //--- Create a new buffer object and place it to the collection list bool CreateBuffer(ENUM_BUFFER_STATUS status); //--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars int GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period); public: //--- Return (1) itself, (2) timeseries list, (3) indicator buffer list (featuring the ID of belonging to an indicator) CBuffersCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } CArrayObj *GetListBuffersWithID(void);
Fora do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Return the list of indicator buffers | //| (featuring the ID of belonging to an indicator) | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBuffersWithID(void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL); return list; } //+------------------------------------------------------------------+
Aqui, simplesmente com ajuda da classe CSelect, obtemos uma lista de objetos-buffers em que o valor do identificador não é igual a -1, e retornamos um ponteiro para a lista resultante.
A lista, após a obtenção bem-sucedida, conterá todos os objetos-buffers que possuem um identificador que não é igual a -1. Isso significa que a lista conterá todos os objetos-buffers criados para trabalhar com indicadores padrão, incluindo quaisquer buffers, tanto calculados como desenhados, para qualquer tipo de indicador padrão.
Para pesquisar objetos-buffer pertencentes a um indicador específico, é necessário realizar uma filtragem adicional desta lista com base no tipo de indicador padrão, identificador e tipo de buffer.
À seção pública da classe vamos adicionar as declarações de métodos para criar objetos-buffers para trabalhar com indicadores padrão:
//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", //--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels", //--- (8) "Display as bars", (9) "Display as candles", calculated buffer bool CreateArrow(void) { return this.CreateBuffer(BUFFER_STATUS_ARROW); } bool CreateLine(void) { return this.CreateBuffer(BUFFER_STATUS_LINE); } bool CreateSection(void) { return this.CreateBuffer(BUFFER_STATUS_SECTION); } bool CreateHistogram(void) { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM); } bool CreateHistogram2(void) { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); } bool CreateZigZag(void) { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG); } bool CreateFilling(void) { return this.CreateBuffer(BUFFER_STATUS_FILLING); } bool CreateBars(void) { return this.CreateBuffer(BUFFER_STATUS_BARS); } bool CreateCandles(void) { return this.CreateBuffer(BUFFER_STATUS_CANDLES); } bool CreateCalculate(void) { return this.CreateBuffer(BUFFER_STATUS_NONE); } //--- Create a multi-symbol multi-period indicator int CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE); int CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE); int CreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE); int CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe, const int jaw_period, const int jaw_shift, const int teeth_period, const int teeth_shift, const int lips_period, const int lips_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ama_period, const int fast_ma_period, const int slow_ma_period, const int ama_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe, const int bands_period, const int bands_shift, const double deviation, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ma_period, const int slow_ma_period, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const double deviation, const int id=WRONG_VALUE); int CreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe, const int jaw_period, const int jaw_shift, const int teeth_period, const int teeth_shift, const int lips_period, const int lips_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe, const int tenkan_sen, const int kijun_sen, const int senkou_span_b, const int id=WRONG_VALUE); int CreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe, const int mom_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe, const double step, const double maximum, const int id=WRONG_VALUE); int CreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe, const int Kperiod, const int Dperiod, const int slowing, const ENUM_MA_METHOD ma_method, const ENUM_STO_PRICE price_field, const int id=WRONG_VALUE); int CreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id=WRONG_VALUE); int CreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int cmo_period, const int ema_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);
Para cada tipo específico de indicador padrão, usaremos nosso próprio método de criação do indicador correspondente e os objetos-buffer necessários.
Por exemplo, hoje vamos trabalhar com o indicador AC. Fora do corpo da classe, vamos escrever a implementação do método para criar o indicador AC e seus buffers:
//+------------------------------------------------------------------+ //| Create multi-symbol multi-period AC | //+------------------------------------------------------------------+ int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE) { //--- Create the indicator handle and set the default ID int handle=::iAC(symbol,timeframe); int identifier=(id==WRONG_VALUE ? IND_AC : id); if(handle!=INVALID_HANDLE) { //--- Create the histogram buffer from the zero line this.CreateHistogram(); //--- Get the last created (drawn) buffer object and set all the necessary parameters to it CBuffer *buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetShowData(true); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); //--- Create a calculated buffer storing standard indicator data this.CreateCalculate(); //--- Get the last created (calculated) buffer object and set all the necessary parameters to it buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetEmptyValue(EMPTY_VALUE); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); } return handle; } //+------------------------------------------------------------------+
Como podemos ver, tudo é simples. Se como identificador for passado -1, o identificador será igual ao valor da constante do tipo de indicador padrão. Após a criação bem-sucedida do indicador (seu identificador não é igual a INVALID_HANDLE), criamos um objeto-buffer com o tipo de desenho "Histograma da linha zero" e usando o método GetLastCreateBuffer(), que retorna um ponteiro para o último buffer criado (consideraremos o método um pouco mais tarde), obtemos um ponteiro para o objeto-buffer de histograma e definimos os parâmetros necessários para sua identificação como um buffer para desenhar dados do indicador AC padrão.
Em seguida, fazemos o mesmo para o buffer calculado. No buffer calculado escrevemos os dados do indicador AC obtidos ao acessar seu identificador. Nosso identificador para o indicador criado é registrado nas propriedades do objeto-buffer, também do desenhado e do calculado, ou seja, podemos obter qualquer um desses objetos-buffers e usar indicador com base no identificador especificado nesses objetos e, em seguida, trabalhar com o indicador.
Vamos adicionar a implementação do método para criar um indicador AD com os objetos de buffer necessários, apenas para que possamos ver as diferenças na implementação de métodos para indicadores padrão de diferentes tipos:
//+------------------------------------------------------------------+ //| Create multi-symbol multi-period AD | //+------------------------------------------------------------------+ int CBuffersCollection::CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE) { //--- Create the indicator handle and set the default ID int handle=::iAD(symbol,timeframe,applied_volume); int identifier=(id==WRONG_VALUE ? IND_AD : id); if(handle!=INVALID_HANDLE) { //--- Create the line buffer this.CreateLine(); //--- Get the last created (drawn) buffer object and set all the necessary parameters to it CBuffer *buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AD); buff.SetShowData(true); buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accumulation/Distribution"); //--- Create a calculated buffer storing standard indicator data this.CreateCalculate(); //--- Get the last created (calculated) buffer object and set all the necessary parameters to it buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AD); buff.SetEmptyValue(EMPTY_VALUE); buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accumulation/Distribution"); } return handle; } //+------------------------------------------------------------------+
As diferenças são pequenas. A diferença está no tipo de buffer desenhado, o tipo de indicador padrão, o nome da série gráfica e o nome do indicador. Outros tipos de indicadores padrão terão (se necessário) um número diferente de objetos-buffer desenhados e calculados para trabalhar com um indicador padrão.
Imediatamente após a declaração de métodos para a criação de indicadores padrão adicionamos as declarações dos métodos restantes necessários:
//--- Prepare calculated buffer data of the specified standard indicator int PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy); //--- Clear buffer data of the specified standard indicator by the timeseries index void ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index); //--- Set the values for the current chart to the specified standard indicator buffer by the timeseries index according to the buffer object period/symbol bool SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE); //--- Return the buffer (1) by the graphical series name, (2) by timeframe, //--- (3) by Plot index, (4) by object index in the collection list, (5) the last created one, //--- list of buffers (6) by ID, (7) by standard indicator type, (8) by type and ID CBuffer *GetBufferByLabel(const string plot_label); CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe); CBuffer *GetBufferByPlot(const int plot_index); CBuffer *GetBufferByListIndex(const int index_list); CBuffer *GetLastCreateBuffer(void); CArrayObj *GetListBufferByID(const int id); CArrayObj *GetListBufferByIndType(const ENUM_INDICATOR indicator_type); CArrayObj *GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id);
Todos os métodos declarados são assinados nos comentários. Vamos considerar suas implementações fora do corpo da classe.
O método que retorna o último objeto-buffer criado:
//+------------------------------------------------------------------+ //| Return the last created buffer | //+------------------------------------------------------------------+ CBuffer *CBuffersCollection::GetLastCreateBuffer(void) { return this.m_list.At(this.m_list.Total()-1); } //+------------------------------------------------------------------+
O método simplesmente retorna um ponteiro para um objeto-buffer localizado no final da lista de objetos-buffers.
Método que retorna uma lista de objetos-buffers com base no identificador:
//+------------------------------------------------------------------+ //| Return the list of buffers by ID | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByID(const int id) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,id,EQUAL); return list; } //+------------------------------------------------------------------+
Recebemos uma lista de objetos-buffers cujas propriedades têm um identificador igual ao valor passado para o método.
Retornamos um ponteiro para a lista resultante a partir do método.
Método que retorna uma lista de objetos-buffers com base no tipo de indicador padrão:
//+------------------------------------------------------------------+ //| Return the list of buffers by the standard indicator type | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByIndType(const ENUM_INDICATOR indicator_type) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_IND_TYPE,indicator_type,EQUAL); return list; } //+------------------------------------------------------------------+
Recebemos uma lista de objetos-buffers cujas propriedades têm um identificador padrão igual ao valor passado para o método.
Retornamos um ponteiro para a lista resultante a partir do método.
Método que retorna uma lista de objetos-buffers com base no tipo de indicador padrão e no identificador:
//+------------------------------------------------------------------+ //| Return the list of buffers by type and ID | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id) { CArrayObj *list=this.GetListBufferByIndType(indicator_type); list=CSelect::ByBufferProperty(list,BUFFER_PROP_ID,id,EQUAL); return list; } //+------------------------------------------------------------------+
Primeiro, recebemos uma lista de objetos-buffers cujas propriedades têm o tipo de identificador padrão especificado em seguida filtramos a lista resultante com base nos objetos-buffers com o identificador especificado em suas propriedades.
O ponteiro para a lista resultante é retornado a partir do método.
Método que retorna uma lista de objetos-buffers pertencentes a algum indicador padrão:
//+------------------------------------------------------------------+ //| Return the list of indicator buffers | //| (featuring the ID of belonging to an indicator) | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBuffersWithID(void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL); return list; } //+------------------------------------------------------------------+
Desde uma lista-coleção de objetos-buffers obtemos uma lista de objetos cuja propriedade "identificador" não é igual a -1.
Retornamos um ponteiro para a lista resultante a partir do método.
O elo entre o programa e a biblioteca é a classe do CEngine, isto é, do objeto principal da biblioteca.
Fazemos as modificações necessárias no arquivo da classe \MQL5\Include\DoEasy\Engine.mqh.
Na seção pública da classe adicionamos um método para recriar todas as séries temporais:
//--- Re-create (1) the specified timeseries of the specified symbol, (2) all collection timeseries bool SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required); } bool SeriesReCreateAll(const int rates_total=0,const uint required=0) { return this.m_time_series.ReCreateSeriesAll(rates_total,required); }
O método simplesmente retorna o resultado do trabalho do método da coleção da série temporal de mesmo nome, que adicionamos acima hoje.
À seção pública da classe adicionamos um método que retorna o número de barras da série temporal especificada:
//--- 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 the umber of bars of the timeseries of the specified symbol/period int SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe);
Escrevemos a implementação deste método fora do corpo da classe:
//+---------------------------------------------------------------------------+ //| Return the umber of bars of the timeseries of the specified symbol/period | //+---------------------------------------------------------------------------+ int CEngine::SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) return WRONG_VALUE; return (int)series.Bars(); } //+------------------------------------------------------------------+
Obtemos a série temporal especificada a partir da classe-coleção da série temporal e retornamos o número de barras da série temporal.
Nós anteriormente tínhamos um método que retornava o último buffer criado:
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);} CBuffer *GetBufferByPlot(const int plot_index) { return this.m_buffers.GetBufferByPlot(plot_index); } CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);} CBuffer *GetLastBuffer(void);
e sua implementação:
//+------------------------------------------------------------------+ //| Return the last indicator buffer | //| in the indicator buffer collection list | //+------------------------------------------------------------------+ CBuffer *CEngine::GetLastBuffer(void) { CArrayObj *list=this.GetListBuffers(); if(list==NULL) return NULL; return list.At(list.Total()-1); } //+------------------------------------------------------------------+
Removemos a implementação do método da lista da classe, e substituímos sua declaração por um novo método:
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);} CBuffer *GetBufferByPlot(const int plot_index) { return this.m_buffers.GetBufferByPlot(plot_index); } CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);} CBuffer *GetLastCreateBuffer(void) { return this.m_buffers.GetLastCreateBuffer(); }
O método retorna o resultado do trabalho do método de mesmo nome da classe-coleção de buffers, que escrevemos acima hoje.
À seção pública da classe adicionamos um método para criar um indicador AC padrão e buffers para sua operação:
//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", //--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels", //--- (8) "Display as bars", (9) "Display as candles", calculated buffer bool BufferCreateArrow(void) { return this.m_buffers.CreateArrow(); } bool BufferCreateLine(void) { return this.m_buffers.CreateLine(); } bool BufferCreateSection(void) { return this.m_buffers.CreateSection(); } bool BufferCreateHistogram(void) { return this.m_buffers.CreateHistogram(); } bool BufferCreateHistogram2(void) { return this.m_buffers.CreateHistogram2(); } bool BufferCreateZigZag(void) { return this.m_buffers.CreateZigZag(); } bool BufferCreateFilling(void) { return this.m_buffers.CreateFilling(); } bool BufferCreateBars(void) { return this.m_buffers.CreateBars(); } bool BufferCreateCandles(void) { return this.m_buffers.CreateCandles(); } bool BufferCreateCalculate(void) { return this.m_buffers.CreateCalculate(); } //--- The methods of creating standard indicators and buffer objects for them bool BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id) { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE); } //--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object
O método retorna o resultado do trabalho do método de mesmo nome para criar um indicador AC a partir da classe-coleção de buffers do indicador, que escrevemos acima hoje. Métodos para a criação de outros indicadores padrão serão adicionados em artigos futuros.
Implementação do método para preparar os dados do buffer calculado para o indicador padrão (até agora apenas para AC):
//+------------------------------------------------------------------+ //| Prepare the calculated buffer data | //| of the specified standard indicator | //+------------------------------------------------------------------+ int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy) { CArrayObj *list=this.GetListBufferByTypeID(std_ind,id); list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); if(list==NULL || list.Total()==0) return 0; CBufferCalculate *buffer=NULL; int copies=WRONG_VALUE; switch((int)std_ind) { case IND_AC : buffer=list.At(0); if(buffer==NULL) return 0; copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy); return copies; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } return 0; } //+------------------------------------------------------------------+
Obtemos a lista de objetos-buffers de acordo com o tipo de indicador e de identificador,
na lista resultante, deixamos apenas os buffers calculados,
obtemos o primeiro (e apenas para o AC) buffer calculado a partir da lista,
preenchemos o buffer calculado com a quantidade especificada de dados e
retornamos a quantidade de dados copiados com sucesso desde o identificador do indicador para o buffer calculado.
Implementação do método para limpar os dados de buffer calculados para o indicador padrão de acordo com o índice especificado (até agora apenas para o AC):
//+------------------------------------------------------------------+ //| Clear buffer data of the specified standard indicator | //| by the timeseries index | //+------------------------------------------------------------------+ void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index) { CArrayObj *list=this.GetListBufferByID(id); if(list==NULL) return; list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); if(list.Total()==0) return; CBuffer *buffer=NULL; switch((int)std_ind) { case IND_AC : buffer=list.At(0); if(buffer==NULL) return; buffer.SetBufferValue(0,series_index,buffer.EmptyValue()); break; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } } //+------------------------------------------------------------------+
O método é quase idêntico ao método de preparação de dados. Apenas em vez de copiar dados do identificador do indicador para o buffer calculado, é definido um valor vazio especificado para o objeto-buffer de acordo com o índice especificado no buffer desenhado.
Implementação do método de preenchimento do buffer desenhado no gráfico atual com os dados de um indicador padrão de símbolo/período qualquer (até agora apenas para o AC):
//+------------------------------------------------------------------+ //| Set values for the current chart to the specified buffer | //| standard indicator by the timeseries index according to | //| the buffer object symbol/period | //+------------------------------------------------------------------+ bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE) { //--- Get the list of buffer objects with ID CArrayObj *list=this.GetListBufferByTypeID(ind_type,id); if(list==NULL) return false; //--- Get the list of drawn objects with ID CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Get the list of calculated buffers with ID CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Exit if any of the lists is empty if(list_data.Total()==0 || list_calc.Total()==0) return false; //--- Declare the necessary objects and variables CBuffer *buffer_data=NULL; CBuffer *buffer_calc=NULL; int index_period=0; int series_index_start=0; int num_bars=1,index=0; datetime time_period=0; double value0=EMPTY_VALUE, value1=EMPTY_VALUE; //--- Depending on the standard indicator type switch((int)ind_type) { case IND_AC : //--- Get drawn and calculated buffer objects buffer_data=list_data.At(0); buffer_calc=list_calc.At(0); if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false; //--- Find the bar index corresponding to the current bar start time index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true); if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false; //--- Get the value by the index from the indicator buffer value0=buffer_calc.GetDataBufferValue(0,index_period); if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period()) { series_index_start=series_index; num_bars=1; } else { //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period); if(time_period==0) return false; //--- Get the appropriate current chart bar series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true); if(series_index_start==WRONG_VALUE) return false; //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT); if(num_bars==0) num_bars=1; } //--- Take values to calculate colors value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars)); //--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index //--- and set the color of the drawn buffer depending on the value0 and value1 values ratio for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data.SetBufferValue(0,index,value0); buffer_data.SetBufferColorIndex(index,uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2)); } break; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } return true; } //+------------------------------------------------------------------+
Toda a lógica do método de cálculo de dados para o AC é descrita nos comentários e, espero, esteja clara.
No final do corpo da classe, declaramos dois métodos para lidar com eventos de biblioteca:
public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); //--- Handling DoEasy library events void OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Working with events in the tester void EventsHandling(void); }; //+------------------------------------------------------------------+
Anteriormente, para lidar com eventos de biblioteca em nossos programas, usávamos as funções de mesmo nome para os métodos recém-declarados. E transferíamos essas funções de um programa para outro sem nenhuma alteração. Isso leva a pensar que esses manipuladores podem ser transferidos para a biblioteca, enquanto o programa pode receber os sinalizadores dos eventos ocorridos (mais tarde faremos o recebimento dos sinalizadores, bem como os próprios sinalizadores de eventos e o processamento de eventos em nossos programas).
Já transferi essas funções desde o indicador de teste para a listagem da classe CEngine, convertendo-as na implementação dos métodos acima:
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void CEngine::OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=this.EventMSC(lparam); ushort reason=this.EventReason(lparam); ushort source=this.EventSource(lparam); long time=::TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=this.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=::DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=this.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=::DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- Display an event in the journal if(reason==BASE_EVENT_REASON_INC) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=this.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); CArrayObj *list=this.m_buffers.GetListBuffersWithID(); if(list!=NULL) { int total=list.Total(); for(int i=0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) continue; string symbol=sparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; if(buff.Symbol()==symbol && buff.Timeframe()==timeframe ) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) continue; int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()); this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count); } } } } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam); } } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=this.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(this.IsTester() ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { ::Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { ::Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { ::Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { ::Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { ::Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { ::Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { ::Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order placed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order removed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP) { ::Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void CEngine::EventsHandling(void) { //--- If a trading event is present if(this.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=this.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=this.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(this.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=this.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(this.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=this.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a timeseries collection event if(this.IsSeriesEvent()) { //--- Get the list of all timeseries events occurred simultaneously CArrayObj* list=this.GetListSeriesEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
Examinamos a operação dessas funções (e agora os métodos da classe CEngine) nos artigos iniciais que descrevem a biblioteca ao criar EAs de teste. A partir da listagem dos métodos, podemos ver que a cada evento (quase) nós apenas gravamos uma mensagem no log. Da mesma forma, podemos criar uma lista de sinalizadores de eventos no escopo global e simplesmente definir os sinalizadores necessários. Nos nossos programas, é fácil fazer manipuladores para cada uma dos sinalizadores mostrados. É isso que faremos mais tarde.
Assim, nos livramos da necessidade de escrever esses manipuladores em cada um de nossos programas.
A classe tem um manipulador de evento calculate, que é chamado a partir do indicador, e se o valor retornado pelo manipulador for igual a zero, isso significa que nem todas as séries temporais usadas no indicador foram construídas ainda. Nesse caso, o indicador deve sair de OnCalculate() com o código de retorno 0, o que significa aguardar o próximo tick e indica que nenhum dado foi calculado.
Como estamos adicionando o funcionamento com indicadores padrão, devemos verificar da mesma forma que o indicador criado esteja calculado.
Para descobrir a quantidade de dados calculados, podemos usar a função BarsCalculated(), que retorna a quantidade de dados já calculada pelo indicador. Se os dados ainda não foram calculados, a função retorna -1.
Vamos escrever, no método que lida com o evento calculate, a verificação do sucesso do cálculo de todos os indicadores padrão criados na coleção de buffers:
//+------------------------------------------------------------------+ //| 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 0; //--- 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 (not in the tester) and //--- return either 0 (in case there are empty timeseries), or rates_total if(!this.IsTester()) this.SeriesRefresh(NULL,data_calculate); int res=(this.SeriesGetSeriesEmpty()==NULL ? data_calculate.rates_total : 0); //--- Check the amount of calculated standard indicator data CArrayObj *list=m_buffers.GetListBuffersWithID(); if(list!=NULL) { //--- In a loop by the number of buffers having an ID int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next calculated buffer using the standard indicator CBuffer *buff=list.At(i); if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorHandle()==INVALID_HANDLE) continue; //--- if the indicator data is not calculated yet, return zero if(buff.IndicatorBarsCalculated()==WRONG_VALUE) return 0; } } return res; } //+------------------------------------------------------------------+
A lógica de processamento de dados dos indicadores criados é descrita na listagem do método.
O toque final na revisão da biblioteca para hoje será adicionar o período do gráfico atual à lista de timeframes usados.
No arquivo de funções de serviço da biblioteca E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Services\DELib.mqh temos uma função que prepara uma lista dos timeframes usados. Se nas configurações do programa não estiver especificado o período do gráfico atual, a biblioteca não criará sua série temporal. No entanto, precisamos dele para trabalhar o tempo todo.
Complementamos a função CreateUsedTimeframesArray() com o bloco de código para adicionar o período do gráfico atual à lista de timeframes usados:
//+------------------------------------------------------------------+ //| Prepare the array of timeframes for the timeseries collection | //+------------------------------------------------------------------+ bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[]) { //--- If working with the current chart period, fill the array with the current timeframe description string if(mode_used_periods==TIMEFRAMES_MODE_CURRENT) { ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return true; } //--- If working with a predefined set of chart periods (from the defined_used_periods string) else if(mode_used_periods==TIMEFRAMES_MODE_LIST) { //--- Set comma as a separator (defined in the Datas.mqh file, page 11) string separator=INPUT_SEPARATOR; //--- Fill in the array of parameters from the string with predefined timeframes int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array); //--- if nothing is found, display the appropriate message (working with the current period is selected automatically) if(n<1) { int err_code=GetLastError(); string err= (n==0 ? DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) : DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code) ); Print(err); //--- Set the current period to the array ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return false; } } //--- If working with the full list of timeframes, fill in the array with strings describing all timeframes else { ArrayResize(used_periods_array,21,21); for(int i=0;i<21;i++) used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1))); } //--- Add the current chart timeframe to the list of used periods bool f=false; for(int i=0;i<ArraySize(used_periods_array);i++) { if(used_periods_array[i]==TimeframeDescription((ENUM_TIMEFRAMES)Period())) { f=true; break; } } //--- If the list of used periods features no timeframe of the current chart if(!f) { //--- Increase the array of used periods by 1 and add the current chart period to it ArrayResize(used_periods_array,ArraySize(used_periods_array)+1); used_periods_array[ArraySize(used_periods_array)-1]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); } //--- All is successful return true; } //+------------------------------------------------------------------+
Assim ficam concluídas as modificações das classes da biblioteca.
Vamos testar a criação do indicador Accelerator Oscillator padrão multissímbolos e multiperíodos.
Teste
Para o teste, vamos pegar o indicador do último artigo
e o armazenamos na nova pasta \MQL5\Indicators\TestDoEasy\Part47\ com o novo nome TestDoEasyPart47.mq5.
Nas configurações do indicador criado, especificaremos qual símbolo e período de tempo usaremos para calcular o indicador AcceleratorOscillator padrão. Nosso indicador exibirá esses dados numa subjanela do gráfico atual.
O cabeçalho do indicador será o seguinte:
//+------------------------------------------------------------------+ //| TestDoEasyPart47.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- properties #property indicator_separate_window #property indicator_buffers 3 #property indicator_plots 1 //--- classes //--- enums //--- defines //--- structures //--- input variables sinput string InpUsedSymbols = "GBPUSD"; // Used symbol (one only) sinput ENUM_TIMEFRAMES InpPeriod = PERIOD_M30; // Used chart period //--- sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers //--- global variables ENUM_SYMBOLS_MODE InpModeUsedSymbols= SYMBOLS_MODE_DEFINES; // Mode of used symbols list ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list string InpUsedTFs; // List of used timeframes CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names int min_bars; // The minimum number of bars for the indicator calculation int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // The array for passing used symbols to the library string array_used_periods[]; // The array for passing used timeframes to the library //+------------------------------------------------------------------+
Especificamos nas configurações apenas um símbolo e um período do gráfico com base no qual será calculado o indicador AC.
No manipulador OnInit() vamos criar um indicador AC padrão com parâmetros especificados nos parâmetros de entrada do indicador com seu identificador sendo igual a 1 , e buffers para trabalhar com ele:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); //--- Initialize DoEasy library OnInitDoEasy(); //--- Set indicator global variables prefix=engine.Name()+"_"; //--- calculate the number of bars of the current period fitting in the maximum used period //--- Use the obtained value if it exceeds 2, otherwise use 2 int num_bars=NumberBarsInTimeframe(InpPeriod); min_bars=(num_bars>2 ? num_bars : 2); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel //--- 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 //--- Create all the necessary buffer objects for constructing AO engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1); //--- Check the number of buffers specified in the 'properties' block if(engine.BuffersPropertyPlotsTotal()!=indicator_plots) Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal()); if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers) Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal()); //--- Create the color array and set non-default colors to all buffers within the collection color array_colors[]={clrGreen,clrRed,clrGray}; engine.BuffersSetColors(array_colors); //--- Display short descriptions of created indicator buffers engine.BuffersPrintShort(); //--- Set the short name for the indicator and bit depth IndicatorSetString(INDICATOR_SHORTNAME,"AC("+InpUsedSymbols+","+TimeframeDescription(InpPeriod)+")"); IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+2); //--- Successful return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Em OnCalculate() primeiro preparamos os dados para o buffer de cálculo do indicador AC, e depoisno ciclo principal do indicador, preenchemos os dados do buffer desenhado no gráfico atual com dados do buffer calculado do indicador AC:
//+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Check for the minimum number of bars for calculation if(rates_total<min_bars || Point()==0) return 0; //--- Handle the Calculate event in the library //--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick if(engine.0) return 0; //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the library timer engine.EventsHandling(); // Working with library events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Check and calculate the number of calculated bars //--- If limit = 0, there are no new bars - calculate the current one //--- If limit = 1, a new bar has appeared - calculate the first and the current ones //--- limit > 1 means the first launch or changes in history - the full recalculation of all data int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; engine.BuffersInitPlots(); engine.BuffersInitCalculates(); } //--- Prepare data int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod); int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total)); //--- Fill in the calculated buffer with AO data CArrayObj *list=engine.GetBuffersCollection().GetListBuffersWithID(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { CBuffer *buff=list.At(i); if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; CSeriesDE *series=engine.SeriesGetSeries(buff.Symbol(),buff.Timeframe()); if(series==NULL) return 0; ulong used_data=series.AvailableUsedData(); int copied=engine.GetBuffersCollection().PreparingDataBufferStdInd(IND_AC,1,(int)used_data); if(copied<(int)used_data) return 0; } } //--- Calculate the indicator CBar *bar=NULL; // Bar object for defining the candle direction uchar color_index=0; // Color index to be set for the buffer depending on the candle direction //--- Main calculation loop of the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Isso é tudo que precisamos fazer para calcular e exibir o indicador AC padrão no gráfico atual, calculado com base em qualquer símbolo/período gráfico.
Vamos finalizar o bloco com a preparação dos dados do indicador padrão nos artigos vindouros (nesta implementação está longe de ser ótimo e foi feito apenas para testar o conceito), e também vamos transferi-lo para a biblioteca.
O código completo do indicador pode ser encontrado nos arquivos anexados ao artigo.
Vamos compilar o indicador e executá-lo no gráfico EURUSD, M1, tendo previamente definido o símbolo GBPUSD e o período gráfico M5 nas configurações do indicador, o que indica que o indicador AC calculado com base no símbolo GBPUSD com o período M5 é exibido no gráfico de minutos EURUSD atual:
Para comparação, o gráfico GBPUSD, M5 com o indicador AC padrão está aberto junto a ele.
O que vem agora?
No próximo artigo, continuaremos a criar indicadores padrão multiperíodos multissímbolos.
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 dúvidas, comentários e sugestões, pode expressá-los nos comentários do artigo.
Gostaria de chamar sua atenção para o fato de que neste artigo fizemos um indicador de teste em MQL5 para MetaTrader 5.
Os arquivos anexados estão destinados apenas ao MetaTrader 5 e a versão atual da biblioteca ainda não foi testada no MetaTrader 4.
Após criar a funcionalidade para trabalhar com buffers de indicador e testá-la, tentaremos implementar algumas coisas de MQL5 para MetaTrader 4.
Artigos desta série:
Trabalhando com séries temporais na biblioteca DoEasy (Parte 35): Objeto "Barra" e lista-série temporal do símboloTrabalhando com séries temporais na biblioteca DoEasy (Parte 36): objeto das séries temporais de todos os períodos usados do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa
Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais
Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Trabalhando com séries temporais na biblioteca DoEasy (Parte 41): exemplo de indicador multissímbolo multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 42): classe de um objeto de buffer abstrato de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 43): classes de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 44): classe-coleção de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/8207
- 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