English Русский Español 日本語 Português
preview
Entwicklung eines Handelssystems auf der Grundlage des Orderbuchs (Teil I): Der Indikator

Entwicklung eines Handelssystems auf der Grundlage des Orderbuchs (Teil I): Der Indikator

MetaTrader 5Beispiele | 14 Mai 2025, 07:39
97 0
Daniel Santos
Daniel Santos

Einführung

Lassen Sie uns kurz zusammenfassen, was Markttiefe, Depth of Market oder DOM, bedeutet. Es handelt sich um eine Reihe von schwebenden Limitaufträgen. Diese Aufträge stellen die Handelsabsichten der Marktteilnehmer dar und führen häufig nicht zu einem tatsächlichen Handelsgeschäft. Der Grund dafür ist, dass die Händler die Möglichkeit haben, ihre zuvor erteilten Aufträge aus verschiedenen Gründen zu stornieren. Dazu können Änderungen der Marktbedingungen und der daraus resultierende Verlust des Interesses an der Ausführung des Auftrags zu dem zuvor festgelegten Preis und der Menge gehören.

Der von der Funktion SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH) zurückgegebene Wert entspricht genau der Tiefe des Orderbuchs und stellt die Hälfte des Arrays dar, das mit den zu analysierenden Kursniveaus aufgefüllt wird. Die Hälfte dieses Feldes ist für die Anzahl der Limit-Verkaufsaufträge vorgesehen, die andere Hälfte für die erteilten Limit-Kaufaufträge. Laut Dokumentation ist der Wert dieser Eigenschaft bei Anlagen, die keine Auftragswarteschlange haben, gleich Null. Ein Beispiel hierfür ist in der nachstehenden Abbildung zu sehen, die das Orderbuch mit einer Tiefe von 10 zeigt, wobei alle verfügbaren Kursniveaus angegeben sind.

Beispiel: Orderbuch mit Tiefe 10

Es ist zu beachten, dass die Tiefe aus dem Symbol und nicht unbedingt aus der Markttiefe abgeleitet werden kann. Die Verwendung der Funktion SymbolInfoInteger reicht aus, um den Eigenschaftswert abzurufen, ohne auf die Ereignisbehandlung durch OnBookEvent oder verwandte Funktionen wie MarketBookAdd zurückgreifen zu müssen. Natürlich könnten wir zum gleichen Ergebnis kommen, indem wir die Anzahl der Elemente im Array MqlBookInfo zählen, die OnBookEvent auffüllt, wie wir später noch genauer untersuchen werden.

Sie fragen sich vielleicht, warum wir diesen Indikator verwenden sollten, anstatt uns einfach auf das Standard-Orderbuch von MetaTrader 5 zu verlassen. Hier sind einige wichtige Gründe:

  • Eine optimierte Ausnutzung des Charts, wodurch die Größe des Histogramms und seine Position auf dem Bildschirm angepasst werden können.
  • Die übersichtlichere Darstellung der Ereignisse im Orderbuch, um die Klarheit zu erhöhen.
  • Die Nutzerfreundlichkeit im Strategietester, mit einer zukünftigen Implementierung eines speicherplatzbasierten Mechanismus für BookEvent-Ereignisse, in Anbetracht der Tatsache, dass natives Testen derzeit nicht unterstützt wird.


Erzeugen eines nutzerdefinierten Symbols

Auf diese Weise kann der Indikator auch dann getestet werden, wenn der Markt geschlossen ist oder wenn der Broker keine Ereignisse für das betreffende Symbol übermittelt. In solchen Fällen gibt es weder eine Live-Warteschlange für Aufträge noch werden diese Ereignisse auf dem lokalen Computer zwischengespeichert. In diesem Stadium werden wir nicht mit vergangenen Ereignissen eines echten Symbols arbeiten, sondern uns auf die Generierung simulierter Daten des BookEvent für fiktive Vermögenswerte konzentrieren. Dies ist notwendig, weil die Erstellung eines solchen Assets und die Simulation von Ereignissen für die Arbeit mit der Funktion CustomBookAdd unerlässlich ist. Diese Funktion ist speziell für nutzerdefinierte Symbole gedacht.

Nachfolgend finden Sie das Skript CloneSymbolTicksAndRates, mit dem das nutzerdefinierte Symbol erzeugt wird. Es wurde aus der Dokumentation an unsere Bedürfnisse angepasst und beginnt mit der Definition einiger Konstanten und der Einbeziehung der Standardbibliothek DateTime.mqh für die Arbeit mit Datumsangaben. Beachten Sie, dass der Name des nutzerdefinierten Symbols von der Nomenklatur des echten Symbols abgeleitet wird, die dem Skript mit der Funktion Symbol() übergeben wird. Daher muss dieses Skript auf dem zu klonenden echten Handelsinstruments ausgeführt werden. Obwohl es auch möglich ist, nutzerdefinierte Symbole zu klonen, scheint dies nicht besonders nützlich zu sein.

#define   CUSTOM_SYMBOL_NAME     Symbol()+".C"     
#define   CUSTOM_SYMBOL_PATH     "Forex"           
#define   CUSTOM_SYMBOL_ORIGIN   Symbol()          

#define   DATATICKS_TO_COPY      UINT_MAX 
#define   DAYS_TO_COPY           5
#include <Tools\DateTime.mqh>

Das folgende Fragment, das in die Funktion OnStart() desselben Skripts eingefügt wird, erstellt das Datumsobjekt „timemaster“. Sie wird verwendet, um den Zeitraum zu berechnen, in dem Ticks und Balken für das Klonen gesammelt werden sollen. Entsprechend der von uns definierten Konstante DAYS_TO_COPY kopiert die Funktion Bars die letzten fünf Tage des Quellsymbols. Dieselbe Anfangszeit des Bereichs wird dann in Millisekunden umgewandelt und von der Funktion CopyTicks verwendet, wodurch das „Klonen“ des Symbols abgeschlossen wird.

   CDateTime timemaster;
   datetime now = TimeTradeServer();
   timemaster.Date(now);
   timemaster.DayDec(DAYS_TO_COPY);
   long DaysAgoMsc = 1000 * timemaster.DateTime();
   int bars_origin = Bars(CUSTOM_SYMBOL_ORIGIN, PERIOD_M1, timemaster.DateTime(), now);
   int create = CreateCustomSymbol(CUSTOM_SYMBOL_NAME, CUSTOM_SYMBOL_PATH, CUSTOM_SYMBOL_ORIGIN);
   if(create != 0 && create != 5304)
      return;
   MqlTick array[] = {};
   MqlRates rates[] = {};
   int attempts = 0;
   while(attempts < 3)
     {
      int received = CopyTicks(CUSTOM_SYMBOL_ORIGIN, array, COPY_TICKS_ALL, DaysAgoMsc, DATATICKS_TO_COPY);
      if(received != -1)
        {
         if(GetLastError() == 0)
            break;
        }
      attempts++;
      Sleep(1000);
     }

Sobald der Vorgang abgeschlossen ist, sollte das neue Symbol in der Marktbeobachtungsliste mit dem Namen <AtivodeOrigem>.C erscheinen. An diesem Punkt müssen wir ein neues Chart mit diesem synthetischen Symbol öffnen und mit dem nächsten Schritt fortfahren.

Wenn bereits ein anderes synthetisches Symbol vorhanden ist, kann es wiederverwendet werden, sodass es nicht notwendig ist, ein neues Symbol zu erstellen, wie in diesem Abschnitt erläutert. Schließlich müssen wir nur noch ein neues Chart mit diesem nutzerdefinierten Symbol öffnen und zwei andere MQL5-Anwendungen ausführen, die wir hier entwickeln werden: den Indikator und das Skript für den Ereignisgenerator. In den folgenden Abschnitten werden wir alle Einzelheiten erläutern.


BookEvent-Type Event Generator Script zum Testen

Ein nutzerdefiniertes Symbol allein kompensiert nicht das Fehlen einer Tick-Sequenz des Online-Orderbuchs, wenn ein Backtest mit dem Indikator durchgeführt wird, der auf Orderbuchereignissen beruht. Deshalb müssen wir simulierte Daten erzeugen. Zu diesem Zweck wurde das folgende Skript entwickelt.

//+------------------------------------------------------------------+
//|                                            GenerateBookEvent.mq5 |
//|                                               Daniel Santos      |
//+------------------------------------------------------------------+
#property copyright "Daniel Santos"
#property version   "1.00"
#define SYNTH_SYMBOL_MARKET_DEPTH      32
#define SYNTH_SYMBOL_BOOK_ITERATIONS   20
#include <Random.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double BidValue, tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
MqlBookInfo    books[];
int marketDepth = SYNTH_SYMBOL_MARKET_DEPTH;
CRandom rdn;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   if(!SymbolInfoInteger(_Symbol, SYMBOL_CUSTOM)) // if the symbol exists
     {
      Print("Custom symbol ", _Symbol, " does not exist");
      return;
     }
   else
      BookGenerationLoop();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void BookGenerationLoop()
  {
   MqlRates BarRates_D1[];
   CopyRates(_Symbol, PERIOD_D1, 0, 1, BarRates_D1);
   if(ArraySize(BarRates_D1) == 0)
      return;
   BidValue = BarRates_D1[0].close;
   ArrayResize(books, 2 * marketDepth);
   for(int j = 0; j < SYNTH_SYMBOL_BOOK_ITERATIONS; j++)
     {
      for(int i = 0, j = 0; i < marketDepth; i++)
        {
         books[i].type = BOOK_TYPE_SELL;
         books[i].price = BidValue + ((marketDepth - i) * tickSize);
         books[i].volume_real = rdn.RandomInteger(10, 500);
         books[i].volume_real = round((books[i].volume_real + books[j].volume_real) / 2);
         books[i].volume = (int)books[i].volume_real;
         //----
         books[marketDepth + i].type = BOOK_TYPE_BUY;
         books[marketDepth + i].price = BidValue - (i * tickSize);
         books[marketDepth + i].volume_real = rdn.RandomInteger(10, 500);
         books[marketDepth + i].volume_real = round((books[marketDepth + i].volume_real
                                              + books[marketDepth + j].volume_real) / 2);
         books[marketDepth + i].volume = (int)books[marketDepth + i].volume_real;
         if(j != i)
            j++;
        }
      CustomBookAdd(_Symbol, books);
      Sleep(rdn.RandomInteger(400, 1000));
     }
  }
//+------------------------------------------------------------------+

Anstelle der Standardfunktion MathRand() haben wir eine alternative Implementierung zur Erzeugung von 32-Bit-Zufallszahlen verwendet. Diese Entscheidung wurde aus mehreren Gründen getroffen, u. a. weil es so einfach ist, ganzzahlige Werte innerhalb eines bestimmten Bereichs zu erzeugen - ein Vorteil, den wir in diesem Skript durch die Verwendung der Funktion RandomInteger(min, max) nutzen.

Für die Tiefe des Orderbuchs haben wir einen relativ großen Wert von 32 gewählt, was bedeutet, dass jede Iteration 64 Preisniveaus erzeugt. Falls erforderlich, kann dieser Wert auf einen kleineren Wert eingestellt werden.

Der Algorithmus prüft zunächst, ob das Symbol ein nutzerdefiniertes Symbol ist. Ist dies der Fall, wird jedes Element des Orderbuchs erzeugt und dieser Vorgang in einer weiteren Schleife mit der angegebenen Anzahl von Iterationen wiederholt. In dieser Implementierung werden 20 Iterationen mit zufällig gewählten Pausen zwischen 400 Millisekunden und 1000 Millisekunden (entspricht 1 Sekunde) durchgeführt. Dieser dynamische Ansatz macht die Visualisierung von Ticks realistischer und visuell ansprechender.

Die Preise sind vertikal mit dem letzten Schlusskurs des täglichen Zeitrahmens verankert, wie durch das Quellensymbol angegeben. Oberhalb dieses Referenzpunktes gibt es 32 Stufen von Verkaufsaufträgen, während unterhalb 32 Stufen von Kaufaufträgen liegen. Nach dem Standard-Farbschema des Indikators haben die Balken des Histogramms, die Verkaufsaufträgen entsprechen, einen rötlichen Farbton, während Kaufaufträge in Hellblau dargestellt werden.

Der Preisunterschied zwischen aufeinanderfolgenden Ebenen wird auf der Grundlage der Tickgröße des Symbols bestimmt, die über die Eigenschaft SYMBOL_TRADE_TICK_SIZE ermittelt wird.


Indikator für die Anzeige der Änderungen der Markttiefe

Quellcode der Bibliothek

Der Indikator wurde mit objektorientierter Programmierung entwickelt. Die Klasse BookEventHistogram wurde geschaffen, um das Orderbuch-Histogramm zu verwalten und seine Erstellung, Aktualisierung und das Entfernen von Balken bei der Zerstörung des Klassenobjekts zu verwalten.

Nachfolgend finden Sie die Variablen- und Funktionsdeklarationen für die Klasse BookEventHistogram:

class BookEventHistogram
  {
protected:
   color                histogramColors[]; //Extreme / Mid-high / Mid-low
   int                  bookSize;
   int                  currElements;
   int                  elementMaxPixelsWidth;
   bool                 showMessages;
   ENUM_ALIGN_MODE      corner;
   string               bookEventElementPrefix;
public:
   MqlBookInfo          lastBook[];
   datetime             lastDate;
   void                 SetAlignLeft(void);
   void                 SetCustomHistogramColors(color &colors[]);
   void                 SetBookSize(int value) {bookSize = value;}
   void                 SetElementMaxPixelsWidth(int m);
   int                  GetBookSize(void) {return bookSize;}
   void                 DrawBookElements(MqlBookInfo& book[], datetime now);
   void                 CleanBookElements(void);
   void                 CreateBookElements(MqlBookInfo& book[], datetime now);
   void                 CreateOrRefreshElement(int buttonHeigh, int buttonWidth, int i, color clr, int ydistance);
   //--- Default constructor
                     BookEventHistogram(void);
                    ~BookEventHistogram(void);
  };

In diesem Abschnitt werden nicht alle Funktionen definiert; sie werden jedoch in den übrigen Zeilen der Datei BookEventHistogram.mqh ergänzt.

Zu den wichtigsten Funktionen gehören CreateBookElements und CreateOrRefreshElement, die gemeinsam dafür sorgen, dass vorhandene Elemente aktualisiert und bei Bedarf neue Elemente erstellt werden. Die übrigen Funktionen dienen dazu, Eigenschaften auf dem neuesten Stand zu halten oder die Werte bestimmter Objektvariablen zurückzugeben.


Quellcode des Indikators:

Am Anfang des Codes wird die Anzahl der „Plots“ und Puffer mit 3 angegeben. Eine genauere Analyse wird zeigen, dass die Wurzelstrukturpuffer eines MQL5-Indikators in Wirklichkeit nicht verwendet werden. Diese Deklaration erleichtert jedoch die Erstellung von Code, der die Interaktion des Nutzers mit bestimmten Eigenschaften während der Initialisierung des Indikators gewährleistet. In diesem Fall liegt der Schwerpunkt auf den Farbeigenschaften, wobei das Eingabeschema so gestaltet ist, dass es eine intuitive und nutzerfreundliche Erfahrung bietet.

Jedem Plot sind zwei Farben zugeordnet - eine für Kauf- und eine für Verkaufsaufträge. Dieser Satz von sechs Farben wird verwendet, um die Farbe jedes Segments anhand von vordefinierten Kriterien zu bestimmen. Grob gesagt, werden die größten Segmente im Histogramm als „extreme“, die über dem Durchschnitt liegenden als „mid-high“ und die unter ihm als „mid-low“ eingestuft.

Die Farben werden mit der Funktion PlotIndexGetInteger abgerufen, die das Chart und die Position innerhalb des Charts angibt, aus dem die Informationen extrahiert werden sollen.

#define NUMBER_OF_PLOTS 3
#property indicator_chart_window
#property indicator_buffers NUMBER_OF_PLOTS
#property indicator_plots   NUMBER_OF_PLOTS
//--- Invisible plots
#property indicator_label1  "Extreme volume elements colors"
#property indicator_type1   DRAW_NONE
#property indicator_color1  C'212,135,114', C'155,208,226'
//---
#property indicator_label2  "Mid-high volume elements colors"
#property indicator_type2   DRAW_NONE
#property indicator_color2  C'217,111,86', C'124,195,216'
//---
#property indicator_label3  "Mid-low volume elements color"
#property indicator_type3   DRAW_NONE
#property indicator_color3  C'208,101,74', C'114,190,214'
#include "BookEventHistogram.mqh"
enum HistogramPosition
  {
   LEFT,      //<<<< Histogram on the left
   RIGHT,     //Histogram on the right >>>>
  };
enum HistogramProportion
  {
   A_QUARTER,   // A quarter of the chart
   A_THIRD,     // A third of the chart
   HALF,        // Half of the chart
  };
input  HistogramPosition position = RIGHT; // Indicator position
input  HistogramProportion proportion = A_QUARTER; // Histogram ratio (compared to chart width)
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
double volumes[];
color histogramColors[];
BookEventHistogram bookEvent;

Als Nächstes führen wir zwei Enumeratorenwerte ein, die dem Nutzer beim Laden des Indikators genaue Optionen bieten sollen. Wir wollen festlegen, wo das Histogramm gezeichnet werden soll: auf der rechten oder linken Seite des Charts. Außerdem muss der Nutzer den Anteil der Chartbreite angeben, den das Histogramm einnehmen soll: ein Viertel, ein Drittel oder die Hälfte des Charts. Wenn die Chartbreite beispielsweise 500 Pixel beträgt und der Nutzer die Option „half-width“ (halbe Breite) auswählt, können die Histogrammbalken eine Größe von 0 bis 250 Pixel haben.

Im Quellcode BookEvents.mq5 schließlich lösen die Funktionen OnBookEvent und OnChartEvent die meisten Histogramm-Aktualisierungsanforderungen aus. Die OnCalculate-Funktion spielt im Algorithmus keine Rolle und wird nur zur Einhaltung der MQL-Syntax beibehalten.


Verwendung der Skripte und Indikatoren

Die korrekte Reihenfolge für die Ausführung der Skripte und des Indikators, die die Konsistenz mit den bisher entwickelten Ressourcen sicherstellt, ist wie folgt:

  • Führen Sie das Skript CloneSymbolTicksAndRates auf dem Chart des realen Symbols aus, das geklont werden soll.
  • -> BookEvents-Indikator (auf dem Chart des erzeugten nutzerdefinierten Symbols)
  • -> Skript GenerateBookEvent (auf dem Chart des erzeugten nutzerdefinierten Symbols)

Das BookEvent wird an alle grafischen Instanzen des gewünschten nutzerdefinierten Assets gesendet. Daher können der Indikator und das Skript für den Ereignisgenerator auf separaten Charts oder innerhalb desselben Charts ausgeführt werden, solange sie auf dasselbe nutzerdefinierte Symbol verweisen.

Die nachstehende Animation veranschaulicht diesen Ablauf sowie die Funktionsweise des Indikators. Ich wünsche Ihnen viel Spaß damit!

Demo - BookEvents Indikator


Schlussfolgerung

„Depth of Market“ ist zweifellos ein sehr wichtiges Element für die Ausführung von schnellen Handelsgeschäften, insbesondere bei den Algorithmen des Hochfrequenzhandels (HFT). Dabei handelt es sich um eine Art von Marktereignis, das Broker für viele Handelssymbole anbieten. Im Laufe der Zeit können die Broker den Erfassungsbereich und die Verfügbarkeit solcher Daten für weitere Vermögenswerte erweitern.

Ich halte es jedoch nicht für ratsam, ein Handelssystem ausschließlich auf der Grundlage des Orderbuchs aufzubauen. Stattdessen kann der DOM helfen, Liquiditätszonen zu identifizieren, und er kann eine gewisse Korrelation mit Kursbewegungen aufweisen. Daher ist die Kombination der Orderbuchanalyse mit anderen Instrumenten und Indikatoren ein kluger Ansatz, um konsistente Handelsergebnisse zu erzielen.

Es gibt Raum für zukünftige Verbesserungen des Indikators, wie z.B. die Implementierung von Mechanismen zur Speicherung von BookEvent-Daten und deren spätere Verwendung im Backtesting, sowohl für den manuellen Handel als auch für automatisierte Strategien.


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/15748

Beigefügte Dateien |
Von der Grundstufe bis zur Mittelstufe: Die Anweisung FOR Von der Grundstufe bis zur Mittelstufe: Die Anweisung FOR
In diesem Artikel werden wir uns mit den grundlegenden Konzepten der FOR-Anweisung befassen. Es ist sehr wichtig, dass Sie alles verstehen, was hier gezeigt wird. Im Gegensatz zu den anderen Anweisungen, über die wir bisher gesprochen haben, hat die FOR-Anweisung einige Eigenheiten, die sie schnell sehr komplex machen. Lassen Sie also nicht zu, dass sich solche Dinge ansammeln. Beginnen Sie so bald wie möglich mit dem Lernen und Üben.
Von der Grundstufe bis zur Mittelstufe: Die Anweisung SWITCH Von der Grundstufe bis zur Mittelstufe: Die Anweisung SWITCH
In diesem Artikel werden wir lernen, wie man SWITCH in ihrer einfachsten und grundlegendsten Form verwendet. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Atmosphere Clouds Model Optimization (ACMO): Die Praxis Atmosphere Clouds Model Optimization (ACMO): Die Praxis
In diesem Artikel werden wir uns weiter mit der Implementierung des ACMO-Algorithmus (Atmospheric Cloud Model Optimization) beschäftigen. Wir werden insbesondere zwei Schlüsselaspekte erörtern: die Bewegung von Wolken in Tiefdruckgebiete und die Regensimulation, einschließlich der Initialisierung von Tröpfchen und ihrer Verteilung auf die Wolken. Wir werden uns auch mit anderen Methoden befassen, die eine wichtige Rolle bei der Verwaltung des Zustands von Wolken und der Gewährleistung ihrer Interaktion mit der Umwelt spielen.
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 18): Automatisierte Gruppenauswahl unter Berücksichtigung der Vorwärtszeitraum Entwicklung eines Expertenberaters für mehrere Währungen (Teil 18): Automatisierte Gruppenauswahl unter Berücksichtigung der Vorwärtszeitraum
Fahren wir fort, die Schritte zu automatisieren, die wir zuvor manuell durchgeführt haben. Diesmal kehren wir zur Automatisierung der zweiten Phase zurück, d. h. zur Auswahl der optimalen Gruppe von Einzelinstanzen von Handelsstrategien, und ergänzen sie durch die Möglichkeit, die Ergebnisse der Instanzen in dem Vorwärtszeitraum zu berücksichtigen.