English Русский 中文 Español 日本語 Português
Das MQL5-Kochbuch: Implementierung Ihrer eigenen Markttiefe

Das MQL5-Kochbuch: Implementierung Ihrer eigenen Markttiefe

MetaTrader 5Beispiele | 27 Juni 2016, 10:28
1 791 0
Vasiliy Sokolov
Vasiliy Sokolov

Inhaltsverzeichnis


Einleitung

Die Sprache MQL5 entwickelt sich immer weiter und bietet jedes Jahr mehr Möglichkeiten für die Arbeit mit Börseninformationen. Ein solcher börsenbezogener Datentyp sind Informationen über die Markttiefe (Depth of Market, DOM). Dabei handelt es sich um eine spezielle Tabelle, die Preisebenen und die Volumina von Limit-Ordern zeigt. MetaTrader 5 verfügt über eine eingebaute Markttiefe für die Anzeige von Limit-Ordern, doch diese reicht nicht immer aus. Als Erstes benötigt Ihr Expert Advisor einen einfachen und praktischen Zugriff auf die Markttiefe. Natürlich bietet die Sprache MQL5 einige spezielle Funktionen für die Arbeit mit solchen Informationen, doch dabei handelt es sich um Funktionen auf niedriger Ebene, die zusätzliche mathematische Berechnungen erfordern.

Doch alle Zwischenberechnungen lassen sich vermeiden. Sie müssen lediglich eine spezielle Klasse für die Arbeit mit der Markttiefe schreiben. Alle komplexen Berechnungen werden innerhalb der Markttiefe durchgeführt und die Klasse selbst liefert praktische Möglichkeiten für die Arbeit mit DOM-Preisen und -Ebenen. Diese Klasse ermöglicht die einfache Erstellung eines effizienten Panels in Form eines Indikators, das den aktuellen Zustand von Preisen in der Markttiefe schnell abbilden wird:


Abb. 1. Anzeige der Markttiefe als Panel

Dieser Beitrag führt den Benutzern vor, wie die Markttiefe (Depth of Market, DOM) programmatisch verwendet wird, und beschreibt das Arbeitsprinzip der Klasse CMarketBook, durch die die Standardbibliothek von MQL5-Klassen erweitert werden kann und die praktische Methoden für die Verwendung der DOM liefert.

Nach dem Lesen des ersten Kapitels dieses Beitrags wird klar, dass die standardmäßig von MetaTrader 5 bereitgestellte Markttiefe über beeindruckende Fähigkeiten verfügt. Wir werden nicht versuchen, all diese zahlreichen Möglichkeiten in unserem Indikator zu duplizieren, denn unsere Aufgabe ist eine ganz andere. Mit einem praktischen Beispiel für die Erstellung eines benutzerfreundlichen Handelspanels für die Markttiefe werden wir zeigen, dass die Prinzipien der objektorientierten Programmierung einen relativ einfachen Umgang mit komplexen Datenstrukturen ermöglichen. Wir werden sicherstellen, dass es nicht schwierig ist, direkt über Ihren Expert Advisor mithilfe von MQL5 Zugriff auf die Markttiefe zu erhalten und folglich die Art ihrer Darstellung auf eine für uns praktische Art zu visualisieren.

 

KAPITEL 1 Standardmarkttiefe in MetaTrader 5 und Anwendungsmethoden


1,1 Standardmarkttiefe in MetaTrader 5

MetaTrader 5 unterstützt die Arbeit auf zentralisierten Börsen und liefert Standard-Tools für die Arbeit mit der Markttiefe. In erster Linie handelt es sich dabei natürlich um eine Tabelle von Limit-Ordern, die kürzlich einen erweiterten Darstellungsmodus erhalten hat. Um die Markttiefe zu öffnen, muss eine Verbindung mit einer der Börsen hergestellt werden, die MetaTrader 5 unterstützen, und im Kontextmenü muss "View" --> "Depth of Market" --> "Name of Instrument" (Ansicht --> Markttiefe --> Name des Instruments) ausgewählt werden. Es wird ein separates Fenster geöffnet, das ein Tick-Preisdiagramm und eine Tabelle von Limit-Ordern enthält:


Abb. 2 Standardmarkttiefe in MetaTrader 5

Die Standardmarkttiefe in MetaTrader 5 liefert eine reichhaltige Funktionalität. Insbesondere ermöglicht sie die Darstellung von:

  • Limit-Ordern zum Kaufen und Verkaufen, deren Preisebene und Volumen (Standardform der klassischen DOM);
  • der aktuellen Spread-Ebene und von Limit-Ordern belegten Preisebenen (erweiterter Modus);
  • einem Tick-Diagramm und visualisierten Volumina von Bid, Ask und dem letzten Abschluss;
  • dem allgemeinen Niveau von Ordern zum Kaufen und Verkaufen (Darstellung in Form von zwei Linien im oberen bzw. unteren Teil des Tick-Diagramms).

Aus dieser Liste wird ersichtlich, dass die Funktionen der Markttiefe mehr als beeindruckend sind. Lassen Sie uns herausfinden, wie wir mit den Daten arbeiten können, indem wir programmatisch Zugriff darauf erhalten. Zuerst müssen Sie eine Vorstellung davon bekommen, wie die Markttiefe festgelegt wird und wie ihre Daten allgemein organisiert sind. Weitere Informationen entnehmen Sie dem Beitrag "Grundlagen der Preisbildung von Börsen anhand des Beispiels des Terminmarktes der Moskauer Börse" in Kapitel "1.3. Zusammenführen von Verkäufern und Käufern. Markttiefe der Börse". Wir verlieren nicht viel Zeit mit der Beschreibung dieser Tabelle und nehmen an, dass der Leser bereits über eine ausreichende Kenntnis des Themas verfügt.

 

1,2 Ereignismodell für die Arbeit mit der Markttiefe

Die Markttiefe hat eine äußerst dynamische Datentabelle. Auf schnellen, dynamischen Märkten kann sich die Tabelle von Limit-Ordern mehrere Dutzend Male pro Sekunde verändern. Deshalb müssen Sie versuchen, nur solche Informationen zu verarbeiten, die tatsächlich für die Verarbeitung benötigt werden. Andernfalls übersteigen die Menge der übertragenen Daten und die Belastung des Zentralprozessors für die Verarbeitung dieser Daten möglicherweise die Grenzen aller Vernunft. Aus diesem Grund benötigt MetaTrader 5 ein spezielles Ereignismodell, das den Erhalt und die Verarbeitung von Daten, die nicht tatsächlich verwendet werden sollen, verhindert. Sehen wir uns dieses Modell detailliert an. 

Jedes Ereignis, das auf dem Markt auftritt, wie das Erscheinen eines neuen Ticks oder die Durchführung einer Handelstransaktion, kann durch den Aufruf der entsprechenden damit verbundenen Funktion verarbeitet werden. So wird beim Erscheinen eines neuen Ticks in MQL5 die spezielle Ereignis-Handlerfunktion OnTick() aufgerufen. Die Änderung der Größe oder der Position des Diagramms ruft die Funktion OnChartEvent() auf. Dieses Ereignismodell wird auch auf Änderungen der Markttiefe angewendet. Wenn beispielsweise jemand Limit-Order zum Verkaufen oder Kaufen in der Markttiefe platziert, ändert sich ihr Status und die spezielle Funktion OnBookEvent() wird aufgerufen.

Da im Terminal Dutzende oder gar hunderte verschiedene Symbole mit ihrer eigenen Markttiefe bereitstehen, kann die Menge der Aufrufe der Funktion OnBookEvent äußerst hoch und ressourcenintensiv sein. Um das zu vermeiden, muss das Terminal beim Start eines Indikators oder Expert Advisors informiert werden, von welchen Instrumenten es Informationen über Gebote auf zweiter Ebene erhalten soll (Informationen, die von der Markttiefe bereitgestellt werden, werden ebenfalls so genannt). Zu diesem Zweck wird die spezielle Systemfunktion MarketBookAdd genutzt. Wenn wir beispielsweise Informationen über die Markttiefe auf Basis des Instruments Si-9.15 erhalten möchten (Terminkontrakt für USD/RUR mit Ablaufdatum September 2015), müssen wir den folgenden Code in die OnInit-Funktion unseres Expert Advisors oder Indikators schreiben:

void OnInit()
{
   MarketBookAdd("Si-9.15");
}

Mit dieser Funktion haben wir ein sogenanntes "Abonnement" erschaffen und das Terminal darüber benachrichtigt, dass der Expert Advisor oder Indikator über das Ereignis der Änderung der Markttiefe auf Basis des Instruments Si-9.15 informiert werden muss. Änderungen der Markttiefe für andere Instrumente sind für uns nicht verfügbar, wodurch die Menge der durch das Programm verbrauchten Ressourcen deutlich sinkt.

Die entgegengesetzte Funktion zu MarketBookAdd ist MarketBookRelease. Sie entfernt das "Abonnement" der Benachrichtigungen über Änderungen der Markttiefe. Programmierern wird empfohlen, diese Löschung des Abonnements in OnDeinit vorzunehmen und somit den Datenzugriff bei der Schließung des Expert Advisors oder Indikators ebenfalls zu schließen:

void OnDeinit(const int reason)
{
   MarketBookRelease("Si-9.15");
}

Der Aufruf der Funktion MarketBookAdd bedeutet im Wesentlichen, dass zum Zeitpunkt der Änderung der Markttiefe für das erforderliche Instrument die spezielle Funktion OnBookEvent() zum Verarbeiten von Ereignissen aufgerufen wird. Somit beinhaltet ein einfacher Expert Advisor oder Indikator, der mit der Markttiefe arbeitet, drei Systemfunktionen:

  • OnInit – Initialisierungsfunktion des Expert Advisors oder Indikators zum Abonnieren von Aktualisierungen bei Änderungen der DOM für das erforderliche Instrument.
  • OnDeinit – Deinitialisierungsfunktion des Expert Advisors oder Indikators zum Entfernen des Abonnements von Informationen bei Änderungen der Markttiefe für das erforderliche Instrument.
  • OnBookEvent – eine Funktion, die nach der Änderung der Markttiefe aufgerufen wird, um zu signalisieren, dass die Markttiefe sich geändert hat.

Unser erster einfacher Expert Advisor wird diese drei Funktionen beinhalten. Wie Sie im nachfolgenden Beispiel sehen, wird für jede Änderung der Markttiefe die folgende Nachricht eingegeben: "Depth of Market for Si-9.15 was changed" (Markttiefe für Si-9.15 hat sich geändert):

//+------------------------------------------------------------------+
//|                                                       Expert.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"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   MarketBookAdd("Si-9.15");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   MarketBookRelease("Si-9.15");
  }
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   printf("Depth of Market for " + symbol +  " was changed"); 
  }
//+------------------------------------------------------------------+

Wichtige Anweisungen im Code sind gelb markiert.

 

1,3 Erhalten von Geboten zweiter Ebene mit MarketBookGet-Funktionen und MqlBookInfo-Struktur

Nun, da wir gelernt haben, wie man Benachrichtigungen über Änderungen der Markttiefe erhält, müssen wir lernen, wie man mithilfe der Funktion MarketBookGet auf DOM-Informationen zugreift. Sehen wir uns ihren Prototyp und ihre Verwendung an.

Wie bereits erwähnt, wird die Markttiefe durch eine spezielle Tabelle aus zwei Teilen zum Anzeigen von Limit-Ordern zum Verkaufen und Kaufen dargestellt. Wie bei jeder anderen Tabelle ist es auch bei dieser am einfachsten, die Markttiefe in Form eines Arrays darzustellen, wobei der Index des Arrays die Zeilennummer der Tabelle ist und der Wert des Arrays eine bestimmte Zeile oder Sequenz von Daten, die aus Volumen, Preis und Ordertyp besteht. Stellen wir uns die Markttiefe also als Tabelle vor, die den Index jeder Zeile zeigt:

Zeilenindex Ordertyp Volumen Kurs
0 Sell Limit 18 56844
1  Sell Limit  1  56843
2  Sell Limit  21  56842
3  Buy Limit  9  56836
4  Buy Limit  5  56835
5  Buy Limit  15  56834

 Tabelle 1 Darstellung der Markttiefe als Tabelle

Um uns die Orientierung in der Tabelle zu erleichtern, sind Order zum Verkaufen rosa markiert, Order zum Kaufen blau. Bei der Tabelle der Markttiefe handelt es sich grundsätzlich um ein zweidimensionales Array. Die erste Dimension deutet auf eine Zeilennummer und die zweite auf einen der drei Faktoren der Tabelle (Ordertyp – 0, Ordervolumen – 1 und Orderpreis – 2). Um allerdings die Arbeit mit mehrdimensionalen Arrays zu vermeiden, wird in MQL5 eine spezielle MqlBookInfo-Struktur genutzt. Sie enthält alle erforderlichen Werte. Auf diese Weise enthält jeder Index der Markttiefe eine MqlBookInfo-Struktur, die ihrerseits Informationen über den Typ, das Volumen und den Preis einer Order beinhaltet. Lassen Sie uns diese Struktur definieren:

struct MqlBookInfo
  {
   ENUM_BOOK_TYPE   type;       // order type from ENUM_BOOK_TYPE enumeration
   double           price;      // order price
   long             volume;     // order volume
  };

Nun dürfte die Methode für die Arbeit mit der Markttiefe klar sein. Die Funktion MarketBookGet gibt ein Array von MqlBookInfo-Strukturen aus. Der Index des Arrays zeigt die Zeile der Preistabelle und die Struktur des Index enthält Informationen über Volumen, Preis und Typ der Order. Mit diesem Wissen versuchen wir, auf die erste DOM-Order zuzugreifen, und modifizieren aus diesem Grund ein wenig die Funktion OnBookEvent unseres Expert Advisors aus dem vorherigen Beispiel:

//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   //printf("Depth of Market " + symbol +  " changed"); 
   MqlBookInfo book[];
   MarketBookGet(symbol, book);
   if(ArraySize(book) == 0)
   {
      printf("Failed load market book price. Reason: " + (string)GetLastError());
      return;
   }
   string line = "Price: " + DoubleToString(book[0].price, Digits()) + "; ";
   line += "Volume: " + (string)book[0].volume + "; ";
   line += "Type: " + EnumToString(book[0].type);
   printf(line);
  }

Beim Ausführen des Expert Advisors auf einem beliebigen Diagramm erhalten wir Berichte über die erste DOM und deren Parameter:

2015.06.05 15:54:17.189 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.078 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.061 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
...

Nach einem Blick auf unsere vorherige Tabelle ist es einfach zu erraten, dass die Ebene, die dem Null-Index der DOM zur Verfügung steht, dem schlechtesten Ask-Preis (BOOK_TYPE_SELL) entspricht. Umgekehrt nimmt der geringste Bid-Preis den letzten Index im erhaltenen Array ein. Die besten Ask- und Bid-Preise befinden sich etwa in der Mitte der Markttiefe. Der erste Nachteil der erhaltenen Preise ist, dass die Berechnung der Markttiefe für gewöhnlich anhand der besten Preise durchgeführt wird, die sich normalerweise in der Mitte der Tabelle befinden. Die niedrigsten Ask- und Bid-Preise sind zweitrangig. Bei der Analyse der Klasse CMarketBook werden wir dieses Problem lösen, indem wir spezielle Indexierer bereitstellen, die praktisch für die Arbeit mit der Markttiefe sind.

 

KAPITEL 2 Klasse CMarketBook für den einfachen Zugriff auf und Arbeit mit der Markttiefe


2,1 Entwurf der Klasse CMarketInfoBook

Im ersten Kapitel haben wir uns mit den Systemfunktionen für die Arbeit mit der Markttiefe vertraut gemacht und die Besonderheiten des Ereignismodells zum Einrichten des Zugriffs auf Gebote auf zweiter Ebene geklärt. In diesem Kapitel werden wir die spezielle Klasse CMarketBook für einen bequemen Gebrauch der Standard-Markttiefe erschaffen. Basierend auf dem Wissen, das wir uns im ersten Kapitel angeeignet haben, können wir die Eigenschaften besprechen, die unsere Klasse aufweisen muss, um mit dieser Art von Daten arbeiten zu können.

Als Erstes muss für den Entwurf dieser Klasse also die Ressourcenintensität der erhaltenen Daten betrachtet werden. Die Markttiefe kann mehrere Dutzend Mal pro Sekunde aktualisiert werden und beinhaltet außerdem Dutzende Elemente des Typen MqlBookInfo. Deshalb muss unsere Klasse zunächst mit nur einem Instrument arbeiten. Bei der Verarbeitung mehrerer Markttiefen von verschiedenen Instrumenten ist es ausreichend, mehrere Kopien unserer Klasse mit spezifischer Bezeichnung der Instrumente zu erstellen:

CMarketBook("Si-9.15");            // Depth of Market for Si-9.15
CMarketBook("ED-9.15");            // Depth of Market for ED-9.15
CMarketBook("SBRF-9.15");          // Depth of Market for SBRF-9.15
CMarketBook("GAZP-9.15");          // Depth of Market for GAZP-9.15

Der zweite Aspekt, um den wir uns kümmern müssen, ist die Einrichtung eines einfachen Datenzugriffs. Da die Limit-Order einen hochfrequenten Strom von Preisebenen erzeugen, ist es unmöglich, die Markttiefe in eine sichere objektorientierte Tabelle zu kopieren. Deshalb wird unsere Klasse einen direkten, wenn auch weniger sicheren Zugriff auf das von der Systemfunktion MarketBookGet bereitgestellte Array MqlBookInfo liefern. Um beispielsweise mit unserer Klasse auf einen Null-Index der DOM zuzugreifen, müssen wir Folgendes schreiben:

CMarketBook BookOnSi("Si-9.15");
...
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   MqlBookInfo info = BookOnSi.MarketBook[0];
  }
//+------------------------------------------------------------------+

MarketBook ist ein Array, das direkt mithilfe der Funktion MarketBookGet erhalten wird. Allerdings basiert der praktische Nutzen der Verwendung dieser Klasse vorrangig darauf, dass diese Klasse neben dem direkten Zugriff auf ein Array von Limit-Ordern einen gezielten Zugriff auf die meistgenutzten Preise der Markttiefe erlauben wird. Um beispielsweise den besten Ask-Preis zu erhalten, genügt es, Folgendes zu schreiben:

double best_ask = BookOnSi.InfoGetDouble(MBOOK_BEST_ASK_PRICE);

Das ist bequemer als die Berechnung des besten Ask-Index in Ihrem Expert Advisor und der anschließende Erhalt des Preiswertes auf Basis dieses Index. Aus dem oben aufgeführten Code ist ersichtlich, dass CMarketBook und viele weitere MQL5-Systemfunktionen wie SymbolInfoDouble oder OrderHistoryInteger ihren eigenen Satz von Modifikatoren und InfoGetInteger- und InfoGetDouble-Methoden nutzen, um auf ganzzahlige bzw. Double-Werte zuzugreifen. Um die erforderlichen Eigenschaften zu erhalten, müssen wir einen bestimmten Modifikator dieser Eigenschaft angeben. Lassen Sie uns die Modifikatoren dieser Eigenschaften detailliert beschreiben:

//+------------------------------------------------------------------+
//| Specifies modifiers for integer type properties                  |
//| of DOM.                                                          |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_INTEGER
{
   MBOOK_BEST_ASK_INDEX,         // Index of best Ask price
   MBOOK_BEST_BID_INDEX,         // Index of best Bid price
   MBOOK_LAST_ASK_INDEX,         // Index of worst Ask price
   MBOOK_LAST_BID_INDEX,         // Index of worst Bid price
   MBOOK_DEPTH_ASK,              // Number of Sell levels
   MBOOK_DEPTH_BID,              // Number of Buy levels
   MBOOK_DEPTH_TOTAL             // Total number of DOM levels
};
//+------------------------------------------------------------------+
//| Specifies modifiers for double type properties                   |
//| of DOM.                                                          |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_DOUBLE
{
   MBOOK_BEST_ASK_PRICE,         // Best Ask price
   MBOOK_BEST_BID_PRICE,         // Best Bid price
   MBOOK_LAST_ASK_PRICE,         // Worst Ask price 
   MBOOK_LAST_BID_PRICE,         // Worst Bid price
   MBOOK_AVERAGE_SPREAD          // Average spread between Ask and Bid
};

Natürlich enthält unsere Klasse neben den Methoden der Gruppe InfoGet... auch die Methode Refresh, die die Aktualisierung der Markttiefe auslöst. Da unsere Klasse eine Aktualisierung von Informationen durch den Aufruf der Methode Refresh() erfordert, nutzen wir nur dann eine ressourcenintensive Klassenaktualisierung, wenn dies erforderlich ist.

 

2,2 Berechnung der Indizes der meistgenutzten Ebenen der Markttiefe

Bei der Klasse CMarketBook handelt es sich um einen Wrapper des Arrays MqlBookInfo. Ihr Hauptzweck besteht darin, einen schnellen und bequemen Zugriff auf die am häufigsten angeforderten Informationen aus diesem Array bereitzustellen. Somit ermöglicht diese Klasse nur zwei grundlegende ressourcenintensive Operationen:

  • Kopieren des Arrays MqlBookInfo mithilfe der Systemfunktion MarketBookGet;
  • Berechnung der Indizes der meistgenutzten Preise.

Wir können die Arbeit der Systemfunktion MarketBookGet nicht beschleunigen, doch das ist auch nicht nötig, da alle Systemfunktionen der Sprache MQL5 maximal optimiert sind. Allerdings können wir eine schnellstmögliche Berechnung der erforderlichen Indizes sicherstellen. Wir wenden uns erneut den Eigenschaftsmodifikatoren ENUM_MBOOK_INFO_INTEGER und ENUM_MBOOK_INFO_DOUBLE zu. Wie Sie sehen können, basieren fast alle verfügbaren Eigenschaften auf der Berechnung von vier Indizes:

  • Index des besten Ask-Preises; 
  • Index des besten Bid-Preises;
  • Index des schlechtesten Bid-Preises;
  • Index des schlechtesten Ask-Preises.

Außerdem werden drei ganzzahlige Eigenschaften genutzt:

  • Menge der Preisebenen für den Verkauf oder Markttiefe für den Verkauf (Ask-Tiefe);
  • Menge der Preisebenen für den Kauf oder Markttiefe für den Kauf (Bid-Tiefe);
  • Gesamt-Markttiefe gleich der Gesamtmenge der Elemente in der DOM.

Es ist offensichtlich, dass der Index des schlechtesten Ask-Preises immer Null sein wird, da das mithilfe der Funktion MarketBookGet erhaltene Array immer mit dem schlechtesten Ask-Preis beginnt. Die Suche nach dem schlechtesten Bid-Preis erweist sich ebenfalls als trivial – er hat immer den letzten Index im erhaltenen Array MqlInfoBook (wir möchten Sie daran erinnern, dass der Index des letzten Elements des Arrays geringer ist als die Gesamtmenge der Elemente dieses Arrays pro Einheit):

Index des schlechtesten Ask-Preises = 0

Index des schlechtesten Bid-Preises = Gesamtmenge der Elemente in der Markttiefe - 1

Die Indizes der ganzzahligen Eigenschaften sind ebenfalls leicht zu berechnen. So entspricht die Gesamt-Markttiefe immer der Menge der Elemente im Array MqlBookInfo. Die Markttiefe von der Ask-Seite berechnet sich folgendermaßen:

Ask-Tiefe = Index des besten Ask-Preises - Index des schlechtesten Ask-Preises + 1

Wir addieren immer eins, da die Nummerierung im Array mit Null beginnt und zum Bestimmen der Menge der Elemente eins zum besseren Index addiert werden muss. Indem wir allerdings eins zum Index des besten Ask-Preises addieren, erhalten wir bereits den Index des besten Bid-Preises. Addieren wir beispielsweise in Tabelle 1 eins zur Zeile mit der zweiten Nummer, gehen wir von der besten Sell-Limit-Order beim Preis 56.842 zur besten Buy-Limit-Order beim Preis 56.836 über. Ebenfalls haben wir herausgefunden, dass der Index des schlechtesten Ask-Preises immer Null ist. Deshalb können wir die Formel zum Finden der Ask-Tiefe entsprechend reduzieren:

Ask-Tiefe = Index des besten Bid-Preises

Die Berechnung der Bid-Tiefe gestaltet sich etwas anders. Es ist offensichtlich, dass die Menge der Kauforder der Gesamtmenge der Order abzüglich der Menge von Verkaufsordern oder der Ask-Tiefe entspricht. Da wir in der vorherigen Formel gelernt haben, dass die Ask-Tiefe dem Index des besten Bid-Preises entspricht, fällt es uns nicht weiter schwer, die Formel zum Bestimmen der Menge von Kaufordern oder der Bid-Tiefe herzustellen:

Bid-Tiefe = Gesamt-Markttiefe - Index des besten Ask-Preises

Die Gesamt-Markttiefe entspricht immer der Gesamttiefe von Ask und Bid und ist somit gleich der Gesamtmenge der Elemente in der Markttiefe:

Gesamt-Markttiefe = Gesamtmenge der Elemente in der Markttiefe

Analytisch betrachtet, haben wir fast alle häufig genutzten Indizes gefunden. Mithilfe mathematischer Kürzungen haben wir die Berechnung der Indizes durch eine direkte Indexierung ersetzt. Das ist in der Klasse CMarketBook äußerst wichtig, da der schnellstmögliche Zugriff auf die Eigenschaften und Indizes der Markttiefe benötigt wird.

Neben den tatsächlichen Indizes muss häufig das durchschnittliche Spread-Niveau für das aktuelle Instrument bekannt sein. Ein Spread ist die Differenz zwischen den besten Bid- und Ask-Preisen. Die Klasse CMarketBook ermöglicht den Erhalt des Durchschnittswerts dieses Parameters mithilfe der Methode InfoGetDouble, indem sie mit dem Modifikator MBOOK_AVERAGE_SPREAD aufgerufen wird. CMarketBook berechnet den aktuellen Spread in der Methode Refresh sowie dessen Durchschnittswert, indem sie sich die Anzahl der Aufrufe dieser Methode merkt.

Allerdings haben wir die Hauptindizes der besten Bid- und Ask-Preise noch nicht gefunden, also fahren wir mit dem nächsten Abschnitt fort.

 

2,3 Vorhersagen der Indizes der besten Bid- und Ask-Preise auf Basis vorhergehender Werte dieser Indizes

Die Berechnung der besten Ask- und Bid-Preise ist die schwierigere Aufgabe, die wir noch lösen müssen. Zum Beispiel dient in Tabelle 1 der Index des besten Ask-Preises als Index mit der Nummer 2 und der Index des besten Bid-Preises als Index Nummer 3. In dieser Tabelle wird eine vereinfachte Markttiefe aus nur 6 Ebenen präsentiert. Tatsächlich kann die Markttiefe wesentlich größer sein und bis zu 64 Kauf- und Verkaufsebenen beinhalten. Wenn man berücksichtigt, dass die DOM mehrmals in der Sekunde aktualisiert werden kann, ist das ein bedeutender Wert.

Die einfachste Lösung wäre hier eine Teilung durch zwei. Und tatsächlich: Wenn wir die Gesamtmenge der Ebenen in Tabelle 1 nehmen (6) und sie durch zwei teilen, ist die erhaltene Zahl (3) der Index des besten Bid-Preises. Somit ist der vorhergehende Index der Index des besten Ask-Preises (2). Allerdings funktioniert diese Methode nur dann, wenn die Menge der Verkaufsebenen gleich der Menge der Kaufebenen in der DOM ist. Dies geschieht für gewöhnlich nur auf liquiden Märkten, allerdings kann die Markttiefe auf illiquiden Märkten nur unvollständig gefüllt sein und auf einer Seite kann es möglicherweise gar keine Ebenen geben.

Unsere Klasse CMarketBook muss auf allen möglichen Märkten und mit jeder beliebigen Markttiefe arbeiten können, also ist die Methode des Teilens durch zwei für uns ungeeignet. Zur Illustration einer Situation, in der das Teilen durch zwei möglicherweise nicht funktioniert, ziehen wir die folgende Abbildung heran:


Abb. 3 Die Menge der Bid-Preisebenen entspricht nicht immer der Menge der Ask-Preisebenen

In Abbildung 3 werden zwei Markttiefen illustriert. Die erste ist die DOM eines Terminkontrakts für zweijährige Bundesanleihen (OFZ2-9.15). Die zweite ist ein EUR/USD-Terminkontrakt (ED-9.15). Die Abbildung zeigt, dass die Menge der Kauf-Preisniveaus für OFZ2-9.15 gleich vier ist, während die Menge der Verkaufs-Preisniveaus gleich acht ist. Auf dem liquideren ED-9.15-Markt beträgt die Menge der Kauf- und Verkaufsebenen 12 für beide Seiten. Im Fall von ED-9.15 hätte die Methode zur Bestimmung der Indizes durch die Teilung durch zwei funktioniert, bei OFZ2 nicht.

Eine zuverlässigere Methode, den Index zu finden, wäre die Nutzung der Iteration der DOM bis zum Auftreten der ersten Order mit dem Typen BOOK_TYPE_BUY. Der vorhergehende Index würde automatisch zum Index des besten Ask-Preises werden. Diese Methode wird von der Klasse CMarketBook genutzt. Wir beziehen uns darauf, um das oben Aufgeführte zu illustrieren:

void CMarketBook::SetBestAskAndBidIndex(void)
{
   if(!FindBestBid())
   {
      //Find best ask by slow full search
      int bookSize = ArraySize(MarketBook);   
      for(int i = 0; i < bookSize; i++)
      {
         if((MarketBook[i].type == BOOK_TYPE_BUY) || (MarketBook[i].type == BOOK_TYPE_BUY_MARKET))
         {
            m_best_ask_index = i-1;
            FindBestBid();
            break;
         }
      }
   }
}

Der Hauptzweck dieser Methode ist die Iteration der DOM durch den Operator for. Sobald die erste Order mit dem Typen BOOK_TYPE_BUY in der Markttiefe auftritt, werden die Indizes der besten Bid- und Ask-Preise festgelegt und die Iteration unterbrochen. Eine vollständige Iteration der Markttiefe bei jeder Aktualisierung wäre extrem ressourcenintensiv.

Anstatt einer Iteration für jede Aktualisierung gibt es die Option, sich vorher erhaltene Indizes der besten Bid- und Ask-Preise zu merken. Tatsächlich beinhaltet die Markttiefe für gewöhnlich eine feste Menge an Kauf- und Verkaufsebenen. Somit ist es nicht nötig, die Markttiefe jedes Mal zu iterieren, um neue Indizes zu finden. Es reicht, sich auf vorher gefundene Indizes zu beziehen und zu verstehen, ob es sich dabei immer noch um die Indizes der besten Bid- und Ask-Preise handelt. Die private Methode FindBestBid wird zum Lösen dieser Aufgabe verwendet. Sehen wir uns ihren Inhalt an:

//+------------------------------------------------------------------+
//| Fast find best bid by best ask                                   |
//+------------------------------------------------------------------+
bool CMarketBook::FindBestBid(void)
{
   m_best_bid_index = -1;
   bool isBestAsk = m_best_ask_index >= 0 && m_best_ask_index < m_depth_total &&
                    (MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL ||
                    MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL_MARKET);
   if(!isBestAsk)return false;
   int bestBid = m_best_ask_index+1;
   bool isBestBid = bestBid >= 0 && bestBid < m_depth_total &&
                    (MarketBook[bestBid].type == BOOK_TYPE_BUY ||
                    MarketBook[bestBid].type == BOOK_TYPE_BUY_MARKET);
   if(isBestBid)
   {
      m_best_bid_index = bestBid;
      return true;
   }
   return false;
}

Ihre Arbeitsweise ist einfach. Als Erstes überprüft die Methode, ob der aktuelle Index des besten Ask-Preises immer noch dem Index des Ask-Preises entspricht. Dann setzt sie den Index des besten Bid-Preises zurück und versucht, ihn erneut zu finden, indem sie sich auf das Element bezieht, das auf den Index des besten Ask-Preises folgt:

int bestBid = m_best_ask_index+1;

Wenn das gefundene Element tatsächlich der Index des besten Bid-Preises ist, dann hatte der vorhergehende Zustand der DOM die gleiche Menge von Kauf- und Verkaufsebenen wie der aktuelle. Somit kann die Iteration der DOM vermieden werden, da in der Methode SetBestAskAndBidIndex die Methode FindBestBid vor der Iteration aufgerufen wird. Also wird die Iteration der DOM nur beim ersten Aufruf der Funktion ausgeführt sowie bei einer Veränderung der Menge der Verkaufs- und/oder Kaufebenen.

Obwohl sich der resultierende Quellcode als umfangreicher und komplexer erwies als eine einfache Markttiefe, arbeitet er tatsächlich schneller. Die Performance-Zunahme macht sich vor allen bei großen DOM von liquiden Märkten bemerkbar. Simple Anweisungen zur Prüfung der Bedingungen werden sehr schnell ausgeführt und die Anzahl dieser Prüfungen ist viel geringer als die Menge der Schleifen des Operators for. Deshalb ist die Performance dieser Methode zum Finden der besten Preisindizes höher als die der normalen Iteration.

 

2,4 Bestimmen der maximalen Slippage mit der Methode GetDeviationByVol

Die Markttiefe wird von Händlern häufig genutzt, um die aktuelle Liquidität des Marktes zu bestimmen, d. h. die Markttiefe wird als zusätzliches Instrument der Risikokontrolle verwendet. Wenn die Liquidität des Marktes gering ist, kann ein Markteintritt über Marktorder eine hohe Slippage verursachen. Slippage bedeutet immer zusätzliche Verluste, die beträchtliche Werte annehmen können.

Um solchen Situationen vorzubeugen, müssen die zusätzlichen Methoden zur Kontrolle des Markteintritts genutzt werden. Weitere Informationen finden Sie im Beitrag "Sichern Ihres Expert Advisors beim Handel auf der Moskauer Börse". Deshalb werden wir diese Methoden nicht detailliert beschreiben und erwähnen nur, dass wir durch den Zugriff auf die Markttiefe den Wert der potenziellen Slippage schätzen können, bevor wir den Markt betreten. Das Ausmaß der Slippage hängt von zwei Faktoren ab:

  • aktuelle Liquidität der Bid-Seite (Verkaufsorder) und Ask-Seite (Kauforder);
  • Volumen eines Abschlusses.

Mit dem Zugriff auf die Markttiefe können wir sehen, bei welchem Volumen und welchen Preisen unsere Order ausgeführt wird. Wenn wir das Volumen unserer Order kennen, können wir den gewichteten Durchschnittspreis für den Markteintritt berechnen. Die Differenz zwischen diesem Preis und dem besten Bid- oder Ask-Preis (je nach Eintrittsrichtung) ist unsere Slippage.

Es ist unmöglich, den gewichteten Durchschnitt des Eintrittspreises manuell zu berechnen, da eine große Menge an Berechnungen in sehr kurzer Zeit durchgeführt werden muss (ich möchte Sie daran erinnern, dass sich der Zustand der DOM mehrere Male in der Sekunde ändern kann). Also ist es selbstverständlich, diese Aufgabe an einen Expert Advisor oder Indikator zu übertragen.

Die Klasse CMarketBook beinhaltet eine spezielle Methode zum Berechnen dieser Eigenschaft: GetDeviationByVol. Da sich das Volumen des Abschlusses auf die Größe der Slippage auswirkt, muss das Volumen, von dem erwartet wird, dass es auf dem Markt ausgeführt wird, an die Methode übergeben werden. Da diese Methode ganzzahlige Werte von Volumina nutzt, wie es auch auf der Moskauer Futures-Börse getan wird, nimmt die Methode ein Volumen in Form eines long-Wertes an. Zusätzlich muss der Methode bekannt sein, für welche Seite der Liquidität die Berechnung durchgeführt werden soll. Deshalb wird die spezielle Aufzählung ENUM_MBOOK_SIDE verwendet:

//+------------------------------------------------------------------+
//| Side of MarketBook.                                              |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_SIDE
{
   MBOOK_ASK,                    // Ask side
   MBOOK_BID                     // Bid side
};

Führen wir nun den Quellcode der Methode GetDeviationByVol ein:

//+------------------------------------------------------------------+
//| Get deviation value by volume. Return -1.0 if deviation is       |
//| infinity (insufficient liquidity)                                |
//+------------------------------------------------------------------+
double CMarketBook::GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side)
{
   int best_ask = InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int last_ask = InfoGetInteger(MBOOK_LAST_ASK_INDEX); 
   int best_bid = InfoGetInteger(MBOOK_BEST_BID_INDEX);
   int last_bid = InfoGetInteger(MBOOK_LAST_BID_INDEX);
   double avrg_price = 0.0;
   long volume_exe = vol;
   if(side == MBOOK_ASK)
   {
      for(int i = best_ask; i >= last_ask; i--)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   else
   {
      for(int i = best_bid; i <= last_bid; i++)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   if(volume_exe > 0)
      return -1.0;
   avrg_price/= (double)vol;
   double deviation = 0.0;
   if(side == MBOOK_ASK)
      deviation = avrg_price - MarketBook[best_ask].price;
   else
      deviation = MarketBook[best_bid].price - avrg_price;
   return deviation;
}

Wie Sie sehen können, hat der Code einen beträchtlichen Umfang, doch das Prinzip seiner Berechnung ist alles andere als komplex. Als Erstes wird die Iteration der Markttiefe vom besten zum schlechtesten Preis ausgeführt. Die Iteration wird für jede Richtung zur jeweiligen Seite ausgeführt. Während der Iteration wird das aktuelle Volumen zum Gesamtvolumen addiert. Wenn das Gesamtvolumen aufgerufen wird und dem erforderlichen Volumen entspricht, wird die Schleife verlassen. Anschließend wird der durchschnittliche Eintrittspreis für ein bestimmtes Volumen berechnet. Und letztendlich wird die Differenz zwischen dem durchschnittlichen Eintrittspreis und den besten Bid-/Ask-Preisen berechnet. Die absolute Differenz stellt die geschätzte Abweichung dar.

Diese Methode ist erforderlich, um die direkte Iteration der DOM zu berechnen. Obwohl nur einer von zwei Teilen der DOM iteriert wird – und das meistens auch nur teilweise – dauert eine solche Berechnung dennoch länger als die Berechnung häufig genutzter Indizes der DOM. Deshalb wird diese Berechnung in einer separaten Methode direkt umgesetzt und nur bei Bedarf ausgeführt, d. h. in Fällen, in denen diese Informationen in eindeutiger Form benötigt werden.

 

2,5 Beispiele für die Arbeit mit der Klasse CMarketBook

Wir haben die grundlegenden Methoden der Klasse CMarketBook also behandelt und sollten den Umgang mit ihnen nun etwas üben. Unser Testbeispiel ist ziemlich simpel und verständlich, auch für Programmierneulinge. Schreiben wir einen Test-EA zum Ausführen der einmaligen Ausgabe von Informationen auf Basis der DOM. Sicherlich wäre ein Script zu diesem Zweck besser geeignet, doch der Zugriff auf die Markttiefe über ein Script ist nicht möglich und es muss entweder ein Expert Advisor oder ein Indikator verwendet werden. Der Quellcode unseres EAs ist nachfolgend aufgeführt:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.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 <Trade\MarketBook.mqh>     // Include CMarketBook class
CMarketBook Book(Symbol());         // Initialize class with current instrument

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                  // Update Depth of Market status.
   /* Obtain basic statistics */
   int total=Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   int total_ask = Book.InfoGetInteger(MBOOK_DEPTH_ASK);
   int total_bid = Book.InfoGetInteger(MBOOK_DEPTH_BID);
   int best_ask = Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int best_bid = Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);

   printf("TOTAL DEPTH OF MARKET: "+(string)total);
   printf("NUMBER OF PRICE LEVELS FOR SELL: "+(string)total_ask);
   printf("NUMBER OF PRICE LEVELS FOR BUY: "+(string)total_bid);
   printf("INDEX OF BEST ASK PRICE: "+(string)best_ask);
   printf(INDEX OF BEST BID: "+(string)best_bid);
   
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE);
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE);
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);
   
   printf("BEST ASK PRICE: " + DoubleToString(best_ask_price, Digits()));
   printf("BEST BID PRICE: " + DoubleToString(best_bid_price, Digits()));
   printf("WORST ASK PRICE: " + DoubleToString(last_ask, Digits()));
   printf("WORST BID PRICE: " + DoubleToString(last_bid, Digits()));
   printf("AVERAGE SPREAD: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

Beim Ausführen dieses Test-EAs auf dem OFZ2-Diagramm erhalten wir den folgenden Bericht:

2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   AVERAGE SPREAD: 70
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   WORST BID PRICE: 9831
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   WORST ASK PRICE: 9999
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST BID PRICE: 9840
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST ASK PRICE: 9910
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST BID INDEX: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST ASK INDEX: 6
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   NUMBER OF PRICE LEVELS FOR BUY: 2
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   NUMBER OF PRICE LEVELS FOR SELL: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   TOTAL DEPTH OF MARKET: 9

Vergleichen wir den erhaltenen Bericht mit dem DOM-Screenshot für dieses Instrument:


Abb. 4 Markttiefe für OFZ2 beim Ausführen eines Testberichts

Wir haben bestätigt, dass die erhaltenen Indizes und Preise vollständig mit der aktuellen Markttiefe übereinstimmen. 

 

KAPITEL 3 Schreiben Ihrer eigenen Markttiefe als Indikator-Panel


3,1 Grundprinzipien für den Entwurf des Markttiefe-Panels. Erstellen eines Indikators

Mit dem Zugriff auf die Klasse CMarketBook ist es vergleichsweise einfach, ein spezielles Panel zu erstellen, das die aktuelle Markttiefe direkt im Diagramm anzeigt. Wir erstellen unser Panel basierend auf dem benutzerdefinierten Indikator. Der Indikator wurde deshalb als Basis gewählt, weil sich je nur ein Expert Advisor in jedem Diagramm befinden kann, während die Menge der Indikatoren unbegrenzt ist. Wenn wir einen Expert Advisor als Basis für das Panel verwenden, ist der Handel mit einem Expert Advisor auf demselben Diagramm nicht länger möglich, was ziemlich unpraktisch wäre.

Wir geben unserer Markttiefe die Möglichkeit, im Diagramm ein- und ausgeblendet zu werden, wie es auch beim Standard-Handelspanel für jedes Diagramm umgesetzt wird. Wir nutzen denselben Button zum Ein- und Ausblenden:

 

Abb. 5 Standard-Handelspanel in MetaTrader 5

Unsere Markttiefe wird in der linken oberen Ecke des Diagramms platziert, wo sich auch das Handelspanel befindet. Dies hängt damit zusammen, dass ein handelnder Expert Advisor in dem Diagramm ausgeführt wird, in dem sich der Indikator der Markttiefe befindet. Um sein Icon in der rechten oberen Ecke nicht zu verdecken, verschieben wir unser Panel nach links.

Bei der Erstellung des Indikators muss eine der beiden Systemfunktionen OnCalculate genutzt werden. Da unser Panel die Informationen aus diesen Funktionen nicht verwertet, lassen wir diese Methoden leer. Außerdem wird der Indikator keine grafische Serie nutzen, also ist die Eigenschaft indicator_plots hier gleich Null.

Unser Indikator wird hauptsächlich die Systemfunktion OnBookEvent nutzen, also müssen wir das aktuelle Diagrammsymbol abonnieren, um Informationen über Änderungen der Markttiefe zu erhalten. Wir richten das Abonnement mithilfe der bereits bekannten Funktion MarketBookAdd ein.

Das Panel mit der Markttiefe wird in Form der speziellen Klasse CBookPanel umgesetzt. Lassen Sie uns, ohne tiefer ins Detail zu gehen, den Code der grundlegendsten Indikatordatei aufführen:

//+------------------------------------------------------------------+
//|                                                   MarketBook.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"
#property indicator_chart_window
#property indicator_plots 0
#include <Trade\MarketBook.mqh>
#include "MBookPanel.mqh"

CBookPanel Panel;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   MarketBookAdd(Symbol());
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   Panel.Refresh();
  }
//+------------------------------------------------------------------+
//| Chart events                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event identifier  
                  const long& lparam,   // event parameter of long type
                  const double& dparam, // event parameter of double type
                  const string& sparam) // event parameter of string type
  {
   Panel.Event(id,lparam,dparam,sparam);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Jetzt enthält die Klasse CBookPanel nur die grundlegenden Elemente für ihre Arbeit: den Pfeil, den man anklickt, damit die Markttiefe eingeblendet wird, und das Label "MarketBook" zum Abonnieren unserer zukünftigen Markttiefe. Bei der Ausführung unseres Indikators sieht das Diagramm so aus:

 

Abb. 6 Lage des zukünftigen Panels MarketBook im Diagramm

Jedes Element dieser Klasse stellt außerdem eine unabhängige, von der Basisklasse CNode abgeleitete Klasse dar. Diese Klasse enthält Basismethoden wie Show und Hide, die in Nachfahrenklassen überschrieben werden können. Die Klasse CNode erzeugt außerdem einen eindeutigen Namen für jede Instanz, wodurch die Verwendung von Standardfunktionen für die Erstellung von grafischen Objekten und die Einstellung ihrer Eigenschaften erleichtert wird.

 

3,2 Verarbeiten von Klick-Ereignissen und Erstellen der Form der Markttiefe

Aktuell reagiert unser Indikator nicht auf das Anklicken des Pfeils, also arbeiten wir weiter daran. Das Erste, was wir für unser Panel tun, ist, den Ereignis-Handler OnChartEvent einzugeben. Wir nennen diese Methode Event. Sie nimmt die Parameter aus OnChartEvent an. Außerdem erweitern wir die Basisklasse CNode durch das Array CArrayObj, das andere grafische Elemente des Typen CNode beinhaltet. In Zukunft hilft uns dies bei der Erstellung zahlreicher Elemente desselben Typen – Zellen der Markttiefe.

Nun führen wir den Quellcode der Klasse CBookPanel und ihrer übergeordneten Klasse CNode auf: 

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"
#include "MBookFon.mqh"
//+------------------------------------------------------------------+
//| CBookPanel class                                                 |
//+------------------------------------------------------------------+
class CBookPanel : CNode
  {
private:
   CMarketBook       m_book;
   bool              m_showed;
   CBookText         m_text;
public:
   CBookPanel();
   ~CBookPanel();
   void              Refresh();
   virtual void Event(int id, long lparam, double dparam, string sparam);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBookPanel::CBookPanel()
{
   m_elements.Add(new CBookFon(GetPointer(m_book)));
   ObjectCreate(ChartID(), m_name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 70);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, -3);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetString(ChartID(), m_name, OBJPROP_FONT, "Webdings");
   ObjectSetString(ChartID(), m_name, OBJPROP_TEXT, CharToString(0x36));
}
CBookPanel::~CBookPanel(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(), m_name);
}

CBookPanel::Refresh(void)
{

}

CBookPanel::Event(int id, long lparam, double dparam, string sparam)
{
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
      {
         if(sparam != m_name)return;
         if(!m_showed)OnShow();        
         else OnHide();
         m_showed = !m_showed;
      }
   }
}
//+------------------------------------------------------------------+

Die Methode Refresh, die den Zustand der DOM aktualisiert, ist noch nicht voll. Wir erstellen sie etwas später. Die aktuelle Funktionalität ermöglicht es uns bereits, den ersten Prototypen unserer Markttiefe zu zeigen. Bislang wird beim Anklicken des Pfeils nur die graue Standard-Zeichenfläche angezeigt. Bei erneutem Klicken wird sie ausgeblendet:

 

Abb. 7 Aussehen der zukünftigen Markttiefe

Die Markttiefe sieht noch nicht sehr überzeugend aus, aber wir werden sie weiter verbessern.

 

3,3 Zellen der Markttiefe

Zellen bilden die Basis der Markttiefe. Jede Zelle ist ein Tabellenelement, das Informationen über Volumina oder Preise beinhaltet. Zellen lassen sich auch anhand ihrer Farbe unterscheiden: bei Limit-Ordern für den Kauf sind sie blau eingefärbt, bei Limit-Ordern für den Verkauf rosa. Die Menge der Zellen kann sich bei jeder Markttiefe unterscheiden, deshalb müssen alle Zellen dynamisch auf Anfrage erzeugt und im speziellen Datencontainer CArrayObj gespeichert werden. Da alle Zellen unabhängig davon, welche Information sie zeigen, die gleiche Größe und den gleichen Typ haben, ist die Klasse, die verschiedene Typen von Zellen implementiert, für alle Typen von Zellen gleich.

Für Zellen, die Volumina zeigen, und für Zellen, die Preise zeigen, wird die spezielle Klasse CBookCeil verwendet. Der Zellentyp wird beim Erstellen eines Objekts dieser Klasse festgelegt, sodass jeder Klasseninstanz bekannt ist, welche Art von Informationen von der Markttiefe angezeigt werden sollen und welche Farbe der Hintergrund haben soll. CBookCeil nutzt zwei grafische Grundelemente: die Textbeschriftung OBJ_TEXT_LABEL und die rechteckige Beschriftung OBJ_RECTANGLE_LABEL. Erstere zeigt den Text an, letztere die tatsächliche Zelle der Markttiefe.

Hier sehen Sie den Quellcode der Klasse CBookCeil:

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include "Node.mqh"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"

#define BOOK_PRICE 0
#define BOOK_VOLUME 1

class CBookCeil : public CNode
{
private:
   long  m_ydist;
   long  m_xdist;
   int   m_index;
   int m_ceil_type;
   CBookText m_text;
   CMarketBook* m_book;
public:
   CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book);
   virtual void Show();
   virtual void Hide();
   virtual void Refresh();
   
};

CBookCeil::CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book)
{
   m_ydist = y_dist;
   m_xdist = x_dist;
   m_index = index_mbook;
   m_book = book;
   m_ceil_type = type;
}

void CBookCeil::Show()
{
   ObjectCreate(ChartID(), m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, m_xdist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   m_text.Show();
   m_text.SetXDist(m_xdist+10);
   m_text.SetYDist(m_ydist+2);
   Refresh();
}

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
}

void CBookCeil::Hide(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(),m_name);
}

Die Hauptarbeit dieser Klasse geschieht mithilfe der Methoden Show und Refresh. Letztere färbt die Zellen je nach dem übertragenen Zellentyp in der entsprechenden Farbe ein und zeigt das Volumen oder den Preis darin an. Um eine Zelle zu erstellen, müssen Sie ihren Typen, die Lage auf der X-Achse, die Lage auf der Y-Achse, den dieser Zelle entsprechenden DOM-Index und die Markttiefe, von der die Zelle ihre Informationen erhalten wird, angeben.

Die spezielle private Methode CreateCeils erstellt Zellen in einer Klasse, die die Grundlage der DOM implementiert. Hier sehen Sie den Quellcode:

void CBookFon::CreateCeils()
{
   int total = m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   for(int i = 0; i < total; i++)
   {
      CBookCeil* Ceil = new CBookCeil(0, 12, i*15+20, i, m_book);
      CBookCeil* CeilVol = new CBookCeil(1, 63, i*15+20, i, m_book);
      m_elements.Add(Ceil);
      m_elements.Add(CeilVol);
      Ceil.Show();
      CeilVol.Show();
   }
}

Die Methode wird durch Anklicken des Pfeils aufgerufen, der die Markttiefe öffnet.

Nun ist alles bereit für die Erstellung unserer neuen Version der Markttiefe. Nach den vorgenommenen Änderungen und der Kompilierung des Projekts hat unser Indikator ein neues Aussehen angenommen:

 

Abb. 8 Erste Version der Markttiefe als Indikator

3,4 Anzeigen des Histogramms der Volumina in der Markttiefe

Die erhaltene Markttiefe erfüllt bereits ihre grundsätzliche Funktion: Sie zeigt Handelsebenen, Volumina und Preise von Limit-Ordern für den Kauf und Verkauf. Somit werden mit jeder Änderung der Werte in der Markttiefe auch die Werte in den entsprechenden Zellen verändert. Allerdings ist es optisch nicht einfach, den Überblick über die Volumina in der erhaltenen Tabelle zu behalten. Zum Beispiel wird das Volumen in einer Standard-DOM in MetaTrader 5 auf dem Hintergrund des Histogramms angezeigt, das die relative Größe des aktuellen Volumens in Bezug auf das maximale Volumen in der Markttiefe zeigt. Außerdem wäre es nicht schlecht, eine ähnliche Funktionalität in unserer Markttiefe zu implementieren.

Das Problem lässt sich auf verschiedene Arten lösen. Am einfachsten wäre es, alle erforderlichen Berechnungen direkt in der Klasse CBookCeil durchzuführen. Dafür muss Folgendes in ihre Refresh-Methode geschrieben werden:

void CBookCeil::Refresh(void)
{
   ...
   MqlBookInfo info = m_book.MarketBook[m_index];
   ...
   //Update Depth of Market histogram
   int begin = m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
   int end = m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   long max_volume = 0;
   if(m_ceil_type != BOOK_VOLUME)return;
   for(int i = begin; i < end; i++)
   {
      if(m_book.MarketBook[i].volume > max_volume)
         max_volume = m_book.MarketBook[i].volume;
   }
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

In der Methode der vollständigen Iteration der DOM befindet sich das maximale Volumen der DOM, dann wird das aktuelle Volumen durch das Maximum geteilt. Der erhaltene Anteil wird mit der maximalen Breite einer Zelle der Volumentabelle multipliziert (sie wird durch eine Konstante von 50 Pixeln dargestellt). Die resultierende Breite der Zeichenfläche ist das benötigte Histogramm:

 

Abb. 9 Markttiefe mit Histogramm der Volumina

Das Problem bei diesem Code ist allerdings, dass die Iteration der DOM bei jedem Aufruf von Refresh in jeder Zelle ausgeführt wird. Bei einer Markttiefe mit 40 Elementen bedeutet das 800 Iterationen der for-Schleife für jede Aktualisierung der Markttiefe. Jede Zelle iteriert nur für ihre eigene Seite über die Markttiefe, also besteht die Iteration innerhalb jeder Zelle aus zwanzig Iterationen (Markttiefe geteilt durch zwei). Obwohl moderne Computer diese Aufgabe bewältigen können, ist diese Methode äußerst ineffizient, insbesondere aufgrund der Tatsache, dass die Arbeit mit der Markttiefe die Verwendung der schnellsten und effizientesten Algorithmen erfordert.

 

3,5 Schnelle Berechnung der maximalen Volumina in der Markttiefe, Optimierung der Iteration

Leider lässt sich die vollständige Iteration der Markttiefe nicht vermeiden. Nach jeder Aktualisierung der Markttiefe können sich das maximale Volumen und dessen Preisniveau drastisch ändern. Allerdings können wir versuchen, die Anzahl der Iterationen zu verringern. Zu diesem Zweck müssen Sie lernen, nicht mehr als eine DOM-Iteration zwischen zwei Refresh-Aufrufen auszuführen. Als zweites müssen Sie die Anzahl der Aufrufe der vollständigen Iteration zu verringern. Dazu müssen Sie eine aufgeschobene Berechnung nutzen bzw. in anderen Worten diese Berechnung nur bei expliziter Aufforderung durchführen. Wir übertragen alle Berechnungen direkt an die Markttiefe-Klasse CMarketBook und schreiben innerhalb von CMarketBook die spezielle Unterklasse CBookCalculation für die Berechnung. Nachfolgend sehen Sie ihren Quellcode: 

class CMarketBook;

class CBookCalculation
{
private:
   int m_max_ask_index;         // Index of maximum Ask volume
   long m_max_ask_volume;       // Volume of maximum Ask price
   
   int m_max_bid_index;         // Index of maximum Bid volume
   long m_max_bid_volume;       // Volume of maximum Bid price
   
   long m_sum_ask_volume;       // Total volume of Ask price in DOM
   long m_sum_bid_volume;       // Total volume of Bid price in DOM.
   
   bool m_calculation;          // flag indicating that all calculations are executed
   CMarketBook* m_book;         // Depth of market indicator
   
   void Calculation(void)
   {
      // FOR ASK SIDE
      int begin = (int)m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
      int end = (int)m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_ask_volume)
         {
            m_max_ask_index = i;
            m_max_ask_volume = m_book.MarketBook[i].volume;
         }
         m_sum_ask_volume += m_book.MarketBook[i].volume;
      }
      // FOR BID SIDE
      begin = (int)m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
      end = (int)m_book.InfoGetInteger(MBOOK_LAST_BID_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_bid_volume)
         {
            m_max_bid_index = i;
            m_max_bid_volume = m_book.MarketBook[i].volume;
         }
         m_sum_bid_volume += m_book.MarketBook[i].volume;
      }
      m_calculation = true;
   }
   
public:
   CBookCalculation(CMarketBook* book)
   {
      Reset();
      m_book = book;
   }
   
   void Reset()
   {
      m_max_ask_volume = 0.0;
      m_max_bid_volume = 0.0;
      m_max_ask_index = -1;
      m_max_bid_index = -1;
      m_sum_ask_volume = 0;
      m_sum_bid_volume = 0;
      m_calculation = false;
   }
   int GetMaxVolAskIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_index;
   }
   
   long GetMaxVolAsk()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_volume;
   }
   int GetMaxVolBidIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_index;
   }
   
   long GetMaxVolBid()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_volume;
   }
   long GetAskVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_ask_volume;
   }
   long GetBidVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_bid_volume;
   }
};

Die gesamte Iteration der Markttiefe und alle ressourcenintensiven Berechnungen befinden sich innerhalb der privaten Methode Calculate. Diese wird nur aufgerufen, wenn das Berechnungs-Flag m_calculate in den Zustand false zurückgesetzt wird. Die Rücksetzung dieses Flags erfolgt ausschließlich in der Methode Reset. Da diese Klasse ausschließlich innerhalb der Klasse CMarketBook arbeiten soll, hat nur diese Klasse Zugriff darauf.

Nach der Aktualisierung der Markttiefe setzt die Refresh-Methode der Klasse CMarketBook den Zustand des Berechnungsmoduls durch einen Aufruf seiner Reset-Methode zurück. Damit findet die vollständige Iteration der Markttiefe nicht mehr als einmal zwischen ihren zwei Aktualisierungen statt. Auch wird eine verzögerte Ausführung genutzt. In anderen Worten: Die Calculate-Methode der Klasse CBookCalculate wird nur dann aufgerufen, wenn es einen klaren Aufruf aus einer von sechs öffentlich verfügbaren Methoden gibt.

Zusätzlich zum Finden des Volumens wurde die Klasse, die eine vollständige Iteration der Markttiefe durchführt, um Felder erweitert, die die Gesamtmenge der Limit-Order zum Kaufen und Verkaufen enthalten. Es ist keine zusätzliche Zeit zum Berechnen dieser Parameter erforderlich, da der gesamte Zyklus des Arrays berechnet wird.

Jetzt findet anstatt einer ständigen Iteration über die Markttiefe eine intelligente Iteration auf Anfrage statt. Dadurch wird der Ressourcenverbrauch deutlich gesenkt, was die Arbeit mit der Markttiefe äußerst effizient und schnell macht.

 

3,6 Feinschliff: Histogramm der Volumina und eine Trennlinie

Wir haben unsere Aufgabe, einen Indikator zu erstellen, fast abgeschlossen. Die praktische Notwendigkeit, das maximale Volumen zu finden, hat uns geholfen, eine effektive Methode der wirtschaftlichen Berechnung der erforderlichen Indikatoren zu erschaffen. Wenn wir in der Zukunft neue Berechnungsparameter zu unserer Markttiefe hinzufügen möchten, wird das leicht zu bewerkstelligen sein. Zu diesem Zweck genügt es, unsere Klasse CBookCalculate um die relevanten Methoden zu erweitern und die entsprechenden Modifikatoren zu den Aufzählungen ENUM_MBOOK_INFO_INTEGER und ENUM_MBOOK_INFO_DOUBLE hinzuzufügen.

Nun müssen wir die bisherige Arbeit praktisch einsetzen und die Refresh-Methode für jede Zelle neu umschreiben:

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   long max_volume = 0;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_BID_VOLUME);
   }
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_ASK_VOLUME); //The volume has been previously calculated, reoccurring iteration doesn't occur
   }
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
   if(m_ceil_type != BOOK_VOLUME)return;
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

Optisch arbeitet unser Panel-Indikator genauso wie die vorherige Version, doch in Wirklichkeit ist die Berechnungsgeschwindigkeit des Histogramms deutlich gestiegen. Genau darum geht es bei der Kunst der Programmierung: die Schaffung wirksamer und benutzerfreundlicher Algorithmen, indem man die Komplexität ihrer Umsetzung in privaten Methoden der entsprechenden Module (Klassen) verbirgt.

Mit dem Inerscheinungtreten des Volumenhistogramms wurde es ziemlich unklar, wo die Trennlinie zwischen den Volumina der Bid- und Ask-Preise verläuft. Deshalb erstellen wir eine solche Linie, indem wir die Klasse CBookPanel um die spezielle Unterklasse CBookLine erweitern, in der diese Funktion implementiert wird:

class CBookLine : public CNode
{
private:
   long m_ydist;
public:
   CBookLine(long y){m_ydist = y;}
   virtual void Show()
   {
      ObjectCreate(ChartID(),     m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 13);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YSIZE, 3);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, 108);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   }
};

Dies ist eine äußerst einfache Klasse, die im Grunde genommen nur ihre eigene Position bestimmt. Die Position der Linie entlang der Y-Achse muss zum Zeitpunkt ihrer Erstellung in der Methode Show berechnet werden. Kennt man den Index des besten Ask-Preises, ist dies relativ leicht zu bewerkstelligen:

long best_bid = m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
long y = best_bid*15+19;

In diesem Fall wird der Index der Zelle best_bid mit der Breite jeder Zelle (15 Pixel) multipliziert und anschließend eine zusätzliche Konstante von 19 Pixeln addiert.

Unsere Markttiefe hat endlich das nötige Mindestmaß an Aussehen und Funktionalität erreicht, um eine angenehme Nutzungserfahrung zu gewährleisten. Sicherlich ließe sich noch vieles tun. Falls gewünscht, könnte die Funktionalität unseres Indikators viel näher an die Standard-Markttiefe in MetaTrader 5 angeglichen werden.

Doch das ist nicht das Hauptziel dieses Beitrags. Das Panel der Markttiefe wurde mit dem einzigen Ziel erstellt, die Möglichkeiten der Klasse CMarketBook vorzuführen. Sie hat geholfen, diese Klasse schneller, besser und funktioneller zu machen und hat ihr Ziel somit vollständig erreicht. Wir zeigen Ihnen ein kurzes Video, das die gesamte bisher getane Arbeit zeigt. Nachfolgend sehen Sie unser DOM-Panel in der Dynamik:


3,7 Hinzufügen der Eigenschaften der DOM mit Informationen über die Gesamtmenge der Limit-Order für das Handelsinstrument

Ein Alleinstellungsmerkmal der Moskauer Börse ist die Übertragung von Informationen über die Gesamtmenge von Limit-Ordern in Echtzeit. Dieser Beitrag beleuchtet die Arbeit der Markttiefe als solche, ohne sich auf einen konkreten Markt zu beziehen. Allerdings sind diese Daten, auch wenn sie spezifisch sind (zu einer bestimmten Handelsplattform gehören), auf der Systemebene des Terminals verfügbar. Außerdem werden durch sie die von der Markttiefe bereitgestellten Daten erweitert. Deshalb wurde beschlossen, die Aufzählung der Eigenschaftsmodifikatoren zu erweitern und die Unterstützung dieser Eigenschaften direkt in der CMarketBook-Klasse der Markttiefe zu ermöglichen.

Die Moskauer Börse stellt die folgenden Echtzeitinformationen bereit:

  • Menge der Limit-Order zum Verkauf nach Instrument zum aktuellen Zeitpunkt;
  • Menge der Limit-Order zum Kauf nach Instrument zum aktuellen Zeitpunkt;
  • Gesamtvolumen der Limit-Order zum Verkauf nach Instrument zum aktuellen Zeitpunkt;
  • Gesamtvolumen der Limit-Order zum Kauf nach Instrument zum aktuellen Zeitpunkt;
  • Anzahl offener Positionen und Open Interest (nur für Terminmärkte).

Obwohl Open Interest nicht direkt mit der Anzahl der Limit-Order auf dem Markt in Verbindung steht (d. h. mit seiner aktuellen Liquidität), wird diese Information dennoch häufig zusammen mit der Information über die Limit-Order benötigt. Deshalb scheint der Zugriff darauf über die Klasse CMarketBook angemessen zu sein. Um auf diese Information zugreifen zu können, müssen Sie die Funktionen SymbolInfoInteger und SymbolInfoDouble nutzen. Damit der Zugriff auf Daten von einem einzigen Ort möglich ist, erweitern wir allerdings unsere Klasse der Markttiefe, indem wir zusätzliche Aufzählungen in den Funktionen InfoGetInteger und InfoGetDouble einführen:

long CMarketBook::InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_BUY_ORDERS);
      case MBOOK_SELL_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_SELL_ORDERS);
      ...
   }
   return 0;
}

 

double CMarketBook::InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_BUY_ORDERS_VOLUME);
      case MBOOK_SELL_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_SELL_ORDERS_VOLUME);
      case MBOOK_OPEN_INTEREST:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_INTEREST);
   }
   return 0.0;  
}

Wie Sie sehen können, ist der Code ziemlich simpel. Im Wesentlichen dupliziert er die Standardfunktionalität von MQL. Doch der Sinn hinter seiner Einfügung in die Klasse CMarketBook ist es, Benutzern ein bequemes und zentralisiertes Modul für den Zugriff auf Informationen über Limit-Order und ihre Preisebenen zur Verfügung zu stellen.


KAPITEL 4 Dokumentation der Klasse CMarketBook

Wir haben die Beschreibung und Erstellung der Klasse CMarketBook für die Arbeit mit der Markttiefe abgeschlossen. Das vierte Kapitel liefert eine Dokumentation für ihre öffentlichen Methoden. Mithilfe dieser Dokumentation wird die Nutzung der Klasse einfach und geradlinig, auch für Neulinge in der Programmierung. Außerdem dient dieses Kapitel als kleiner Leitfaden für die Arbeit mit dieser Klasse.


4,1 Methoden zum Erhalten grundlegender Operationen aus der Markttiefe und die Arbeit damit

Die Methode Refresh()

Aktualisiert den Zustand der Markttiefe. Bei jedem Aufruf des Systemereignisses OnBookEvent (Veränderung der Markttiefe) muss auch diese Methode aufgerufen werden.

void        Refresh(void);

Nutzung

Das Nutzungsbeispiel finden Sie im entsprechenden Abschnitt des vierten Kapitels.

 

Die Methode InfoGetInteger()

Gibt eine der Eigenschaften der Markttiefe gemäß dem Modifikator ENUM_MBOOK_INFO_INTEGER aus. Eine vollständige Liste der unterstützten Eigenschaften finden Sie in der Beschreibung zur Aufzählung ENUM_MBOOK_INFO_INTEGER.

long        InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property);

Gelieferter Wert

Ganzzahlige Eigenschaft der Markttiefe des Typen long. Bei einem Fehlschlag wird -1 ausgegeben.

Nutzung

Das Nutzungsbeispiel finden Sie im entsprechenden Abschnitt des vierten Kapitels. 

 

Die Methode InfoGetDouble()

Gibt eine der Eigenschaften der Markttiefe gemäß dem Modifikator ENUM_MBOOK_INFO_DOUBLE aus. Eine vollständige Liste der unterstützten Eigenschaften finden Sie in der Beschreibung zur Aufzählung ENUM_MBOOK_INFO_DOUBLE.

double      InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property);

Gelieferter Wert

Eigenschaft der Markttiefe des Typen double. Bei einem Fehlschlag wird -1.0 ausgegeben.

Nutzung

Das Nutzungsbeispiel finden Sie im entsprechenden Abschnitt des vierten Kapitels.  

 

Die Methode IsAvailable()

Gibt true aus, wenn die Informationen über die Markttiefe verfügbar sind, ansonsten false. Diese Methode muss vor der Arbeit mit der Klasse der Markttiefe aufgerufen werden, um die Möglichkeit der Arbeit mit dieser Art von Informationen zu prüfen.

bool        IsAvailable(void);

Gelieferter Wert

True, wenn die Markttiefe für die weitere Arbeit verfügbar ist, ansonsten false.

 

Die Methode SetMarketBookSymbol()

Legt das Symbol fest, dessen Markttiefe für die Arbeit erforderlich ist. Es ist auch möglich, während der Erstellung einer Instanz der Klasse CMarketBook ein Symbol der Markttiefe festzulegen, indem man den Namen des im Konstruktor verwendeten Symbols eindeutig angibt.

bool        SetMarketBookSymbol(string symbol);

Gelieferter Wert

True, wenn 'symbol' für den Handel verfügbar ist, ansonsten false.

 

Die Methode GetMarketBookSymbol()

Gibt das Symbol eines Instruments aus, für dessen Arbeit mit der Markttiefe die aktuelle Instanz der Klasse festgelegt ist. 

string      GetMarketBookSymbol(void);

Gelieferter Wert

Name des Instruments, dessen Markttiefe die aktuelle Instanz der Klasse anzeigt. NULL, falls ein Instrument nicht ausgewählt wurde oder nicht verfügbar ist. 

 

Die Methode GetDeviationByVol()

Gibt den Wert der potenziellen Slippage beim Markteintritt mit einer Marktorder aus. Dieser Wert hat einen Schätzungscharakter und die erhaltene Slippage kann sich von dem, was durch diese Funktion berechnet wurde, unterscheiden, wenn der Zustand der Markttiefe sich am Eintrittspunkt geändert hat. Allerdings liefert diese Funktion eine ausreichend genaue Einschätzung der Slippage, die zum Zeitpunkt des Markteintritts herrschen wird, und kann als zusätzliche Informationsquelle genutzt werden.

Die Methode nimmt zwei Parameter an: das Volumen des vorgeschlagenen Abschlusses und die Iteration, die auf die Art der bei der Tätigung des Abschlusses verwendeten Liquidität hinweist. Beispielsweise wird die Liquidität von Limit-Ordern zum Verkauf zum Kaufen genutzt. In diesem Fall muss der Typ von MBOOK_ASK als side angegeben werden. Umgekehrt muss zum Verkaufen MBOOK_BID angegeben werden. Weitere Informationen zur DOM-Seite finden Sie in der Beschreibung der Aufzählung ENUM_BOOK_SIDE.

double     GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side);

Parameter:

  • [in] vol – Volumen des vorgeschlagenen Abschlusses;
  • [in] side – Seite der Markttiefe, die zum Tätigen eines Abschlusses genutzt wird.  

Gelieferter Wert

Höhe der potenziellen Slippage in Punkten des Instruments.

 

4,2 Aufzählungen und Modifikatoren der Klasse CMarketBook

Die Aufzählung ENUM_MBOOK_SIDE

Die Aufzählung ENUM_BOOK_SIDE enthält Modifikatoren, die auf die Art der Liquidität hindeuten. Felder der Aufzählung und deren Beschreibung sehen Sie in der nachfolgenden Tabelle:

FeldBeschreibung
MBOOK_ASK Deutet auf die durch Limit-Order zum Verkauf bereitgestellte Liquidität hin.
MBOOK_BID Deutet auf die durch Limit-Order zum Kauf bereitgestellte Liquidität hin.

Hinweise 

Jede Marktorder kann durch Limit-Order ausgeführt werden. Je nach Richtung der Order werden Limit-Order zum Kaufen oder Verkaufen genutzt. Die Rückseite eines Kauf-Abschlusses werden eine oder mehrere Limit-Order zum Verkauf sein. Die Rückseite eines Verkauf-Abschlusses werden eine oder mehrere Limit-Order zum Kauf sein. Auf diese Weise zeigt der Modifikator auf einen von zwei Teilen der Markttiefe: die Kauf- oder Verkaufsseite. Der Modifikator wird von der Funktion GetDeviationByVol verwendet, für deren Arbeit Sie wissen müssen, welche Seite der Liquidität von dem zu erwartenden Marktabschluss verwendet wird.

 

Die Aufzählung ENUM_MBOOK_INFO_INTEGER

Die Aufzählung ENUM_MBOOK_INFO_INTEGER beinhaltet Eigenschaftsmodifikatoren, die mithilfe der Methode InfoGetInteger abgerufen werden müssen. Felder der Aufzählung und deren Beschreibung sehen Sie in der nachfolgenden Tabelle:

FeldBeschreibung
MBOOK_BEST_ASK_INDEX Index des besten Ask-Preises
MBOOK_BEST_BID_INDEX Index des besten Bid-Preises
MBOOK_LAST_ASK_INDEX Index des schlechtesten oder letzten Ask-Preises
MBOOK_LAST_BID_INDEX Index des schlechtesten oder letzten Bid-Preises
MBOOK_DEPTH_ASK Markttiefe von der Ask-Seite oder die Gesamtmenge ihrer Handelsebenen
MBOOK_DEPTH_BID Markttiefe von der Bid-Seite oder die Gesamtmenge ihrer Handelsebenen
MBOOK_DEPTH_TOTAL Gesamt-Markttiefe oder die Menge der Kauf- und Verkaufshandelsebenen
MBOOK_MAX_ASK_VOLUME Maximales Ask-Volumen
MBOOK_MAX_ASK_VOLUME_INDEX Index der Ebene des maximalen Ask-Volumens
MBOOK_MAX_BID_VOLUME Maximales Bid-Volumen
MBOOK_MAX_BID_VOLUME_INDEX Index der Ebene der maximalen Bid-Ebene
MBOOK_ASK_VOLUME_TOTAL Gesamtvolumen der Limit-Order zum Verkauf in der aktuellen Markttiefe
MBOOK_BID_VOLUME_TOTAL  Gesamtvolumen der Limit-Order zum Kauf in der aktuellen Markttiefe
MBOOK_BUY_ORDERS Gesamtvolumen der Limit-Order zum Kauf, das aktuell auf der Börse verfügbar ist
MBOOK_SELL_ORDERS Gesamtvolumen der Limit-Order zum Verkauf, das aktuell auf der Börse verfügbar ist

 

Die Aufzählung ENUM_MBOOK_INFO_DOUBLE

Die Aufzählung ENUM_MBOOK_INFO_DOUBLE beinhaltet Eigenschaftsmodifikatoren, die mithilfe der Methode InfoGetDouble abgerufen werden müssen. Felder der Aufzählung und deren Beschreibung sehen Sie in der nachfolgenden Tabelle:

FeldBeschreibung
MBOOK_BEST_ASK_PRICE Bester Ask-Preis
MBOOK_BEST_BID_PRICE Bester Bid-Preis
MBOOK_LAST_ASK_PRICE Schlechtester oder letzter Ask-Preis
MBOOK_LAST_BID_PRICE Schlechtester oder letzter Bid-Preis
MBOOK_AVERAGE_SPREAD Durchschnittliche Differenz zwischen dem besten Bid- und Ask-Preis oder ein Spread
MBOOK_OPEN_INTEREST  Open Interest
MBOOK_BUY_ORDERS_VOLUME Menge der Kauforder
MBOOK_SELL_ORDERS_VOLUME  Menge der Verkaufsorder

 

4,3 Anwendungsbeispiel der Klasse CMarketBook

Dieses Beispiel beinhaltet einen Quellcode in Form eines Expert Advisors, der grundlegende Informationen über die Markttiefe zum Zeitpunkt des Beginns seiner Ausführung anzeigt:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.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 <Trade\MarketBook.mqh>     // Include CMarketBook class
CMarketBook Book(Symbol());         // Initialize class with current instrument

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                                                   // Update Depth of Market status.
//--- Get main integer-value statistics
   int total=(int)Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);            // Get total Depth of Market
   int total_ask = (int)Book.InfoGetInteger(MBOOK_DEPTH_ASK);        // Get the amount of Sell price levels
   int total_bid = (int)Book.InfoGetInteger(MBOOK_DEPTH_BID);        // Get the amount of Buy price levels
   int best_ask = (int)Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);    // Get index of best Ask price
   int best_bid = (int)Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);    // Get index of best Bid price

//--- Displaу basic statistics
   printf("TOTAL DEPTH OF MARKET: "+(string)total);
   printf("NUMBER OF PRICE LEVELS FOR SELL: "+(string)total_ask);
   printf("NUMBER OF PRICE LEVELS FOR BUY: "+(string)total_bid);
   printf("INDEX OF BEST ASK PRICE: "+(string)best_ask);
   printf(INDEX OF BEST BID: "+(string)best_bid);
   
//--- Get main statistics of double
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE); // Get best Ask price
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE); // Get best Bid price
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);       // Get worst Ask price
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);       // Get worst Bid price
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);    // Get the average spread during Depth of Market operation
   
//--- Display prices and spread
   printf("BEST ASK PRICE: " + DoubleToString(best_ask_price, Digits()));
   printf("BEST BID PRICE: " + DoubleToString(best_bid_price, Digits()));
   printf("WORST ASK PRICE: " + DoubleToString(last_ask, Digits()));
   printf("WORST BID PRICE: " + DoubleToString(last_bid, Digits()));
   printf("AVERAGE SPREAD: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

 

Fazit

Dieser Beitrag hat sich als ziemlich dynamisch erwiesen. Wir haben die Markttiefe von einem technischen Standpunkt analysiert und eine hochleistungsfähige Containerklasse für die Arbeit mit ihr vorgeschlagen. Als Beispiel haben wir einen Indikator der Markttiefe auf Basis dieser Containerklasse erstellt, der in kompakter Form im Preisdiagramm des Instruments dargestellt werden kann.

Unser Indikator der Markttiefe ist sehr einfach und weiterhin unvollständig. Allerdings wurde das Hauptziel erreicht: Wir haben sichergestellt, dass wir mit der von uns erstellten Klasse CMarketBook relativ schnell komplexe Expert Advisors und Indikatoren für die Analyse der aktuellen Liquidität nach Instrument erstellen können. Bei der Konzeptionierung der Klasse CMarketBook wurde sehr auf die Performance geachtet, da die Markttiefe eine äußerst dynamische Tabelle mit sich bringt, die sich jede Minute hunderte Male ändert.

Die in diesem Beitrag beschriebene Klasse kann zu einer soliden Basis für Ihren Scalper oder ein hochfrequentes System werden. Sie können sie gerne um Funktionen erweitern, die für Ihr System geeignet sind. Erstellen Sie dazu einfach Ihre eigene von CMarketBook abgeleitete Klasse für die Markttiefe und schreiben Sie die benötigten Erweiterungsmethoden. Wir hoffen allerdings, dass auch diese grundlegenden Eigenschaften, die von der Markttiefe bereitgestellt werden, Ihre Arbeit schon einfacher und zuverlässiger machen werden.

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

Beigefügte Dateien |
MQL5.zip (202.33 KB)
Tipps zum Kauf eines Produkts auf dem Market. Schritt für Schritt Tipps zum Kauf eines Produkts auf dem Market. Schritt für Schritt
Diese Schritt-für-Schritt-Anleitung liefert Tipps und Tricks zum besseren Verstehen und Suchen nach einem benötigten Produkt. Dieser Beitrag versucht, verschiedene Methoden zum Suchen nach einem geeigneten Produkt, zum Aussortieren unerwünschter Produkte und zum Bestimmen der Wirksamkeit und Bedeutung eines Produkts für Sie zu erschaffen. Einleitung
Reguläre Ausdrücke für Trader Reguläre Ausdrücke für Trader
Reguläre Ausdrücke (eng. regular expressions) stellen eine spezielle Sprache für die Textverarbeitung nach einer vorbestimmten Regel dar, die auch als Muster bezeichnet wird oder die Maske eines regulären Ausdrucks. In diesem Artikel zeigen wir Ihnen, wie Sie den Handelsbericht mit Hilfe von der Bibliothek RegularExpressions für MQL5 verarbeiten können, auch werden die Ergebnisse der Optimierung mit ihrer Anwendung demonstriert.
Zeichnen von Widerstands- und Unterstützungsebenen mithilfe von MQL5 Zeichnen von Widerstands- und Unterstützungsebenen mithilfe von MQL5
Dieser Beitrag beschreibt eine Methode zum Finden von vier Extrema zum Zeichnen von darauf basierenden Unterstützungs- und Widerstandsebenen. Zum Finden der Extrema in einem Diagramm eines Währungspaars wird der Indikator RSI verwendet. Als Beispiel wird ein Indikatorcode bereitgestellt, der die Unterstützungs- und Widerstandsebenen anzeigt.
Die Erstellung des Bots für Telegram in der Sprache MQL5 Die Erstellung des Bots für Telegram in der Sprache MQL5
Dieser Artikel ist eine Anleitung, um Schritt um Schritt Bots für Telegramm in MQL5 zu erstellen. Dieses Material wird für diejenigen interessant sein, die ihre Traiding-Bots mit ihrem mobilen Gerät verknüpfen möchten. Der Artikel gibt Beispiele für Bots, die Trading-Signale senden, die Suche nach Informationen auf der Web-Seite durchführen, Informationen über den Status des Trading-Kontos senden, Notierungen und Screenshots der Charts auf Ihr Smartphone senden.