Geschwindigkeitsvergleich von sich selbst speichernden Indikatoren

Vladimir Karputov | 7 Mai, 2018


Einführung

Angenommen, wir sind plötzlich des klassischen MQL5-Zugriffs auf die Indikatoren überdrüssig, und wir entschließen uns einfach, einmal alternative Zugriffe mit deren Geschwindigkeit auszuprobieren. Zum Beispiel können wir es mit dem Zugriff auf Indikatoren im MQL4-Stil mit und ohne Zwischenspeicher vergleichen. Ideen zum MQL4-Zugang stammen aus dem Artikel "LifeHack für Händler: Fast-Food aus Indikatoren" und wurden etwas verbessert.


Analyse der MQL5-Nummerierung der Handles der Indikatoren

Angenommen, das Terminal verfügt über eine fortlaufende Nummerierung der Handles der Indikatoren, die mit Null beginnt. Um diese Annahme zu überprüfen, erstellen wir einen einfachen Expert Advisor iMACD and IndicatorRelease.mq5 - er erzeugt mehrere Indikator-Handles, druckt sie sofort aus und greift regelmäßig in OnTick() darauf zu:

//+------------------------------------------------------------------+
//|                                   iMACD and IndicatorRelease.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.003"
//--- Eingabeparameter
input int   count=6;   // Nummer des MACD Indikators

int    handles_array[]; // Der Array für die Handles des iMACD
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   int array_resize=ArrayResize(handles_array,count);
   if(array_resize==-1)
     {
      Print("ArrayResize error# ",GetLastError());
      return(INIT_FAILED);
     }
   if(array_resize!=count)
     {
      Print("ArrayResize != \"Count MACD indicators\"");
      return(INIT_FAILED);
     }
   ArrayInitialize(handles_array,0);
   for(int i=0;i<count;i++)
     {
      handles_array[i]=CreateHandleMACD(12+i);
      //--- wenn die Erstellung fehlschlug 
      if(handles_array[i]==INVALID_HANDLE)
        {
         //--- Grund des Fehlers und die Ausgabe der Fehlernummer 
         PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d",
                     Symbol(),
                     EnumToString(Period()),
                     GetLastError());
         //--- Der Indikator wurde zu früh beendet 
         return(INIT_FAILED);
        }
      Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7),
            ", create handle iMACD (",handles_array[i],")");
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
   for(int i=0;i<count;i++)
     {
      Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7),
            ", remove handle iMACD (",handles_array[i],"): ",IndicatorRelease(handles_array[i]));
     }
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   string text="";
   for(int i=0;i<count;i++)
     {
      double macd_main_1=iMACDGet(handles_array[i],MAIN_LINE,1);
      if(i<15)
        {
         text+="\n"+"ChartID: "+IntegerToString(ChartID())+": "+Symbol()+
               ", MACD#"+IntegerToString(i)+" "+DoubleToString(macd_main_1,Digits()+1);
         Comment(text);
        }
      else if(i==15)
        {
         text+="\n"+"only the first 15 indicators are displayed ...";
         Comment(text);
        }
     }
  }
//+------------------------------------------------------------------+
//| Werteabfrage des Puffers des iMACD                               |
//|  Die Puffernummerierung ist wie folgt:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(const int handle_iMACD,const int buffer,const int index)
  {
   double MACD[1];
//--- Fehlernummer zurücksetzen 
   ResetLastError();
//--- Füllen eine Teils des iMACDBuffer-Arrays mit Werten aus dem Indikatorpuffer mit dem Index 0. 
   if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0)
     {
      //--- Im Fehlerfall von CopyBuffer, drucke die Fehlernummer 
      PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError());
      //--- Beenden mit Ergebnis Null - das heißt, anscheinend hat der Indikator nicht berechnet 
      return(0.0);
     }
   return(MACD[0]);
  }
//+------------------------------------------------------------------+
//| Erstellen des Handles des MACD                                   |
//+------------------------------------------------------------------+
int CreateHandleMACD(const int fast_ema_period)
  {
//--- Erstellen des Handles für den Indikator iMACD
   return(iMACD(Symbol(),Period(),fast_ema_period,52,9,PRICE_CLOSE));
  }
//+------------------------------------------------------------------+

Experiment 1

Quelldaten: Das Terminal hat offene AUDJPY M15, USDJPY M15 und EURUSD M15 Charts ohne Indikatoren und ohne EAs. Die Zahl der Parameter der MACD Indikatoren von iMACD und IndicatorRelease.mq5 ist 6.

Starten wir iMACD and IndicatorRelease.mq5 auf AUDJPY M15 (ChartID 131571247244850509) sofort nach Neustart des Terminals:

2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (11)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (12)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (13)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (14)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (15)

Wie wir sehen können, beginnt die Nummerierung der bei 10 und nicht bei 0.

Experiment 2

Quelldaten: iMACD and IndicatorRelease.mq5 wird auf AUDJPY M15 gestartet, Zahl der MACD-Indikatoren ist 6.

Start von iMACD and IndicatorRelease.mq5 auf USDJPY, M15 (ChartID 131571247244850510):

2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (10)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (11)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (12)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (13)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (14)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (15)

Wie wir sehen können, beginnt die Nummerierung auf dem Chart (USDJPY M15) der bei 10 und nicht bei 0.

Schlussfolgerung: Die Nummerierung der Handles der Indikatoren im Terminal (das bei den Benutzern läuft) ist NICHT fortlaufend und beginnt nicht bei Null.

Experiment 3

Zwei identische Charts AUDJPY, M15 (ChartID 131571247244850509) und AUDJPY, M15 (ChartID 131571247244850510). Auf jeden läuft iMACD and IndicatorRelease.mq5 mit der Zahl der MACD-Indikatoren gleich 6.

Die nicht fortlaufende Nummerierung der erzeugten Indikator-Handles bestätigt, dass MQL5 eine interne Liste führt (mit den Zählern für jedes einzelne Handle). Um dies sicherzustellen, lassen Sie uns die Periodenerweiterung auskommentieren :

int OnInit()
  {
***
   ArrayInitialize(handles_array,0);
   for(int i=0;i<count;i++)
     {
      handles_array[i]=CreateHandleMACD(12/*+i*/);
      //--- wenn die Erstellung fehlschlug 

Daher versuchen wir, die Handles mehrerer MACD-Indikatoren mit genau den gleichen Einstellungen zu erstellen.

Entfernen wir die Charts der Experimente 1 und 2 verbleibenden und starten iMACD und IndicatorRelease.mq5 auf AUDJPY, M15 (ChartID 131571247244850509):

2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)

Wie wir sehen können, wird das gleiche Handle als Antwort auf das Erstellen absolut identischer Indikatoren zurückgegeben.

Starten wir den EA iMACD und IndicatorRelease.mq5 (auch mit auskommentierter Periodenerweiterung) auf AUDJPY, M15 (ChartID 131571247244850510):

2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)

Es wird das gleiche Handle wieder zurückgegeben. Sind die Handles "10" des ersten und zweiten Charts ein und dasselbe oder zwei verschiedene? Um dies zu überprüfen, entfernen wir den EA aus den Charts (wir erinnern uns, der EA übergibt das Array der Handles in OnDeinit() und entfernt jedes mit IndicatorRelease).

2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): true
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false

2018.02.18 07:53:36.116 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): true
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false

Es ist das erwartete Ergebnis, wie es auch in der Dokumentation zu Durchführung der Programme steht:

Der EA wird in einem eigenen Thread ausgeführt, es gibt so viele Threads, wie es EAs gibt.

Das bedeutet, dass, wenn zwei EAs auf gleichen Charts (gleiches Symbol und Zeitrahmen) Indikatoren mit den gleichen Eingabeparametern verwenden, wird MQL5 sie als zwei verschiedene Handles in seiner internen Liste identifiziert.

Allgemeine Schlussfolgerung zur Entwicklung von Indikatoren in EAs

Die Nummerierung der Handles der Indikatoren im Terminal (die eines Benutzers) ist NICHT fortlaufend und beginnt NICHT mit Null, während MQL5 in seiner internen Liste der Handles folgendes berücksichtigt:

  • Technische Indikatorfunktionen (iMA, iAC, iMACD, iIchimoku, etc.);
  • Indikatoreingaben;
  • Das Symbol, für das der Indikator gestartet wurde;
  • Der Zeitrahmen, für das der Indikator gestartet wurde;
  • Die ChartID des Charts, auf dem der EA läuft.

Hat das Speichern der Handles einen Grund?

Die Ausgangsdaten (Zeitrahmen, Symbol, getestetes Zeitintervall und Tick-Generierungstyp) sind wie folgt:

Speichertest Einstellungen

Abb. 1. Einstellungen

Tests mit Zugriff auf Indikatoren im MQL4-Stil (mit und ohne Handle-Speicherung) werden mit Hilfe des EAs Cache-Test.mq5 durchgeführt, während Tests mit MQL5-Zugriff mit MQL5-Test.mq5 durchgeführt werden:

//+------------------------------------------------------------------+
//|                                                    MQL5 test.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.000"
//--- Eingabeparameter
input bool     UseOneIndicator=false;  // Verwendung: "false" -> 9 Indikatoren, "true" - 1 Indikator
//---
int            arr_handle_iMACD[];     // Array für die Handles des iMACD
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(UseOneIndicator)
      ArrayResize(arr_handle_iMACD,1);
   else
      ArrayResize(arr_handle_iMACD,9);
   if(!CreateHandle(arr_handle_iMACD))
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int arr_size=ArraySize(arr_handle_iMACD);
   for(int i=0;i<arr_size;i++)
     {
      double macd_main_30=iMACDGet(arr_handle_iMACD[i],MAIN_LINE,0);
     }
  }
//+------------------------------------------------------------------+
//| Handle erstelen                                                  |
//+------------------------------------------------------------------+
bool CreateHandle(int &arr_handles[])
  {
   int arr_size=ArraySize(arr_handles);
   for(int i=0;i<arr_size;i++)
     {
      int fast_ema_repiod=30+10*i;
      //--- Erstellen des Handles für den Indikator iMACD
      arr_handles[i]=iMACD(NULL,0,fast_ema_repiod,26,9,PRICE_CLOSE);
      //--- wenn die Erstellung fehlschlug 
      if(arr_handles[i]==INVALID_HANDLE)
        {
         //--- Grund des Fehlers und die Ausgabe der Fehlernummer 
         PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d",
                     Symbol(),
                     EnumToString(Period()),
                     GetLastError());
         //--- Der Indikator wurde zu früh beendet 
         return(false);
        }
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Werteabfrage des Puffers des iMACD                               |
//|  Die Puffernummerierung ist wie folgt:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(const int handle_iMACD,const int buffer,const int index)
  {
   double MACD[1];
//--- Fehlernummer zurücksetzen 
   ResetLastError();
//--- Füllen eine Teils des iMACDBuffer-Arrays mit Werten aus dem Indikatorpuffer mit dem Index 0. 
   if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0)
     {
      //--- Im Fehlerfall von CopyBuffer, drucke die Fehlernummer 
      PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError());
      //--- Beenden mit Ergebnis Null - das heißt, anscheinend hat der Indikator nicht berechnet 
      return(0.0);
     }
   return(MACD[0]);
  }
//+------------------------------------------------------------------+

MQL5 test.mq5 EA-Parameter:

MQL5 test 1

Abb. 2. MQL5 test.mq5. Neun Indikatoren

Cache test.mq5 EA-Parameter:

  • Use Timer ("0" -> kein Timer) — Verwenden des Timers (0 — nicht verwendet).
  • Use indicator ("false" -> 9 Indikatoren, "true" - 1 Indikator) — Anzahl der beobachteten Indikatoren (1 oder 9).

Speicher-Test 1

Abb. 3. Cache test.mq5. Kein Timer, neun Indikatoren

Die Datei IndicatorsMQL4.mq misst "MQL4Stil, ohne Speichern des Handles". Die Datei verwendet SimpleCallMQL4.mqh (siehe im Artikel "LifeHack für Händler: ForEach mit #define zubereiten" ).

#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
//#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten

Um "MQL4 Style mit Speichern des Handles" zu messen, wird der Code für das Caching des Handles aus dem Post #113 zu IndicatorsMQL4.mqh hinzugefügt (nur bei MACD werden andere Funktionen gelöscht). Die Datei wird als IndikatorenMQL4Caching.mqh gespeichert — auch sie bindet SimpleCallCaching.mqh mit ein:

//#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
//#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten

Ergebnisse des Vergleichs der Zugriffsarten mit den neun Indikatoren (Einstellungen sind in Abb. 1 dargestellt):

MQL5 vs MQL4 9 Indikatoren

Abb. 4. Zeitaufwand für den Zugriff auf neun Indikatoren

Beim Vergleich der Ergebnisse ist zu beachten, dass der Test EA die Aufgabe erheblich erschwert hat:

  • Daten werden gleichzeitig aus neun Indikatoren abgefragt;
  • Indikatoren werden BEI JEDEM Tick abgerufen;
  • M1 Zeitrahmen — 26 169 180 Ticks und 370 355 Bars wurden generiert.

Führen wir nun einen Test durch: Wir rufen nur einen Indikator auf (für beide EAs, MQL5-Test.mq5 und Cache Test.mq5, der Parameter Use indicator... ist "true", während für Cachetest.mq5, Use Timer "0" ist)

MQL5 vs MQL4 1 Indikator

Abb. 5. Zeitaufwand für den Zugriff auf einen Indikator

Schlussfolgerung

MQL4-Style mit Handle-Speicherung bietet einen Vorteil gegenüber MQL4-Style ohne Handle-Speicherung. Allerdings verliert der MQL4-Stil komplett gegenüber MQL5. 

Keine Überprüfung der Gültigkeit der Handles

Nun sollten wir den großen Nachteil der Verwendung des Speicherns von Handles erwähnen: Es gibt keine Prüfung der Existenz des Handles im Cache des Benutzers. Mit anderen Worten, der Fall des gelöschten Handles eines Indikators wird in keiner Weise verarbeitet. 

Betrachten wir folgende Situation: Wir arbeiten mit Indikatoren im MQL4-Stil und speichern die Handles. Nach dem ersten Zugriff vom EA:

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

wird das Handle im User-Speicher gesichert (dies kann ein Array von Strukturen oder ein Array von Strings sein). Danach werden alle nachfolgenden Zugriffe aus dem EA

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

nicht an den MQL5-Kern übergeben. Stattdessen werden die Indikatorwerte durch das Handle aus dem Speicher zurückgegeben. Löschen Sie nun das Handle in OnTimer() — angenommen, wir wissen, dass es gleich "10" ist. Als Test verwenden wir die Datei Cache-Test.mq5, die die Datei SimpleCallMQL4Caching.mqh einbindet:

//#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
//#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten

Stellen Sie sicher, dass Sie den Timer einstellen (hier ist der Timer auf sechs Sekunden eingestellt, wir erhalten Zugriff auf einen Indikator).

Cache Test 2

Abb. 6. Testeinstellungen für das Löschen der Handles

Nach dem ersten Aufruf von OnTimer()

OnTimer, IndicatorRelease(10)=true
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807 

erhalten wir den Fehler 4807:

 ERR_INDICATOR_WRONG_HANDLE  4807  Fehlerhandle des Indikators

Dies bedeutet, dass das Handles der Indikatoren nicht überprüft werden.


Speichern der Handles der Indikatoren. Wie es funktioniert.

Das allgemeine Prinzip des Speicherns der Indikator-Handles ist wie folgt:

  • Einen eigenen Speicher für die Handles erstellen;
  • Beim Anfordern von Daten aus dem Indikator prüfen, ob das Handle bereits mit den gewünschten Einstellungen (Symbol, Zeitrahmen, Mittelungszeitraum, etc.) angelegt wurde:
    • Wenn er bereits im eigenen Speicher vorhanden ist, geben Sie Daten des Indikators zurück;
    • Wenn noch kein solches Handle existiert, wird es angelegt, im Speicher gesichert und dann die Daten des Indikators zurückgegeben.


Option 1: Array von Strukturen

Die Implementierung erfolgt in IndikatorenMQL4Caching.mqh (eingebunden von Cache-Test.mq5 unter Verwendung von SimpleCallMQL4Caching.mqh).

Cache-Test.mq5 bindet SimpleCallMQL4Caching.mqhein:

//#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
//#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten

Betrachten wir den größeren Teil des Codes, der in die Datei und die Funktion des MACD eingefügt wurde:

...         
//+------------------------------------------------------------------+
//| Struct CHandle                                                   |
//+------------------------------------------------------------------+
template<typename T>
struct SHandle
  {
private:
   int               Handle;
   T                 Inputs;

public:
   //+------------------------------------------------------------------+
   //| Ein Konstruktor mit initialisierender Liste                      |
   //+------------------------------------------------------------------+
                     SHandle() : Handle(INVALID_HANDLE)
     {
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "=="                                       |
   //+------------------------------------------------------------------+
   bool operator==(const T &Inputs2) const
     {
      return(this.Inputs == Inputs2);
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "="                                        |
   //+------------------------------------------------------------------+
   void operator=(const T &Inputs2)
     {
      this.Inputs=Inputs2;
     }
   //+------------------------------------------------------------------+
   //| SHandle::GetHandle                                               |
   //+------------------------------------------------------------------+
   int GetHandle()
     {
      return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
     }
  };
//+------------------------------------------------------------------+
//| Get Handle                                                       |
//+------------------------------------------------------------------+
template<typename T>
int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const int Size=ArraySize(Handles);

   for(int i=0; i<Size; i++)
      if(Handles[i]==Inputs)
         return(Handles[i].GetHandle());

   ArrayResize(Handles,Size+1);
   Handles[Size]=Inputs;

   return(Handles[Size].GetHandle());
  }
//+------------------------------------------------------------------+
//| Struct Macd                                                      |
//+------------------------------------------------------------------+
struct SMacd
  {
   string            symbol;
   ENUM_TIMEFRAMES   period;
   int               fast_ema_period;
   int               slow_ema_period;
   int               signal_period;
   ENUM_APPLIED_PRICE applied_price;
   //+------------------------------------------------------------------+
   //| Ein leerer Standardkonstruktor                                   |
   //+------------------------------------------------------------------+
                     SMacd(void)
     {
     }
   //+------------------------------------------------------------------+
   //| Ein Konstruktor mit initialisierender Liste                      |
   //+------------------------------------------------------------------+
                     SMacd(const string             &isymbol,
                                             const ENUM_TIMEFRAMES    &iperiod,
                                             const int                &ifast_ema_period,
                                             const int                &islow_ema_period,
                                             const int                &isignal_period,
                                             const ENUM_APPLIED_PRICE &iapplied_price) :
                                             symbol((isymbol== NULL)||(isymbol == "") ? Symbol() : isymbol),
                                             period(iperiod == PERIOD_CURRENT ? Period() : iperiod),
                                             fast_ema_period(ifast_ema_period),
                                             slow_ema_period(islow_ema_period),
                                             signal_period(isignal_period),
                                             applied_price(iapplied_price)
     {
     }
   //+------------------------------------------------------------------+
   //| SMacd::GetHandle                                                 |
   //+------------------------------------------------------------------+
   int GetHandle(void) const
     {
      return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "=="                                       |
   //+------------------------------------------------------------------+
   bool operator==(const SMacd &Inputs) const
     {
      return((this.symbol == Inputs.symbol) &&
             (this.period == Inputs.period) &&
             (this.fast_ema_period == Inputs.fast_ema_period) &&
             (this.slow_ema_period == Inputs.slow_ema_period) &&
             (this.signal_period == Inputs.signal_period) &&
             (this.applied_price == Inputs.applied_price));
     }
  };
//+------------------------------------------------------------------+
//| iMACD2 in MQL4 Schreibweise                                      |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
int iMACD2(const string             symbol,
           const ENUM_TIMEFRAMES    period,
           const int                fast_ema_period,
           const int                slow_ema_period,
           const int                signal_period,
           const ENUM_APPLIED_PRICE applied_price)
  {
   static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);

   return(GetHandle(Handles, Inputs));
  }
//+------------------------------------------------------------------+
//| iAC in MQL4 Schreibweise                                         |
...
//+------------------------------------------------------------------+
//| iMACD in MQL4 Schreibweise                                       |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
double   iMACD(
               string                     symbol,              // Symbol-Name
               ENUM_TIMEFRAMES            timeframe,           // Zeitrahmen 
               int                        fast_ema_period,     // Periodenlänge der schnellen Glättung 
               int                        slow_ema_period,     // Periodenlänge der langsame Glättung 
               int                        signal_period,       // Periodenlänge der Glättung der Signallinie 
               ENUM_APPLIED_PRICE         applied_price,       // Preistyp oder Handle
               int                        buffer,              // Puffer 
               int                        shift                // Versatz
               )
  {
   double result=NaN;
//---
   int handle=iMACD2(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(handle==INVALID_HANDLE)
...

Und so funktioniert das. Zuerst, gibt es die Datenanfrage des MACD:

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

Danach gelangen wir über die Funktion von iMACD zu iMACD2:

//+------------------------------------------------------------------+
//| iMACD2 in MQL4 Schreibweise                                      |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
int iMACD2(const string             symbol,
           const ENUM_TIMEFRAMES    period,
           const int                fast_ema_period,
           const int                slow_ema_period,
           const int                signal_period,
           const ENUM_APPLIED_PRICE applied_price)
  {
   static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);

   return(GetHandle(Handles, Inputs));
  }

Der static Array Handles[] vom Typ SMacd wird hier deklariert (er wird beim ersten Aufruf erzeugt und bei nachfolgenden Aufrufen nicht noch einmal erstellt). Auch das Objekt Inputs vom Typ SMacd wird angelegt und mit Parametern initialisiert.

Danach werden die Links verwendet, um den Array Handles[] und das Objekt Inputs mit der Funktion GetHandle zu verbinden (nicht mit SHandle::GetHandle und SMacd::GetHandle)::

//+------------------------------------------------------------------+
//| Get Handle                                                       |
//+------------------------------------------------------------------+
template<typename T>
int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const int Size=ArraySize(Handles);

   for(int i=0; i<Size; i++)
      if(Handles[i]==Inputs)
         return(Handles[i].GetHandle());

   ArrayResize(Handles,Size+1);
   Handles[Size]=Inputs;
   return(Handles[Size].GetHandle());
  }

In dieser Funktion geben wir das gefundene Handle des Indikators im Array zurück oder, falls das Handle nicht gefunden wird, erhalten wir es von SHandle::GetHandle.

Aber da dies der erste Zugriff ist und es noch kein solches Handle gibt,  

   //+------------------------------------------------------------------+
   //| SHandle::GetHandle                                               |
   //+------------------------------------------------------------------+
   int GetHandle()
     {
      return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
     }

wird es in SMacd::GetHandle erstellt:

   //+------------------------------------------------------------------+
   //| SMacd::GetHandle                                                 |
   //+------------------------------------------------------------------+
   int GetHandle(void) const
     {
      return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
     }


Option 2: Array von Zeichenketten

Die Implementierung erfolgt in der Datei IndicatorsMQL4String.mqh (verbunden mit Cache test.mq5 über SimpleCallString.mqh).

Im EA Cache-Test.mq5, wird SimpleCallString.mqh eingebunden:

//#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten

Das Arbeiten mit Zeichenketten ist sehr zeitintensiv. Das werden wir später sehen. Die Idee, Parameter als Zeichenkette zu speichern, sieht also wie folgt aus:

   string Hashes[];
   static int Handles[];
   string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
               (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
               (string)(fast_ema_period)+
               (string)(slow_ema_period)+
               (string)(signal_period)+
               (string)(applied_price);

Wir werden auf iMACD aus dem EA mit den oben angegebenen Parametern zugreifen, in Abb. 1.

 NN  Code Zeit
  1
//--- NN2
//static string Hashes[];
//static int Handles[];
//string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
//            (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
//            (string)(fast_ema_period)+
//            (string)(slow_ema_period)+
//            (string)(signal_period)+
//            (string)(applied_price);
//--- NN3
//static string Hashes[];
//static int Handles[];
//string hash="";
//StringConcatenate(hash,
//                  ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
//                  (timeframe==PERIOD_CURRENT ? Period() : timeframe),
//                  fast_ema_period,
//                  slow_ema_period,
//                  signal_period,
//                  applied_price);
 0:01:40.953
  2
//--- NN2
   static string Hashes[];
   static int Handles[];
   string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
               (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
               (string)(fast_ema_period)+
               (string)(slow_ema_period)+
               (string)(signal_period)+
               (string)(applied_price);
//--- NN3
//static string Hashes[];
//static int Handles[];
//string hash="";
//StringConcatenate(hash,
//                  ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
//                  (timeframe==PERIOD_CURRENT ? Period() : timeframe),
//                  fast_ema_period,
//                  slow_ema_period,
//                  signal_period,
//                  applied_price);
 0:05:20.953
  3
//--- NN2
//static string Hashes[];
//static int Handles[];
//string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
//            (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
//            (string)(fast_ema_period)+
//            (string)(slow_ema_period)+
//            (string)(signal_period)+
//            (string)(applied_price);
//--- NN3
   static string Hashes[];
   static int Handles[];
   string hash="";
   StringConcatenate(hash,
                     ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
                     (timeframe==PERIOD_CURRENT ? Period() : timeframe),
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     applied_price);
 0:04:12.672

Test 1 is a benchmark test with MQL4-style access to indicators without working with strings. In test 2, we already work with strings and the string is formed using "+". In test 3, the string is formed using StringConcatenate.

Nach den Zeitmessungen ist es klar, dass, obwohl StringConcatenate 21% Zeitgewinn im Vergleich zu Test 2 ergibt, die Gesamtleistung immer noch 2,5 mal geringer ist als im Test 1.

Daher kann die Idee, die Handles der Indikatoren als Zeichenketten zu speichern, verworfen werden.


Option 3 — Klasse mit dem Caching der Handles (die Klasse iIndikatoren.mqh ist mit dem EA Cache-Test.mq5 verbunden mittels SimpleCallMQL4CachingCiIndicators.mqh).

Im EA Cache-Test.mq5 binden wir SimpleCallMQL4CachingCiIndicators.mqh ein:

//#include <SimpleCall\SimpleCallMQL4.mqh> // für Tests ohne Speichern der Handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // für Tests mit Speichern der Handles
//#include <SimpleCall\SimpleCallString.mqh> // für Tests mit Zeichenketten
#include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>

Für jeden Indikator wird ein statisches Objekt der Klasse CHandle erzeugt (innerhalb der entsprechenden MQL4-Style-Funktion). Es dient als Objektspeicher der Klasse CiIndicators — eine Klasse, die die Indikatorparameter und -einstellungen enthält.

Schema

Abb. 7. Struktur

Die Klasse CiIndicators basiert auf fünf 'private' Variablen:

//+------------------------------------------------------------------+
//| Class iIndicators                                                |
//+------------------------------------------------------------------+
class CiIndicators
  {
private:
   string            m_symbol;                        // Symbolname 
   ENUM_TIMEFRAMES   m_period;                        // Zeitrahmen 
   ENUM_INDICATOR    m_indicator_type;                // Indikatortyp der Enumeration ENUM_INDICATOR 
   int               m_parameters_cnt;                // Anzahl der Parameter
   MqlParam          m_parameters_array[];            // Array der Parameter

public:

Das korrespondiert vollständig mit den Variablen der Funktion IndicatorCreate. Das geschieht nicht ohne Grund, erhalten wir doch das Handle des Indikators über IndicatorCreate.

Die Klasse CHandle wird unter Verwendung von zwei Arrays erstellt:

//+------------------------------------------------------------------+
//| Class CHandle                                                    |
//+------------------------------------------------------------------+
class CHandle
  {
private:
   int               m_handle[];
   CiIndicators      m_indicators[];

public:

Der Array m_handle beinhaltet die erstellten Handles, während der Array m_indicators die Klassen von CiIndicators speichert.

Der Code, um mit den Klassen CiIndicators und CHandle zu arbeiten schaut mit dem MACD als Beispiel wie folgt aus:

//+------------------------------------------------------------------+
//| iMACD in MQL4 Schreibweise                                       |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
double   iMACD(
               string                     symbol,              // Symbol-Name
               ENUM_TIMEFRAMES            timeframe,           // Zeitrahmen 
               int                        fast_ema_period,     // Periodenlänge der schnellen Glättung 
               int                        slow_ema_period,     // Periodenlänge der langsame Glättung 
               int                        signal_period,       // Periodenlänge der Glättung der Signallinie 
               ENUM_APPLIED_PRICE         applied_price,       // Preistyp oder Handle
               int                        buffer,              // Puffer 
               int                        shift                // Versatz
               )
  {
//---
   static CHandle Handles_MACD;
//--- Eintragen der Indikatorparameter in die Struktur      
   MqlParam pars[4];
//--- Periodenlänge des schnelle MA 
   pars[0].type=TYPE_INT;
   pars[0].integer_value=fast_ema_period;
//--- Periodenlänge des langsamen MA 
   pars[1].type=TYPE_INT;
   pars[1].integer_value=slow_ema_period;
//--- Periodenlänge der Signallinie, der Differenz von schnellen und langsamen gleitenden Durchschnitt 
   pars[2].type=TYPE_INT;
   pars[2].integer_value=signal_period;
//--- Preistyp 
   pars[3].type=TYPE_INT;
   pars[3].integer_value=applied_price;

   CiIndicators MACD_Indicator;
   MACD_Indicator.Init(Symbol(),Period(),IND_MACD,4);
   int handle=Handles_MACD.GetHandle(MACD_Indicator,Symbol(),Period(),IND_MACD,4,pars);
//---
   double result=NaN;
//---
   if(handle==INVALID_HANDLE)
     {
      Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError());
      return(result);
     }
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyBuffer error=",GetLastError());
   return(result);
  }

  • Deklarieren von Handles_MACD einer Klasse von CHandle — der Speicher der generierten Handles und der Parameter des MACD.
  • Anlegen und Initialisierung des Objektes MACD_Indicator einer Klasse von CiIndicators.
  • Das Handle der Indikators wird in der Funktion Handles_MACD::GetHandle erzeugt (oder übergeben, falls es bereits für solche Parameter angelegt wurde).

Die Betriebszeit der Klasse von CiIndikatoren.mqh mit MQL4-Zugriff und den Handles dauerte 2 Minuten und 30 Sekunden.


Endgültige Grafik der Zugriffsgeschwindigkeit auf die neun Indikatoren

Der MQL4-Stil mit und ohne Caching wird mit Cache-Test.mq5 geprüft, während die Standard-MQL5-Tests mit MQL5 test.mq5 durchgeführt werden.

MQL5 vs MQL4 mit 9 Indikatoren, Übersichtstabelle


Schlussfolgerung

Wir haben einige interessante Experimente durchgeführt, die gegen das Paradigma des korrekten MQL5-Zugriffs auf Indikatoren verstoßen. Als Ergebnis haben wir mehr über den internen Mechanismus der Verarbeitung der Handles innerhalb des MQL5-Kerns gelernt:

  • Über die Nummerierung der Handles;
  • Über das Speichern und das Management der Handles.

Die Ergebnisse der Tests verschiedener Methoden zum Zugriff auf die Indikatoren zeigten, dass der Stil der MQL5-Zugriffe viel schneller ist als jeder andere MQL4-Stil (sowohl mit als auch ohne Handle-Speicher).