English Русский 中文 Español 日本語 Português
Die Stärke von ZigZag (Teil II). Beispiele für das Empfangen, Verarbeiten und Anzeigen von Daten

Die Stärke von ZigZag (Teil II). Beispiele für das Empfangen, Verarbeiten und Anzeigen von Daten

MetaTrader 5Beispiele | 23 April 2019, 15:04
1 103 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt


Einführung

Im ersten Teil der Serie habe ich einen modifizierten ZigZag-Indikator und eine Klasse zum Empfangen von Daten dieser Art von Indikatoren beschrieben. Hier werde ich zeigen, wie man Indikatoren entwickelt, die auf diesen Tools basieren, und ein EA für Tests schreiben, der gemäß den Signalen des ZigZag-Indikators handelt.

Als Ergänzung wird der Artikel eine neue Version der Bibliothek EasyAndFast zur Entwicklung grafischer Benutzeroberflächen vorstellen. 

Hauptthemen des Artikels:

  • Indikatoren, die das Preisverhalten definieren;
  • EA mit einer grafischen Oberfläche zum Erfassen der Preisverhaltensstatistik;
  • ein EA zur Berechnung der Anzahl der ZigZag-Indikatorsegmente in bestimmten Bereichen.


Indikatoren, die das Preisverhalten definieren

Betrachten wir drei Indikatoren, die das Preisverhalten erkennen. 

  • FrequencyChangeZZZ berechnet die Frequenz der gegenläufig gerichteten ZigZag-Indikatorsegmente.
  • SumSegmentsZZ berechnet die Summen und deren Durchschnittswerte der ermittelten Segmentreihen.
  • ProzentsatzSegmenteZZ errechnet das prozentuale Verhältnis der Segmentsummen und die Differenzen zwischen ihnen.
  • MultiPorcentageSegmentsZZ definiert die Art der Bildung mehrerer Segmente aus einem höheren Zeitrahmen basierend auf den Werten des vorherigen Indikators PercentageSegmentsZZZ .

Die Codestruktur jedes dieser Indikatoren ist gleich der des im ersten Teil der Serie beschriebenen ZigZag-Indikators. Daher werden wir uns nur mit der Hauptfunktion (FillIndicatorBuffers) befassen, bei der Daten empfangen und Indikatorpuffer befüllt werden.


Indikator FrequenzwechselZZ

Für den Indikator FrequencyChangeZZ ist der Code der Hauptfunktion derselbe wie unten aufgeführt. Barindex und Zeitarray werden der Funktion übergeben. Anschließend wird eine notwendige Anzahl von ZigZag-Indikator- und Zeitarray-Elementen (Quelldaten) aus der aktuellen Zeit des Balkens kopiert. Wenn Quelldaten empfangen werden, werden die Enddaten angefordert. Danach bleibt nur noch, eine Methode aufzurufen, die die Anzahl der Balken in der Segmentreihe zurückgibt. Das Ergebnis wird im aktuellen Element des Indikatorpuffers gespeichert.

//+------------------------------------------------------------------+
//| Ausfüllen der Indikatorpuffer                                    | 
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Abrufen der ZZ-Daten
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Speichern Sie die Anzahl der Balken im eingestellten Segment im Indikatorpuffer.
         segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
         break;
        }
     }
  }

In den Parametern der externen Indikatoren werden wir Folgendes festlegen:

(1) Die Werte sollten auf der Grundlage aller verfügbaren Daten berechnet werden,
(2) Eine Mindestunterschied für eine neue Segmentbildung des ZigZag-Indikators und
(3) Die Anzahl der Extrema für die Gewinnung der endgültigen Daten.

Alle Indikatoren dieses Artikels haben die gleichen Parameter.

 Abb. 1. Die externen Indikatorparameter

Abb. 1. Die externen Indikatorparameter

Der Indikator FrequencyChangeZZ zeigt das Diagramm in einem Subfenster an, wie unten dargestellt. Der ZigZag-Indikator wird zur besseren Sichtbarkeit auf das Hauptchart geladen. Der Indikator zeigt deutlich an, wann sich die Kurse bei der Wahl seiner Richtung verlangsamen.  

 Abb. 2. Indikator FrequenzwechselZZ

Abb. 2. Indikator FrequenzwechselZZ


Indikator SumSegmentsZZZ

Beim Indikator SumSegmentsZZ sieht die Hauptfunktion zur Datenbeschaffung wie in der folgenden Auflistung dargestellt aus. Alles ist gleich wie im vorherigen Beispiel. Der einzige Unterschied besteht darin, dass hier drei Indikatorpuffer für Aufwärts- und Abwärtssegmente separat befüllt werden. Ein weiterer Puffer wird verwendet, um den Durchschnitt dieser Parameter auf den aktuellen Werten zu berechnen.

//+------------------------------------------------------------------+
//| Ausfüllen der Indikatorpuffer                                    | 
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Abrufen der ZZ-Daten
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Abrufen der Daten der Segmente
         segments_up_total_buffer[i] =zz.SumSegmentsUp();
         segments_dw_total_buffer[i] =zz.SumSegmentsDown();
         segments_average_buffer[i]  =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2;
         break;
        }
     }
  }

Nachdem Sie SumSegmentsZZ auf das Chart gestartet haben, sehen Sie das Ergebnis wie im Screenshot unten. Hier sehen wir, dass, nachdem die blaue Linie die rote überschritten hat, die Summe der Aufwärtssegmente größer ist als die Summe der Abwärtssegmente. Die Situation wird umgekehrt, wenn die rote Linie die blaue überschreitet. Nur Experimente im Strategietester können uns sagen, ob dies eine zuverlässige Informationsquelle über die zukünftige Preisentwicklung ist. Auf den ersten Blick gilt: Je länger die Summe der unidirektionalen Segmente die Summe der gegenüberliegenden Segmente übersteigt, desto höher ist die Umkehrwahrscheinlichkeit. 

 Abb. 3. Indikator SumSegmentsZZZ

Abb. 3. Indikator SumSegmentsZZZ


Indikator ProzentsegmenteZZZZ

Betrachten wir jetzt den Indikator PercentageSegmentsZZ. Wie im vorherigen Fall sollten in der Hauptfunktion des Indikators drei Indikatorpuffer befüllt werden: je ein Puffer für die prozentualen Anteile der nach oben und unten gerichteten Segmentsummen (1) sowie ein Puffer (3) für die Differenz zwischen diesen Werten

//+------------------------------------------------------------------+
//| Ausfüllen der Indikatorpuffer                                    | 
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Abrufen der ZZ-Daten
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Abrufen der Daten der Segmente
         double sum_up =zz.SumSegmentsUp();
         double sum_dw =zz.SumSegmentsDown();
         double sum    =sum_up+sum_dw;
         //--- Prozentuales Verhältnis und Differenz
         if(sum>0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            break;
           }
        }
     }
  }

Das Ergebnis ist unten dargestellt. Versuchen wir es zu verstehen. Wenn die Differenz in den Prozentverhältnissen zwischen den Mengen der multidirektionalen Segmente kleiner als ein bestimmter Schwellenwert ist, kann dies als flach, als eine Seitwärtsbewegung, angesehen werden. In diesem Fall sollten wir auch bedenken, dass sich die Verhältnisse oft ändern sollten, da sich der Preis über einen längeren Zeitraum in eine Richtung verschieben kann, während die Differenz geringer ist als das vom Optimierer gewählte Niveau. In diesen Fällen sollten wir die Modelle anwenden, die die Bildung der Muster in einer bestimmten Reihenfolge berücksichtigen.

 Abb. 4. Indikator ProzentsegmenteZZZZ

Abb. 4. Indikator ProzentsegmenteZZZZ


Indikator MultiPercentageSegmentsZZ

Im vorherigen Artikel haben wir einen EA beschrieben, der ZigZag-Indikatorendaten aus höheren und niedrigeren Zeiträumen gleichzeitig analysiert. So war es möglich, das Verhalten des Preises innerhalb der Segmente aus dem höheren Zeitrahmen heraus genauer zu analysieren. Mit anderen Worten, wir haben definiert, wie sich die höheren Zeitrahmensegmente in einem niedrigeren Zeitrahmen gebildet haben. Schauen wir uns an, wie diese Gruppe von Parametern in Form eines separaten Indikators aussehen wird, der diese Werte auf der Preisentwicklung anzeigt.

Wie im EA des vorherigen Artikels werden wir vier Werte der Differenz zwischen den Prozentverhältnissen der gegensätzlichen Segmentbeträge erhalten: ein Wert ist für den höheren Zeitrahmen und drei Werte für den niedrigeren. Die Werte werden von den letzten drei Segmenten des ZigZag-Indikators auf dem höheren Zeitfenster berechnet. Die Farben der Indikatorpuffer sind die gleichen wie im EA des vorherigen Teils. Danach werden wir ein EA entwickeln, um den Indikator zu testen, so dass wir viel einfacher verstehen können, welche Daten und für welchen Zeitraum wir auf der Grafik beobachten.

//---- Anzahl der Puffer
#property indicator_buffers 4
#property indicator_plots   4
//--- Farbe der Farbpuffer
#property indicator_color1 clrSilver
#property indicator_color2 clrRed
#property indicator_color3 clrLimeGreen
#property indicator_color4 clrMediumPurple

Deklarieren von vier Instanzen der Klasse CZigZagModule:

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

Fügen wir die Möglichkeit hinzu, einen Zeitrahmen für einen höheren Indikator für externe Parameter festzulegen:

input             int NumberOfBars    =0;         //Anzahl der Bars zur Berechnung des ZZ
input             int MinImpulseSize  =0;         // Minimale Points eines Strahls
input             int CopyExtremum    =5;         // Kopieren der Extrema
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Höhere Zeitrahmen

Die Hauptfunktion zum Befüllen der Indikatorpuffer ist wie folgt implementiert. Zuerst werden die Quelldaten aus dem höheren Zeitrahmen abgerufen, der in den externen Parametern angegeben ist. Dann erhalten wir die endgültigen Daten und speichern den Parameterwert. Als nächstes erhalten wir die Daten zu den drei Indikatorsegmenten aus dem höheren Zeitrahmen. Danach werden alle Indikatorpuffer befüllt. Ich habe zwei separate Codeblöcke entwickelt, damit der Indikator korrekt berechnet werden konnte mit historischen Daten und mit dem letzten Balken in Echtzeit/Tester

//+------------------------------------------------------------------+
//| Befüllen der Indikatorpuffer                                     |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
  {
   int index=total-i-1;
   int copy_total=1000;
   int h_buff=2,l_buff=3;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Datenabfrage des höheren Zeitrahmens
   datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- Abfrage der endgültigen Daten des höheren Zeitrahmens
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- Erste Daten des Segments
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Zweite Daten des Segments
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Dritte Daten des Segments
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Bei der letzten Bar
   if(i<total-1)
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
     }
//--- Mit der Historie
   else
     {
      //--- Falls es einen neuen Balken im höheren Zeitrahmen gibt
      if(new_bar_time!=t_zz_buffer_temp[0])
        {
         new_bar_time=t_zz_buffer_temp[0];
         //---
         if(i>2)
           {
            int f=1,s=2;
            buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
            buffer_segment_0[i-f]    =buffer_segment_0[i-s];
            buffer_segment_1[i-f]    =buffer_segment_1[i-s];
            buffer_segment_2[i-f]    =buffer_segment_2[i-s];
           }
        }
      else
        {
         buffer_zz_higher_tf[i] =htf_value;
         buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
         buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
         buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
        }
     }
  }

Machen wir eine Kopie des EA aus dem vorherigen Artikel und fügen ein paar Zeilen hinzu, um den Indikator MultiPercentageSegmentsZZZ zu testen. Hinzufügen des externen Parameters zum Einstellen eines höheren Zeitrahmens. Damit der Indikator während eines EA-Tests im Tester im Visualisierungsmodus angezeigt wird, genügt es, sein Handle zu haben. 

//--- Externe Parameter
input            uint CopyExtremum    =3;         // Kopieren der Extrema
input             int MinImpulseSize  =0;         // Min. impulse size
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Höhere Zeitrahmen

...

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {

...

//--- Pfad zum ZZ-Indikator
   string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
//--- Abrufen des Indikatorhandles
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false);
   zz_handle           =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe);

...

   return(INIT_SUCCEEDED);
  }

Und so schaut er dann im Tester aus:

 Abb. 5. Indikator MultiPercentageSegmentsZZ

Abb. 5. Indikator MultiPercentageSegmentsZZ

Alle oben beschriebenen Indikatoren können in verschiedenen Kombinationen und gleichzeitig für verschiedene Zeitrahmen verwendet werden. Lassen Sie uns nun mit den beschriebenen Tools einige Statistiken über den Satz von Symbolen sammeln, um zu verstehen, welche von ihnen besser für den Handel im Preiskanal geeignet sind.


EA zum Sammeln und Anzeigen der Statistiken

Als Ergänzung stellt der Artikel eine neue Version der Bibliothek EasyAndFast zur Entwicklung grafischer Benutzeroberflächen vor. Hier werden wir nur die neuen Eigenschaften der Bibliothek auflisten:

  • Ändern der Hintergrundfarbe jeder Tabellenzelle (CTable Klasse).
  • Sortierrichtung.
  • Wenn der entsprechende Modus aktiviert ist, wird eine Zeile beim Anklicken eines Kontrollkästchens in der Tabellenzelle nicht markiert.
  • Unterstützung für den Ziffernblock in der Klasse CKeys hinzugefügt.
  • Die Klasse CFrame zum Kombinieren von Elementen zu Gruppen wurde hinzugefügt:

 Abb. 6. Kombinieren der Elemente in Gruppen

Abb. 6. Kombinieren der Elemente in Gruppen

  • Vertikales Scrollen in Tabellen und Listen.
  • Es wurde die Klasse CWndCreate hinzugefügt, die grundlegende Vorlagenmethoden für die schnelle Erstellung der meisten Elemente enthält. Sie sollte als Basis für die benutzerdefinierte Klasse verwendet werden. Die Verwendung dieser Klasse erlaubt es Ihnen, die Deklaration und Implementierung derselben Methoden zur Erzeugung von Elementen in verschiedenen Projekten nicht zu wiederholen, was die Entwicklung erheblich beschleunigt. 
  • In der Klasse CElement wurde die Prüfung auf die korrekte Reihenfolge der Erzeugung von Elementen hinzugefügt.
  • In der Klasse CWndEvents wird die ID nach dem Entfernen eines Elements immer zurückgesetzt.
  • Die Methode GetActiveWindowIndex() wurde zur Methode CWndEvents zum Empfangen des aktivierten Fensterindex hinzugefügt.
  • Die Klasse CListView wurde korrigiert. Einige Hilfsfelder sollten in der Methode Clear() zurückgesetzt werden, um einen Array-Überlauf in anderen Methoden der Klasse CListView zu vermeiden.

Die neue Version der Bibliothek kann aus der CodeBase heruntergeladen werden. 

Als Nächstes erstellen wir einen Test-EA zum Sammeln einiger Statistiken mit der neuen Version der EasyAndFast Bibliothek. Wir beginnen mit der Entwicklung der grafischen Benutzeroberfläche (GUI) der Anwendung und gehen dann zu den Methoden zum Sammeln und Anzeigen von Statistiken über.

Lassen Sie uns definieren, welche GUI-Steuerelemente wir benötigen:

  • Kontrollformulare.
  • Statusleiste.
  • Eingabefeld zum Sortieren von Währungen, die in der Liste der Marktbeobachtungsfenster gesammelt werden sollen.
  • Dropdown-Kalender, um Start- und Enddatum für die Erstellung von Statistiken anzuzeigen.
  • Eingabefeld zur Einstellung des Indikatorlevels.
  • Datenanfrage-Taste.
  • Tabelle zur Anzeige der gesammelten Daten.
  • Fortschrittsanzeige.

Wie bereits erwähnt, sollte die Klasse CWndCreate in die benutzerdefinierte Klasse als Basisklasse aufgenommen werden, um eine GUI schneller und komfortabler entwickeln zu können. Die vollständige Verbindung sieht wie folgt aus: CWndContainer -> CWndEvents -> CWndCreate -> CProgramm. Das Vorhandensein der Klasse CWndCreate ermöglicht es, GUI-Elemente in einer einzigen Zeile zu erstellen, ohne separate Methoden in einer benutzerdefinierten Klasse erstellen zu müssen. Die Klasse enthält verschiedene Vorlagen für fast alle Bibliothekselemente. Sie können bei Bedarf neue Vorlagen hinzufügen. 

Um eine GUI zu erstellen, deklarieren Sie die in der obigen Liste enthaltenen Elemente wie in der folgenden Codeliste gezeigt. Die aktuelle Version der Klasse CWndCreate hat keine schnelle Tabellenerstellungsvorlage, deshalb lassen Sie uns diese Methode selbst entwickeln

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndCreate.mqh>
//+------------------------------------------------------------------+
//| Klasse zum Erstellen der Anwendung                               |
//+------------------------------------------------------------------+
class CProgram : public CWndCreate
  {
private:
   //--- Fenster
   CWindow           m_window;
   //--- Statusleiste
   CStatusBar        m_status_bar;
   //--- Dropdown-Kalender
   CDropCalendar     m_from_date;
   CDropCalendar     m_to_date;
   //--- Tasten
   CButton           m_request;
   //--- Eingabefelder
   CTextEdit         m_filter;
   CTextEdit         m_level;
   //--- Kombinationsfeld
   CComboBox         m_data_type;
   //--- Tabellen
   CTable            m_table;
   //--- Fortschrittsanzeige
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Erstellen einer GUI
   bool              CreateGUI(void);
   //---
private:
   //--- Tabellen
   bool              CreateTable(const int x_gap,const int y_gap);
  };

Um eine grafische Oberfläche mit solch einem Inhalt zu erstellen, rufen Sie einfach die notwendigen Methoden der Klasse CWndCreate auf, indem Sie die Werte der Eigenschaften als Argumente angeben, wie in der Codeauflistung unten gezeigt. Um eine Eigenschaft zu definieren, auf die sich ein Methodenparameter bezieht, setzen Sie einen Textcursor darin und klicken Sie auf Ctrl + Shift + Space:

 Abb. 7. Ansicht der Parameter der Methode

Abb. 7. Ansicht der Parameter der Methode

Wenn Sie zusätzliche Eigenschaften einstellen müssen, können Sie dies auf die gleiche Weise tun, wie im Beispiel mit dem Eingabefeld Währungsfilter. Hier wird angezeigt, dass das Kontrollkästchen direkt nach der Erstellung des Elements standardmäßig aktiviert werden soll.

//+------------------------------------------------------------------+
//| Erstellen einer GUI                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Erstellen des Formulars des Steuerelements
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true))
      return(false);
//--- Statusleiste
   string text_items[1];
   text_items[0]="For Help, press F1";
   int width_items[]={0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items))
      return(false);
//--- Eingabefeld des Währungsfilters
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK"))
      return(false);
   else
      m_filter.IsPressed(true);
//--- Dropdown-Kalender
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01'))
      return(false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent()))
      return(false);
//--- Eingabefeld für die Angabe der Level
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30))
      return(false);
//--- Schaltfläche
   if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70))
      return(false);
//--- Tabelle
   if(!CreateTable(2,75))
      return(false);
//--- Fortschrittsanzeige
   if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3))
      return(false);
//--- Ende der Entwicklung der GUI
   CWndEvents::CompletedGUI();
   return(true);
  }

Im Falle einer Tabelle erstellen wir eine benutzerdefinierte Methode, da es sich um ein komplexes Element mit einer großen Anzahl von Eigenschaften handelt, die vor der Erstellung eines Elements angegeben werden sollten. Sie soll vier Spalten enthalten. Die erste zeigt Währungspaare an. Die übrigen zeigen die statistischen Daten über drei Zeiträume: M5, H1 und H8.

//+------------------------------------------------------------------+
//| Erstellen der Tabelle                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL    1
//--- Sichern des Pointers auf das Hauptelement
   m_table.MainPointer(m_window);
//--- Array der Spaltenbreite
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,50);
   width[0]=80;
//--- Array der Textabstände in der Spalte auf der x-Achse
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,7);
//--- Array der Textausrichtungen in Spalten
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_LEFT;
//--- Eigenschaften
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ShowHeaders(true);
   m_table.IsSortMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoYResizeMode(true);
   m_table.AutoXResizeRightOffset(2);
   m_table.AutoYResizeBottomOffset(24);
//--- Erstellen des Kontrollelements
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Kopfzeile
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
//--- Ein Objekt in das allgemeine Array von Objektgruppen eintragen
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

Betrachten wir nun die Methoden zur Datenbeschaffung. Zuerst müssen wir Symbole bekommen, mit denen wir arbeiten sollen. In dieser EA-Version werden wir Daten von Forex-Symbolen verwenden. Gleichzeitig werden wir Symbole ausschließen, bei denen der Handel deaktiviert ist. Hier benötigen wir auch die Hilfsmethode CheckFilterText(), um das Symbol mit dem Filter zu überprüfen. In das Eingabefeld können Benutzer kommagetrennte Textwerte eingeben, die die Namen der Symbole stehen sollten. Wenn das Kontrollkästchen für das Feld deaktiviert ist oder der Text nicht eingegeben wird , wird die Prüfung nicht durchgeführt. Wenn die Prüfungen bestanden sind und eine Übereinstimmung gefunden werden soll, wird der eingegebene Text in Teilzeichenketten unterteilt und die Suche nach einer benötigten Zeichenkette durchgeführt. 

class CProgram : public CWndCreate
  {
private:
   //--- Prüfen der gefilterten Symbole
   bool              CheckFilterText(const string symbol_name);
  };
//+------------------------------------------------------------------+
//| Prüfen der gefilterten Symbole                                   |
//+------------------------------------------------------------------+
bool CProgram::CheckFilterText(const string symbol_name)
  {
   bool check=false;
//--- Wenn der Filter das Symbol erlaubt hat
   if(!m_filter.IsPressed())
      return(true);
//--- Bei einer Texteingabe
   string text=m_filter.GetValue();
   if(text=="")
      return(true);
//--- Aufteilen in Teilstrings
   string elements[];
   ushort sep=::StringGetCharacter(",",0);
   ::StringSplit(text,sep,elements);
//--- Aus Übereinstimmung prüfen
   int elements_total=::ArraySize(elements);
   for(int e=0; e<elements_total; e++)
     {
      //--- Löschen der Leerzeichen am Rande
      ::StringTrimLeft(elements[e]);
      ::StringTrimRight(elements[e]);
      //--- Bei einer Übereinstimmung
      if(::StringFind(symbol_name,elements[e])>-1)
        {
         check=true;
         break;
        }
     }
//--- Ergebnis
   return(check);
  }

In der Methode CProgram::GetSymbols() übergeben wir alle auf dem Server vorhandenen Symbole in einer Schleife und befüllen mit den Symbolen, die den angegebenen Kriterien entsprechen, das Array. In der allgemeinen Schleife werden alle Symbole aus dem Fenster Market Watch gelöscht. Nur die im Array enthaltenen werden anschließend dem Fenster wieder hinzugefügt. 

class CProgram : public CWndCreate
  {
private:
   //--- Symbolarray
   string            m_symbols[];
   //---
private:
   //--- Abrufen der Symbole
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| Abrufen der Symbole                                              |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
//--- Ausführung
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(0,1);
//--- Löschen des Symbolarrays
   ::ArrayFree(m_symbols);
//--- Sammeln der Forexsymbole des Arrays
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Abrufen der Symbolnamen
      string symbol_name=::SymbolName(i,false);
      //--- Ausblenden des Market Watch
      ::SymbolSelect(symbol_name,false);
      //--- Ist es kein Forexsymbol, weiter zum Nächsten
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Wenn der Handel micht erlaubt ist, weiter zum Nächsten
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
         continue;
      //--- Prüfen der gefilterten Symbole
      if(!CheckFilterText(symbol_name))
         continue;
      //--- Sichern eines Symbols im Array
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1,1000);
      m_symbols[array_size]=symbol_name;
     }
//--- Ist der Array leer, wird das aktuelle Symbol zum Standard
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=_Symbol;
     }
//--- Anzeigen im Fenster der Marktübersicht
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

Um Daten über die gesammelten Symbole zu erhalten, sollten wir zunächst die Indikatoren-Handles auf ihnen erhalten. Jedes Mal, wenn wir den Indikator-Handle erhalten, müssen wir bis zum Ende seiner Berechnung warten, bevor wir seine Daten zur weiteren Analyse kopieren. Nachdem alle Daten eingegangen sind, werden die notwendigen Berechnungen durchgeführt. 

Dazu wird die Methode CProgram::GetSymbolsData() verwendet. Es werden zwei Parameter akzeptiert: Symbol und Zeitrahmen. Nach Erhalt des Indikatorhandles ermitteln wir, wie viele Balken im angegebenen Zeitbereich vorhanden sind. Die Zeitspanne kann über die GUI-Steuerelemente der Anwendung angegeben werden. Als Nächstes versuchen wir, die Menge der berechneten Indikatorendaten zu erhalten. Die Berechnung des Indikators kann nicht sofort nach Erhalt des Handles abgeschlossen werden. Wenn die Funktion BarsCalculated() -1 zurückgibt, versuchen wir daher erneut, einen gültigen Wert zu erhalten, bis er gleich oder größer als die Gesamtzahl der Balken im angegebenen Zeitbereich ist.

Nachdem die Indikatordaten berechnet wurden, können wir versuchen, sie in das Array zu bekommen. Es kann auch mehrere Versuche dauern, bis die Menge auch größer oder gleich der Gesamtzahl der Balken ist. 

Wenn die Indikatoren erfolgreich in das Array kopiert wurden, bleibt nur noch, die notwendigen Berechnungen durchzuführen. In diesem Fall berechnen wir das prozentuale Verhältnis der Gesamtdatenmenge zu dem Betrag, in dem der Indikatorwert über dem angegebenen Niveau liegt. Diese Ebene kann auch in der Benutzeroberfläche der Anwendung angegeben werden. 

Am Ende des Verfahrens entfernen wir das Indikatorhandle und geben seinen Berechnungsteil frei. Die Methode CProgram::GetSymbolsData() wird für eine ausgewählte Liste von Symbolen und mehreren Zeitrahmen mehrfach aufgerufen. Die Berechnung für jeden von ihnen sollte nur einmal durchgeführt werden, und der resultierende Wert wird in der GUI-Tabelle angezeigt, so dass die Handles nicht mehr benötigt werden und entfernt werden können. 

class CProgram : public CWndCreate
  {
private:
   //--- Abrufen der Symboldaten
   double            GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Abrufen der Symboldaten                                          |
//+------------------------------------------------------------------+
double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period)
  {
   double result       =0.0;
   int    buffer_index =2;
//--- Indikatorhandle
   string path   ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5";
   int    handle =::iCustom(symbol,period,path,0,0,5);
   if(handle!=INVALID_HANDLE)
     {
      //--- Kopieren der Daten der angegebenen Spanne
      double   data[];
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      //--- Anzahl der Bars in der angegebenen Spanne
      int bars_total=::Bars(symbol,period,start_time,end_time);
      //--- Anzahl der Bars in der angegebenen Spanne
      int bars_calculated=::BarsCalculated(handle);
      if(bars_calculated<bars_total)
        {
         while(true)
           {
            ::Sleep(100);
            bars_calculated=::BarsCalculated(handle);
            if(bars_calculated>=bars_total)
               break;
           }
        }
      //--- Datenabruf
      int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
      if(copied<1)
        {
         while(true)
           {
            ::Sleep(100);
            copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
            if(copied>=bars_total)
               break;
           }

        }
      //--- Verlassen, wenn keine Daten angekommen sind
      int total=::ArraySize(data);
      if(total<1)
         return(result);
      //--- Zählen der Wiederholungen
      int counter=0;
      for(int k=0; k<total; k++)
        {
         if(data[k]>(double)m_level.GetValue())
            counter++;
        }
      //--- Prozentsatz
      result=((double)counter/(double)total)*100;
     }
//--- Freigeben des Indikators
   ::IndicatorRelease(handle);
//--- Werterückgabe
   return(result);
  }

Jedes Mal, wenn eine neue Symbolliste erstellt wird, muss die Tabelle neu aufgebaut werden. Dazu löschen wir einfach alle Zeilen und tragen die erforderlichen Werte ein.

class CProgram : public CWndCreate
  {
private:
   //--- Wiederherstellen der Tabelle
   void              RebuildingTables(void);
  };
//+------------------------------------------------------------------+
//| Wiederherstellen der Tabelle                                     |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Entfernen aller Zeilen
   m_table.DeleteAllRows();
//--- Daten ergänzen
   int symbols_total=::ArraySize(m_symbols);
   for(int i=1; i<symbols_total; i++)
      m_table.AddRow(i);
  }

Die Methode CProgram::SetData() wird verwendet, um die Tabellenspalten mit Daten zu befüllen. An ihn werden zwei Parameter (Spaltenindex und Zeitrahmen) übergeben. Hier bewegen wir uns durch die Zellen einer bestimmten Spalte und tragen in einer Schleife die berechneten Werte ein. Der Fortschrittsbalken zeigt ein Symbol und einen Zeitrahmen an, deren Daten gerade empfangen wurden, so dass die Benutzer verstehen, was gerade passiert.

class CProgram : public CWndCreate
  {
private:
   //--- Eintragen der Werte in die angegebene Spalte
   void              SetData(const int column_index,const ENUM_TIMEFRAMES period);
   //--- Zeitrahmen als Zeichenkette
   string            GetPeriodName(const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Eintragen der Werte in die angegebene Spalte                     |
//+------------------------------------------------------------------+
void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period)
  {
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
     {
      double value=GetSymbolsData(m_symbols[r],period);
      m_table.SetValue(column_index,r,string(value),2,true);
      m_table.Update();
      //--- Ausführung
      m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]...");
      m_progress_bar.Update(r,m_table.RowsTotal());
     }
  }
//+------------------------------------------------------------------+ 
//| Rückgabe der Periodenlänge als Zeichenkette                      |
//+------------------------------------------------------------------+ 
string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period)
  {
   return(::StringSubstr(::EnumToString(period),7));
  }

Die Hauptmethode zum Befüllen der Tabelle mit Daten ist CProgram::SetDataToTable(). Der Tabelle wird hier zuerst wieder aufgebaut. Als Nächstes müssen wir Header und Datentyp darin festlegen (TYPE_DOUBLE). Wir setzten die gesammelten Symbole in die erste Spalte. Unmittelbares Neuzeichnen der Tabelle, um die Änderungen anzuzeigen.

Jetzt können wir mit dem Abrufen der Indikatordaten für alle angegebenen Symbole und Zeitrahmen beginnen. Wir rufen dazu einfach die Methode CProgram::SetData() auf, der der Spaltenindex und der Zeitrahmen als Parameter übergeben wird. 

class CProgram : public CWndCreate
  {
private:
   //--- Befüllen der Tabelle mit Daten
   void              SetDataToTable(void);
  };
//+------------------------------------------------------------------+
//| Befüllen der Tabelle mit Daten                                   |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable(void)
  {
//--- Ausführung
   m_progress_bar.LabelText("Data preparation...");
   m_progress_bar.Update(0,1);
//--- Wiederherstellen der Tabelle
   RebuildingTable();
//--- Kopfzeile
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
   for(uint i=1; i<m_table.ColumnsTotal(); i++)
      m_table.DataType(i,TYPE_DOUBLE);
//--- Eintragen der Werte in die erste Spalte
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
      m_table.SetValue(0,r,m_symbols[r],0,true);
//--- Einblenden der Tabelle
   m_table.Update(true);
//--- Befüllen der verbleibenden Spalten mit Daten
   SetData(1,PERIOD_M5);
   SetData(2,PERIOD_H1);
   SetData(3,PERIOD_H8);
  }

Bevor wir neue Daten mit der Methode CProgram::GetData() empfangen, sollten wir den Fortschrittsbalken mit Hilfe der Methode CProgram::StartProgress() sichtbar machen. Nachdem neue Daten empfangen wurden, blenden Sie den Fortschrittsbalken aus und entfernen Sie den Fokus von der gedrückten Taste. Rufen Sie dazu die Methode CProgram::EndProgress() auf. 

class CProgram : public CWndCreate
  {
private:
   //--- Datenabruf
   void              GetData(void);

   //--- Ausführen (1) Start und (2) Ende
   void              StartProgress(void);
   void              EndProgress(void);
  };
//+------------------------------------------------------------------+
//| Datenabfrage                                                     |
//+------------------------------------------------------------------+
void CProgram::GetData(void)
  {
//--- Start der Ausführung
   StartProgress();
//--- Abrufen der Symbolliste
   GetSymbols();
//--- Befüllen der Tabelle mit Daten
   SetDataToTable();
//--- Ende der Ausführung
   EndProgress();
  }
//+------------------------------------------------------------------+
//| Star der Ausführung                                              |
//+------------------------------------------------------------------+
void CProgram::StartProgress(void)
  {
   m_progress_bar.LabelText("Please wait...");
   m_progress_bar.Update(0,1);
   m_progress_bar.Show();
   m_chart.Redraw();
  }
//+------------------------------------------------------------------+
//| Ende der Ausführung                                              |
//+------------------------------------------------------------------+
void CProgram::EndProgress(void)
  {
//--- Ausblenden der Fortschrittsanzeige
   m_progress_bar.Hide();
//--- Schaltfläche aktualisieren
   m_request.MouseFocus(false);
   m_request.Update(true);
   m_chart.Redraw();
  }

Wenn ein Nutzer auf Request klickt, wird das benutzerdefinierte Ereignis ON_CLICK_BUTTON generiert, und wir können eine gedrückte Schaltfläche über die Element-ID definieren. Wenn dies die Schaltfläche Request ist, starten Sie den Datenerfassungsprozess

In der Methode zur Tabellenerstellung haben wir die Möglichkeit aufgenommen, die Tabelle zu sortieren, indem wir auf die Spaltenköpfe klicken. Das nutzerdefinierte Ereignis ON_SORT_DATA wird jedes Mal generiert, wenn wir dies tun. Wenn das Ereignis empfangen wird, sollte die Tabelle aktualisiert werden, um die Änderungen anzuzeigen. 

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis eines Tastendrucks
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      if(lparam==m_request.Id())
        {
         //--- Datenabruf
         GetData();
         return;
        }
      //---
      return;
     }
//--- Ereignisse der Tabellensortierung
   if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA)
     {
      if(lparam==m_table.Id())
        {
         m_table.Update(true);
         return;
        }
      //---
      return;
     }
  }

Nun, lassen Sie uns die Ergebnisse sehen. Wenn wir das Programm kompilieren und in das Diagramm laden, ist das Ergebnis wie im Screenshot unten. Die folgenden Parameter sind standardmäßig eingestellt:

  • Das Eingabefeld Symbolfilter ist aktiviert. Es zeigt an, dass es notwendig ist, Daten nur für Symbole zu abzurufen, die USD in ihrem Namen tragen.
  • Die Daten sollten innerhalb des Zeitintervalls 2018.01.01.01 - 2018.12.21 abgerufen werden.
  • Der Levelwert , der als Bezugspunkt für die Berechnungen verwendet wird, wird auf 30 gesetzt.
  • In dieser Version sind die Zeitrahmen, in denen die Berechnungen durchgeführt werden, im Code fest vorgegeben: M5, H1 und H8.

 Abb. 8. GUI einer MQL-Anwendung

Abb. 8. GUI einer MQL-Anwendung

Ein Klick auf Request startet die Datenabfrage:

 Abb. 9. Datenempfang

Abb. 9. Datenempfang

Nach dem Erhalt aller Daten, können wir sie sortieren:

 Abb. 10. Sortieren der Tabellendaten

Abb. 10. Sortieren der Tabellendaten

Sie können diese Anwendung modifizieren und verwenden, um einige Ihrer Aufgaben zu lösen. Die Tabelle kann mit beliebigen anderen Parametern befüllt werden.

Im Folgenden werde ich ein weiteres Beispiel geben, das zeigt, wie die Sichtbarkeit der Tabellendaten noch weiter verbessert werden kann. Wie ich bereits am Anfang dieses Abschnitts erwähnt habe, bietet die neueste Version der EasyAndFast Bibliothek die Möglichkeit, die Hintergrundfarbe der Tabellenzellen einzustellen. Dies ermöglicht es Ihnen, die Tabelle nach Belieben zu formatieren, wie es in verschiedenen Tabelleneditoren der Fall ist. Der folgende Screenshot zeigt die Formatierung von Daten in Excel-Tabellen. Jede Zelle hat ihre eigene Hintergrundfarbe, die auch beim Sortieren von Arrays auf dem gleichen Wert bleibt.

 Abb. 11. Farbskalen in Excel

Abb. 11. Farbskalen in Excel

Eine solche Formatierung ermöglicht eine schnelle, visuelle Datenanalyse. 

Lassen Sie uns kleine Änderungen und Ergänzungen an der oben betrachteten MQL-Anwendung vornehmen. Um eine eindeutige Farbe für jede Tabellenzelle festzulegen, deaktivieren Sie die Formatierung im Zebra-Stil. Entkommentieren Sie diese Codezeile.

// m_table.IsZebraFormatRows(clrWhiteSmoke);

Erstellen wir nun die Methode CProgram::SetColorsToTable() zur Tabellenformatierung. Die Klasse CColors ist für das Arbeiten mit Farbe zu verwenden. Sie ist bereits in der Bibliothek für die Erstellung von GUIs vorhanden, so dass es nicht notwendig ist, die Datei in das Projekt aufzunehmen. Wie deklarieren zwei Arrays für die Arbeit: (1) Anordnung zum Erhalten von Gradientenfarben und (2) Anordnung von Farben, aus denen der Gradient gebildet werden soll. Wir werden den Dreifarben-Gradienten erstellen. Je niedriger der Wert, desto roter wird die Farbe (clrTomato). Je höher der Wert, desto blauer wird er (clrCornflowerBlue). Wir fügen die weiße Farbe hinzu, um diese beiden Farbzonen zu trennen. 

Wir definieren die Größe der Wertebereiche vom Minimum bis zum Maximum. Dies ist die Größe des Gradienten-Arrays. Die Methode CColors::Gradient() wird verwendet, um die Array-Größe einzustellen und auszufüllen. Farben von Tabellenzellen werden in der letzten Schleife gesetzt. Um den Array-Bereich nicht zu verlassen, wird der Index berechnet als der Zellenwert minus dem Bereichsminimumwert. Am Ende der Methode wird die Tabelle aktualisiert, um die durchgeführten Änderungen anzuzeigen.

class CProgram : public CWndCreate
  {
private:
   //--- Ausfüllen der Tabelle mit der Hintergrundfarbe der Zellen
   void              SetColorsToTable(void);
  };
//+------------------------------------------------------------------+
//| Formatieren der Tabelle                                          |
//+------------------------------------------------------------------+
void CProgram::SetColorsToTable(void)
  {
//--- Für die Arbeit mit der Q"uelle
   CColors clr;
//--- Array für den Gradienten
   color out_colors[];
//--- Gradient mit drei Farben
   color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- Finden des niedrigsten und höchsten Tabellenwertes
   double max =0;
   double min =100;
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         max =::fmax(max,(double)m_table.GetValue(c,r));
         min =::fmin(min,(double)m_table.GetValue(c,r));
        }
     }
//--- Korrektur auzf die nächst niedrigere Ganzzahl
   max =::floor(max);
   min =::floor(min);
//--- Abrufen der SPanne
   int range =int(max-min)+1;
//--- Abrufen der Gradientenarrays der Farben
   clr.Gradient(colors,out_colors,range);
//--- Setzen der Hintergrundfarbe der Zellen
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         int index=(int)m_table.GetValue(c,r)-(int)min;
         m_table.BackColor(c,r,out_colors[index],true);
        }
     }
//--- Aktualisieren der Tabelle
   m_table.Update();
  }

Unten sehen Sie, wie dies in der GUI aussieht. In diesem Fall zeigen die Ergebnisse, dass je kleiner der Wert, desto geringer die Anzahl der Trends in dem betrachteten Bereich. Es wäre ratsam, ein möglichst breites Spektrum an Daten festzulegen, um Informationen mit so vielen Daten wie möglich zu erhalten. 

 Abb. 12. Farbskala zur Visualisierung von Tabellendaten

Abb. 12. Farbskala zur Visualisierung von Tabellendaten

Je größer die Bandbreite der Daten, desto mehr Daten werden verwendet und desto mehr Zeit wird benötigt, um Daten zu generieren und die Parameter zu berechnen. Wenn nicht genügend Daten vorhanden sind, wird versucht, diese vom Server herunterzuladen. 


Zählen der Anzahl der Segmente nach Größe

Nun entwickeln wir ein Programm, das die Anzahl der Segmente nach ihrer Größe zählt. Wir kopieren den EA aus dem vorherigen Abschnitt und nehmen die notwendigen Änderungen und Ergänzungen vor. Jetzt wird es zwei Tabellen geben. Die erste wird nur eine Spalte mit einer Liste der zu untersuchenden Symbol haben. Die zweite verwendet zwei Datenspalten: (1) Erhöhen von Bereichen in Punkten und (2) die Anzahl der Segmente nach Bereichen in der ersten Spalte. Unten sehen Sie, wie die GUI direkt nach dem Starten der Anwendung auf dem Chart aussieht.

 Abb. 13. Programm zum Berechnen der Anzahl der Segmente nach Größe

Abb. 13. Programm zum Berechnen der Anzahl der Segmente nach Größe

Die Schaltfläche Request ruft die Symbolliste über einen bestimmten Filter ab. Wenn Sie auf Calculate klicken, werden Daten aus dem angegebenen Zeitbereich gesammelt und in der zweiten Tabelle verteilt. 

Im Grunde genommen sind alle Methoden die gleichen geblieben wie im vorherigen EA, also betrachten wir nur die Dinge, die sich auf die zweite Tabelle beziehen. Zuerst müssen wir die Indikatorendaten erhalten. Dies geschieht in der Methode CProgram::GetIndicatorData(). Zuerst verbinden wir uns mit dem ZigZag-Indikator und erhalten dann seine Daten im angegebenen Zeitbereich. Symbol, Zeitrahmen und die Anzahl der erhaltenen Indikatorsegmente werden in der Statusleiste angezeigt. 

class CProgram : public CWndCreate
  {
private:
   //--- Abfrage der Indikatordaten
   void              GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Abrufen der Indikatordaten                                       |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period)
  {
//--- Abfrage des Handles des Indikators
   string path   ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5";
   int    handle =::iCustom(symbol,period,path,0,0);
   if(handle!=INVALID_HANDLE)
     {
      //--- Kopieren der Daten der angegebenen Spanne
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time);
      //--- Datenanzeige in der Statusleiste
      string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal();
      m_status_bar.SetValue(0,text);
      m_status_bar.GetItemPointer(0).Update(true);
     }
//--- Freigeben des Indikators
   ::IndicatorRelease(handle);
  }

Preisspannen mit einer bestimmten Schrittweite sollten für die erste Spalte berechnet werden. Dazu wird die Methode CProgram::GetLevels() verwendet. Um die Anzahl der Bereiche zu definieren, sollten wir zunächst die maximale Segmentgröße im erhaltenen Datensatz ermitteln. Als Nächstes befüllen wir das Array mit den Level in einer bestimmten Schrittweite in einer Schleife, bis der Maximalwert erreicht ist. 

class CProgram : public CWndCreate
  {
private:
   //--- Array der Spanne
   int               m_levels_array[];
   //---
private:
   //--- Abrufen der Level
   void              GetLevels(void);
  };
//+------------------------------------------------------------------+
//| Abrufen der Level                                                |
//+------------------------------------------------------------------+
void CProgram::GetLevels(void)
  {
//--- Freigeben des Arrays
   ::ArrayFree(m_levels_array);
//--- Abrufen der kleinsten Segmentgröße
   int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- Befüllen der Arrays mit den Level
   int counter_levels=0;
   while(true)
     {
      int size=::ArraySize(m_levels_array);
      ::ArrayResize(m_levels_array,size+1);
      m_levels_array[size]=counter_levels;
      //---
      if(counter_levels>max_value)
         break;
      //---
      counter_levels+=(int)m_step.GetValue();
     }
  }

Die Methode CProgram::SetDataToTable2() wird verwendet, um die zweite Tabelle mit Daten zu füllen. Zu Beginn wird geprüft, ob das Symbol in der Liste der ersten Tabelle hervorgehoben ist. Ist dies nicht der Fall, beendet das Programm die Methode, die die Nachricht an das Log des Experten sendet. Wenn eine Zeile in der ersten Tabelle markiert ist, definieren wir das Symbol und erhalten die Daten darüber. Danach werden die oben beschriebenen Methoden aufgerufen, um die Indikatordaten zu empfangen und die Level zu berechnen. Wir erhalten die Indikatordaten mit dem gleichen Zeitrahmen, in dem das EA gestartet wird.

Wenn wir die Anzahl der Ebenen kennen, sind wir in der Lage, eine Tabelle in der gewünschten Größe aufzubauen und mit Werten zu füllen. Füllen Sie zunächst die erste Spalte mit Bereichswerten aus. Anschließend füllen wir die zweite Spalte aus. Indem wir uns nacheinander durch alle Bereiche bewegen, erhöhen wir den Zähler in den Zellen für die Segmente, die in diesen Bereich passen.

class CProgram : public CWndCreate
  {
private:
   //--- Befüllen der Tabelle 2 mit Daten
   void              SetDataToTable2(void);
  };
//+------------------------------------------------------------------+
//| Befüllen der Tabelle 2 mit Daten                                 |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable2(void)
  {
//--- Verlassen, wenn die Zeile nicht hervorgehoben ist
   if(m_table1.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a symbol in the table on the left!");
      return;
     }
//--- Start der Ausführung
   StartProgress();
//--- Tabelle ausblenden
   m_table2.Hide();
//--- Abrufen des Symbol aus der ersten Tabelle
   string symbol=m_table1.GetValue(0,m_table1.SelectedItem());
   m_symbol.Name(symbol);
//--- Abfrage der Indikatordaten
   GetIndicatorData(symbol,_Period);
//--- Abrufen der Level
   GetLevels();
//--- Wiederherstellen der Tabelle
   RebuildingTable2();
//--- Setzen der Spannen in der ersten Spalte
   for(uint r=0; r<(uint)m_table2.RowsTotal(); r++)
      m_table2.SetValue(0,r,(string)m_levels_array[r],0);
//--- Datenabruf für die zweite Spalte
   int items_total=::ArraySize(m_levels_array);
   int segments_total=m_zz.SegmentsTotal();
   for(int i=0; i<items_total-1; i++)
     {
      //--- Ausführung
      m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]...");
      m_progress_bar.Update(i,m_table2.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size=int(m_zz.SegmentSize(s)/m_symbol.Point());
         if(size>m_levels_array[i] && size<m_levels_array[i+1])
           {
            int value=(int)m_table2.GetValue(1,i)+1;
            m_table2.SetValue(1,i,(string)value,0);
           }
        }
     }
//--- Einblenden der Tabelle
   m_table2.Update(true);
//--- Ende der Ausführung
   EndProgress();
  }

Als Beispiel erhalten wir Segmente für den EURUSD seit 2010 bis zur Gegenwart auf dem M5-Chart. Stellen Sie die Bereiche mit dem Schritt 100 fünfstellige Punkte ein. Das Ergebnis ist auf dem folgenden Screenshot zu sehen.

Die Gesamtzahl der Segmente ist 302145. Wie wir sehen können, liegt die maximale Anzahl der Segmente im Bereich von Null bis 100. Weiterhin wird die Anzahl der Segmente von Level zu Level reduziert. Innerhalb des angegebenen Zeitraums erreichte die maximale Segmentgröße 2400 fünfstellige Points. 

 Abb. 14. Ergebnis der Berechnung der Anzahl der Segmente nach Größe

Abb. 14. Ergebnis der Berechnung der Anzahl der Segmente nach Größe


Zählen der Anzahl der Segmente nach Dauer

Es wäre auch gut, die Dauer der Segmente in den gebildeten Gruppen zu kennen. Um irgendwelche Muster zu finden, benötigen wir alle Statistiken über die analysierten Daten. Lassen Sie uns eine weitere EA-Version entwickeln. Wir kopieren einfach das Programm aus dem vorherigen Abschnitt und fügen eine weitere Tabelle der GUI hinzu. Die Tabelle soll zwei Spalten enthalten: (1) Anzahl der Balken und (2) Anzahl der Segmente mit dieser Anzahl von Balken. Unten sehen Sie, wie die GUI direkt nach dem Starten der Anwendung auf dem Chart aussieht.

 Abb. 15. Programm zur Berechnung der Anzahl der Segmente nach der Dauer

Abb. 15. Programm zur Berechnung der Anzahl der Segmente nach der Dauer

Die Reihenfolge der Aktionen zum Empfangen von Daten in allen Tabellen ist wie folgt:

  • Klicken Sie auf Request, um die Symbolliste zu erhalten.
  • Wählen Sie ein Symbol aus, das eine Zeile in der ersten Tabelle hervorhebt.
  • Klicken Sie auf Calculate, um die Daten für die zweite Tabelle zu empfangen.
  • Um Daten für die Dritte zu erhalten, wählen Sie den gewünschten Bereich aus, indem Sie eine Zeile in der zweiten Tabelle markieren.

Die folgende Auflistung enthält den Code der Methode CProgram::SetDataToTable3() zum Erhalt von Daten und Füllen der dritten Tabelle. Die hier hervorgehobene Zeile dient zum Empfangen des Bereichs, innerhalb dessen die Anzahl der Segmente nach ihrer Dauer berechnet werden soll. Die Anzahl der Zeilen in der Tabelle wird durch das (in Balken) längste Segment aus dem erhaltenen Datensatz definiert. Wenn Sie die zweite Spalte der Tabelle ausfüllen, gehen Sie durch alle Zeilen und zählen Sie die Segmente, die dem ausgewählten Bereich und der Anzahl der Balken nach Größe entsprechen.

class CProgram : public CWndCreate
  {
private:
   //--- Befüllen der Tabelle 3 mit Daten
   void              SetDataToTable3(void);
  };
//+------------------------------------------------------------------+
//| Befüllen der Tabelle 3 mit Daten                                 |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable3(void)
  {
//--- Verlassen, wenn die Zeile nicht hervorgehoben ist
   if(m_table2.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a range in the table on the left!");
      return;
     }
//--- Start der Ausführung
   StartProgress();
//--- Tabelle ausblenden
   m_table3.Hide();
//--- Abrufen der hervorgehobenen Zeile
   int selected_row_index=m_table2.SelectedItem();
//--- Spanne
   int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- Wiederherstellen der Tabelle
   RebuildingTable3();
//--- Setzen der Werte der ersten Spalte
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
      m_table3.SetValue(0,r,(string)(r+1),0);
//--- Datenabruf für die zweite Tabelle
   int segments_total=m_zz.SegmentsTotal();
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
     {
      //--- Ausführung
      m_progress_bar.LabelText("Get data ["+(string)r+"]...");
      m_progress_bar.Update(r,m_table3.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
         int bars =m_zz.SegmentBars(s);
         //---
         if(size>selected_range && 
            size<selected_range+(int)m_step.GetValue() && 
            bars==r+1)
           {
            int value=(int)m_table3.GetValue(1,r)+1;
            m_table3.SetValue(1,r,(string)value,0);
           }
        }
     }
//--- Tabellenanzeige
   m_table3.Update(true);
//--- Ende der Ausführung
   EndProgress();
  }

Beim Hervorheben von Tabellen- und Listenzeilen wird das nutzerdefinierte Ereignis ON_CLICK_LIST_ITEM erzeugt. In diesem Fall verfolgen wir den Eintritt des Ereignisses mit der ID der zweiten Tabelle.

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Ereignisse der Klicks auf eine Zeile
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Tabellenzeile wurde geklickt
      if(lparam==m_table2.Id())
        {
         //--- Datenabfrage für die dritte Tabelle
         SetDataToTable3();
         return;
        }
      //---
      return;
     }
...
  }

Beim Erhalt einer neuen Liste von Symbolen oder beim Berechnen von der Daten eines neuen, markierten Symbols in der ersten Tabelle sollten irrelevante Daten aus früheren Berechnungen aus den Tabellen gelöscht werden, um Verwirrung darüber zu vermeiden, welche Daten aktuell angezeigt werden.

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis eines Tastendrucks
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Taste Request wurde geklickt
      if(lparam==m_request.Id())
        {
         //--- Datenabfrage für die erste Tabelle
         SetDataToTable1();
         //--- Löschen irrelevanter Daten aus der Tabelle
         m_table2.DeleteAllRows(true);
         m_table3.DeleteAllRows(true);
         return;
        }
      //--- Berechnen der geklickten Taste
      if(lparam==m_calculate.Id())
        {
         //--- Datenabfrage für die zweite Tabelle
         SetDataToTable2();
         //--- Löschen irrelevanter Daten aus der Tabelle
         m_table3.DeleteAllRows(true);
        }
      //---
      return;
     }
...
  }

Nachdem wir den EA auf dem Chart gestartet haben, erhalten wir das Ergebnis wie unten gezeigt. In diesem Fall haben wir eine Liste der Währungspaare mit USD erstellt. Die Daten von GBPUSD vom Anfang des Jahres 2018 wurden anschließend empfangen und die Bereichsliste (zweite Tabelle) wurde mit dem Schritt 100 gebildet und die Segmente für jeden von ihnen berechnet. Als Beispiel wird in der zweiten Tabelle die Zeile mit dem Bereich von 200 und der Anzahl der Segmente von 1922 (von 200 bis 300) markiert. Die dritte Tabelle zeigt die Dauer aller Segmente aus dem in der zweiten Tabelle hervorgehobenen Bereich an. Zum Beispiel können wir sehen, dass in diesem Zeitraum nur 11 Segmente mit der Dauer von 10 Balken aus dem angegebenen Bereich auf GBPUSD vorhanden waren.

 Abb. 16. Ergebnis der Berechnung der Anzahl der Segmente nach Dauer

Abb. 16. Ergebnis der Berechnung der Anzahl der Segmente nach Dauer


Einige Details zur Arbeit mit der grafischen Oberfläche

Als Ergänzung möchte ich zeigen, wie man das Ereignis der Änderung eines Chartsymbols und -zeitrahmens richtig behandelt, wenn eine GUI in einem MQL-Programm verwendet wird. Da GUIs mehrere verschiedene Steuerelemente enthalten können, kann es einige Zeit dauern, bis das gesamte Set hochgeladen und initialisiert ist. Manchmal kann diese Zeit gespeichert werden, was genau der Fall ist, wenn Sie ein Chartsymbol und einen Zeitrahmen ändern. Hier ist es nicht notwendig, ständig eine GUI zu entfernen und immer wieder neu zu erstellen.

Dies kann wie folgt erreicht werden:

Wir erstellen ein Feld zur Speicherung der Ursache der Deinitialisierung des Programms an seinem Ende in der Hauptklasse:

class CProgram : public CWndCreate
  {
private:
   //--- Ursache für die Deinitialisierung
   int               m_last_deinit_reason;
  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE)
  {
  }

Während der Deinitialisierung wird die GUI in allen Fällen entfernt, außer dann, wenn die Ursache REASON_CHARTCHANGE ist.

//+------------------------------------------------------------------+
//| Deinitialisierung                                                |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
//--- Sichern der Ursache für die letzte Deinitialisierung
   m_last_deinit_reason=reason;
//--- Entfernen der GUI, falls die Ursache nicht eine Änderung des Symbols oder des Zeitrahmens ist
   if(reason!=REASON_CHARTCHANGE)
     {
      CWndEvents::Destroy();
     }
  }

Da die GUI bei der Initialisierung des Programms durch den Aufruf der Methode CProgram::CreateGUI() erstellt wird, genügt es nun, die letzte Ursache der Deinitialisierung zu überprüfen. Wenn der Grund dafür ist, dass ein Symbol oder ein Zeitrahmen geändert wurde, dann ist es nicht notwendig, eine GUI zu erstellen. Stattdessen beenden Sie einfach die Methode und benachrichtigen Sie, dass alles gut ist.

//+------------------------------------------------------------------+
//| Erstellen einer GUI                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Verlassen, wenn der Chart oder Zeitrahmen geändert wurden
   if(m_last_deinit_reason==REASON_CHARTCHANGE)
      return(true);
...
   return(true);
  }


Schlussfolgerung

Die Vorstellung, dass der ZigZag nicht geeignet ist, Handelssignale zu erzeugen, ist in Handelsforen weit verbreitet. Das ist ein großes Missverständnis. Tatsächlich liefert kein anderer Indikator so viele Informationen, um die Art des Preisverhaltens zu bestimmen. Jetzt haben Sie ein Werkzeug, mit dem Sie auf einfache Weise alle notwendigen ZigZag-Indikatorendaten für eine detailliertere Analyze erhalten.

Im nächsten Teil werde ich zeigen, welche weiteren Daten mit den in diesen Artikeln entwickelten Werkzeugen gewonnen werden können.

Dateiname Kommentar
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 Der Indikator zur Berechnung der Frequenz der gegenläufigen Bildung der Segmente des ZigZag-Indikators
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 Der Indikator zur Berechnung der Summe der Segmente aus dem erhaltenen Satz und deren Durchschnittswert
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 Der Indikator für den prozentualen Anteil der Segmentbeträge und die Differenz zwischen ihnen
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 Der Indikator zur Definition der Art der Bildung mehrerer Segmente aus einem höheren Zeitrahmen unter Verwendung der Differenz zwischen den Prozentverhältnissen der gegenläufigen Segmentsummen.
MQL5\Experts\ZigZag\TestZZ_05.mq5 EA zum Testen des Indikators MultiPorcentageSegmentsZZZ.
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 Der EA zum Sammeln von Statistiken des Indikators ProzentsatzSegmenteZZZ.
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 Der EA zur Berechnung von Segmenten in verschiedenen Preisspannen
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5  Der EA zur Berechnung von Segmenten, die sich in verschiedenen Preisspannen befinden und eine unterschiedliche Dauer haben.


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

Beigefügte Dateien |
Files.zip (45.43 KB)
MQL-Parsing mit Hilfe von MQL MQL-Parsing mit Hilfe von MQL
Der Artikel beschreibt einen Präprozessor, einen Scanner und einen Parser, die beim Parsen der MQL-basierten Quellcodes verwendet werden sollten. Die MQL-Implementierung ist beigefügt.
Die Entwicklung von grafischen Oberflächen für Expert Advisors und Indikatoren auf Basis von .Net Framework und C# Die Entwicklung von grafischen Oberflächen für Expert Advisors und Indikatoren auf Basis von .Net Framework und C#
Der Artikel stellt eine einfache und schnelle Methode zur Erstellung von grafischen Fenstern mit Visual Studio mit anschließender Integration in den MQL-Code des Expert Advisors vor. Der Artikel richtet sich an ein nicht spezialisiertes Publikum und erfordert keine Kenntnisse der Technologie von C# oder .Net.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil I). Konzept, Datenverwaltung und erste Ergebnisse Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil I). Konzept, Datenverwaltung und erste Ergebnisse
Bei der Analyse einer Vielzahl von Handelsstrategien, der Entwicklung von Anwendungen für MetaTrader 5 und MetaTrader 4 Terminals und verschiedenen MetaTrader Websites kam ich zu dem Schluss, dass diese ganze Vielfalt hauptsächlich auf den gleichen elementaren Funktionen, Aktionen und Werten basiert, die regelmäßig in verschiedenen Programmen wiederkehren. Daraus entstand die plattformübergreifende Bibliothek DoEasy für die einfache und schnelle Entwicklung von Anwendungen für МetaТrader 4 und 5.
Untersuchung von Techniken zur Analyse der Kerzen (Teil I): Überprüfen vorhandener Muster Untersuchung von Techniken zur Analyse der Kerzen (Teil I): Überprüfen vorhandener Muster
In diesem Artikel werden wir uns mit den beliebten Kerzenmustern beschäftigen und versuchen herauszufinden, ob sie in den heutigen Märkten noch relevant und effektiv sind. Die Analyse von Kerzen ist vor mehr als 20 Jahren erschienen und erfreut sich inzwischen großer Beliebtheit. Viele Händler halten die japanischen Kerzen für die bequemste und leicht verständlichste Form der Visualisierung von Wertpapierpreisen.