
Entwicklung eines Handelssystems auf der Grundlage des Orderbuchs (Teil I): Der Indikator
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.
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!
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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.