Baukasten des Händlers: Gestaltung der Indikatoren

TheXpert | 29 März, 2016

Einleitung

Was ist ein Indikator? Es ist ein Tool, das für die Anzeige einer bestimmten Art von Daten vorgesehen ist. Üblicherweise sind es Informationen über die Eigenschaften von Kursverläufen und genau diese Art von Indikatoren werden wir genauer betrachten.

Jeder Indikator hat auch seine eigenen Eigenschaften und Charakteristiken: zum Beispiel der Bereich der Werte, die überkauft/überverkauft-Zonen, das Kreuzen von Linien, Hochs und Tiefs... Sie sind zahlreich und können erfolgreich zusammen mit den wichtigsten Indikatorwerten verwendet werden. Allerdings sind solche Eigenschaften nicht immer klar. Die Gründe dafür können verschieden sein - geringe Größe des Anzeigefensters, geringe Konzentration, usw.

Der Zweck dieses Artikels ist Ihnen zu helfen, die Darstellung und die Aussagefähigkeit der Indikatoren zu verbessern, sowie die teilweise Automatisierung und die Implementierung des Codes zu vereinfachen. Ich hoffe, dass der unten angeführte Code weder Anfängern noch professionellen Entwicklern Schwierigkeiten bereiten wird.

Der Artikel ist für diejenigen gedacht, die zumindest über ein gewissen MQL4-Wissen verfügen als auch einfache Ideen und Algorithmen in einen Code implementieren können, sowie die Struktur von Codes im Terminal kennen und die Bibliotheken (Experten/Bibliotheken) und Header-Dateien (Experten/Inkludieren) verwenden.



1. Einrichten einer Aufgabe

Unter allen Indikatoren möchte ich die informativsten und am häufigsten genutzten umschreiben:

Lassen Sie uns diese diskutieren.



2. Grundbegriffe

Um Missverständnisse zu vermeiden, nehmen wir uns etwas Zeit, um die Indikator-Struktur zu betrachten.

#property indicator_separate_window

// number of visible buffers of the indicator
#property indicator_buffers 3

// setting the range of indicator values
#property indicator_minimum 0
#property indicator_maximum 100

// setting indicator colors
#property indicator_color1  White
#property indicator_color2  Red
#property indicator_color3  Blue

// external settings
extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// declaring indicator buffers. Here they can be declared in any order.
// Any names can be given to buffers, though better meaningful

double Values[];           // Values
double SmoothedValues[];   // Smoothed values
double Crosses[];          // intersections

// Significant number of digits after a decimal point in indicator values
int DigitsUsed = 5;

// Used empty value. In MQL4 there are two empty values -- EMPTY (-1)
// -- used as an empty parameter when calling functions
// EMPTY_VALUE (0x7FFFFFFF) -- used as an unacceptable value 
// (or default value) of a variable in indicators and function calls. 
// The fact is, most built-in indicators return 0 if there is no value
// Besides, in custom (iCustom) indicators the empty value can be 
// set as any, this must be noted.
int EmptyValueUsed = 0;

// Initialization function.
int init()
{
   // Number of used buffers can be larger than that of displayed ones; some 
   // may contain intermediate calculations and additional information. The total 
   // number of buffers including additional ones is displayed here. 
   // If there are no additional buffers,
   // this line is not needed. Total number must not exceed 8
   // IndicatorBuffers(3);

   // associate buffers. Indexes must go from 0 till the declared number (not including)
   // buffers are drawn in the order of index growing, this is important and can be 
   // used when righting indicators further.
   // It means that a buffer with a larger index is drawn above the buffer with lower one
   SetIndexBuffer(0, Values);
   SetIndexBuffer(1, SmoothedValues);
   SetIndexBuffer(2, Crosses);
   // besides, it is important that additional buffers are located after displayed ones 
   // (i.e. they must have higher index) otherwise problems may occur displaying buffers, 
   // and sometimes the error can be hardly found

   // This function sets an empty value for the buffer with the preset index
   // I do not recommend to use this function in order to avoid possible difficulties
   // Default empty value for buffers -- EMPTY_VALUE. 
   // Empty buffer values are not drawn in a chart (except for DRAW_ZIGZAG)

   // SetIndexEmptyValue(0, EMPTY_VALUE);
   
   // Set parameters for buffers
   SetIndexStyle(0, DRAW_LINE);     // The main signal is a solid line
   SetIndexStyle(1, DRAW_LINE, STYLE_DASH); // Smoothed -- dotted line
   SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID, 2); // Intersections -- crosses of the size 2
   
   SetIndexArrow(2, 251); // cross code in Wingdings
   
   IndicatorDigits(DigitsUsed); // set number of significant digits after point
   
   // Setting the starting plotting point for each indicator. If in terms of the current index 
   // the history depth 
   // is lower than the value written here, the buffer value with this index will not be drawn.
   SetIndexDrawBegin(0, RSIPeriod); 
   SetIndexDrawBegin(1, RSIPeriod + MAPeriod);
   SetIndexDrawBegin(2, RSIPeriod + MAPeriod + 1);

   return(0);
}

int start()
{
   // counting number of bars for re-calculation
   int toCount = Bars - IndicatorCounted();  
   
   // Calculating values
   // counting from history start till the current moment
   for (int i = toCount - 1; i >=0; i--)
   {
      // I understood its convenience only when I started to use it
      // I recommend to conduct the normalization of data at once, 
      // so that later comparison could be easily made
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
      
   // Counting smoothed values
   for (i = toCount - 1; i >=0; i--)
   {
      SmoothedValues[i] = NormalizeDouble(iMAOnArray(Values, 0, MAPeriod, 0, MODE_EMA, i), DigitsUsed);
   }
      
   // ...
   
   return(0);
}


3. Charakteristiken

Lassen Sie uns die Charakteristiken im Detail betrachten.


3.1. Linien-Kreuzung

Möglicherweise hat ein jeder Entwickler schon mal versucht einen Trading-Algorithmus zu implementieren, der die Kreuzung zweier MAs (Moving Averages) oder die Kreuzung der Basislinie und Signallinie vom MACD nutzt. Lassen Sie uns versuchen, dies zu visualisieren und es deutlicher zu machen, indem wir den Kreuzungspunkt im Indikator anzeigen.

Als Beispiel werden wir im gesamten Text den Relative Strength Index verwenden, so dass unser Ziel lautet, einen verbesserten RSI mit einigen neuen Vorteilen zu entwickeln.


3.1.1. Formalisierung der Aufgabe

Es ist notwendig, die Balken, welche die Linie kreuzen, in einem separaten Puffer zu kennzeichnen.


3.1.2. Probleme

Es scheint, dass alles einfach und klar ist. Die Aufgabe ist nicht schwierig und kann durch ein paar Codezeilen gelöst werden.

Wir müssen die Linien-Kreuzung wie zum Beispiel diese hier beschreiben:


if ((x1 > y1 && x2 < y2) || (x1 < y1 && x2 > y2))
{
    // line crossing here
}

Oder wir können es vereinfachen:

if ((x1 - y1)*(x2 - y2) < 0)
{
    // line crossing here
}

Aber lassen Sie uns folgenden Fall betrachten:


Beachten Sie, dass die grünen Punkte die gleichen Werte haben. In solch einem Fall haben wir keine durchkreuzte Linie, sondern nur eine Linienberührung.

Aber hier:


ist es nicht so einfach, die Kreuzung zu bestimmen. Dieser Fall ist durchaus möglich.

Es ist auch notwendig, den Fall einer Berührung vom Fall einer Kreuzung korrekt zu unterscheiden, wobei zu berücksichtigen ist, dass wir während der Suche einen leeren Wert in einem Puffer finden können.


3.1.3. Lösung

Das Platzieren der Funktion init() wird nicht berücksichtigt, da diese nicht wichtig ist. Der vollständige Code kann in der Quelle gefunden werden.

Hier ist die Lösung für die einfachen und geglätteten Werte des Relative Strength Index Indikators.


//|                                 RSI_Crosses_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// buffers
double Values[];           // Values
double SmoothedValues[];   // Smoothed values
double Crosses[];          // Crosses

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Reading the values and normalizing them 
   // Mark the crosses
   for (i = toCount - 1; i >=0; i--)
   {
      // i+1 must be greater or equal bars count in the history
      if (i + 1 >= Bars)
      {
         continue;
      }

      // if some of the values are empty, it is not necessary to check
      if (
            Values[i]               == EmptyValueUsed || 
            Values[i + 1]           == EmptyValueUsed ||
            SmoothedValues[i]       == EmptyValueUsed || 
            SmoothedValues[i + 1]   == EmptyValueUsed ||
            Values[i]               == EMPTY_VALUE    || 
            Values[i + 1]           == EMPTY_VALUE    ||
            SmoothedValues[i]       == EMPTY_VALUE    || 
            SmoothedValues[i + 1]   == EMPTY_VALUE
      )
      {
         continue;
      }
      
      // clear the current value
      Crosses[i] = EMPTY_VALUE;
      
      // crossing check (simple case)
      if ((Values[i] - SmoothedValues[i])*(Values[i + 1] - SmoothedValues[i + 1]) < 0)
      {
         Crosses[i] = SmoothedValues[i];
         continue;
      }
      
      // the crossing condition for a complicated case - 
      // when crossing contain several bars with the same values
      // 
      if (Values[i + 1] == SmoothedValues[i + 1] && Values[i] != SmoothedValues[i])
      {
         // there is potential crossing - checking it
         // lets find the second end

         int index = i + 1;
         bool found = false;
         while (
               index < Bars &&    // out of range
               Values[index] != EmptyValueUsed &&   // check for empty
               Values[index] != EMPTY_VALUE &&      // check for empty 
               SmoothedValues[index] != EmptyValueUsed &&  // check for empty
               SmoothedValues[index] != EMPTY_VALUE)       // check for empty
         {
            if (Values[index] != SmoothedValues[index])
            {
               // ok, we have found the second end
               found = true;
               break;
            }
            
            index++;
         }

         if (!found)
         {
            // the case of the end of history or empty value
            // anyway, we mean that there is no crossing
            continue;
         }
         
         // checking the ends for crossing
         if ((Values[i] - SmoothedValues[i])*(Values[index] - SmoothedValues[index]) < 0)
         {
            // crossing found
            Crosses[i] = SmoothedValues[i];
         }  // else we have a touching - do not mark it
            
      }
   }
   
   return(0);
}

3.1.4. Automatisierung

In diesem Abschnitt betrachten wir die Lösung des Problems mit Hilfe der Indicator_Painting Bibliothek.

//|                                 RSI_Crosses_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;
extern int MAPeriod        = 5;

// buffers
double Values[];           // Values
double SmoothedValues[];   // Smoothed values
double Crosses[];          // Crosses

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   // Reading values
   // ...
      
   // Mark crosses
   MarkCrosses
   (
      Values,            // the fast buffer with values to check
      SmoothedValues,    // the slow buffer with values to check
      Crosses,           // the crosses buffer
      toCount - 1,       // start check index
      0,                 // final check index
      CROSS_ALL,         // use CROSS_UP for up crosses CROSS_DOWN for down crosses CROSS_ALL for all
      0);                // used empty value
   
   return(0);
}

3.2. Pegelmarke

Einige der Oszillatoren mit limitiertem und strikt eingestelltem Wertebereich (RSI, Stochastischer Oszillator, DeMarker, Money Flow Index, Williams' Percent Range) benötigen oftmals das Kennzeichnen von Zonen oder Niveaus. Zum Beispiel die flachen Zonen, die überkauft/überverkauft-Zonen, die Trendzonen... Lassen Sie uns versuchen, das definierte Niveau zu skizzieren, indem wir verschiedene Farben dafür verwenden.


3.2.1. Formalisierung der Aufgabe

Es ist notwendig, die Balken mit den Werten außerhalb der definierten Niveaus in einem separaten Puffer zu kennzeichnen.



3.2.2. Probleme

Es ist nicht so einfach, wie es auf den ersten Blick scheint.

Das erste Problem ist das Zeichnen der Balken, auf denen das definierte Niveau gekreuzt wird. Hier ist die Lösung.

//|                                 RSI_Cut_Levels_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Overbought
double Lower[];            // Oversold
double Values[];           // Values

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Reading values
   // ...
      
   // Mark levels - upper
   for (i = toCount - 1; i >=0; i--)
   {
      // check for empty values
      if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed)
      {
         continue;
      }
      
      // empty current value
      Higher[i] = EMPTY_VALUE;
   
      // greater than high
      if (Values[i] >= HigherLevel)
      {
         Higher[i] = Values[i];
      }
   }
   
   // for the levels mark - the code is same
   // ...

   return(0);
}

Dieser Code löst die definierte Aufgabe, aber hat ein Problem:


Es ist schwierig, den Chart visuell zu analysieren, da das Zeichnen des Signals von dem Wert beginnt, der größer (niedriger) ist als das Niveau. Das ist, warum einige der Signalbalken nicht analysiert werden können, da Besonderheiten gezeichnet werden, die durch schnelle Veränderungen der benachbarten Balken verursacht werden.

Die Lösung ist, nicht nur die Balken zu kennzeichnen, die höher (niedriger) als das definierte Niveau sind, sondern auch jeweils den Balken, der sich vor als auch nach den bereits markierten befindet. Und es ist notwendig, diese nicht mit ihren eigenen Werten, sondern mit den Werten des Niveaus zu kennzeichnen.

Das zweite Problem erscheint nach der Lösung des ersten - der Signalpuffer hat Pseudomarken der "falschen" Niveau-Untergliederungen als Folge der Algorithmus-Komplikation.

Es bedeutet, dass sich der Kurs während der Balkenbildung außerhalb des Niveaus befunden hat, jedoch der letzte Balken einen Wert innerhalb des Niveaus hat. Aufgrund dieser Tatsache können wir ein Bild wie dieses erhalten:


Das Problem tritt nur auf, wenn wir einen Indikator für die Echtzeit-Kurse verwenden. Die Lösung ist einfach - wir überprüfen zwei Balken (0 und 1) während der Verarbeitung, die anderen werden nur wenn nötig geprüft.

Danach werden wir folgendes Bild für den RSI haben:



3.2.3. Lösung

Lassen Sie uns also all dies in einen Code schreiben:


//|                                 RSI_Levels_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Overbought
double Lower[];            // Oversold
double Values[];           // Values

int DigitsUsed = 5;
int EmptyValueUsed = 0;

// looking at least two bars - 0 and 1.
int Depth = 2;

int start()
{
   int toCount = Bars - IndicatorCounted();  
 
   // Reading values
   // ...
   
   toCount = MathMax(toCount, Depth);
      
   // Marking levels - upper
   for (i = toCount - 1; i >=0; i--)
   {
      if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed) continue;
      
      Higher[i] = EMPTY_VALUE;
   
      // greater than level
      if (Values[i] >= HigherLevel)
      {
         Higher[i] = Values[i];
      
         // if previous is lower
         if (Values[i + 1] < HigherLevel && Values[i + 1] != EmptyValueUsed)
         {
         // mark it also but with the level value
            Higher[i + 1] = HigherLevel;
         }
      }
      // if current lower
      else
      {
         // if previous is greater
         if (Values[i + 1] >= HigherLevel && Values[i + 1] != EMPTY_VALUE)
         {
            // mark it also but with the level value
            Higher[i] = HigherLevel;
         }
      }
   }
   
   // Mark levels - the code is the same
   // ...

   return(0);
}

3.2.4. Automatisierung

Das Lösen des gleichen Problems durch die Verwendung der Indicator_Painting Bibliothek.

//|                                 RSI_Levels_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

extern int HigherLevel     = 70;
extern int LowerLevel      = 30;

// buffers
double Higher[];           // Overbought
double Lower[];            // Oversold
double Values[];           // Values

int DigitsUsed = 5;
int EmptyValueUsed = 0;
int Depth = 2;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   
   // Read values
   for (int i = toCount - 1; i >= 0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Mark levels - upper
   MarkLevel(Values, Higher, 0, toCount - 1, HigherLevel, GREATER_THAN, EmptyValueUsed);
   // Mark levels - lower
   MarkLevel(Values, Lower, 0, toCount - 1, LowerLevel, LESS_THAN, EmptyValueUsed);

   return(0);
}


3.3. Hochs und Tiefs


Die Extrempunkte (Extrema) des Indikators können als Signale verwendet werden. In diesem Artikel wird der Begriff "Extremum" in seiner einfachsten Bedeutung verwendet - wenn der Balken einen größeren (niedrigeren) Wert hat als sein Nachbar, dann sprechen wir von einem Extremum.

3.3.1. Formalisierung der Aufgabe

Es ist notwendig, die Balken mit Extremwerten in einem separaten Puffer zu kennzeichnen.


3.3.2. Probleme

Lassen Sie uns einige Beispiele betrachten:


Hier sind die offensichtlichen Extremwerte mit roten Punkten gekennzeichnet:


if ((x1 > x2 && x3 > x2) || (x1 < x2 && x3 < x2))
{
    // x2 is extremal
}

Oder wir können es vereinfachen:


if ((x1 - x2)*(x2 - x3) < 0)
{
    // x2 is extremal
}

Aber lassen Sie uns folgenden Fall betrachten:


Die gekennzeichneten Punkte haben die gleichen Werte. Der blaue Punkt ist ein Extremum. Es ist nicht einfach, diesen zu bestimmen. Und im folgenden Fall:


gibt es kein Extremum. Wir nehmen an, dass es eine Biegung ist.

Die Lösung ist in diesen Fällen, das zweite Ende zu finden, wie bei den Fällen von Kreuzungen.



3.3.3. Lösung

Hier ist der Code:

//|                                 RSI_Extremums_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Values
double Extremums[];        // Extremums

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
      
   for (i = toCount - 1; i >=0; i--)
   {
      // check the values relative to the current index.
      if (i + 2 >= Bars)
      {
         continue;
      }

      // check for empty values, if there are, it is not necessary to check
      if (
            Values[i]      == EmptyValueUsed || 
            Values[i + 1]  == EmptyValueUsed ||
            Values[i + 2]  == EmptyValueUsed
      )
      {
         continue;
      }
      
      // fill the current value of the mark buffer
      Extremums[i + 1] = EMPTY_VALUE;
      
      // cross condition - the simple case
      if ((Values[i] - Values[i + 1])*(Values[i + 1] - Values[i + 2]) < 0)
      {
         // we have found the cross
         Extremums[i + 1] = Values[i + 1];
         continue;
      }
      
      // the cross condition in a complicated case - 
      // when top contain several bars with the same value
      if (Values[i + 1] == Values[i + 2] && Values[i] != Values[i + 1])
      {
         // there is possible extremum - to check it
         // we have to find the second end

         int index = i + 2;
         bool found = false;
         while (index < Bars && Values[index] != EmptyValueUsed && Values[index] != EMPTY_VALUE)
         {
            if (Values[i + 2] != Values[index])
            {
               // ok, we have found the second end
               found = true;
               break;
            }
            
            index++;
         }

         if (!found)
         {
            // we are at the end of the history or have an empty value
            // for the both cases we assume that there is no extremum
            continue;
         }
         
         // checking the ends for a cross
         if ((Values[i] - Values[i + 1])*(Values[i + 1] - Values[index]) < 0)
         {
            // there is a cross
            Extremums[i + 1] = Values[i + 1];
         }  // else -- there is a bend point, do not mark it
      }
   }
   
   return(0);
}

3.3.4. Automatisierung

Die gleiche Aufgabenlösung durch die Nutzung der Indicator_Painting Bibliothek.

//|                                 RSI_Extremums_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Values
double Extremums[];        // Extremal points

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();  

   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   MarkExtremums(Values, Extremums, toCount - 1, 0, DIR_ALL, EmptyValueUsed);
   
   return(0);
}


3.4. Unterschiedliche Farben für die jeweilige Richtung

Diese Visualisierungsmethode wird in einigen Standardindikatoren verwendet und kann auch sehr nützlich sein.



3.4.1. Formalisierung der Aufgabe

Es ist notwendig, einige Reihen von Indikatorwerten mit verschiedenen Farben einzufärben (zum Beispiel Reihen, während einer Richtung nach oben oder unten). Die Richtung kann ganz einfach erklärt werden - wenn der aktuelle Wert größer als der vorige ist, haben wir eine Richtung nach oben und umgekehrt sprechen wir von einer Richtung nach unten.


3.4.2. Probleme

Gehen wir von der Funktion aus. Es wird angenommen, dass wir einen Basisdatenpuffer haben und dieser Puffer grafisch dargestellt wird. Wenn nicht, werden wir es tun, denn wir benötigen für das benutzerdefinierte Malen der Richtung mindestens zwei Farben und mindestens zwei Puffer. Nun die Funktion. Wenn wir eine der Richtungen einfärben, ist es nicht notwendig die andere Richtung einzufärben - wir sehen dies auf den nicht eingefärbten Teilen des Basispuffers.

Der Basispuffer:

Hier ist der Basispuffer mit einer eingefärbten Richtung nach oben:

Deshalb werden wir jetzt nur die eine eingefärbte Richtung betrachten, in diesem Beispiel nach oben. Lassen Sie uns die Probleme betrachten, die auftreten können.

Die naive Implementierung der Funktion ist die folgende:


//|                                 RSI_Simple_Directions_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Values
double Growing[];          // Growing buffer

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Reading values
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Mark the growing levels - we will get the falling levels as a result
   for (i = toCount - 1; i >=0; i--)
   {

      // check for empty value, if there are, the further check is not necessary
      // ...
      
      // filling the current values with empty values
      Growing[i] = EMPTY_VALUE;
      
      // if growing
      if (Values[i] > Values[i + 1])
      {
         Growing[i] = Values[i];
         Growing[i + 1] = Values[i + 1];
      }
   }

   return(0);
}

Kompilieren, Anbringen an Charts und... das Ergebnis der Codeausführung sehen:


Es gibt einige Probleme, die mit Punkten gekennzeichnet sind. Lassen Sie uns betrachten, warum es so aussieht. Während wir eine Richtung einfärben, erhalten wir den Effekt, dass bei einigen Teilstücken es keine Werte gibt (EMPTY_VALUE).

Lassen Sie uns folgenden Fall betrachten:

Die zusätzlichen Pufferdaten, die keine leeren Werte haben sollten, sind durch schwarze Punkte gekennzeichnet. Um das Zeichnen der geraden Linie zwischen den Punten zu vermeiden (mit dem Stil DRAW_LINE), ist es notwendig, mindestens einen nicht leeren Wert zwischen ihnen zu haben. Der gesamte dargestellte Bereich hat keine leeren Werte, weshalb der Basispuffer nur bei "Sägezahn"-Stücken eingezeichnet ist.


Die Lösung dieses Problems ist nicht so ganz einfach - beispielsweise würde eine Glättung oder das Verwenden von einigen zusätzlichen Bedingungen es viel komplizierter machen. Als Folge müssten wir vielleicht mehrere Balken neu einfärben oder etwas anderes schwieriges tun.



Die Lösung ist, zwei zusätzliche Puffer dafür zu verwenden. Dann ist es möglich, dass sich die bemalten Stücke abwechseln - wodurch wir die notwendigen leeren Wertte in jedem der Puffer erhalten.

Lassen Sie uns die verschiedenen Farben den zusätzlichen Puffern zuweisen und das Ergebnis ansehen:

Das Hauptproblem ist gelöst, aber es gibt noch ein weiteres kleines Problem mit dem Null-Balken. Er wird jedes Mal neu gezeichnet und in einigen Fällen ist es notwendig, das Stück nach oben zu löschen, wenn sich die Richtung geändert hat.


Betrachten wir die Implementierung.

3.4.3. Lösung


//|                                 RSI_Directions_Sample.mq4 |

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Values
double Growing1[];         // First growing buffer
double Growing2[];         // Second growing buffer

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Reading values
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Mark for the growing levels - we will get the falling levels as a resut
   for (i = toCount - 1; i >=0; i--)
   {
      // check of an empty values
      // ...
      
      // assume that the current values are empty
      Growing1[i] = EMPTY_VALUE;
      Growing2[i] = EMPTY_VALUE;
      
      // if it growing
      if (Values[i] > Values[i + 1])
      {
         // if it growing on the previous bar
         if (Values[i + 1] > Values[i + 2])
         {
            // writing to the current growing buffer
            if (Growing1[i + 1] != EMPTY_VALUE) Growing1[i] = Values[i];
            else                                Growing2[i] = Values[i];
         }
         // if the previous bar was not increasing
         else
         {
            // write to the buffer which it was not used the last 2 bars
            // we must have at least one such bar

            if (Growing2[i + 2] == EMPTY_VALUE) 
            {
               Growing2[i] = Values[i];
               Growing2[i + 1] = Values[i + 1];
            }
            else
            {
               Growing1[i] = Values[i];
               Growing1[i + 1] = Values[i + 1];
            }
         }
      }
      // if the last value does not grow, remove it
      else if (i == 0)
      {
         if (Growing1[i + 1] != EMPTY_VALUE && Growing1[i + 2] == EMPTY_VALUE)
         {
            Growing1[i + 1] = EMPTY_VALUE;
         }

         if (Growing2[i + 1] != EMPTY_VALUE && Growing2[i + 2] == EMPTY_VALUE)
         {
            Growing2[i + 1] = EMPTY_VALUE;
         }
      }
   }

   return(0);
}

3.4.4. Automatisierung

Die gleiche Aufgabenlösung durch die Nutzung der Indicator_Painting Bibliothek.

In der Bibliothek gibt es auch eine ähnliche Implementierung für die Richtung nach unten.


//|                                 RSI_Directions_Lib_Sample.mq4 |

#include <Indicator_Painting.mqh>

extern int RSIPeriod       = 9;
extern int AppliedPrice    = 0;

// buffers
double Values[];           // Values
double Growing1[];         // First growing buffer
double Growing2[];         // Second growing buffer

int DigitsUsed = 5;
int EmptyValueUsed = 0;

int start()
{
   int toCount = Bars - IndicatorCounted();

   // Reading values
   for (int i = toCount - 1; i >=0; i--)
   {
      Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed);
   }
   
   // Mark the growing levels - we will get the falling levels automatically
   MarkGrowing(Values, Growing1, Growing2, toCount - 1, 0, EmptyValueUsed);
   
   return(0);
}

4. Die Indicator_Painting Bibliothek

Als Ergebnis aller abgeschlossenen Arbeiten gibt es eine Indicator_Painting Bibliothek.

Sie wurde speziell für die Automatisierung der beschriebenen Vorgänge, mit einigen Ergänzungen entworfen.

Hier ist eine Liste der verfügbaren Funktionen:


// ====================================================
// Mark for tops and bottoms
// ====================================================
void MarkExtremums( 
      double values[],        // Indicator values
      double& extremums[],    // Buffer for extremums
      int startIndex,         // Start index for check (it included) 
      int endIndex,           // End index for check (it included)
      int direction,          // DIR_TOP for tops, DIR_BOTTOM for bottoms, DIR_ALL for tops an bottoms
      double emptyValueUsed); // The value used for "empty" mark
      
      
// ====================================================
// Mark for crosses
// ====================================================
void MarkCrosses( 
      double values1[],       // Values of the first indicator
      double values2[],       // Values of the second indicator
      double& crosses[],      // Buffer for their crosses
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      int direction,          // CROSS_UP for up crosses,CROSS_DOWN for down crosses, CROSS_ALL for all crosses
      double emptyValueUsed); // The value used for "empty" mark
      
      
// ====================================================
// Mark for level crosses
// ====================================================
void MarkLevelCrosses( 
      double values[],        // Values of the indicator
      double level,           // Level value for a cross check
      double& crosses[],      // Buffer for the crosses
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      int direction,          // CROSS_UP for up crosses,CROSS_DOWN for down crosses, CROSS_ALL for all crosses
      double emptyValueUsed); // The value used for "empty" mark
      
      
// ====================================================
// Mark for levels
// ====================================================
void MarkLevel( 
      double values[],        // Values of the indicator
      double& level[],        // Buffer for the crosses
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      double levelValue,      // Level value
      int condition,          // Mark condition (LESS_THAN = -1, GREATER_THAN = 1)
      double emptyValueUsed); // The value used for "empty" mark
      
// ====================================================
// Mark for dynamic levels
// ====================================================
void MarkDynamicLevel( 
      double values[],        // Values of the indicator
      double dynamicLevel[],  // Dynamical level values for check
      double& level[],        // Buffer for the crosses
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      int condition,          // меньше (LESS_THAN = -1) или больше уровня (GREATER_THAN = 1)
      double emptyValueUsed); // The value used for "empty" mark
      
// ====================================================
// Mark for direction (upward)
// ====================================================
void MarkGrowing( 
      double values[],        // Values of the indicator
      double& growing1[],     // The first buffer to mark the direction
      double& growing2[],     // The second buffer to mark the direction
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      double emptyValueUsed); // The value used for "empty" mark

// ====================================================
// Mark for direction (downward)
// ====================================================
void MarkReducing( 
      double values[],        // Values of the indicator
      double& reducing1[],    // The first buffer to mark the direction
      double& reducing2[],    // The second buffer to mark the direction
      int startIndex,         // Start index for check (it included)
      int endIndex,           // End index for check (it included)
      double emptyValueUsed); // The value used for "empty" mark

Hier sind einige Beispiele für die Nutzung der Bibliothek:




Um die Bibliothek zu verwenden, sollte folgendes getan werden:

1. Kopieren Sie die Datei "Indicator_Painting.mq4" in den Ordner "experts/libraries"

2. Kopieren Sie die Datei "Indicator_Painting.mqh" in den Ordner "experts/include"

3. Fügen Sie dem Indikator-Code die folgende Befehlszeile hinzu:

#include <Indicator_Painting.mqh>

Jetzt ist es möglich, alle Funktionen der Bibliothek zu nutzen. Weitere Details finden Sie in der Datei "Indicator_Painting.mqh".

Beispiele können Sie in den Dateien finden, die dem Artikel angehängt sind.



Fazit

Der Autor hofft, dass dieser Artikel helfen und die Arbeit einiger Leute vereinfachen wird. Der Autor erachtet das Ziel als erreicht.


Danksagungen

Der Autor möchte sich bei Herrn Viktor Rustamov (granit77) für den Aufgabenvorschlag, die Hilfe und die Kommentare bedanken, die den Artikel verbessert haben.