
MQL5-Kochbuch: Der Umgang mit benutzerdefinierten Chartereignissen
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
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
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
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
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
Benutzerspezifische Ereignisse, die auf dem „Source“-Kriterium basieren, können als Ereignisse klassifiziert werden, die in einem der folgenden Teilsysteme generiert werden:
- im Signalsubsystem;
- im Subsystem zum Verfolgen offener Positionen;
- 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
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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.