Eine neue Schiene: Benutzerdefinierte Indikatoren in MQL5

TheXpert | 11 Januar, 2016

Einleitung

Endlich haben wir eine Gelegenheit, das neue Handelsterminal auszuprobieren – MetaTrader 5. Es ist zweifelsohne bemerkenswert und hat im Vergleich zu seinem Vorgänger viele neue Funktionen. Zu den wichtigen Vorteilen dieser Plattform zählen unter anderem:

Ich werde nicht alle neuen Möglichkeiten und Funktionen des neuen Terminals und der Sprache aufzählen. Sie sind zahlreich und einige der Neuheiten sind eine Diskussion in einem eigenen Beitrag wert. Auch gibt es hier keinen Code, der mit objektorientierter Programmierung geschrieben wurde. Das Thema ist zu wichtig, um es nur im Kontext zusätzlicher Vorteile für Entwickler zu erwähnen.

In diesem Beitrag gehen wir auf Indikatoren, ihre Struktur, Zeichnung, Typen und Programmierdetails im Vergleich zu MQL4 ein.

In diesem Beitrag gibt es keine komplizierten Details. Zudem kann alles, worauf hier eingegangen wird, mithilfe der angehängten Dateien direkt im Terminal geprüft werden.

Ich hoffe, dass dieser Beitrag für Einsteiger und erfahrene Entwickler hilfreich sein wird. Vielleicht entdeckt hier auch jemand etwas Neues.

 

Die allgemeine Struktur

Die allgemeine Struktur der Indikatoren hat sich gegenüber MQL4 nicht verändert.

Wie schon zuvor gibt es drei Funktionen – Initialisierung, Datenverarbeitung und Deinitialisierung von Indikatoren.

Wie zuvor können viele Parameter von Indikatoren durch Eigenschaften definiert werden (Schlüsselwort #property). Die meisten davon sind speziell für Indikatoren ausgelegt. Wie zuvor sind Eigenschaften und Eingabeparameter in einem globalen Kontext definiert.

Gehen wir beispielsweise auf die Implementierung der benutzerdefinierten Farbgebung für RSI-Indikatoren ein. Hier ist die gekürzte Version, die vollständige finden Sie in der Datei Color_RSI.mq5.

Betrachten wir die Teile des Codes.

//--- group of data properties
#property copyright "TheXpert"
#property link      "theforexpert@gmail.com"
#property version   "1.00"
//--- description of the indicator should not exceed 511 symbols in total
//--- including newline symbols
#property description "      "
#property description "Demonstration of the indicator creation"
#property description "by the example of RSI coloring"

Die oben festgelegten Eigenschaften werden in der Informationsleiste des Indikators angezeigt (Registerkarte "Common" (Allgemein) in den Eigenschaften). So sieht es aus:

//--- indicator properties
#property indicator_separate_window // the indicator will be displayed in a separate subwindow
#property indicator_buffers 2       // number of used buffers
#property indicator_plots   1       // number of displayed buffers
//--- plot 1
#property indicator_color1 clrDarkSalmon, clrDeepSkyBlue // use 2 colors
#property indicator_type1  DRAW_COLOR_LINE               // and the special color display type

Diese Eigenschaften sind die Eigenschaften des Indikators. Die Beschreibung der anderen Eigenschaften finden Sie in der Hilfe.

//---- buffers
double Values[];                 // buffer of values
double ValuesPainting[];         // buffer of color indices
//--- indicator input parameters
input string             _1           = "RSI parameters";
input int                RSIPeriod    = 5;
input int                SmoothPeriod = 5;
input ENUM_APPLIED_PRICE AppliedPrice = PRICE_CLOSE;
input string             _2           = "Color settings";
input color              Down         = clrDarkSalmon;
input color              Up           = clrDeepSkyBlue;
//--- variable for storing the indicator handle
int RSIHandle;

Hier sind die Eingabeparameter und globalen Variablen des Indikators (verwechseln Sie diese nicht mit globalen Variablen des Client Terminals). Die Eingabeparameter des Indikators werden durch den Identifikator input festgelegt.

Es ist nun möglich, eine Aufzählung für die Eingabeparameter einzurichten. Dies ist manchmal nützlich, um die Auswahl falscher Parameter zu verhindern.

Beispielsweise wird der Parameter AppliedPrice in einer Drop-Down-Liste mit möglichen gültigen Werten angezeigt.

Also werden alle Aufzählungen, einschließlich der benutzerdefinierten, in derselben Drop-Down-Liste angezeigt. Beispielsweise wird der folgende Parameter

//...
enum DayOfWeek
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

input DayOfWeek Day;

//...

folgendermaßen angezeigt:

int OnInit()
  {
//--- bind indicator buffers
//--- Values serves as a display buffer
   SetIndexBuffer(0,Values,INDICATOR_DATA);
//--- ValuesPainting serves as the buffer for storing colors
   SetIndexBuffer(1,ValuesPainting,INDICATOR_COLOR_INDEX);
//--- Set the start of drawing Values buffer
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,RSIPeriod);
//--- Set the indicator name
   IndicatorSetString(INDICATOR_SHORTNAME,"Color RSI("+string(RSIPeriod)+")");
//--- Set an empty value for plots
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
//--- Set buffer colors
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,Down);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,Up);
//--- Receive indicator handles
   RSIHandle=iRSI(NULL,0,RSIPeriod,AppliedPrice);
//--- Set the sequence of buffer indexation
   ArraySetAsSeries(Values,true);
   ArraySetAsSeries(ValuesPainting,true);
//--- successful execution
   return(0);
  }

OnInit ist die Initialisierungsfunktion des Indikators. Hier konfigurieren wir die Indikatorpuffer und ihre Eigenschaften und definieren die Variablen des Indikators, die nicht in den Eigenschaften definiert werden können oder dynamisch eingerichtet werden müssen. Es gibt außerdem eine anfängliche Dateninitialisierung, einschließlich der Zuweisung der Handles, die für die Indikatoren benötigt werden.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- number of bars for calculation
   int toCount=(int)MathMin(rates_total,rates_total-prev_calculated+1);
//--- try to copy iRSI indicator data
   if(CopyBuffer(RSIHandle,0,0,toCount,Values)==-1)
     {
      Print("Data copy error, №",GetLastError());
      //--- return command for recalculation of the indicator values
      return(0);
     }
//--- coloring. Yer, now it has become that easy
   for(int i=toCount-2;i>=0;--i)
     {
      //--- coloring the first line
      if(Values[i+1]!=EMPTY_VALUE && Values[i]>Values[i+1])
         ValuesPainting[i]=1;
      else
         ValuesPainting[i]=0;
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

OnCalculate ist die Funktion für die Datenberechnung. Diese Funktion kann zwei Formen annehmen. Hier ist die Standardform. Die Details sind unten aufgeführt.

Funktion:

//--- this function usage in not obligatory
/*
void OnDeinit()
{

}
*/

OnDeinit ist die Deinitialisierungsfunktion des Indikators. Es ist oftmals erforderlich, die Ressourcen, beispielsweise Datei-Handles, freizugeben. In anderen Fällen ist diese Funktion nicht erforderlich.

 

Zwei Konzepte von Indikatoren

Das erste ist Standard – das gleiche, an das wir uns bereits in MQL4 gewöhnt haben, aber in einer leicht abgewandelten Form. Die Funktion OnCalculate wird anstatt der Funktion Start verwendet.

Für die Standardform sieht es wie folgt aus:

int OnCalculate(const int rates_total,      // Arrays size
                const int prev_calculated,  // Bars processed on the previous call
                const datetime& time[],     // Data for the current chart and timeframe...
                const double& open[],
                const double& high[],       
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
{
   return rates_total;
}

Um die erforderliche Menge an Code für das Kopieren von Daten zu reduzieren, werden die Diagrammdaten direkt als Arrays an die Parameter der Funktion übergeben. Außerdem wird die Anzahl verfügbarer Bars als erster Parameter der Funktion übergeben, die Anzahl der verarbeiteten Bars nach dem letzten Aufruf oder 0 (Null) als zweiter Parameter.

Der Wert 0 (Null) kann als erster Indikatoraufruf sowie beim Laden neuer oder fehlender Daten übergeben werden. Dieser Parameter ist ein Ersatz (ob Alternative oder Äquivalent, ist Ihnen überlassen) für IndicatorCounted(), der für viele Entwickler unpraktisch ist.

Das zweite Konzept ist die Ersetzung und Erweiterung der i<...>OnArray-artigen Funktionen von MQL4. Es gibt einen Indikator dieses Typen in den Terminal-Beispielen: benutzerdefinierter gleitender Mittelwert. Dieser Typ von Indikatoren ist für die von der Wahl des Anwenders abhängige Verarbeitung von Daten gedacht, einschließlich der benutzerdefinierten Indikatoren.

Die Funktion für die Datenverarbeitung bei Indikatoren dieses Typen sieht wie folgt aus:

int OnCalculate (const int rates_total,      // the size of the price[] array
                 const int prev_calculated,  // bars calculated in the previous call
                 const int begin,            // where notional data start from
                 const double& price[]       // data array for calculation
                 )
  {
   return rates_total;
  }

Der letzte Parameter dieser Funktion sind die vom Anwender ausgewählten zu verarbeitenden Daten. Falls Sie einen Indikator mit vielen Puffern anwenden möchten, wird der erste Indikatorpuffer für die Datenverarbeitung übergeben.

First Indicator's Data (Daten des ersten Indikators) bedeutet, dass der Indikator auf den Indikator angewendet wird, der als erster an das ausgewählte Diagrammfenster angehängt wurde.

Previous Indicator's Data (Daten des vorherigen Indikators) bedeutet, dass der Indikator auf den Indikator angewendet wird, der zuletzt an das ausgewählte Diagrammfenster angehängt wurde.

Diese Indikatoren können für die Zusammensetzung der gesamten Stacks genutzt werden. Beispielsweise ist es mithilfe des Indikators Benutzerdefinierter gleitender Mittelwert möglich, eine dreifache Glättung zu erhalten, indem der erste Indikator auf die erforderlichen Daten angewendet wird, der zweite auf den ersten und der dritte auf den zweiten:

Es gibt viele Standardindikatoren, die dieses Konzept umsetzen. Wenn Sie also die Aufforderung für den Funktionsparameter applied_price_or_handle sehen:

bedeutet dies, dass der Indikator auf eine Weise implementiert ist, die seine Berechnung anhand der Anwenderdaten ermöglicht. Der Identifikator dieser Daten muss als Parameter applied_price_or_handle übergeben werden.

Auf die gleiche Weise kann die Datenverarbeitung direkt im Indikatorcode organisiert werden:

  {
   //...
   RSIHandle = iRSI(NULL, 0, RSIPeriod, AppliedPrice);
   SmoothHandle = iMA(NULL, 0, SmoothPeriod, 0, MODE_EMA, RSIHandle);
   //...
  }

Es gibt eine weitere neue Anwendung für dieses Konzept – die Möglichkeit, Universal-Service-Indikatoren zu schreiben. Ein Beispiel für einen solchen Indikator ist in der Datei Direction_Brush.mq5 angehängt.

Die Ergebnisse sind im oberen Diagramm dargestellt. Die Richtungsfärbung ist in diesem Fall eine unabhängige Entität und ist im anderen Indikator implementiert.

Ihre universelle Einsatzfähigkeit ist sicherlich eingeschränkt, da sie nur für den Nullpuffer des Indikators anwendbar sind. Dennoch glaube ich, dass Indikatoren dieses Typen nützlich sein könnten.

Andererseits sollten Sie beim Schreiben eines benutzerdefinierten Indikators darauf achten, da die hauptsächliche Informationsverarbeitung im Nullpuffer es ermöglicht, die Implementierung einer multifunktionalen Maschine in einem Indikator zu vermeiden. Viele der anderen Aktionen können in den externen Service-Indikatoren erstellt und ausgeführt werden. Sie müssen lediglich Service-Indikatoren mit der gewünschten Funktionalität an Ihre Personalisierung anhängen.

Der Anwendungsbereich ist nicht so eingeschränkt, wie es auf den ersten Blick erscheinen mag:

Die oben aufgeführten Funktionen stellen keine vollständige Liste der Konzeptumsetzung dar. Ich glaube, dass später noch viele weitere effektive Umsetzungsmöglichkeiten gefunden werden.

 

Datenzugriff

Die Datenzugriffsprinzipien haben sich in MQL5 geändert. Die Bearbeitung findet nun direkt in den Arrays statt. Dadurch wird die Berechnungsgeschwindigkeit deutlich erhöht. Es ist nicht länger erforderlich, ein Array zu erstellen und für jeden Wert die iCustom-Funktion aufzurufen. Stattdessen können die benötigten Daten über eine Funktion abgerufen werden. Anschließend können die angeforderten Daten direkt in ein festgelegtes lokales Array kopiert verwendet werden.

Das Kopieren von Daten findet mithilfe der Systemfunktion CopyBuffer statt. Sie finden die Beschreibung der Funktion in der Hilfe.

Die maximale Anzahl an zu kopierenden Daten für den Indikator und statische Arrays (Arrays mit vordefinierter Größe) ist durch die Größe des Arrays festgelegt. Die Größe dynamischer Arrays kann angepasst werden, wenn die Anzahl zu kopierender Daten seine Größe überschreitet.

Zusätzlich gibt es spezielle Funktionen für den Zugriff auf historische Daten:

Funktion Beschreibung
CopyBuffer Ruft Daten eines festgelegten Puffers eines bestimmten Indikators in der erforderlichen Menge ab.
CopyRates Ruft historische Daten einer MqlRates-Struktur eines bestimmten Symbolzeitraums in der erforderlichen Menge im Array rates_array ab.
CopyTime Ruft historische time_array-Daten der Bar-Eröffnungszeit für das festgelegte Symbolzeitraum-Paar in der erforderlichen Menge ab.
CopyOpen Ruft historische open_array-Daten der Bar-Eröffnungspreise für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopyHigh Ruft historische high_array-Daten der höchsten Bar-Preise für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopyLow Ruft historische low_array-Daten der niedrigsten Bar-Preise für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopyClose Ruft historische close_array-Daten der Bar-Schließungspreise für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopyTickVolume Ruft historische volume_array-Daten der Tick-Volumina für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopyRealVolume Ruft historische volume_array-Daten des Handelsaufkommens für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.
CopySpread Ruft historische spread_array-Daten der Differenzwerte für das festgelegte Symbolzeitraum-Paar in der ausgewählten Menge ab.

Details finden Sie in der Hilfe.

Diese Daten werden in nur einer Form des Indikators übergeben, für die andere wird ein eigener Indikator benötigt.

Da der Typ des historischen Daten-Arrays nicht zwangsläufig double ist, wird empfohlen, ausschließlich dynamische Nichtindikator-Puffer für ihre Speicherung zu verwenden.

Es gibt noch ein weiteres nicht dokumentiertes Detail: Wenn die Zahl kopierter Daten gleich 0 (Null) ist, erzeugt die Funktion CopyBuffer einen Fehler mit Code-Nr. 4003. Deshalb muss die Zahl zu kopierender Daten mindestens 1 (eins) betragen.

 

Die Indikatorpuffer

Die Zahl der Puffer ist nicht begrenzt.

Nun müssen Sie sich keine Sorgen darüber machen, wie Sie die Informationen korrekt einordnen oder wie Sie die Zwischenberechnungen auf effiziente Weise durchführen können, wenn Sie an der Erstellung des Cluster-Indikators arbeiten.

Doch wir sollten nicht vergessen, dass die Speicherung des Puffers ein gewisses Speichervolumen erfordert. Deshalb dürfen Sie nicht überrascht sein, wenn das Terminal mehrere Gigabyte Speicher verbraucht, wenn Sie eine Historientiefe für das Terminal mit 1.000.000 Bars angeben und den "dicken" Cluster-Indikator an das Minutendiagramm anhängen.

Das Grundprinzip des Puffers wurde ebenfalls verändert. Die Anzahl der verwendeten Puffer ist in den Eigenschaften angegeben.

#property indicator_buffers 2       // buffers used

Dieser Wert sollte der Gesamtzahl der Puffer entsprechen.

Die Anzahl angezeigter Puffer wird in den Eigenschaften angezeigt:

#property indicator_plots 1         // buffers displeyed

Hier sind ein paar Details. Die meisten Diagrammstile benötigen nur einen INDICATOR_DATA-Puffer für das Diagramm. Allerdings gibt es einige Stile, die mehrere Indikatorpuffer für das Diagramm benötigen.

Damit meinen wir die folgenden Diagrammstile:

Für alle oben aufgeführten Typen mit Ausnahme des Stils DRAW_FILLING (dieser kann nicht farbig sein) gibt es farbige Gegenstücke.

Alle Indikatorpuffer werden jetzt in 3 Typen unterteilt:

int OnInit()
{
   // ...
   SetIndexBuffer(0, V2, INDICATOR_DATA);
   SetIndexBuffer(1, V2C,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, V4, INDICATOR_DATA);
   SetIndexBuffer(3, V4C,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4, V1, INDICATOR_CALCULATIONS);
   SetIndexBuffer(5, V3, INDICATOR_CALCULATIONS);

   // ...
   return 0;
}

Es gibt auch einige Funktionen, wenn Indikatoren über iCustom angesprochen werden.

Falls ein nicht zugänglicher Puffer angefragt wird, wird der Fehler 4806 ("The requested data was not found" (Die angefragten Daten konnten nicht gefunden werden)) generiert.

Sehen wir uns die Farbpuffer im Detail an.

In MQL4 musste für jede Farbe ein separater Puffer erstellt werden, doch durch die Verwendung von Farbstilen können Sie jetzt bis zu 63 Farben für einen Puffer festlegen. In einigen Fällen kann dadurch die Anzahl der verwendeten Puffer optimiert und somit der Speicherverbrauch reduziert werden. Es werden auch neue Möglichkeiten zum Schreiben von Indikatoren eröffnet, insbesondere die Verwendung der Farbton-Visualisierung.

Zusätzlich vereinfacht diese Innovation in einigen Fällen deutlich die Logik der Anwendung mehrerer Farben im Vergleich zu MQL4. Das deutlichste Beispiel ist die Trennung von Trends durch Farben. Im sparsamsten (korrekten) Umsetzungsfall in MQL4 wurden drei (3) Puffer und komplizierte Programmierung benötigt.

Dies ist nun einfacher denn je. Hier sind die Code-Beispiele, die vollständige Implementierung finden Sie in der Datei Color_RSI.mq5.

#property indicator_color1 clrDarkSalmon, clrDeepSkyBlue // use 2 colors
#property indicator_type1  DRAW_COLOR_LINE               // and the special color display type
//---- buffers
double Values[];                 // buffer of values
double ValuesPainting[];         // buffer of color indices
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- bind indicator buffers
//--- Values serves as a display buffer
   SetIndexBuffer(0,Values,INDICATOR_DATA);
//--- ValuesPainting serves as the buffer for storing colors
   SetIndexBuffer(1,ValuesPainting,INDICATOR_COLOR_INDEX);
//...
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(/*...*/)
  {
//--- number of bars for calculation
   int toCount=(int)MathMin(rates_total,rates_total-prev_calculated+1);
//--- try to copy iRSI indicator data
   if(CopyBuffer(RSIHandle,0,0,toCount,Values)==-1)
     {
      Print("Data copy error, №",GetLastError());
      //--- return command for recalculation of the indicator values
      return(0);
     }
//--- coloring. Yer, now it has become that easy
   for(int i=toCount-2;i>=0;--i)
     {
      //--- coloring the first line
      if(Values[i+1]!=EMPTY_VALUE && Values[i]>Values[i+1])
         ValuesPainting[i]=1;
      else
         ValuesPainting[i]=0;
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Noch etwas mehr Code und wir erhalten das folgende Ergebnis:

Sie sollten bei der Verwendung von Farbtypen für die Zeichnung einige Details beachten.

Bei der Verwendung eines dynamischen Farbdefinitionsschemas für die Farbpuffer ist die maximale Anzahl verwendeter Farben durch den Farbzähler in der Eigenschaft indicator_colorN begrenzt. Zum Beispiel

#property indicator_color1 clrDarkSalmon, clrDeepSkyBlue // use 2 colors

enthält das Farbschema des Puffers maximal zwei Farben, auch wenn Sie eine größere Anzahl von Farben dynamisch einstellen (mithilfe von PlotIndexSetInteger).

Deshalb sollte die erforderliche Anzahl von Farben in eine Zeile geschrieben werden, die Definitionszeile der Eigenschaft. Dies kann daraufhin dynamisch verändert werden. Die kürzeste Farbe, die ich finden konnte, ist "Rot". Allerdings können Sie jederzeit

anstatt

#property indicator_color1  clrRed, clrRed, clrRed, clrRed, clrRed, clrRed, clrRed, clrRed, //…

Folgendes schreiben:

#define C clrRed
#property indicator_color1  C, C, C, C, C, C, C, C, C, C, C, C, C, C, //…

Die maximale Anzahl von Farben für einen Puffer ist 63. Ist die Zahl der Farben höher als das Maximum (definiert durch die Eigenschaft indicator_colorN), wird der Puffer nicht angezeigt.

Hier sehen Sie ein Beispiel für die Farbton-Visualisierung mit der maximalen Anzahl an Farben:

Die Zeichenmöglichkeiten wurden allgemein deutlich erweitert, was großartig ist.

 

Arrays

Bei der direkten Referenzierung der Array-Daten durch Indizes muss die Reihenfolge der Daten, die Eigenschaft AsSeries, berücksichtigt werden. Für einige Array-Typen kann diese nicht definiert werden.

Dieses Flag kann nicht für multidimensionale und statische Arrays gesetzt werden. Für die Array-Typen, die an die OnCalculate-Funktion übergeben werden, kann ein solches Flag gesetzt werden.

Das Kopieren von Daten mithilfe der CopyBuffer-Funktion hängt nicht von der Eigenschaft AsSeries ab, doch die Implementierung unterscheidet sich je nach Puffer.

Bei Indikatorpuffern muss die gesamte Tiefe der verfügbaren Historie kopiert werden. Daran müssen Sie immer denken.

Für dynamische und statische (mit vordefinierter Größe) Arrays wird das Kopieren von Daten von der Gegenwart zur Vergangenheit durchgeführt.

Die Funktion CopyBuffer ändert die Größe von Puffern für dynamische Puffer (außer Indikatorpuffern) zur benötigten Größe.

Es wird nicht empfohlen, statische Arrays zum Kopieren von Daten zu verwenden.

Allgemein rate ich Ihnen, stets zu überprüfen, wie Sie Daten kopieren und behandeln. Die einfachste und sicherste Art ist:

Zusätzlich empfehle ich dringend, die Hilfe für die CopyBuffer-Funktion und für alle mit der Eigenschaft AsSeries in Verbindung stehenden Funktionen sorgfältig durchzulesen.

 

IndicatorCounted

All die Debatten über die Notwendigkeit der IndicatorCounted-Funktion können nun vergessen werden, da dieser Wert direkt durch uns als Ausgabewert des vorherigen Funktionsaufrufs definiert wird.

int OnCalculate(const int rates_total,      // array size
                const int prev_calculated,  // bars processed after last call
                //...)
  {
   return rates_total;
  }

Meistens reicht es aus, den rates_total-Wert auszugeben, der die Anzahl der Bars des aktuellen Funktionsaufrufs enthält.

Wurden allerdings die Preisdaten seit dem letzten Aufruf von OnCalculate() verändert (beispielsweise durch Laden der historischen Daten oder Ausfüllen leerer Felder in den historischen Daten), wird der Wert des Eingabeparameters prev_calculated

durch das Client Terminal auf 0 (Null) gesetzt.

Gibt die OnCalculate-Funktion Null aus, werden die Indikatorwerte außerdem nicht im DataWindow des Client Terminals angezeigt. Deshalb müssen Sie 1 statt 0 ausgeben, wenn Sie den Indikator sehen und seine vollständige Neuberechnung beim Ladevorgang der Historie oder nach einem Verbindungsfehler durchführen möchten.

Eine weitere nützliche neue Funktion ist die Bestimmung, wie viele Bars für den Indikator berechnet wurden. Diese Funktion ist für Expert Advisors nützlicher, die Berechnungen für große Datensätze durchführen. Die Funktion heißt BarsCalculated, ihre Details finden Sie in der Hilfe.

Diese Funktion hat eine weitere nützliche Anwendungsmöglichkeit. Falls der Indikator nicht geladen wurde, kann sein Ladevorgang einige Zeit in Anspruch nehmen – die Zeit zwischen der Erstellung des Handles des Indikators und seiner Verwendung für die Berechnungen.

Diese Zeit wird für die Initialisierung und die erste Vorberechnung benötigt. Sie hängt von der Berechnungsgeschwindigkeit und den Details des Indikators ab.

Während dieser Zeit generiert ein Aufruf der CopyBuffer-Funktion den Fehler 4806 "The requested data was not found" (Die angefragten Daten konnten nicht gefunden werden).

Die Funktion BarsCalculated kann verwendet werden, um die Verfügbarkeit der Indikatordaten für das Kopieren zu bestimmen:

//--- number of bars for calculation
   int toCount=rates_total-(int)MathMax(prev_calculated-1,0);
//--- try to copy iWPR indicator data
   int copied=CopyBuffer(WPRHandle,0,0,toCount,Values);
   int err=GetLastError();
//--- check coying result
   if(copied==-1)
     {
      //--- if error number is 4806, the data simply have not been uploaded yet
      if(err==4806)
        {
         //--- wait till the data is uploaded
         for(int i=0;i<1000;++i)
            if(BarsCalculated(WPRHandle)>0)
               break;
         //--- try to copy iWPR indicator data again
         copied=CopyBuffer(WPRHandle,0,0,rates_total,Values);
         err=GetLastError();
        }
     }
//--- check coying result
   if(copied==-1)
     {
      Print("Error when trying to get WPR values, last error is ",err," bars ",rates_total);
      return(0);
     }
//...

 

Zusammenfassung

Zusammenfassend möchte ich sagen, dass dieser Beitrag nur auf einige wenige Details eingegangen ist. Doch ich hoffe, dass hier die wichtigsten Grundaspekte präsentiert werden.

Es wäre schön, wenn dieser Beitrag eine nützliche Referenz zu dem Thema wäre, in der Sie Informationen über die Details immer suchen und auch finden können.

Falls Sie Fehler in diesem Beitrag entdeckt oder etwas Wichtiges gefunden haben, setzen Sie mich bitte in Kenntnis, damit ich den Beitrag so bald wie möglich korrigieren und verbessern kann.

Ich plane, aktuelle Änderungen zusammenzufassen, und hoffe außerdem, dass einige der nützlichen Informationen in den Kommentaren erscheinen werden.

Aus diesem Grund sollten sie sorgfältig gelesen werden.

 

Anhang

Alle oben aufgeführten Dateien sind Indikatoren.

 

Danksagungen

Ich möchte erneut Herrn Victor Rustamov (granit77) für das Korrekturlesen des Manuskripts, nützliche Diskussionen und hilfreiche Ratschläge danken.