Die Umsetzung des Mehrwährungsmodus in MetaTrader 5

Konstantin Gruzdev | 14 März, 2016

Einleitung

Es gibt derzeit zahlreiche fertig entwickelte Handelssysteme, Indikatoren und Expert Advisors für mehrere Währungen. Dennoch stehen die Entwickler weiterhin vor den besonderen Problemen der Entwicklung von Mehrwährungssystemen.

Mit der Veröffentlichung des MetaTrader 5 Client Terminals und der Programmiersprache MQL5 haben wir eine neue Möglichkeit erhalten, einen vollwertigen Mehrwährungsmodus und somit effizientere Mehrwährungsroboter und -indikatoren umzusetzen. Diese neuen Möglichkeiten sind das Thema dieses Beitrags.

Übersicht über die herkömmliche Herangehensweise

In unserem Fall ist die herkömmliche Herangehensweise der Versuch, ein Mehrwährungssystem auf Basis der Standardfunktionen OnTick() und OnCalculate() umzusetzen, die die start()-Funktion aus MQL4 ersetzt haben. Vereinfacht gesagt, werden beim Erscheinen eines neuen Ticks oder Balkens im aktuellen Diagramm alle Währungspaare (die am Mehrwährungssystem beteiligt sind) sequentiell für die anschließende Analyse und Entscheidungsfindung abgefragt.

Die Probleme dieser Herangehensweise sind:

  1. Die Abhängigkeit des gesamten Systems von eingehenden Ticks auf einem Handelssymbol des aktuellen Diagramms.

    Bei einem schnellen Markt, bei dem häufig Ticks für das Symbol des aktuellen Diagramms erscheinen, gibt es keine wirklichen Probleme. Bei einem langsamen Markt, beispielsweise in der Nacht, erscheinen Ticks möglicherweise sehr selten – jede halbe Minute oder noch seltener. Während des Intervalls zwischen dem Eingehen seltener Ticks "schläft" das gesamte Mehrwährungssystem, obwohl die Veränderungen auf den anderen Symbolen ziemlich drastisch sein können.

    Dieses Defizit ist nicht ausschlaggebend, wenn das System auf große Timeframes ausgelegt ist. Doch je kleiner der Timeframe, desto stärker die Auswirkungen. Die Welt wird von Tag zu Tag schneller, Computer können immer mehr Informationen pro Zeiteinheit verarbeiten und deshalb sind mehr Menschen bereit, mit kleinen Zeiträumen und sogar Ticks zu arbeiten.

  2. Die Komplexität der Synchronisierung der historischen Daten auf allen in einem Mehrwährungssystem verwendeten Symbolen.

    "Das System des Charts-Aufbaus in MT4 ist so programmiert, dass nur die Bars angezeigt werden, in deren Zeiträumen mindestens eine Preisänderung stattgefunden hat. Wenn es innerhalb von einer Minute keine Preisänderung gab, dann tritt im einminütigen Chart eine Gap aus einem Bar", heißt es am Anfang des Beitrags Charts ohne "Gaps" (Lücken). [Anm. d. Übers.: bestehende Übersetzung unter https://www.mql5.com/de/articles/1407 übernommen.]

    Diese Herangehensweise an den Aufbau eines Diagramms besteht auch in MetaTrader 5. Das heißt, die gleiche Anzahl von Balken im Diagramm jedes Symbols bedeutet nicht, dass sie zeitlich synchronisiert werden. So kann der hundertste Balken eine unterschiedliche Eröffnungszeit für jedes Symbol haben. Deshalb ist es während des Aufbaus und der Neuberechnung eines Mehrwährungsindikators wichtig, sicherzustellen, dass alle Balken einander für jedes Symbol entsprechen.

    Das ist ebenfalls nicht besonders wichtig, wenn das System mit einem großen Timeframe arbeitet, da die Wahrscheinlichkeit, einen Balken zu verpassen, mit der Erhöhung des Zeitraums deutlich kleiner wird. Dennoch kann man nie zu vorsichtig sein. Man kann nie wissen, ob nicht jemand untätig ist.

    Wir sollten die Synchronisierungszeit des aktuellen unvollständigen Balkens separat notieren. Wenn ein neuer Balken im aktuellen Diagramm erscheint, bedeutet das nicht, dass auch auf anderen Symbolen neue Balken entstanden sind. Deshalb kann der Versuch, den Preis eines neuen Balkens mithilfe der Funktionen CopyXXXX() über ein anderes Symbol abzurufen, dazu führen, dass Sie den Preis des vorherigen Balkens für das Symbol oder schlicht einen Kopierfehler erhalten. Auch kann ein neuer Balken auf einem anderen Symbol wesentlich früher entstehen als auf dem aktuellen. Dies kann sich auch auf die Genauigkeit der Einschätzung der Situation auswirken.

    Der Beitrag Erstellen eines Mehrwährungsindikators mithilfe einer Reihe von Indikator-Zwischenpuffern beschreibt Optionen, die das Problem der Synchronisierung historischer Daten mehr oder weniger lösen.

  3. Ein weiterer wichtiger Punkt für die Datensynchronisierung: Wie finden wir heraus, ob eine Aktualisierung der Historie eines bestimmten Handelssymbols stattgefunden hat?

    Zum Beispiel gibt es bei der Konstruktion eines Einwährungsindikators keine Probleme. Wenn die Eingabevariable prev_calculated der Funktion OnCalculate() auf Null gesetzt wurde, berechnen wir den Indikator neu. Doch was tun wir, wenn eine Aktualisierung der Historie des Symbols nicht im aktuellen Diagramm stattgefunden hat? Das kann jederzeit passieren und möglicherweise muss der Mehrwährungsindikator neu berechnet werden. Die Antwort auf diese Frage ist ziemlich wichtig.

Ohne auf andere Details einzugehen, sehen wir, dass diese drei Punkte ausreichen, um so viele Fragen aufzuwerfen, dass der Code des Mehrwährungs-EAs oder -indikators zu groß werden würde. Doch das volle Ausmaß des Problems wurde nicht gelöst...

Neue Hoffnung mit der OnTimer()-Funktion

Die neue Möglichkeit des MQL-Programms, das Timer-Ereignis und den Standard-Ereignis-Handler OnTimer() zu erzeugen, hat für Hoffnung auf das Aufkommen neuer Arten von Mehrwährungssystemen gesorgt. Dies liegt einerseits daran, dass der Mehrwährungs-Expert-Advisor/-indikator sich nun vom Eingang von Ticks nach Symbol im aktuellen Diagramm lösen kann, andererseits, dass wir die Arbeit des EAs mit der Zeit kontrollieren können. Aber...

Damit werden die in Paragraph 1 des vorherigen Abschnitts beschriebenen Probleme gelöst und natürlich bietet dies einige Vorteile. Doch genauso wie beim Eingang des NewTick-Ereignisses ist es mit dem Eingang des Timer-Ereignisses erforderlich, alle Währungspaare sequentiell abzufragen, um die Änderungen zu verfolgen. Manchmal muss dies ziemlich oft geschehen, was den Ressourcenverbrauch der MQL5-Programme deutlich hebt.

Die in Paragraph 2 und 3 identifizierten Probleme bleiben auf dem gleichen Lösungsniveau. Zudem müssen die speziellen Probleme der OnTimer()-Funktion gelöst werden. Zum Beispiel die Frage der Initialisierung/Deinitialisierung des Timers, seine Arbeit an Wochenenden usw.

Ohne die offensichtlichen Vorteile des Ereignis-Handlers OnTimer() schmälern zu wollen, muss festgehalten werden, dass damit immer noch kein vollwertiger Mehrwährungsmodus umgesetzt werden kann.

Neue Möglichkeiten mit der OnChartEvent()-Funktion

Die Einschränkungen der oben erwähnten Standardfunktionen hängen mit ihrer engen Spezialisierung zusammen: Sie sind auf die Verarbeitung spezifischer, vordefinierter Ereignisse ausgelegt und nicht für Mehrwährungssysteme gedacht. 

Die Rettung für Entwickler von Mehrwährungssystemen kann der Standard-Handler für benutzerdefinierte Ereignisse OnChartEvent() sein. Er ermöglicht es dem Programmierer, nach eigenem Ermessen Ereignisse zu erzeugen.

Beispielsweise kann uns niemand daran hindern, diese Funktion zum Erhalten von Ticks für jedes beliebige Symbol im aktuellen Diagramm zu nutzen. Wir müssen dafür lediglich einen "Spion" an das Diagramm mit dem jeweiligen Symbol senden.

Mit dem Wort "Spion" meine ich den folgenden Indikator:

//+------------------------------------------------------------------+
//|                                                         iSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#define VERSION         "1.00 Build 2 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "iSpy agent-indicator. If you want to get ticks, attach it to the chart"
#property indicator_chart_window

input long            chart_id=0;        // chart id
input ushort          custom_event_id=0; // event id

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,        // size of price[] array
                 const int prev_calculated,  // bars, calculated at the previous call
                 const int begin,            // starting index of data
                 const double& price[]       // array for the calculation
   )
  {
   double price_current=price[rates_total-1];

   //--- Initialization:
   if(prev_calculated==0) 
     { // Generate and send "Initialization" event
      EventChartCustom(chart_id,0,(long)_Period,price_current,_Symbol);
      return(rates_total);
     }
   
   // When the new tick, let's generate the "New tick" custom event
   // that can be processed by Expert Advisor or indicator
   EventChartCustom(chart_id,custom_event_id+1,(long)_Period,price_current,_Symbol);
   
   //--- return value of prev_calculated for next call
   return(rates_total);
  }

Wir können diesen Spion im Diagramm des gewünschten Symbols starten und dann seine Nachrichten mithilfe der Funktion OnChartEvent() im EA oder Indikator verarbeiten. Um die Nachrichten des "Spions" korrekt zu dechiffrieren, müssen wir die Parameter dieser Funktion auf die folgende Art interpretieren:

Zur Vorführung der gleichzeitigen Arbeit mehrerer "Spione" schreiben wir den einfachen EA exSpy.mq5 (vollständige Version im Archiv):

//+------------------------------------------------------------------+
//|                                                        exSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "The Expert Advisor shows the work of iSPY indicator"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),0)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),1)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY spy indicator                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event id:
                                     // if id-CHARTEVENT_CUSTOM=0-"initialization" event
                const long&   lparam, // chart period
                const double& dparam, // price
                const string& sparam  // symbol
               )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",
            id-CHARTEVENT_CUSTOM,":  ",sparam," ",
            EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam);
     }
  }
//+------------------------------------------------------------------+

Starten Sie den Expert Advisor auf einem beliebigen Diagramm.

Hier sehen Sie die Ergebnisse:


 

Wie wir im Protokoll sehen können, erhalten wir alle Ticks für das gewünschte Symbol sowie das Ereignis "Initialisierung", das insbesondere dann erhalten wird, wenn es eine Aktualisierung oder einen Upload in der Historie gibt.

Fassen wir die Zwischenergebnisse zusammen:

An dieser Stelle könnten wir den Artikel zu Ende bringen, da wir erfolgreich einen Mehrwährungsmodus mithilfe des MetaTrader-5-Terminals und der Programmiersprache MQL5 umgesetzt haben. Doch dabei handelt es sich um eine "rohe" Umsetzung. Deshalb machen wir weiter.

Die Umsetzung des Mehrwährungsmodus

Wenn Sie das oben vorgestellte Konzept der Umsetzung des Mehrwährungsmodus in seiner reinen Form anwenden, stoßen Sie ab einem gewissen Punkt auf Schwierigkeiten. Das Problem ist, dass diese Herangehensweise es uns ermöglicht, alle Ticks für jedes Handelssymbol zu erhalten, auf dem der "Spion" ausgeführt wird.

Bei einem schnellen Markt kann es in einer Sekunde mehrere Ticks pro Symbol geben. Dies kann zu einer "Verstopfung" der Reihenfolge der Ereignisse führen. Hier ist eine Warnung aus der Hilfe:

Das Client Terminal fügt neu erscheinende Ereignisse zur Ereigniswarteschlange hinzu. Auf diese Weise werden Ereignisse eins nach dem anderen in der Reihenfolge ihres Eingangs verarbeitet. Eine Ausnahme besteht für das Ereignis NewTick. Wenn in der Warteschlange bereits ein solches Ereignis existiert oder dieses Ereignis gerade verarbeitet wird, wird das neue NewTick-Ereignis nicht zur Schlange hinzugefügt.

Die Größe der Ereigniswarteschlange ist begrenzt. Bei einer Überfüllung der Warteschlange werden alte Ereignisse unverarbeitet entfernt, um den Eingang neuer Ereignisse zu ermöglichen. Deshalb wird empfohlen, effiziente Ereignis-Händler zu schreiben, und wird nicht empfohlen, Endlosschleifen zu nutzen (es gibt eine Ausnahme bei Scripts, die nur das Start-Ereignis verarbeiten).

Die Überfüllung der Warteschlange kann zum Verlust wichtiger Ereignisse für den Mehrwährungsindikator oder -EA führen. Das ist die eine Seite. Auf der anderen Seite brauchen wir nicht immer Ticks für alle Symbole. Manchmal müssen wir nur das Ereignis "neuer Balken" auf einem beliebigen Timeframe erhalten. Oder mehrere "neuer Balken"-Ereignisse für unterschiedliche Timeframes. Im Allgemeinen ist unser "Spion" für solche Anforderungen nicht geeignet und sein Einsatz nicht sehr bequem.

Machen wir ihn universell einsatzfähig, damit wir nie wieder zu der Frage zurückkehren müssen, wie wir ein Ereignis auf Basis des Symbols eines Mehrwährungs-EAs oder -indikators erhalten können. Nehmen wir als Muster für diesen Zweck die Ereignisaufzählung ENUM_CHART_EVENT_SYMBOL aus der Beschreibung "MCM Control Panel" für mehrwährungsfähige Expert Advisors und Indikatoren:

enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_INIT      =0,         // "Initialization" event
   CHARTEVENT_NO        =0,         // No events

   CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart
   CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart
   CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart
   CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart
   
   CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart
   CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart
   CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart
   CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart
   
   CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart
   CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart
   CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart
   CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart
   
   CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
   CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
   CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
   CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
   
   CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
   CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
   CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
   CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
     
   CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
   CHARTEVENT_TICK      =0x00200000, // "New tick" event
   
   CHARTEVENT_ALL       =0xFFFFFFFF, // All events
  };

An sich ist diese Aufzählung eine Aufzählung von Flags der benutzerdefinierten Diagrammereignisse. Sie ist der Mindestsatz, der für den Mehrwährungsmodus erforderlich sein kann. Natürlich kann sie ergänzt werden. Die Kombination von Flags bestimmt die Ereignisse, die wir aus dem "Spion" versenden werden.

Die Flags können mithilfe des bitweisen Operators "ODER" kombiniert werden. Beispielsweise bedeutet die Kombination CHARTEVENT_NEWBAR_M1 | CHARTEVENT_NEWBAR_H1, dass wir die "neuer Balken"-Ereignisse aus dem minütlichen und stündlichen Timeframe mithilfe des "Spions" versenden werden. Diese Flags werden der Eingabeparameter unseres Spion-Indikators sein. Im weiteren Verlauf nennen wir ihn den "Agentenindikator".

Der Indikator selbst sieht gemäß den neuen Erwägungen so aus:

//+------------------------------------------------------------------+
//|                                        Spy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/de/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 3 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/de/users/Lizar"
#property version     VERSION
#property description "This is the MCM Control Panel agent-indicator."
#property description "Is launched on the required symbol on any time-frame"
#property description "and generates the custom NewBar event and/or NewTick"
#property description "for the chart which receives the event"

#property indicator_chart_window
  
input long                    chart_id;                 // identifier of the chart which receives the event
input ushort                  custom_event_id;          // event identifier  
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;// indicator, which determines the event type.

MqlDateTime time, prev_time;

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,       // size of the price[] array
                 const int prev_calculated, // bars processed at the previous call
                 const int begin,           // where the data begins
                 const double& price[]      // calculations array
   )
  {  
   double price_current=price[rates_total-1];

   TimeCurrent(time);
   
   if(prev_calculated==0)
     {
      EventCustom(CHARTEVENT_INIT,price_current);
      prev_time=time; 
      return(rates_total);
     }
   
//--- new tick
   if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);       

//--- check change time
   if(time.min==prev_time.min && 
      time.hour==prev_time.hour && 
      time.day==prev_time.day &&
      time.mon==prev_time.mon) return(rates_total);

//--- new minute
   if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);     
   if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0)  EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
   if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0)  EventCustom(CHARTEVENT_NEWBAR_M3,price_current); 
   if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0)  EventCustom(CHARTEVENT_NEWBAR_M4,price_current);      
   if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0)  EventCustom(CHARTEVENT_NEWBAR_M5,price_current);     
   if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0)  EventCustom(CHARTEVENT_NEWBAR_M6,price_current);     
   if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);      
   if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);      
   if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);      
   if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);      
   if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);      
   if(time.min!=0) {prev_time=time; return(rates_total);}
//--- new hour
   if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
   if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0)  EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
   if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0)  EventCustom(CHARTEVENT_NEWBAR_H3,price_current);      
   if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0)  EventCustom(CHARTEVENT_NEWBAR_H4,price_current);      
   if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0)  EventCustom(CHARTEVENT_NEWBAR_H6,price_current);      
   if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0)  EventCustom(CHARTEVENT_NEWBAR_H8,price_current);      
   if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);      
   if(time.hour!=0) {prev_time=time; return(rates_total);}
//--- new day
   if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);      
//--- new week
   if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);      
//--- new month
   if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current);      
   prev_time=time;
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price)
  {
   EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol);
   return;
  } 

Da dieser Indikator Teil des MCM Control Panel ist, haben wir ihn nicht umbenannt. Die Anhänge beinhalten einfach eine aktualisierte Version von ihm (siehe "Spy Control panel MCM.mq5"). Doch das bedeutet nicht, dass er nicht unabhängig vom Panel genutzt werden kann.

Dieser Agentenindikator erzeugt benutzerdefinierte Ereignisse und überträgt sie an das Empfängerdiagramm für die weitere Verarbeitung dieser Ereignisse im EA oder Indikator mithilfe der Funktion OnChartEvent(). Nun sollten die Eingabeparameter dieser Funktion wie folgt interpretiert werden:

 Der Vorführungs-EA sieht nicht komplizierter aus als der vorherige (vollständige Version im Archiv):

//+------------------------------------------------------------------+
//|                                      exSpy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/de/users/Lizar   |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (28 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/de/users/Lizar"
#property version     VERSION
#property description "The EA demonstrates the work of the MCM Spy Control panel"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),0,
             CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),1,
             CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),2,
             CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY Control panel MCM indicator.                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,           // event identifier
                  const long&   lparam, // the event flag.
                                       // event flag is a bit mask of ENUM_CHART_EVENT_SYMBOL enumeration.
                  const double& dparam, // price
                  const string& sparam  // symbol 
                 )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",id-CHARTEVENT_CUSTOM,
           ":  ",sparam," ",EnumToString((ENUM_CHART_EVENT_SYMBOL)lparam)," price=",dparam);
     }
  }

Die Ergebnisse der Arbeit von exSpy Control panel MCM:


 

Wie Sie sehen können, erhalten wir regelmäßig alle angefragten Ereignisse.

Fassen wir das Zwischenergebnis nochmal zusammen:

Es hat sich herausgestellt, dass es gar nicht so schwierig ist, einen vollwertigen Mehrwährungsmodus in MetaTrader 5 umzusetzen.

Zusätzlich möchte ich ein paar Nuancen unterstreichen.

Erstens. Alle Ereignisse, die durch die "Agenten" für unseren Mehrwährungs-EA/-indikator erzeugt werden, sind extern. In diesem Zusammenhang stellt sich eine Frage: "Ist es notwendig, die Agenten direkt aus dem EA/Indikator auszuführen?". Die Antwort lautet: "Nein."

Zweitens. In der OnChartEvent()-Funktion scheint der Ereignisidentifikator id überflüssig zu sein, da wir anhand des Parameters sparam – des Namen des Handelssymbols – herausfinden können, für welches Symbol das Ereignis eingegangen ist. Können wir ihn also für irgendeinen anderen Zweck nutzen? Die Antwort lautet: "Ja, das können wir." 

Solche Überlegungen haben zum Erscheinen des "MCM Control Panel" für mehrwährungsfähige Expert Advisors und Indikatoren geführt. Es bildet eine Art "Zwischenschicht" zwischen dem Terminal und einem EA/Indikator. Dies lieferte uns weitere Vorteile und mehr Flexibilität bei der Konfiguration einer mehrwährungsfähigen Umgebung:

Mehrwährungsindikator RSI für den USDx-Dollar-Index

Um alle Vorteile der oben aufgeführten Methode wahrzunehmen, schlage ich vor, die mehrwährungsfähige Variante des RSI-Indikators für den USDx-Dollar-Index mithilfe des MCM Control Panel zu implementieren.

Für den Einstieg zähle ich ein paar Besonderheiten auf. Beim Versuch, den Dollar-Index zu analysieren, zählen wir oft einfach nur die Indikatoren der Werte des Index. Aus meiner Sicht ist das nicht ganz richtig, da jedes Symbol aus dem Korb der Währungspaar-Indizes seine eigenen Beiträge leistet. Berechnen wir deshalb beispielsweise den RSI für den Dollar-Index mit einer Formel, die der Formel der Indexberechnung stark ähnelt:

Das heißt, wir berechnen zuerst den RSI für ein bestimmtes Währungspaar und lesen anschließend den RSI für den Index unter Berücksichtigung der Gewichtung der Koeffizienten aus.

Dem Leser könnte aufgefallen sein, dass es ein Problem bei der Synchronisierung der historischen Daten aller im Mehrwährungssystem verwendeten Symbole gibt (siehe Paragraph 2 des Abschnitts "Übersicht über die herkömmliche Herangehensweise").

Dieses Problem wurde im Indikator mithilfe der Klassenfunktionen für die Erstellung der synchronisierten RSI-Puffer (Datei SynchronizedBufferRSI.mqh) gelöst. Es ist nicht sinnvoll, den gesamten Code der Klasse aufzuführen, deshalb werden im Folgenden nur relevante Details präsentiert. 

Als Erstes wird der Indikatorpuffer mit dem public-Zugriffsmodifikator innerhalb der Klasse definiert:

public:
   double   buffer[];   // indicator buffer

Als Zweites wird der Indikator mithilfe der Klassenmethode initialisiert:

//--- Initialization methods:
bool Init(int n,string symbol,int rsi_count,int rsi_period);

Und als Drittes wird der Wert des Indikatorpuffers für jeden Balken mithilfe der refresh-Methode der Klasse mit dem aktuellen Timeframe synchronisiert:

//+------------------------------------------------------------------+
//| The method of receiving/updating indicator data for one bar      |
//| of the indicator buffer.                                         |
//| INPUT:  bar   - bar number                                       |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSynchronizedBufferRSI::Refresh(int bar=0)
  {
   buffer[bar]=EMPTY_VALUE; // Initialization of the bar of the indicator buffer.
     
   //--- Inquire the time of the bar for the current graph:
   datetime time[1];      
   if(CopyTime(_Symbol,_Period,bar,1,time)!=1) return; // In case of an error, we wait for the next tick/bar...

   //--- Request the value of the indicator for the symbol for the time,
   //--- consistent with that of the bar of the current graph:
   double value[1];
   if(CopyBuffer(m_handle,0,time[0],time[0],value)!=1) return; // In case of an error, wait for the next tick/bar...

   buffer[bar]=value[0];
   return;
  }

Für eine vollständige Synchronisierung aller Indikatorpuffer müssen wir einen ganzminütigen Timeframe ohne "Lücken" nutzen, wie es in dem folgenden Beitrag beschrieben wird. Für diese Methode der Synchronisierung der Indikatorpuffer haben wir allerdings speziell den Timeframe des aktuellen Diagramms ausgewählt, da die Darstellung des Indikators darauf stattfindet.

Aus eigener Erfahrung kann ich sagen, dass es bei kleinen Zeiträumen sinnvoll ist, eine solche Synchronisierungsmethode für jede Zeitreihe oder jeden Indikatorpuffer anzuwenden, wenn sich ihr Symbol vom Symbol auf dem aktuellen Diagramm unterscheidet.

Das Diagramm zeigt deutlich, warum es das wert ist:


 

Bei größeren Timeframes ist dies klassischerweise nicht zu beobachten.

Und zu guter Letzt: Hier sehen Sie den Code eines standardmäßigen benutzerdefinierten Ereignis-Handlers, der im Indikator verwendet wird:

//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event identifier or position symbol in the "Market Match"+CHARTEVENT_CUSTOM  
                const long& lparam,   // event indicator
                const double& dparam, // price
                const string& sparam  // symbol
                )
  {
   int custom_id=id-CHARTEVENT_CUSTOM-1;
   
   if(custom_id>=0)      
     {
      if(lparam!=CHARTEVENT_NEWBAR_NO)
        { 
         //--- Recalculation of the last uncompleted bar:
         if(EventToPeriod(lparam)==_Period && sparam==_Symbol)
           { // Recalculation of the indicator, if a new bar on the current chart
            iRSIUSDx_Ind[0]=EMPTY_VALUE;
            //--- Updating the value of the RSI for all of the currency pairs for the new bar
            for(int i=0;i<symbol_total;i++) buffer[i].Refresh();
            iRSIUSDx(symbol_total);   // calculation of the current incomplete bar RSI for the index
            return;
           }
         
         buffer[custom_id].Refresh(); // The value of RSI for the custom_id of the currency pair for the current bar
         iRSIUSDx(symbol_total);      // calculation of the RSI for the current(uncompleted) bar RSIx
         return;
        }
      else 
        { 
         //--- Recalculation of the indicator for the "Initialization" event 
         buffer[custom_id].RefreshBuffer();     // Update of the RSI buffer for the custom_id of the currency pair
         Init_iRSIUSDx(symbol_total,calculate); // Update of the RSI buffer for the index
         return;
        }
     }
  }

Besonderheiten des Codes:

Nach der Untersuchung des vollständigen Codes des RSI-Indikators für den Dollar-Index USDx wird deutlicher, wie das alles funktioniert.

Besonderheiten der Installation:


Fazit

Die betrachtete Umsetzung eines vollwertigen Mehrwährungsmodus in MetaTrader 5 demonstriert in vollem Umfang die Vorteile der Plattform und der Programmiersprache MQL5 bei der Lösung dieses Problems. Was früher die meisten Schwierigkeiten bereitete, lässt sich nun durch verfügbare Mittel lösen.

Natürlich ist dies nur der Anfang einer Entwicklung in dieser Richtung. Sicherlich werden noch mehr Möglichkeiten für eine noch bessere Synchronisierung von Daten, Verwaltung von Mehrwährungsmodi usw. gefunden. Doch ich hoffe, dass es durch diesen Beitrag ersichtlich wird, dass wir über alle dafür benötigten Werkzeuge verfügen.