English Русский 中文 Español 日本語 Português
Automatische Suche nach Divergenzen und Konvergenzen

Automatische Suche nach Divergenzen und Konvergenzen

MetaTrader 5Beispiele | 30 Oktober 2017, 11:27
1 857 0
Dmitry Fedoseev
Dmitry Fedoseev

Inhalt

Einführung

Der Begriff "Divergenz" kommt vom lateinischen Wort "divergere" ("Abweichung erkennen"). Die Divergenz wird in der Regel als Abweichung zwischen den Indikatorenwerten und der Kursentwicklung definiert. Der antonyme Begriff ist "Konvergenz", der sich vom lateinischen Wort "Convergo" ("Ich bringe zusammen") ableitet. Es gibt weiter gefasste Klassifizierungssysteme für Divergenz und Konvergenz, einschließlich Definitionen wie "versteckte Divergenz","erweiterte Divergenz", Divergenzen der Klassen A, B und C usw.

In diesem Artikel befassen wir uns zunächst mit den Grundbegriffen Divergenz und Konvergenz. Dann werden wir uns mit anderen Methoden ihrer Klassifizierung beschäftigen, vergleichende Analyse durchführen sowie die Vor- und Nachteile identifizieren. Schließlich werden wir unsere eigene Klassifikation entwickeln, die vollständiger ist und keine offensichtlichen Nachteile hat, sowie einen universellen Indikator für die Suche und Anzeige von Konvergenzen/Divergenzen auf dem Chart. 

Divergenz und Konvergenz (Begriffsbestimmung)

Divergenz ist also eine Diskrepanz zwischen den Indikatorenwerten und der Kursentwicklung. Wir haben auch den zweiten Begriff - die Konvergenz - mit der entgegengesetzten Bedeutung. Wenn Divergenz eine Diskrepanz zwischen den Indikatorenwerten und der Kursentwicklung ist, dann bedeutet Konvergenz eine Angleichung. Dies ist jedoch nicht der Fall, da die Angleichung nicht mit Konvergenz gleichgesetzt werden kann. 

Damit beide Begriffe - Divergenz und Konvergenz - eine genauere Bedeutung haben, brauchen sie engere Definitionen. Werfen wir einen Blick auf die Kurs- und Indikator-Charts. Wenn der Preis steigt, während der Indikator sinkt, haben wir eine Divergenz. Wenn sich der Kurs nach unten bewegt, während der Indikator steigt, haben wir eine Konvergenz (Abb. 1).


Abb. 1. Diskrepanz der Bewegung von Preis und Indikator. Links steigt der Preis 
während der Indikator fällt — Divergenz. Rechts fällt der Preis, während der Indikator steigt — Konvergenz.

Wir sind es gewohnt, dass der Indikator in der Regel unterhalb des Kurscharts platziert wird, so dass diese Definition auf den ersten Blick akzeptabel erscheint. Kehrt man jedoch die Positionen von Indikator und Kurschart um, ändert sich alles radikal: Aus Divergenz wird eine Konvergenz und umgekehrt (Abb. 2).


Abb. 2. Diskrepanz der Bewegung von Preis und Indikator. Links steigt der Preis 
während der Indikator fällt — Konvergenz. Rechts fällt der Preis, während der Indikator steigt — Divergenz

Schauen wir uns nun die Abbildungen 1 und 2 aus der Sicht der Wahl der Handelsrichtung an. Angenommen, der Preis bewegt sich nach oben, während der Indikator nach unten bewegt, so haben wir beschlossen, zu verkaufen. Entsprechend sollten wir verkaufen, wenn sich der Kurs nach unten bewegt, während der Indikator nach oben bewegt (Abb. 3).


Abb. 3. Links: Bedingung für Verkauf, Preis und Indikator laufen auseinander. Rechts: Bedingung
für Kauf (ähnlich dem Verkauf), Preis und Indikator laufen zusammen
 

Es stellt sich heraus, dass im Falle eines Verkaufs der Preis und der Indikator auseinander laufen, während sie beim Kauf zusammen kommen, obwohl die Kauf- und Verkaufskonditionen identisch sind, wenn auch entgegengesetzt. Das bedeutet, dass wir eine der Bedingungen als 'bearish' bezeichnen können, während die zweite 'bullish' ist. Dies bedeutet, dass die Position des Indikators unterhalb der Preise auf dem Chart nicht ausreicht, um Konvergenz und Divergenz zu definieren. 

Alle angegebenen Definitionen haben mit der Verkaufsrichtung zu tun, während die Argumentation für den Kauf entgegengesetzt ist. Aber es gibt eine einfachere und präzisere Version der Definition, die auf dem Wesen der technischen Analyse beruht. Wenn wir die Annahme der weiteren Preisbewegung in die Definition von Divergenz und Konvergenz aufnehmen, wird es einfach und genau.

Die Divergenz ist ein Umkehrsignal für die Preise in Form einer unterschiedlichen Entwicklung der Indikatorwerte und der Preise.

Da dies ein Umkehrsignal ist, sollte der Preis für einen Kauf fallen und für einen Verkauf steigen. Damit diese Diskrepanz deutlich wird, sollte sich der Indikator nach oben bzw. unten bewegen. In dieser Definition wird der Verkauf als Referenzrichtung verwendet, während der Indikator unter dem Kurs-Chart platziert werden sollte. Dieser Definition folgt die in Abb. 3 gezeigte Abweichung. 

Die Konvergenz verhält sich entgegengesetzt: der Preis sollte sich nach unten bewegen, während gleichzeitig der Indikator steigt. Die geschätzte Preisrichtung ändert sich nicht. Daher die folgende Definition der Konvergenz:

Die Konvergenz ist ein Signal für die Fortsetzung des Trends in Form einer unterschiedlichen Entwicklung der Indikatorwerte und der Preise.

Da dies ein Fortsetzungssignal ist, sollte sich der Preis für den Verkauf nach unten und für einen Kauf nach oben bewegen. Der Indikator sollte sich nach oben bzw. unten bewegen (Abb. 4).


Abb. 4. Konvergenzsignale

Natürlich kann man darüber streiten, ob die Divergenz ein Umkehrsignal ist, und Konvergenz ein Fortsetzungssignal. Aber das ist schon jetzt eine Frage der praktischen Anwendung der technischen Analyse.

Die Abb. 5 zeigt Divergenz- und Konvergenzsignale gleichzeitig, um die Terminologie zu verdeutlichen.


Abb. 5. Divergenz- und Konvergenzsignale

 

Methoden zur Bestimmung der Richtung der Preise und Indikatoren

Die Linen des Indikators und der Preise sind bisher gerade. Aber das ist eine Vereinfachung, die nichts mit realen Preisbewegung zu tun hat. Betrachten wir also die Methoden, mit denen sich die Richtung der Preise und des Indikators ermitteln und eine Divergenz erkennen lässt. Dann werden wir uns mit den Divergenzen in der Praxis beschäftigen.

Im Allgemeinen müssen wir zuerst die Preise oder die Hochs und Tiefs des Charts identifizieren, um dann die Werte zu vergleichen. Eine 'bullsche' Divergenz (Kaufsignal) wird durch die Tiefs erkannt: Ist ein Tief höher als das vorherige, zeigt der Indikator nach oben und umgekehrt. Die 'bearsche' Divergenz (Verkaufssignale) wird von den Hochs her erkannt.    

Es gibt drei Möglichkeiten die Extrema auf dem Chart zu erkennen.

  1. Durch die Bars.
  2. Durch Überschreiten eines Schwellwertes aus dem letzten Hoch/Tief.
  3. Die Maxima/Minima liegt oberhalb/unterhalb der Mittellinie des Indikators.  

Erkennen der Hochs/Tief durch die Bars. Wir verwenden die Anzahl der Höchst-/Tiefstbars. Wenn der Parameter z. B. den Wert 2 hat, sollte der Indikatorwert der höchsten Bar die Werte von zwei Bars nach links und zwei nach rechts übersteigen. Entsprechend sollte der Wert der Tiefs niedriger sein als die der benachbarten Bars (Abb. 6).


Abb. 6. Definition der Hochs und Tiefs für zwei Bars. Links ist die Definition des Hochs. Die Bar mit dem Pfeil, wird erkannt
durch die Bar mit dem OK-Häkchen. Rechts ist die Definition eines Tiefs.

Die benötigte Anzahl der Bars links und rechts des Hochs oder Tiefs kann variieren: z. B. 5 nach links und 2 nach rechts (Abb. 7).


Abb. 7. Das Hoch wird durch fünf Bars links und zwei Bars rechts definiert. 

Definieren der Hochs und Tiefs durch Schwellenwerte. Wenn der Indikator steigt, überprüft das System den Maximalwert. Bei Bars ohne neues Extrema wird der aktuelle Wert mit dem vorher ermittelten Hoch/Tief verglichen. Überschreitet die Differenz den durch einen externen Parameter eingestellten Schwellwert, wird angenommen, dass der Indikator die Richtung geändert hat, während die Bars, bei dem der Maximal-/Minimalwert erreicht wurde, als die Bar mit einem Hoch/Tief betrachtet wird (Abb. 8).


Abb. 8. Definition der Hochs und Tiefs mit einem Schwellenwert Der Schwellenwert wird in der linken oberen Ecke angezeigt.
Bis zur Bar 2 steigt der Indikator, das Maximum liegt auf der Bars 2, während auf der Bar 5 der Wert
um den Schwellwert verringert wird, d. h. der Indikator ändert seine Richtung
. Ab der Bar 6 hat der Indikator
den Schwellwert wieder überschritten und seine Richtung geändert etc.

Die Definition über die Bars ist am einfachsten, da sie überhaupt nicht vom Indikator abhängt. Der Wert des Schwellwertes ist dagegen abhängig vom Indikatortyp. Beim RSI mit dem Oszillationsbereich von 0-100 kann der Schwellwert z. B. etwa 5 betragen. Für ein Momentum wäre ein Schwellwert zwischen 0,1-1, da der Indikator leicht um den Wert von 100 schwankt. Außerdem hängt das Ausmaß dieser Schwankungen vom Zeitrahmen ab. Dies erschwert die Verwendung des Schwellenwertes zusätzlich.

Der Maximal-/Minimalwert liegt oberhalb/unterhalb der Mittellinie des Indikators. Diese Methode wird seltener als andere verwendet. Es hängt auch vom verwendeten Indikator ab, da nicht alle Indikatoren einen Mittelwert von 0 haben (z. B. der Mittelwert von RSI ist 50). Der Hauptnachteil ist jedoch seine starke Verzögerung (Abb. 9). 


Abb. 9. Definition der Hochs und Tiefs durch das Kreuzen der Mittellinie. Wir kennen das Hoch 1
erst nachdem die Mittellinie von der Bar 2 gekreuzt wurde. Wir kennen das Tief 3
nach dem Kreuzen der Mittellinie durch die Bar 4.


Klassifizierungssysteme für Divergenzen

Über die Divergenz finden Sie viele Artikel im Internet. Sie beschreiben eine Vielzahl von Ansätzen, die sich sowohl in der Terminologie als auch in den Prinzipien der Systematisierung von Divergenz und Konvergenz unterscheiden. Es gibt einfache, klassische, versteckte und erweiterte Divergenzen. Jemandem teilt sie in die Klassen A, B und C. Wir werden die primären Quellen in diesem Artikel nicht analysieren. Stattdessen werden wir einige der identifizierten Typen durchgehen.

Klassische Divergenz. Diese Art wurde oben bereits beschrieben und in Abb. 5 gezeigt.

Versteckte Divergenz. Die versteckte Divergenz unterscheidet sich von der klassischen durch die Bewegungsrichtung der Preise und des Indikators. Mit anderen Worten, die versteckte Divergenz ähnelt der Konvergenz.

Erweiterte Divergenz. Bisher haben wir nur die Aufwärts- und Abwärtsbewegungen der Preise und des Indikators diskutiert. Wenn wir die horizontale Bewegung hinzufügen, erhöht sich die Anzahl der Optionen. Trotz der vielen Optionen, die wir durch die Kombination von drei Richtungen der Preisbewegung und drei Richtungen der Indikatorbewegung erreichen können, ist nur eine Version der erweiterten Divergenz herausgehoben worden:

  • Horizontale Preisbewegung, der Indikator bewegt sich abwärts - erweiterte Abwärtsdivergenz (Verkaufssignal)
  • Horizontale Preisbewegung, der Indikator bewegt sich nach oben — erweiterte Aufwärtsivergenz (Kaufsignal).

Klassen: A, B, C. А Klasse ist eine klassische Divergenz, während B-und C-Klassen sind erweiterte Versionen der Divergenz.

Klasse B: 

    • Horizontale Preisbewegung, der Indikator bewegt sich nach unten — 'bearish' (Verkaufssignal).
    • Horizontale Preisbewegung, der Indikator bewegt sich nach oben — 'bullish' (Kaufsignal).

Klasse С: 

    • Der Preis steigt, die Hochs des Indikators sind auf einer Ebene — 'bearish' (Verkaufssignal).
    • Der Preis fällt, die Tiefs des Indikators sind auf einer Ebene — 'bullish' (Kaufsignal).

Wie wir sehen können, sind die Klassen B und С die Varianten der erweiterten Divergenz. Die Klasse B wiederholt komplett die obige Definition.

Die wichtigste Schlussfolgerung, die ich beim Durchsuchen des verfügbaren Materials zur Divergenz gezogen habe, ist das Fehlen einer klaren Terminologie und eine unvollständige Erfassung der möglichen Versionen. Daher analysieren wir die verschiedenen Optionen zur Kombination von Preis- und Indikatorrichtung und systematisieren sie.

Komplette Systematisierung der Preis- und Indikatorbewegung

Zunächst definieren wir zwei mögliche Preis und Indikator Bewegungsrichtungen.

  1. Zwei Bewegungsrichtungen: nach oben und unten. 
  2. Drei Bewegungsrichtungen: oben, unten und horizontal.

Der erste Fall bietet nur vier mögliche Kombinationen. Sehen wir sie uns am Beispiel eines Verkaufssignal an.

  1. Preist steigt, Indikator steigt. 
  2. Preis steigt, Indikator fällt (Divergenz).
  3. Preis fällt, Indikator steigt (Konvergenz).
  4. Preis fällt, Indikator fällt.  

Nachdem wir uns nun mit der Definition der Richtungen beschäftigt haben, lassen Sie uns diese Optionen darstellen (Abb. 10).  


Abb. 10. Alle möglichen Kombinationen der Bewegungen von Preis und Indikator im Fall von zwei Richtungen 

Im Fall der drei Richtungen, gibt es jetzt neun Kombinationen.

  1. Preist steigt, Indikator steigt.
  2. Preis steigt, Indikator horizontal.
  3. Preis steigt, Indikator fällt (Divergenz).
  4. Preis horizontal, Indikator steigt.
  5. Preis horizontal, Indikator horizontal.
  6. Preis horizontal, Indikator fällt.
  7. Preis fällt, Indikator steigt (Konvergenz).
  8. Preis fällt, Indikator horizontal.
  9. Preis fällt, Indikator fällt.

Alle diese Kombinationen zeigt Abb. 11.


Abb. 11. Alle möglichen Kombinationen der Bewegungen von Preis und Indikator im Fall von drei Richtungen 

Wenn Sie einen Indikator erstellen, der es Ihnen erlaubt, eine der betrachteten Optionen auszuwählen, können Sie die Divergenz, Konvergenz, versteckte oder erweiterte Divergenz, die Sie für richtig halten, auswählen. Mit anderen Worten, wir bekommen einen universellen Indikator, der auch für diejenigen nützlich ist, die mit der Systematisierung und den in diesem Artikel gegebenen Definitionen nicht einverstanden sind.

Dreifache Divergenz

Bisher wurde die Bewegungsrichtung der Preise, nach oben, unten und horizontal, und des Indikators durch zwei Werte bestimmt. Wir können den dritten Wert hinzufügen, um die Anzahl der möglichen Optionen für Preis- und Indikatorbewegungen zu erhöhen. Insgesamt gibt es neun Optionen: 

  1. Steigt, steigt.
  2. Steigt, horizontal.
  3. Steigt, fällt.
  4. Horizontal, steigt.
  5. Horizontal, horizontal.
  6. Horizontal, fällt.
  7. Fällt, Steigt.
  8. Fällt, horizontal.
  9. Fällt, fällt.

In diesem Fall wäre es korrekter von der Form der Bewegung zu sprechen als von der Richtung. Die Bewegungsarten, definiert durch drei Hochs, sind in Abb. 12 dargestellt.


Abb. 12. Verschiedenen möglichen Bewegungen, basiert auf drei Hochs 

Die entsprechenden Bewegungen basierend auf den Tiefs sind dargestellt in Abb. 13.

 
Abb. 13. Verschiedenen möglichen Bewegungen basiert auf drei Tiefs 

Durch die Kombination von 9 Typen der Preisbewegung mit den 9 Typen der Indikatorbewegung erhalten wir 81 Varianten der dreifachen Divergenz.

So können Sie die Bewegung mit beliebig viele Punkte bestimmen. Wenn wir den vierten Punkt hinzufügen, haben wir 81 Variationen der Bewegung der Preise und des Indikators und 6561 (81*81) mögliche Versionen ihrer Kombination. Natürlich, je mehr Möglichkeiten, desto unwahrscheinlicher sind sie. Vielleicht ist es nicht sinnvoll, den vierten Punkt zu übernehmen, aber der Indikator des aktuellen Artikel soll keine Einschränkung der Anzahl der Punkte enthalten, mit denen die Bewegungsart definiert wird.

Universalindikator zur Bestimmung der Divergenz

Nachdem wir uns mit der Theorie beschäftigt haben, beginnen wir mit der Entwicklung des Indikators. 

Auswahl eines Oszillators. Um nicht durch einen einzigen Oszillator zur Definition der Divergenz begrenzt zu werden, verwenden wir den in diesem Artikel beschriebenen Universaloszillator. Beigefügt sind: iUniOsc (Universaloszillator) und iUniOscGUI (gleicher Oszillator mit der grafischen Oberfläche). Wir werden die Basisversion - iUniOsc - verwenden.

Erstellen eines neuen Indikators. Erstellen wir den Indikator iDivergence im MetaEditor. Wir verwenden die Funktion OnCalculate(). Die Funktion OnTimer() wird nicht benötigt. Markieren Sie das Ankreuzkästchen "Indikator im separaten Fenster". Wir erstellen drei Puffer: eine Linie für die Oszillatoranzeige Linie, und zwei Puffer mit Pfeilen für das Auftreten einer Divergenz. Nachdem eine neue Datei im Editor geöffnet wurde, ändern Sie die Puffernamen: 1 - buf_osc, 2 - buf_buy, 3 - buf_sell. Die Namen sollten dort geändert werden, wo die Arrays deklariert werden und in der Funktion OnInit(). Wir können auch die Puffereigenschaften anpassen: Indikator_label1, Indikator_label2, Indikator_label3 - die Werte dieser Eigenschaften werden im Tooltip angezeigt, wenn Sie mit der Maus über die Linie oder den Namen des Indikators fahren, sowie im Datenfenster. Nennen wir sie "osc", "buy" und "sell".

Anwenden des Universaloszillators. Fügen Sie alle externen Parameter des Indikators iUniOsc ein. Die Parameter ColorLine1, ColorLine2 und ColorHisto werden im Fenster der Eigenschaften nicht benötigt. Wir werden Sie zu verbergen. Der Parameter 'Type' hat den benutzerdefinierten Typ OscUni_RSI, der in der Datei UniOsc/UniOscDefines.mqh deklariert ist. Wir laden diese Datei. Standardmäßig ist der Parameter 'Typ' auf OscUni_ATR — dem Indikator ATR — gesetzt. Der ATR ist jedoch nicht von der Bewegungsrichtung der Preise abhängig, d. h. er eignet sich nicht für die Definition von Divergenzen. Setzen Sie daher den Indikator OscUni_RSI — der Indikator RSI — als Standard:

#include <UniOsc/UniOscDefines.mqh>

input EOscUniType          Type              =  OscUni_RSI;
input int                  Period1           =  14;
input int                  Period2           =  14;
input int                  Period3           =  14;
input ENUM_MA_METHOD       MaMethod          =  MODE_EMA;
input ENUM_APPLIED_PRICE   Price             =  PRICE_CLOSE;   
input ENUM_APPLIED_VOLUME  Volume            =  VOLUME_TICK;   
input ENUM_STO_PRICE       StPrice           =  STO_LOWHIGH;   
      color                ColorLine1        =  clrLightSeaGreen;
      color                ColorLine2        =  clrRed;
      color                ColorHisto        =  clrGray;

Deklarieren der Handle des Universaloszillators unter den externen Variablen:

int h;

Laden Sie den Universaloszillator zu Beginn der Funktion OnInit():

h=iCustom(Symbol(),Period(),"iUniOsc", Type,
                                       Period1,
                                       Period2,
                                       Period3,
                                       MaMethod,
                                       Price,
                                       Volume,
                                       StPrice,
                                       ColorLine1,
                                       ColorLine2,
                                       ColorHisto);
if(h==INVALID_HANDLE){
   Alert("Can't load indicator");
   return(INIT_FAILED);
}

In der Funktion OnCalculate() kopieren Sie die Daten des Universaloszillators in den Puffer buf_osc:

int cnt;   

if(prev_calculated==0){
   cnt=rates_total;
}
else{ 
   cnt=rates_total-prev_calculated+1; 
}

if(CopyBuffer(h,0,0,cnt,buf_osc)<=0){
   return(0);
}  

In diesem Stadium können wir die Korrektheit der durchgeführten Aktionen überprüfen, indem wir den Indikator iDivergence auf den Chart ziehen. Wenn alles richtig gemacht wurde, sehen Sie im Subfenster die Linie des Oszillators. 

Definition der Extrema des Oszillators. Wir haben bereits drei Möglichkeiten zur Definition von Extrema in Betracht gezogen. Wir nehmen sie in den Indikator auf und bieten die Möglichkeit, jede von ihnen auszuwählen (externe Variable mit einer Dropdown-Liste). Im Verzeichnis Include erstellen wir das Verzeichnis UniDiver, in dem sich alle weiteren Dateien mit dem Code befinden sollen. Erstellen Sie die Datei UniDiver/UniDiverDefines.mqh und schreiben Sie die Enumeration EExtrType in diese Datei:

enum EExtrType{
   ExtrBars,
   ExtrThreshold,
   ExtrMiddle
};

Enumeration:

  • ExtrBars — mit Bars;
  • ExtrThreshold — mit einem Schwellenwert vom letzten Hoch/Tief;
  • ExtrMiddle — Maximum oder Minimum, wenn der Indikator über oder unter seiner Mittellinie ist. 

Legen Sie im Indikator den externen Parameter ExtremumType an und fügen Sie ihn vor alle anderen externen Parameter ein. Bei der Definition der Extrema durch Bars benötigen wir zwei Parameter - Anzahl der Bars links und rechts vom Extremum, während wir bei der Definition durch Schwellwert den Parameter für die Berechnung des Schwellenwertes benötigen:

input EExtrType            ExtremumType      =  ExtrBars; // Typ des Extremums
input int                  LeftBars          =  2;        // Bars links der Bar des Extremums
input int                  RightBars         =  -1;       // Bars rechts der Bar des Extremum
input double               MinMaxThreshold   =  5;        // Schwellenwert für ExtrThreshold 

Implementieren wir die Möglichkeit, einen Parameter, RightBars oder LeftBars, zu verwenden, zusätzlich zur gleichzeitigen Verwendung von zwei. RightBars ist standardmäßig gleich -1. Das bedeutet, dass er nicht verwendet wird und ihm der Wert des zweiten Parameters zugewiesen werden soll.

Klassen für die Definition der Extrema. Eine Änderung der Methode für die Definition der Extrema ist während der Laufzeit des Indikators nicht notwendig, daher wäre es sinnvoller, OOP anstelle der Operatoren 'if' und 'switch' zu verwenden. Legen Sie die Basisklasse und drei abgeleitete Klassen für drei Methoden zur Definition eines Extremas an. Eine dieser abgeleiteten Klassen soll beim Start des Kennzeichens ausgewählt werden. Diese Klassen sind sowohl für die Definition der Extremas als auch für die Durchführung aller notwendigen Arbeiten zur Auffindung einer Divergenz zu verwenden. Sie unterscheiden sich nur in der Definition von Extremas, während die Definition der Konvergenz in allen Fällen völlig identisch ist. Daher soll die Funktion mit der Definition der Divergenz in der Basisklasse liegen und von abgeleiteten Klassen aufgerufen werden. Aber zuerst müssen wir einen einfachen Zugang zu allen Extrema der Indikatoren ermöglichen (wie es bei den Hochs der ZigZags im Artikel "Wolfe Wellen" der Fall war).

Die Struktur SExtremum wird dient der Speicherung der Daten eines Extremums. Die Beschreibung der Struktur befindet sich in UniDiverDefines:

struct SExtremum{
   int SignalBar;
   int ExtremumBar;
   datetime ExtremumTime;
   double IndicatorValue;
   double PriceValue;
};

Die Felder der Struktur:

  • SignalBar — Bar, für die eine gültige Formation eines Extremums erkannt worden ist
  • ExtremumBar — Bar mit einem Extremum
  • ExtremumTime — Zeitstempel der Bar mit einem Extremum
  • IndicatorValue — Indikatorwert der Bar des Extremums
  • PriceValue — Preis der Bar mit einem Extremum des Indikators

Zwei Arrays dieser Strukturen sollen dazu dienen, Daten aller Hochs und Tiefs zu speichern. Sie werden Mitglieder der Basisklasse sein.

Die Klassen zur Definition der Extrema befinden sich in der Datei UniDiver/CUniDiverExtremums.mqh, der Name der Basisklasse ist CDiverBase. Betrachten wir zunächst nur die Struktur der Klasse mit den grundlegenden Methoden. Der Rest wird bei Bedarf nachträglich hinzugefügt.

class CDiverBase{
   protected: 
      
      SExtremum m_upper[];
      SExtremum m_lower[];

      void AddExtremum( SExtremum & a[],
                        int & cnt,
                        double iv,
                        double pv,
                        int mb,
                        int sb,
                        datetime et);
   
      void CheckDiver(  int i,
                        int ucnt,
                        int lcnt,
                        const datetime & time[],
                        const double &high[],
                        const double &low[],                     
                        double & buy[],
                        double & sell[],
                        double & osc[]
      );

   public:
      virtual void Calculate( const int rates_total,
                              const int prev_calculated,
                              const datetime &time[],
                              const double &high[],
                              const double &low[],
                              double & osc[],
                              double & buy[],     
                              double & sell[]
      );
}; 

Mit der virtuellen Methode Calculate() können Sie die Definition der Extrema auswählen. Die Methoden AddExtremum() und CheckDiver() befinden sich im Abschnitt 'protected' — sie werden von der Methode Calculate() der abgeleiteten Klassen aufgerufen. Die Methode AddExtremum() fügt die Daten über die Hochs und Tiefs in die Arrays m_upper[] und m_lower[]. Die Methode CheckDiver() prüft, ob die Divergenzbedingung erfüllt ist und setzt die Pfeile des Indikators. Nachfolgend werden wir alle diese Methoden näher betrachten, aber für jetzt machen wir uns mit den abgeleiteten Klassen für die anderen Möglichkeiten der Definition der Extrema vertraut.

Bestimmen der Extrema durch Bars. Die Klasse, um die Extrema durch Bars zu bestimmen:

class CDiverBars:public CDiverBase{
   private:   
   
      SPseudoBuffers1 Cur;
      SPseudoBuffers1 Pre;  
           
      int m_left,m_right,m_start,m_period;   
   
   public:
         
      void CDiverBars(int Left,int Right);
   
      void Calculate( const int rates_total,
                      const int prev_calculated,
                      const datetime &time[],
                      const double &high[],
                      const double &low[],
                      double & osc[],
                      double & buy[],
                      double & sell[]
      );
}; 

Die Parameter für die Definition von Extremwerten (die externen Variablen LeftBars und RightBars) werden an den Klassenkonstruktor übergeben, deren Werte überprüft und gegebenenfalls geändert und zusätzliche Parameter berechnet:

void CDiverBars(int Left,int Right){
   m_left=Left;
   m_right=Right;   
   if(m_left<1)m_left=m_right;   // Parameter für links nicht angegeben
   if(m_right<1)m_right=m_left;  // Parameter für rechts nicht angegeben
   if(m_left<1 && m_right<1){    // Beide Parameter nicht angegeben
      m_left=2;
      m_right=2;
   }
   m_start=m_left+m_right;       // verschieben des Startpunkts
   m_period=m_start+1;           // Anzahl der Bars des Intervalls
}

Zuerst werden die Parameterwerte verifiziert. Wenn einige von ihnen nicht positiv (nicht gesetzt) sind, wird ihm der Wert des zweiten Parameters zugewiesen. Wenn kein einziger Parameter gesetzt ist, werden ihnen die Standardwerte zugewiesen (2). Danach werden der Abstand der Anfangsbar für die Suche nach einem Extremum (m_start) und die Gesamtzahl der Extrema (m_period) berechnet.

Die Methode Calculate() ist identisch mit der Standardfunktion OnCalculate(), erhält aber nur die benötigten Preisarrays: time[], high[], low[] und die Indikatorpuffer osc[] (Oszillatordaten), buy[] und sell[] (Pfeile). Wie üblich wird der Bereich der berechneten Bars in der Funktion OnCalculte() definiert. Die Extrema der Indikatoren (die Funktionen ArrayMaximum() und ArrayMinimum()) werden anschließend in der Standardschleife des Indikators definiert. Nach dem Erkennen eines Extremums wird die Methode AddExtremum() aufgerufen, um Daten zum Array m_upper[] oder m_lower[] hinzuzufügen. Am Ende wird die Methode CheckDiver() aufgerufen, um Daten aus den Arrays mit Extremwerten zu analysieren. Wenn eine Divergenz erkannt wird, werden die Pfeile platziert.

void Calculate( const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &high[],
                const double &low[],
                double & osc[],
                double & buy[],
                double & sell[]
){

   int start; // Variable de Indexes der Anfangsbar
   
   if(prev_calculated==0){ // vollständige Berechnung des Indikators
      start=m_period; // Definieren der Anfangsbar für die Berechnung
      m_LastTime=0;   // Rücksetzen der Variablen zur Definition einer neuen Bar
      Cur.Reset();    // Rücksetzen der Hilfsstrukturen
      Pre.Reset();    // Rücksetzen der Hilfsstrukturen
   }
   else{ // Berechnen nur der neuen Bars
      start=prev_calculated-1; // Berechnen des Index der Bar, ab der die Berechnung fortgesetzt wird
   }

   for(int i=start;i<rates_total;i++){ // Hauptschleife des Indikators
      
      if(time[i]>m_LastTime){ // neue Bar
         m_LastTime=time[i];
         Pre=Cur;
      }
      else{ // Neuberechnen derselben Bar
         Cur=Pre;
      }
      
      // Berechnen der Suchparameter für die Hochs/Tiefs 
      int sb=i-m_start; // Index der Bar, ab der das Intervall beginnt
      int mb=i-m_right; // Index der Bar mit einem Hoch/Tief
      
      if(ArrayMaximum(osc,sb,m_period)==mb){ // es ist ein Hoch
         // Hinzufügen des Hochs zum Array
         this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
      }
      if(ArrayMinimum(osc,sb,m_period)==mb){ // es ist ein Tief
         // Hinzufügen des Tiefs zum Array
         this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
      }
      
      // Prüfen auf eine Divergenz
      this.CheckDiver(i,Cur.UpperCnt,Cur.LowerCnt,time,high,low,buy,sell,osc);
   } 
}

Wir betrachten diesen Code im Detail. Gleich zu Beginn der Schleife:

if(time[i]>m_LastTime){ // neue Bar
   m_LastTime=time[i];
   Pre=Cur;
}
else{ // Neuberechnen derselben Bar
   Cur=Pre;
}

In der Basisklasse wird die Variable m_LastTime deklariert. Überschreitet die Bar time[i] die Variable, wird die Bar zum ersten Mal berechnet. Die Zeit der Bar wird der Variablen m_LastTime zugewiesen, während der Variable Pre der Wert der Variablen Cur zugewiesen wird. Umgekehrt wird beim Neuberechnen der gleichen Bars die Variable Pre der Variablen Cur zugewiesen. Die Verwendung der Variablen Cur und Pre wird in diesem Artikel im Detail beschrieben. Die Pre- und Cur-Variablen sind vom Typ SPseudoBuffers1, wie in der Datei UniDiverDefines beschrieben:

struct SPseudoBuffers1{
   int UpperCnt;
   int LowerCnt;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
   }   
};

Die Struktur enthält zwei Felder:

  • UpperCount — Anzahl der verwendeten Elemente im Array m_upper[];
  • LowerCount — Anzahl der verwendeten Elemente im Array m_lower[];

Die Methode Reset() dient dem schnellen Rücksetzen aller Felder der Struktur.

Nach der Arbeit mit den Variablen Cur und Pre werden die Indices für die Suche nach den Extrema berechnet:

// Berechnen der Parameter zur Suche nach Hochs/Tiefs 
int sb=i-m_start; // Index der Bar, ab der das Intervall beginnt
int mb=i-m_right; // Index der Bar mit einem Hoch/Tief

Der Index der Bar, ab dem die Suche nach den Hochs/Tiefs beginnt, wird der Variablen 'sb' zugewiesen. Der Index der Bar, die ein Hoch/Tief aufweisen könnten, wird der Variablen 'mb' zugewiesen.

Definieren Sie das Hoch oder Tief mit den Funktionen ArrayMaximum() und ArrayMinimum():

if(ArrayMaximum(osc,sb,m_period)==mb){ // es ist ein Hoch
   // Hinzufügen eines Hochs zum Array
   this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
}
if(ArrayMinimum(osc,sb,m_period)==mb){ // es ist ein Tief
   // Hinzufügen eines Tiefs zum Array
   this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
}

Wenn die Funktion ArrayMaximum() oder ArrayMinimum() 'mb' zurückgibt, bedeutet dies, dass die angegebene Anzahl der Bars links und rechts des Hochs oder Tiefs angeordnet ist. Dies wiederum zeigt an, dass sich das gesuchte Hoch/Tief gebildet hat. Die Methode AddExtremum() wird aufgerufen und die Daten werden dem Array m_upper[] oder m_lower[] hinzugefügt.

Betrachten wir die einfache Methode AddExtremum():

void AddExtremum( SExtremum & a[], // Hinzufügen zu dem Array
                  int & cnt,       // Anzahl der belegten Arrayelemente
                  double iv,       // Indikatorwert
                  double pv,       // Preis 
                  int mb,          // Index der Bar mit einem Extremum
                  int sb,          // Index der Bar, für die eine gültige Formation eines Extremums erkannt worden ist
                  datetime et      // Zeitstempel der Bar mit einem Extremum
){
   if(cnt>=ArraySize(a)){ //das Array ist ausgefüllt
      // Erhöhen der Arraygröße
      ArrayResize(a,ArraySize(a)+1024);
   }
   // Ergänzen neuer Daten
   a[cnt].IndicatorValue=iv; // Indikatorwert
   a[cnt].PriceValue=pv;     // Preis
   a[cnt].ExtremumBar=mb;    // Index der Bar mit einem Extremum
   a[cnt].SignalBar=sb;      // Index der Bar, für die eine gültige Formation eines Extremums erkannt worden ist 
   a[cnt].ExtremumTime=et;   // Zeitstempel der Bar mit einem Extremum
   cnt++;                    // erhöhen des Zählers für die belegten Elemente
}

Das Array a[], dem die neuen Daten hinzugefügt werden sollen, wird über die Parameter an die Methode übergeben. Dies kann das Array m_upper[] oder m_lower[] sein. Die Anzahl der belegten Elemente des Arrays a[] wird über die Variable 'cnt' übergeben. Dies kann die Variable Cur.UpperCnt or Cur.LowerCnt sein. Das Array a[] und die Variable 'cnt' werden als Referenz übergeben, da sie in der Methode geändert werden. 

iv - Indikatorwert auf Basis des Extremwertes, pv - Preis auf der Bar mit einem Extremwert, mb - Index der Bar mit einem Extremwert, sb - Signalbar (bei der das Extremum bekannt geworden ist), et - Zeitstempel der Bar mit einem Extremum.

Die Arraygröße wird am Anfang der Methode AddExtremum() geprüft. Wenn er voll ist, vergrößert sich seine Größe auf bis zu 1024 Elemente. Die Daten werden hinzugefügt und die Variable 'cnt' wird anschließend erhöht.

Wir werden uns die Methode CheckDiver() später anschauen.

Festlegung einen Extremums durch einen Schwellenwert.

Die Klasse für die Definition durch einen Schwellenwert unterscheidet sich von der Klasse der Definition durch Bars hauptsächlich in den Variablentypen Cur und Pre: dies ist der Typ SPseudoBuffers2, der in der Datei UniDiverDefines.mqh beschrieben wird:

struct SPseudoBuffers2{
   int UpperCnt;
   int LowerCnt;
   double MinMaxVal;
   int MinMaxBar;   
   int Trend;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
      MinMaxVal=0;
      MinMaxBar=0;
      Trend=1;
   }   
};

Die Struktur SPseudoBuffers2 hat die gleichen Felder wie SPseudoBuffers1 und einige weitere:

  • MinMaxVal — Variable für den maximalen oder minimalen Indikatorwert
  • MinMaxBar — Variable für den Index der Bar, auf dem der maximale oder minimale Indikatorwert gefunden wird
  • Trend — Variabele für die Bewegungsrichtung des Indikators. Der Wert 1 bedeutet, dass sich der Indikator steigt und sein Maximum kontrolliert wird. Bei -1 wird das Minimum verfolgt.

Der externe Parameter mit dem Schwellwert - die Variable MinMaxThreshold - wird an den Klassenkonstruktor übergeben. Sein Wert wird in der im Abschnitt 'private' deklarierten Variablen m_threshold gespeichert. 

Die Methode Calculate() dieser Klasse unterscheidet sich in der Methode für die Definition der Extrema:

switch(Cur.Trend){ // aktuelle Richtung des Indikators
   case 1: // steigt
      if(osc[i]>Cur.MinMaxVal){ // neues Maximum
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<Cur.MinMaxVal-m_threshold){ // Schwellenwert überschritten
         // Hinzufügen eines Hochs zum Array
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;          // Wechseln der Richtung
         Cur.MinMaxVal=osc[i];  // Wert des Anfangsminimum
         Cur.MinMaxBar=i;       // Bar mit dem Anfangsminimum
      }
   break;
   case -1: // fällt
      if(osc[i]<Cur.MinMaxVal){ // neues Minimum
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>Cur.MinMaxVal+m_threshold){ // Schwellenwert überschritten
         // Hinzufügen des Tiefs zum Array
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;           // Wechseln der Richtung
         Cur.MinMaxVal=osc[i];  // Wert des Anfangsmaximum
         Cur.MinMaxBar=i;       // Bar mit dem Anfangsmaximum
      }         
   break;
}

Wenn die Variable Cur.Trend 1 ist, wird der Oszillatorwert mit dem Wert von Cur.MinMaxValue verglichen. Wenn der neue Oszillatorwert den Wert aus der Variablen überschreitet, wird der Variablenwert aktualisiert. Der Index der Bar, die ein neues Hoch aufweist, wird der Variablen Cur.MinMaxBar zugewiesen. Es ist auch sichergestellt, dass der Oszillatorwert nicht um den Wert m_threshold vom zuletzt bekannten Maximum gefallen ist. Wenn doch, hat der Oszillator seine Richtung geändert. Die Methode AddExtremum() wird aufgerufen, die Daten des neuen Extremums werden im Array gespeichert, der aktuelle Trendwert wird durch den entgegengesetzten Wert ersetzt, während die Initialparameter des neuen Minimums in den Variablen Cur.MinMaxVal und Cur.MinMaxBar gesetzt werden. Da sich der Wert der Variable Aktueller Trend geändert hat, wird von nun an ein anderer 'case'-Abschnitt ausgeführt, der die minimalen Oszillatorwerte das Einhalten des Schwellenwertes verfolgt — falls die Ausführung fortgesetzt wird.

Definieren von Extrema durch ihre Position relativ zur Mittellinie des Oszillators. Der Typ des verwendeten Oszillators wird an den Klassenkonstruktor übergeben. Je nach Typ wird der Mittelwert des Oszillators definiert:

void CDiverMiddle(EOscUniType type){
   if(type==OscUni_Momentum){
      m_level=100.0;
   }
   else if(type==OscUni_RSI || type==OscUni_Stochastic){
      m_level=50.0;
   }
   else if(type==OscUni_WPR){
      m_level=-50.0;
   }
   else{
      m_level=0.0;
   }
}

Für das Momentum ist der Wert 100, für RSI und Stochastic 50, für WPR -50, für andere Oszillatoren der Wert 0.

Die Methode zur Bestimmung der Extrema ähnelt in vielerlei Hinsicht der Schwellwertmethode:

switch(Cur.Trend){
   case 1:
      if(osc[i]>Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<m_level){
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;               
      }
   break;
   case -1:
      if(osc[i]<Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>m_level){
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }         
   break;
}

Der einzige Unterschied besteht darin, dass die Richtungsänderung durch einen Vergleich mit der mittleren Ebene des Oszillators erfolgt: osc[i]<m_level oder osc[i]>m_level.

Methode zur Festlegung der Divergenzart. Fügen Sie den externen Parametern die Variable zur Auswahl des Typs der erkannten Divergenz hinzu:

input int                  Number            =  3;

Standardmäßig ist der Wert des Parameters 3. Gemäß Abb. 11 bedeutet das eine klassische Divergenz. Insgesamt werden in Abb. 11 9 Kombinationen aus Kurs- und Indikatorbewegung dargestellt. Ergänzen wir jetzt noch eine weitere Option — "not checked". Jetzt haben wir 10 Kombinationen. Mit den normalen Dezimalzahlen können wir also jede beliebige Kombination verschiedener Bewegungen mit einer Zahl beschreiben (einschließlich einer dreifachen Divergenz). Es stellt sich heraus, dass eine einstellige Zahl einer einfachen Divergenz entspricht (auf zwei Hochs oder Tiefs), eine zweistellige Zahl ist eine dreifache Zahl usw. Beispielsweise entspricht die Zahl 13 der in Abb. 14 dargestellten Kombination.


Abb. 14. Kombination von Indikator und
Preisen für Number=13 für ein Verkaufssignal
 

Klassen zur Prüfung der Divergenzbedingungen. Die Basis- und die ableiteten Klassen werden zur Überprüfung der Divergenzbedingungen angelegt. Der Wert des Parameters Number wird beim Start des Indikators analysiert. Das wichtigste Attribut ist seine Länge, da es die Größe des Arrays von Zeigern auf Klassen beeinflusst. Anschließend wird für jedes Arrayelement entsprechend des Typs der Bedingung das entsprechende Objekt generiert.    

Die Klassen für die Verifikation der Bedingungen befinden sich in der Datei UniDiver/CUniDiverConditions.mqh. Die Basisklasse heißt CDiverConditionsBase. Schauen wir sie uns mal an:

class CDiverConditionsBase{
   protected:
      double m_pt;
      double m_it;
   public:
   void SetParameters(double pt,double it){
      m_pt=pt;
      m_it=it;
   }
   virtual bool CheckBuy(double i1,double p1,double i2,double p2){
      return(false);
   }
   virtual bool CheckSell(double i1,double p1,double i2,double p2){
      return(false);
   }
};

Die Klasse verfügt über zwei virtuelle Methoden zum Vergleich zweier benachbarter Hochs. Ihre Parameter werden in der Klasse übergeben: 

  • i1 — Indikatorwert im Punkt 1
  • p1 — Preis im Punkt 1
  • i2 — Indikatorwert im Punkt 2
  • p2 — Preis im Punkt 2

Die Punkte werden von rechts nach links gezählt, beginnend mit 1.

Mit der Methode SetParameters() werden zusätzliche Vergleichsparameter gesetzt: pt - verfügbare Differenz der Preiswerte, bei der angenommen wird, dass die Preise auf einer Ebene liegen, it - ähnlicher Parameter für den Vergleich der Hochs des Indikators. Die Werte dieser Parameter werden über das Fenster der Eigenschaften festgelegt:

input double               IndLevel          =  0;
input int                  PriceLevel        =  0;

Der Code einer der abgeleiteten Klassen ist unten aufgeführt:

class CDiverConditions1:public CDiverConditionsBase{
   private:
   public:
   bool CheckBuy(double i1,double p1,double i2,double p2){
      return((p1>p2+m_pt) && (i1>i2+m_it));
   }
   bool CheckSell(double i1,double p1,double i2,double p2){
      return((p1<p2-m_pt) && (i1<i2-m_it));
   }
};

In der Methode für CheckBuy() wird geprüft, ob der Preis im Punkt 1 den des Punktes 2 überschreitet. Dasselbe gilt für den Indikator: Der Preis im Punkt 1, der den von Punkt 2 übersteigen. Die Methode CheckSell() ist spiegelsymmetrisch zur Methode CheckBuy(). Alle anderen Klassen sind ähnlich und unterscheiden sich nur in logischen Ausdrücken, außer CDiverConditions0. Die Methoden CheckSell() und CheckBuy() liefern in dieser Klasse sofort 'true' zurück. Sie werden verwendet, wenn die Bedingungsprüfung deaktiviert ist (jede Option ist möglich).

Vorbereitung zur Prüfung der Bedingungen von Divergenzen. Das Array und die Variable für seine Größe werden im Abschnitt 'protected' der Klasse CDiverBase deklariert:

CDiverConditionsBase * m_conditions[];
int m_ccnt;  

In der Methode SetConditions() wird die Größe des Arrays m_conditions geändert und die Verifikationsobjekte für die Divergenzbedingungen erzeugt:

void SetConditions(int num,      // Divergenzzahl
                   double pt,    // PriceLevel Parameter
                   double it){   // IndLevel Parameter
   if(num<1)num=1; // die Divergenzzahl sollte nicht kleiner als 1 sein
   ArrayResize(m_conditions,10); // Maximalzahl möglicher Bedingungen
   m_ccnt=0; // Zähler der aktuellen Zahl von Bedingungen
   while(num>0){
      int cn=num%10; // Varianten der Divergenz zwischen neuen Paaren von Extrema
      m_conditions[m_ccnt]=CreateConditions(cn); // Erstellen eines Objektes
      m_conditions[m_ccnt].SetParameters(pt,it); // Setzen der Parameter zur Verifikation der Bedingungen
      num=num/10; // Wechseln zur nächsten Bedingung 
      m_ccnt++; // Zählen der Bedingung
   }
   // Anpassen der Arraygröße gemäß der aktuellen Zahl von Bedingungen 
   ArrayResize(m_conditions,m_ccnt);    
}

Die folgenden Parameter werden der Methode übergeben:

  • num — Anzahl, externer Parameter;
  • pt — PriceLevel, externer Parameter;
  • it — IndLevel, externer Parameter.

Der Parameter 'num' wird als erster geprüft:

if(num<1)num=1; // die Divergenzzahl sollte nicht kleiner als 1 sein

Dann erhöht sich das Array m_conditions bis zur maximal möglichen Größe (10 - Länge des Maximalwertes der Variablen 'int'). Danach wird das Objekt der Zustandsüberprüfung durch die Methode CreateConditions() erstellt und die Parameter werden mit der Methode SetParameters() in der 'while' Schleife gesetzt, abhängig vom Wert der einzelnen Ziffern von 'num'. Nach der Schleife ändert sich die Größe des Arrays entsprechend der tatsächlichen Anzahl der angewandten Bedingungen.

Betrachten wir die Methode CreateConditions():

CDiverConditionsBase * CreateConditions(int i){
   switch(i){
      case 0:
         return(new CDiverConditions0());      
      break;
      case 1:
         return(new CDiverConditions1());      
      break;
      case 2:
         return(new CDiverConditions2());      
      break;
      case 3:
         return(new CDiverConditions3());      
      break;
      case 4:
         return(new CDiverConditions4());      
      break;
      case 5:
         return(new CDiverConditions5());      
      break;      
      case 6:
         return(new CDiverConditions6());      
      break;
      case 7:
         return(new CDiverConditions7());      
      break;
      case 8:
         return(new CDiverConditions8());      
      break;
      case 9:
         return(new CDiverConditions9());
      break;
   }
   return(new CDiverConditions0()); 
}

Die Methode ist einfach: Abhängig vom Parameter i wird das entsprechende Objekt erzeugt und die Referenz darauf zurückgegeben.

Definition der Divergenz. Jetzt können wir die Methode CheckDivergence() der Klasse CDiverBase-Klasse betrachten. Zuerst betrachten wir den Code der Methode insgesamt, dann im Einzelnen:

void CheckDiver(  int i,                    // Berechnen des Index der Bar
                  int ucnt,                 // Anzahl der Hochs im Array m_upper
                  int lcnt,                 // Anzahl der Tiefs im Array m_lower
                  const datetime & time[],  // Array der Zeitstempel der Bars
                  const double &high[],     // Array der Preise der Hochs
                  const double &low[],      // Array der Preise der Tiefs
                  double & buy[],           // Indikatorpuffer für die Pfeile nach oben
                  double & sell[],          // Indikatorpuffer für die Pfeile nach unten
                  double & osc[]            // Indikatorpuffer für Werte des Oszillators
){

   // Löschen der Puffer für die Pfeile
   buy[i]=EMPTY_VALUE;
   sell[i]=EMPTY_VALUE;
   
   // Löschen der Grafikobjekte
   this.DelObjects(time[i]);
   
   // für die Hochs (Verkaufssignale)
   if(ucnt>m_ccnt){ // Anzahl der Hochs genügt
      if(m_upper[ucnt-1].SignalBar==i){ // ein Hoch erkannt auf der aktuellen Bar

         bool check=true; // Angenommen, eine Divergenz ist aufgetreten
         
         for(int j=0;j<m_ccnt;j++){ // für alle Hoch-Paare
            // Verifizieren der Bedingungen eines neuen Hoch-Paares
            bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                                   m_upper[ucnt-1-j].PriceValue,
                                                   m_upper[ucnt-1-j-1].IndicatorValue,
                                                   m_upper[ucnt-1-j-1].PriceValue
                                                );
            if(!result){ // Bedingung nicht erfüllt
               check=false; // keine Divergenz
               break; 
            } 
                                
         }
         if(check){ // Divergenz ist aufgetreten
            // Setzen des Puffers des Indikators der Pfeile
            sell[i]=osc[i];
            // Zeichnen zuständlicher Linien und/oder Pfeil auf dem Chart
            this.DrawSellObjects(time[i],high[i],ucnt);
         }
      }
   }
   
   // für Tiefs (Kaufsignale)
   if(lcnt>m_ccnt){
      if(m_lower[lcnt-1].SignalBar==i){
         bool check=true;
         for(int j=0;j<m_ccnt;j++){
            bool result=m_conditions[j].CheckBuy(  m_lower[lcnt-1-j].IndicatorValue,
                                                   m_lower[lcnt-1-j].PriceValue,
                                                   m_lower[lcnt-2-j].IndicatorValue,
                                                   m_lower[lcnt-2-j].PriceValue
                                                );
            if(!result){
               check=false;
               break;
            }                                          
         }
         if(check){
            buy[i]=osc[i];
            this.DrawBuyObjects(time[i],low[i],lcnt);
         }
      }
   }    
}

Die folgenden Parameter werden der Methode übergeben:

  • i — Index der gerade berechneten Bar;
  • ucnt — Anzahl der angewendeten Elemente des Arrays m_upper[];
  • lcnt — Anzahl der angewendeten Elemente des Arrays m_lower[];
  • time[] — Array mit den Zeitstempel der Bars;
  • high[] — Array mit den Hochs der Preisbars;
  • low[] — Array mit den Tiefs der Preisbars;                  
  • buy[] — Indikatorpuffer der Pfeile für Kauf;
  • sell[] — Indikatorpuffer der Pfeile für Verkauf;
  • osc[] — Indikatorpuffer der Werte des Oszillators.

Die Puffer mit den Pfeilen werden zuerst gelöscht:

// Löschen der Puffer für die Pfeile
buy[i]=EMPTY_VALUE;
sell[i]=EMPTY_VALUE;

Die Grafikobjekte der gerade berechneten Bar werden gelöscht:

// Löschen der Grafikobjekte
this.DelObjects(time[i]);

Abgesehen von Pfeilen verwendet iDivergence grafische Objekte, um auf dem Kurschart Pfeile und Linien, die die Extrema der Preise mit denen des Indikators verbinden, zu zeichnen.  

Dann gibt es zwei identische Codeteile, um die Bedingungen für Kauf und Verkauf zu überprüfen. Betrachten wir den ersten Abschnitt zum Verkaufen. Die Anzahl der verfügbaren Oszillatorspitzen sollte 1 mehr sein als die Anzahl der geprüften Bedingungen. Daher wird die Prüfung durchgeführt:  

// für die Hochs (Verkaufssignale)
if(ucnt>m_ccnt){ // es gibt eine ausreichende Zahl von Hochs 

}

Danach prüfen wir, ob der obere Teil der berechneten Bar vorhanden ist. Dies wird durch die Übereinstimmung zwischen dem Index der berechneten Bar und dem Index aus dem Array mit den Daten von Hoch/Tief bestimmt:

if(m_upper[ucnt-1].SignalBar==i){ // in Hoch erkannt auf der aktuellen Bar

}

Eine Hilfsvariable ist für das Ergebnis der Prüfung der Bedingungen erforderlich:

bool check=true; // Angenommen, eine Divergenz ist aufgetreten

Die Schleife 'for' geht über alle Bedingungen und übergibt die Daten der Hochs:

for(int j=0;j<m_ccnt;j++){ // für alle Hoch-Paare
   // Prüfen, ob die Bedingungen des nächsten Hochpaares erfüllt sind
   bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                          m_upper[ucnt-1-j].PriceValue,
                                          m_upper[ucnt-1-j-1].IndicatorValue,
                                          m_upper[ucnt-1-j-1].PriceValue
                                        );
   if(!result){ // Bedingung nicht erfüllt
      check=false; // keine Divergenz
      break; 
   } 
}

Wenn eine der Bedingung nicht erfüllt ist, wird die die Schleife verlassen, und der Variablen 'check' wird 'false' zugewiesen. Wenn alle Bedienungen erfüllt sind, wird der 'check' 'true' zugewiesen und es wird ein Grafikobjekt gezeichnet:

if(check){ // Divergenz ist aufgetreten
   // Platzieren des Pfeils des Indikatorpuffers
   sell[i]=osc[i];
   // Zeichne Hilfslinien und/oder einen Pfeil auf dem Preischart
   this.DrawSellObjects(time[i],high[i],ucnt);
}

Darstellung der grafischen Objekte können im Fenster für die Eigenschaften des Indikators der aktiviert/deaktiviert werden. Die folgenden Variablen sind dafür deklariert:

input bool                 ArrowsOnChart     =  true;
input bool                 DrawLines         =  true;
input color                ColBuy            =  clrAqua;
input color                ColSell           =  clrDeepPink;
  • ArrowsOnChart — Aktivieren der Pfeile auf dem Preischart
  • DrawLines — Aktivieren der Linien, die die Preise die Hochs/Tiefs des Indikators verbinden
  • ColBuy and ColSell — Farben der Grafikobjekte der Signale für Kauf und Verkauf.

Im Abschnitt 'protected' der Klasse CDiverBase sind die entsprechenden Variablen deklariert:

bool m_arrows;  // entspricht der Variablen ArrowsOnChart
bool m_lines;   // entspricht der Variablen DrawLines
color m_cbuy;   // entspricht der Variablen ColBuy
color m_csell;  // entspricht der Variablen ColSell

Die Werte dieser Variablen werden in der Methode SetDrawParmeters () bestimmt:

void SetDrawParmeters(bool arrows,bool lines,color cbuy,color csell){
   m_arrows=arrows;
   m_lines=lines;
   m_cbuy=cbuy;
   m_csell=csell;
}

Betrachten Sie die Methoden, die mit den grafischen Objekten arbeiten. Entfernen:

void DelObjects(datetime bartime){ // Aktivieren des Zeichnens der Linien
   if(m_lines){
      // Bilden des gemeinsamen Präfix
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      for(int j=0;j<m_ccnt;j++){ // für die Zahl der Bedingungen der Divergenzen
         ObjectDelete(0,pref+"bp_"+IntegerToString(j)); // Linie im Preischart im Falle eines Kaufsignals
         ObjectDelete(0,pref+"bi_"+IntegerToString(j)); // Linie im Indikatorfenster im Falles eines Kaufsignals
         ObjectDelete(0,pref+"sp_"+IntegerToString(j)); // Linie im Preischart im Falles eines Verkaufssignals 
         ObjectDelete(0,pref+"si_"+IntegerToString(j)); // Linie im Indikatorfenster im Falles eines Verkaufssignals 
      }            
   }
   if(m_arrows){ // Aktivieren der Pfeile auf dem Preischart
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_ba");
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa");
   }
}

Linien, die die Hochs/Tiefs verbinden, werden getrennt von den Pfeilen entfernt. Die Namen aller grafischen Objekte beginnen mit dem Indikatornamen, gefolgt vom Zeitstempel der Bar, an dem die Divergenz erkannt wurde. Die Namen werden in der Schleife entsprechend der Anzahl der m_ccnt-Bedingungen gebildet. Bei Kaufsignalen wird "bp" auf dem Preischart und "bi" im Indikatorfenster angezeigt. Ebenso werden für Verkaufssignale "sp" und "ip" hinzugefügt. Der Index j wird am Ende des Namens hinzugefügt. Den Pfeilnamen werden entweder "_ba" (Kaufsignalpfeil) oder "_sa" (Verkaufssignalpfeil) angehängt.

Die Erstellung von grafischen Objekten erfolgt in den Methoden DrawSellObjects() und DrawBuyObjects(). Betrachten wir eine davon:

void DrawSellObjects(datetime bartime,double arprice,int ucnt){
   if(m_lines){ // aktivieren der Anzeige der Linien
      
      // Bilden des gemeinsamen Präfix
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      
      for(int j=0;j<m_ccnt;j++){  // für alle Bedingungen der Divergenzen
                  
         // Linie auf dem Preischart
         fObjTrend(  pref+"sp_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].PriceValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].PriceValue,
                     m_csell);
                     
         // Linie im Indikatorfenster
         fObjTrend(  pref+"si_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].IndicatorValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].IndicatorValue,
                     m_csell,
                     ChartWindowFind(0,MQLInfoString(MQL_PROGRAM_NAME)));  
      }
   }
      
   if(m_arrows){ // Pfeile auf dem Chart sind aktiviert
      fObjArrow(MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa",
               bartime,
               arprice,
               234,
               m_csell,
               ANCHOR_LOWER); 
   }            
} 

Objektnamen werden gebildet so wie wieder gelöscht werden. Danach werden die grafischen Objekte mit den Funktionen fObjTrend() und fObjArrow() erzeugt. Sie befinden sich in der "Includedatei" UniDiver/UniDiverGObjects.mqh.

Fertigstellung des Indikators. Wir müssen nur die angelegten Klassen in den Indikator übernehmen. Erstellen Sie das entsprechende Objekt in der Funktion OnInit(), abhängig vom gewählten Typ der Definition der Extrema:

switch(ExtremumType){
   case ExtrBars:
      diver=new CDiverBars(LeftBars,RightBars);
   break;
   case ExtrThreshold:
      diver=new CDiverThreshold(MinMaxThreshold);
   break;      
   case ExtrMiddle:
      diver=new CDiverMiddle(Type);
   break;      
}

Einer der externen Parameter - PriceLevel - wird in Points gemessen, so dass es vorteilhaft ist, ihn in Abhängigkeit von der Anzahl der Nachkommastellen zu korrigieren. Deklarieren Sie dazu eine weitere Variable, um diese Korrektur abschalten zu können:

input bool                 Auto5Digits       =  true;

Dann wird eine Hilfsvariable für diese Korrektur in der Funktion OnInit() deklariert:

int pl=PriceLevel;   
if(Auto5Digits && (Digits()==5 || Digits()==3)){
   pl*=10;
}  

Festlegen der Parameter für die Divergenz und die Darstellung:

diver.SetConditions(Number,Point()*pl,IndLevel);
diver.SetDrawParmeters(ArrowsOnChart,DrawLines,ColBuy,ColSell);

Mehrere Zeichenketten verbleiben in der Funktion OnCalculate(). Aufruf der Basismethode Calculate():

diver.Calculate(  rates_total,
                  prev_calculated,
                  time,
                  high,
                  low,
                  buf_osc,
                  buf_buy,
                  buf_sell);

Falls grafischen Objekten verwendet werden, sollten wir das Zeichnen beschleunigen:

if(ArrowsOnChart || DrawLines){
   ChartRedraw();
}

Wenn der Indikator seine Arbeit beendet hat, sollten die grafischen Objekte entfernt werden. Dies geschieht im Destruktor der Klasse CDiverBase. Dort werden auch die Verifikationsobjekte der Divergenzbedingungen entfernt:

void ~CDiverBase(){
   for(int i=0;i<ArraySize(m_conditions);i++){ // für alle Bedingungen
      if(CheckPointer(m_conditions[i])==POINTER_DYNAMIC){
         delete(m_conditions[i]); // Lösche Objekt
      }      
   }  
   // Löschen der Grafikobjekte
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw();          
}   

Zu diesem Punkt ist die Hauptphase der Entwicklung des Indikators abgeschlossen. Abb. 15 zeigt der Chart mit dem dazugehörigen Indikator (im Subfenster), der aktivierten Anzeige von Pfeilen auf dem Kurschart und Linien zwischen den Spitzen. 


Abb. 15. Der Divergenzindikator auf dem Kurschart mit Pfeilen, die auf dem Kurs-Chart und der Verbindungslinie zwischen den Extrempunkten angezeigt werden.

Jetzt müssen wir nur noch die Alarmfunktion hinzufügen. Das ist sehr einfach und wurde bereits in anderen Artikeln beschrieben. In den folgenden Anlagen finden Sie ein fertigen Indikator mit Alarmfunktion sowie alle für den Indikator notwendigen Dateien.

Schlussfolgerung

Trotz der Vielseitigkeit des Indikators können wir auch Nachteile feststellen. Der Hauptnachteil ist die Abhängigkeit des Parameters IndLevel von dem Typus des verwendeten Oszillators, sowie die Abhängigkeit des Parameters PriceLevel vom Zeitrahmen. Um diese Abhängigkeit auszuschließen, sind die Standardwerte für diese Parameter 0. Gleichzeitig ist es aber fast unmöglich, die Bedingungen für bestimmte Kombinationen von Preis- und Indikatorbewegung zu erfüllen. Wird die Prüfung auf eine horizontale Bewegung in die Divergenzverifikation einbezogen, ist deren Durchführung unwahrscheinlich. In diesem Fall bleiben die Divergenzoptionen 1,3,7 und 9 erhalten. Dies kann ein Problem nur dann sein, wenn der Tester verwendet wird, um ein EA mit dem Indikator zu optimieren.

Bei der richtigen Vorgehensweise ist dies kein Problem, da die Optimierung in der Regel auf ein einziges Symbol und einen einzigen Zeitrahmen ausgerichtet ist. Zunächst müssen wir das verwendete Symbol und den Zeitrahmen bestimmen und den entsprechenden Wert für den Parameter PriceLevel einstellen. Dann müssen wir den angewandten Oszillator auswählen und den entsprechenden Wert für den Parameter IndLevel einstellen. Es hat keinen Sinn, eine automatische Optimierung des verwendeten Oszillatortyps und der Parameterwerte von PriceLevel und IndLevel anzustreben, da es viele weitere Optimierungsparameter gibt. Dies ist zunächst einmal die Art der Divergenz (die Variable 'Number') und die Periodenlänge des Oszillators.

Anlagen

Für die Verwendung von iDivergence benötigen Sie einen Universaloszillator aus dem Artikel "Der universell Oszillator mit dem graphischen Interface". Der Oszillator und alle notwendigen Dateien befinden sich in der Anwendung.

Alle beigefügte Dateien:

  • Include/InDiver/CUniDiverConditions.mqh — Datei mit den Klassen zur Prüfung der Bedingungen einer Divergenz;
  • Include/InDiver/CUniDiverExtremums.mqh — Datei mit den Klassen zur Definition der Extrema; 
  • Include/InDiver/UniDiverDefines.mqh — Beschreibung der Strukturen und Enumerationen;
  • Include/InDiver/UniDiverGObjects.mqh — Funktionen, die mit den Grafikobjekten arbeiten;
  • Indicators/iDivergence.mq5 — Indikator;
  • Indicators/iUniOsc.mq5 — Universaloszillator;
  • Include/UniOsc/CUniOsc.mqh — Datei mit den Klassen des Universaloszillators;
  • Include/UniOsc/UniOscDefines.mqh — Beschreibung der Strukturen und Enumerationen für den Universaloszillator.

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

Beigefügte Dateien |
MQL5.zip (13.96 KB)
Cross-Plattform Expert Advisor: Die Klassen CExpertAdvisor und CExpertAdvisors Classes Cross-Plattform Expert Advisor: Die Klassen CExpertAdvisor und CExpertAdvisors Classes
In diesem Artikel geht es in erster Linie um die Klassen CExpertAdvisor und CExpertAdvisors, die als Container für alle anderen in dieser Artikelserie beschriebenen Komponenten im Hinblick auf einen plattformübergreifende Expert Advisor dienen.
Cross-Plattform Expert Advisor: Eigene Stopps, Breakeven und Trailing Cross-Plattform Expert Advisor: Eigene Stopps, Breakeven und Trailing
Dieser Artikel beschreibt, wie nutzerdefinierte Stopps in einem plattformübergreifenden Expert Advisor eingerichtet werden können. Darüber hinaus wird eine eng verwandte Methode diskutiert, mit der das Nachziehen von Stopps für die Dauer einer Position entwickelt werden können.
Wir betrachten die adaptive Trendfolgemethode in der Praxis Wir betrachten die adaptive Trendfolgemethode in der Praxis
Das besondere Merkmal des im Artikel vorgestellten Handelssystems besteht in der Verwendung mathematischer Werkzeuge für die Analyse von Börsenkursen. Im System werden digitale Filter und die Spektralschätzung diskreter Zeitreihen verwendet. Es werden theoretische Aspekte der Strategie beschrieben und ein Expert Advisor für das Testen der Strategie erstellt.
Universeller Expert Advisor: CUnIndicator und das Arbeiten mit Pending Orders (Teil 9) Universeller Expert Advisor: CUnIndicator und das Arbeiten mit Pending Orders (Teil 9)
Der Artikel beschreibt das Arbeiten mit Indikatoren anhand der universellen Klasse CUnIndicator. Darüber hinaus wurden im Artikel neue Arbeitsmethoden mit Pending Orders betrachtet. Bitte beachten Sie, dass die Struktur des CStrategy Projektes wesentlich verändert wurde. Jetzt sind alle seine Dateien in einem einheitlichen Verzeichnis für die Bequemlichkeit der Nutzer abgelegt.