English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Die Umsetzung von Indikatoren als Klassen mit den Beispielen Zigzag und ATR

Die Umsetzung von Indikatoren als Klassen mit den Beispielen Zigzag und ATR

MetaTrader 5Indikatoren | 14 März 2016, 16:11
878 0
Aleksandr Chugunov
Aleksandr Chugunov

Wofür brauchen wir das?

MetaQuotes Software Corp. hat das Konzept der Arbeit mit benutzerdefinierten Indikatoren in der 5. Version des MetaTrader Client Terminals überarbeitet. Sie werden nun viel schneller ausgeführt. Es gibt nur ein Exemplar jedes Indikators mit eindeutigen Eingabeparametern, sodass er sogar unabhängig von der Verwendung seiner Kopien in zehn Diagrammen eines Symbols usw. nur einmal berechnet wird.

Doch die Arbeit eines bestimmten Algorithmus bleibt unverändert. Bei einer Unterbrechung der Verbindung zu einem Server oder einer wesentlichen Synchronisierung der Historie wird der Wert prev_calculated (oder IndicatorCounted() in MetaTrader 4) auf Null gesetzt, was zu einer vollständigen Neuberechnung des Indikators über die gesamte Historie führt (dies war von den Entwicklern so beabsichtigt, um die Richtigkeit der Werte von Indikatoren unter allen Umständen zu gewährleisten). Es gibt mehrere Faktoren, die die Geschwindigkeit der Berechnung von Indikatoren beeinflussen können:

  • Großer Zeitraum: rates_total;
  • Komplexe, ressourcenintensive Berechnungen;
  • Verwendung mehrerer Symbole und Zeiträume;
  • Schwacher Computer.

Je mehr dieser Punkte auf Sie zutreffen, desto relevanter ist das Problem der Neuberechnung des Indikators für die gesamte Historie. Die Situation wird durch einen schlechten Kanal für die Datenübertragung noch weiter verschlimmert.

Natürlich können Sie die Tiefe der Indikatorberechnung mithilfe eines zusätzlichen Eingabeparameters eingrenzen, doch bei der Verwendung von iCustom-Indikatoren gibt es eine kleine Besonderheit. Die Höchstmenge von Balken, die von jedem beliebigen Diagramm oder benutzerdefinierten Indikator genutzt werden, ist für das gesamte Terminal global festgelegt. Der Speicher wird für jeden Puffer eines benutzerdefinierten Indikators zugeteilt und wird nur durch TERMINAL_MAXBARS beschränkt.

Doch es gibt noch einen wichtigen Zusatz: Wenn Sie die Höchstmenge der berechneten Balken direkt im Algorithmus des Indikators einschränken (beispielsweise mithilfe eines Eingabeparameters oder direkt im Code), wird der Speicher dynamisch beim Erscheinen jedes neuen Balkens zugeteilt (stufenweiser Anstieg bis zum festgelegten Grenzwert TERMINAL_MAXBARS (oder etwas mehr; der Algorithmus hängt vollständig von den Entwicklern ab, sie können ihn in den nächsten Builds abändern)).


Wie lässt sich die Neuberechnung des Indikators für die gesamte Historie verhindern?

Aktuell sehe ich die folgenden Möglichkeiten, dieses Problem zu lösen:
  1. MetaQuotes bitten, das Problem auf Plattformebene zu beheben
  2. Erstellen einer separaten Klasse für die Implementierung eines Gegenstücks zu prev_calculated

Eine weitere Möglichkeit ist die Annahme, dass Sie direkt im Indikator einen Algorithmus für die Berechnung von prev_calculated einbauen, doch es hat sich herausgestellt, dass MetaTrader 5 im Gegensatz zu MetaTrader 4 alle Indikatorpuffer beim Nullsetzen von prev_calculated "bereinigt" (d. h. die Nullsetzung aller Indikator-Arrays wird erzwungen; Sie haben keine Kontrolle darüber, da dieses Verhalten auf Plattformebene umgesetzt wird).

Analysieren wir alle Varianten im Einzelnen.

  • Die erste Variante hängt vollständig von den Entwicklern ab. Vielleicht kümmern Sie sich nach der Veröffentlichung dieses Beitrags darum. Und vielleicht wirkt sich die Implementierung eines ausgereiften Mechanismus stark auf die Performance des Berechnungsblocks von benutzerdefinierten Indikatoren aus (obwohl dieser Mechanismus optional implementiert werden kann) und sie lassen alles unverändert.
  • Die zweite Variante: Die Erstellung einer speziellen Klasse, die für die Implementierung eines Gegenstücks zu prev_calculated verantwortlich ist. Wir können sie sowohl in einem benutzerdefinierten Indikator (nur zum Erhalten der prev_calculated-Werte) als auch als Datenlieferant für Expert Advisors (oder Scripts) zusammen mit einer gesondert entwickelten Klasse für die Berechnung des benötigten benutzerdefinierten Indikators nutzen.


Vor- und Nachteile der zweiten Variante der Lösung des Problems

Vorteile:
  • festes erforderliches Speichervolumen durch die einmalige Zuweisung von Speicher für ein dynamisches Array mit Errichtung eines ringförmigen Zugriffs auf die Array-Elemente;
  • Synchronisierung und Berechnung des Indikators bei der Verwendung einer gesonderten Klasse für seine Berechnung auf Anfrage (ohne die Verwendung von Semaphoren, Flags, Ereignissen usw.);
  • bei der Verwendung eines separaten Aufrufs der Berechnung des Indikators wird das Ergebnis der Neuberechnung in erweiterter Form ausgegeben (zum Beispiel: es gab keine Änderungen, nur der letzte Strahl hat sich verändert, neuer Strahl erschienen usw.).
Nachteile:
  • Notwendigkeit der Speicherung einer eigenen Kopie der Preishistorie, die für die Berechnung von Indikatorwerten genutzt wird;
  • Notwendigkeit einer manuellen Synchronisierung der Historie mit der Terminal-Historie mithilfe logischer Operationen zum Datenabgleich.


Erstellen der Klasse CCustPrevCalculated für die Implementierung eines Gegenstücks zu prev_calculated

Die Implementierung der Klasse selbst ist nicht sonderlich interessant. Der Algorithmus berücksichtigt sowohl die Erweiterung der Historie in beiden Richtungen als auch mögliches "Abschneiden" von der linken Seite. Außerdem kann der Algorithmus das Einfügen der Historie innerhalb der berechneten Daten verarbeiten (das gilt für MetaTrader 4, in MetaTrader 5 bin ich noch nicht darauf gestoßen). Den Quellcode der Klasse finden Sie in der Datei CustPrevCalculated.mqh.

Lassen Sie mich das Wichtigste erörtern.


Errichtung eines ringförmigen Zugriffs auf Array-Elemente

Für die Erstellung dieser Klasse nutzen wir eine unkonventionelle Methode: den ringförmigen Zugriff auf die Array-Elemente für die einmalige Zuweisung von Speicher für das Array und zum Vermeiden überflüssiger Kopiervorgänge von Arrays. Betrachten wir dies anhand von 5 Elementen:


Ringförmiger Zugriff auf die Array-Elemente


 
Zunächst arbeiten wir mit einem Array, dessen Nummerierung bei 0 beginnt. Doch was sollen wir tun, wenn wir den nächsten Wert einfügen und dabei die Größe des Arrays beibehalten wollen (neuen Balken hinzufügen)? Es gibt zwei Möglichkeiten:
  • Kopieren der Speicherzellen 2-5 in die entsprechenden Zellen 1-4. Dadurch erhalten wir eine leere Speicherzelle 5;
  • Ändern der Indexierung des Arrays, ohne die darin gespeicherten Informationen zu verändern (kreisförmige Adressierung).

Zum Umsetzung der zweiten Variante brauchen wir eine Variable. Nennen wir sie DataStartInd. Sie speichert die Position des Null-Index des Arrays. Für die Vereinfachung der weiteren Berechnungen entspricht ihre Nummerierung der herkömmlichen Indexierung eines Arrays (d. h. sie beginnt bei Null). In der Variable BarsLimit speichern wir die Anzahl der Elemente des Arrays. Somit wird die reale Adresse des Array-Elements für den virtuellen Index 'I' mithilfe der folgenden einfachen Formel berechnet:

  • (DataStartInd+I) % BarsLimit – für die herkömmliche Nummerierung
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – für die Adressierung wie in einer Zeitreihe
Die Variable DataBarsCount speichert die Menge der tatsächlich genutzten Speicherzellen (beispielsweise können wir nur 3 von 5 verwenden).


Algorithmen für die Synchronisierung der Historie

Für meine eigenen Zwecke habe ich drei Arten der Arbeit des Algorithmus für die Synchronisierung einer Kopie der Historie (lokale Historie) mit der Historie im Client Terminal ausgewählt und umgesetzt:
  • CPCHSM_NotSynch – keine Synchronisierung der lokalen Historie für bereits geformte Balken (auf eigenes Risiko und eigene Verantwortung). Tatsächlich kann diese Möglichkeit ruhigen Gewissens für Indikatoren verwendet werden, bei denen unwesentliche Abweichungen von Preiswerten sich nicht stark auf die Genauigkeit der Berechnungen auswirken können (MA, ADX usw.). Diese Möglichkeit kann fatal sein, beispielsweise für ZigZag, bei dem die Überschreitung eines Höhepunkts durch den anderen ausschlaggebend ist.
  • CPCHSM_Normal – Synchronisierung der lokalen Historie bei jedem neuen Balken durch den unten beschriebenen Algorithmus.
  • CPCHSM_Paranoid – Synchronisierung der lokalen Historie bei jedem Aufruf der unten beschriebenen Funktion der Datensynchronisierung.

Der Mechanismus der Synchronisierung selbst basiert auf einem weiteren durch den Programmierer festgelegten Parameter: HSMinute (gespeichert als HistorySynchSecond). Wir nehmen an, dass ein Dealer Center nur die letzten HSMinute Minuten der Historie korrigieren kann. Falls während der Synchronisierung dieses Zeitraums keine Unterschiede festgestellt werden, wird die Historie als identisch betrachtet und der Abgleich gestoppt. Wird ein Unterschied festgestellt, wird die gesamte Historie überprüft und korrigiert.

Davon abgesehen, ermöglicht der Algorithmus nur die Überprüfung von bei der Initialisierung festgelegten Preisen/Spreads/Volumina aus der Struktur MqlRates. Beispielsweise benötigen wir für die Zeichnung von ZigZag nur die High- und Low-Preise.


Praktische Anwendung der Klasse CCustPrevCalculated

Zum Initialisieren der Klasse CCustPrevCalculated müssen wir die Funktion InitData() aufrufen, die bei erfolgreichem Aufruf 'true' ausgibt:
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
Für die Synchronisierung der Historie müssen wir die Funktion PrepareData() aufrufen:
CPCPrepareDataResultCode resData;
resData = CustPrevCalculated.PrepareData();

Varianten von Werten können durch die Funktion PrepareData() ausgegeben werden:

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Returned when there is no data for calculation (not prepared by the server)
   CPCPDRC_FullInitialization,         // Full initialization of the array has been performed
   CPCPDRC_Synch,                      // Synchronization with adding new bars has been performed
   CPCPDRC_SynchOnlyLastBar,           // Synchronization of only the last bar has been performed (possible cutting of the history)
   CPCPDRC_NoRecountNotRequired        // Recalculation has not been performed, since the data was not changed
  };


Funktionen der Klasse CCustPrevCalculated für den Datenzugriff

Hinweis: Um die Berechnungen zu beschleunigen, werden die Überprüfungen auf Overflowing der Arrays ausgeschlossen. Genauer gesagt, werden falsche Werte ausgegeben, falls der Index nicht korrekt ist.

Bezeichnung Zweck
 uint GetDataBarsCount()  Gibt die Anzahl verfügbarer Balken aus
 uint GetDataBarsCalculated()  Gibt die Anzahl unveränderter Balken aus
 uint GetDataStartInd()  Gibt den Index für den kreisförmigen Zugriff aus (für benutzerdefinierte Indikatoren)
 bool GetDataBarsCuttingLeft()  Gibt das Ergebnis des Abschneidens von Balken von links aus
 double GetDataOpen(int shift, bool AsSeries)  Gibt 'Open' für den shift-Balken aus
 double GetDataHigh(int shift, bool AsSeries)  Gibt 'High' für den shift-Balken aus
 double GetDataLow(int shift, bool AsSeries)  Gibt 'Low' für den shift-Balken aus
 double GetDataClose(int shift, bool AsSeries)  Gibt 'Close' für den shift-Balken aus
 datetime GetDataTime(int shift, bool AsSeries)  Gibt 'Time' für den shift-Balken aus
 long GetDataTick_volume(int shift, bool AsSeries)  Gibt 'Tick_volume' für den shift-Balken aus
 long GetDataReal_volume(int shift, bool AsSeries)  Gibt 'Real_volume' für den shift-Balken aus
 int GetDataSpread(int shift, bool AsSeries)  Gibt 'Spread' für den shift-Balken aus

Beispiele für die weitere Optimierung der Klasse CCustPrevCalculated

  • Verzicht auf MqlRates mit Übergang zu mehreren (durch einen bestimmten Zweck festgelegten) Arrays (verringert die Speicheranforderung, aber erhöht das Laden der Anzahl der Aufrufe des Kopierens von Arrays).
  • Aufteilung aller Zugriffsfunktionen in zwei unabhängige Funktionen für den eindeutigen Gebrauch mit einem bestimmten Typ der Array-Indexierung (Verzicht auf den Parameter "bool AsSeries"). Der Vorteil liegt nur in der Logikbedingung "if (AsSeries)".


Erstellen der Klasse CCustZigZagPPC für die Berechnung des benutzerdefinierten Indikators ZigZag auf Basis der Daten der Klasse CCustPrevCalculated

Dieser Algorithmus basiert auf dem benutzerdefinierten Indikator Professional ZigZag. Den Quellcode der Klasse finden Sie in der Datei ZigZags.mqh. Zusätzlich wird die Bibliothek OutsideBar.mqh für die Arbeit mit externen Balken verwendet.

Erstellen wir eine separate Struktur für die Beschreibung eines Balkens unseres Indikators:

struct ZZBar
  {
   double UP, DN;                      // Buffers of the ZigZag indicator
   OrderFormationBarHighLow OB;       // Buffer for caching of an external bar
  };

Bestimmen wir außerdem das Ergebnis der Ausgabe der Berechnungen dieser Klasse:

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Class is no initialized
   CPCZZRC_NoData,                     // Faield to receive data (including the external bar)
   CPCZZRC_NotChanged,                 // No changes of ZZ rays
   CPCZZRC_Changed                     // ZZ rays changed
  };

Zum Initialisieren der Klasse CCustZigZagPPC müssen wir die Funktion Init() einmal aufrufen. Bei erfolgreichem Aufruf gibt sie 'true' aus:

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

Für die Berechnungen des Indikators müssen wir die Aktualisierung der Daten auf Basis der vorher berechneten Daten der Klasse CCustPrevCalculated auslösen:

CPCPrepareDataResultCode resZZ1;
resZZ1 = ZZ1.PrepareData(resData);

Rufen Sie anschließend das Verfahren Calculate() auf:

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();

Das vollständige Beispiel für die Verwendung einer CCustPrevCalculated-Klasse zusammen mit mehreren CCustZigZagPPC-Klassen finden Sie in der Datei ScriptSample_CustZigZagPPC.mq5.

Funktionen der Klasse CCustZigZagPPC für den Datenzugriff

Bezeichnung Zweck
 uint GetBarsCount()  Gibt die Anzahl verfügbarer Balken aus
 uint GetBarsCalculated()  Gibt die Anzahl berechneter Balken aus
 double GetUP(uint shift, bool AsSeries)  Gibt den Wert des ZigZag-Höhepunkts für einen Balken aus
 double GetDN(uint shift, bool AsSeries)  Gibt den Wert des ZigZag-Tiefpunkts für einen Balken aus
 OrderFormationBarHighLow GetOB(uint shift, bool AsSeries)  Gibt den 'Outside'-Wert für einen Balken aus

Sicht- und Programmprüfung

Für die Sichtprüfung hängen wir den ursprünglichen Indikator an ein Diagramm an und hängen darüber den speziell geschriebenen Testindikator Indicator_CustZigZag.mq5 mit identischen Parametern an (Sie sollten allerdings andere Farben wählen, um beide Indikatoren sehen zu können). Hier sehen Sie das Ergebnis seiner Arbeit:

Rot: Original; blau: unser eigener Indikator, berechnet auf den letzten 100 Balken.

Auf die gleiche Weise können wir sie in einem Expert Advisor vergleichen. Wird es Unterschiede geben? Die Ergebnisse aus iCustom("AlexSTAL_ZigZagProf") und der Klasse CCustZigZagPPC werden bei jedem Tick im Test-Expert-Advisor Expert_CustZigZagPPC_test.mq5 verglichen. Die Information über die Berechnung wird im Logbuch angezeigt (es gibt aufgrund der unzureichenden Historie für den Algorithmus möglicherweise keine Berechnungen bei den ersten Balken):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Divergence on the bar: 7

Betrachten wir diesen Expert Advisor im Detail. Bestimmen Sie die globalen Variablen für die Arbeit:

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

Initialisieren Sie die Variablen:

int OnInit()
  {
   // Creating new class and initializing it
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Initializing the class ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Receiving handle for the custom indicator
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
Verarbeitung von Ticks im Expert Advisor:
void OnTick()
  {
   // Calculation of data
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Start recalculation for each indicator! PrepareData obligatory!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

Nun haben wir ZZ1.GetBarsCalculated() durch CCustZigZagPPC berechnete Balken. Fügen wir den Code für den Vergleich der Daten von iCustom("AlexSTAL_ZigZagProf") und der Klasse CCustZigZagPPC ein:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Perform comparison
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Divergence on the bar: ", i);
      if (tmpDN != zzDN[i])
         Print("Divergence on the bar: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Tick processed: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

Das ist im Wesentlichen eine einfache praktische Anwendung der Klasse CCustZigZagPPC in einem Expert Advisor oder Script. Anstatt CopyBuffer() lauten die Funktionen für den direkten Zugriff GetUP(), GetDN(), GetOB().

Verschieben unseres Indikators in eine separate Klasse (anhand des Beispiels iATR)

Basierend auf der Datei ZigZags.mqh habe ich die Vorlage MyIndicator.mqh für die schnelle Entwicklung benutzerdefinierter Indikatoren in Übereinstimmung mit den oben beschriebenen Prinzipien erstellt.

Allgemeiner Plan:

1. Vorbereitungsphase.

  • Kopieren Sie MyIndicator.mqh als Datei mit einem anderen Namen (ATRsample.mqh in meinem Beispiel) und öffnen Sie letztere in MetaEditor 5.
  • Ersetzen Sie den Text "MyInd" durch den Namen Ihres Indikators ("ATR" in meinem Beispiel).

2. Bestimmen Sie die externen Parameter, die vom ursprünglichen (originalen) Indikator in die Klasse übertragen werden, deklarieren und initialisieren Sie sie.

In meinem Beispiel hat der ATR-Indikator einen externen Parameter:
input int InpAtrPeriod=14;  // ATR period
  • fügen Sie diesen Parameter unserer Klasse und der Funktion der Initialisierung der Klasse hinzu:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • ändern Sie die Kopfzeile des Körpers der Init-Funktion und initialisieren Sie den Variablenparameter mit dem Eingabewert:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. Bestimmen Sie die erforderliche Menge an Puffern im ursprünglichen Indikator und deklarieren Sie sie in unserer Klasse. Deklarieren Sie außerdem die Funktionen für die Ausgabe der INDICATOR_DATA-Puffer.

  • Ändern Sie die Struktur
struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

zu unserer eigenen Struktur:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • Bestimmen Sie die Nullwerte:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • Ändern Sie die Datei zum Ausgeben der Werte der INDICATOR_DATA-Puffer und fügen Sie sie hinzu:

ersetzen Sie (sofern es nur einen Puffer gibt, können Sie die Ersetzung überspringen)

class CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // returns the Val value of the buffer for a bar
   ...

durch

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

und ändern Sie den Code der entsprechenden Funktion:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}
Hinweis: Anstatt mehrerer Funktionen zum Ausgeben der Pufferwerte können Sie nur eine verwenden, die einen zusätzlichen Parameter, die Nummer oder die Bezeichnung des Puffers, hat.

4. Kopieren Sie die Logik der OnCalculate()-Funktion des ursprünglichen Indikators in die entsprechende Funktion der Klasse

  • Erste Überprüfungen
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • Berechnungen: beim ersten Tick und die Menge der Balken für die Berechnungen bei den nächsten Ticks:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • Die Berechnung selbst bei jedem Tick:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

Das ist alles. Unsere Klasse ist fertig. Für die Sichtprüfung können Sie einen Testindikator erstellen (in meinem Beispiel ist es Indicator_ATRsample.mq5):

Beim Korrekturlesen dieses Beitrags kam mir der Einfall, dass bei der Nutzung der Klasse CCustPrevCalculated zusammen mit nur einem benutzerdefinierten Indikator die Erstellung, Initialisierung und Synchronisierung dieser Klasse in den benutzerdefinierten Indikator integriert werden kann (in meinen Beispielen sind das CCustZigZagPPC und CCustATR). Beim Aufruf der Funktion für die Initialisierung der benutzerdefinierten Indikatoren müssen Sie zu diesem Zweck den Null-Pointer zum Objekt nutzen:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

Dabei wird die allgemeine Struktur

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

vereinfacht zu:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
Ein praktisches Beispiel finden Sie in der Datei Indicator_ATRsample2.mq5.

Auswirkungen der beschriebenen Technologie auf die Performance im Strategietester

Für die Prüfung habe ich einen Test-Expert-Advisor erstellt (TestSpeed_IndPrevCalculated.mq5), der den Wert des Indikators des Nullbalkens bei jedem Tick gemäß einer von drei Varianten erhält:

enum eTestVariant
  {
   BuiltIn,    // Built-in indicator iATR
   Custom,     // Custom indicator iCustom("ATR")
   IndClass    // Calculation in the class
  };

Dieser Expert Advisor wurde 10 Mal auf 1 Agenten mit den folgenden Optimierungsparametern ausgeführt:

  • Symbol: EURUSD
  • Zeitraum: gesamte Historie [1993..2001]
  • Handelsmodus: jeder Tick
  • Externer Parameter: FalseParameter [0..9]

Ich habe die Optimierungsdauer bei der Verwendung jeder der drei Varianten des Indikators gemessen. Das Ergebnis der Überprüfung wird als lineares Histogramm dargestellt.

Optimierungsdauer für drei Arten der Implementierung des ATR-Indikators

    Der Quellcode des für die Messung der Optimierungsdauer verwendeten Expert Advisors:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- connect the include file with the CustATR class
    #include <ATRsample.mqh>
    //--- set the selection of the parameter as an enumeration
    enum eTestVariant
      {
       BuiltIn,    // Built-in indicator iATR
       Custom,     // Custom indicator iCustom("ATR")
       IndClass    // Calculation withing the class
      };
    //--- input variables
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- period of the ATR indicator
    const uchar        InpAtrPeriod = 14;
    //--- handle of the built-in or custom indicator
    int                Handle;
    //--- indicator based on the class 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+

    Wie wir sehen können, beeinträchtigt diese Technologie die Performance im Strategietester im Vergleich zur Verwendung eines herkömmlichen benutzerdefinierten Indikators nicht wesentlich.

    Hinweise zur praktischen Nutzung dieser Technologie

    • beim Testen eines Expert Advisors im Strategietester kann der prev_calculated-Wert nicht in einem benutzerdefinierten Indikator auf Null gesetzt werden; deshalb ist die Synchronisierung der Historie in diesem Modus deaktiviert;
    • die Berechnung des Indikators wird nur bei den letzten 'n' Balken ausgeführt, die strikt auf die ursprüngliche Initialisierung der Klassen festgelegt sind;
    • die Berechnung impliziert eine strikte Bindung an ein bestimmtes Symbol und Zeitraum der initialisierten Klasse. Für die Durchführung von Berechnungen auf anderen Symbolen oder Zeiträumen müssen Sie neue Instanzen der Klassen erstellen.

    Fazit

    Ein Programmierer sollte in jeder Situation alle Vor- und Nachteile verschiedener Varianten der Umsetzung dieser Aufgabe bedenken. Die in diesem Beitrag vorgeschlagene Implementierung ist nur eine Art mit ihren eigenen Vor- und Nachteilen.

    P.S. Wer keine Fehler macht, der arbeitet auch nicht! Bitte setzen Sie mich darüber in Kenntnis, falls Sie Fehler finden.

    Übersetzt aus dem Russischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/ru/articles/247

    Random Walk und der Trendindikator Random Walk und der Trendindikator
    Der Random Walk sieht realen Marktdaten sehr ähnlich, hat aber einige wichtige Besonderheiten. In diesem Beitrag betrachten wir die Besonderheiten des Random Walk, der mithilfe eines Münzwurfs simuliert wird. Für die Analyse der Eigenschaften der Daten wird der Trendindikator entwickelt.
    Diagramme in HTML Diagramme in HTML
    Es ist heutzutage schwierig, einen Computer zu finden, auf dem kein Web-Browser installiert ist. Browser werden seit langer Zeit weiterentwickelt und ständig verbessert. In diesem Beitrag wird eine einfache und sichere Art der Erstellung von Diagrammen auf Basis von Informationen aus dem MetaTrader 5 Client Terminal besprochen, die im Browser angezeigt werden.
    Offenlegen von C#-Code in MQL5 mithilfe nicht gemanagter Exporte Offenlegen von C#-Code in MQL5 mithilfe nicht gemanagter Exporte
    In diesem Beitrag stelle ich verschiedene Interaktionsmethoden zwischen MQL5-Code und gemanagtem C#-Code vor. Ich habe auch mehrere Beispiele dafür bereitgestellt, wie MQL5-Strukturen gegen C# angeordnet werden können und wie sich exportierte DLL-Funktionen in MQL5-Scripts aufrufen lassen. Ich bin überzeugt, dass die hier bereitgestellten Beispiele als Basis für zukünftige Forschungen zum Schreiben von DLLs in gemanagtem Code dienen können. Dieser Beitrag bereitet auch Wege für die Nutzung bereits in C# implementierter Bibliotheken in MetaTrader.
    Der Player des Handels auf Basis der Abschlusshistorie Der Player des Handels auf Basis der Abschlusshistorie
    Der Player des Handels. Nur vier Wörter, keine Erklärung erforderlich. Man denkt an eine kleine Kiste mit Knöpfen. Drückt man einen Knopf, erfolgt die Wiedergabe. Bewegt man den Hebel, ändert sich die Wiedergabegeschwindigkeit. Die Realität sieht sehr ähnlich aus. In diesem Beitrag möchte ich mein Programm vorstellen, das die Handelshistorie fast wie in Echtzeit abspielt. Der Beitrag behandelt einige Nuancen der OOP bei der Arbeit mit Indikatoren und der Verwaltung von Diagrammen.