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

Aleksandr Chugunov | 14 März, 2016

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:

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.


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

Vorteile:
Nachteile:


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:

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:

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:

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


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.

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
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);
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.

struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

zu unserer eigenen Struktur:

struct ATRBar
  {
   double ATR;
   double TR;
  };
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;
     }
   ...

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

CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
   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;
     }
   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:

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

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.