
Guia Prático MQL5: Processamento de Eventos Personalizados do Gráfico
Introdução
Este artigo é uma continuação lógica do artigo Guia Prático MQL5: Processamento de Eventos Típicos do Gráfico. Ele abrange os métodos de trabalho com os eventos personalizados do gráfico. Aqui o leitor pode encontrar exemplos de desenvolvimento e tratamento de eventos personalizados. Todas as idéias discutidas neste artigo foram implementados com ferramentas orientada a objeto.
Como o tema de eventos personalizados é bastante amplo, é possível que os programadores e desenvolvedores possam apresentar muita criatividade em seus trabalhos.
1. Evento personalizado do Gráfico
É claro que este evento é definido pelo utilizador. Cabe ao programador decidir o que exatamente e qual tarefa ou bloco de programa que poderá ser tomado na forma de um evento. Os desenvolvedores em MQL5 podem criar seus próprios eventos, possibilitando a expansão das capacidades da linguagem em implementar algoritmos complexos.
O evento personalizado é o segundo tipo possível de um evento gráfico. O primeiro é um evento típico. Embora não haja um termo tal como "evento típico do gráfico" na Documentação, eu ainda sugiro usá-lo para os dez primeiros tipos de eventos do gráfico.
Os desenvolvedores sugerem apenas uma enumeração para todos os eventos do gráfico - ENUM_CHART_EVENT.
De acordo com a documentação, existem 65.535 identificadores de eventos personalizados. O primeiro e o último identificador dos eventos personalizados são definidos pelos valores explícitos de CHARTEVENT_CUSTOM e CHARTEVENT_CUSTOM_LAST, sendo numericamente iguais a 1000 e 66.534 respectivamente (Fig.1).
Fig.1 O primeiro e o último identificador de eventos personalizados
Cálculos simples, considerando o primeiro e o último identificador irá produzir: 66534-1000 + 1 = 65535.
Antes de usar os eventos personalizados, eles devem ser projetados primeiro. Neste sentido, um desenvolvedor se torna um mentor e autor do conceito de evento, na qual, então, ele será implementado como um algoritmo para o futuro Expert. Seria útil ter uma classificação dos eventos personalizados. Este método cognitivo não permitirá se livrar da ambiguidade, mas irá certamente reduzir o grau dela e irá ajudar a organizar a linha de raciocínio.
Vamos considerar tal critério de um evento personalizado como fonte. Por exemplo, o desenvolvedor sergeev sugeriu a idéia de um protótipo de robô de negociação. Ele divide todos os eventos em três grupos (Fig.2).
Fig.2 Grupos de fontes de eventos personalizados
Então, de acordo com esta idéia principal, os eventos personalizados devem ser desenvolvidos com base em sua associação ao grupo.
Vamos tentar fazer algo simples para começar. Primeiramente, devemos tomar o primeiro grupo, que inclui os eventos de indicadores. Os eventos que podem pertencer a este grupo são: a criação e exclusão de um indicador, receber um sinal de abertura e fechamento de uma posição. O segundo grupo inclui os eventos de alteração do estado de ordens e posições. Em nosso exemplo, a abertura e o fechamento de posições estará neste grupo. É tudo muito simples. E, finalmente, o grupo mais complexo para formalização é o grupo de eventos externos.
Tomemos dois eventos: a ativação e desativação da negociação manual.
Fig.3 Fontes de eventos personalizados
O padrão primário pode ser estabelecido através do método dedutivo (do geral ao específico) (Fig.3). Este é o mesmo padrão que vamos usar mais tarde para a criação de tipos de eventos na classe correspondente (Tabela 1).
Tabela 1 Eventos personalizados
Esta tabela ainda não pode ser chamada como um "conceito de evento", mas ela será o começo. Aqui está outra abordagem. É do conhecimento de todos que um modelo de um sistema de negociação abstrato consiste de três módulos básicos de subsistemas (Fig.4).
Fig.4 Modelo de um sistema de comércio abstrato
Eventos personalizados com base no critério "fonte" podem ser classificados como eventos gerados em:
- no subsistema de sinal;
- subsistema de rastreio de posições em aberto;
- subsistema de gestão do dinheiro.
Este último, por exemplo, pode incluir eventos como atingir o nível de rebaixamento permitido, aumentar o volume de negociação por um valor definido, aumentar a percentagem do limite de perdas, etc
2. Manipulador e Gerador do ChartEvent
As seguintes linhas serão dedicadas ao manipulador e gerador de eventos de gráfico. O processamento de um evento personalizado do gráfico, a princípio, é semelhante ao tratamento de um evento típico do gráfico.
Um manipulador, a função OnChartEvent(), leva quatro constantes como parâmetros. Aparentemente, os desenvolvedores usaram esse mecanismo para implementar a idéia de identificar um evento e obter informações adicionais sobre isso. Na minha opinião, é um mecanismo do programa muito compacto e prático.
A função EventChartCustom() gera um evento personalizado do gráfico. Notavelmente, um evento personalizado do gráfico pode ser criado para o gráfico "atual" e também para o "exterior". Eu acho que, o artigo mais interessante sobre o significado de gráfico atual e exterior é A Implementação de um Modo Multi-currency (múltiplas moedas) no MetaTrader 5.
Em minha opinião, existe uma discordância no fato do identificador de evento ser do tipo ushort no gerador, enquanto que no manipulador ele é do tipo int. Seria mais lógico usar o tipo de dados ushort no manipulador também.
3. Classe de Evento Personalizado
Como mencionei antes, o conceito de evento depende do desenvolvedor do Expert. Agora vamos trabalhar com os eventos da Tabela 1. Primeiramente, vamos classificar a classe de evento personalizado CEventBase e seus derivados (Fig.5).
Fig.5 hierarquia das classes de eventos
A classe básica é a seguinte:
//+------------------------------------------------------------------+ //| Class CEventBase. | //| Purpose: base class for a custom event | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CEventBase : public CObject { protected: ENUM_EVENT_TYPE m_type; ushort m_id; SEventData m_data; public: void CEventBase(void) { this.m_id=0; this.m_type=EVENT_TYPE_NULL; }; void ~CEventBase(void){}; //-- bool Generate(const ushort _event_id,const SEventData &_data, const bool _is_custom=true); ushort GetId(void) {return this.m_id;}; private: virtual bool Validate(void) {return true;}; };
O tipo de evento é definido pelo enumerador ENUM_EVENT_TYPE:
//+------------------------------------------------------------------+ //| A custom event type enumeration | //+------------------------------------------------------------------+ enum ENUM_EVENT_TYPE { EVENT_TYPE_NULL=0, // no event //--- EVENT_TYPE_INDICATOR=1, // indicator event EVENT_TYPE_ORDER=2, // order event EVENT_TYPE_EXTERNAL=3, // external event };
Os membros de dados compreendem o identificador do evento e a estrutura de dados.
O método Generate() da classe básica CEventBase lida com a geração de um evento. O método GetId() retorna o ID do evento e o método virtual Validate() verifica o valor do identificador do evento. No começo eu incluí o método de manipulação do evento na classe, mas depois eu me dei conta de que cada evento é único e um método abstrato aqui não é suficiente. Eu acabei delegando essa tarefa para a classe CEventProcessor que lida com eventos personalizados.
4. Classe que Manipula Eventos Personalizados
A classe CEventProcessor supostamente gera e manipula oito eventos apresentados. Os membros de dados da classe seguem abaixo:
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+----------------------------Data members--------------------------+ protected: ulong m_magic; //--- flags bool m_is_init; bool m_is_trade; //--- CEventBase *m_ptr_event; //--- CTrade m_trade; //--- CiMA m_fast_ema; CiMA m_slow_ema; //--- CButton m_button; bool m_button_state; //+------------------------------------------------------------------+ };
Entre a lista de atributos, há flags de inicialização e de negociação. O primeiro não permitirá que o EA negocie, caso ele não for iniciado corretamente. O segundo verifica a permissão para negociação.
Há também o ponteiro para o objeto do tipo CEventBase, que funciona com diferentes tipos de eventos, utilizando polimorfismo. Uma instância da classe CTrade fornece acesso as operações de negociação.
Objetos do tipo CiMA facilitam o manuseio dos dados recebidos dos indicadores. Para simplificar o exemplo, eu tomei duas médias móveis, que irão receber um sinal de negociação. Há também uma instância da classe "CButton" que será usada para a ativação/desativação manual do EA.
Os métodos da classe foram divididos pelo princípio de "módulos - procedimentos - funções - macros":
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+-------------------------------Methods----------------------------+ public: //--- constructor/destructor void CEventProcessor(const ulong _magic); void ~CEventProcessor(void); //--- Modules //--- event generating bool Start(void); void Finish(void); void Main(void); //--- event processing void ProcessEvent(const ushort _event_id,const SEventData &_data); private: //--- Procedures void Close(void); void Open(void); //--- Functions ENUM_ORDER_TYPE CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig); ENUM_ORDER_TYPE CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig); bool GetIndicatorData(double &_fast_vals[],double &_slow_vals[]); //--- Macros void ResetEvent(void); bool ButtonStop(void); bool ButtonResume(void); };
Entre os módulos, há três que só geram eventos: o de início Start(), o de término Finish() e o principal Main(). O quarto módulo ProcessEvent() é ao mesmo tempo um manipulador de eventos e um gerador.
4.1 Módulo de Início
Este módulo foi projetado para ser chamado no manipulador OnInit().
//+------------------------------------------------------------------+ //| Start module | //+------------------------------------------------------------------+ bool CEventProcessor::Start(void) { //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+1 event if(this.m_ptr_event.Generate(1,data)) //--- create a button if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50)) if(this.ButtonStop()) { this.m_button_state=false; return true; } } //--- return false; }
Neste módulo é criado um ponteiro para o indicador objeto de evento. Em seguida, o evento "criação do indicador" é gerado. O botão é o último a ser criado. Ele é trocado no modo de "Stop". Isso significa que se o botão for pressionado, o Expert iria parar de trabalhar.
A estrutura SEventData também está relacionada na definição deste método. Ela é um simples recipiente para os parâmetros passados do gerador de evento personalizado. Apenas um campo da estrutura será preenchida aqui - será o campo do tipo long. Ela irá armazenar o número mágico do EA.
4.2 Módulo de Término
Este módulo deve ser chamado no manipulador OnDeinit().
//+------------------------------------------------------------------+ //| Finish module | //+------------------------------------------------------------------+ void CEventProcessor::Finish(void) { //--- reset the event object this.ResetEvent(); //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+2 event bool is_generated=this.m_ptr_event.Generate(2,data,false); //--- process CHARTEVENT_CUSTOM+2 event if(is_generated) this.ProcessEvent(CHARTEVENT_CUSTOM+2,data); } }
Aqui, o ponteiro do evento anterior é eliminado e o evento "remoção do indicador" é gerado. Devo notar que, se um evento personalizado é gerado no manipulador OnDeinit(), você receberá um erro de execução 4001 (Erro externo inesperado). Portanto, a geração e manipulação de eventos são realizados dentro deste método sem chamar a OnChartEvent().
Mais uma vez, o número mágico do EA será armazenado usando a estrutura SEventData.
4.3 Módulo Principal
Este módulo deve ser chamado no manipulador OnTick().
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void CEventProcessor::Main(void) { //--- a new bar object static CisNewBar newBar; //--- if initialized if(this.m_is_init) //--- if not paused if(this.m_is_trade) //--- if a new bar if(newBar.isNewBar()) { //--- close module this.Close(); //--- open module this.Open(); } }
Os procedimentos Open() e Close() são chamados neste módulo. O primeiro procedimento pode gerar o evento "Recebendo um sinal para a abertura", e o segundo o evento "Recebendo um sinal para o fechamento". A versão atual do módulo é totalmente funcional na aparência de uma nova barra. Uma classe para a detecção de uma nova barra foi descrito por Konstantin Gruzdev.
4.4 Módulo de Manipulação de Eventos
Este módulo deve ser chamado no manipulador OnChartEvent(). Este módulo é o maior em termos de tamanho e funcionalidade.
//+------------------------------------------------------------------+ //| Process event module | //+------------------------------------------------------------------+ void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data) { //--- check event id if(_event_id==CHARTEVENT_OBJECT_CLICK) { //--- button click if(StringCompare(_data.sparam,this.m_button.Name())==0) { //--- button state bool button_curr_state=this.m_button.Pressed(); //--- to stop if(button_curr_state && !this.m_button_state) { if(this.ButtonResume()) { this.m_button_state=true; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+7 event ushort curr_id=7; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- to resume else if(!button_curr_state && this.m_button_state) { if(this.ButtonStop()) { this.m_button_state=false; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+8 event ushort curr_id=8; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } } } //--- user event else if(_event_id>CHARTEVENT_CUSTOM) { long magic=_data.lparam; ushort curr_event_id=this.m_ptr_event.GetId(); //--- check magic if(magic==this.m_magic) //--- check id if(curr_event_id==_event_id) { //--- process the definite user event switch(_event_id) { //--- 1) indicator creation case CHARTEVENT_CUSTOM+1: { //--- create a fast ema if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE)) if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE)) if(this.m_fast_ema.Handle()!=INVALID_HANDLE) if(this.m_slow_ema.Handle()!=INVALID_HANDLE) { this.m_trade.SetExpertMagicNumber(this.m_magic); this.m_trade.SetDeviationInPoints(InpSlippage); //--- this.m_is_init=true; } //--- break; } //--- 2) indicator deletion case CHARTEVENT_CUSTOM+2: { //---release indicators bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle()); bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle()); if(!(is_slow_released && is_fast_released)) { //--- to log? if(InpIsLogging) Print("Failed to release the indicators!"); } //--- reset the event object this.ResetEvent(); //--- break; } //--- 3) check open signal case CHARTEVENT_CUSTOM+3: { MqlTick last_tick; if(SymbolInfoTick(_Symbol,last_tick)) { //--- signal type ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam; //--- double open_pr,sl_pr,tp_pr,coeff; open_pr=sl_pr=tp_pr=coeff=0.; //--- if(open_ord_type==ORDER_TYPE_BUY) { open_pr=last_tick.ask; coeff=1.; } else if(open_ord_type==ORDER_TYPE_SELL) { open_pr=last_tick.bid; coeff=-1.; } sl_pr=open_pr-coeff*InpStopLoss*_Point; tp_pr=open_pr+coeff*InpStopLoss*_Point; //--- to normalize prices open_pr=NormalizeDouble(open_pr,_Digits); sl_pr=NormalizeDouble(sl_pr,_Digits); tp_pr=NormalizeDouble(tp_pr,_Digits); //--- open the position if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr, sl_pr,tp_pr)) { //--- to log? if(InpIsLogging) Print("Failed to open the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+5 event ushort curr_id=5; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- break; } //--- 4) check close signal case CHARTEVENT_CUSTOM+4: { if(!this.m_trade.PositionClose(_Symbol)) { //--- to log? if(InpIsLogging) Print("Failed to close the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+6 event ushort curr_id=6; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } //--- break; } //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 6) position closing case CHARTEVENT_CUSTOM+6: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_OUT) { //--- to log? if(InpIsLogging) { Print("\nClosed position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 7) stop trading case CHARTEVENT_CUSTOM+7: { datetime stop_time=(datetime)_data.dparam; //--- this.m_is_trade=false; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is stopped at: %s", TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } //--- 8) resume trading case CHARTEVENT_CUSTOM+8: { datetime resume_time=(datetime)_data.dparam; this.m_is_trade=true; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is resumed at: %s", TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } } } } }
Ele consiste de duas partes. A primeira é para lidar com eventos relacionados ao clique sobre o objeto "Button". Este clique irá gerar um evento externo personalizado, que será tratado pelo manipulador mais tarde.
A segunda parte é projetada para o processamento de eventos personalizados que foram gerados. Ele contém dois blocos, onde após um evento relevante tem sido tratado, um novo é gerado. O evento "Recebendo um sinal de abertura" é processado no primeiro bloco. Seu manuseio bem sucedido gera uma nova ordem de evento "Abertura de uma posição". O evento "Recebendo um sinal para o fechamento" é processado no segundo bloco. Se o sinal é tratado, então o evento "fechamento de uma posição" acontece.
The Expert CustomEventProcessor.mq5 é um bom exemplo do uso da classe CEventProcessor. A EA foi projetado para a criação de eventos e responder a elas de forma adequada. Com o paradigma da POO, fomos capazes de minimizar o número de linhas do código-fonte. O código fonte do EA pode ser encontrado em anexo neste artigo.
A meu ver, não há necessidade de referir-se toda hora a um mecanismo de evento personalizado. Há vários coisas menores, insignificantes e menos agitadas em termos de estratégia que podem ter uma forma diferente.
Conclusão
Neste artigo eu tentei ilustrar os princípios de trabalhar com os eventos personalizados no ambiente MQL5. Eu espero que as ideias abordadas neste artigo seja de interesse aos programadores com diferentes experiências, não apenas aos novatos.
Eu fico feliz que a linguagem MQL5 esteja se desenvolvendo. Provavelmente, em um futuro próximo, haverá modelos de classe e que possa apontar para as funções. Então seremos capazes de escrever e delegar um ponteiro com direito pleno para um método de um objeto arbitrário.
Os arquivos fonte do arquivo podem ser colocados em uma pasta do projeto. No meu caso, na pasta MQL5\Projects\ChartUserEvent.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1163





- 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