Ein universeller RSI-Indikator für das gleichzeitiges Arbeiten in beiden Richtungen

Anatoli Kazharski | 25 September, 2018

Inhalt

Einführung

Bei der Entwicklung von Handelsalgorithmen stoßen wir oft auf ein Problem: Wie kann man feststellen, wo ein Trend bzw. eine Seitwärtsbewegung beginnt und endet? Es ist schwierig, eine klare Lösung zu finden. Dieses Ziel scheint erreichbar zu sein, wenn man beide, die Trend- und Seitwärts-basierten Strategien, in einem Algorithmus kombiniert. In diesem Artikel werden wir einen universellen Indikator erstellen, der Signale für verschiedene Arten von Strategien kombiniert. Wir werden versuchen, die Generierung von Handelssignalen in einem Experten so weit wie möglich zu vereinfachen. Ein Beispiel für die Kombination mehrerer Indikatoren in einem einzigen wird ausgearbeitet. Dies geschieht mit Hilfe der objektorientierten Programmierung, wobei jeder Indikator oder sein Teil in Form einer Klasse in der Hauptprogrammdatei implementiert wird. 

Die Aufgabe besteht also darin, einen Experten zu schreiben, der zwei Handelsstrategien kombiniert: Eine für den Trend-, die andere für den Seitwärtshandel. Angenommen, der gleichzeitige Handel in zwei Richtungen ist effektiver, und eine solche Strategie wird stabiler sein. Dann ist es bequemer, nur einen Indikator in einen Experten aufzunehmen, der Signale für beide Strategiearten erzeugt. In ihm können Sie ein komplexes System zur Bestimmung von Kauf- und Verkaufssignalen implementieren. Es kann notwendig sein, mehrere identische Indikatoren mit unterschiedlichen Einstellungen zu einem einzigen zu kombinieren. Oder sogar zwei verschiedene in einen Indikator zu integrieren: Hauptanzeige und Filterung (Hilfsanzeige). Es ist bequem, solche Systeme mit Hilfe von OOP zu implementieren.

Das folgende Schema zeigt die Aufnahme von zwei Indikatorklassen (CIndicator1 und CIndicator2) in die Hauptindikator (Indicator.mq5). CIndicator2 ist ein Hilfsindikator, seine Berechnungsergebnisse sind notwendig für CIndicator1. Hier wenden wir die Methode zur Bestimmung des echten Balkens an, die zuvor im Artikel über die Erstellung von Multi-Symbol-Indikatoren diskutiert wurde. Für diesen Artikel wurde eine separate Klasse CFirstTrueBar geschrieben. Sie wird in alle Indikatoren eingebunden, um die Berechnung von Balken zu vermeiden, die nicht zum aktuellen Zeitrahmen gehören.

 Abb. 1. Ein mögliches Schema, um mittels OOP einen Indikator zu erstellen.

Abb. 1. Ein mögliches Schema, um mittels OOP einen Indikator zu erstellen.


Auswahl der Indikatoren

Für die Signalerzeugung kann jeder Indikator aus dem Standardpaket des Terminals ausgewählt werden. Die meisten von ihnen verfolgen ähnliche Ideen, und jeder von ihnen ist in der Regel nicht besonders vorteilhaft gegenüber den anderen. Einige Kombinationen von Indikatoren und Filtern sind in bestimmten Zeitintervallen wirksam, während sich andere in anderen Intervallen als nützlich erweisen.

Aber für die Bequemlichkeit der Analyse ist es besser, Indikatoren in Form von Oszillatoren auszuwählen. Die können zum Bestimmen von Signalen sowohl während eines Trends als auch in einer Seitwärtsbewegung verwendet werden. Die Oszillatordaten können auch für die Darstellung eines Preiskanals verwendet werden. Auf diese Weise erhalten wir die Möglichkeit, einen universellen Indikator zu erstellen, der bei der Entwicklung von Handelsstrategien beliebiger Komplexität hilfreich ist.

Der Indikator RSI (Relative Strength Index) wird in dieser Artikelserie als Beispiel verwendet. Nachfolgend sind die Berechnungsergebnisse dieses Indikators mit einer Periodenlänge von 8 auf dem Chart AUDUSD H1 aufgeführt. 

 Abb. 2. Der Indikator Relative Strength Index.

Abb. 2. Der Indikator Relative Strength Index.

Auf den ersten Blick erscheint es einfach, auf Basis der Signale dieses Indikators Gewinne zu erzielen. Aber das ist eine Illusion. Bevor Sie eine neue Ebene des ungefähren Verständnisses dafür erreichen, wohin es als nächstes gehen soll, muss viel Arbeit geleistet werden. Gleichzeitig gibt es keine Garantie, dass die Ziele erreicht werden. 

Betrachten Sie den einfachsten und offensichtlichsten Fall: Wir denken, dass ein Gewinn erzielt werden kann, wenn die Indikatorlinie die Standardwerte überschreitet: 70/30. Wenn das Niveau 70 nach unten gekreuzt wird, gilt es als Verkaufssignal. Wird das Niveau 30 nach oben überschritten, gilt es als Kaufsignal. Allerdings sehen wir viele falsche Signale, während der Preis in die entgegengesetzte Richtung zur geöffneten Position geht. 

Hier ist ein weiteres Beispiel für die Analyse der Signale dieses Indikators (siehe Abbildung 3). Wir sehen, dass der Preis für eine längere Zeit nach unten geht. Rote Linien markieren die Signale, die sich bilden, wenn die Anzeige den Pegel 30 nach oben überschreitet. Wenn Ihr Algorithmus den Kauf auf der Grundlage dieser Signale durchführt, entsteht einen schwebender Drawdown. Wenn Sie bei jeder Eröffnung einer Position einen Stop-Loss setzen, entstehen die Verluste mehrfach. Gleichzeitig hat der Kurs seit dem letzten Kaufsignal bis zum Verkaufssignal (grüne Linie) nie ein positives Ergebnis erreicht. Ihr Ergebnis ist ein Verlust. Auch dieses Segment des Charts deutet darauf hin, dass der Handel im Trend verläuft, was bedeutet, dass wir nichts Eindeutiges sehen.

Ähnliche Probleme treten bei der Verwendung jedes Indikators auf. Es spielt daher keine Rolle, welcher von ihnen für die weitere Arbeit ausgewählt wird.

 Abb. 3. Signale des RSI Indikators.

Abb. 3. Signale des RSI Indikators.


Änderung des RSI-Indikators

Der ausgewählte Indikator sollte erweitert werden, um eine spätere Zusammenarbeit mit einem Experten zu erleichtern. Wir erstellen fünf Versionen des RSI, die sukzessive komplexer werden (zum besseren Verständnis).

Die erste Version. Hinzufügen von Signalpuffern

Die Standardversion des RSI befindet sich im Verzeichnis \MQL5\Indicators\Examples des Terminals. Machen wir eine Kopie davon und beginnen mit der Änderung. Wir erweitern die Liste der festen Parameter um zwei weitere Indikatorpuffer. Ihre Gesamtzahl ist ebenfalls gleich 5, und 3 werden im Chart angezeigt. Zwei Puffer bleiben für Hilfsrechnungen reserviert. Die Markierung der Kaufsignale sind grün (clrMediumSeaGreen), rot (clrRed) die der Verkaufssignale. 

//--- Eigenschaften
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_buffers 5
#property indicator_plots   3
#property indicator_color1  clrSteelBlue
#property indicator_color2  clrMediumSeaGreen
#property indicator_color3  clrRed

Definitionen der Signalmarkierungen. Wenn Punkte angezeigt werden sollen, der Code wäre 159. Sollen es aber Pfeile sein, wäre der Code 233 bzw. 234.

//--- Signalmarkierung: 159 - Punkte; 233/234 - Pfeile
#define ARROW_BUY  159
#define ARROW_SELL 159

Die Schnittpunkte der Indikatorlinien können sowohl als Kauf- als auch als Verkaufssignale dienen. Daher erfordert der externe Parameter eine Enumeration, mit der festgelegt werden kann, wie die Indikatorsignale zu interpretieren sind.

Alle diese Modi werden in der folgenden Tabelle dargestellt.

//--- Enumeration der Modi für das Kreuzen der Kanalgrenzen
enum ENUM_BREAK_INOUT
  {
   BREAK_IN          =0, // Break in
   BREAK_IN_REVERSE  =1, // Break in reverse
   BREAK_OUT         =2, // Break out
   BREAK_OUT_REVERSE =3  // Break out reverse
  };

Der Indikator wird diese drei externen Parameter insgesamt haben:

//--- Eingabeparameter
input  int              PeriodRSI   =8;         // RSI Periodenlänge
input  double           SignalLevel =30;        // Signal Level
input  ENUM_BREAK_INOUT BreakMode   =BREAK_OUT; // Modus des Ausbruchs

Die Eigenschaften des Indikators werden in der Funktion SetPropertiesIndicator() festgelegt. Die Hilfsarrays werden zuletzt eingerichtet. Alle Indikatorarrays werden in der Funktion ZeroIndicatorBuffers() mit Null initialisiert. Dann geben wir an, dass Null nicht auf dem Diagramm angezeigt werden sollen, was bedeutet, dass diese Werte leer sind.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Kurzname
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS1");
//--- Dezimalstellen
   ::IndicatorSetInteger(INDICATOR_DIGITS,2);
//--- Indikatorarrays
   ::SetIndexBuffer(0,rsi_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(4,neg_buffer,INDICATOR_CALCULATIONS);
//--- Initialisierung der Arrays
   ZeroIndicatorBuffers();
//--- Setzen der Kennzeichnung
   string plot_label[]={"RSI","buy","sell"};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetString(i,PLOT_LABEL,plot_label[i]);
//--- Setzen der Weite des Indikatorarrays
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_LINE_WIDTH,1);
//--- Setzen des Typs des Indikatorarrays
   ENUM_DRAW_TYPE draw_type[]={DRAW_LINE,DRAW_ARROW,DRAW_ARROW};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]);
//--- Kennziffer der Kennzeichnung
   ::PlotIndexSetInteger(1,PLOT_ARROW,ARROW_BUY);
   ::PlotIndexSetInteger(2,PLOT_ARROW,ARROW_SELL);
//--- Index des Elements, bei dem die Berechnung beginnt
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_BEGIN,period_rsi);
//--- Anzahl der horizontalen Ebenen des Indikators
   ::IndicatorSetInteger(INDICATOR_LEVELS,2);
//--- Werte der horizontalen Ebenen des Indikators
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,0,down_level);
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,1,up_level);
//--- Linientyp
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,STYLE_DOT);
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,STYLE_DOT);
//--- "Empty-Value" die nicht gezeichnet wird
   for(int i=0; i<indicator_buffers; i++)
      ::PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,0);
//--- Versatz entlang der Y-Achse
   if(BreakMode==BREAK_IN_REVERSE || BreakMode==BREAK_OUT_REVERSE)
     {
      ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,arrow_shift);
      ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,-arrow_shift);
     }
   else
     {
      ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-arrow_shift);
      ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,arrow_shift);
     }
  }

Zur Vereinfachung werden Vor- und Hauptberechnungen der RSI Indikatorwerte in die separate Funktionen PreliminaryCalculations() und CalculateRSI() verschoben. Ihr Inhalt ist derselbe wie der des Indikators RSI des Standardpakets. Betrachten wir nur die Funktion zum Bestimmen der Indikatorsignale - CalculateSignals(). Hier werden zunächst die Bedingungen überprüft, abhängig von dem in den externen Parametern festgelegten Modus. Wenn die Bedingungen erfüllt sind, wird dann der Indikatorwert RSI in der entsprechenden Indikatoranordnung gespeichert. Wenn eine Bedingung nicht erfüllt ist, wird Null gespeichert, ein Wert, der nicht in der Grafik angezeigt wird.

//+------------------------------------------------------------------+
//| Berechnen der Indikatorsignale                                   |
//+------------------------------------------------------------------+
void CalculateSignals(const int i)
  {
   bool condition1 =false;
   bool condition2 =false;
//--- Eindringen in den Kanal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level;
      condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level;
     }
   else
     {
      condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level;
      condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level;
     }
//--- Signal anzeigen, wenn die Bedingungen erfüllt sind fulfilled
   if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT)
     {
      buy_buffer[i]  =(condition1)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition2)? rsi_buffer[i] : 0;
     }
   else
     {
      buy_buffer[i]  =(condition2)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition1)? rsi_buffer[i] : 0;
     }
  }

Im Ergebnis schaut der Code die Funktionen des Hauptindikators, wie OnInit() und OnCalculate(), klar und lesbar aus.

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des nutzerdefinierten Indikators        |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Prüfen der externen Parameterwerte
   if(PeriodRSI<1)
     {
      period_rsi=2;
      Print("Incorrect value for input variable PeriodRSI =",PeriodRSI,
            "Indicator will use value =",period_rsi,"for calculations.");
     }
   else
      period_rsi=PeriodRSI;
//--- Setze der Indikatoreinstellungen
   SetPropertiesIndicator();
  }
//+------------------------------------------------------------------+
//| Relative Strength Index                                          |
//+------------------------------------------------------------------+
int OnCalculate(const int      rates_total,
                const int      prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int      &spread[])
  {
//--- Verlassen, wenn die Daten nicht ausreichen
   if(rates_total<=period_rsi)
      return(0);
//--- Vorberechnungen
   PreliminaryCalculations(prev_calculated,close);
//--- Schleife der Berechnungen
   for(int i=start_pos; i<rates_total && !::IsStopped(); i++)
     {
      //--- Berechnen des RSI
      CalculateRSI(i,close);
      //--- Berechnen des Signals
      CalculateSignals(i);
     }
//--- Rückgabe der berechneten Nummer des Elements
   return(rates_total);
  }

Starten wir den Indikator auf dem Chart und schauen mal, was passiert. Unten sieht man das Ergebnis aller vier Arbeitsmodi (externer Parameter Break Mode).

 Abb. 4. Demonstration des modifizierten RSI.

Abb. 4. Demonstration des modifizierten RSI.

Nach dieser Modifikation des Indikators RSI ist die Anzahl der von ihm erzeugten Fehlsignale besser zu erkennen. Aus der Analyse des Charts schließen wir, dass dieser Indikator manchmal besser für einen Seitwärtshandel und manchmal während eines Trends ist. 

Können die Signale dieses Indikators nur in einer Seitwärtsbewegung oder nur während eines Trends gehandelt werden? Wie kann die Wahrscheinlichkeit positiver Ergebnisse und die Genauigkeit der Einträge erhöht werden? Es ist durchaus möglich, dass, wenn die Position nicht durch das erste Signal in einer kontinuierlichen Serie geöffnet wird, das Ergebnis verbessert werden kann.

Die zweite Version. Hinzufügen von Puffern für Signalzähler

Versuchen wir, Indikatorpuffer hinzuzufügen, in denen die Anzahl der kontinuierlichen Signale in einer Richtung angezeigt wird. Sobald ein entgegengesetztes Signal erscheint, wird der Zähler des vorherigen Puffers auf Null gesetzt und der Zähler für die aktuelle Signalreihe aktiviert. Für die Umsetzung ergänzen wir den Code etwas.

Es gibt Änderungen bei bestimmten Parametern. Wir bestimmen eine neue Anzahl von Puffern, Serien für das Zeichnen und legen die Farbe für diese fest.

//--- Eigenschaften
...
#property indicator_buffers 7
#property indicator_plots   5
...
#property indicator_color4  clrMediumSeaGreen
#property indicator_color5  clrRed

Auf globaler Ebene ergänzen wir zwei weitere Arrays zur Darstellung der Werte der Zähler und zwei entsprechende Hilfsvariablen:

//--- Indikatorarrays
...
double buy_counter_buffer[];
double sell_counter_buffer[];
//--- Zähler der Reihe der kontinuierlichen Signale
int buy_counter  =0;
int sell_counter =0;

Die restlichen Änderungen betreffen nur die Funktion zum Bestimmen der Signale. Sind die Bedingungen erfüllt (das nächste Kauf- oder Verkaufssignal erscheint), wird hier ein entsprechender Zähler ausgelöst, während der Zähler für die entgegengesetzte Signalreihe zurückgesetzt wird. Um zu vermeiden, den Zähler auf dem aktuellen Balken zu erhöhen, ist es notwendig, sein Auslösen während des letzten unvollständigen Balken auszusetzen.

//+------------------------------------------------------------------+
//| Berechnen der Indikatorsignale                                   |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Eindringen in den Kanal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level;
      condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level;
     }
   else
     {
      condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level;
      condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level;
     }
//--- Signal anzeigen, wenn die Bedingungen erfüllt sind fulfilled
   if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT)
     {
      buy_buffer[i]  =(condition1)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition2)? rsi_buffer[i] : 0;
      //--- Zähler nur der vollständigen Balken
      if(i<last_index)
        {
         if(condition1)
           {
            buy_counter++;
            sell_counter=0;
           }
         else if(condition2)
           {
            sell_counter++;

            buy_counter=0;
           }
        }
     }
   else
     {
      buy_buffer[i]  =(condition2)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition1)? rsi_buffer[i] : 0;
      //--- Zähler nur der vollständigen Balken
      if(i<last_index)
        {
         if(condition2)
           {
            buy_counter++;
            sell_counter=0;
           }
         else if(condition1)
           {
            sell_counter++;
            buy_counter=0;
           }
        }
     }
//--- Korrektur des letzten Wertes (gleich dem Vorherigen)
   if(i<last_index)
     {
      buy_counter_buffer[i]  =buy_counter;
      sell_counter_buffer[i] =sell_counter;
     }
   else
     {
      buy_counter_buffer[i]  =buy_counter_buffer[i-1];
      sell_counter_buffer[i] =sell_counter_buffer[i-1];
     }
  }

Nachdem die Änderungen vorgenommen wurden, starten wir den Indikator auf einem Chart und schauen, was passiert. Nun ist der Indikator RSI informativer. Die Werte dieser Zähler können von einem Expert Advisor abgerufen werden, um zusätzliche Bedingungen für den Kauf und Verkauf zu schaffen.

 Abb. 5. Modifizierte RSI-Indikator mit Zählern kontinuierlicher Signale in die gleiche Richtung.

Abb. 5. Modifizierte RSI-Indikator mit Zählern kontinuierlicher Signale in die gleiche Richtung.

Um sicherzustellen, dass alles wie vorgesehen funktioniert, überprüfen wir den Indikator sowohl im Tester als auch in Echtzeit. Das Ergebnis des Strategie-Testers:

 Abb. 6. Überprüfung der Funktion des modifizierten RSI-Indikators im Strategie-Tester.

Abb. 6. Überprüfung der Funktion des modifizierten RSI-Indikators im Strategie-Tester.

Die nächste Abbildung unten zeigt die Indikatorwerte aller Arbeitsmodi des Parameters Break Mode.

 Abb. 7. Der Indikator mit allen Arbeitsmodi des Parameters Break Mode.

Abb. 7. Der Indikator mit allen Arbeitsmodi des Parameters Break Mode.

Es kann sich eine Situation auf dem Chart ergeben, in der sich der Kurs zwischen zwei ähnlichen Signalen über einen relativ großen Abstand bewegen kann. Dies geschieht sehr oft und zu jedem Zeitpunkt. Gleichzeitig können Sie den externen Parameter Signal Level beliebig ändern und die Bereichsgrenzen konfigurieren - aber das löst das Problem nicht. Diese Unsicherheit kann die Schaffung einer klaren Handelslogik beeinträchtigen oder die Notwendigkeit, zusätzliche Bedingungen zu schreiben, erschweren. 

 Abb. 8. Ignorierte Signale trotz signifikanter Preisbewegungen.

Abb. 8. Ignorierte Signale trotz signifikanter Preisbewegungen.

In der nächsten Version eliminieren wir das Auslassen und machen den Indikator noch informativer.  

Die dritte Version. Erhöhung der Anzahl der Signale, Vermeiden des Auslassens

Aber diesmal bleibt die Anzahl der Indikatorpuffer gleich. Aber wir müssen Arrays für die Indikatorlevel ergänzen, die für die Signale, die beim Kreuzen der Linien entstehen, verwendet werden.  

//--- Werte der horizontalen Ebenen des Indikators und deren Werte
double up_level          =0;
double down_level        =0;
int    up_levels_total   =0;
int    down_levels_total =0;
//--- Arrays der horizontalen Ebenen 
double up_levels[];
double down_levels[];

Um das Auslassen von Signalen zu vermeiden, wie im vorherigen Abschnitt erwähnt, werden die Ebenen auf alle 5 Points eingestellt. Das heißt, wenn im externen Parameter Signal Level ein Wert von 30 angegeben ist, werden für die oberen Stufen folgende Werte berechnet: 70, 75, 80, 85, 90, 95. 

Die Level der Indikatoren werden mit der Funktion GetLevelsIndicator() berechnet. Zwei getrennte Schleifen berechnen die Werte der Level, die in den Arrays eingetragen werden sollen. Die Funktion gibt die Gesamtzahl der Ebenen zurück.

//+------------------------------------------------------------------+
//| Rückgabe der Indikatorebenen                                     |
//+------------------------------------------------------------------+
int GetLevelsIndicator(void)
  {
   int levels_counter=0;
   double level=down_level;
//--- Untere Ebenen bis zum unteren Grenze
   while(level>0 && !::IsStopped())
     {
      int size=::ArraySize(down_levels);
      ::ArrayResize(down_levels,size+1);
      down_levels[size]=level;
      level-=5;
      levels_counter++;
     }
   level=up_level;
//--- Obere Ebenen bis zur Obergrenze
   while(level<100 && !::IsStopped())
     {
      int size=::ArraySize(up_levels);
      ::ArrayResize(up_levels,size+1);
      up_levels[size]=level;
      level+=5;
      levels_counter++;
     }
//---
   return(levels_counter);
  }

Die Ebenen werden in der Funktion SetPropertiesIndicator() ermittelt. Die verkürzte Version ist unten dargestellt. Hier werden zunächst die Anfangswerte für den oberen und unteren Bereich berechnet und die Arrays der Ebenen auf Null gesetzt. Dann wird die Gesamtzahl der Indikatorebenen durch Aufruf der Funktion GetLevelsIndicator() errechnet. Danach werden die berechneten Pegel des oberen und unteren Bereichs aus den Arrays ermittelt.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Berechnung der ersten Ebene
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
//--- Freigabe der Arrays der Ebenen
   ::ArrayFree(up_levels);
   ::ArrayFree(down_levels);
//--- Anzahl der horizontalen Ebenen des Indikators
   ::IndicatorSetInteger(INDICATOR_LEVELS,GetLevelsIndicator());
//--- Werte der unteren horizontalen Ebenen des Indikators
   down_levels_total=::ArraySize(down_levels);
   for(int i=0; i<down_levels_total; i++)
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,down_levels[i]);
//--- Werte der obere horizontalen Ebenen des Indikators<
   up_levels_total=::ArraySize(up_levels);
   int total=up_levels_total+down_levels_total;
   for(int i=down_levels_total,k=0; i<total; i++,k++)
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,up_levels[k]);
...
  }

Dementsprechend ist es notwendig, Änderungen in der Funktion CalculateSignals() vorzunehmen. Auch hier wird nur der geänderte Teil der Funktion angezeigt. Um zu überprüfen, ob die Bedingungen in den Zyklen erfüllt sind, überprüfen wir, ob mindestens eine der Ebenen in den Arrays überschritten ist. 

//+------------------------------------------------------------------+
//| Berechnen der Indikatorsignale                                   |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Eindringen in den Kanal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      if(rsi_buffer[i]<50)
        {
         for(int j=0; j<down_levels_total; j++)
           {
            condition1=rsi_buffer[i-1]<down_levels[j] && rsi_buffer[i]>down_levels[j];
            if(condition1)
               break;
           }
        }
      //---
      if(rsi_buffer[i]>50)
        {
         for(int j=0; j<up_levels_total; j++)
           {
            condition2=rsi_buffer[i-1]>up_levels[j] && rsi_buffer[i]<up_levels[j];
            if(condition2)
               break;
           }
        }
     }
   else
     {
      for(int j=0; j<up_levels_total; j++)
        {
         condition1=rsi_buffer[i-1]<up_levels[j] && rsi_buffer[i]>up_levels[j];
         if(condition1)
            break;
        }
      //---
      for(int j=0; j<down_levels_total; j++)
        {
         condition2=rsi_buffer[i-1]>down_levels[j] && rsi_buffer[i]<down_levels[j];
         if(condition2)
            break;
        }
     }
//--- Signal anzeigen, wenn die Bedingungen erfüllt sind fulfilled
...
//--- Korrektur des letzten Wertes (gleich dem Vorherigen)
...
  }

Abbildung 9 zeigt, wie es aussieht. 

 Abb. 9. Signalbildung beim Kreuzen der mehrerer Ebenen.

Abb. 9. Signalbildung beim Kreuzen der mehrerer Ebenen.

Ein Problem ist gelöst, aber zwei weitere sind aufgetreten. Das erste liegt in der Notwendigkeit, die Signale auszuschließen, bei denen sich herausstellt, dass der Preis höher als das vorherige Kaufsignal oder unter dem vorherigen Verkaufssignal liegt. Abbildung 10 zeigt eine solche Situation für eine Reihe von Kaufsignalen: Der Preis beim letzten Signal liegt über dem Preis des vorherigen Signals. Dies ist relevant für die Modi Break in und Break out reverse, wo es notwendig ist, sich an das Konzept des Kaufens des Tiefs und des Verkaufs des Hochs zu halten.

 Abb. 10. Die Situation, in der der Signalwert höher als der vorherige Signalwert ist.

Abb. 10. Die Situation, in der der Signalwert höher als der vorherige Signalwert ist.

Das zweite Problem sind zu häufige Signale, manchmal sogar auf mehreren Balken in Folge. In diesem Fall existiert nur ein unbedeutender Abstand zwischen den Signalwerten (siehe Abb. 11).

 Abb. 11. Häufung von Signalen mit zu geringem Abstand.

Abb. 11. Häufung von Signalen mit zu geringem Abstand.

Das Problem löst die nächste Version.

Die vierte Version. Verschieben des Indikators in das Fenster des Hauptcharts

Diese Version verschiebt den Indikator in das Hauptchart. Die Funktionsweise dieses Indikators wird hier besser visualisiert: Die Signale werden direkt auf dem Preis angezeigt. Um den Abstand zwischen den Signalen (in Points) zu steuern, kann man einfach einen festen Wert in einem externen Parameter angeben. Wir werden jedoch versuchen, eine dynamische Variante zu erstellen und diesen Wert an den Volatilitätsindikator (ATR) zu binden. Aus Gründen der Übersichtlichkeit werden die Berechnungen für die Indikatoren in separaten Klassen durchgeführt: CATR und CRsiPlus. Mit dieser Methode ist es möglich, beliebig viele Indikatoren zu kombinieren und deren Berechnungsergebnisse in einem Programm zu kombinieren. 

Bestimmen eines echten Balkens

Es wird davon ausgegangen, dass diese Version in Zukunft zur Entwicklung eines Expert Advisors verwendet wird. Um den Einfluss der höheren Zeitrahmen in historischen Daten zu eliminieren, werden wir daher den wahren Balken bestimmen, wenn die Balken des aktuellen Zeitrahmens nicht ausreichend sind. Dies wurde im Artikel über mehrwährungsfähige Indikatoren ausführlich beschrieben. Um den ersten echten Balken zu bestimmen, schreiben wir die separate Klasse CFirstTrueBar. Werfen wir zunächst einen genaueren Blick auf diese Klasse.

Die Definitionen der Klassenmitgliederund Methoden der CFirstTrueBar sind nachfolgend dargestellt. Gehen wir kurz darauf ein. 

//+------------------------------------------------------------------+
//| Klasse zur Bestimmung der echten Bar                             |
//+------------------------------------------------------------------+
class CFirstTrueBar
  {
private:
   //--- Zeit des echten Balkens
   datetime          m_limit_time;
   //--- Nummer des echten Balkens
   int               m_limit_bar;
   //---
public:
                     CFirstTrueBar(void);
                    ~CFirstTrueBar(void);
   //--- Rückgabe (1) der Zeit und (2) des Index des echten Balkens
   datetime          LimitTime(void) const { return(m_limit_time); }
   int               LimitBar(void)  const { return(m_limit_bar);  }
   //--- Bestimmen des echten Balkens
   bool              DetermineFirstTrueBar(void);
   //---
private:
   //--- Suche nach dem ersten echten Balken der aktuellen Periode
   void              GetFirstTrueBarTime(const datetime &time[]);
  };

Die private Methode CFirstTrueBar::GetFirstTrueBarTime() wird verwendet, um nach den echten Balken zu suchen. Es sollte ein Array der Zeiten der historischen Balken übergeben werden, um nach dem ersten echten Balken zu suchen. Wir iterieren dann vom Anfang des Arrays und, sobald ein Balken entsprechend dem aktuellen Zeitrahmen gefunden wurde, speichern wir die Zeit und den Index dieses Balkens. Wenn der echte Balken identifiziert wird, kann seine Zeit und sein Index mit den Methoden CFirstTrueBar::LimitTime() und CFirstTrueBar::LimitBar() ermittelt werden.

//+------------------------------------------------------------------+
//| Suche nach dem ersten echten Balken der aktuellen Periode        |
//+------------------------------------------------------------------+
void CFirstTrueBar::GetFirstTrueBarTime(const datetime &time[])
  {
//--- Abfrage der Arraygröße
   int array_size=::ArraySize(time);
   ::ArraySetAsSeries(time,false);
//--- Prüfen jedes Balkens
   for(int i=1; i<array_size; i++)
     {
      //--- Wenn der Balken dem aktuellen Zeitrahmen entspricht
      if(time[i]-time[i-1]==::PeriodSeconds())
        {
         //--- Sichern und Beenden der Schleife
         m_limit_time =time[i-1];
         m_limit_bar  =i-1;
         break;
        }
     }
  }

Die Methode CFirstTrueBar::GetFirstTrueBarTime() wird in der Methode CFirstTrueBar::DetermineFirstTrueBar() aufgerufen. Hier wird das Array der Zeiten der Balken erhalten, mit dem später nach dem ersten echten Balken gesucht wird.

//+------------------------------------------------------------------+
//| Ermitteln des ersten echten Balkens                              |
//+------------------------------------------------------------------+
bool CFirstTrueBar::DetermineFirstTrueBar(void)
  {
//--- Array der Zeiten der Balken
   datetime time[];
//--- Abfrage der Gesamtzahl der Balken für das Symbol
   int available_bars=::Bars(_Symbol,_Period);
//--- Kopieren des Zeitarrays der Balken If this action failed, try again.
   if(::CopyTime(_Symbol,_Period,0,available_bars,time)<available_bars)
      return(false);
//--- Abfrage der der Zeit des ersten echten Balkens gemäß dem aktuellen Zeitrahmen
   GetFirstTrueBarTime(time);
   return(true);
  }

Hinzufügen des ATR-Indikators

Der Indikator ATR wird wie im Standardpaket berechnet. Der Code wird von hier übernommen: \MQL5\Indikatoren\Beispiele. Nachfolgend die Deklarationen von Mitgliedern und Methoden der Klasse CATR. Der einzige Unterschied zur Standardversion besteht darin, dass hier der erste echte Balken bestimmt wird, von dem aus die Berechnungen beginnen.

Wir binden die Datei mit der Klasse CFirstTrueBar ein und deklarieren ihre Instanz im Hauptteil der Klasse CATR. Bitte beachten Sie, dass die Indikator-Arrays hier mit 'public' deklariert wurden. Dies ist notwendig, um sie als Indikatorpuffer in der Hauptindikatorendatei verwenden zu können.

//+------------------------------------------------------------------+
//|                                                          ATR.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
//+------------------------------------------------------------------+
//| The ATR indicator                                                |
//+------------------------------------------------------------------+
class CATR
  {
private:
   //--- Bestimmen des echten Balkens
   CFirstTrueBar     m_first_true_bar;
   //--- Periodenlänge des Indikators
   int               m_period;
   //--- Beschränkung der Berechnung der Indikatorwerte
   int               m_limit;
   //---
public:
   //--- Indikatorpuffer
   double            m_tr_buffer[];
   double            m_atr_buffer[];
   //---
public:
                     CATR(const int period);
                    ~CATR(void);
   //--- Periodenlänge des Indikators
   void              PeriodATR(const int period) { m_period=period; }
   //--- Berechnen des ATR 
   bool              CalculateIndicatorATR(const int rates_total,const int prev_calculated,const datetime &time[],const double &close[],const double &high[],const double &low[]);
   //--- Indikatorpuffer nullifizieren
   void              ZeroIndicatorBuffers(void);
   //---
private:
   //--- Vorberechnungen
   bool              PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[]);
   //--- Berechnen des ATR 
   void              CalculateATR(const int i,const datetime &time[],const double &close[],const double &high[],const double &low[]);
  };

Der erste echte Balken, aus dem der Indikator weiter berechnet wird, wird in der Methode der Vorberechnung bestimmt. Kann er nicht ermittelt werden, verlässt das Programm die Methode.

//+------------------------------------------------------------------+
//| Vorberechnung                                                    |
//+------------------------------------------------------------------+
bool CATR::PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[])
  {
//--- bei erster Berechnung oder nach Änderungen
   if(prev_calculated==0)
     {
      //--- Ermitteln der Anzahl der echten Balken
      m_first_true_bar.DetermineFirstTrueBar();
      //--- Verlassen, wenn kein echter Balken identifiziert werden konnte
      if(m_first_true_bar.LimitBar()<0)
         return(false);
      //---
      m_tr_buffer[0]  =0.0;
      m_atr_buffer[0] =0.0;
      //--- Balken, ab dem gerechnet wird
      m_limit=(::Period()<PERIOD_D1)? m_first_true_bar.LimitBar()+m_period : m_period;
      //--- Verlassen bei einem Überlauf (ungenügende Balken)
      if(m_limit>=rates_total)
         return(false);
      //--- Berechnen der Werte des ATR
      int start_pos=(m_first_true_bar.LimitBar()<1)? 1 : m_first_true_bar.LimitBar();
      for(int i=start_pos; i<m_limit && !::IsStopped(); i++)
         m_tr_buffer[i]=::fmax(high[i],close[i-1])-::fmin(low[i],close[i-1]);
      //--- Die ersten Werte des ATR werden nicht berechnet
      double first_value=0.0;
      for(int i=m_first_true_bar.LimitBar(); i<m_limit; i++)
        {
         m_atr_buffer[i]=0.0;
         first_value+=m_tr_buffer[i];
        }
      //--- Berechnen des ersten Wertes
      first_value/=m_period;
      m_atr_buffer[m_limit-1]=first_value;
     }
   else
      m_limit=prev_calculated-1;
//---
   return(true);
  }

Signalfilterung im RSI-Indikator

Zusätzlich zu den oben beschriebenen Indikatorpuffern wird diese Version des RSI zwei weitere haben. Diese werden kontinuierliche Ebenen sein, die separat basierend auf den Preisen der Kauf- und Verkaufssignale unter Berücksichtigung der Spreads dargestellt werden. Um die Daten des Indikators ATR in die Berechnungen einbeziehen zu können, ist es notwendig, den Pointer auf die Instanz der in der Hauptprogrammdatei erstellten Klasse ATR zu erhalten. Daher werden hier ein Pointer des Typs CATR und die entsprechenden Methoden zum Erhalten und Setzen deklariert.

Um den Code zu optimieren, sind einige seiner Blöcke nun als separate Methoden implementiert. Dazu gehören die Überprüfung der Bedingungen, das Arbeiten mit Zählern, etc. Die einzige neue Methode, die bisher nicht berücksichtigt wurde, ist CRsiPlus::DirectionControl(). Es ist das Verfahren, bei dem die Bewegungsrichtung gesteuert wird und die übermäßigen Signale basierend auf der aktuellen Volatilität herausgefiltert werden. Zusätzlich gibt es Hilfsmethoden zum Entfernen unnötiger Signale - CRsiPlus::DeleteBuySignal() und CRsiPlus::DeleteSellSignal().

//+------------------------------------------------------------------+
//|                                                          RSI.mqh |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
#include "ATR.mqh"
//--- Enumeration der Modi des Kanalausbruchs
enum ENUM_BREAK_INOUT
  {
   BREAK_IN          =0, // Break in
   BREAK_IN_REVERSE  =1, // Break in reverse
   BREAK_OUT         =2, // Break out
   BREAK_OUT_REVERSE =3  // Break out reverse
  };
//+------------------------------------------------------------------+
//| Der RSI mit einem Volatilitätsfilter                             |
//+------------------------------------------------------------------+
class CRsiPlus
  {
private:
   //--- Bestimmen des echten Balkens
   CFirstTrueBar     m_first_true_bar;
   //--- Pointer auf den ATR
   CATR             *m_atr;
   
   //--- Periodenlänge des Indikators
   int               m_period;
   //--- RSI Ebenen
   double            m_signal_level;
   //--- Modus des zu erzeugenden Signals
   ENUM_BREAK_INOUT  m_break_mode;
      
   //--- Zähler der Signale in derselben Richtung
   int               m_buy_counter;
   int               m_sell_counter;
   //--- Indikatorebenen
   double            m_up_level;
   double            m_down_level;
   double            m_up_levels[];
   double            m_down_levels[];
   int               m_up_levels_total;
   int               m_down_levels_total;
   
   //--- Beschränkung der Berechnung der Indikatorwerte
   int               m_limit;
   //--- Bestimmen des letzten Balkens
   bool              m_is_last_index;
   //---
public:
   //--- Indikatorpuffer
   double            m_rsi_buffer[];
   double            m_pos_buffer[];
   double            m_neg_buffer[];
   //---
   double            m_buy_buffer[];
   double            m_sell_buffer[];
   double            m_buy_level_buffer[];
   double            m_sell_level_buffer[];
   double            m_buy_counter_buffer[];
   double            m_sell_counter_buffer[];
   //---
public:
                     CRsiPlus(const int period,const double signal_level,const ENUM_BREAK_INOUT break_mode);
                    ~CRsiPlus(void) {}
   //--- Pointer auf den ATR
   void              AtrPointer(CATR &object) { m_atr=::GetPointer(object);  }
   CATR             *AtrPointer(void)         { return(::GetPointer(m_atr)); }
   //--- Berechnen des RSI
   bool              CalculateIndicatorRSI(const int rates_total,const int prev_calculated,const double &close[],const int &spread[]);
   //--- Initialisierung von allen Indikatorpuffern
   void              ZeroIndicatorBuffers(void);
   //---
private:
   //--- Abfrage der Indikatorebenen
   int               GetLevelsIndicator(void);
   //--- Vorberechnungen
   bool              PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[]);
   //--- Berechnen des RSI
   void              CalculateRSI(const int i,const double &price[]);
   //--- Berechnen der Indikatorsignale
   void              CalculateSignals(const int i,const int rates_total,const double &close[],const int &spread[]);

   //--- Prüfen der Bedingungen
   void              CheckConditions(const int i,bool &condition1,bool &condition2);
   //--- Prüfen der Zähler
   void              CheckCounters(bool &condition1,bool &condition2);
   //--- Erhöhen der Zähler für Kauf und Verkauf
   void              IncreaseBuyCounter(const bool condition);
   void              IncreaseSellCounter(const bool condition);

   //--- Kontrolle der Bewegungsrichtung
   void              DirectionControl(const int i,bool &condition1,bool &condition2);
   //--- Entfernen ausufernder Kauf- und Verkaufssignale
   void              DeleteBuySignal(const int i);
   void              DeleteSellSignal(const int i);
   //--- Nullifizieren des angegebenen Elements des Indikatorpuffers
   void              ZeroIndexBuffers(const int index);
  };

Die Methode CRsiPlus::DirectionControl() überprüft die folgenden Bedingungen, die bestimmen, ob das Signal zu hoch ist:

Wenn die Bedingungen erfüllt sind, wird das Signal gelöscht.

//+------------------------------------------------------------------+
//| Kontrolle der Bewegungsrichtung                                  |
//+------------------------------------------------------------------+
void CRsiPlus::DirectionControl(const int i,bool &condition1,bool &condition2)
  {
   double atr_coeff     =0.0;
   double impulse_size  =0.0;
   bool   atr_condition =false;
//---
   bool buy_condition  =false;
   bool sell_condition =false;
//--- Wenn die Umkehr deaktiviert wurde
   if(m_break_mode==BREAK_IN || m_break_mode==BREAK_OUT)
     {
      buy_condition =condition1 && m_buy_counter>1;
      impulse_size  =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]);
      atr_condition =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_buy_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1]))
        {
         DeleteBuySignal(i);
        }
      //---
      sell_condition =condition2 && m_sell_counter>1;
      impulse_size   =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]);
      atr_condition  =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_sell_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1]))
        {
         DeleteSellSignal(i);
        }
     }
//--- Umkehr ist aktiviert     
   else
     {
      buy_condition =condition2 && m_buy_counter>1;
      impulse_size  =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]);
      atr_condition =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_buy_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN_REVERSE && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT_REVERSE && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1]))
        {
         DeleteBuySignal(i);
        }
      //---
      sell_condition =condition1 && m_sell_counter>1;
      impulse_size   =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]);
      atr_condition  =impulse_size<m_atr.m_atr_buffer[i];
      //---      
      if((m_sell_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN_REVERSE && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT_REVERSE && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1]))
        {
         DeleteSellSignal(i);
        }
     }
  }

Werfen wir nun einen genaueren Blick auf die Hauptordner des Indikators. Diese Version des Indikators hat bereits 11 Puffer, 7 davon sind die Hauptpuffer, und 4 sind Hilfspuffer.

//--- Eigenschaften
#property indicator_chart_window
#property indicator_buffers 11
#property indicator_plots   7
#property indicator_color1  clrMediumSeaGreen
#property indicator_color2  clrRed
#property indicator_color5  clrMediumSeaGreen
#property indicator_color6  clrRed

Der Einfachheit halber sind alle Dateien mit Klassen im Verzeichnis Includes des Indikators:

 Abb. 12. Verzeichnis des Indikators.

Abb. 12. Verzeichnis des Indikators.

Daher schaut das Einbinden der Dateien wie folgt aus:

//--- Einbinden der Klassen des Indikators
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

Zu den externen Parametern fügen wir einen weitere hinzu, die Periodenlänge für den Indikator ATR.

//--- Eingabeparameter
input int              PeriodRSI   =8;         // RSI period
input double           SignalLevel =30;        // Signal level
input ENUM_BREAK_INOUT BreakMode   =BREAK_OUT; // Break mode
input int              PeriodATR   =200;       // ATR period

Bei der Deklaration der Indikator werden die Parameter dem Konstruktor übergeben.

//--- Instanzen der Indikatoren
CATR     atr(PeriodATR);
CRsiPlus rsi(PeriodRSI,SignalLevel,BreakMode);

In der Initialisierungsfunktion OnInit() darf nicht vergessen werden, die Pointer der Indikatoren ATR und RSI zu übergeben.

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des nutzerdefinierten Indikators        |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Initialisierung der Indikatoren
   rsi.AtrPointer(atr);
//--- Setze der Indikatoreinstellungen
   SetPropertiesIndicator();
  }

Da die für die Indikatorpuffer zugeordneten Arrays in jeder Indikatorklasse als 'public' deklariert sind, sieht ihre Ergänzung des Indikators in der Hauptdatei wie die übliche Einbindung von dynamischen Arrays aus.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Kurzname
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS_CHART");
//--- Dezimalstellen
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Indikatorpuffer
   ::SetIndexBuffer(0,rsi.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,rsi.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,rsi.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,rsi.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(4,rsi.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(5,rsi.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(6,atr.m_atr_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(7,rsi.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(8,rsi.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(9,rsi.m_neg_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(10,atr.m_tr_buffer,INDICATOR_CALCULATIONS);
//--- Initialisierung der Arrays
   atr.ZeroIndicatorBuffers();
   rsi.ZeroIndicatorBuffers();
...
  }

Hilfsarrays, die für zusätzliche Berechnungen verwendet werden, werden im Diagramm und im Datenfenster nicht angezeigt. Wenn es notwendig ist, die Daten im Datenfenster anzuzeigen, ohne sie im Diagramm anzuzeigen, ist es notwendig, die Eigenschaft DRAW_NONE für solche Serien einzustellen.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Festlegen der Typen der Indikatorpuffer
   ENUM_DRAW_TYPE draw_type[]={DRAW_ARROW,DRAW_ARROW,DRAW_NONE,DRAW_NONE,DRAW_LINE,DRAW_LINE,DRAW_NONE};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]);
...
  }

Die Funktion OnCalculate() ruft im Wesentlichen nur die zwei Methoden zur Berechnung des ATR und des modifizierten RSI auf. 

//+------------------------------------------------------------------+
//| Aufruf der nutzerdefinierten Indikatoren                         |
//+------------------------------------------------------------------+
int OnCalculate(const int      rates_total,
                const int      prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int      &spread[])
  {
//--- Berechnen des ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Berechnen des RSI
   if(!rsi.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
//--- Rückgabe der berechneten Nummer des Elements
   return(rates_total);
  }

Nach dem Kompilieren und Starten des Indikators auf dem Chart sehen wir ein Ergebnis, wie es die Abbildung 13 zeigt. Die auf Basis der Indikatorsignale aufgetragenen Ebenen bilden eine Art Kanal. Es ist einfach, damit die Entfernung zu bestimmen, die der Preis seit den letzten entgegengesetzten Signalen der vorherigen Serie passiert hat.

 Abb. 13. Ergebnis des modifizierten RSI auf dem Hauptchart.

Abb. 13. Ergebnis des modifizierten RSI auf dem Hauptchart.

Bei der Entwicklung eines Handelssystems kann es notwendig sein, die genauen Werte des einen oder anderen Indikatorpuffers zu visualisieren. Dies gilt insbesondere, wenn die Serie einiger Puffer mit außer Sichtweite fallenden Werten nicht auf dem Chart angezeigt werden. Deren Werte sind im Datenfenster zu sehen. Zur Vereinfachung können Sie das Fadenkreuz mit der mittleren Maustaste aktivieren. In Abbildung 14 wird der Indikator ATR aus dem Standardpaket dem Diagramm hinzugefügt, so dass er mit demjenigen verglichen werden kann, der in dem modifizierten RSI berechnet wurde.

 Abb. 14. Darstellung der Werte im Datenfenster des Indikators.

Abb. 14. Darstellung der Werte im Datenfenster des Indikators.

Die fünfte Version. Ein universeller RSI-Indikator für das gleichzeitiges Arbeiten in beiden Richtungen

Und was ist, wenn wir einen Indikator brauchen, der Signale in zwei Richtungen gleichzeitig anzeigt? Schließlich bietet MetaTrader 5 die Möglichkeit, Hedging-Konten zu eröffnen, so dass es möglich ist, ein System mit unterschiedlich gerichteten Positionen zu entwickeln. Es wäre sinnvoll, einen Indikator zu haben, der Signale sowohl für einen Trend als auch für eine Seitwärtsbewegung erzeugt. 

Lassen Sie uns kurz überlegen, wie man einen solchen Indikator erstellt. Wir haben bereits alles Notwendige, und die Änderungen werden nur in der Hauptdatei vorgenommen. Diese Version wird insgesamt 20 Puffer haben, von denen 15 angezeigt werden.

#property indicator_buffers 20
#property indicator_plots   15

Trend-Signale werden als Pfeile dargestellt, Signal der Seitwärtsbewegung als Punkte. Dadurch wird der Typ, zu dem ein bestimmtes Signal gehört, besser visualisiert und die Verständlichkeit verbessert.

//--- Signalmarkierung: 159 - Punkte; 233/234 - Pfeile
#define ARROW_BUY_IN   233
#define ARROW_SELL_IN  234
#define ARROW_BUY_OUT  159
#define ARROW_SELL_OUT 159

Es werden die gleichen Dateien wie in den anderen Version eingebunden, ebenso aus dem lokalen Verzeichnis des Indikators. Anschließend haben Sie die Möglichkeit, eine einzelne Kopie dieser Dateien dort zu platzieren, wo Sie es wollen. 

//--- Einbinden der Klassen des Indikators
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

In dieser Version des Indikators ist es nicht notwendig, den Signaltyp in den externen Parametern anzugeben. Aber spezifizieren wir die Ebenen der trendbasierten Signale (Signal level In) und die Signale der Seitwärtsbewegung (Signal level Out) getrennt voneinander

//--- Eingabeparameter
input int    PeriodRSI      =8;   // RSI period
input double SignalLevelIn  =35;  // Signal level In
input double SignalLevelOut =30;  // Signal level Out
input int    PeriodATR      =100; // ATR period

Dann ist es notwendig, zwei Instanzen der Klasse CRsiPlus zu deklarieren: Eine für die Trendsignale und eine weitere für Seitwärtssignale. In beiden Fällen werden die umgekehrten Signaltypen verwendet (BREAK_IN_REVERSE und BREAK_OUT_REVERSE). Das heißt, der Impuls aus dem Kanal dient als Signal für die Seitwärtsbewegung; und für den Trend werden nach einem Rollback Eröffnungen in Trendrichtung vorgenommen.

//--- Instanzen der Indikatoren
CATR atr(PeriodATR);
CRsiPlus rsi_in(PeriodRSI,SignalLevelIn,BREAK_IN_REVERSE);
CRsiPlus rsi_out(PeriodRSI,SignalLevelOut,BREAK_OUT_REVERSE);

Pointer zum Indikator ATR sollte beiden Instanzen des RSI übergeben werden:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des nutzerdefinierten Indikators        |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Übergabe des Pointers an den ATR  
   rsi_in.AtrPointer(atr);
   rsi_out.AtrPointer(atr);
//--- Setze der Indikatoreinstellungen
   SetPropertiesIndicator();
  }

Die Indikatorpuffer für jede Instanz sind in dieser Reihenfolge gesetzt, um die Navigation im Code zu erleichtern. Die Reihenfolge spielt natürlich keine Rolle. Sie müssen nur die Puffernummer kennen, um die Werte eines bestimmten Puffers zu erhalten, um später die Bedingungen im Expert Advisor zu bilden.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Kurzname
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS2_CHART");
//--- Dezimalstellen
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Indikatorpuffer
   ::SetIndexBuffer(0,rsi_in.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,rsi_in.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,rsi_in.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,rsi_in.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(4,rsi_in.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(5,rsi_in.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(6,rsi_in.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(7,rsi_in.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(8,rsi_in.m_neg_buffer,INDICATOR_CALCULATIONS);
//---
   ::SetIndexBuffer(9,rsi_out.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(10,rsi_out.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(11,rsi_out.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(12,rsi_out.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(13,rsi_out.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(14,rsi_out.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(15,rsi_out.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(16,rsi_out.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(17,rsi_out.m_neg_buffer,INDICATOR_CALCULATIONS);
//---
   ::SetIndexBuffer(18,atr.m_atr_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(19,atr.m_tr_buffer,INDICATOR_CALCULATIONS);
//--- Initialisierung der Arrays
   atr.ZeroIndicatorBuffers();
   rsi_in.ZeroIndicatorBuffers();
   rsi_out.ZeroIndicatorBuffers();
...
  }

Um Verwechselungen zu vermeiden, welches Signal zu welchem Typ gehört, werden die der Seitwärtsbewegung gestrichelt gezeichnet.

//+------------------------------------------------------------------+
//| Bestimmen der Eigenschaften des Indikators                       |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Setzen des Linientyps des angegebenen Indikatorpuffers
   ::PlotIndexSetInteger(13,PLOT_LINE_STYLE,STYLE_DOT);
   ::PlotIndexSetInteger(14,PLOT_LINE_STYLE,STYLE_DOT);
...
  }

The code of the OnCalculate() function is limited to only calling the methods for each instance of the indicator.

//+------------------------------------------------------------------+
//| Aufruf der nutzerdefinierten Indikatoren                         |
//+------------------------------------------------------------------+
int OnCalculate(const int      rates_total,
                const int      prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int      &spread[])
  {
//--- Berechnen des ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Berechnen des RSI
   if(!rsi_in.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
   if(!rsi_out.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
//--- Rückgabe der berechneten Nummer des Elements
   return(rates_total);
  }

Abbildung 15 zeigt die Funktionsweise des Indikators auf dem Chart. All dies ist nur das Ergebnis der Ausführung von RSI.

 Abb. 15. Der universelle RSI-Indikator.

Abb. 15. Der universelle RSI-Indikator.

 Abb. 16. Der universelle RSI-Indikator im Strategietester.

Abb. 16. Der universelle RSI-Indikator im Strategietester.

In dieser Form zeigt der Indikator die Einstiegspunkte sowie die Punkte zur Erhöhung des Positionsvolumens an. Darüber hinaus ist es möglich, den Index des aktuellen Signals in der kontinuierlichen Serie zu erhalten, zu der es gehört. Und da es auch Preisniveaus gibt, ist es möglich, die Entfernung zu erhalten, die sich der Preis gegenüber dem letzten Signal der vorherigen Serie bewegt hat. Ein komplexerer Handelsalgorithmus kann ebenfalls implementiert werden. So können beispielsweise beim Öffnen von Positionen mit den Trends diese teilweise oder vollständig geschlossen werden, basierend auf den Signalen für Seitwärtsbewegungen vorgesehen sind.

Unter Berücksichtigung der Ergebnisse des Indikators auf dem Chart können Sie verschiedene Handelsalgorithmen entwickeln, die zunächst im Tester überprüft werden müssen. Auch das ist ein groß angelegtes Unterfangen: Es kann unendlich viele Varianten für die Umsetzung geben. Jedes Handelsmodul kann so konfiguriert werden, dass es die Handelsergebnisse eines anderen erhält. Das heißt, das Trendhandelsmodul kann das Handelsergebnis des flachen Handelsmoduls erhalten und umgekehrt. Auf der Grundlage dieser Daten können sie ihr Verhalten anpassen und sich an die aktuelle Situation anpassen. Jedes Modul kann die Handelstaktik des anderen beeinflussen: Änderung der Bedingungen für die Eröffnung, Schließung oder Verwaltung von Positionen, Änderung des Managementsystems (Umwandlung von einem konservativen in ein aggressives Modell und umgekehrt). Das Handelssystem kann sehr komplex sein und sich an die aktuelle Kursentwicklung anpassen.

Schlussfolgerung

Wie man sehen kann, kann ein einfacher Indikator viel informativer gestaltet werden. Dies wiederum erleichtert die Entwicklung von Handelsalgorithmen. Andernfalls müsste all dies innerhalb des Expert Advisor erfolgen, was seinen Code verkompliziert. Jetzt ist alles in einem Programm verborgen, und Sie können die berechneten Werte einfach in seinen Arrays auslesen.

Sie können diese Idee weiter entwickeln. Beispielsweise können Sie weitere Puffer für neue Merkmale einführen, die den Status für Trend- oder Nicht-Trend angeben. Sie können einen Puffer für Trailing Stop hinzufügen, dessen Berechnung an die aktuelle Volatilität gebunden werden kann (Indikator ATR). Dadurch werden alle notwendigen Berechnungen in einem Indikator umgesetzt, während der Expert Advisor einfach die vorbereiteten Ebenen erhält.

Dateiname Kommentar
MQL5\Indicators\RSI\RSI_Plus1.mq5 Die erste Version des modifizierten Indikators RSI
MQL5\Indicators\RSI\RSI_Plus2.mq5 Die zweite Version des modifizierten Indikators RSI
MQL5\Indicators\RSI\RSI_Plus3.mq5 Die dritte Version des modifizierten Indikators RSI
MQL5\Indicators\RSI\ChartRSI_Plus1\ChartRSI_Plus1.mq5 Die vierte Version des modifizierten Indikators RSI
MQL5\Indicators\RSI\ChartRSI_Plus2\ChartRSI_Plus2.mq5 Die fünfte Version des modifizierten Indikators RSI