Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 Cookbook: Handling Custom Chart Events

MQL5 Cookbook: Handling Custom Chart Events

MetaTrader 5Examples | 17 October 2014, 16:02
5 543 0
Denis Kirichenko
Denis Kirichenko

Introduction

This article is a logical continuation of the article MQL5 Cookbook: Handling Typical Chart Events. It covers methods of work with custom chart events. Here the reader can find examples of developing and handling of custom events. All the ideas discussed in this article were implemented with object-oriented tools.

As the theme of custom events is rather broad, it is the case when a programmer and developer can introduce creativity to their work.


1. Custom Chart Event

It is clear that this event is defined by the user. It is up to a programmer to decide what exactly and which task or a program block could take the form of an event. The MQL5 developers can create their own events, which expands the language capabilities for implementation of complex algorithms.

A custom event is the second possible type of a chart event. The first one is a typical event. Although there is no such a term as "typical chart event" in the Documentation, I still suggest using it for the first ten types of chart events.

The developers suggest only one enumeration for all chart events—ENUM_CHART_EVENT.

According to the Documentation, there are 65535 identifiers of custom events. The first and the last identifiers of the custom events are set by the the explicit values of CHARTEVENT_CUSTOM and CHARTEVENT_CUSTOM_LAST, which is numerically equal to 1000 and 66534 accordingly (Fig.1).

Fig.1 The first and the last identifiers of custom events

Fig.1 The first and the last identifiers of custom events

Simple calculations considering the first and the last identifiers will produce: 66534-1000+1=65535.

Before using custom events, they have to be designed first. In this sense a developer becomes a mastermind and the author of the event concept, which is then gets implemented as an algorithm for the future Expert. It would be useful to have a classification of custom events. This cognitive method will not allow to get rid of the ambiguity but will certainly reduce the degree of it and will arrange the line of reasoning.

Let us consider such a criterion of a custom event as the source. For example, developer sergeev suggested an idea of a trading robot prototype. He divides all events into three groups (Fig.2).

Fig.2 Groups of custom event sources

Fig.2 Groups of custom event sources

Then, according to this main idea, the custom events are to be developed based on their group affiliation.

Let us try to do something simple to start with. At first we shall take the first group, which comprises indicator events. Events that can belong to this group are: creation and deletion of an indicator, receiving a signal for opening and closing of a position. The second group includes events of changing the state of orders and positions. In our example, opening and closing of positions will be in this group. It is all very simple. And, finally, the most complex group for formalization is a group of external events.

Let us take two events: enabling and disabling the manual trading.

Fig.3 Sources of custom events

Fig.3 Sources of custom events

The primary pattern can be established through the deductive method (from the general to the special) (Fig.3). This is the very pattern we are going to use later on for creating types of events in the correspondent class (Table 1).

Table 1 Custom events

Table 1 Custom events

This table can not be called an "event concept" yet, but it is a start. Here is another approach. It is common knowledge that a model of an abstract trading system consists of three subsystems—basic modules (Fig.4).

Fig.4 Model of an abstract trading system

Fig.4 Model of an abstract trading system

Custom events based on the "source" criterion can be classified as events generated in:

  1. the signal subsystem;
  2. subsystem of trailing open positions;
  3. subsystem of money management.

The latter, for instance, can include such events as reaching the permissible drawdown level, increasing a trading volume by a set value, increasing percentage of a loss limit, etc.


2. Handler and Generator of ChartEvent

The following few lines will be dedicated to the handler and generator of chart events. As for handling a custom chart event, its principle is similar to the one of handling of a typical chart event.

A handler, the OnChartEvent() function, takes four constants as parameters. Apparently, the developers used this mechanism to implement the idea of identifying an event and obtaining additional information about it. In my opinion it is a very compact and convenient program mechanism.

Function EventChartCustom() generates a custom chart event. Remarkably, a custom chart event can be created for "own" chart and for an "alien" one. I think, the most interesting article about the meaning of own and alien charts is The Implementation of a Multi-currency Mode in MetaTrader 5.

In my opinion, there is a discord in the fact that the event identifier is of the ushort type in the generator, while in the handler it is of the int type. It would be logical to use the ushort data type in the handler too.


3. Class of Custom Event

As I mentioned before, the event concept is up to the Expert developer. Now we are going to work with the events from the Table 1. At first we will sort the class of the custom event CEventBase and its derivatives (Fig.5).

Fig.5 Hierarchy of event classes

Fig.5 Hierarchy of event classes

The basic class looks as follows:

//+------------------------------------------------------------------+
//| 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;};
  };

The event type is set by the ENUM_EVENT_TYPE enumeration:

//+------------------------------------------------------------------+
//| 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
  };

The data members comprise of the event identifier and the data structure.

The Generate() method of the CEventBase basic class deals with generating an event. The GetId() method returns the event id and the virtual method Validate() checks the event identifier value. At first I included the method of event handling into the class, but later I realized that every event is unique and an abstract method is not sufficient here. I ended up delegating this task to the CEventProcessor class that handles custom events.


4. Custom Event Handler Class

The CEventProcessor class is supposed to generate and handle eight presented events. Data members of the class look like:

//+------------------------------------------------------------------+
//| 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;
//+------------------------------------------------------------------+
  };

Among the list of attributes there are flags of initialization and trade. The first one will not allow the EA to trade if it failed to start correctly. The second one checks permission for trading.

There is also the pointer to the object of the CEventBase type, which works with events of different types using polymorphism. An instance of the CTrade class provides access to trading operations.

Objects of the CiMA type facilitate handling of data received from indicators. To simplify the example, I took two Moving Averages which are going to receive a trading signal. There is also an instance of the "CButton" class that will be used for manual enabling/disabling of the EA.

The methods of the class were divided by the "modules – procedures – functions – macros" principle:

//+------------------------------------------------------------------+
//| 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);
  };

Among the modules there are three that only generate events: the starting one—Start(), the finishing one—Finish() and the main one—Main(). The fourth module ProcessEvent() though is both an event handler and a generator.


4.1 Starting Module

This module is designed to be called in the OnInit() handler.

//+------------------------------------------------------------------+
//| 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;
  }

A pointer to the indicator event object is created in this module. Then the "Indicator creation" event is generated. A button is the last one to be created. It is switched to the "Stop" mode. It means that should the button be pressed, the Expert would stop working.

The SEventData structure is also involved in this method definition. It is a simple container for parameters passed on to the generator of the custom event. Only one field of the structure will be filled here—it is the field of the long type. It will hold EA's magic number.


4.2 Finishing Module

This module is supposed to be called in the OnDeinit() handler.

//+------------------------------------------------------------------+
//| 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);
     }
  }

Here the previous event pointer is cleared and the "Indicator deletion" event is generated. I must note that if a custom event is generated in the OnDeinit() handler, you will get a runtime error 4001 (unexpected external error). Therefore, event generation and handling are carried out within this method without calling OnChartEvent().

Again, EA's magic number will be stored using the SEventData structure.


4.3 Main Module

This module is supposed to be called in the OnTick() handler.

//+------------------------------------------------------------------+
//| 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();
           }
  }

Procedures Open() and Close() are called in this module. The first procedure can generate the "Receiving a signal for opening" event, and the second one—"Receiving a signal for closing" event. The current version of the module is fully functional at a new bar appearance. A class for detecting a new bar was described by Konstantin Gruzdev.


4.4 Module of Event Handling

This module is supposed to be called in the OnChartEvent() handler. This module is the largest in terms of size and functionality.

//+------------------------------------------------------------------+
//| 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;
                 }
              }
           }
     }
  }

It consists of two parts. The first one is handling events connected with a click on the "Button" object. This click will generate an external custom event, which will be dealt with by the handler later.

The second part is designed for processing of generated custom events. It contains two blocks, where after a relevant event has been handled, a new one is generated. The "Receiving a signal for opening" event is processed in the first block. Its successful handling generates a new order event "Opening of a position". The "Receiving a signal for closing" event is processed in the second block. If the signal is handled, then the "Closing of a position" event takes place.

The Expert CustomEventProcessor.mq5 is a good example of using the CEventProcessor class. The EA was designed for creating events and responding to them appropriately. With OPP paradigm, we were able to minimize the source code to a fewer number of lines. EA source code can be found in attachment to this article.

To my mind, there is no need to refer to the mechanism of a custom event every time. There are plenty of minor, insignificant and non-eventful in terms of strategy things that can have a different form.


Conclusion

In this article I tried to illustrate principles of working with custom events in the MQL5 environment. I hope that the ideas covered in this article will be of interest for programmers with different experience, not only novice ones.

I am glad that the MQL5 language is developing. Probably, in the nearest future there will be class templates and may be pointers to functions. Then we shall be able to write a fully-fledged delegate pointing to a method of an arbitrary object.

Source files from the archive can be placed into a project folder. In my case, it is the folder MQL5\Projects\ChartUserEvent.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/1163

Attached files |
How to Access the MySQL Database from MQL5 (MQL4) How to Access the MySQL Database from MQL5 (MQL4)
The article describes the development of an interface between MQL and the MySQL database. It discusses existing practical solutions and offers a more convenient way to implement a library for working with databases. The article contains a detailed description of the functions, the interface structure, examples and some of specific features of working with MySQL. As for the software solutions, the article attachments include the files of dynamic libraries, documentation and script examples for the MQL4 and MQL5 languages.
MQL5 Cookbook: Handling Typical Chart Events MQL5 Cookbook: Handling Typical Chart Events
This article considers typical chart events and includes examples of their processing. We will focus on mouse events, keystrokes, creation/modification/removal of a graphical object, mouse click on a chart and on a graphical object, moving a graphical object with a mouse, finish editing of text in a text field, as well as on chart modification events. A sample of an MQL5 program is provided for each type of event considered.
MQL5 Cookbook: Handling BookEvent MQL5 Cookbook: Handling BookEvent
This article considers BookEvent - a Depth of Market event, and the principle of its processing. An MQL program, handling states of Depth of Market, serves as an example. It is written using the object-oriented approach. Results of handling are displayed on the screen as a panel and Depth of Market levels.
MQL5 Cookbook: Processing of the TradeTransaction Event MQL5 Cookbook: Processing of the TradeTransaction Event
This article considers capabilities of the MQL5 language from the point of view of the event-driven programming. The greatest advantage of this approach is that the program can receive information about phased implementation of a trade operation. The article also contains an example of receiving and processing information about ongoing trade operation using the TradeTransaction event handler. In my opinion, such an approach can be used for copying deals from one terminal to another.