Das Preishistogramm (Marktprofil) und seine Umsetzung in MQL5

Dmitry Voronkov | 11 Februar, 2016

"Das Marktprofil versucht, diese innere Logik im Kontext des Marktes einzubringen.
Es ist eine Analysemethode, die mit dem Verständnis beginnt, dass der Preis allein
dem Teilnehmer keine Informationen vermittelt, genauso wie Worte
ohne Syntax
oder Kontext möglicherweise keine Bedeutung haben. Das Volumen ist ein integraler Bestandteil des direkten Ausdrucks
des Marktes. Wenn Sie das Volumen verstehen, verstehen Sie die Sprache des Marktes."
Robin Mesh

Einleitung

Als ich vor langer Zeit eine Ausgabe der russischen Fachzeitschrift "Valutny Spekulant" (aktuell heißt es "Active Trader") durchblätterte, fand ich den Artikel "Marktprofil und Verstehen der Marktsprache" (Oktober 2002). Der Originalartikel wurde in "New Thinking in Technical Analysis: Trading Models from the Masters" veröffentlicht.

Das Marktprofil wurde von Peter Steidlmayer, einem wahrhaft brillanten Denker, entwickelt. Er fand den natürlichen Ausdruck des Marktes (Volumen) und richtete sie auf eine lesbare Weise aus (die Glockenkurve), sodass Marktteilnehmer auf objektive Informationen, die vom Markt generiert werden, zugreifen können. Steidlmayer schlug die alternative Darstellung von Informationen über "horizontale" und "vertikale" Marktbewegungen vor, die eine völlig neue Reihe von Modellen ermöglicht. Er stellte die These auf, dass dem Markt ein Puls oder ein grundlegendes Muster namens Zyklus des Gleichgewichts und Ungleichgewichts zugrunde liegen muss.

Das Marktprofil misst die horizontale Bewegung des Marktes durch die vertikale Bewegung.  Bezeichnen wir dies als "Gleichgewicht" durch "Ungleichgewicht". Dieses Verhältnis ist das wichtigste Ordnungsprinzip des Marktes. Der gesamte Handelsstil eines Händlers kann sich abhängig davon, in welchem Abschnitt des Gleichgewichts-/Ungleichgewichtszyklus sich der Markt befindet, ändern. Das Marktprofil kann sowohl bestimmen, wann der Markt vom Gleichgewicht ins Ungleichgewicht übergeht, als auch, wie bedeutend dieser Übergang ausfallen wird.

Die zwei Grundprinzipien des Marktprofils sind:

  1. Der Markt ist eine Auktion und bewegt sich in die Richtung des Preisbereichs, in dem Angebot und Nachfrage mehr oder weniger gleich hoch sind.
  2. Der Markt hat zwei Phasen: horizontale Aktivität und vertikale Aktivität. Der Markt bewegt sich vertikal, wenn Angebot und Nachfrage nicht gleich bzw. im Ungleichgewicht sind, und horizontal, wenn sie im Gleichgewicht sind.

Der Markt im Gleichgewicht, der im unten abgebildeten Diagramm mithilfe des Marktprofils gezeigt wird, neigt dazu, eine fast perfekte Glockenkurve zu formen, die aufgrund der Ausrichtung des Diagramms um 90 Grad gedreht ist:

 

Abb. 1 Das Marktprofil des Marktes im Gleichgewicht

Der trendbasierte Markt im Ungleichgewicht formt ebenfalls eine Glockenkurve, doch ihr Mittelpunkt ist nach oben oder unten verschoben. Andere Konfigurationen, die die zwei höchsten Punkte der Glocken formen, abhängig von der Preisbewegung und der Zuversichtlichkeit der Mitbewerber, sind möglich.

Abb. 2 Das Marktprofil des (trendbasierten) Marktes im Ungleichgewicht

Die Nutzung tagesaktueller Profilformen zur Bestimmung des Maßes an Gleichgewicht/Ungleichgewicht des Marktes kann hilfreich sein, da Sie damit einen Ansatzpunkt haben, um die Verschiebungen zwischen verschiedenen Marktteilnehmern zu begreifen.

Eine Handelsgelegenheit mit dem größtmöglichen Gewinn ergibt sich dann, wenn der Übergang vom Gleichgewicht ins Ungleichgewicht gleich stattfinden soll. Zudem können Sie die Qualität des Geschäfts und die dafür benötigte Zeit besser einschätzen, wenn Sie diese Handelsgelegenheit erkennen und das potenzielle Ausmaß dieses Übergangs genau beurteilen können.

Dazu muss angemerkt werden, dass wir in diesem Beitrag den Code für die Zeichnung einer vereinfachten Version des Marktprofils betrachten werden, das sogenannte Preishistogramm, auf Basis des Verhältnisses zwischen Preis und Zeit.

Ein Beispiel für die Methoden hinter der Arbeit mit diesem Werkzeug finden Sie unter http://www.enthios.com/. Dort hat eine Gruppe von Händlern das Preishistogramm seit 1998 studiert. Die Enthios-Universal-Strategie und ein Beispiel für ihre Verwendung finden Sie ebenfalls dort.

1. Preishistogramm

Das Preishistogramm ist ein äußerst zuverlässiges Werkzeug. Es ist relativ intuitiv und gleichzeitig extrem effektiv. Das Preishistogramm zeigt Ihnen einfach die "bequemsten" Handelspunkte des Marktes an. Dies ist ein Frühindikator, denn er zeigt im Voraus die Punkte, an denen der Markt seine Richtung ändern kann. Indikatoren wie gleitende Mittelwerte oder Oszillatoren können die genauen Punkte von Widerstand und Unterstützung nicht bestimmen, sondern lediglich anzeigen, ob der Markt überkauft oder überverkauft ist.

Für gewöhnlich wird das Preishistogramm (oder Marktprofil) auf 30-minütige Preisdiagramme angewandt, um die Marktaktivität im Verlauf eines Tages zu studieren. Ich bevorzuge 5-minütige Diagramme für Aktienmärkte und 15- bis 30-minütige Diagramme für FOREX.

2. Kontrollpunkt

In der Abbildung weiter oben sehen Sie das Niveau, auf dem der Markt die längste Zeit gehandelt wurde. Es ist durch die längste Linie im Histogramm umrissen. Dies nennt sich Kontrollpunkt oder POC. Wie in der Abbildung zu sehen ist, hat das Histogramm manchmal zwei Höchstpunkte, von denen einer ein wenig tiefer liegt. In diesem Fall sehen wir, dass der Indikator nur einen POC anzeigt. Es muss allerdings berücksichtigt werden, dass es tatsächlich zwei davon gibt.

Zusätzlich erschafft das prozentuale Niveau im Histogramm zusätzliche Niveaus, sogenannte sekundäre POC-Niveaus:

Abb. 3 Kontrollpunkte

Was zeigt ein POC? Den Preis, an den sich die Mehrheit der Händler erinnert. Je länger der Markt zu diesem Preis gehandelt wird, desto länger erinnert sich der Markt an ihn.

Auf psychologischer Ebene dient der POC als Anziehungspunkt.

Das nächste Diagramm zeigt, was einige Tage zuvor geschehen ist. Dies ist eine hervorragende Demonstration der Leistung des Preishistogramms.

Abb. 4 Der Kontrollpunkt ist nicht absolut, sondern zeigt den Bereich des Handels

Der Kontrollpunkt ist nicht absolut, sondern zeigt den Bereich des Handels. Deshalb sollte der Händler bereit sein, zu handeln, wenn sich der Markt dem POC nähert. So können Aufträge mithilfe historischer Beobachtungen optimiert werden.

Denken wir über Abbildung 4 nach. Der POC am 29.12.2009 befindet sich beim Preis 68.87. Auch ohne das Histogramm und den POC ist ersichtlich, dass sich der Markt beinahe den ganzen Tag im Bereich 68.82~68.96 befand. Am Ende des Tages schloss der Markt 5 Punkte unter dem POC. Am nächsten Tag führte dies zu einer Öffnung des Marktes mit einer Lücke nach unten.

Man muss verstehen, dass wir nicht vorhersagen können, ob sich der Markt nach oben oder nach unten bewegen wird. Wir können nur annehmen, dass der Markt zur POC-Linie und zur größtmöglichen Ansammlung von Histogrammlinien zurückkehren wird. Doch was passiert, wenn der Preis den POC berührt? Das ist das gleiche Prinzip wie bei einem elastischen Objekt, das auf den Boden fällt: Es springt zurück.  Falls dies schnell geschieht, wie bei einem Tennisball, der mit einem Schläger zurückgeschlagen wird, kehrt der Preis sehr schnell zum Ausgangsniveau zurück.

Am 30.12.2009 sehen wir, dass der Markt nach der Öffnung mit Lücke den POC des vorherigen Tages berührte und anschließend schnell zum Öffnungspreis zurückkehrte und den Mindestwert aktualisierte.

Beachten Sie, dass der POC nicht absolut genau ist (erfahrende Händler wissen, dass es keine klaren Widerstandsniveaus gibt, wenn der Preis ein Maximum, ein Minimum oder einen konzentrierten Bereich erreicht). Was an dieser Stelle geschieht, hängt von den Mitbewerbern ab. Falls es einen einheitlichen gemeinsamen Wunsch gibt (beispielsweise die Veröffentlichung von Nachrichten), durchschreitet der Markt den POC. Das passiert allerdings selten und kann für die Entwicklung eines Handelssystems genutzt werden.

Beachten Sie, dass der Markt sich am 31.12.2009 genauso verhielt. Als der Preis den POC berührte, gaben die Käufer den Verkäufern den Vortritt.

3. Virgin Point of Control ("jungfräulicher" Kontrollpunkt)

Beim Virgin POC ("jungfräulicher" Kontrollpunkt) handelt es sich um ein Niveau, das der Preis an den folgenden Tagen nicht erreichte.

Die Logik ist simpel: Wie oben beschrieben, ist der POC ein Anziehungspunkt für den Markt. Entfernt sich ein Preis vom POC, erhöht sich damit die Anziehungskraft. Und je weiter sich ein Preis vom Virgin POC entfernt, desto größer ist die Wahrscheinlichkeit, dass ein Rücksprung stattfindet, wenn er zu diesem Niveau zurückkehrt. Höchstwahrscheinlich findet auch eine Preisumkehrung statt.

Abb. 5 Vorherige und aktuelle Virgin POC

In Abbildung 5 sind die vorherigen Virgin POCs, die als Unterstützungs- und Widerstandsniveaus dienten, mit Kreisen markiert. Die aktiven Virgin POCs sind mit Preiswerten markiert.

Hat ein Preis einen Virgin POC berührt, gilt dieser nicht mehr als "jungfräulich". Vom psychologischen Standpunkt gesehen, betrachtet ihn der Markt nicht mehr als wesentliches Niveau für Unterstützung oder Widerstand. Die Händler können die Preisniveaus, die ursprünglich den POC bildeten weiterhin sehen, allerdings als einfache Ansammlung von Preisen.

Weitere Details über Preisniveaus finden Sie im Buch "Master trading: The X-Files" von Eric Naiman (Kapitel 4, "Price Level is a base line").

4. Umsetzung des Preishistogramms in MQL5

Meine erste Version des Preishistogramms erschien 2006 und wurde in MetaTrader4 in MQL4 für den persönlichen Gebrauch geschrieben. Während der Entwicklung dieses Indikators bin ich auf einige Schwierigkeiten gestoßen, zum Beispiel:

  1. sehr geringe Anzahl von Balken in der Historie von M5, ganz zu schweigen von M1;
  2. die Notwendigkeit, spezielle Funktionen für die Arbeit mit der Historie zu entwickeln, beispielsweise Zurückgehen um einen Tag, unter Berücksichtigung von Feiertagen, Überprüfung der Schließungszeit des Marktes am Freitag, Überprüfung der Öffnungs- und Schießungszeiten des CFC-Marktes usw.;
  3. Neuberechnung des Indikators nach der Änderung von Timeframes und somit Verlangsamung des Terminals.

Deshalb beschloss ich, den Indikator in MQL5 zu konvertieren, als das Beta-Testing von MetaTrader5 und MQL5 begann.

Aller Anfang ist schwer, und ich habe versucht, ihn als Indikator zu implementieren.

Fangen wir mit den guten Neuigkeiten an: das Vorhandensein einer langen Historie von minütlichen Geboten für alle Symbole, die Möglichkeit, historische Daten für einen bestimmten Zeitraum bei jedem Zeitbereich abzurufen.

Jetzt erkläre ich, warum es misslungen ist. Ich habe die Merkmale der MQL5-Indikatoren nicht berücksichtigt:

  1. die Laufzeit des Indikators ist kritisch;
  2. die Besonderheiten der Arbeit des Indikators nach Veränderungen der Timeframes.

Die Ausführung der Funktion OnCalculate(), die dem Ereignis-Handler Calculate entspricht, hat eine kritische Laufzeit. Dementsprechend dauert die Verarbeitung von 260 Tagen (in etwa ein Arbeitsjahr) mithilfe der Minutenbalken sehr lange, teilweise mehrere Minuten. Natürlich wäre das akzeptabel, wenn die Berechnungen sofort nach dem Anhängen des Indikators an das Diagramm ausgeführt würden. Doch bei Änderungen von Timeframes ist das nicht der Fall. Wenn ein Indikator zu einem anderen Timeframe umschaltet, wird die alte Kopie des Indikators zerstört und eine neue erstellt. Deshalb müssen wir nach den Änderungen der Timeframes dieselben Niveaus neu berechnen, was viel Zeit kostet.

Wenn man nicht weiß, was man tun soll, sollte man die Dokumentation lesen. In unserem Fall ist das die MQL5-Dokumentation. Die Lösung war sehr einfach: Dieser Indikator wurde als Expert Advisor implementiert, der nicht handelt.

Die Vorteile des Expert Advisors sind:

  1. die Verarbeitungszeit ist für den Ereignis-Handler Init in OnTick() nicht kritisch;
  2. die Möglichkeit, die Parameter des Handlers OnDeinit abzurufen (const int reason).

Der Unterschied zwischen Expert Advisors und Indikatoren ist: Nach der Änderung des Timeframes generiert der Expert Advisor lediglich das Ereignis DeInit mit dem Begründungsparameter REASON_CHARTCHANGE, ohne aus dem Speicher ausgeladen zu werden, und speichert die Werte von globalen Variablen. Dies ermöglicht uns die gleichzeitige Durchführung aller Berechnungen nach dem Anhängen des Expert Advisors, dem Ändern seiner Parameter und dem Erscheinen neuer Daten, in unserem Fall für einen neuen Handelstag.

Führen wir ein paar Definitionen ein, die wir später brauchen werden.

Die objektorientierte Programmierung (OOP) ist ein Programmierstil, dessen Grundkonzepte die Konzepte von Objekten und Klassen sind.

Das Objekt ist eine Entität im virtuellen Raum mit festgelegtem Zustand und Verhalten: Es hat Werte von Eigenschaften (Attribute) und von Verfahren, die diese nutzen (Methoden).

Die Klasse ist ein spezieller abstrakter Datentyp in der OOP, der durch seine Konstruktion charakterisiert wird. Die Klasse ist eines der Schlüsselkonzepte in der OOP. Die Klasse unterscheidet sich von anderen abstrakten Datentypen. Die Datendefinition der Klasse beinhaltet auch Klassenmethoden für die Verarbeitung ihrer Daten (Interface).

In der Programmierung existiert ein Software-Interfacekonzept, das aus einer Liste möglicher Berechnungen besteht, die von einem bestimmten Teil des Programms durchgeführt werden können, einschließlich Algorithmen, der Beschreibung von Argumenten und der Reihenfolge von Eingabeparametern für das Verfahren und der Ausgabewerte. Das Interface für den abstrakten Datentyp wurde für die formalisierte Beschreibung einer solchen Liste entwickelt. Die Algorithmen selbst und der Code, der all diese Berechnungen ausführen wird, sind nicht festgelegt und werden als Umsetzung des Interfaces bezeichnet.

Die Erstellung der Klasse besteht aus der Erstellung einer gewissen Struktur mit Feldern und Methoden. Die gesamte Klasse kann als Template für die Erstellung von Objekten, Instanzen der Klasse, betrachtet werden. Die Klasseninstanzen werden mithilfe desselben Templates erstellt, also verfügen sie über die gleichen Felder und Methoden.

Fangen wir an...

Der Quellcode befindet sich in 4 Dateien. Die Hauptdatei ist PriceHistogram.mq5, die anderen Dateien sind ClassExpert.mqh, ClassPriceHistogram.mqh und ClassProgressBar.mqh. Die Dateien mit der Erweiterung .mqh enthalten die Beschreibungen und Methoden der Klassen. Alle Dateien müssen sich in demselben Verzeichnis befinden. Mein Verzeichnis ist \MQL5\Experts\PriceHistogram.

4,1. PriceHistogram.mq5

Die erste Anweisung im Quellcode lautet:

#include "ClassExpert.mqh"

Die Compiler-Direktive #include fügt den Text aus der angegebenen Datei ein. In unserem Fall ist das die Beschreibung der Klasse CExpert (weiter unten beschrieben).

Es folgt ein Block von Eingabevariablen, die Parameter des Expert Advisors sind.

// The block input parameters
input int         DayTheHistogram   = 10;          // Days for histogram
input int         DaysForCalculation= 500;         // Days for calculation(-1 all)
input uint        RangePercent      = 70;          // Percent range
input color       InnerRange        =Indigo;       // Inner range
input color       OuterRange        =Magenta;      // Outer range
input color       ControlPoint      =Orange;       // Point of Control
input bool        ShowValue         =true;         // Show Values

Anschließend wird die Variable ExtExpert (des Klassentypen CExpert) deklariert.

Als Nächstes folgen in MQL5 geschriebene Standard-Ereignis-Handler. Die Ereignis-Handler rufen die entsprechenden Methoden der CExpert-Klasse auf.

Es gibt nur eine Methode, die bestimmte Aktivitäten vor der Ausführung von CExpert durchführt: die Methode OnInit():

int OnInit()
  {
//---
// We check for symbol synchronization before the start of calculations
   int err=0;
   while(!(bool)SeriesInfoInteger(Symbol(),0,SERIES_SYNCRONIZED) && err<AMOUNT_OF_ATTEMPTS)
     {
      Sleep(500);
      err++;
     }
// CExpert class initialization
   ExtExpert.RangePercent=RangePercent;
   ExtExpert.InnerRange=InnerRange;
   ExtExpert.OuterRange=OuterRange;
   ExtExpert.ControlPoint=ControlPoint;
   ExtExpert.ShowValue=ShowValue;
   ExtExpert.DaysForCalculation=DaysForCalculation;
   ExtExpert.DayTheHistogram=DayTheHistogram;
   ExtExpert.Init();
   return(0);
  }

Als ich die erste Version des Expert Advisors geschrieben und ausgeführt hatte, hatte ich Schwierigkeiten, zu verstehen, warum er nach dem Neustart des Client Terminals oder der Änderung eines Symbols mit einem Fehler beendet wurde. Dies tritt auf, wenn die Verbindung des Client Terminals unterbrochen oder ein Symbol lange nicht verwendet wurde.

Es ist schön, dass die Entwickler MetaEditor5 um einen Debugger bereichert haben. Ich erinnere mich noch an zahllose Print()- und Comment()-Befehle zum Prüfen von Werten von Variablen in MetaEditor4. Vielen Dank an die Entwickler von MetaEditor5.

In meinem Fall stellte sich alles als einfach heraus: Der Expert startet vor der Verbindung zum Server und der Aktualisierung der historischen Daten. Um dieses Problem zu lösen, musste ich die Funktion SeriesInfoInteger(Symbol(),0,SERIES_SYNCHRONIZED), die meldet, ob Daten synchronisiert wurden, und den Zyklus while() nutzen. Bei fehlender Verbindung wird die Zählervariable err verwendet.

Sobald die Daten synchronisiert wurden oder der Zyklus aufgrund des Zählers bei fehlender Verbindung beendet wurde, übergeben wir die Eingabeparameter unserer Expert-Klasse CExpert und rufen die Initialisierungsmethode Init() der Klasse auf.

Wie Sie sehen können, hat sich unsere Datei PriceHistogram.mq5 dank des Klassenkonzepts in MQL5 in ein einfaches Template verwandelt. Sämtliche weitere Verarbeitung findet in der CExpert-Klasse statt, die in der Datei ClassExpert.mqh deklariert ist.

4,2. ClassExpert.mqh

Betrachten wir die Beschreibung.

//+------------------------------------------------------------------+
//|   Class CExpert                                                  |
//|   Class description                                              |
//+------------------------------------------------------------------+
class CExpert
  {
public:
   int               DaysForCalculation; // Days to calculate (-1 for all)
   int               DayTheHistogram;    // Days for Histogram 
   int               RangePercent;       // Percent range
   color             InnerRange;         // Internal range color
   color             OuterRange;         // Outer range color
   color             ControlPoint;       // Point of Control (POC) Color
   bool              ShowValue;          // Show value

Der Bereich public ist offen und für Variablen von außen zugänglich. Ihnen wird auffallen, dass die Namen der Variablen mit den Namen der im Abschnitt PriceHistogram.mq5 beschriebenen Eingabeparameter übereinstimmen. Das ist nicht unbedingt erforderlich, da die Eingabeparameter global sind. Doch in diesem Fall gehört es zum guten Ton. Die Verwendung externer Variablen innerhalb der Klasse sollte verhindert werden.

private:
   CList             list_object;        // The dynamic list of CObject class instances
   string            name_symbol;        // Symbol name
   int               count_bars;         // Number of daily bars
   bool              event_on;           // Flag of events processing

Der Bereich private ist nach außen geschlossen und nur innerhalb der Klasse zugänglich. Ich möchte gerne die Variable list_object des Typen CList umreißen, die eine Klasse der MQL5-Standardbibliothek ist. Die CList-Klasse ist eine dynamische Klasse mit einer Liste von Instanzen der Klasse CObject und deren Erben. Ich werde diese Liste für die Speicherung der Verweise für Elemente der Klasse CPriceHistogram verwenden, die ein Erbe der CObject-Klasse ist. Wir gehen weiter unten auf die Details ein. Die Beschreibung der CList-Klasse befindet sich in der Datei List.mqh und wird mithilfe der Compiler-Direktive #include <Arrays\List.mqh> eingebunden.

public:
   // Class constructor
                     CExpert();
   // Class destructor
                    ~CExpert(){Deinit(REASON_CHARTCLOSE);}
   // Initialization method
   bool              Init();
   // Deinitialization method
   void              Deinit(const int reason);
   // Method of OnTick processing
   void              OnTick();
   // Method of OnChartEvent() event processing
   void              OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   // Method of OnTimer() event processing
   void              OnTimer();
};

Das Folgende ist ein Abschnitt von public-Methoden. Wie Sie schon erraten haben, sind diese Methoden (Funktionen) außerhalb der Klasse verfügbar.

Die Klassenbeschreibung wird durch die geschweifte Klammer und den Semikolon beendet.

Sehen wir uns Klassenmethoden im Detail an.

Der Klassenkonstruktor ist ein spezieller Block aus Anweisungen, der bei der Erstellung des Objekts aufgerufen wird. Der Konstruktor ist der Methode ähnlich, unterscheidet sich aber darin von der Methode, dass er keinen expliziten Typ von ausgegebenen Daten hat.

Die Konstruktoren können in MQL5 keine Eingabeparameter haben und jede Klasse sollte nur einen einzigen Konstruktor haben. In unserem Fall dient der Konstruktor als Erstinitialisierung von Variablen.

Der Destruktor ist eine spezielle Klassenmethode, die für die Deinitialisierung von Objekten genutzt wird (z. B., um Speicher freizugeben). In unserem Fall wird die Methode mit Deinit (REASON_CHARTCLOSE); aufgerufen.

Init() ist eine Methode zum Initialisieren von Klassen. Dies ist die wichtigste Methode der CExpert-Klasse. Darin wird die Erstellung von Histogrammobjekten durchgeführt. Details finden Sie in den Kommentaren, die Sie unbedingt lesen sollten. Ich möchte allerdings auf drei Punkte eingehen.

Erstens, um ein tägliches Preishistogramm aufzubauen, benötigen wir die Öffnungszeitdaten für die zu verarbeitende Menge an Tagen. An dieser Stelle möchte ich kurz vom Thema abweichen und Sie auf die Besonderheiten der Arbeit mit Zeitreihen hinweisen.  Für die Datenanfrage von den anderen Timeframes haben wir eine Zeit benötigt, deshalb geben die Funktionen Bars() und CopyTime() nicht immer beim ersten Aufruf die gewünschten Daten aus.

Deshalb musste ich diese Funktion in der Schleife do {...} while() platzieren. Damit die Schleife nicht unendlich weiterläuft, habe ich die Zählervariable verwendet.

 int err=0;
 do
   {
    // Calculate the number of days which available from the history
    count_bars=Bars(NULL,PERIOD_D1);
    if(DaysForCalculation+1<count_bars)
       count=DaysForCalculation+1;
    else
       count=count_bars;
    if(DaysForCalculation<=0) count=count_bars;
    rates_total=CopyTime(NULL,PERIOD_D1,0,count,day_time_open);
    Sleep(1);
    err++;
   }
 while(rates_total<=0 && err<AMOUNT_OF_ATTEMPTS);
 if(err>=AMOUNT_OF_ATTEMPTS)
   {
   Print("There is no accessible history PERIOD_D1");
   name_symbol=NULL;
   return(false);
   }

Zweitens, die Minutenhistorie von MetaTrader 5 entspricht den verfügbaren Tagen. Dies kann sehr lange dauern, deshalb ist es erforderlich, den Berechnungsprozess zu visualisieren. Zu diesem Zweck wurde die Klasse CProgressBar (#include "ClassProgressBar.mqh") entwickelt. Sie erstellt den Fortschrittsbalken im Diagrammfenster und aktualisiert ihn während des Berechnungsprozesses.

 // We create the progress bar on the char to shot the loading process
 CProgressBar   *progress=new CProgressBar;
 progress.Create(0,"Loading",0,150,20);
 progress.Text("Calculation:");
 progress.Maximum=rates_total; 

Drittens, wir erstellen das CPriceHistogram-Objekt mithilfe der Anweisung "new" in der Schleife, konfigurieren es mithilfe seiner Methoden und initialisieren es durch den Aufruf von Init(). Bei erfolgreicher Ausführung fügen wir es zur Liste list_object hinzu, andernfalls löschen wir das Objekt hist_obj mithilfe der Anweisung delete. Die Beschreibung der CPriceHistogram-Klasse wird später bereitgestellt, siehe Kommentare im Code.

 // In this cycle there is creation of object CPriceHistogram
 // its initialization and addition to the list of objects
 for(int i=0;i<rates_total;i++)
   {
    CPriceHistogram  *hist_obj=new CPriceHistogram();
    //         hist_obj.StepHistigram(step);
    // We set the flag to show text labels
    hist_obj.ShowLevel(ShowValue);
    // We set POCs colour
    hist_obj.ColorPOCs(ControlPoint);
    // We set colour for inner range
    hist_obj.ColorInner(InnerRange);
    // We set colour for outer range
    hist_obj.ColorOuter(OuterRange);
    // We set the percent range
    hist_obj.RangePercent(RangePercent);
    //  hist_obj.ShowSecondaryPOCs((i>=rates_total-DayTheHistogram),PeriodSeconds(PERIOD_D1));
    if(hist_obj.Init(day_time_open[i],day_time_open[i]+PeriodSeconds(PERIOD_D1),(i>=rates_total-DayTheHistogram)))
       list_object.Add(hist_obj);
    else
       delete hist_obj; // Delete object if there was an error
    progress.Value(i);
   }; 

OnTick() ist eine Methode, die aufgerufen wird, wenn Sie einen neuen Tick für ein Symbol erhalten. Wir vergleichen die Werte der Menge der in der Variable count_bars gespeicherten Tage mit der Menge der täglichen von Bars(Symbol(),PERIOD_D1) ausgegebenen Balken. Sind diese Werte nicht gleich, erzwingen wir den Aufruf der Methode Init() für die Initialisierung der Klasse, löschen die Liste list_object und ändern den Wert der Variable name_symbol in NULL. Falls die Anzahl der Tage unverändert geblieben ist, durchläuft die Schleife alle in list_object gespeicherten Objekte der Klasse CPriceHistogram und führt die Methode Redraw() für alle Objekte aus, die Virgin ("jungfräulich") sind.

Deinit() ist eine Methode zum Deinitialisieren von Klassen. Im Fall von REASON_PARAMETERS (Eingabeparameter wurden durch den Benutzer verändert) löschen wir die Liste list_object und setzen den Wert der Variable name_symbol auf NULL. In anderen Fällen bleibt der Expert untätig. Lesen Sie die Kommentare, falls Sie etwas hinzufügen möchten.

OnEvent() ist eine Methode für die Verarbeitung von Ereignissen des Client Terminals. Ereignisse werden vom Client Terminal generiert, wenn der Benutzer mit dem Diagramm arbeitet. Die Details finden Sie in der Dokumentation zu MQL5. In diesem Expert Advisor wurde das Diagrammereignis CHARTEVENT_OBJECT_CLICK verwendet. Nach einem Klick auf das Histogrammelement zeigt es die sekundären POC-Niveaus an und kehrt die Farbe des Histogramms um.

OnTimer(void) ist eine Methode für die Verarbeitung von Timer-Ereignissen. Sie wird in meinem Programm nicht verwendet, aber wenn Sie Timer-Aktionen hinzufügen wollen (zum Beispiel zum Anzeigen der Zeit), finden Sie die Details hier. Vor der Verwendung muss die folgende Zeile zum Klassenkonstruktor hinzugefügt werden:

EventSetTimer(time in seconds);

Und die folgende Zeile zum Destruktor:

EventKillTimer(); 

vor dem Aufruf der Methode Deinit(REASON_CHARTCLOSE).

Damit sind wir mit der CExpert-Klasse fertig. Sie wurde erstellt, um die Methoden der Klasse CPriceHistogram zu demonstrieren.

4,3. ClassPriceHistogram.mqh

//+------------------------------------------------------------------+
//|   Class CPriceHistogram                                          |
//|   Class description                                              |
//+------------------------------------------------------------------+
class CPriceHistogram : public CObject
  {
private:
   // Class variables
   double            high_day,low_day;
   bool              Init_passed;      // Flag if the initialization has passed or not
   CChartObjectTrend *POCLine;
   CChartObjectTrend *SecondTopPOCLine,*SecondBottomPOCLine;
   CChartObjectText  *POCLable;
   CList             ListHistogramInner; // list for inner lines storage 
   CList             ListHistogramOuter; // list for outer lines storage
   bool              show_level;         // to show values of level
   bool              virgin;             // is it virgin
   bool              show_second_poc;    // show secondary POC levels
   double            second_poc_top;     // value of the top secondary POC level
   double            second_poc_bottom;  // value of the bottom secondary POC level
   double            poc_value;          // POC level value
   color             poc_color;          // color of POC level
   datetime          poc_start_time;
   datetime          poc_end_time;
   bool              show_histogram;     // show histogram  
   color             inner_color;        // inner color of the histogram
   color             outer_color;        // outer color of the histogram
   uint              range_percent;      // percent range
   datetime          time_start;         // start time for construction
   datetime          time_end;           // final time of construction
public:
   // Class constructor
                     CPriceHistogram();
   // Class destructor
                    ~CPriceHistogram(){Delete();}
   // Class initialization
   bool              Init(datetime time_open,datetime time_close,bool showhistogram);
   // To level value
   void              ShowLevel(bool show){show_level=show; if(Init_passed) RefreshPOCs();}
   bool              ShowLevel(){return(show_level);}
   // To show histogram
   void              ShowHistogram(bool show);
   bool              ShowHistogram(){return(show_histogram);}
   // To show Secondary POC levels
   void              ShowSecondaryPOCs(bool show){show_second_poc=show;if(Init_passed)RefreshPOCs();}
   bool              ShowSecondaryPOCs(){return(show_second_poc);}
   // To set color of POC levels
   void              ColorPOCs(color col){poc_color=col; if(Init_passed)RefreshPOCs();}
   color             ColorPOCs(){return(poc_color);}
   // To set internal colour of histogram
   void              ColorInner(color col);
   color             ColorInner(){return(inner_color);}
   // To set outer colour of histogram
   void              ColorOuter(color col);
   color             ColorOuter(){return(outer_color);}
   // To set percent range
   void              RangePercent(uint percent){range_percent=percent; if(Init_passed)calculationPOCs();}
   uint              RangePercent(){return(range_percent);}
   // Returns value of virginity of POC level
   bool              VirginPOCs(){return(virgin);}
   // Returns starting time of histogram construction
   datetime          GetStartDateTime(){return(time_start);}
   // Updating of POC levels
   bool              RefreshPOCs();
private:
   // Calculations of the histogram and POC levels
   bool              calculationPOCs();
   // Class delete
   void              Delete();
  }; 

In der Beschreibung der Klasse habe ich versucht, Kommentare zu Klassenvariablen und -methoden zu verfassen. Sehen wir uns einige davon im Detail an.

//+------------------------------------------------------------------+
//|   Class initialization                                           |
//+------------------------------------------------------------------+
bool CPriceHistogram::Init(datetime time_open,datetime time_close,bool showhistogram) 

Diese Methode nutzt drei Eingabeparameter: Eröffnung des Aufbaus, Schließung des Aufbaus und ein Flag, das festlegt, ob ein Histogramm oder nur die POC-Niveaus aufgebaut werden sollen.

In meinem Beispiel (CExpert-Klasse) werden die Tagesöffnungszeit und die Öffnungszeit des nächsten Tages day_time_open[i]+PeriodSeconds(PERIOD_D1) als Eingabeparameter übergeben. Wenn Sie diese Klasse nutzen, hindert Sie nichts daran, beispielsweise die Zeit einer europäischen oder amerikanischen Sitzung oder die Lückengröße in der Woche, im Monat usw. einzugeben.

//+---------------------------------------------------------------------------------------+
//|   Calculations of the histogram and POCs levels                                       |
//+---------------------------------------------------------------------------------------+
bool CPriceHistogram::calculationPOCs() 

In dieser Methode ist der Ursprung aller Niveaus und der Berechnungen ihres Aufbaus eine geschlossene private-Methode, auf die von außen nicht zugegriffen werden kann.

// We get the data from time_start to time_end
   int err=0;
   do
     {
      //--- for each bar we are copying the open time
      rates_time=CopyTime(NULL,PERIOD_M1,time_start,time_end,iTime);
      if(rates_time<0)
         PrintErrorOnCopyFunction("CopyTime",_Symbol,PERIOD_M1,GetLastError());

      //--- for each bar we are copying the High prices
      rates_high=CopyHigh(NULL,PERIOD_M1,time_start,time_end,iHigh);
      if(rates_high<0)
         PrintErrorOnCopyFunction("CopyHigh",_Symbol,PERIOD_M1,GetLastError());

      //--- for each bar we are copying the Low prices
      rates_total=CopyLow(NULL,PERIOD_M1,time_start,time_end,iLow);
      if(rates_total<0)
         PrintErrorOnCopyFunction("CopyLow",_Symbol,PERIOD_M1,GetLastError());

      err++;
     }
   while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS&&!IsStopped());
   if(err>=AMOUNT_OF_ATTEMPTS)
     {
      return(false);
     }
   poc_start_time=iTime[0];
   high_day=iHigh[ArrayMaximum(iHigh,0,WHOLE_ARRAY)];
   low_day=iLow[ArrayMinimum(iLow,0,WHOLE_ARRAY)];
   int count=int((high_day-low_day)/_Point)+1;
// Count of duration of a finding of the price at each level
   int ThicknessOfLevel[];    // create an array for count of ticks
   ArrayResize(ThicknessOfLevel,count);
   ArrayInitialize(ThicknessOfLevel,0);
   for(int i=0;i<rates_total;i++)
     {
      double C=iLow[i];
      while(C<iHigh[i])
        {
         int Index=int((C-low_day)/_Point);
         ThicknessOfLevel[Index]++;
         C+=_Point;
        }
     }
   int MaxLevel=ArrayMaximum(ThicknessOfLevel,0,count);
   poc_value=low_day+_Point*MaxLevel;

Zuerst erhalten wir die historischen Daten der Minutenbalken für einen bestimmten Zeitraum (iTime[], iHigh[], iLow[]). Anschließend finden wir das Maximum-Element iHigh[] und das Minimum-Element iLow[] heraus. Dann berechnen wir die Zahl der Punkte (count) vom Minimum zum Maximum und reservieren das Array ThicknessOfLevel mit ThicknessOfLevel-Elementen. In der Schleife gehen wir jede Minuten-Kerze von Low bis High durch und geben die Daten der auf diesem Preisniveau verbrachten Zeit ein. Danach finden wir das Maximum-Element des Arrays ThicknessOfLevel. Das wird das Niveau sein, auf dem sich der Preis für die längste Zeit befand. Das ist unser POC-Niveau.

// Search for the secondary POCs
   int range_min=ThicknessOfLevel[MaxLevel]-ThicknessOfLevel[MaxLevel]*range_percent/100;
   int DownLine=0;
   int UpLine=0;
   for(int i=0;i<count;i++)
     {
      if(ThicknessOfLevel[i]>=range_min)
        {
         DownLine=i;
         break;
        }
     }
   for(int i=count-1;i>0;i--)
     {
      if(ThicknessOfLevel[i]>=range_min)
        {
         UpLine=i;
         break;
        }
     }
   if(DownLine==0)
      DownLine=MaxLevel;
   if(UpLine==0)
      UpLine=MaxLevel;
   second_poc_top=low_day+_Point*UpLine;
   second_poc_bottom=low_day+_Point*DownLine;

Als Nächstes müssen wir die sekundären POC-Niveaus finden. Denken Sie daran, dass unser Diagramm aufgeteilt ist. Denken Sie daran, dass unser Diagramm in zwei Bereiche aufgeteilt ist, intern und extern (in verschiedenen Farben angezeigt), und die Größe des Bereichs als prozentualer Anteil der Zeit, die sich der Preis auf diesem Niveau befand, definiert ist. Der Bereich der internen Grenzen bildet die sekundären POC-Niveaus.

Fahren Sie nach dem Finden des sekundären POC – der Grenzen des prozentualen Bereichs – mit der Konstruktion des Histogramms fort.

// Histogram formation 
   if(show_histogram)
     {
      datetime Delta=(iTime[rates_total-1]-iTime[0]-PeriodSeconds(PERIOD_H1))/ThicknessOfLevel[MaxLevel];
      int step=1;
      
      if(count>100)
         step=count/100;  // Calculate the step of the histogram (100 lines as max)

      ListHistogramInner.Clear();
      ListHistogramOuter.Clear();
      for(int i=0;i<count;i+=step)
        {
         string name=TimeToString(time_start)+" "+IntegerToString(i);
         double StartY= low_day+_Point*i;
         datetime EndX= iTime[0]+(ThicknessOfLevel[i])*Delta;

         CChartObjectTrend *obj=new CChartObjectTrend();
         obj.Create(0,name,0,poc_start_time,StartY,EndX,StartY);
         obj.Background(true);
         if(i>=DownLine && i<=UpLine)
           {
            obj.Color(inner_color);
            ListHistogramInner.Add(obj);
           }
         else
           {
            obj.Color(outer_color);
            ListHistogramOuter.Add(obj);
           }
        }
     }

Es sollte erwähnt werden, dass ich pro Histogramm maximal 100 Zeilen auf dem Bildschirm anzeigen lasse, um die Belastung des Terminals zu reduzieren. Zeilen des Histogramms werden in zwei Listen gespeichert, ListHistogramInner und ListHistogramOuter. Diese Listen sind Objekte der uns bereits bekannten CList-Klasse. Diese Pointer werden allerdings in einer Standardklasse des Objekts CChartObjectTrend gespeichert. Wie Sie vermutlich schon dem Titel entnehmen können, werden zwei Listen genutzt, um die Farbe des Histogramms ändern zu können.

// We receive data beginning from the final time of the histogram till current time
   err=0;
   do
     {
      rates_time=CopyTime(NULL,PERIOD_M1,time_end,last_tick.time,iTime);
      rates_high=CopyHigh(NULL,PERIOD_M1,time_end,last_tick.time,iHigh);
      rates_total=CopyLow(NULL,PERIOD_M1,time_end,last_tick.time,iLow);
      err++;
     }
   while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS);
// If there isn't history, the present day, level is virgin, we hoist the colours
   if(rates_time==0)
     {
      virgin=true;
     }
   else
// Otherwise we check history
     {
      for(index=0;index<rates_total;index++)
         if(poc_value<iHigh[index] && poc_value>iLow[index]) break;

      if(index<rates_total)   // If level has crossed
         poc_end_time=iTime[index];
      else
         virgin=true;
     }
   if(POCLine==NULL)
     {     
      POCLine=new CChartObjectTrend();
      POCLine.Create(0,TimeToString(time_start)+" POC ",0,poc_start_time,poc_value,0,0);
     }
   POCLine.Color(poc_color);
   RefreshPOCs();

Ich habe versucht, die Klasse CPriceHistogram mit allen nötigen Methoden auszustatten. Sollte es nicht ausreichen, können Sie sie selbst erweitern und ich werde Sie gerne dabei unterstützen.

Zusammenfassung

Einmal mehr möchte ich Sie daran erinnern, dass das Preishistogramm ein zuverlässiges und intuitives Werkzeug ist und bei seiner Nutzung Bestätigungssignale notwendig sind.

Vielen Dank für Ihr Interesse. Gerne beantworte ich all Ihre Fragen.