English Русский 中文 Español 日本語 Português
Wir schreiben eine Scalping-Markttiefe aufgrund der graphischen Bibliothek CGraphic

Wir schreiben eine Scalping-Markttiefe aufgrund der graphischen Bibliothek CGraphic

MetaTrader 5Beispiele | 28 August 2017, 07:56
1 262 1
Vasiliy Sokolov
Vasiliy Sokolov

Inhaltsverzeichnis

Einführung

Dieser Artikel ist die logische Fortsetzung der Beschreibung der Bibliothek für die Arbeit mit der Markttiefe, die vor zwei Jahren veröffentlicht wurde. Seit dieser Zeit entstand in MQL5 der Zugang zu der Ticks-History. Außerdem wurde mit Hilfe von MetaQuotes die Bibliothek CGraphic erstellt, um die benutzerdefinierten Daten in Form einer komplizierten statistischen Graphik zu visualisieren. Die CGraphic ist nach den erfüllten Aufgaben der Funktion plot ähnlich, welche in der Sprache des Programmieren R ist. Die Arbeit mit dieser Bibliothek wurde extra in dem Artikel detailliert beschrieben. 

Die Entstehung dieser Möglichkeiten hat ermöglicht, die früher angebotene Markttiefe völlig zu modernisieren. In der neuen Version, außer der Tabelle der Anfragen, werden jetzt das Ticks-Chart, und die Trades Last von ihm dargestellt:

in Abb. 1. Die Markttiefe mit einem Ticks-Chart.

Wir erinnern Sie, dass die früher angebotene Bibliothek aus zwei großen Modulen bestand: aus der Klasse CMarketBook für die Arbeit mit der Markttiefe und deren graphischen Bedienfelds, welches sie darstellte. Seit dieser Zeit hat der Code viele Nacharbeiten und Veränderungen gehabt. Darin wurden Fehler korrigiert, und der graphische Teil der Markttiefe hat die eigene graphische Bibliothek CPanel bekommen, die einfach und wenig Platz im Speicher erfordert.

Aber wir werden zur CGraphic zurückkehren, mit ihrer Fähigkeit, die komplizierten Diagramme und linearen Charts in einem separaten Fenster zu zeichnen. Es wäre klar, dass solche spezifischen Möglichkeiten nützlich sein könnten, nur die Lösung für die statistischen Aufgaben zu finden. Ist aber nicht so! In diesem Artikel versuche ich zu zeigen, wie man die Möglichkeiten von CGraphic in den Projekten verwenden kann, die weit von den Statistiken sind — zum Beispiel, bei der Erstellung des Scalping-Markttiefe der Preise.

Die Veränderungen, die seit der Veröffentlichung der vorhergehenden Version gemacht wurden

Nach der Veröffentlichung des Artikels "Das MQL5-Kochbuch: Implementierung Ihrer eigenen Markttiefe" verwendete ich in der Praxis viel CMarketBook und hatte währenddessen eine Reihe von Fehlern im Code gefunden. Allmählich habe ich das Interface fertig gemacht, und als Folge hat man einen Haufen von den folgenden Veränderungen bekommen:

  1. Ursprünglich war das ganze Chart in der Markttiefe sehr minimal. Die Zellen in der Tabelle der Preise wurden mit Hilfe von einigen einfachen Klassen dargestellt. Nach einer bestimmten Zeit haben diese Klassen eine zusätzliche Funktional bekommen, und ihre Einfachheit und minimale Anforderung im Speicherplatz haben sehr bequem bei dem Projektieren anderer Arten der Bedienfelder gepasst. Im Endeffekt hat man einen ganzen Satz der Klassen bekommen, die Bibliothek CPanel, die ins unabhängige Projekt geordnet wurde. Sie ist im Ordner Include.
  2. Es hat sich das Aussehen der Markttiefe verbessert. Zum Beispiel, anstatt eines kleinen Dreiecks ist eine große quadratische Schaltfläche entstanden, die die Markttiefe schließt und öffnet. Es wurde der Fehler des Auflegens der Elemente gelöst, welcher bei der nochmaligen Eröffnung der Tabelle durch das neu Zeichen der Elementen der Markttiefe erschien, und das war schon oberhalb der gezeichneten Elementen.
  3. Es wurden die Einstellungen hinzugefügt, die die Schaltfläche der Eröffnung/Schließung der Markttiefe nach den Achsen X und Y des Charts positionieren. Oft schloss die Schaltfläche der Eröffnung/Schließung wegen eines nicht standardmäßigen Namens des Instrumentes und eines zusätzlichen Handelspaneels die anderen aktiven Elemente des Charts. Jetzt, wenn die Möglichkeit gibt, die Anordnung der Schaltfläche manuell festzustellen, kann man ein solches Auflegen vermeiden.
  4. Stark hat sich auch die Klasse CMarketBook geändert. Darin wurden die Fehler des Verlassen vom Umfang des Arrays (array out of range); die Fehler, die bei der leeren oder teilweise gefüllten Marttiefe entstehen; der Fehler der Teilung in die Null beim Wechsel des Symbols gelöst. Die Klasse CMarketBook ist ein unabhängiges Modul geworden und befindet sich im Verzeichnis MQL5\Include\Trade;
  5. Es wurde eine Reihe der kleineren Veränderungen für die Verbesserung der allgemeinen Stabilität des Indikators durchgeführt.

Eben mit dieser verbesserten und ergänzten Version beginnen wir zu arbeiten, um von ihr allmählich eine Markttiefe zu machen.

Der kurze Überblick der graphischen Bibliothek CPanel

Der Erstellung der Benutzer-Interfaces in MQL5 wurden viele Artikel gewidmet. Unter ihnen fallen besonders eine Reihe von Artikeln von Anatolijs Kascharski "Graphische Interfaces" auf, nach den es schwer ist, etwas neu zu diesem Thema hinzuzufügen. Deshalb betrachten wir nicht die Konstruktion eines graphischen Interfaces detailliert. Aber, wie es schon höher erwähnt wurde, der graphische Teil der Markttiefe hat sich in die vollwertige Bibliothek CPanel umgewandelt. Ihre grundlegende Architektur muss man beschreiben, denn auf ihrer Grundlage wird das spezielle graphische Element erstellt: das Ticks-Chart. Wir werden es mit der Tabelle der Preise vereinen, und dadurch machen ein vollwertiges Bedienfeld mit einigen Elementen.

Also, wir betrachten CPanel detailliert, um das Prinzip der weiteren Arbeit zu verstehen. Die graphischen Elemente in MQL5 wurden von einigen graphischen Ringpuffern vorgestellt. Das sind:

  • Text Markierung;
  • Taste;
  • Eingabefeld;
  • Das rechteckige Zeichen;
  • Das graphische Zeichen.

Aller diese haben eine Reihe von identischen Eigenschaften. Zum Beispiel, das rechteckige Zeichen, die Schaltfläche und das Eingabefeld kann man so einstellen, sodass sie sich voneinander nicht unterscheiden werden. So kann zugrunde dieses oder jenes Elementes tatsächlich ein graphischer Ringpuffer liegen. Zum Beispiel, anstatt der Schaltfläche kann in der Tat ein Eingabefeld dargestellt werden, und anstatt des rechteckigen Zeichens — eine Schaltfläche. Optisch wird eine solche Auswechselung nicht auffallen, und der Benutzer, der auf das Eingabefeld drücken wird, wird denken, dass er tatsächlich auf die Schaltfläche drückt.

Natürlich, eine solche Auswechselung kann seltsam aussehen und das allgemeine Verständnis der Aufbaus-Prinzipien des Benutzer-Interfaces kompliziert machen. Aber man muss verstehen, dass außer den allgemeinen Charakteristiken, jedes grundlegende Element eine einzigartige Eigenschaft hat. Zum Beispiel, das Eingabefeld darf man nicht durchsichtig machen, aber das rechteckige Zeichen schon. Dadurch kann man die Elemente mit der einzigartigen Art aus einer und derselben Klassen erstellen.

Wir erklären es auf dem Beispiel. Nehmen wir an, dass wir ein graphisches Bedienfeld mit einem gewöhnlichen Textzeichen erstellen müssen:

in Abb. 2. Das Beispiel der Form mit dem Schreiben ohne Rahmen.

Aber wenn wir wollen, den Text vom Rahmen zu markieren, werden Probleme entstehen, weil die Eigenschaft "Rahmen" beim Textzeichen nicht gibt. Die Lösung ist ganz einfach: wir werden kein Textzeichen verwenden, sondern die Schaltfläche! So wird diese Form mit ihr aussehen:

in Abb. 3. Das Beispiel der Form mit dem Schreiben im Rahmen.

Solche feine Momente bei der Erstellung des graphischen Interfaces können eine Menge entstehen. Im Voraus zusagen, was dem Benutzer benötigt wird, ist unmöglich. Deshalb ist es richtiger, das graphische Element überhaupt auf keinen irgendwelchen konkreten Ringpuffer zu gründen, und, dem Benutzer die Möglichkeit zu geben, das selbständig zu bestimmen.

Gerade so wurde die Bibliothek der graphischen Elemente CPanel erstellt. Eigentlich ist CPanel in der Tat ein Satz der Klassen, von denen jeder dieses oder jenes Element des hochebenen graphischen Interface vorstellt. Für die Initialization eines solchen Elementes muss man den Typ des graphischen Ringpuffers bezeichnen, auf dem er gegründet sein wird. Jede solche Klasse hat einen allgemeinen Elternteil — die Klasse CNode, die nur eine Funktion erfüllt — den Typ des grundlegenden Ringpuffers zu bewahren. Sein einziger geschützter Konstrukteur fordert, diesen Typ im Moment der Erstellung des Elementes zu bezeichnen. 

Die einzigartigen hochebenen graphischen Elementen gibt es sehr wenig. "Die Einmaligkeit" hängt vom Satz der Eigenschaften ab, mit denen man das universelle grundlegende Element versorgen muss, damit er einzigartig sein wird. Ein solches universelles Element in CPanel ist die Klasse CElChart. Genauso wie alle anderen Klassen wird er von CNode beerbt und enthält die Methoden für die Einstellung der folgenden Eigenschaften:

  • Die Länge und die Höhe des Elementes;
  • Die Koordinaten X und Y des Elementes bezüglich des Charts;
  • Die Breite und die Höhe des Elementes;
  • Die Farbe des Hintergrunds und des Rahmens des Elementes (wenn solche Eigenschaften unterstützt werden);
  • Der Typ des Rahmens des Elementes (wenn eine solche Eigenschaft unterstützt wird);
  • Der Text innerhalb des Elementes, seine Schrift und die Größe, die Angleichung (wenn solche Eigenschaften unterstützt werden).

CElChart bietet die Methoden für die Installation dieser oder jener Eigenschaften an, aber garantiert nicht, dass diese Eigenschaften tatsächlich gestellt werden. Ob CElChart diese oder jene Eigenschaft unterstützen wird, wird vollständig vom grundlegenden Element bestimmt. Genauso wie CNode, fordert CElChart den Typ des graphischen Ringpuffers zu bezeichnen, auf dem er gegründet wird. So kann man mit Hilfe von einem CElChart sowie eine gewöhnliche Form, auch beispielsweise eine Schaltfläche oder ein Textfeld erstellen. In der Praxis wird es sehr bequem sein.

Zum Beispiel: wir werden ein Bedienfeld zeichnen, wie es in Abb. 3 ist. Dazu werden zwei Elemente benötigt: der Hintergrund mit dem Rahmen und der Text mit dem Rahmen. Beide sind sie — die Exemplare einer und derselbe Klasse CElChart. Aber in ihnen werden zwei verschiedene graphische Ringpuffer verwendet:  OBJ_RECTANGLE_LABEL und BJ_BUTTON. Es wird ein solcher Code entstehen:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);
CElChart Label(OBJ_BUTTON);
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   Fon.Show();
   
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.Show();
   Label.YCoord(240);
   Label.XCoord(250);
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Nachdem die Elemente erstellt sind, bleibt es übrig, ihre Eigenschaften in der Funktion OnInit zu installieren. Jetzt kann man die Elemente auf dem Chart darstellen, auch ihre Methoden Show aufrufen.

Dank der Kombination des grundlegenden Ringpuffers mit der Klasse CElChart kann man die mächtige, flexible, und hauptsächlich — einfachen graphischen Interface erstellen. Ebenso ist die graphische Darstellung der Markttiefe veranstaltet, in der die Tabelle der Markttiefe aus einer Menge der Elemente CBookCell besteht, die seinerseits auf CElChart gegründet sind.

Der graphische Engine CPanel unterstützt die Schachtelung. Es bedeutet, dass man innerhalb eines Elementes zusätzliche Elemente ordnen kann. Dank der Schachtelung wird die vielseitige Anwendbarkeit der Verwaltung erreicht. Zum Beispiel, der Befehl, der für die globale Form gegeben wird, kann ihren ganzen Unterelementen gerichtet werden. Wir werden das obengenannte Beispiel ändern:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);
CElChart *Label;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   
   Label = new CElChart(OBJ_BUTTON);
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.YCoord(240);
   Label.XCoord(250);
   //Label.Show();
   Fon.AddElement(Label);
   
   Fon.Show();
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }

Jetzt entsteht dynamisch im Laufe der Ausführung des Programms CLabel — der Anzeiger auf das Element CElCahrt. Nach der Erstellung und der Setzung der entsprechenden Eigenschaften wird er zur Form Form hinzugefügt. Jetzt muss man ihn nicht durch den Sonderbefehl Show darstellen. Stattdessen ist es schon genügend, den Befehl Show nur für das Element Fon auszuführen — die Hauptform unserer Anlage. Die Besonderheit dieses Befehls ist das, dass er für alle angelegten Unterelemente, einschließlich für Label erfüllt wird. 

CPanel gibt nicht nur die Eigenschaften des Elementes ein, sondern unterstützt auch das entwickelte Ereignis-Modell. Das Ereignis in CPanel kann alles Mögliche sein, und nicht nur das Ereignis, das vom Chart bekommen wird. Dafür verantworten die Klasse CEvent und die Methode Event. Die Klasse CEvent ist abstrakt. Darauf ist eine Menge schon konkretere Klassen basierend — zum Beispiel, CEventChartObjClick.

Wir werden vermuten, dass in unserer benutzerdefinierten Form einige angelegten graphischen Unterelemente gibt. Der Benutzer kann das Ereignis erstellen — zum Beispiel, durch ein Drücken mit der Maus über jedem diesen Elemente. Wie soll man es erkennen, welches Exemplar der Klasse das Ereignis bearbeiten soll? Dazu werden wir das Ereignis CEventChartObjClick benutzen: wir erstellen das Exemplar der Klasse und wir werden in unsere zentrale Form Form absenden:

CElChart Fon(OBJ_RECTANGLE_LABEL);
...
...
void OnChartEvent(const int id,         // Der Identifikator des Ereignisses   
                  const long& lparam,   // Der Parameter des Ereignisses des Typs long 
                  const double& dparam, // Der Parameter des Ereignisses des Typs double 
                  const string& sparam  // Der Parameter des Ereignisses des Typs string 
  )
{ 
   CEvent* event = NULL;
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
         event = new CEventChartObjClick(sparam);
         break;
   }
   if(event != NULL)
   {
      Fon.Event(event);
      delete event;
   }
}

Mit Hilfe dieser Methode haben wir das breit ausstrahlende Ereignis CEventChartObjClick abgeschickt, das alle Elemente innerhalb des Exemplares Fon bekommen wird. Ob dieses Ereignis bearbeitet sein wird, hängt schon von der inneren Logik der Form ab. 

Möge auch unser Zeichen Meta Quotes Language einen solchen Druck bearbeiten — ändert dadurch seinen Text um "Enjoy". Dazu werden wir die Klasse CEnjoy erstellen und wir werden sie mit der notwendigen Logik versorgen: wir werden die Methode OnClick — der Bearbeiter des gleichnamigen Ereignisses umdefinieren:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>
#include <Panel\Node.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
class CEnjoy : public CElChart
{
protected:
   virtual void OnClick(void);
public:
                CEnjoy(void);
   
};

CEnjoy::CEnjoy(void) : CElChart(OBJ_BUTTON)
{
}
void CEnjoy::OnClick(void)
{
   if(Text() != "Enjoy!")
      Text("Enjoy!");
   else
      Text("Meta Quotes Language");
}
CEnjoy Label;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.YCoord(240);
   Label.XCoord(250);
   //Label.Show();
   Fon.AddElement(&Label);
   
   Fon.Show();
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}
void OnChartEvent(const int id,         // Der Identifikator des Ereignisses   
                  const long& lparam,   // Der Parameter des Ereignisses des Typs long 
                  const double& dparam, // Der Parameter des Ereignisses des Typs double 
                  const string& sparam  // Der Parameter des Ereignisses des Typs string 
  )
{ 
   CEvent* event = NULL;
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
         event = new CEventChartObjClick(sparam);
         break;
   }
   if(event != NULL)
   {
      Fon.Event(event);
      delete event;
   }
   ChartRedraw();
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Es kann seltsam aussehen, dass wir das Ereignis CEventObjClick in die Form Form durch die Methode Event absenden, und wir bearbeiten es — in der Methode OnClick. Wirklich, viele standard Ereignisse (zum Beispiel, beim Klick der Maus) haben die speziellen Methoden-Ereignisse. Wenn sie umdefiniert werden, wird das entsprechende Ereignis zu ihnen kommen. Wenn man das nicht macht, alle Ereignisse werden vom Niveau höher bearbeitet, in der Methode Event. Es ist auch eine virtuelle Methode, sie kann man ebenso, wie die Methode OnClick umdefinieren. Auf diesem Niveau wird die Arbeit mit dem Ereignis durch die Analyse des kommenden Exemplares CEvent stattfinden. 

Erstmal lassen wir diese Einzelheiten und werden die Haupteigenschaften von CPanel bezeichnen.

  • Alle Klassen СPanel, die die Elemente des graphischen Interfaces realisieren, können sich auf einem gewählten graphischen Ringpuffer basieren. Er wird im Moment der Erstellung des Exemplars der Klasse ausgewählt und angewiesen.
  • Jedes willkürliche Element CPanel kann eine unbeschränkte Menge anderer Elemente CPanel enthalten. So wird die Schachtelung, und daraus folgend, auch die vielseitige Anwendbarkeit der Verwaltung realisiert. Alle Ereignisse werden nach dem Baum der Schachtelung verteilt, und so bekommt jedes Element den Zugang zu jedem Ereignis.
  • Das Ereignis-Modell CPanel hat zwei Ebenen. In der Grundlage des nieder ebenen Modells — sind die Methode Event und die Klassen des Typs CEvent. So kann man ein absolut beliebiges Ereignis bearbeiten, welches sogar in MQL nicht unterstützt wird. Auch die Ereignisse, die durch CEvent geschickt werden, sind immer breit ausstrahlende.  Auf der höheren Ebene werden die standard Ereignisse in die Aufrufe der entsprechenden Methoden umgewandelt. Zum Beispiel, das Ereignis CEventChartObjClick wird in den Aufruf OnClick umgewandelt, und der Aufruf der Methode Show bewirkt den rekursiven Aufruf der Methoden OnShow aller Tochterelemente. Auf dieser Ebene kann man das Ereignis direkt aufrufen. So, wenn die Methode Show() aufgerufen wird, stellt er das Bedienfeld dar, und der Aufruf der Methode Refresh wird die Abbildung dieses Bedienfelds erneuern.

Der Überblick ist oberflächlich und eng genug geworden, jedoch soll der beklommenen Vorstellung muss genug sein, um unsere weiteren Handlungen bei der Erstellung der Scalping-Markttiefe zu verstehen.

Die Synchronisation des Ticks-Stroms und der Tabelle der Anfragen

Die Tabelle der Anfragen — die dynamische Struktur, die die Werte auf den liquiden Märkten um Zehn mal pro Sekunde ändert. Für den Zugang zu dem laufenden Schnitt der Tabelle der Anfragen muss man das spezielle Ereignis BookEvent im gleichnamigen Bearbeiter des Ereignisses — die Funktion OnBookEvent bearbeiten. Im Moment der Veränderung in der Tabelle der Anfragen wird das Terminal das Ereignis OnBookEvent aufrufen, mit dem Hinweis auf das Symbol, auf dem diese Veränderungen stattfinden. Wir werden erinnern, dass in der vorhergehenden Version des Artikels die Klasse CMarketBook entwickelt wurde, die den bequemen Zugang zum laufenden Schnitt der Markttiefe gibt. Um den laufenden Schnitt der Markttiefe zu bekommen, ist es genug, in dieser Klasse die Methode Regresh() in der Funktion OnBookEvent aufzurufen. Es würde ungefähr so aussehen:

//+------------------------------------------------------------------+
//|                                                   MarketBook.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
#include <MarketBook.mqh>

CMarketBook MarketBook.mqh
double fake_buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   MarketBook.SetMarketBookSymbol(Symbol());
//--- indicator buffers mapping
   SetIndexBuffer(0,fake_buffer,INDICATOR_CALCULATIONS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   ChartRedraw();
}

In der neuen Version muss unsere Markttiefe, außer der Tabelle der Trades, das Tick-Chart im online Modus darstellen. Deshalb muss man sie mit den zusätzlichen Funktionen für die Arbeit mit wieder kommenden Ticks versorgen. Diese Ticks kann man in MQL5 durch drei Haupt- Mechanismen analysieren. Das sind:

  • Durch das Erhalten des letzten bekannten Ticks über die Funktion SymbolInfoTick;
  • Durch die Bearbeitung des Ankunftsereignises des neuen Ticks in der Funktion OnTick für die Experten und in OnCalculate — für die Indikatoren;
  • Durch die Erhaltung der Ticks-History mit Hilfe von den Funktionen CopyTicks und CopyTicksRange.

Zwei erste Arten kann man untereinander kombinieren. Zum Beispiel, im Ereignis OnTick oder OnCalculate kann man die Funktion SymbolInfoTick aufrufen und den Zugang zu den Parametern des letzten Ticks bekommen. Jedoch werden diese zwei Arten uns wegen der Gründe der Entstehung dieses Ticks-Stroms nicht passen.

Um zu verstehen, wie Ticks sich bilden, lassen Sie uns bitte den Artikel "Grundlagen der Börsenkursbildung am Beispiel des Terminhandelsbereichs der Moskauer Börse" betrachten und wir betrachten eine Markttiefe für das Gold: 

Der Preis, $ für die Feinunze des Goldes Die Anzahl der Unzen (Trades)
1280.8 17
1280.3 3
1280.1 5
1280.0 1
1279.8 2
1279.7 15
1279.3 3
1278.8 13

in Abb 4. Das Beispiel der Markttiefe-Anfragen.

Stellen wir uns vor, dass wir im Moment der Erneuerung der Markttiefe die Ticks-History erfordern und mit Hilfe vom speziellen Algorithmus finden wir heraus, wie viel Ticks ab dem Moment der vorherigen Erneuerung der Markttiefe gekommen sind. Theoretisch muss jeder Tick mindestens einer Veränderung der Markttiefe entsprechen, so heißt das, bei jeder Veränderung der Markttiefe kann maximal nur ein Tick kommen. Jedoch sieht man sowas in der Praxis nicht, und für die korrekte Synchronisation muss man mit der Ticks-History arbeiten.

Wir nehmen an, dass ein Käufer gekommen ist, der bereit ist, 9 Trades für das Gold abzuschließen. Wenn er es tut, wird er mindestens drei Trades abschließen. Wenn es auf der Ebene 1280.1 oder 1280.3 ein paar Verkäufer gibt, so werden noch mehrere Trades abgeschlossen. Durch eine Handlung (den Kauf) wird er direkt ein paar Trades abschließen, die gleichzeitig stattfinden werden. So werden Ticks zum Terminal MetaTrader 5 auch in einem "Paket" kommen. Deshalb, wenn man in OnCalculate die Funktion SymbolInfoTick verwendet, wird sie nur der letzte Tick aus dieser Serie zurückgeben, und die vorhergehenden Ticks werden verloren sein.

Deshalb brauchen einen anderen sichereren Mechanismus des Ticks-Erhaltens durch die Funktion CopyTicks. Sie ermöglicht, sowie auch CopyTicksRange, im Unterschied zu SymbolInfoTick eine Serie von Ticks zu bekommen. Dank ihr wird die Ticks-History klar dargestellt und es wird nichts verpasst.

Aber die Funktion CopyTiks lässt nicht zu, die N-Zahl der Letzten Ticks aufzurufen. Stattdessen bietet sie alle Ticks an, die ab dem gegebenen Moment gekommen sind. Es macht unsere Aufgabe komplizierter. Wir müssen einen Aufruf durchführen, das Ticks-Array bekommen und das mit Ticks-Array vergleichen, welches auf der vorhergehenden Erneuerung bekommen wurde. Dabei werden wir klarstellen, welche aus wieder kommenden Ticks zu "der vorherigen Lieferung" nicht gehören, das heißt, sind neu. Aber die Ticks direkt untereinander zu vergleichen ist unmöglich, weil einfach die sichtbaren Unterschiede zwischen ihnen überhaupt nicht sein können. Zum Beispiel, Betrachten wir die unten aufgeführten Tabelle der Trades:

in Abb. 5. Die Tabelle aller Trades mit dem Beispiel der gleichen Trades.

Wir sehen direkt zwei Gruppen absolut gleichen Ticks. Sie wurden von den roten Rahmen bezeichnet, sie haben die gleiche Zeit, Volumen, Richtung und Preis. So überzeugen wir uns, dass einzelne Ticks miteinander nicht verglichen werden dürfen.

Aber man kann eine Gruppe von Ticks vergleichen. Wenn zwei Gruppen Ticks untereinander gleich sind, kann man eine Schlussfolgerung ziehen, dass diese und nachfolgende Ticks bei der vorherigen Erneuerung der Preise schon analysiert wurden.

Wir synchronisieren die Klasse CMarketBook mit dem Ticks-Strom: wir werden zu ihr das Array MqlTiks hinzufügen, das neue Ticks enthält, die ab dem Moment der vorhergehenden Erneuerung gekommen sind. Selbst neu Ticks werden von der inneren Methode CompareTiks berechnet:

//+------------------------------------------------------------------+
//| Compare two tiks collections and find new tiks                   |
//+------------------------------------------------------------------+
void CMarketBook::CompareTiks(void)
{
   MqlTick n_tiks[];
   ulong t_begin = (TimeCurrent()-(1*20))*1000; // from 20 sec ago
   int total = CopyTicks(m_symbol, n_tiks, COPY_TICKS_ALL, t_begin, 1000);
   if(total<1)
   {
      printf("die Ticks-Erhaltung ist nicht stattgefunden");
      return;
   }
   if(ArraySize(m_ticks) == 0)
   {
      ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY);
      return;
   }
   int k = ArraySize(m_ticks)-1;
   int n_t = 0;
   int limit_comp = 20;
   int comp_sucess = 0;
   //Wir sortieren die neue bekommenden Handelstrades vom letzten Trade
   for(int i = ArraySize(n_tiks)-1; i >= 0 && k >= 0; i--)
   {
      if(!CompareTiks(n_tiks[i], m_ticks[k]))
      {
         n_t = ArraySize(n_tiks) - i;
         k = ArraySize(m_ticks)-1;
         comp_sucess = 0;
      }
      else
      {
         comp_sucess += 1;
         if(comp_sucess >= limit_comp)
            break;
         k--;
      }
   }
   //Wir speichern die bekommenden Ticks
   ArrayResize(m_ticks, total);
   ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY);
   //Wir rechnen den Anfangs-Index der neuen Ticks und kopieren sie in den Puffer für den Zugang 
   ArrayResize(LastTicks, n_t);
   if(n_t > 0)
   {
      int index = ArraySize(n_tiks)-n_t;
      ArrayCopy(LastTicks, m_ticks, 0, index, n_t);
   }
}

Der vorgestellte Algorithmus ist nicht trivial. CompareTicks ruft alle Ticks in letzten 20 Sekunden auf und vergleicht sie mit dem früher gespeicherten Ticks-Array, vom Ende an. Wenn die 20 Ticks des aktuellen Arrays nacheinander 20 Ticks aus dem vorhergehenden Array gleich sind, wird es angenommen, dass alle Ticks, die nach diesen 20 Ticks kommen, sind neu.

Wir erklären diesen Algorithmus durch ein einfaches Schema. Wir stellen uns vor, dass wir die Funktion CopyTiks zweimal über ein kurzes Zeitintervall aufrufen. Aber anstatt Ticks gibt sie das Array von 0 und 1 zurück. Wenn wir diese 2 Arrays bekommen, stellen wir fest, wie viel die einzigartigen letzten Elemente im zweiten Arrays gibt, die nicht mit den Elementen des ersten Arrays übereinstimmen. Es wird die Anzahl der neuen Ticks sein, die ab dem Moment der vorherigen Erneuerung gekommen sind. Auf dem Schema kann es so aussehen:

in Abb. 6. Das Schema der Synchronisation der wiederholten Serien.

Beim Vergleich sehen wir, dass die Zahlen von 6 bis 14 des ersten Arrays den Zahlen von 1 bis 8 des zweiten Arrays gleich sind. Also, im Array2 gibt es fünf neue Werte - das sind die Elemente von 9 bis 14. Der Algorithmus arbeitet in verschiedenen Kombinationen: die Arrays können verschiedene Länge haben, keine allgemeinen Elemente haben oder können absolut identisch sein. Bei allen diesen Fällen wird die Anzahl der neuen Werte richtig gefunden.

Nachdem die Anzahl der neuen Ticks bestimmt wird, kopieren wir sie ins Array LastTiks. Dieses Array ist wie das öffentliche Feld innerhalb der Klasse CMarketBook bestimmt.

Wir bekommen die neue Version der Klasse CMarketBook, die außer der Tabelle der Anfragen, das Array der enthält, die zwischen den vorhergehenden und laufenden Erneuerungen gekommen sind. Zum Beispiel, um neu Ticks zu erfahren, kann man einen solchen Code schreiben:

//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   string new_tiks = (string)ArraySize(MarketBook.LastTicks);
   printf("Gekommen " + new_tiks + " neue Ticks");
}

Die Ordnung der Ticks in der Klasse der Markttiefe erlaubt die aktuellen Anfragen richtig mit dem Ticks-Strom zu synchronisieren. In jedem Moment der Erneuerung der Markttiefe haben wir die Liste aus Ticks, die vor dieser Erneuerung waren. So sind der Ticks-Strom und die Markttiefe untereinander vollständig synchronisiert. Wir werden diese wichtige Eigenschaft im Folgenden verwenden, aber erstmal betrachten wir die graphische Bibliothek CGraphic.

Die Grundlagen der Bibliothek CGraphic

In Reserve der Bibliothek CGraphic — gibt es die Linie, das Histogramm, der Punkt, die komplizierten geometrischen Figuren. Für unsere Ziele brauchen wir nur einen kleinen Teil ihrer Möglichkeiten. Wir benötigen zwei Linien der Abbildung der Ebenen Ask und Bid und die speziellen Punkte für die Abbildung der Trades Last. Im Kern ist CGraphic — ein Container, der die Objekte CCurve enthält. Jedes solches Objekt, leicht nach dem Namen zu erraten, — eine gewisse Kurve, die aus den Punkten mit den Koordinaten X und Y besteht. Je nach der Art der Abbildung können sie sich mit den Linien verbinden, können die Gipfel der Spalten des Histogramms sein oder — in Form von einem Punkt dargestellt werden. 

Wegen einiger Besonderheiten der Darstellung der Trades Last werden wir mit den zweidimensionalen Charts arbeiten. Wir werden versuchen, das einfache zweidimensionale Chart in Form von der Linie zu erstellen:

//+------------------------------------------------------------------+
//|                                                   TestCanvas.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Graphics\Graphic.mqh>

CGraphic Graph;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   double x[] = {1,2,3,4,5,6,7,8,9,10};
   double y[] = {1,2,3,2,4,3,5,6,4,3};
   CCurve* cur = Graph.CurveAdd(x, y, CURVE_LINES, "Line");   
   Graph.CurvePlotAll();
   Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); 
   Graph.Redraw(true);
   Graph.Update();
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Wenn es in Form vom Experten gestartet wird, wird auf dem Chart ein solches Bild projiziert:

in Abb. 7. Das Beispiel des zweidimensionalen linearen Charts, das mit Hilfe von CGraphic erstellt wurde

Jetzt versuchen wir die Darstellung zu ändern, wofür wir den Typ der Abbildung bei unserer zweidimensionalen Kurve CCurve auf die Punkte ersetzen werden:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   double x[] = {1,2,3,4,5,6,7,8,9,10};
   double y[] = {1,2,3,2,4,3,5,6,4,3};
   CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points");   
   cur.PointsType(POINT_CIRCLE);
   Graph.CurvePlotAll();
   Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); 
   Graph.Redraw(true);
   Graph.Update();
   return(INIT_SUCCEEDED);
}

Das gleiche Chart in Form von den Punkten wird schon so auszusehen:

in Abb. 8 dargestellt ist. Das Beispiel des genauen zweidimensionalen Charts, das mit Hilfe von CGraphic erstellt wurde

Wie man es sehen kann, die Haupthandlungen bestehen in der Erstellung des Objektes der Kurve, deren Werte in den Arrays x und y vorläufig enthalten sein müssen: 

double x[] = {1,2,3,4,5,6,7,8,9,10};
double y[] = {1,2,3,2,4,3,5,6,4,3};
CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points"); 

Das erstellte Objekt wird in CGraphic geordnet, und die Methode CurveAdd gibt den Link auf ihn zurück. Es wurde gemacht, damit man die nötigen Eigenschaften dieser Kurve geben kann, was wir eben im zweiten Beispiel gemacht haben, durch die Typs-Eingabe der Kurve als CURVE_POINTS und durch den Hinweis auf den Abzeichen-Typ in Form vom Kreis:

cur.PointsType(POINT_CIRCLE);

Nachdem alle Linien hinzugefügt werden, muss man die Grafik auf dem Chart darstellen, und das wird durch die Ausführung der Befehle Create und Redraw durchgeführt.

Wir werden die gleiche Reihenfolge der Handlungen in unserem Projekt der Markttiefe durchführen, aber die Daten für die Kurven werden wir in der besonderen Art vorbereiten, und alle Befehle werden wir innerhalb der speziellen Klasse CElTickGraph ordnen — im Tochterelement CElChart.

Die Integration CGraphic mit der Bibliothek CPanel

Wir haben die Hauptmomente der Arbeit mit CGraphic erklärt. Jetzt ist es die Zeit, diese Klasse in die Bibliothek CPanel einzuführen. Wie es schon gesagt wurde, bietet CPanel den Zugang zu den notwendigen Ereignissen an, ordnet die graphischen Elemente richtig, steuert ihre Eigenschaften. Das alles ist notwendig, damit das Ticks-Chart als organischer Teil des einheitlichen Bedienfelds der Markttiefe zu machen. Deshalb schreiben wir zuerst das spezielle Element CElTickGraph — der Teil CPanel, der CGraphic ins Bedienfeld integriert. Außerdem wird CElTickGraph den erneuerten Ticks-Strom der Preise bekommen und, das Ticks-Chart umzeichnen. Die letzte Aufgabe — die ist am komplizierten. Wir zählen kurz auf, was CElTickGraph eben erledigen muss.

  • CElTickGraph ordnet das rechteckige Gebiet innerhalb des allgemeinen Bedienfelds der Markttiefe. Das Gebiet wurde mit einem schwarzen Rahmen ausgewählt. 
  • Im Inneren des Gebiets CElTickGraph wird das Chart CGraphic geordnet, das den Ticks-Strom der Preise darstellen wird.
  • Der Ticks-Strom stellt die letzten N-Ticks dar. Die Zahl-N kann man in den Einstellungen ändern.
  • Die CElTickGraph erneuert die Werte der Kurven CCurve, die in CGraphic sind, dadurch, dass alte Ticks vom Chart entfernt werden, und die neuen Ticks werden zu ihm hinzugefügt. Dank ihm CElTickGraph erstellt den Effekt, das fließend ändernde Ticks-Chart.

Um die Aufgabe CElTickGraph zu vereinfachen, verwenden wir eine Hilfslösung: einen Ringpuffer, dessen Arbeitsprinzip in dem Artikel schon beschrieben ist.

Wir erstellen vier Ringpuffer für die Abbildung der folgenden Werte:

  • die Ebene Ask (wird in Form von der roten Linie dargestellt);
  • die Ebene Bid (wird in Form von der blauen Linie dargestellt);
  • Das letzte Trade vom Kauf (wird in Form vom blauen Dreieck dargestellt, das nach unten gerichtet ist);
  • Das letzte Trade vom Verkauf (wird in Form vom roten Dreieck dargestellt, das nach oben gerichtet ist).

Wie man es schon sehen kann, werden wir die Trades Last untereinander unterscheiden, deshalb werden eben zwei Ringpuffer benötigt, und nicht nur einer.

Das zweite Problem besteht — darin, dass die Anzahl der Punkte zwischen Ask/Bid und den Preisen Last nicht übereinstimmt. Wenn wir die ununterbrochene Linie zeichnen, gibt es für jeden Punkt auf der Achse X die Werte auf der Achse Y. Aber wenn anstatt der Linie die Punkte verwendet werden, der Punkt in X kann auf dem Chart sein, aber er kann auch fehlen. Man muss diese Eigenschaft berücksichtigen und das zweidimensionale Chart verwenden.  Angenommen haben wir einen Punkt, der die folgenden X-Y-Koordinaten hat: 1000-57034. Dann wird der gleiche Punkt im Ankunftsmoment eines neuen Ticks die Koordinaten 999-57034 haben. Noch nach fünf Ticks wird er auf die Position 994-57034 verschieben. Seine letzte Position wird 0-57034 sein. Dann wird er vom Chart verschwinden. Der folgende Punkt kann ihm hinterher um verschiedener Anzahl der Schritte gehen. Wenn der Punkt 1 die Koordinaten 994-57034 haben wird, wird der Punkt 2 auf 995:57035 oder auf 998:57035 sein. Durch die Kombination der Linien auf dem zweidimensionalen Chart können wir diese Lücken richtig darstellen, und den Strom ohne die ununterbrochene Reihe bekommen.

Wir werden uns die hypothetische Tabelle der Ticks vorstellen, die den Strom der Trades unter Berücksichtigung der Indexe darstellt:

Index Ask Bid Buy Sell
999 57034 57032 57034
998 57034 57032
57032
997 57034 57031 57034
996 57035 57033 57035
995 57036 57035

994 57036 57035

993 57036 57035
57035
992 57036 57034
57034
991 57036 57035 57035
...



Tabelle 1. Das Schema der Synchronisation des Ticks-Stroms in der zweidimensionalen Tabelle (am Chart).

In der Tabelle Ask und Bid sind vollgefüllt, und die Trades für den Kauf (Buy) und den Verkauf (Sell) manchmal fehlen. Die Ordnung der Messwerte nach den Indexen synchronisiert richtig die Serien verschiedener Länge. Egal wieviel Trades Last abgeschlossen wurden, werden sie immer mit den nötigen Ebene Ask und Bid entsprechen.

Wir haben die allgemeinen Prinzipien der Arbeit CElTickGraph beschrieben. Jetzt werden wir seinen Quellcode vollständig einführen, und dann bearbeiten wir die kompliziertesten Momente darin.

//+------------------------------------------------------------------+
//|                                                         Graf.mqh |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Panel\ElChart.mqh>
#include <RingBuffer\RiBuffDbl.mqh>
#include <RingBuffer\RiBuffInt.mqh>
#include <RingBuffer\RiMaxMin.mqh>
#include "GlobalMarketBook.mqh"
#include "GraphicMain.mqh"
#include "EventNewTick.mqh"

input int TicksHistoryTotal = 200;
//+------------------------------------------------------------------+
//| Das bestimmt die Nummer der Kurve im graphischen Objekt CGraphic |
//+------------------------------------------------------------------+
enum ENUM_TICK_LINES
{
   ASK_LINE,
   BID_LINE,
   LAST_BUY,
   LAST_SELL,
   LAST_LINE,
   VOL_LINE
};
//+------------------------------------------------------------------+
//| das graphische Element, das das Ticks-Chart darstellt            |
//+------------------------------------------------------------------+
class CElTickGraph : public CElChart
{
private:
   
   CGraphicMain m_graf;
   /* Die Ringpuffer für die schnelle Arbeit mit dem Ticks-Strom*/
   CRiMaxMin    m_ask;
   CRiMaxMin    m_bid;
   CRiMaxMin    m_last;
   CRiBuffDbl   m_last_buy;
   CRiMaxMin    m_last_sell;
   CRiBuffInt   m_vol;
   CRiBuffInt   m_flags;
   
   double       m_xpoints[];  // Das Array der Indexe
   void         RefreshCurves();
   void         SetMaxMin(void);
public:
                CElTickGraph(void);
   virtual void Event(CEvent* event);
   void         SetTiksTotal(int tiks);
   int          GetTiksTotal(void);
   void         Redraw(void);
   virtual void Show(void);
   virtual void OnHide(void);
   virtual void OnRefresh(CEventRefresh* refresh);
   void         AddLastTick();
};
//+------------------------------------------------------------------+
//| Das Initialisieren des Charts                                    |
//+------------------------------------------------------------------+
CElTickGraph::CElTickGraph(void) : CElChart(OBJ_RECTANGLE_LABEL)
{
   double y[] = {0};
   y[0] = MarketBook.InfoGetDouble(MBOOK_BEST_ASK_PRICE);
   double x[] = {0};
   
   CCurve* cur = m_graf.CurveAdd(x, y, CURVE_LINES, "Ask");   
   cur.Color(ColorToARGB(clrLightCoral, 255));
   cur.LinesEndStyle(LINE_END_ROUND);
   
   cur = m_graf.CurveAdd(x, y, CURVE_LINES, "Bid");
   cur.Color(ColorToARGB(clrCornflowerBlue, 255));
   
   cur = m_graf.CurveAdd(x, y, CURVE_POINTS, "Buy");
   cur.PointsType(POINT_TRIANGLE_DOWN);
   cur.PointsColor(ColorToARGB(clrCornflowerBlue, 255));
   cur.Color(ColorToARGB(clrCornflowerBlue, 255));
   cur.PointsFill(true);
   cur.PointsSize(5);
   
   
   cur = m_graf.CurveAdd(x, y, CURVE_POINTS, "Sell");
   cur.PointsType(POINT_TRIANGLE);
   cur.PointsColor(ColorToARGB(clrLightCoral, 255));
   cur.Color(ColorToARGB(clrLightCoral, 255));
   cur.PointsFill(true);
   cur.PointsSize(5);
   
   m_graf.CurvePlotAll();
   m_graf.IndentRight(1);
   m_graf.GapSize(1);
   SetTiksTotal(TicksHistoryTotal);
}
//+------------------------------------------------------------------+
//| Das setzt die Anzahl der Ticks im Chart                          |
//+------------------------------------------------------------------+
void CElTickGraph::SetTiksTotal(int tiks)
{
   m_last.SetMaxTotal(tiks);
   m_last_buy.SetMaxTotal(tiks);
   m_last_sell.SetMaxTotal(tiks);
   m_ask.SetMaxTotal(tiks);
   m_bid.SetMaxTotal(tiks);
   m_vol.SetMaxTotal(tiks);
   ArrayResize(m_xpoints, tiks);
   for(int i = 0; i < ArraySize(m_xpoints); i++)
      m_xpoints[i] = i;
}

//+------------------------------------------------------------------+
//| Das erneuert die Ticks-Linien                                    |
//+------------------------------------------------------------------+
void CElTickGraph::RefreshCurves(void) 
{
   int total_last = m_last.GetTotal();
   int total_ask = m_ask.GetTotal();
   int total_bid = m_bid.GetTotal();
   int total = 10;
   for(int i = 0; i < m_graf.CurvesTotal(); i++)
   {
      CCurve* curve = m_graf.CurveGetByIndex(i);
      double y_points[];
      double x_points[];
      switch(i)
      {
         case LAST_LINE:
         {
            m_last.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         }
         case ASK_LINE:
            m_ask.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         case BID_LINE:
            m_bid.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         case LAST_BUY:
         {
            m_last_buy.ToArray(y_points);
            CPoint2D points[];
            ArrayResize(points, ArraySize(y_points));
            int k = 0;
            for(int p = 0; p < ArraySize(y_points);p++)
            {
               if(y_points[p] == -1)
                  continue;
               points[k].x = p;
               points[k].y = y_points[p];
               k++;
            }
            if(k > 0)
            {
               ArrayResize(points, k);
               curve.Update(points);
            }
            break;
         }
         case LAST_SELL:
         {
            m_last_sell.ToArray(y_points);
            CPoint2D points[];
            ArrayResize(points, ArraySize(y_points));
            int k = 0;
            for(int p = 0; p < ArraySize(y_points);p++)
            {
               if(y_points[p] == -1)
                  continue;
               points[k].x = p;
               points[k].y = y_points[p];
               k++;
            }
            if(k > 0)
            {
               ArrayResize(points, k);
               curve.Update(points);
            }
            break;
         }
      }
   }
   
}
//+------------------------------------------------------------------+
//| Es gibt die Anzahl der Ticks ins Chart zurück                    |
//+------------------------------------------------------------------+
int CElTickGraph::GetTiksTotal(void)
{
   return m_ask.GetMaxTotal();
}
//+------------------------------------------------------------------+
//| Das erneuert das Chart im Moment der Erneuerung der Markttiefe   |
//+------------------------------------------------------------------+
void CElTickGraph::OnRefresh(CEventRefresh* refresh)
{
   //Wir zeichnen am Chart die zuletzt gekommenen Ticks
   int dbg = 5;
   int total = ArraySize(MarketBook.LastTicks);
   for(int i = 0; i < ArraySize(MarketBook.LastTicks); i++)
   {
      MqlTick tick = MarketBook.LastTicks[i];
      if((tick.flags & TICK_FLAG_BUY)==TICK_FLAG_BUY)
      {
         m_last_buy.AddValue(tick.last);
         m_last_sell.AddValue(-1);
         m_ask.AddValue(tick.last);
         m_bid.AddValue(tick.bid);
      }
      if((tick.flags & TICK_FLAG_SELL)==TICK_FLAG_SELL)
      {
         m_last_sell.AddValue(tick.last);
         m_last_buy.AddValue(-1);
         m_bid.AddValue(tick.last);
         m_ask.AddValue(tick.ask);
      }
      if((tick.flags & TICK_FLAG_ASK)==TICK_FLAG_ASK ||
         (tick.flags & TICK_FLAG_BID)==TICK_FLAG_BID)
      {
         m_last_sell.AddValue(-1);
         m_last_buy.AddValue(-1);
         m_bid.AddValue(tick.bid);
         m_ask.AddValue(tick.ask);
      }
   }
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(), tick))
       return;
   if(ArraySize(MarketBook.LastTicks)>0)
   {
      RefreshCurves();
      m_graf.Redraw(true);
      m_graf.Update();
   }
}
void CElTickGraph::Event(CEvent *event)
{
   CElChart::Event(event);
   if(event.EventType() != EVENT_CHART_CUSTOM)
      return;
   CEventNewTick* ent = dynamic_cast<CEventNewTick*>(event);
   if(ent == NULL)
      return;
   MqlTick tick;
   ent.GetNewTick(tick);
   if((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY)
   {
      int last = m_last_buy.GetTotal()-1;
      if(last >= 0)
         m_last_buy.ChangeValue(last, tick.last);
   }
}
//+-------------------------------------------------------------------------------------+
//| Das rechnet den Maßstab nach den Achsen so, damit den laufenden Preis immer gibt    |
//| Auf der Mitte des Preischarts                                                       |
//+-------------------------------------------------------------------------------------+
void CElTickGraph::SetMaxMin(void)
{
   double max = m_last.MaxValue();
   double min = m_last.MinValue();
   double curr = m_last.GetValue(m_last.GetTotal()-1);
   double max_delta = max - curr;
   double min_delta = curr - min;
   if(max_delta > min_delta)
      m_graf.SetMaxMinValues(0, m_last.GetTotal(), (max-max_delta*2.0), max);
   else
      m_graf.SetMaxMinValues(0, m_last.GetTotal(), min, (min+min_delta*2.0));
}
//+------------------------------------------------------------------+
//| Das erneuert das Chart                                           |
//+------------------------------------------------------------------+
void CElTickGraph::Redraw(void)
{
   m_graf.Redraw(true);
   m_graf.Update();
}
//+----------------------------------------------------------------------------------+
//| Das fängt die Abbildung des Charts auf, und ändert die Priorität der Abbildung   |
//+----------------------------------------------------------------------------------+
void CElTickGraph::Show(void)
{
   BackgroundColor(clrNONE);
   BorderColor(clrBlack);
   Text("Ticks:");
   //m_graf.BackgroundColor(clrWhiteSmoke);
   m_graf.Create(ChartID(), "Ticks", 0, (int)XCoord()+20, (int)YCoord()+30, 610, 600); 
   m_graf.Redraw(true);
   m_graf.Update();
   CElChart::Show();
}

//+------------------------------------------------------------------+
//| Im Moment der Abbildung zeigen wir das Chart                     |
//+------------------------------------------------------------------+
void CElTickGraph::OnHide(void)
{
   m_graf.Destroy();
   CNode::OnHide();
}

Wir werden diesen Code ausführlicher betrachten. Wir beginnen mit dem Konstrukteur der Klasse CElTickGraph::CElTickGraph. Aus seiner Deklaration ist es klar, dass die Klasse selbst auf dem graphischen Ringpuffer OBJ_RECTANGLE_LABEL basierend ist, d.h. auf einem gewöhnlichen rechteckigen Zeichen. Im Konstrukteur werden einige Kurven des Typs CCurve erstellt, jede von denen für den Typ der Daten verantwortlich ist. Für jeden von ihnen werden die Eigenschaften eingestellt: der Linie-Name, ihren Typ und die Farbe. Im Moment der Erstellung der Kurve sind die Werte, die sie darstellen wird, noch unbekannt, deshalb verwenden wir die Fake-Arrays double x und y, die die Koordinaten des ersten Punktes enthalten. Nachdem wie die Kurven im Objekt CGraphic erstellt und gesetzt sind, werden die Ringpuffer in der Methode SetTiksTotal konfiguriert. Die Konfiguration führt zur Installierung der höchsten Anzahl der speichernden Ticks, welche vom äußerlichen Parameter TicksHistoryTotal eingegeben werden.

Wenn in CGraphic alle notwendigen Kurven hinzugefügt sind, und die Ringpuffer entsprechend konfiguriert sind, ist die Markttiefe einsatzbereit. Im Laufe der Arbeit werden zwei Haupt-Methoden aufgerufen: es ist CElTickGraph:: OnRefresh und CElTickGraph :: RefreshCurves. Wir betrachten sie.

Die Methode OnRefresh wird gleich nach der Veränderung der Markttiefe aufgerufen. Solche Veränderungen werden mit Hilfe von der Funktion OnBookEvent geprüft:

//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   MButton.Refresh();
   ChartRedraw();
}

Erstens wird die Markttiefe (MarketBook. Refresh ()) erneuert, dann wird das darstellende Bedienfeld: MButton. Refresh () erneuert. Da das Bedienfeld in Form von der Schaltfläche dargestellt wird und sie kann man minimieren/maximieren, dann die Schaltfläche selbst — ist das Elternelement des ganzen Bedienfelds. Deshalb alle Ereignisse, einschließlich des Befehls der Erneuerung, kommen durch diese Schaltfläche (MButton). Der Befehl der Erneuerung geht durch alle Elemente, die in der Schaltfläche gelegt sind und endlich geht bis zu CElTickGraph, in dem der Algorithmus gesetzt ist, der das Chart selbst erneuert. Der Algorithmus wurde in der Methode OnRefresh realisiert.

Zunächst bekommt der Algorithmus die Anzahl der Ticks, die ab dem Moment der vorhergehenden Erneuerung entstanden sind. Dann werden die Werte jedes dieses Ticks zu dem entsprechenden Ringpuffer hinzugefügt. Der Preis des Ask-Ticks wird zum Ringpuffer m_ask und der Preis Bid — zum Puffer m_bid usw. hinzugefügt. Wenn der Typ des letzten Ticks - ein Trade Last ist, so werden die Preise Ask und Bid mit dem Preis Last zwangsläufig synchronisiert. Es wurde gemacht, weil das Terminal selbst eine solche Synchronisation nicht erzeugt, sondern gibt die Werte Ask und Bid von vorhergehenden Ticks aus. So befinden sich die Trades Last garantiert entweder auf der Ebene Ask, oder auf der Ebene Bid. Achten wir darauf, dass die standard Markttiefe eine solche Synchronisation nicht macht, und Last kann darin zwischen diesen zwei Linien optisch bleiben.

Nachdem die letzte Reihe der Ticks in den Ringpuffern gesetzt ist, wird die Methode OnRefreshCurves aufgerufen, die für die Zeichnung der Ticks auf dem Chart verantwortlich ist. In der Methode wurde der Loop geordnet, in dem alle verfügbaren Kurven CCurve sortiert werden. Für jede Kurve wird die volle Erneuerung der Punkte mit Hilfe von der Methode curve. Update. durchgeführt. Die Punkte für die Achsen Y bekommen wir durch das Kopieren aller Werte aus dem Ringpuffer ins gewöhnliche Array double. Die Punkte für die Achsen X werden mehr feiner sein. Mit Hilfe von der vollen Sortierung, bei jedem Punkt y wird wird die x-Koordinate um x-1 ersetzt. D.h., wenn das Element x den Wert 1000 hat, so wird er nach dieser Sortierung den Wert 999 haben. Nach dieser Weise wird der Bewegungseffekt durchgesetzt, wenn das Chart neue Werte zeichnet, und die alte werden von ihm spurlos verschwinden.

Nachdem wie alle Werte nach den nötigen Indexen gesetzt sind und die Kurven CCurve wurden erneuert, bleibt es übrig, die Markttiefe selbst zu erneuern, deshalb werden in der Methode OnRefresh die Erneuerung-Methoden des Charts: m_graf. Redraw und m_graf. Update. aufgerufen.

Der Algorithmus der Darstellung des Ticks-Charts ermöglicht zwei Modus zu wählen:

  • Das Ticks-Chart wird ohne Verknüpfung der letzten Preise zur Mitte der Markttiefe dargestellt. Die Maximums und Minimums des Charts werden automatisch in CGraphic gerechnet.
  • Das Ticks-Chart wird mit der Rücksicht auf die Verknüpfung der letzten Preise zur Mitte der Markttiefe dargestellt. Egal wo Maximum und Minimum der Preise ist, wird der aktuelle (letzte) Preis auf der Mitte des Charts sein.

Im ersten Fall wird die automatische Skalierung aufgerufen, die selbst von CGraphic durchgeführt wird. Im zweiten Fall mit der Skalierung beschäftigt sich die Methode SetMaxMin.

Installation. Der Arbeitsablauf. Die vergleichenden Charakteristiken der Markttiefen

Alle Dateien, die für die von uns entwickelte Anwendung notwendig sind, kann man in 4 Gruppen teilen:

  • Die Dateien der graphischen Bibliothek CPanel. die in MQL5\Include\Panel sind;
  • Die Datei der Klasse der Markttiefe MarketBook. die in MQL5\Include\Trade ist;
  • Die Dateien der Klassen der Ringpuffer. Welche in MQL5\Include\RingBuffer sind;
  • Eigentlich sind noch die Dateien der Scalping-Markttiefe. Welche in MQL5\Indicators\MarketBookArticle sind.

Das hinzugefügte Archiv enthält alle diese Dateien in den entsprechenden Katalogen. Für die Installation des Programms dekomprimieren Sie einfach das Archiv im Ordner MQL5. Irgendwelche Unterordner müssen dabei nicht erstellt werden. Nach dem Dekomprimieren kompilieren Sie die Datei MQL5\Indicators\MarketBookArticle\MarketBook.mq5. Nach der Kompilation wird der entsprechende Indikator erscheinen, der im Fenster des Navigators MetaTrader 5 auftaucht. 

Die beste Art, den bekommenden Algorithmus zu bewerten — die Veränderungen des Ticks-Charts in der Dynamik darzustellen. Das untere Video führt vor, wie sich das Ticks-Chart mit der Zeit ändert, dabei verschiebt er das Fenster von der Grafik nach rechts:


Wir bemerken, dass das bekommende Tick-Chart unserer Markttiefe vom ähnlichen Chart der Markttiefe in MetaTrader 5 wesentlich unterscheidet. In der vergleichenden Tabelle unten werden diese Unterschiede vorgestellt:

Die standard Markttiefe MetaTrader 5 Die entwickelte Markttiefe
Die Preise Last, Ask und Bid sind nicht zusammengebunden. Der Preis Last kann auf den Ebenen sein, die von Ask und Bid unterschiedlich sind. Die Preise Last, Ask, Bid sind untereinander synchronisiert. Der Last befindet sich immer entweder auf der Ebene Ask, oder auf der Ebene Bid.
Die Preise Last werden in Form von den Kreisen verschiedener Durchmessers dargestellt, die proportional dem Umfang der Trades ist. Der Kreis mit dem maximalen Durchmesser entspricht dem Trade mit dem maximalen Umfang, der in den letzten N-Ticks abgeschlossen wurde, wo N — die Periode des gleitenden Fensters von der Ticks-Grafik. Die Trades für den Kauf werden in Form vom blauen Dreieck dargestellt, das nach unten gerichtet ist, die Trades für den Verkauf — in Form vom roten Dreieck, das nach oben gerichtet ist. Die Auswahl der Trades wird je nach dem Volumen nicht abgeschlossen.
Der Maßstab des Ticks-Charts ist mit der Höhe der Tabelle der Pending Anfragen synchronisiert. So wird jede Ebene in der Tabelle der Trades der gleichen Ebene auf dem Ticks-Chart entsprechen. Der Nachteil einer solchen Lösung ist die Unmöglichkeit der Abbildung des Ticks-Charts im größeren Maßstab. Der Vorteil — die Anschaulichkeit der Preise und die ganze Entsprechung der Markttiefe-Ebenen mit dem Ticks-Chart. Der Maßstab des Ticks-Charts und der Tabelle der Pending Anfragen entsprechen einander nicht. Der aktuelle Preis des Ticks-Charts kann nur etwas der Mitte der Anfragen-Tabelle entsprechen. Der Nachteil einer solchen Lösung — ist die Abwesenheit der visuellen Übereinstimmung der Ebenen der Anfragen-Tabelle mit dem Ticks-Chart. Der Vorteil — ist die Option, fast jede Skalierung vom Ticks-Chart einzusetzen.
Das Ticks-Chart ist mit dem zusätzlichen Histogramm-Volumen versorgt, der unter ihm geordnet ist.  Das Ticks-Chart enthält keine irgendwelche Ergänzungen.

Tabelle 2. Die vergleichenden Charakteristiken der standarten und entwickelten Markttiefen.

Fazit

Wir betrachteten alle Hauptpunkte der Entwicklung der Scalping-Markttiefe.

  • Wir haben das Aussehen der Anfragen-Tabelle verbessert
  • Wir haben das Ticks-Chart aufgrund CGraphic zum Bedienfeld hinzugefügt, haben wesentlich den graphischen Engine modernisiert
  • Wir haben die Klasse der Markttiefe fertig gemacht, haben zu ihm den Algorithmus der Ticks-Synchronisation mit der aktuellen Anfragen-Tabelle hinzugefügt.

Aber sogar jetzt ist unsere Markttiefe noch sehr weit von einer vollständigen Scalping-Version. Natürlich, viele Benutzer können enttäuscht werden, wenn sie den Punkt gelesen haben, und am Ende keinen vollwertigen Analog der standard Markttiefe gesehen zu haben oder sogar keine spezialisierten Programme, die ähnlich dem Antrieb des Bondars oder QScalp sind. Aber man muss verstehen, dass jedes kompliziertes Softwareprodukt in seiner Entwicklung eine Reihe von den Evolutionsstufen durchhaben muss. Unten ist das, was ich vernünftig finde, zu den weiteren Versionen von Markttiefen hinzuzufügen:

  • Die Option, die Grenzanforderungen gerade auf dem Paneel des Glases auszustellen
  • Die Möglichkeit, die großen Anfragen auf dem Ticks-Chart zu verfolgen
  • Die Trades Last nach dem Volumen zu differenzieren, dabei sie am Chart nach verschiedenen Arten darzustellen
  • Die zusätzlichen Indikatoren parallel dem Ticks-Chart darzustellen. Zum Beispiel, unter dem Ticks-Chart das Histogramm des Verhältnisses aller Anfragen von Buy Limit zu allen Anfragen von Sell Limit darzustellen.
  • Und endlich das Wichtigste: die History der Markttiefe laden und speichern zu können, damit man die Handelsstrategien im offline Test-Modus bauen kann.

Das alles kann man realisieren, und es ist möglich, diese Optionen zu ermöglichen. Wenn Benutzer sich für dieses Thema interessieren werden, so wird die Reihe von diesen Artikeln fortgesetzt. 

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
BND
BND | 9 Mai 2022 in 11:35

Same problem as in the former article of this author. The code does not work. One even cannot compile it:

'Event' - unexpected token, probably type is missing?    MBookBtn.mqh    57    12
'Event' - function already defined and has different type    MBookBtn.mqh    57    12

If you ask the author for help you will get an answer: "No".

Ridiculous, why do you even write articles?

Cross-Plattform Expert Advisor: Zeitfilter Cross-Plattform Expert Advisor: Zeitfilter
Dieser Artikel beschreibt die Implementierung verschiedener Methoden einer Zeitfilterung für einen Cross-Plattform Expert Advisor. Die Klassen der Zeitfilter sind verantwortlich für die Prüfung, ob ein bestimmter Zeitpunkt in eine besondere Zeitkonfiguration fällt oder nicht.
Graphisches Interface XI: Gezeichnete Steuerelemente (build 14.2) Graphisches Interface XI: Gezeichnete Steuerelemente (build 14.2)
In der neuen Version der Bibliothek werden alle Steuerelemente als eigenständige Grafikobjekte des Typs OBJ_BITMAP_LABEL gezeichnet. Der Code wird auch weiterhin optimiert: die Änderungen in den Kernklassen werden beschrieben.
Verwendung eines Cloud-Speichers für den Datenaustausch zwischen Terminals Verwendung eines Cloud-Speichers für den Datenaustausch zwischen Terminals
Immer beliebter werden die wolkigen Technologien. Sowohl kostenpflichtige, als auch kostenfreie Speicher stehen uns zur Verfügung. Können wir sie im Traiding verwenden? In diesem Artikel wird die Technologie für den Datenaustausch zwischen Terminals durch die Verwendung wolkiger Speichers angeboten.
Testen der Muster, die beim Handel mit Körben von Währungspaaren auftreten. Teil I Testen der Muster, die beim Handel mit Körben von Währungspaaren auftreten. Teil I
Wir beginnen mit dem Testen der Muster und der Prüfung der Methoden, die in den Artikeln über den Handel mit Körben von Währungspaaren beschrieben wurden. Schauen wir, wie die Ausbruchsmuster der Levels für Überkauft/Überverkauft in der Praxis angewandt werden.