English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
MQL5-Kochbuch: Der Umgang mit benutzerdefinierten Chartereignissen

MQL5-Kochbuch: Der Umgang mit benutzerdefinierten Chartereignissen

MetaTrader 5Beispiele | 13 Juni 2016, 12:41
763 0
Denis Kirichenko
Denis Kirichenko

Einleitung

Der Artikel stellt eine logische Fortsetzung des Artikels MQL5-Kochbuch dar. Der Umgang mit typischen Chartereignissen. Er deckt dabei Methoden zum Arbeiten mit benutzerdefinierten Chartereignissen ab. Hier findet der interessierte Leser Beispiele zur Entwicklung und zur Behandlung von benutzerdefinierten Ereignissen. Alle Ideen, die in diesem Artikel diskutiert werden werden, wurden mithilfe von objektorientierten Tools implementiert.

Da das Feld benutzerdefinierter Ereignisse relativ groß ist, handelt es sich um einen Fall, bei dem Programmierer und Entwickler gleichermaßen ihrer Kreativität freien Lauf lassen können.

1. Benutzerdefiniertes Chartereignis

Es bedarf keiner Erwähnung, dass ein derartiges Ereignis durch den Benutzer definiert wird. Es bleibt also vollkommen dem Programmierer überlassen, darüber zu entscheiden, was genau bzw. welche/r Aufgabe/Programmblock die Form des Ereignisses annehmen soll. Die MQL5-Entwickler können ihre eigenen Ereignisse kreieren, wodurch die Möglichkeiten der Sprache zur Implementation komplexer Algorithmen ausgeweitet werden.

Benutzerdefinierte Ereignisse stellen den zweiten Typ von Chartereignissen dar. Der erste Typ ist ein typisches Ereignis. Obgleich es in der Dokumentation an keiner Stelle den Ausdruck „typisches Chartereignis“ gibt, schlage ich dennoch vor, ihn für die ersten zehn Typen von Chartereignissen zu verwenden.

Die Entwickler schlagen nur eine Aufzählung für alle Chartereignisse vor—ENUM_CHART_EVENT.

Entsprechend der Dokumentation gibt es insgesamt 65535 benutzerdefinierte Ereignisidentifikatoren. Die ersten und die letzten Identifikatoren des benutzerdefinierten Ereignisses werden durch die expliziten Werte von CHARTEVENT_CUSTOM bzw. CHARTEVENT_CUSTOM_LAST eingestellt, die numerisch identisch mit 1000 bzw. 66534 sind (Abb. 1).

Abb. 1 Die ersten und die letzten Identifikatoren eines benutzerdefinierten Ereignisses

Abb. 1 Die ersten und die letzten Identifikatoren eines benutzerdefinierten Ereignisses

Einige simple Kalkulationen betreffend die ersten und letzten Identifikatoren führen zu: 66534-1000+1=65535

Bevor man benutzerdefinierte Ereignisse benutzen kann, müssen diese zunächst konzipiert werden. Man könnte daher sagen, dass ein Entwickler gewissermaßen zu einer Art Superhirn, d.h. zum Autor eines Ereigniskonzepts, mutiert, welches dann als ein Algorithmus für zukünftige EAs implementiert wird. Es wäre sehr sinnvoll, wenn es eine Klassifizierung benutzerdefinierter Ereignisse gäbe. Diese kognitive Methode wird es zwar nicht erlauben, Mehrdeutigkeit vollends zu überwinden, aber immerhin ihren Grad signifikant zu reduzieren.

Begeben wir uns auf die Suche nach einem Kriterium für ein benutzerspezifisches Ereignis, das als Basis dienen soll. Beispiel: Der Entwickler sergeev hat die Idee eines neuartigen Handelsroboters publik gemacht. Er teilt alle Ereignisse in drei Gruppen ein (Abb. 2).

Abb. 2 Gruppen von Quellen benutzerdefinierter Ereignisse

Abb. 2 Gruppen von Quellen benutzerdefinierter Ereignisse

Dann, basierend auf der Hauptidee, werden die benutzerspezifischen Ereignisse entsprechend ihrer Konzernzugehörigkeit entwickelt.

Lassen Sie uns zum Anfang mit etwas Einfachem anfangen. Nehmen wir uns zunächst die erste Gruppe vor, die die Indikatorereignisse enthält. Ereignisse, die zu dieser Gruppe gehören könnten, sind: Generieren und Löschen eines Indikators sowie das Empfangen eines Signals zum Öffnen/Schließen einer Position. Die zweite Gruppe schließt Ereignisse ein, die den Zustand von Aufträgen und Positionen verändern. Für unser Beispiel gilt, dass sich in dieser Gruppe das Öffnen und das Schließen von Positionen befinden wird. Alles relativ simpel. Schließlich muss nur noch gesagt werden, dass die komplexeste Gruppe zur Formalisierung eine Gruppe externer Ereignisse darstellt.

Lassen Sie uns zwei Ereignisse herausgreifen: das Aktivieren und das Deaktivieren manuellen Tradings.

Abb 3. Quellen benutzerdefinierter Ereignisse

Abb 3. Quellen benutzerdefinierter Ereignisse

Die primäre Muster kann mithilfe der deduktiven Methode eingerichtet werden (vom Allgemeinen zum Spezifischen). Dies ist genau dasjenige Muster, das wir später zum Kreieren verschiedener Typen von Ereignissen in der korrespondieren Klasse verwenden werden (Tabelle 1).

Tabelle 1 Benutzerdefinierte Ereignisse

Tabelle 1 Benutzerdefinierte Ereignisse

Diese Tabelle kann zu diesem Zeitpunkt sicherlich noch nicht als ein vollständiges „Ereigniskonzept“ tituliert werden, aber sie ist schon mal ein guter Anfang. Und hier ein weiterer Ansatz. Es ist allgemein bekannt, dass ein Modell eines abstrakten Handelssystems aus drei untergeordneten Teilsystemen besteht - aus sogenannten Basismodulen (Abb.4).

Abb. 4 Modell eines abstrakten Handelssystems

Abb. 4 Modell eines abstrakten Handelssystems

Benutzerspezifische Ereignisse, die auf dem „Source“-Kriterium basieren, können als Ereignisse klassifiziert werden, die in einem der folgenden Teilsysteme generiert werden:

  1. im Signalsubsystem;
  2. im Subsystem zum Verfolgen offener Positionen;
  3. im Subsystem, das für das Geldmanagement verantwortlich ist.

Beispielsweise kann letztgenanntes derartige Ereignisse beinhalten, wenn das zulässige Drawdownlevel erreicht wird, wodurch das Tradingvolumen um einen festgesetzten Wert erhöht wird, das Loss-Limit prozentual ansteigt, usw.

2. ChartEvent-Generator und -Behandler

Die folgenden Zeilen werden explizit auf den Behandler sowie den Generator eines Chartereignisses eingehen. Was die Behandlung eines benutzerspezifischen Ereignisses betrifft, so ist dessen Prinzip ähnlich dem der Behandlung eines typischen Chartereignisses.

Ein Behandler, die Funktion OnChartEvent(), benötigt insgesamt vier Konstanten als Parameter. Augenscheinlich benutzen die Entwickler diesen Mechanismus, um die Idee umzusetzen, ein Ereignis zu identifizieren und zusätzliche Informationen darüber zu erhalten. Meiner Ansicht nach handelt es sich dabei um einen ebenso kompakten wie bequemen Programmmechanismus.

Die Funktion EventChartCustom() generiert ein benutzerdefiniertes Chartereignis. Bemerkenswert hieran: Ein benutzerdefiniertes Chartereignis kann entweder für einen „eigenen“ oder aber auch für einen „fremden“ Chart erstellt werden. Ich glaube, dass es sich bei dem Artikel Die Umsetzung eines Multiwährungsmodus in MetaTrader 5 zweifellos um den interessantesten Artikel über die Bedeutung von eigenen und fremden Charts handelt.

Meiner Meinung nach gibt es eine Disharmonie betreffend den Fakt, dass der Ereignisidentifikator im Generator den Typ ushort, im Behandler hingegen den Typ int aufweist. Es wäre nur logisch, den Datentyp ushort ebenfalls im Behandler zu benutzen.

3. Klasse des benutzerspezifischen Ereignisses

Wie bereits erwähnt, obliegt das Konzept des Ereignisses voll und ganz dem Entwickler des EAs. Wir werden nun mit den Ereignissen der Tabelle 1 arbeiten. Zunächst werden wir die Klasse des benutzerspezifischen Ereignisses CEventBase und die seiner Derivate (Fig.5) sortieren.

Abb. 5 Hierarchie von Ereignisklassen

Abb. 5 Hierarchie von Ereignisklassen

Die Basisklasse sieht wie folgt aus:

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

Der Ereignistyp wird durch die Aufzählung ENUM_EVENT_TYPE eingestellt.

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

Die DataMembers bestehen aus dem Ereignisidentifikator als auch aus der Datenstruktur.

Die Methode Generate() der Basisklasse CEventBase sorgt für die Generierung des Ereignisses. Die Methode GetId() gibt die Ereignis-ID zurück und die virtuelle Methode Validate() sorgt für eine Überprüfung des Werts des Ereignisidentifikators. Zuerst habe ich die Methode zur Behandlung von Ereignissen in die Klasse mit einbezogen, allerdings musste ich später bemerken, dass jedes Ereignis einzigartig ist und dass eine abstrakte Methode nicht ausreicht. Schlussendlich bin ich dazu übergangen, diese Aufgabe der CEventProcessor-Klasse zu übertragen, die benutzerdefinierte Ereignisse händelt.

4. Benutzerspezifische Ereignisbehandlerklassen

Die Klasse CEventProcessor soll insgesamt acht verschiedene Ereignisse generieren und behandeln. Die DataMembers der Klasse sehen wie folgt aus:

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

Zu der Liste der Attribute zählen auch Initialisierungs- sowie Tradingflaggen. Die erste wird es einem EA nicht gestatten, zu traden, falls kein ordnungsgemäßer Start gegeben ist. Die zweite überprüft die Erlaubnis zum Traden.

Außerdem gibt es noch einen Pointer, der auf das Objekt des Typs CEventBase zeigt, welches mit Ereignissen verschiedenen Typs via Polymorphismus arbeitet. Eine Instanz der Klasse CTrade ermöglicht den Zugang zu Handelsoperationen.

Objekte des Typs CiMA sorgen für die Behandlung von Daten, die vom Indikator erhalten werden. Um das Beispiel zu simplifizieren, habe ich mir zwei gleitende Mittelwerte genommen, die ein Handelssignal erhalten sollen. Ferner gibt es noch eine Instanz der Klasse „CButton“, die für ein manuelles Aktivieren/Deaktivieren des EAs verwendet werden kann.

Die Methoden der Klasse werden entsprechend des „Module-Prozeduren-Funktionen-Makros“-Prinzips kategorisiert:

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

Zu den Modulen zählen dabei drei, die nur Ereignisse generieren: eins zum Staren—Start(), eins zum Beenden—Finish() und das hauptsächliche—Main(). Das vierte Modul ProcessEvent() ist jedoch beides: ein Ereignisbehandler als auch ein Generator.

4.1 Das Startmodul

Dieses Modul wurde dafür designet, vom Behandler OnInit() aufgerufen zu werden.

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

In ihm wird ein Pointer kreiert, der in Richtung des Indikatorereignisobjekts zeigt. Daraufhin wird ein „Indikatorgenerierungs“-Ereignis in die Wege geleitet. Zum Schluss wird noch ein Button kreiert. Dieser leitet den „Stopp“-Modus ein. Betätigte man also den Button, so würde der Expert-Advisor aufhören, zu arbeiten.

Auch die Struktur SEventData ist in dieser Methodendefinition enthalten. Es handelt sich hierbei um einen simplen Container für Parameter, die dem Generator des benutzerspezifischen Ereignisses übergeben werden. Hier wird lediglich ein einziges Feld der Struktur ausgefüllt sein - das Feld des Typs long. Es enthält die magische Zahl des EAs.

4.2 Das Endmodul

Dieses Modul wurde dafür designet, vom Behandler OnDeinit() aufgerufen zu werden.

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

Hier wird der vorherige Ereignispointer gelöscht und das „Indikator-löschen“-Ereignis generiert. Ich führe für alle Fälle an, dass, falls ein benutzerdefiniertes Ereignis im OnDeinit()-Behandler generiert wird, Sie den Laufzeitfehler (Runtime Error) 4001 (unerwarteter, externer Fehler) erhalten werden. Daher werden die Ereignisgenerierung und -behandlung innerhalb dieser Methode ohne einen Aufruf von OnChartEvent() ausgeführt.

Und noch einmal gilt: Die magische Zahl des EAs wird mithilfe der Struktur SEventData gespeichert.

4.3 Das Hauptmodul

Dieses Modul soll vom OnTick()-Behandler aufgerufen werden.

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

In ihm werden die beiden Prozeduren Open() und Close() aufgerufen. Die erste Prozedur kann dabei ein „Empfangen-eines-Signals-zum-Öffnen“-Ereignis und die zweite ein „Empfangen-eines-Signals-zum-Schließen“-Ereignis generieren. Die aktuelle Version des Moduls ist beim Auftreten eines neuen Balkens voll funktionsfähig. Eine Klasse zum Entdecken eines neuen Balkens wurde bereits von Konstantin Gruzdev beschrieben.

4.4 Das Modul des Ereignisbehandlers.

Dieses Modul wurde dafür designet, vom Behandler OnChartEvent() aufgerufen zu werden. Was die Größe und Funktionalität betrifft, so ist dieses Modul das größte.

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

Es besteht aus zwei Teilen. Der erste Teil behandelt Ereignisse, die mittels eines Klicks auf das „Button“-Objekt verknüpft werden. Ein derartiger Klick wird ein externes, benutzerspezifisches Ereignis generieren, welches später vom Behandler abgearbeitet werden wird.

Der zweite Teil wurde speziell dafür designt, generierte, benutzerspezifische Ereignisse zu verarbeiten. Er besteht aus zwei Blöcken, in denen nach der Behandlung eines relevanten Ereignisses ein neues generiert wird. Das Ereignis „Empfangen eines Signals zum Öffnen“ wird im ersten Block verarbeitet. Seine erfolgreiche Behandlung generiert ein neues Auftragsereignis, das sich „Öffnen einer Position“ nennt. Das „Empfangen-eines-Signals-zum-Schließen“-Ereignis wird wie erwartet im zweiten Block verarbeitet. Wenn das Signal behandelt wird, stellt sich das Ereignis „Schließung einer Position“ ein.

Der EA CustomEventProcessor.mq5 ist ein sehr gutes Beispiel dafür, wie die Klasse CEventProcessor angewendet wird. Der EA wurde speziell dafür designt, Ereignisse zu generieren und adäquat auf sie zu reagieren. Mit einem Paradigma, das voll und ganz auf objektorientiertes Programmieren setzt, war es uns möglich, den Sourcecode minimal zu halten, sprich ihn auf wenige Zeilen zu reduzieren. Sie finden den Quelltext des EAs - wie so oft - im Anhang des Artikels.

Meiner Meinung nach gibt es übrigens keinen Grund dafür, jedes Mal auf die Mechanismen eines benutzerspezifischen Ereignisses zu verweisen. Es gibt jede Menge untergeordnete, irrelevante und wenig ereignisreiche, strategiebezogene Dinge, die eine andere Form haben können.

Fazit

Ich habe mich in diesem Artikel bemüht, die Prinzipien des Arbeitens mit benutzerspezifischen Ereignissen (in einer MQL5-Umgebung) zu illustrieren. Ich hoffe, dass meine Ideen nicht nur Neulingen zugutekommen werden, sondern auch dem ein oder anderen Fortgeschrittenen noch gute Dienste leisten können.

Insbesondere freue ich mich natürlich, dass sich die Sprache MQL5 immer mehr entwickelt. Möglicherweise hält ja die nahe Zukunft sogar Klassentemplates also auch Funktionspointer für uns bereit. Dann werden wir in der Lage sein, ein voll ausgereiftes Pointing zu schreiben, das auf die Methode eines beliebigen Objekts zeigt.

Die Source-Dateien des Archivs können in dem Projektordner platziert werden. In meinem Fall wäre dies MQL5\Projects\ChartUserEvent.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/1163

Beigefügte Dateien |
MQL5-Kochbuch: Verarbeitung des TradeTransaction-Ereignisses MQL5-Kochbuch: Verarbeitung des TradeTransaction-Ereignisses
Dieser Artikel beschäftigt sich mit den Möglichkeiten der MQL5-Sprache aus Sicht des ereignisgesteuerten Programmierens. Der größte Vorteil dieses Ansatzes besteht darin, dass das Programm Informationen über die schrittweise Umsetzung von Handelsoperationen empfangen kann. Der Artikel enthält ferner Beispiele zum Empfangen und Verarbeiten von Informationen über Handelsoperationen mithilfe des TradeTransaction-Ereignisbehandlers. Meiner Meinung nach eignet sich solch ein Ansatz vorzüglich dafür, Deals von einem Terminal in Richtung eines anderen zu kopieren.
Wie man ein Handelskonto auf die Migration zu virtuellem Hosting vorbereitet Wie man ein Handelskonto auf die Migration zu virtuellem Hosting vorbereitet
Das MetaTrader Client Terminal ist perfekt für die Automatisierung von Handelsstrategien geeignet. Es bietet alle nötigen Werkzeuge für Entwickler von Handelsrobotern – die leistungsstarke, C++-basierte Programmiersprache MQL4/MQL5, die praktische Entwicklungsumgebung MetaEditor und einen Multithreading-fähigen Strategietester, der verteiltes Rechnen im MQL5 Cloud Network unterstützt. In diesem Beitrag erfahren Sie, wie Sie Ihr Client Terminal mit allen benutzerdefinierten Elementen in eine virtuelle Umgebung migrieren können.
Neuronale Netzwerke der dritten Generation: Tiefe Netzwerke Neuronale Netzwerke der dritten Generation: Tiefe Netzwerke
In diesem Beitrag widmen wir uns einer neuen und vielversprechenden Richtung des maschinellen Lernens: dem tiefen Lernen oder, genauer gesagt, tiefen neuronalen Netzwerken. Wir sehen uns kurz noch einmal die zweite Generation der neuronalen Netzwerke, die Architektur ihrer Verknüpfungen und die wichtigsten Typen, Methoden und Regeln des Einlernens sowie ihre wichtigsten Unzulänglichkeiten an und gehen dann zur Geschichte der Entwicklung der dritten Generation der neuronalen Netzwerke, ihren wichtigsten Typen, Besonderheiten und Einlernmethoden über. Wir führen praktische Experimente zum Aufbau und zum Einlernen eines tiefen neuronalen Netzwerks durch, eingeleitet durch die Gewichte eines gestackten Autoencoders mit realen Daten. Alle Phasen von der Auswahl der Eingabedaten bis zur Ableitung von Messwerten werden detailliert besprochen. Der letzte Teil des Beitrags liefert eine Softwareumsetzung eines tiefen neuronalen Netzwerks in einem Expert Advisor mit eingebautem Indikator auf Basis von MQL4/R.
Random-Forest-Vorhersage-Trends Random-Forest-Vorhersage-Trends
Dieser Artikel widmet sich der Verwendung des Rattle-Pakets zur automatischen Suche nach Mustern zur Vorhersage von Long- und Short-Positionen von Forex-basierten Währungspaaren. Dieser Artikel richtet sich an Neulinge ebenso wie an erfahrene Trader.