Die Umsetzung von Indikatoren als Klassen mit den Beispielen Zigzag und ATR
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:- MetaQuotes bitten, das Problem auf Plattformebene zu beheben
- 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.).
- 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:
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
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)
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.
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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.