Trabalhando com séries temporais na biblioteca DoEasy (Parte 58): séries temporais de dados de buffers de indicadores
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Classe da série temporal para os dados dos buffers do indicador
- Teste
- O que vem agora?
Ideia
No final do tópico sobre trabalho com séries temporais, realizaremos o armazenamento, pesquisa e classificação dos dados armazenados em buffers de indicadores, o que nos permitirá realizar análises posteriores com base nos valores dos indicadores criados assentes na biblioteca para nossos programas.
O conceito por trás da construção de séries temporais para os dados dos indicadores é semelhante ao de criação de uma coleção de séries temporais - para cada indicador, criaremos uma lista própria, que armazenará todos os dados de todos os buffers em questão. A quantidade de dados na lista de buffers de indicador refletirá a quantidade de dados nas devidas séries temporais, para as quais os indicadores são calculados. O conceito geral por trás de todas as classes-coleções da biblioteca torna mais fácil encontrar os dados necessários na coleção correspondente, assim, o mesmo será possível na classe que será criada hoje.
No futuro, criaremos classes para análise com base em todos os dados armazenados na biblioteca, o que fornecerá uma ferramenta muito flexível para a realização de pesquisas estatísticas com uma comparação abrangente de quaisquer dados das coleções da biblioteca.
Aprimorando as classes da biblioteca
A classe para coleção de dados dos buffers de indicador atualizará automaticamente os dados de todos os indicadores criados na barra atual e, quando uma nova barra aparecer, criará novos dados de buffers de indicador e os colocará na coleção.
Já temos a classe "Nova Barra" no arquivo NewBarObj.mqh que criamos para a coleção de séries temporais.
Ela é armazenada na pasta contendo as classes das séries temporais no diretório da biblioteca \MQL5\Include\DoEasyPart57\Objects\Series\.
Agora também precisamos dela para rastrear uma nova barra na classe da coleção de dados dos buffers de indicador.
Por isso, vamos movê-la para a pasta de funções de serviço e classes de biblioteca \MQL5\Include\DoEasy\Services\.
Excluímos o arquivo em seu local antigo.
Como a classe TimeSeries usa a classe "Nova Barra", precisamos mudar o caminho antigo para esta classe no arquivo da classe "TimeSeries" \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh no bloco de arquivos de inclusão:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
para o novo caminho:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "..\..\Services\NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Data.mqh terminamos de escrever os índices das novas mensagens:
MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK, // Failed to get Ask price. Error MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_BID, // ailed to get Bid price. Error MSG_LIB_SYS_ERROR_FAILED_GET_TIME, // Failed to get time. Error MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY, // Failed to open Buy position. Error
...
//--- CDataInd MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM, // Indicator buffer number MSG_LIB_TEXT_IND_DATA_BUFFER_VALUE, // Indicator buffer value //--- CSeriesDataInd MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS, // The method is not intended to handle indicator programs MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA, // Failed to get indicator data timeseries MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA, // Failed to get current data of indicator buffer MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ, // Failed to create indicator data object MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST, // Failed to add indicator data object to list }; //+------------------------------------------------------------------+
bem como os textos das mensagens correspondentes aos índices adicionados recentemente:
{"Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "}, {"Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "}, {"Не удалось получить время. Ошибка ","Could not get time. Error "}, {"Не удалось открыть позицию Buy. Ошибка ","Failed to open Buy position. Error "},
...
{"Номер буфера индикатора","Indicator buffer number"}, {"Значение буфера индикатора","Indicator buffer value"}, {"Метод не предназначен для работы с программами-индикаторами","The method is not intended for working with indicator programs"}, {"Не удалось получить таймсерию индикаторных данных","Failed to get indicator data timeseries"}, {"Не удалось получить текущие данные буфера индикатора","Failed to get the current data of the indicator buffer"}, {"Не удалось создать объект индикаторных данных","Failed to create indicator data object"}, {"Не удалось добавить объект индикаторных данных в список","Failed to add indicator data object to the list"}, }; //+---------------------------------------------------------------------+
Como hoje vamos criar uma nova coleção, precisamos definir para ela um identificador de coleção próprio. Além do mais, criamos constantes para os parâmetros do temporizador da nova coleção - afinal, os dados dos buffers de indicador serão atualizados automaticamente no temporizador.
Inserimos os novos dados no arquivo \MQL5\Include\DoEasy\Defines.mqh:
//--- Parameters of the timeseries collection timer #define COLLECTION_TS_PAUSE (64) // Timeseries collection timer pause in milliseconds #define COLLECTION_TS_COUNTER_STEP (16) // Account timer counter increment #define COLLECTION_TS_COUNTER_ID (6) // Timeseries timer counter ID //--- Parameters of the timer of indicator data timeseries collection #define COLLECTION_IND_TS_PAUSE (64) // Pause of the timer of indicator data timeseries collection in milliseconds #define COLLECTION_IND_TS_COUNTER_STEP (16) // Increment of indicator data timeseries timer counter #define COLLECTION_IND_TS_COUNTER_ID (7) // ID of indicator data timeseries timer counter //--- Collection list IDs #define COLLECTION_HISTORY_ID (0x777A) // Historical collection list ID #define COLLECTION_MARKET_ID (0x777B) // Market collection list ID #define COLLECTION_EVENTS_ID (0x777C) // Event collection list ID #define COLLECTION_ACCOUNT_ID (0x777D) // Account collection list ID #define COLLECTION_SYMBOLS_ID (0x777E) // Symbol collection list ID #define COLLECTION_SERIES_ID (0x777F) // Timeseries collection list ID #define COLLECTION_BUFFERS_ID (0x7780) // Indicator buffer collection list ID #define COLLECTION_INDICATORS_ID (0x7781) // Indicator collection list ID #define COLLECTION_INDICATORS_DATA_ID (0x7782) // Indicator data collection list ID
Para pesquisar os dados dos buffers de indicador na coleção correspondente, precisaremos buscar dados adicionalmente pelo identificador do indicador (afinal, os dados pertencerão a este último, e seria estranho não usar o identificador exato que diz respeito ao indicador para pesquisa de dados).
Ás propriedades inteiras dos dados de indicador adicionamos uma nova propriedade e aumentamos a quantidade desses dados de 5 para 6:
//+------------------------------------------------------------------+ //| Integer properties of indicator data | //+------------------------------------------------------------------+ enum ENUM_IND_DATA_PROP_INTEGER { IND_DATA_PROP_TIME = 0, // Start time of indicator data bar period IND_DATA_PROP_PERIOD, // Indicator data period (timeframe) IND_DATA_PROP_INDICATOR_TYPE, // Indicator type IND_DATA_PROP_IND_BUFFER_NUM, // Indicator data buffer number IND_DATA_PROP_IND_ID, // Indicator ID IND_DATA_PROP_IND_HANDLE, // Indicator handle }; #define IND_DATA_PROP_INTEGER_TOTAL (6) // Total number of indicator data integer properties #define IND_DATA_PROP_INTEGER_SKIP (0) // Number of indicator data properties not used in sorting //+------------------------------------------------------------------+
Consequentemente, uma vez que adicionamos uma nova propriedade inteira, para conseguirmos pesquisar e classificar de acordo com ela vamos adicioná-la à lista de possíveis critérios de classificação:
//+------------------------------------------------------------------+ //| Possible criteria for indicator data sorting | //+------------------------------------------------------------------+ #define FIRST_IND_DATA_DBL_PROP (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP) #define FIRST_IND_DATA_STR_PROP (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP+IND_DATA_PROP_DOUBLE_TOTAL-IND_DATA_PROP_DOUBLE_SKIP) enum ENUM_SORT_IND_DATA_MODE { //--- Sort by integer properties SORT_BY_IND_DATA_TIME = 0, // Sort by bar period start time of indicator data SORT_BY_IND_DATA_PERIOD, // Sort by indicator data period (timeframe) SORT_BY_IND_DATA_INDICATOR_TYPE, // Sort by indicator type SORT_BY_IND_DATA_IND_BUFFER_NUM, // Sort by indicator data buffer number SORT_BY_IND_DATA_IND_ID, // Sort by indicator ID SORT_BY_IND_DATA_IND_HANDLE, // Sort by indicator handle //--- Sort by real properties SORT_BY_IND_DATA_BUFFER_VALUE = FIRST_IND_DATA_DBL_PROP, // Sort by indicator data value //--- Sort by string properties SORT_BY_IND_DATA_SYMBOL = FIRST_IND_DATA_STR_PROP, // Sort by indicator data symbol SORT_BY_IND_DATA_IND_NAME, // Sort by indicator name SORT_BY_IND_DATA_IND_SHORTNAME, // Sort by indicator short name }; //+------------------------------------------------------------------+
Os dados dos buffers de indicador armazenados na coleção são apresentados com uma classe de dados dos buffers dos indicadores; nós criamos essa classe no último artigo.
Agora precisamos adicionar a ela (no arquivo \MQL5\Include\DoEasy\Objects\Indicators\DataInd.mqh) o método de configuração do identificador do indicador cujos dados de buffer são armazenados num objeto desta classe. Além disso ao construtor de classe adicionamos a transmissão de parâmetros do identificador do indicador e seus valores de buffer, o que permitirá especificar os valores desses parâmetros imediatamente ao criar um objeto:
//--- Set (1) symbol, timeframe and time for the object, (2) indicator type, (3) number of buffers, (4) number of data buffer, //--- (5) ID, (6) handle, (7) data value, (8) name, (9) indicator short name void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetIndicatorType(const ENUM_INDICATOR type) { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type); } void SetBufferNum(const int num) { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num); } void SetIndicatorID(const int id) { this.SetProperty(IND_DATA_PROP_IND_ID,id); } void SetIndicatorHandle(const int handle) { this.SetProperty(IND_DATA_PROP_IND_HANDLE,handle); } void SetBufferValue(const double value) { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value); } void SetIndicatorName(const string name) { this.SetProperty(IND_DATA_PROP_IND_NAME,name); } void SetIndicatorShortname(const string shortname) { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname); } //--- Compare CDataInd objects with each other by all possible properties (for sorting the lists by a specified data object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CDataInd objects with each other by all properties (to search equal objects) bool IsEqual(CDataInd* compared_data) const; //--- Constructors CDataInd(){;} CDataInd(const ENUM_INDICATOR ind_type, const int ind_handle, const int ind_id, const int buffer_num, const string symbol, const ENUM_TIMEFRAMES timeframe, const datetime time, const double value);
Ao bloco de métodos para fácil acesso às propriedades do objeto adicionamos um método que retorne o identificador do indicador, e renomeamos o método que retorna o valor dos dados do buffer do indicador (o método era anteriormente denominado PriceValue()):
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Return (1) bar period start time, (2) timeframe, (3) indicator type, //--- (4) number of buffers, (5) buffer number, (6) ID, (7) indicator handle datetime Time(void) const { return (datetime)this.GetProperty(IND_DATA_PROP_TIME); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(IND_DATA_PROP_PERIOD); } ENUM_INDICATOR IndicatorType(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_INDICATOR_TYPE); } int BufferNum(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_BUFFER_NUM); } int IndicatorID(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_ID); } int IndicatorHandle(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_HANDLE); } //--- Return the value of indicator buffer data double BufferValue(void) const { return this.GetProperty(IND_DATA_PROP_BUFFER_VALUE); }
No corpo do construtor da classe, definimos os valores das novas propriedades passadas a ele ao criar um novo objeto:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CDataInd::CDataInd(const ENUM_INDICATOR ind_type, const int ind_handle, const int ind_id, const int buffer_num, const string symbol, const ENUM_TIMEFRAMES timeframe, const datetime time, const double value) { this.m_type=COLLECTION_INDICATORS_DATA_ID; this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)+1; this.m_period_description=TimeframeDescription(timeframe); this.SetSymbolPeriod(symbol,timeframe,time); this.SetIndicatorType(ind_type); this.SetIndicatorHandle(ind_handle); this.SetBufferNum(buffer_num); this.SetIndicatorID(ind_id); this.SetBufferValue(value); } //+------------------------------------------------------------------+
Ao método que retorna uma descrição de propriedades inteiras adicionamos um bloco de código para exibir a descrição do identificador do indicador:
//+------------------------------------------------------------------+ //| Return description of object's integer property | //+------------------------------------------------------------------+ string CDataInd::GetPropertyDescription(ENUM_IND_DATA_PROP_INTEGER property) { return ( property==IND_DATA_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==IND_DATA_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==IND_DATA_PROP_INDICATOR_TYPE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.IndicatorTypeDescription() ) : property==IND_DATA_PROP_IND_BUFFER_NUM ? CMessage::Text(MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==IND_DATA_PROP_IND_ID ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==IND_DATA_PROP_IND_HANDLE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
Classe da série temporal para os dados dos buffers do indicador
Todas as classes-coleções da biblioteca são baseadas na classe uma matriz dinâmica de ponteiros para instâncias da classe CObject e seus herdeiros da Biblioteca padrão. A classe-coleção de dados de buffers de indicador não será uma exceção.
Esta classe permitirá armazenar objetos de dados do buffer de um indicador na lista de objetos CArrayObj, bem como receber dados de qualquer buffer de indicador correspondentes ao tempo de abertura da barra da série temporal. Naturalmente, a lista será capaz de atualizar, pesquisar e classificar automaticamente as propriedades dos objetos armazenados nela.
Na pasta \MQL5\Include\DoEasy\Objects\Indicators\ criamos a nova classe CSeriesDataInd no arquivo SeriesDataInd.mqh.
O objeto de classe deve ser herdado do objeto base de todos os objetos da biblioteca CBaseObj.
Vejamos o corpo da classe com todas as suas variáveis e métodos:
//+------------------------------------------------------------------+ //| SeriesDataInd.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "..\..\Services\NewBarObj.mqh" #include "DataInd.mqh" //+------------------------------------------------------------------+ //| “Indicator data list” class | //+------------------------------------------------------------------+ class CSeriesDataInd : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe ENUM_INDICATOR m_ind_type; // Indicator type string m_symbol; // Symbol string m_period_description; // Timeframe string description int m_ind_handle; // Indicator handle int m_ind_id; // Indicator ID int m_buffers_total; // Number of indicator buffers 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_data; // Indicator data list CNewBarObj m_new_bar_obj; // "New bar" object //--- Create a new indicator data object, return pointer to it CDataInd *CreateNewDataInd(const int buffer_num,const datetime time,const double value); public: //--- Return (1) oneself, (2) the full list of indicator data CSeriesDataInd *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list_data; } //--- Return the list of indicator data by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj *GetList(ENUM_IND_DATA_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_IND_DATA_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_IND_DATA_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } //--- Return indicator data object by (1) time, (2) bar number and buffer number CDataInd *GetIndDataByTime(const int buffer_num,const datetime time); CDataInd *GetIndDataByBar(const int buffer_num,const uint shift); //--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data, //--- (5) handle, (6) ID, (7) number of indicator buffers void SetSymbol(const string symbol); void SetTimeframe(const ENUM_TIMEFRAMES timeframe); void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe); bool SetRequiredUsedData(const uint required); void SetIndHandle(const int handle) { this.m_ind_handle=handle; } void SetIndID(const int id) { this.m_ind_id=id; } void SetIndBuffersTotal(const int total) { this.m_buffers_total=total; } //--- Return (1) symbol, (2) timeframe, number of (3) used and (4) requested timeseries data, //--- (5) number of bars in timeseries, (6) handle, (7) ID, (8) number of indicator buffers string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } ulong AvailableUsedData(void) const { return this.m_amount; } ulong RequiredUsedData(void) const { return this.m_required; } ulong Bars(void) const { return this.m_bars; } int IndHandle(void) const { return this.m_ind_handle; } int IndID(void) const { return this.m_ind_id; } int IndBuffersTotal(void) const { return this.m_buffers_total; } bool IsNewBar(const datetime time) { return this.m_new_bar_obj.IsNewBar(time); } bool IsNewBarManual(const datetime time) { return this.m_new_bar_obj.IsNewBarManual(time); } //--- Return real list size int DataTotal(void) const { return this.m_list_data.Total(); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_new_bar_obj.SaveNewBarTime(time); } //--- (1) Create, (2) update the indicator data list int Create(const uint required=0); void Refresh(void); //--- Return data of specified indicator buffer by (1) open time, (2) bar index double BufferValue(const int buffer_num,const datetime time); double BufferValue(const int buffer_num,const uint shift); //--- Constructors CSeriesDataInd(void){;} CSeriesDataInd(const int handle, const ENUM_INDICATOR ind_type, const int ind_id, const int buffers_total, const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); }; //+------------------------------------------------------------------+
No objeto, vemos métodos já familiares que são inerentes a todos os objetos de biblioteca e um conjunto de variáveis-membros de classe para armazenar parâmetros de objeto.
Na seção pública da classe, os métodos são declarados para simplificar o acesso às propriedades do objeto e para defini-las externamente.
Consideremos a implementação de métodos de classe.
Ao construtor de classe paramétrico são passados todos seus valores de propriedades necessários para criar um objeto:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CSeriesDataInd::CSeriesDataInd(const int handle, const ENUM_INDICATOR ind_type, const int ind_id, const int buffers_total, const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false) { //--- To the object set indicator data collection list ID this.m_type=COLLECTION_INDICATORS_DATA_ID; //--- Clear the list and set for it the flag of the list sorted by time this.m_list_data.Clear(); this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); //--- Set values for all object variables this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetRequiredUsedData(required); this.m_period_description=TimeframeDescription(this.m_timeframe); this.m_ind_handle=handle; this.m_ind_type=ind_type; this.m_ind_id=ind_id; this.m_buffers_total=buffers_total; } //+------------------------------------------------------------------+
Para o objeto é definido o identificador da coleção das séries temporais dos dados dos buffers de indicador, é apagada a lista na qual serão armazenados os objetos da classe CDataInd, e para a lista é definido o sinalizador de lista classificada. O melhor modo de classificação para esses objetos é classificar por tempo - para cotejar sua localização na lista com a dos dados no buffer físico do indicador.
Eu acho que os métodos para definir símbolo e período gráfico não precisam de explicações:
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CSeriesDataInd::SetSymbol(const string symbol) { if(this.m_symbol==symbol) return; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } //+------------------------------------------------------------------+ //| Set a timeframe | //+------------------------------------------------------------------+ void CSeriesDataInd::SetTimeframe(const ENUM_TIMEFRAMES timeframe) { if(this.m_timeframe==timeframe) return; this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeriesDataInd::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); } //+------------------------------------------------------------------+
Método para definir a quantidade necessária de dados de buffers de indicador:
//+------------------------------------------------------------------+ //| Set the number of required data | //+------------------------------------------------------------------+ bool CSeriesDataInd::SetRequiredUsedData(const uint required) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return false; } //--- Set the necessary amount of data - if less than 1 is passed use by default (1000), or else - the passed value this.m_required=(required<1 ? SERIES_DEFAULT_BARS_COUNT : required); //--- Launch download of historical data (relevant for the “cold” start) datetime array[1]; ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array); //--- Set the number of available timeseries bars this.m_bars=(uint)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT); //--- If managed to set the number of available history, set the amount of data in the list: if(this.m_bars>0) { //--- if zero 'required' value is passed, //--- use either the default value (1000 bars) or the number of available history bars - the least one of them //--- if non-zero 'required' value is passed, //--- use either the 'required' value or the number of available history bars - the least one of them this.m_amount=(required==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(required,this.m_bars)); return true; } return false; } //+------------------------------------------------------------------+
Para funcionar como parte de indicadores, já temos outras classes, por isso, aqui é verificado imediatamente o tipo de programa em execução, e se for um indicador, uma mensagem sobre isso será exibida e será retornado false. Consideramos a operação de um método semelhante quando criamos as classes das séries temporais no artigo 35.
Método que cria um novo objeto de dados de indicador:
//+------------------------------------------------------------------+ //| Create a new indicator data object, return the pointer | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::CreateNewDataInd(const int buffer_num,const datetime time,const double value) { CDataInd* data_ind=new CDataInd(this.m_ind_type,this.m_ind_handle,this.m_ind_id,buffer_num,this.m_symbol,this.m_timeframe,time,value); return data_ind; } //+------------------------------------------------------------------+
Cria um novo objeto da classe CDataInd e retorna um ponteiro para o objeto recém-criado, ou devolve NULL caso o objeto não tenha sido criado.
Método para criar uma lista-séries temporais de dados de indicador:
//+------------------------------------------------------------------+ //| Create indicator data timeseries list | //+------------------------------------------------------------------+ int CSeriesDataInd::Create(const uint required=0) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return false; } //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, //--- while being lower than the available bar number, //--- set the new value of the required history depth for the list else if(required>0 && this.m_amount!=required && required<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetRequiredUsedData(required)) return 0; } //--- For the data[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the list of indicator data objects and set the flag of sorting by timeseries bar time double data[]; ::ArraySetAsSeries(data,true); this.m_list_data.Clear(); this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); ::ResetLastError(); int err=ERR_SUCCESS; //--- In a loop by the number of indicator data buffers for(int i=0;i<this.m_buffers_total;i++) { //--- Get historical data of i buffer of indicator to data[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyBuffer(this.m_ind_handle,i,0,this.m_amount,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- In the loop by amount of required data for(int j=0;j<(int)this.m_amount;j++) { //--- Get time of j bar ::ResetLastError(); datetime time=::iTime(this.m_symbol,this.m_timeframe,j); //--- If failed to get time //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j) if(time==0) { err=::GetLastError(); ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err)); continue; } //--- If failed to create a new indicator data object from i buffer //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j) CDataInd* data_ind=CreateNewDataInd(i,time,data[j]); if(data_ind==NULL) { err=::GetLastError(); ::Print ( DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ)," ",::TimeToString(time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err) ); continue; } //--- If failed to add a new indicator data object to list //--- display the appropriate message with the error description in the journal, remove data object //--- and move on to the next loop iteration by data if(!this.m_list_data.Add(data_ind)) { err=::GetLastError(); ::Print ( DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST)," ",::TimeToString(time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err) ); delete data_ind; continue; } } } //--- Return the size of the created list of indicator data objects return this.m_list_data.Total(); } //+------------------------------------------------------------------+
Toda a lógica do método é descrita em detalhes em sua listagem. O método copia os dados a partir de todos os buffers do indicador para uma matriz, com base nesses dados cria novos objetos de dados de indicador CDataInd e os coloca numa lista-coleção.
Método para atualizar a lista-coleção de dados do indicador:
//+------------------------------------------------------------------+ //| Update indicator data timeseries list and data | //+------------------------------------------------------------------+ void CSeriesDataInd::Refresh(void) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return; } //--- If indicator data timeseries is not used, exit if(!this.m_available) return; double data[1]; //--- Get the current bar time int err=ERR_SUCCESS; ::ResetLastError(); datetime time=::iTime(this.m_symbol,this.m_timeframe,0); //--- If failed to get time //--- display the appropriate message with the error description in the journal and exit if(time==0) { err=::GetLastError(); ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Set the flag of sorting the data list by time this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); //--- If a new bar is present on a symbol and period, if(this.IsNewBarManual(time)) { //--- create new indicator data objects by the number of buffers and add them to the list end for(int i=0;i<this.m_buffers_total;i++) { //--- Get data of the current bar of indicator’s i buffer to data[] array, //--- if failed to get data, display the appropriate message and exit int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Create a new data object of indicator’s i buffer CDataInd* data_ind=CreateNewDataInd(i,time,data[0]); if(data_ind==NULL) return; //--- If the created object was not added to the list - remove this object and exit from the method if(!this.m_list_data.InsertSort(data_ind)) { delete data_ind; return; } } //--- if the size of indicator data timeseries has ecxeeded the requested number of bars, remove the earliest data object if(this.m_list_data.Total()>(int)this.m_required) this.m_list_data.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(time); } //--- Get the list of indicator data objects by the time of current bar start CArrayObj *list=CSelect::ByIndicatorDataProperty(this.GetList(),IND_DATA_PROP_TIME,time,EQUAL); //--- In the loop, by the number of indicator buffers get indicator data objects from the list by loop index for(int i=0;i<this.m_buffers_total;i++) { //--- If failed to get object from the list - exit CDataInd *data_ind=this.GetIndDataByTime(i,time); if(data_ind==NULL) return; //--- Copy data of indicator’s i buffer from current bar int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Add received value of indicator’s i buffer to indicator data object data_ind.SetBufferValue(data[0]); } } //+------------------------------------------------------------------+
A lógica do método é detalhada em sua listagem. O método atualiza os dados de todos os buffers do indicador na barra atual e cria novos objetos para a barra atual numa nova barra e os adiciona à lista-coleção. Quando o tamanho da lista-coleção excede o tamanho necessário, os objetos mais antigos são removidos da lista.
Método que retorna um objeto de dados de indicador por tempo e número de buffer:
//+------------------------------------------------------------------+ //| Return indicator data object by time and buffer number | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::GetIndDataByTime(const int buffer_num,const datetime time) { CArrayObj *list=GetList(IND_DATA_PROP_TIME,time,EQUAL); list=CSelect::ByIndicatorDataProperty(list,IND_DATA_PROP_IND_BUFFER_NUM,buffer_num,EQUAL); return list.At(0); } //+------------------------------------------------------------------+
Aqui, obtemos uma lista contendo objetos de dados de indicador correspondentes ao tempo especificado,
em seguida filtramos a lista resultante para que apenas o objeto do buffer de indicador especificado permaneça nela.
Retornamos o único objeto que permanece na lista.
Método que retorna um objeto de dados de indicador por barra e número de buffer:
//+------------------------------------------------------------------+ //| Return indicator data object by bar and buffer number | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::GetIndDataByBar(const int buffer_num,const uint shift) { datetime time=::iTime(this.m_symbol,this.m_timeframe,(int)shift); if(time==0) return NULL; return this.GetIndDataByTime(buffer_num,time); } //+------------------------------------------------------------------+
Aqui: obtemos o tempo de abertura da barra com base no índice especificado, em seguida, retornamos o objeto obtido usando o método acima.
Método que retorna dados do buffer especificado do indicador de acordo com o tempo:
//+------------------------------------------------------------------+ //| Return data of specified indicator buffer by time | //+------------------------------------------------------------------+ double CSeriesDataInd::BufferValue(const int buffer_num,const datetime time) { CDataInd *data_ind=this.GetIndDataByTime(buffer_num,time); return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue()); } //+------------------------------------------------------------------+
Aqui, obtemos um objeto de dados usando o método GetIndDataByTime() e retornamos o valor do buffer registrado no objeto, ou "valor vazio" se o objeto não foi recebido.
Método que retorna os dados do buffer do indicador especificado de acordo com o índice da barra:
//+------------------------------------------------------------------+ //| Return data of specified indicator buffer by bar index | //+------------------------------------------------------------------+ double CSeriesDataInd::BufferValue(const int buffer_num,const uint shift) { CDataInd *data_ind=this.GetIndDataByBar(buffer_num,shift); return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue()); } //+------------------------------------------------------------------+
Aqui, obtemos o objeto de dados usando o método GetIndDataByBar() e retornamos o valor do buffer registrado no objeto, ou "valor vazio" se o objeto não foi recebido.
Todos os indicadores criados no programa são colocados na coleção de objetos-indicadores criados por nós no artigo 54. Cada um desses objetos contém todos os dados de um indicador padrão ou personalizado. Mas ainda precisamos complementar alguns dados. Para que possamos sempre saber quantos buffers um indicador descrito por um objeto desta classe possui, precisamos adicionar uma variável a ele para armazenar o número de buffers do indicador. Isso é especialmente relevante para indicadores personalizados, onde não podemos saber com antecedência seu número de buffers desenhados. Também precisamos adicionar uma lista-coleção de dados de buffers de indicador, cuja classe acabamos de criar - assim, o objeto do indicador irá armazenar imediatamente as listas de dados de todos os seus buffers, de onde podemos receber dados para cada barra de cada buffer do indicador.
Abrimos o arquivo da classe do objeto-indicador \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh e inserimos nele todas as modificações necessárias:
//+------------------------------------------------------------------+ //| Ind.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "..\..\Objects\BaseObj.mqh" #include "..\..\Objects\Indicators\SeriesDataInd.mqh" //+------------------------------------------------------------------+ //| Abstract indicator class | //+------------------------------------------------------------------+ class CIndicatorDE : public CBaseObj { protected: MqlParam m_mql_param[]; // Array of indicator parameters CSeriesDataInd m_series_data; // Timeseries object of indicator buffer data private: long m_long_prop[INDICATOR_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[INDICATOR_PROP_STRING_TOTAL]; // String properties string m_ind_type_description; // Indicator type description int m_buffers_total; // Total number of indicator buffers //--- Return the index of the array the buffer's (1) double and (2) string properties are located at int IndexProp(ENUM_INDICATOR_PROP_DOUBLE property) const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_INDICATOR_PROP_STRING property) const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_DOUBLE_TOTAL;} //--- Comare (1) structures MqlParam, (2) array of structures MqlParam between each other bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const; bool IsEqualMqlParamArrays(MqlParam &compared_struct[]) const; protected: //--- Protected parametric constructor CIndicatorDE(ENUM_INDICATOR ind_type, string symbol, ENUM_TIMEFRAMES timeframe, ENUM_INDICATOR_STATUS status, ENUM_INDICATOR_GROUP group, string name, string shortname, MqlParam &mql_params[]); public: //--- Default constructor CIndicatorDE(void){;} //--- Destructor ~CIndicatorDE(void); //--- Set buffer's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_INDICATOR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_INDICATOR_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_INDICATOR_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string buffer properties from the properties array long GetProperty(ENUM_INDICATOR_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_INDICATOR_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_INDICATOR_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Get description of buffer's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property); string GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property); string GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property); //--- Return the flag of the buffer supporting the property virtual bool SupportProperty(ENUM_INDICATOR_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_INDICATOR_PROP_STRING property) { return true; } //--- Compare CIndicatorDE objects by all possible properties (for sorting the lists by a specified indicator object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CIndicatorDE objects by all properties (to search for equal indicator objects) bool IsEqual(CIndicatorDE* compared_obj) const; //--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name, (5) indicator ID void SetGroup(const ENUM_INDICATOR_GROUP group) { this.SetProperty(INDICATOR_PROP_GROUP,group); } void SetEmptyValue(const double value) { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value); } void SetName(const string name) { this.SetProperty(INDICATOR_PROP_NAME,name); } void SetShortName(const string shortname) { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname); } void SetID(const int id) { this.SetProperty(INDICATOR_PROP_ID,id); } void SetBuffersTotal(const int buffers_total) { this.m_buffers_total=buffers_total; } //--- Return (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID, //--- (7) empty value of buffers, (8) name, (9) short name, (10) symbol, (11) number of buffers ENUM_INDICATOR_STATUS Status(void) const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);} ENUM_INDICATOR_GROUP Group(void) const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME); } ENUM_INDICATOR TypeIndicator(void) const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE); } int Handle(void) const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE); } int ID(void) const { return (int)this.GetProperty(INDICATOR_PROP_ID); } double EmptyValue(void) const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE); } string Name(void) const { return this.GetProperty(INDICATOR_PROP_NAME); } string ShortName(void) const { return this.GetProperty(INDICATOR_PROP_SHORTNAME); } string Symbol(void) const { return this.GetProperty(INDICATOR_PROP_SYMBOL); } int BuffersTotal(void) const { return this.m_buffers_total; } //--- Return description of (1) type, (2) status, (3) group, (4) timeframe, (5) empty value of indicator, (6) parameter of m_mql_param array string GetTypeDescription(void) const { return m_ind_type_description; } string GetStatusDescription(void) const; string GetGroupDescription(void) const; string GetTimeframeDescription(void) const; string GetEmptyValueDescription(void) const; string GetMqlParamDescription(const int index) const; //--- Return indicator data timeseries CSeriesDataInd *GetSeriesData(void) { return &this.m_series_data; } //--- Display the description of indicator object properties in the journal (full_prop=true - all properties, false - supported ones only) void Print(const bool full_prop=false); //--- Display (1) a short description, (2) description of indicator object parameters in the journal (implementation in the descendants) virtual void PrintShort(void) {;} virtual void PrintParameters(void) {;} //--- Return data of specified buffer from specified bar (1) by index, (2) by bar time double GetDataBuffer(const int buffer_num,const int index); double GetDataBuffer(const int buffer_num,const datetime time); }; //+------------------------------------------------------------------+
Para que a classe veja o objeto da classe-coleção de dados de indicador, anexamos seu arquivo SeriesDataInd.mqh na seção de arquivos de inclusão.
No escopo privado da classe, declaramos um objeto da classe-coleção de dados de indicador m_series_data.
A variável m_buffers_total armazenará o número total de buffers de indicador desenhados. Os objetos de dados de todos esses buffers serão armazenados na coleção de dados dos buffers do indicador.
Os métodos SetBuffersTotal() e BuffersTotal() definem/retornam o número total de buffers desenhados do indicador.
O método GetSeriesData() retorna um ponteiro para a coleção de dados dos buffers de indicador.
Agora abrimos o arquivo da classe da coleção dos indicadores \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh e fazemos as melhorias necessárias.
Durante a criação do indicador, um objeto da classe abstrata do indicador é criado com a especificação de todos os dados de acordo com o tipo do indicador criado. Este objeto indicador é então adicionado à lista-coleção. É no momento de adicionar o objeto-indicador criado à coleção usando o método AddIndicatorToList() que definimos adicionalmente os parâmetros do indicador necessários para sua identificação exata.
Vamos adicionar a este método a indicação do número de buffers desenhados do indicador e a quantidade necessária de dados dos buffers:
//--- (1) Create, (2) add to collection list a new indicator object and set an ID for it CIndicatorDE *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT); int AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0);
No método de criação de um indicador personalizado, passaremos adicionalmente seu número total de buffers desenhados:
int CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int buffers_total, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]);
Declaramos um método, que retorna um ponteiro para o objeto de dados da série temporal do buffer do indicador com base na hora especificada, bem como os métodos para criar, atualizar e retornar dados da coleção de dados de buffers de indicador:
//--- Return (1) indicator object by its ID, (2) the data object of indicator buffer timeseries by time CIndicatorDE *GetIndByID(const uint id); CDataInd *GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time); //--- Display (1) the complete and (2) short collection description in the journal void Print(void); void PrintShort(void); //--- Create (1) timeseries of specified indicator data, (2) all timeseries used of all collection indicators bool SeriesCreate(CIndicatorDE *indicator,const uint required=0); bool SeriesCreateAll(const uint required=0); //--- Update buffer data of all indicators void SeriesRefreshAll(void); void SeriesRefresh(const int ind_id); //--- Return by bar (1) time, (2) number the value of the buffer specified by indicator ID double GetBufferValue(const uint ind_id,const int buffer_num,const datetime time); double GetBufferValue(const uint ind_id,const int buffer_num,const uint shift); //--- Constructor CIndicatorsCollection(); }; //+------------------------------------------------------------------+
Na implementação do método AddIndicatorToList() adicionamos a configuração do número de buffers do indicador e a criação de sua série temporal:
//+------------------------------------------------------------------+ //| Add a new indicator object to collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0) { //--- If invalid indicator is passed to the object - return INVALID_HANDLE if(indicator==NULL) return INVALID_HANDLE; //--- If such indicator is already in the list int index=this.Index(indicator); if(index!=WRONG_VALUE) { //--- Remove the earlier created object, get indicator object from the list and return indicator handle delete indicator; indicator=this.m_list.At(index); } //--- If indicator object is not in the list yet else { //--- If failed to add indicator object to the list - display a corresponding message, //--- remove object and return INVALID_HANDLE if(!this.m_list.Add(indicator)) { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST)); delete indicator; return INVALID_HANDLE; } } //--- If indicator is successfully added to the list or is already there... //--- If indicator with specified ID (not -1) is not in the list - set ID if(id>WRONG_VALUE && !this.CheckID(id)) indicator.SetID(id); //--- Set the total number of buffers and create data timeseries of all indicator buffers indicator.SetBuffersTotal(buffers_total); this.SeriesCreate(indicator,required); //--- Return the handle of a new indicator added to the list return indicator.Handle(); } //+------------------------------------------------------------------+
Como agora o número total de buffers do indicador também é passado para o método AddIndicatorToList(), é necessário adicionar a transferência do valor do número de buffers em todos os métodos de criação de objetos-indicadores. Para indicadores padrão, sabemos seu número exato, já para um indicador personalizado, passamos esse valor no seu método de criação.
Todos esses métodos de classe já tiveram essas alterações. Vamos considerar apenas alguns.
Método para criar o indicador Accelerator Oscillator:
//+------------------------------------------------------------------+ //| Create a new indicator object Accelerator Oscillator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id) { //--- AC indicator possesses no parameters - resize the array of parameter structures ::ArrayResize(this.m_mql_param,0); //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,1); } //+------------------------------------------------------------------+
Ao chamar o método para adicionar um objeto-indicador a uma lista-coleção, indicamos que este indicador tem um buffer desenhado.
Método para criar o indicador Average Directional Movement Index:
//+------------------------------------------------------------------+ //| Create a new indicator object | //| Average Directional Movement Index | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int adx_period=14) { //--- Add required indicator parameters to the array of parameter structures ::ArrayResize(this.m_mql_param,1); this.m_mql_param[0].type=TYPE_INT; this.m_mql_param[0].integer_value=adx_period; //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_ADX,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,3); } //+------------------------------------------------------------------+
Ao chamar o método para adicionar um objeto-indicador a uma lista-coleção, indicamos que este indicador tem três buffers desenhados.
Método para criar um indicador personalizado:
//+------------------------------------------------------------------+ //| Create a new object - custom indicator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, const int buffers_total, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]) { //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe); if(indicator==NULL) return INVALID_HANDLE; //--- Set a group for indicator object indicator.SetGroup(group); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,buffers_total); } //+------------------------------------------------------------------+
Nas variáveis de entrada do método passamos o valor do número de buffers desenhados do indicador, e ao chamar o método para adicionar um objeto-indicador a uma lista-coleção especificamos o número de buffers a serem desenhados e passados para o método.
Método que retorna um ponteiro para o objeto de dados da série temporal do buffer do indicador de acordo com o tempo:
//+------------------------------------------------------------------+ //| Return data object of indicator buffer timeseries by time | //+------------------------------------------------------------------+ CDataInd *CIndicatorsCollection::GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time) { CIndicatorDE *indicator=this.GetIndByID(ind_id); if(indicator==NULL) return NULL; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) return NULL; return buffers_data.GetIndDataByTime(buffer_num,time); } //+------------------------------------------------------------------+
Aqui, obtemos o objeto-indicador a partir da coleção por seu identificador,
a partir do objeto obtemos um ponteiro para sua lista-coleção de dados de buffers,
devolvemos os dados do buffer especificado pela hora de abertura da barra da série temporal.
Método que cria uma série temporal de dados para o indicador especificado:
//+------------------------------------------------------------------+ //| Create data timeseries of the specified indicator | //+------------------------------------------------------------------+ bool CIndicatorsCollection::SeriesCreate(CIndicatorDE *indicator,const uint required=0) { if(indicator==NULL) return false; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data!=NULL) { buffers_data.SetSymbolPeriod(indicator.Symbol(),indicator.Timeframe()); buffers_data.SetIndHandle(indicator.Handle()); buffers_data.SetIndID(indicator.ID()); buffers_data.SetIndBuffersTotal(indicator.BuffersTotal()); buffers_data.SetRequiredUsedData(required); } return(buffers_data!=NULL ? buffers_data.Create(required)>0 : false); } //+------------------------------------------------------------------+
Aqui, ao método é passado um ponteiro para o objeto-indicador e o número necessário de barras criadas de dados de buffers do indicador.
Do objeto-indicador obtemos um ponteiro para sua coleção de dados de buffers,
definimos todos os parâmetros necessários da coleção de dados e
retornamos o resultado da criação da quantidade solicitada de dados de buffers do indicador.
Método que cria todas as séries temporais usadas de todos os indicadores da coleção:
//+------------------------------------------------------------------+ //| Create all timeseries used of all collection indicators | //+------------------------------------------------------------------+ bool CIndicatorsCollection::SeriesCreateAll(const uint required=0) { bool res=true; for(int i=0;i<m_list.Total();i++) { CIndicatorDE *indicator=m_list.At(i); if(!this.SeriesCreate(indicator,required)) res&=false; } return res; } //+------------------------------------------------------------------+
Aqui, num loop sobre o número total de objetos-indicadores na coleção obtém um ponteiro para o próximo objeto-indicador e para a variável res adicionamos seu resultado da criação de uma série temporal de dados dos buffers usando o método acima. No final do loop, retornamos o valor da variável res. Se pelo menos uma série temporal desses buffers não tiver sido criada, esta variável terá o valor false.
Método de atualização dos dados dos buffers de todos os indicadores da coleção:
//+------------------------------------------------------------------+ //| Update buffer data of all indicators | //+------------------------------------------------------------------+ void CIndicatorsCollection::SeriesRefreshAll(void) { for(int i=0;i<m_list.Total();i++) { CIndicatorDE *indicator=m_list.At(i); if(indicator==NULL) continue; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) continue; buffers_data.Refresh(); } } //+------------------------------------------------------------------+
Aqui, num loop percorrendo o número total de objetos-indicadores na coleção obtemos um ponteiro para o próximo objeto indicador, obtemos um ponteiro para o objeto de dados da série temporal dos buffers e atualize a série temporal com o método Refresh().
Método para atualizar dados de buffers do indicador especificado:
//+------------------------------------------------------------------+ //| Update buffer data of the specified indicator | //+------------------------------------------------------------------+ void CIndicatorsCollection::SeriesRefresh(const int ind_id) { CIndicatorDE *indicator=this.GetIndByID(ind_id); if(indicator==NULL) return; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) return; buffers_data.Refresh(); } //+------------------------------------------------------------------+
O identificador do indicador necessário é passado para o método. Usando o método GetIndByID() obtemos um ponteiro para o indicador necessário, obtemos sue objeto-série temporal de dados de buffers e atualizamos a série temporal com o método Refresh().
Métodos que retornam o valor do buffer especificado do indicador especificado segundo o tempo ou índice de barra:
//+------------------------------------------------------------------+ //| Return buffer value by bar time | //| specified by indicator ID | //+------------------------------------------------------------------+ double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const datetime time) { CIndicatorDE *indicator=GetIndByID(ind_id); if(indicator==NULL) return EMPTY_VALUE; CSeriesDataInd *series=indicator.GetSeriesData(); return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,time) : EMPTY_VALUE); } //+------------------------------------------------------------------+ //| Return by bar number the buffer value | //| specified by indicator ID | //+------------------------------------------------------------------+ double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const uint shift) { CIndicatorDE *indicator=GetIndByID(ind_id); if(indicator==NULL) return EMPTY_VALUE; CSeriesDataInd *series=indicator.GetSeriesData(); return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,shift) : EMPTY_VALUE); } //+------------------------------------------------------------------+
Os métodos são idênticos, exceto que o primeiro contém o tempo de abertura da barra e o segundo, o índice da barra.
Obtemos um ponteiro para o indicador necessário na coleção, obtemos um ponteiro para seu objeto da coleção de dados dos buffers e retornamos o valor do buffer especificado usando o método BufferValue() sobrecarregado.
A classe principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh serve para vincular as classes da biblioteca ao "mundo externo"; ela contém métodos de acesso a todos os métodos da biblioteca a partir de programas.
Na seção pública da classe adicionamos métodos para trabalhar com coleções de dados de buffers de indicador://--- Return (1) the indicator collection, (2) the indicator list from the collection CIndicatorsCollection *GetIndicatorsCollection(void) { return &this.m_indicators; } CArrayObj *GetListIndicators(void) { return this.m_indicators.GetList(); } //--- Create all timeseries used of all collection indicators bool IndicatorSeriesCreateAll(void) { return this.m_indicators.SeriesCreateAll();} //--- Update buffer data of all indicators void IndicatorSeriesRefreshAll(void) { this.m_indicators.SeriesRefreshAll(); } void IndicatorSeriesRefresh(const int ind_id) { this.m_indicators.SeriesRefresh(ind_id);} //--- Return the value of the specified buffer by (1) time, (2) number of the bar specified by indicator ID double IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const datetime time) { return this.m_indicators.GetBufferValue(ind_id,buffer_num,time); } double IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const uint shift) { return this.m_indicators.GetBufferValue(ind_id,buffer_num,shift); }
Todos esses métodos chamam os devidos métodos que adicionamos à classe da coleção dos indicadores acima.
No construtor da classe criamos um novo temporizador para atualizar as coleções de dados dos buffers de indicador:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name=::MQLInfoString(MQL_PROGRAM_NAME); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE); this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE); this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1); this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2); this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE); this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE); this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } #endif //--- } //+------------------------------------------------------------------+
No temporizador da classe adicionamos blocos de código para trabalhar com séries temporais de dados de buffers de indicador segundo temporizador e tick (no testador):
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(SDataCalculate &data_calculate) { //--- If this is not a tester, work with collection events by timer if(!this.IsTester()) { //--- Timer of the collections of historical orders and deals, as well as of market orders and positions int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); CTimerCounter* cnt1=this.m_list_counters.At(index); if(cnt1!=NULL) { //--- If unpaused, work with the order, deal and position collections events if(cnt1.IsTimeDone()) this.TradeEventsControl(); } //--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); CTimerCounter* cnt2=this.m_list_counters.At(index); if(cnt2!=NULL) { //--- If unpaused, work with the account collection events if(cnt2.IsTimeDone()) this.AccountEventsControl(); } //--- Timer 1 of the symbol collection (updating symbol quote data in the collection) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); CTimerCounter* cnt3=this.m_list_counters.At(index); if(cnt3!=NULL) { //--- If the pause is over, update quote data of all symbols in the collection if(cnt3.IsTimeDone()) this.m_symbols.RefreshRates(); } //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); CTimerCounter* cnt4=this.m_list_counters.At(index); if(cnt4!=NULL) { //--- If the pause is over if(cnt4.IsTimeDone()) { //--- update data and work with events of all symbols in the collection this.SymbolEventsControl(); //--- When working with the market watch list, check the market watch window events if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } //--- Trading class timer index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID); CTimerCounter* cnt5=this.m_list_counters.At(index); if(cnt5!=NULL) { //--- If unpaused, work with the list of pending requests if(cnt5.IsTimeDone()) this.m_trading.OnTimer(); } //--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); CTimerCounter* cnt6=this.m_list_counters.At(index); if(cnt6!=NULL) { //--- If the pause is over, work with the timeseries list (update all except the current one) if(cnt6.IsTimeDone()) this.SeriesRefreshAllExceptCurrent(data_calculate); } //--- Timer of timeseries collection of indicator buffer data index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID); CTimerCounter* cnt7=this.m_list_counters.At(index); if(cnt7!=NULL) { //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one) if(cnt7.IsTimeDone()) this.IndicatorSeriesRefreshAll(); } } //--- If this is a tester, work with collection events by tick else { //--- work with events of collections of orders, deals and positions by tick this.TradeEventsControl(); //--- work with events of collections of accounts by tick this.AccountEventsControl(); //--- update quote data of all collection symbols by tick this.m_symbols.RefreshRates(); //--- work with events of all symbols in the collection by tick this.SymbolEventsControl(); //--- work with the list of pending orders by tick this.m_trading.OnTimer(); //--- work with the timeseries list by tick this.SeriesRefresh(data_calculate); //--- work with the timeseries list of indicator buffers by tick this.IndicatorSeriesRefreshAll(); } } //+------------------------------------------------------------------+
Todas essas são as melhorias para hoje.
Teste
Para realizar o teste, vamos pegar o Expert Advisor do artigo anterior e
e o armazenamos na nova pasta \MQL5\Experts\TestDoEasy\Part58\ com o novo nome TestDoEasyPart58.mq5.
Testaremos da mesma forma que no Expert Advisor anterior - quatro indicadores, dois dos quais são padrão e dois são personalizados.
A diferença é que no Expert Advisor anterior, criamos objetos de todas as classes diretamente nele, e hoje vamos usar os objetos de classes-coleções de dados dos buffers para os indicadores criados por nós na biblioteca.
Do escopo global removemos ponteiros para objetos de dados de indicadores:
//--- Pointers to indicator data objects CDataInd *data_ma1_0=NULL; CDataInd *data_ma1_1=NULL; CDataInd *data_ma2_0=NULL; CDataInd *data_ma2_1=NULL; CDataInd *data_ama1_0=NULL; CDataInd *data_ama1_1=NULL; CDataInd *data_ama2_0=NULL; CDataInd *data_ama2_1=NULL; //+------------------------------------------------------------------+
No manipulador OnInit() do Expert Advisor adicionamos o número de buffers dos indicadores personalizados criados:
//--- Create indicators ArrayResize(param_ma1,4); //--- Name of indicator 1 param_ma1[0].type=TYPE_STRING; param_ma1[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma1[1].type=TYPE_INT; param_ma1[1].integer_value=13; //--- Horizontal shift param_ma1[2].type=TYPE_INT; param_ma1[2].integer_value=0; //--- Smoothing method param_ma1[3].type=TYPE_INT; param_ma1[3].integer_value=MODE_SMA; //--- Create indicator 1 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,1,INDICATOR_GROUP_TREND,param_ma1); ArrayResize(param_ma2,5); //--- Name of indicator 2 param_ma2[0].type=TYPE_STRING; param_ma2[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma2[1].type=TYPE_INT; param_ma2[1].integer_value=13; //--- Horizontal shift param_ma2[2].type=TYPE_INT; param_ma2[2].integer_value=0; //--- Smoothing method param_ma2[3].type=TYPE_INT; param_ma2[3].integer_value=MODE_SMA; //--- Calculation price param_ma2[4].type=TYPE_INT; param_ma2[4].integer_value=PRICE_OPEN; //--- Create indicator 2 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,1,INDICATOR_GROUP_TREND,param_ma2);
No manipulador OnDeinit() do Expert Advisor removemos a exclusão de objetos criados de indicadores:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove EA graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); //--- Remove created data objects of MA indicators if(CheckPointer(data_ma1_0)==POINTER_DYNAMIC) delete data_ma1_0; if(CheckPointer(data_ma1_1)==POINTER_DYNAMIC) delete data_ma1_1; if(CheckPointer(data_ma2_0)==POINTER_DYNAMIC) delete data_ma2_0; if(CheckPointer(data_ma2_1)==POINTER_DYNAMIC) delete data_ma2_1; //--- Remove created data objects of AMA indicators if(CheckPointer(data_ama1_0)==POINTER_DYNAMIC) delete data_ama1_0; if(CheckPointer(data_ama1_1)==POINTER_DYNAMIC) delete data_ama1_1; if(CheckPointer(data_ama2_0)==POINTER_DYNAMIC) delete data_ama2_0; if(CheckPointer(data_ama2_1)==POINTER_DYNAMIC) delete data_ama2_1; //--- Deinitialize library engine.OnDeinit(); } //+------------------------------------------------------------------+
Comparado ao Expert Advisor anterior, agora o manipulador OnTick() se tornou muito mais conciso:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Handle the NewTick event in the library engine.OnTick(rates_data); //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the timer PressButtonsControl(); // Button pressing control engine.EventsHandling(); // Working with events } //--- Get indicator values by their IDs from data collections of their buffers from bars 0 and 1 double ma1_value0=engine.IndicatorGetBufferValue(MA1,0,0), ma1_value1=engine.IndicatorGetBufferValue(MA1,0,1); double ma2_value0=engine.IndicatorGetBufferValue(MA2,0,0), ma2_value1=engine.IndicatorGetBufferValue(MA2,0,1); double ama1_value0=engine.IndicatorGetBufferValue(AMA1,0,0), ama1_value1=engine.IndicatorGetBufferValue(AMA1,0,1); double ama2_value0=engine.IndicatorGetBufferValue(AMA2,0,0), ama2_value1=engine.IndicatorGetBufferValue(AMA2,0,1); //--- Display the values of indicator buffers to comment on the chart from data objects Comment ( "ma1(1)=",DoubleToString(ma1_value1,6),", ma1(0)=",DoubleToString(ma1_value0,6)," ", "ma2(1)=",DoubleToString(ma2_value1,6),", ma2(0)=",DoubleToString(ma2_value0,6),", \n", "ama1(1)=",DoubleToString(ama1_value1,6),", ama1(0)=",DoubleToString(ama1_value0,6)," ", "ama1(1)=",DoubleToString(ama2_value1,6),", ama1(0)=",DoubleToString(ama2_value0,6) ); //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing pending orders } } //+------------------------------------------------------------------+
Vamos compilar o Expert Advisor e executá-lo no gráfico, especificando nas configurações usar apenas o símbolo atual e o período gráfico atual. Os comentários no gráfico exibirão os dados da primeira e da barra zero (atual) de todos os indicadores criados:
Para maior clareza, os mesmos indicadores com exatamente as mesmas configurações são plotados no gráfico - os dados do indicador nos comentários no gráfico e na janela de dados (Ctrl+D) coincidem e os valores na barra atual são atualizados.
O que vem agora?
No próximo artigo, começaremos a nos preparar para a criação de classes para trabalhar com ticks e livro de ofertas.
Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
Artigos desta série:
Trabalhando com séries temporais na biblioteca DoEasy (Parte 35): objeto "Barra" e lista-série temporal do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 36): objeto das séries temporais de todos os períodos usados do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa
Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais
Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Trabalhando com séries temporais na biblioteca DoEasy (Parte 41): exemplo de indicador multissímbolo multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 42): classe de um objeto de buffer abstrato de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 43): classes de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 44): classe-coleção de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 46): buffers de indicador multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela
Trabalhando com séries temporais na biblioteca DoEasy (Parte 49): indicadores padrão multiperíodos multissímbolos multibuffer
Trabalhando com séries temporais na biblioteca DoEasy (Parte 50): indicadores padrão multiperíodos multissímbolos com deslocamento
Trabalhando com séries temporais na biblioteca DoEasy (Parte 51): indicadores padrão multiperíodos multissímbolos compostos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 52): natureza multiplataforma de indicadores padrão multiperíodos multissímbolos de buffer único
Trabalhando com séries temporais na biblioteca DoEasy (Parte 53): classe do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 54): classes herdeiras do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 55): classe-coleção de indicadores
Trabalhando com séries temporais na biblioteca DoEasy (Parte 56): objeto de indicador personalizado, obtenção de dados a partir de objetos-indicadores numa coleção
Trabalhando com séries temporais na biblioteca DoEasy (Parte 57): objeto de dados do buffer do indicador
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/8787
- 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