Die Ereignisverarbeitungsroutine "Neuer Balken"

Konstantin Gruzdev | 14 März, 2016

Einleitung

Für die Entwickler von Indikatoren und Expert-Systemen stand stets die Herausforderung im Vordergrund, einen hinsichtlich der Ausführungszeit möglichst ökonomischen Programmcode zu schreiben. Dieser Aufgabe kann man sich aus verschiedenen Richtungen annehmen. Aus diesem schier uferlosen Themenfeld werden wir in diesem Beitrag eine scheinbar bereits gelöste Frage behandeln: die Überprüfung des Auftretens eines neuen Balkens. Dabei handelt es sich um ein recht verbreitetes Verfahren zur Beschränkung der Anzahl der Berechnungszyklen, da alle Berechnungen und Handelsoperationen bei der Erstellung eines neuen Balkens in einem Diagramm nur einmal ausgeführt werden. Also, es geht um Folgendes:  

Verfahren zur Ermittlung des Auftretens eines neuen Balkens

Es gibt bereits annehmbare Varianten zur Lösung der Frage, wie das Auftreten eines neuen Balkens zu ermitteln ist. Sie finden sich beispielsweise in den Beiträgen Beschränkungen und Überprüfungen in Expert Advisors und Prinzipien der wirtschaftlichen Berechnung von Indikatoren sowie hier isNewBar. Ich kann die Lektüre dieser Materialien übrigens nur empfehlen. Sie erleichtern das Verständnis dessen, wovon hier die Rede ist.  

In diesen Materialien wird das Prinzip der Beobachtung des Zeitpunktes der Eröffnung eines aktuellen noch nicht geschlossenen Balkens eingesetzt. Das ist ein sehr einfaches und zuverlässiges Vorgehen. Es gibt jedoch auch andere Verfahren zur Ermittlung eines neuen Balkens.

Bei benutzerdefinierten Indikatoren können zu diesem Zweck zwei Eingangsparameter der Funktion OnCalculate() verwendet werden: rates_total bzw. prev_calculated. Die Beschränkung dieses Verfahrens besteht darin, dass es lediglich zur Ermittlung eines neuen Balkens in dem jeweils aktuellen Diagramm sowie ausschließlich in Indikatoren angewendet werden kann. Wenn ein neuer Balken in einem anderen Zeitraum oder bei einem anderen Kürzel ermittelt werden soll, bedarf dies weiterer Vorgänge.

Oder man kann einen neuen Balken zum Beispiel bei der ersten Kursänderung auf ihm erfassen, wenn für den Umfang der Kursänderung gilt: Tick Volume = 1, oder wenn alle Kurse in dem Balken gleich sind: Eröffnungskurs = Höchstkurs = Tiefstkurs = Schlusskurs. Diese Verfahren können zur Prüfung bedenkenlos eingesetzt werden, aber im „wahren Handelsleben“ sind sie zu störanfällig. Und zwar aufgrund der Zeitabstandes zwischen der ersten und der zweiten Kursänderung, der zur Erfassung sich bildenden Balkens nicht ausreicht. Das macht sich bei starken Marktbewegungen oder bei einer schlechten Datenübertragungsverbindung besonders bemerkbar.  

Es besteht die Möglichkeit, einen neuen Balken mithilfe der Funktion TimeCurrent() zu ermitteln. Außerdem handelt es sich dabei um ein geeignetes Verfahren, wenn das Auftreten eines neuen Balkens in dem aktuellen Diagramm ermittelt werden muss. Wir werden es am Ende dieses Beitrages anwenden.

Sie könnten auch einen Nachbarn fragen: „Hör‘ mal, ist da nicht ein neuer Balken aufgetaucht?“ Mich würde interessieren, wie die Antwort lautet. Entscheiden wir uns also für das Prinzip der Beobachtung der Eröffnungszeit des aktuellen nicht geschlossenen Balkens zur Ermittlung eines neuen. Hinsichtlich des Grades seiner Einfachheit und Zuverlässigkeit scheint dieses Vorgehen vertrauenerweckend, und es hat sich überdies bereits bewährt. 

Der Ausgangspunkt

In den oben aufgeführten Materialien ist es um die Beobachtung des Auftretens eines neuen Balkens nicht schlecht bestellt. Aber...  

Um zu verstehen, was dieses „aber“ zu bedeuten hat, nehmen wir als Ausgangspunkt oder Prototyp die einfache und gut funktionierende Funktion zur Überprüfung des Auftretens eines neuen Balkens aus dem Artikel Beschränkungen und Überprüfungen in Expert Advisors. Hier ist sie:

//+------------------------------------------------------------------+
//| Returns true if a new bar has appeared for a symbol/period pair  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

Dieser Prototyp unserer Funktion ist tatsächlich einsatzbereit und hat das Recht zu leben. Wäre da nicht... 

Analyse des Prototyps der Funktion

Ich habe diese Funktion in den Code meines selbstverständlich genialen und besten Expert-Systems kopiert. Das hat nicht funktioniert. Ich musste weiter suchen. Es folgen meine Überlegungen hinsichtlich dieser Funktion.

Kopfzeile der Funktion. Gehen wir der Reihe nach vor. Zunächst wäre da die Kopfzeile der Funktion:

bool isNewBar()

Die Kopfzeile der Funktion gefällt mir, sie ist sehr schlicht, selbsterklärend, und man muss sich nicht mit übernommenen Parametern herumschlagen. Es ist ratsam, sie in dieser Form für die Zukunft zu speichern.

Begrenzung der Anzahl der Aufrufe. Der Kopfzeile folgt die erste Anweisung, durch die folgende statische Variable bereitgestellt wird:

//---  memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;

Eigentlich ganz schön, wäre da nicht...

Das Problem besteht darin, dass wir eine statische Variable verwenden. In der Hilfe heißt es dazu: In der Hilfe heißt es dazu:

Statische Variablen bestehen ab dem Zeitpunkt der Ausführung des Programms und werden vor dem Aufruf der Sonderfunktion OnInit() lediglich einmal bereitgestellt. Sind keine Anfangswerte eingerichtet, so nehmen die Variablen der statischen Klasse als Anfangswerte jeweils „0“ an. 

Lokale Variablen, die mit der Deklaration „static“ ausgezeichnet sind, behalten ihren jeweiligen Wert während der gesamten Existenz der Funktion. Bei jedem nachfolgenden Aufruf der Funktion beinhalten die lokalen Variablen den Wert, den sie bei dem vorherigen Aufruf hatten.

Erfolgt der Aufruf dieses Funktionsprototyps von einer bestimmten Stelle aus, so erhalten wir, was wir brauchen. Wenn wir diese Funktion jedoch beispielsweise an einer anderen Stelle in demselben Berechnungszyklus noch einmal verwenden möchten, wird sie stets „False“ ausgeben, was bedeutet, dass „kein Balken vorhanden“ ist. Das stimmt jedoch nicht immer. In unserem Fall legt die statische Variable eine künstliche Beschränkung für die Anzahl der Aufrufe des Funktionsprototyps fest.

Frage der Allgemeingültigkeit. Die nächste Anweisung in dem Prototypen der Funktion sieht folgendermaßen aus:

//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

Logischerweise wird zum Abruf der Eröffnungszeit des letzten nicht geschlossenen Balkens die Funktion SeriesInfoInteger() mit dem Attribut SERIES_LASTBAR_DATE verwendet.

Unser Funktionsprototyp isNewBar() wurde von Anfang an ganz einfach angelegt und verwendet standardmäßig das Handelsinstrument und den Zeitraum des aktuellen Diagramms. Das ist annehmbar, wenn das Auftreten neuer Balken lediglich in dem aktuellen Diagramm beobachtet werden soll. Aber was ist zu tun, wenn Zeitraum und Instrument nicht nur des aktuellen Diagramms verwendet werden? Oder was ist, wenn man es mit einem ganz verschrobenen Diagramm zu tun hat? Etwa wenn man sich entschieden hat, ein Renko- oder Kagi-Diagramm abzubilden?

Fehlende Allgemeingültigkeit kann unseren Spielraum erheblich einschränken. Wir werden noch sehen, wie das im Handumdrehen zu beheben ist.  

Fehlerbehandlung. Werfen wir noch einmal einen Blick auf die Funktion SeriesInfoInteger(). Was wird sie Ihrer Ansicht nach wiedergeben, wenn sie ausgeführt wird, ohne dass das Diagramm bereits erstellt ist? Dazu kann es beispielsweise kommen, wenn Sie Ihr Expert-System oder Ihren Indikator in ein Diagramm eingebunden haben, und sich entschließen, den Diagrammzeitraum oder das betreffende Kürzel zu ändern, oder wenn Sie Ihr Programm neu starten müssen. Und was passiert während der Aktualisierung der Zeitreihen? In der Hilfe gibt es übrigen folgenden Hinweis dazu:

Verfügbarkeit der Daten

Das Vorliegen von Daten im Format HCC oder selbst im einsatzfähigen HC-Format bedeutet nicht immer die bedingungslose Verfügbarkeit dieser Daten zur Abbildung in einem Diagramm oder zur Verwendung in MQL5-Programmen.

Beim Zugriff auf Kursdaten oder Indikatorwerte aus MQL5-Programmen ist zu bedenken, dass ihre Verfügbarkeit weder zu noch ab einem bestimmten Zeitpunkt gewährleistet ist. Das ist damit verbunden, dass, um Speicherplatz zu sparen, in MetaTrader 5 keine vollständige Kopie der von dem MQL5-Programm benötigten Daten gespeichert wird, sondern ein Direktzugriff auf die Datenbank des Ausgabegerätes hergestellt wird.

Der Kursverlauf für alle Zeiträume wird aus den gemeinsamen Daten im Format HCC erstellt, und jede Aktualisierung der Daten auf dem Server zieht die Aktualisierung der Daten zu allen Zeiträumen sowie die Neuberechnung der Indikatoren nach sich. Infolgedessen kann der Zugriff auf Daten selbst dann verweigert werden, wenn Sekundenbruchteile vorher auf dieselben Daten noch zugegriffen werden konnte.

Was also genau wird diese Funktion liefern? Um dieser Ungewissheit zu entrinnen, müssen wir irgendwie anfangen, die Fehler bei der Abfrage der Eröffnungszeit des letzten nicht geschlossenen Balkens irgendwie zu erfassen.  

Möglichkeit zur Bereitstellung. Weiter im Text. Sehen wir uns die nächsten Anweisungen unseres Funktionsprototypen an:

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

Hier stimmt alles haargenau. Es gibt allerdings eine Kleinigkeit. Haben Sie den oben angeführten Satz aus der Hilfe beachtet: „Statische Variablen bestehen ab dem Zeitpunkt der Ausführung des Programms und werden vor dem Aufruf der Sonderfunktion OnInit() lediglich einmal bereitgestellt“? Aber was ist, wenn die Variable last_time noch einmal bereitgestellt werden muss? Genauer, was ist zu tun, um die Situation für den ersten Aufruf künstlich zu erschaffen? Oder irgendeine andere Situation? Es ist leicht, Fragen zu stellen, wenn man die Antworten bereits kennt. Aber dazu kommen wir noch.

Anzahl der Balken. In unserem Funktionsprototyp folgt dieser Code:

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }

Ein Programmierer wie ich, kann den Operator „if“ so einrichten, dass der das Ausgabegerät des Anwenders ebenso verblüfft wie das Programm zur Prüfung der Handelsstrategie. Das liegt daran, dass in der Natur der Sache die Vergangenheit bei der Zeit immer geringere Werte aufweist als die Gegenwart. Also gilt: last_time < lastbar_time. Bei mir hat sich aus einem zufälligen Programmierfehler eine „Zeitmaschine“ ergeben, genauer gesagt eine Umkehrung in lastbar_time < last_time. Ist das nicht erstaunlich? Generell ist ein solches Zeitparadoxon leicht zu ermitteln, und die entsprechende Fehlermeldung erscheint sofort.

Aber: kein Rauch ohne Feuer. Bei der Beobachtung unserer „Zeitmaschine“ ist aufgefallen, dass zwischen den Aufrufen von isNewBar() nicht ein einziger neuer Balken entstehen kann. Je kürzer der Diagrammzeitraum desto größer die Wahrscheinlichkeit des Auftretens mehrerer Balken zwischen den Funktionsaufrufen. Dafür kann es zahlreiche Gründe geben: angefangen mit einem „großen Zeitbedarf für die Berechnungen“ bis hin zu einer vorübergehenden Unterbrechung der Verbindung zum Server. Die Möglichkeit, nicht nur das Signal für das Auftreten eines neuen Balkens zu empfangen, sondern auch die Anzahl der Balken, wird gewiss hilfreich sein.

Unser Funktionsprototyp endet wie folgt:

//--- if we passed to this line, then the bar is not new; return false
   return(false);

Nun, wenn wir bis hierher gekommen sind, heißt das, dass der Balken nicht neu ist.

Anlegen einer neuen Funktion isNewBar() 

Hier fängt es an, interessant zu werden. Wir machen uns an die Behebung der festgestellten Schwachpunkte. Um es gleich vorweg zu sagen, ich habe etwas tief gestapelt, als ich diesen Abschnitt mit „Anlegen einer neuen Funktion isNewBar()“ überschrieben habe. Wir werden etwas Solideres machen.

Zunächst werden wir uns damit beschäftigen, wie wir die Beschränkung der Anzahl der Funktionsaufrufe loswerden können.

Spontan kommt einem in den Sinn, dass man ja die passenden gleichnamigen Funktionen wie isNewBar() aus dem Artikel Prinzipien der wirtschaftlichen Berechnung von Indikatoren oder von hier aus dem Abschnitt isNewBar verwenden kann. Das heißt, man bindet Datenfelder zur Speicherung mehrerer last_time-Werte in den Haupttext der Funktion ein, legt Zähler für die Aufrufe der Funktion isNewBar() von unterschiedlichen Stellen an und so fort. Dabei handelt es sich zweifelsfrei um arbeitsfähige Varianten, die tatsächlich eingesetzt werden können. Aber wir nehmen einmal an, wir wollten ein mehrwährungsfähiges Expert-System für die Arbeit mit 12 Währungspaaren programmieren. Wie schafft man es, bei den ganzen zu berücksichtigenden Feinheiten nicht den Überblick verlieren?

Was tun? Die Antwort ist hier!

Das Schöne an der objektorientierten Programmierung ist, dass alle Objekte oder Instanzen einer Klasse ihr eigenes Dasein unabhängig von den übrigen Mitgliedern dieser Klasse fristen können. Fangen wir also mit der Erstellung der Klasse CisNewBar an, um uns die Möglichkeit zu verschaffen, die Instanzen dieser Klasse an jeder Stelle unseres Expert-Systems oder Indikators beliebig oft zu „vermehren“. Damit jede Instanz „ihr eigenes Leben lebt“.

Das haben wir für den Anfang zur Hand:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar();       // First type of request for new bar
  };  

bool CisNewBar::isNewBar()
  {
   //--- here is the definition of static variable

   //--- here will be the rest of method's code   
   ...

   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Was zuvor die Funktion isNewBar() gewesen ist, ist jetzt eine Methode. Beachten Sie bitte, dass wir es jetzt nicht mehr mit der statischen Variablen last_time zu tun haben, sondern dass die geschützte Klassenvariable m_lastbar_time an ihre Stelle getreten ist. Hätten wir die statische Variable in der Methode isNewBar() gelassen, so wären all unsere Mühen vergebens gewesen, da dieselben Probleme weiter bestehen würden wie vorher mit der Funktion isNewBar(), das sind eben die Besonderheiten statischer Variablen.

Jetzt wird die Zeit des letzten Balkens in der geschützten Variablen der Klasse m_lastbar_time gespeichert, und in jeder Instanz der Klasse wird für sie ein eigener Platz im Speicher des Computers festgelegt. Auf diese Weise haben wir die für unseren Funktionsprototypen bestehende Beschränkung der Anzahl der Aufrufe aufgehoben. Wir können die Methode isNewBar() an verschiedenen Stellen unseres MQL5-Programms so oft aufrufen, wie wir wollen, wobei wir für jede Stelle eine Instanz unserer Klasse erstellen.

Da haben wir schon ganz schön etwas geschafft, aber packen wir nun die Allgemeingültigkeit an. Bevor wir unserer neuen Klasse irgendetwas hinzufügen, möchte ich Ihnen einen amüsanten Gedanken vorführen.

Lassen Sie uns nachdenken! Was wollen wir? Wir wollen bei Auftreten eines neuen Balkens ein Signal empfangen. Wie wollen wir das bewerkstelligen? Also, wenn der Zeitpunkt der Eröffnung des aktuellen, nicht geschlossenen Balkens bei der letzten Kursänderung (oder zum letztmöglichen Zeitpunkt) höher (= später) ist als der Zeitpunkt der Eröffnung des aktuellen, nicht geschlossenen Balkens bei der vorhergehenden Kursänderung (oder zum vorhergehenden Zeitpunkt), dann wurde ein neuer Balken angelegt. Ich weiß zwar nicht, was ich da geschrieben habe, aber es stimmt. Das Wesentliche besteht darin, dass wir die Zeitpunkte vergleichen müssen. Deshalb habe ich entschieden, dass es logisch ist, wenn man die Eröffnungszeit „newbar_time“ des aktuellen, nicht abgeschlossenen Balkens als Parameter an die Methode isNewBar() weitergibt. Die Kopfzeile der Methode sieht dann wie folgt aus:

bool isNewBar(datetime newbar_time)

Fragen Sie jetzt nur nicht, woher wir die newbar_time nehmen, wir gehen einfach davon aus, dass sie bekannt ist. Wir kommen später darauf zurück.  

Durch die Übergabe des Zeitpunktes an die Methode isNewBar() erhalten wir ein recht flexibles Instrument zur Beobachtung des Auftretens neuer Balken. Wir können alle Standardzeiträume von Diagrammen mit allen möglichen Handelsinstrumenten erfassen. Es hat sich somit ergeben, dass wir jetzt nicht mehr von der Bezeichnung des Kürzels und dem Umfang des Zeitraums abhängig sind.  

Auch nicht dem Standard entsprechende Zeiträume können erfasst werden. Bei der Erstellung von Kursänderungskerzen oder Renko- bzw. Kagi-Diagrammen zum Beispiel fällt der Zeitpunkt der Eröffnung des Balkens faktisch nie mit den Standardzeiträumen für Diagramme zusammen. In diesem Fall ist unsere Funktion unersetzlich.

Damit wäre für uns jetzt mit der Allgemeingültigkeit alles in Ordnung. Jetzt erweitern wir unsere Klasse CisNewBar unseren Vorstellungen entsprechend:

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      uint              m_retcode;        // Result code of detecting new bar
      int               m_new_bars;       // Number of new bars
      string            m_comment;        // Comment of execution
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar(datetime new_Time); // First type of request for new bar
  };
   
//+------------------------------------------------------------------+
//| First type of request for new bar                                |
//| INPUT:  newbar_time - time of opening (hypothetically) new bar   |
//| OUTPUT: true   - if new bar(s) has(ve) appeared                  |
//|         false  - if there is no new bar or in case of error      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Initialization of protected variables
   m_new_bars = 0;      // Number of new bars
   m_retcode  = 0;      // Result code of detecting new bar: 0 - no error
   m_comment  =__FUNCTION__+" Successful check for new bar";
   //---
   
   //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // If new bar is older than last bar, print error message
      m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+
                                                  ", time of new bar request "+TimeToString(newbar_time);
      m_retcode=-1;     // Result code of detecting new bar: return -1 - synchronization error
      return(false);
     }
   //---
        
   //--- if it's the first call
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- set time of last bar and exit
      m_comment   =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Check for new bar:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Number of new bars
      m_lastbar_time=newbar_time; // remember time of last bar
      return(true);
     }
   //---
   
   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

Beim Ansehen des Codes unserer Klasse, haben Sie wahrscheinlich bemerkt, dass wir die Erfassung von Ausführungsfehlern studiert und die Variable zur Speicherung der Anzahl neuer Balken eingeführt haben.

Es wäre alles in Ordnung, wenn unsere allgemein gültige Methode isNewBar(datetime newbar_time) nicht ein großes Manko aufweisen würde. Diese Schwäche besteht darin, dass wir uns im Code unseres Expert-Systems oder Indikators stets um die Berechnung der Zeit des vermeintlichen neuen Balkens (newbar_time) kümmern müssen.  

Glücklicherweise können wir uns die Sache in einigen Fällen erleichtern, indem wir diese Funktion an die neue zusätzliche Methode unserer Klasse übertragen. Das ist bei Standardzeiträumen und -handelsinstrumenten wie in unserem Funktionsprototyp möglich, indem man die zweite Variante der bekannten Funktion SeriesInfoInteger() mit dem Attribut SERIES_LASTBAR_DATE einsetzt, während in allen anderen Fällen eine allgemein gültige Methode verwendet wird. Und das kam bei mir heraus:

//+------------------------------------------------------------------+
//| Second type of request for new bar                               |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - Number of new bars                          |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Request time of opening last bar:
   ResetLastError(); // Set value of predefined variable _LastError as 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // If request has failed, print error message:
      m_retcode=GetLastError();  // Result code of detecting new bar: write value of variable _LastError
      m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Next use first type of request for new bar, to complete analysis:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Correct number of new bars:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- If we've reached this line - then there is(are) new bar(s), return their number:
   return(m_new_bars);
  }

Also, was haben wir bis jetzt? Wir müssen uns bei Standardzeiträumen nicht mehr um die Ermittlung des Zeitpunktes der Eröffnung des letzten noch nicht abgeschlossenen Balkens kümmern. De facto sind wir bei unserem Funktionsprototypen mit seinem einfachen Aufruf, aber ohne seine ehemaligen Schwächen angekommen. Und wir haben sogar einige Extras erhalten, darunter die Fehlercodes, Kommentare zur Ausführung und die Anzahl der neuen Balken.    

Ist sonst noch etwas zu tun? Ja. Es bleibt ein letztes Moment, die Bereitstellung. Dazu verwenden wir den Konstruktor der Klasse sowie einige Einstellungsmethoden der Art „Set“. Unser Klassenkonstruktor sieht so aus:  

//+------------------------------------------------------------------+
//| CisNewBar constructor.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Result code of detecting new bar
   m_lastbar_time=0;    // Time of opening last bar
   m_new_bars=0;        // Number of new bars
   m_comment="";        // Comment of execution
   m_symbol=Symbol();   // Symbol name, by default - symbol of current chart
   m_period=Period();   // Chart period, by default - period of current chart
  }

Und so die „Set“-Methoden:

//--- Methods of initializing protected data:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

Dank des Klassenkonstruktors müssen wir uns nicht weiter mit der Bereitstellung des Handelsinstruments und des Zeitraums für das aktuelle Diagramm befassen. Wie in dem Prototyp der Funktion werden sie standardmäßig verwendet. Wenn wir jedoch ein anderes Handelsinstrument oder einen anderen Diagrammzeitraum verwenden müssen, können wir dazu auf die von uns angelegten „Set“-Methoden zurückgreifen. Außerdem können wir die Situation des „ersten Aufrufs“ erforderlichenfalls mithilfe der Methode SetLastBarTime(datetime lastbar_time) die Situation des „ersten Aufrufs“ wiederherstellen.

Zum Abschluss legen wir einige „Get“-Methoden an, um in Expert-Systemen und Indikatoren Daten aus unserer Klasse zu beziehen: 

      //--- Methods of access to protected data:
uint              GetRetCode()     const  {return(m_retcode);     }  // Result code of detecting new bar 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Time of opening last bar
int               GetNewBars()     const  {return(m_new_bars);    }  // Number of new bars
string            GetComment()     const  {return(m_comment);     }  // Comment of execution
string            GetSymbol()      const  {return(m_symbol);      }  // Symbol name
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Chart period

Jetzt können wir in unseren MQL5-Programmen alle erforderlichen Informationen beziehen. Damit kann die die Erstellung der Klasse CisNewBar endgültig abgeschossen werden.

Der vollständige Code unserer Klasse befindet sich in der Datei Lib CisNewBar.mqh im Anhang.

Anwendungsbeispiele für die Klasse CisNewBar

Ich schlage vor, einige Beispiele für die Verwendung unserer Klasse mit dem Ziel zu betrachten, alle Einzelheiten dessen, was wir geschaffen haben, zu durchdringen. Es mag sein, dass wir nicht nur Vorzüge sondern auch Nachteile entdecken.

Beispiel 1. Fürs Erste erstellen wir ein vollkommen identisches Expert-System wie das für die Funktion isNewBar() aus dem Beitrag Beschränkungen und Überprüfungen in Expert Advisors:

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Lassen wir beide Expert-Systeme auf Diagrammen mit demselben Währungspaar und dem gleichen Zeitraum laufen. Sehen wir, was dabei herausgekommen ist:

Zunächst einmal melden beide Expert-Systeme zeitgleich das Auftreten eines neuen Balkens. Danach schweigen sie und melden erst vier Minuten später, dass ein neuer Balken aufgetreten ist (dieser Zeitpunkt ist mit der Ziffer „1“ gekennzeichnet). Mit den Expert-Systemen ist alles in Ordnung, ich habe lediglich für einige Minuten die Verbindung zum Internet unterbrochen, um zu sehen, was passiert. Obwohl sich einige Balken gebildet haben, wurde uns das nicht mitgeteilt. In unserem neuen Expert-System können wir das beheben, da unsere Methode isNewBar() das möglich macht.

Als Nächstes habe ich den Diagrammzeitraum auf zweiminütig (M2) heraufgesetzt. Die Expert-Systeme haben unterschiedlich darauf reagiert. Das Expert-System CheckLastBar begann, einmal alle 2 Minuten das Auftreten eines neuen Balkens zu melden, Example1NewBar meldet das Auftreten neuer Balken dagegen weiter jede Minute, als sei der Zeitraum gar nicht geändert worden (mit „2“ gekennzeichnet).

Es geht darum, dass die Bereitstellung unserer Instanz der Klasse current_chart bei der Verbindung des Expert-Systems mit dem Diagramm durch den Klassenkonstruktor vorgenommen worden ist. Wird der Zeitraum bei bereits bestehender Verbindung des Expert-Systems mit dem Diagramm geändert, unterbleibt die Auslösung des Konstruktors, und das Expert-System wird weiterhin mit dem einminütigen Zeitraum (M1) ausgeführt. Das besagt, dass die von uns erzeugte Klasseninstanz „ihr eigenes Leben lebt“, und dass die Veränderungen der Umgebung keine Auswirkungen auf sie haben. Das kann je nach zu lösender Aufgabe ebenso ein Vor- wie ein Nachteil sein.  

Damit unser Expert-System so arbeitet wie sein Pendant CheckLastBar, müssen in der Funktion OnInit() die geschützten Klassenvariablen m_symbol und m_period bereitgestellt werden. Packen wir‘s an.

Beispiel 2. Wir nehmen an unserem Expert-System einige Ergänzungen vor und vergleichen seine Leistung noch einmal mit der von CheckLastBar. Der Programmcode des Expert-Systems ist in der DateiExample2NewBar.mq5 im Anhang beigefügt. Wir führen beide Expert-Systeme auf Diagrammen mit demselben Währungspaar und dem gleichen Zeitraum aus. Dann legen wir für sie noch dieselben Beeinträchtigungen an wie beim letzten Mal. Sehen wir, was dabei herausgekommen ist:

Wie beim letzten Mal melden die Expert-Systeme anfangs gleichzeitig das Auftreten eines neuen Balkens. Dann trenne ich für ein paar Minuten die Verbindung zum Internet... Und stelle sie wieder her. Unser neues Expert-System hat nicht bloß gemeldet, dass neue Balken aufgetreten sind, sondern auch wie viele (mit „1“ gekennzeichnet). Für die Mehrzahl der Indikatoren und Expert-Systeme bezeichnet diese Ziffer die Anzahl der nicht berechneten Balken. Somit haben wir eine gute Grundlage für die Erstellung ökonomischer Algorithmen für die Umrechnung.  

Als Nächstes habe ich die Diagrammzeiträume auf zweiminütig (M2) heraufgesetzt. Anders als in Beispiel 1 arbeiten die Expert-Systeme im Gleichschritt (mit „2“ gekennzeichnet). Dabei war die Bereitstellung der geschützten Klassenvariablen m_symbol und m_period in der Funktion OnInit() hilfreich! Bei einem Wechsel des zu bearbeitenden Instruments (mit „3“ gekennzeichnet) funktionieren die Expert-Systeme ebenfalls auf gleiche Weise. 

Beispiel 3. In unserer Klasse CisNewBar haben wir die Möglichkeit zur Beobachtung des Auftretens von Fehlern angelegt. Es kommt vor, dass ein Expert-System so ausgelegt ist, dass keine Notwendigkeit besteht, Fehler nachzuverfolgen. Dann verwenden Sie diese Möglichkeit hier also einfach nicht. Wir werden versuchen, künstlich eine Situation zu schaffen, in der ein solcher Fehler möglich ist, und diesen zu erfassen. Dazu erweitern wir den Code des Expert-Systems ein wenig (s. die Datei Example3NewBar.mq5).

Was mache ich? Wie üblich führe ich das Expert-System Example3NewBar auf Minutendiagrammen aus. Danach beginne ich, zum Beispiel die Kürzel des Diagramms zu ändern in der Hoffnung, dass eine Situation eintritt, in der die Anwendung auf dem Ausgabegerät es nicht schafft, die Zeitreihe vor der Abfrage durch das Expert-System aufzubauen. Generell quäle ich die Anwendungsinstanz auf dem Ausgabegerät von ganzem Herzen, um zu sehen, was dabei herauskommt...  

Nach mehreren Versuchen hat unser Expert-System einen Fehler erkannt:

 

Jetzt können wir mit Gewissheit sagen, dass wir Ausführungsfehler ermitteln können. Wie wir mit ihnen umgehen, ist Geschmackssache. Beachten Sie, dass wir den Fehler vier Mal beobachtet haben. Nach dem Herunterladen und der Erstellung des Diagramms hat das Expert-System gemeldet, dass wir insgesamt nur einen Balken verpasst haben.

Übrigens konnten alle, die sich den Programmcode des Expert-Systems angesehen haben, feststellen, dass die Prüfung auf Fehler nur dann Sinn macht, wenn die Methode isNewBar() einen Wert kleiner oder gleich „0“ ausgibt.

Achtung: Wenn Sie im Verlauf dieses Experiments beginnen, den Diagrammzeitraum zu ändern, wird beim Heraufsetzen von einem kurzen auf einen längeren Zeitraum ein Fehler bei der Synchronisierung gemeldet. Zum Beispiel liegt der Zeitpunkt der Eröffnung eines Balkens in einem einstündigen Zeitraum (H1) in 59 Fällen vor dem eines Minutenzeitraums (M1). Um das Auftreten dieses Fehlers beim Wechsel des Diagrammzeitraums zu vermeiden, muss die Variable m_lastbar_time in der Funktion OnInit() mithilfe der Methode SetLastBarTime (datetime lastbar_time) korrekt bereitgestellt werden.

Beispiel 4. In diesem Beispiel gestalten wir die Aufgabe für das Expert-System etwas anspruchsvoller. Wir nehmen drei Währungspaare: EURUSD in M1, GBPUSD in M1 sowie USDJPY in M2. Das Diagramm des ersten Paares ist jeweils das aktuelle, in ihm achten wir einfach auf das Auftreten eines neuen Balkens. Anhand des zweiten Paares berechnen wir die Anzahl der nach dem Aufrufen des Expert-System entstandenen Balken. Die Berechnung wird ausgeführt, wenn das erste Paar signalisiert, dass sich ein neuer Balken gebildet hat. Und anhand des dritten Paares stellen wir fortlaufend (bei Auftreten eines Balkens unter EURUSD) die geschützte Klassenvariable m_lastbar_time bereit. Der Programmcode des Expert-Systems befindet sich in der DateiExample4NewBar.mq5 im Anhang.

Mithilfe dieses Beispiels möchte ich klären, wie unsere Funktionsklasse CisNewBar im Mehrwährungsmodus funktioniert. Also, löse ich sie aus... Und das kam dabei heraus:

Die Ergebnisse geben Anlass zu Fragen. Ich gieße Öl ins Feuer und lasse genau diesen Zeitabschnitt durch das Prüfprogramm laufen. Die Ergebnisse des Strategieprüfprogramms:

Hier können wir das Spiel „Finde zehn Unterschiede“ spielen. Neben den Eigentümlichkeiten des Einsatzes des Expert-Systems in einem Demo-Konto fällt auf, dass zwischen dem Demo-Konto und dem Prüfprogramm Unterschiede bestehen und mit bloßem Auge erkennbar sind. Ein ähnlicher Vergleich ermöglicht bei richtiger Vorgehensweise nicht nur die Aufdeckung der Unzulänglichkeiten des Expert-Systems sondern auch deren Behebung. Also, ich werde jetzt keine Analyse durchführen, wieso es dazu gekommen ist, wie es dazu gekommen ist, und was am Expert-System verändert werden muss.  

Beispiel 5. Bisher haben wir die allgemeingültigste Methode zur Ermittlung eines neuen Balkens, isNewBar(datetime newbar_time), in den Beispielen noch nicht ein einziges Mal ausdrücklich verwendet. Ich benutze dazu den Kerzenindikator aus dem Artikel Erzeugung von Kursschwankungs-Indikatoren in MQL5 und ergänze ihn um einen Puffer zur Speicherung des Zeitpunktes der Eröffnung des Balkens (s. die Datei TickColorCandles v2.00.mq5). Ich werde ein ganz kurzes Expert-System programmieren, das den Zeitpunkt des Auftretens einer neuen Kursänderungskerze meldet (s. die Datei Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick
int HandleIndicator;  // indicator handle
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Get indicator handle:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Error when creating indicator handle, error code: ",GetLastError());
      Print(" Incorrect initialization of Expert Advisor. Trade is not allowed.");
      return(1);
     }

//--- Attach indicator to chart:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Error when attaching indicator to chart, error code: ",GetLastError());
      return(1);
     }
//--- If you passed until here, initialization was successful
   Print(" Successful initialization of Expert Advisor. Trade is allowed.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Get time of opening last unfinished tick candlestick:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Failed to get time value of indicator. "+
            "\nNext attempt to get indicator values will be made on the next tick.",GetLastError());
      return;
     }
//--- Detect the next tick candlestick:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("New bar. Opening time: %s  Time of last tick: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

Sie werden sicher darauf geachtet haben, wie die Eröffnungszeit einer Kursschwankungskerze abgerufen werden kann. Ist doch ganz einfach? Ich lege den Indikator und das Expert-System in den entsprechenden Ordnern ab, stelle das Expert-System zusammen und lasse es laufen. Es hat sich gelohnt, hier sind die Ergebnisse:  

 

Die Ereignisverarbeitungsroutine „Neuer Balken“


Zum Ende dieses Beitrags kommend möchte ich einen weiteren Gedanken äußern. In dem Forum (in russischer Sprache) wurde die Vorstellung geäußert, dass es gut wäre, eine Standardroutine zur Verarbeitung des Ereignisses „neuer Balken“ zu haben. Mag sein, dass die Entwickler eins Tages darauf stoßen, kann aber auch sein, dass nicht. Der Reiz von MQL5 besteht darin, dass in ihr selbst die grandiosesten Ideen elegant und einfach umgesetzt werden können.

Wenn eine Ereignisverarbeitungsroutine „Neuer Balken“ (NewBar) gewünscht wird, legen wir eine an! Umso mehr, da wir dieses Ereignis jetzt mithilfe unserer Klasse mit Leichtigkeit „einfangen“ können. So wird unser Expert-System mit der Verarbeitungsroutine OnNewBar() für das Ereignis NewBar aussehen:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler

//+------------------------------------------------------------------+
//| New bar event handler function                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

Prächtig. Von außen wirkt unser Expert-System eher schlicht. Diese Routine gibt lediglich eine Textzeile bezüglich des Auftretens eines neuen Balkens aus. Mehr nicht. Um nachzuvollziehen, wie wir das Ereignis NewBar beobachten und wie die Verarbeitungsroutine ausgelöst wird, müssen wir einen Blick in die Datei OnNewBar.mqh werfen:

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Number of seconds in current chart period
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart
   if(current_chart.isNewBar(new_time)) OnNewBar();               // When new bar appears - launch the NewBar event handler
  }

Wie zu sehen gibt es auch hier nichts Kompliziertes. Allerdings sind da noch zwei Gesichtspunkte, auf die ich Ihre Aufmerksamkeit lenken möchte:

Zum Ersten. Wie Sie bemerkt haben werden, verwende ich die Funktion TimeCurrent() zur Berechnung des Eröffnungszeitpunktes des Balkens und setze die erste Methode zur Prüfung des Eintretens des Ereignisses NewBar aus unserer Klasse ein. Das hat einen angenehmen Vorteil. Er besteht darin, dass dieses Vorgehen anders als bei Verwendung der Funktion SeriesInfoInteger() mit dem Attribut SERIES_LASTBAR_DATE keine Verarbeitung von Fehlern erfordert. Für uns zählt, dass unsere Verarbeitungsroutine OnNewBar() möglichst zuverlässig ist.

Zum Zweiten. Der Einsatz der Funktion TimeCurrent() zur Berechnung des Eröffnungszeitpunktes des Balkens ist der schnellste Weg. Die Verwendung der Funktion SeriesInfoInteger() für diese Zwecke ist auch ohne Fehlerüberwachung langsamer.

Das Ergebnis der Arbeit unserer Verarbeitungsroutine:

   

Fazit

  Im Verlauf der Vorstellung der Materialien haben wir eine gründliche Untersuchung der Verfahren zur Ermittlung neuer Balken unternommen. Es wurden die Mängel und Vorzüge der bestehenden Vorgehensweisen zur Erfassung der Zeitpunkte des Auftretens neuer Balken dargelegt. Ausgehend von dem, was war, haben wir die Klasse CisNewBar geschaffen, die es möglich macht, das Ereignis „neuer Balken“ bei praktisch allen Aufgaben ohne weiteren Programmieraufwand zu erfassen. Dabei konnten wir den meisten Unzulänglichkeiten früherer Lösungen aus dem Weg gehen.    

Die betrachteten Beispiele haben uns geholfen, die Vor- und Nachteile der von uns ersonnenen Methoden zu erkennen. Besondere Aufmerksamkeit hinsichtlich der Richtigkeit der Arbeit erfordert der Mehrwährungsmodus. Es muss eine gründliche Analyse der zutage geförderten Schwächen durchgeführt und Möglichkeiten zu ihrer Behebung entwickelt werden.

  Die von uns erstellte Verarbeitungsroutine für das Ereignis „neuer Balken“ ist lediglich für Expert-Systeme mit nur einer Währung geeignet. Aber wir haben gelernt, zu diesem Zweck ein zuverlässigeres und schnelleres Verfahren einzusetzen. Jetzt können wir einen Schritt weiter gehen und eine mehrwährungsfähige Routine für die Verarbeitung des Ereignisses „neuer Balken“ schaffen. Aber das ist wiederum Thema eines ganz eigenen Beitrags.