Einführung in MQL5: Schreiben eines einfachen Expert Advisor und benutzerdefinierten Indikators

Denis Zyatkevich | 11 Januar, 2016

Einleitung

Die im MetaTrader 5 Client Terminal enthaltene MetaQuotes Programming Language 5 (MQL5) bietet im Vergleich zu MQL4 zahlreiche neue Möglichkeiten und bessere Performance. Dieser Beitrag hilft Ihnen dabei, sich mit dieser neuen Programmiersprache vertraut zu machen. In diesem Beitrag werden einfache Beispiele zum Schreiben eines Expert Advisors und eines benutzerdefinierten Indikators vorgestellt. Wir werden auch auf einige Details der MQL5-Sprache eingehen, die notwendig sind, um diese Beispiele zu verstehen.

Die Beitragsdetails und die vollständige Beschreibung von MQL5 finden Sie in der in MetaTrader 5 enthaltenen MQL5-Referenz. Die in der in MQL5 integrierten Hilfe enthaltenen Informationen sind ausreichend, um die Sprache zu erlernen. Dieser Beitrag kann nützlich für Personen sein, die mit MQL4 vertraut sind, aber auch für Neueinsteiger, die gerade erst anfangen, Handelssysteme und Indikatoren zu programmieren.


Einstieg in MQL5

Die Handelsplattform MetaTrader 5 ermöglicht es Ihnen, technische Analysen von Finanzinstrumenten durchzuführen und Handel zu treiben, sowohl manuell als auch im automatischen Modus. MetaTrader 5 unterscheidet sich von seinem Vorgänger MetaTrader 4. Insbesondere wurden die Konzepte Abschluss, Position und Auftrag verfeinert.

Die Programmiersprache MQL5 ist im Client Terminal integriert und ermöglicht es Ihnen, verschiedene Arten von Programmen mit unterschiedlichen Zwecken zu schreiben:

Expert Advisors, Indikatoren und Scripts können Funktionen der MQL5-Standardbibliothek und DLL-Funktionen aufrufen, einschließlich der Bibliotheken des Betriebssystems. Die Code-Teile, die sich in den anderen Dateien befinden, können in dem in MQL5 geschriebenen Text des Programms enthalten sein.

Um ein Programm (Expert Advisor, Indikator oder Script) zu schreiben, können Sie MetaTrader 5 Client Terminal starten und MetaQuotes Language Editor aus dem Tools-Menü auswählen oder einfach F4 drücken.

Abbildung 1. Starten von MetaEditor.

Wählen Sie im MetaEditor-5-Fenster die Option New (Neu) aus dem Menü File (Datei) oder drücken Sie Strg+N.

Abbildung 2. Erstellen eines neuen Programms.

Wählen Sie im MQL5 Wizard die Art des zu erstellenden Programms: 

Abbildung 3. MQL5 Wizard.

Im nächsten Schritt können Sie den Programmnamen, Informationen zum Autor und Parameter eingeben, nach denen der Benutzer nach dem Programmstart gefragt wird.

Abbildung 4. Allgemeine Eigenschaften von Expert Advisor.

Danach wird das Programm-Template (Expert Advisor, Indikator oder Script) erstellt. Dieses können Sie bearbeiten und mit Ihrem Code befüllen:

Abbildung 5. Template eines neuen Programms.

Wenn das Programm fertig ist, muss es kompiliert werden. Um das Programm zu kompilieren, wählen Sie Compile (Kompilieren) aus dem Menü File (Datei) oder drücken Sie F7:

Abbildung 6. Programmkompilierung.

Wenn der Programmcode fehlerfrei ist, wird die Datei mit der Erweiterung .ex5 erstellt. Danach können Sie diesen neuen Expert Advisor, Indikator oder Script zur Ausführung an das Diagramm des MetaTrader 5 Client Terminal anhängen.

Ein MQL5-Programm ist eine Sequenz von Operatoren. Jeder Operator endet mit einem Semikolon ";". Sie können Kommentare zu Ihrem Code hinzufügen. Diese stehen innerhalb der Zeichen "/*" und "*/" oder hinter "//" am Ende der Zeile. MQL5 ist eine "ereignisorientierte" Programmiersprache. Das bedeutet, dass das Client Terminal nach Eintreten bestimmter Ereignisse (Start oder Beendigung eines Programms, Eingehen eines neuen Angebots usw.) die entsprechende vom Anwender geschriebene Funktion (Unterprogramm) ausführt, die bestimmte Tätigkeiten ausführt. Das Client Terminal hat die folgenden vordefinierten Ereignisse:

Vor dem Gebrauch der Variablen muss der Datentyp jeder Variable angegeben werden. MQL5 unterstützt mehr Datentypen als MQL4:

Für optimale Performance und sinnvolle Speichernutzung muss der geeignete Datentyp ausgewählt werden. In MQL5 gibt es ein neues Konzept namens Struktur. Die Struktur kombiniert logisch zusammenhängende Daten.

Handelssystem

Das in diesem Artikel als Beispiel verwendete Handelssystem nimmt an, dass europäische Finanzinstitute am Morgen öffnen und die wirtschaftlichen Ereignisse später in den USA veröffentlicht werden. Dadurch entsteht der EURUSD-Trend. Der Zeitraum für das Diagramm ist unwichtig, aber ich empfehle die Verwendung der Minuten-Bars, da der ganze Tag (oder ein entsprechender Teil) auf einmal sichtbar ist, was für die Beobachtung sehr bequem ist.

Abbildung 7. Handelssystem.

Um 7 Uhr morgens (Serverzeit) werden Buy-Stop- und Sell-Stop-Aufträge mit einem Abstand von einem Punkt unter dem Preisbereich des aktuellen Tags platziert. Bei ausstehenden Buy-Stop-Aufträgen wird die Differenz berücksichtigt. Die StopLoss-Levels werden auf den sich gegenüberliegenden Seiten des Bereichs platziert. Nach der Ausführung wird der StopLoss-Auftrag zum einfachen gleitenden Mittelwert verschoben, allerdings nur, wenn dies profitabel ist.

Der Vorteil dieser Art von Trailing im Vergleich mit dem klassischen Trailing Stop ist folgender: Es ermöglicht die Vermeidung einer verfrühten Schließung der Position im Fall von Preisausschlägen mit Korrekturen. Andererseits führt es zur Schließung der Position, wenn der Trend endet und die flache Bewegung beginnt. Der einfache gleitende Mittelwert wird mithilfe der Minutendaten des Diagramms errechnet und hat eine Mittelungszeit von 240.

Das Gewinnniveau hängt von der aktuellen Marktvolatilität ab. Zur Bestimmung der Marktvolatilität wird der Average-True-Range-Indikator (ATR) (mit Anwendung eines Zeitraums von 5 auf das tägliche Diagramm) verwendet. Es wird also der durchschnittliche tägliche Bereich der vergangenen Woche angezeigt. Um das Take-Profit-Niveau der Long Position zu bestimmen, addieren wir den Wert des ATR-Indikators zum niedrigsten Preis des aktuellen Tages. Das Gleiche gilt für die Short Positions: Wir ziehen den Wert des ATR-Indikators vom höchsten Preis des aktuellen Tages ab. Der Auftrag wird nicht platziert, wenn der Preis des Auftrags über den StopLoss- und TakeProfit-Niveaus liegt. Nach 19 Uhr (Serverzeit) werden alle ausstehenden Aufträge gelöscht und an diesem Tag nicht platziert (die offenen Positionen werden bis zur Schließung verfolgt).


Schreiben eines Indikators

Schreiben wir einen Indikator, der die Gewinnniveaus des oben beschriebenen Handelssystems zeigt.

Ist das erste Zeichen einer Zeile "#", bedeutet dies, dass dieser String ein Präprozessor-Direktive ist. Direktiven werden benutzt, um zusätzliche Programmeigenschaften zu bestimmen, Konstanten zu deklarieren und Header-Dateien und importierte Funktionen einzufügen. Beachten Sie, dass auf Präprozessor-Direktiven keine Semikolons (;) folgen.

#property copyright   "2010, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property description "This indicator calculates TakeProfit levels"
#property description "using the average market volatility. It uses the values"
#property description "of Average True Range (ATR) indicator, calculated"
#property description "on daily price data. Indicator values are calculated"
#property description "using maximal and minimal price values per day."
#property version     "1.00"

Die Informationen zum Autor und seine Webseite können in den Copyright- und Link-Eigenschaften angegeben werden, die Eigenschaft description (Beschreibung) ermöglicht es Ihnen, eine Kurzbeschreibung einzufügen, die Eigenschaft version ermöglicht Ihnen die Angabe der Programmversion. Wenn der Indikator ausgeführt wird, sehen diese Informationen folgendermaßen aus:

Abbildung 8. Indikator-Informationen.

Die Position des Indikators muss bestimmt werden: in einem Diagramm oder in einem separaten Fenster. Dies kann durch Angabe einer der folgenden Eigenschaften bewerkstelligt werden: indicator_chart_window oder indicator_separate_window:

#property indicator_chart_window

Zusätzlich müssen Sie die Anzahl der zu verwendenden Indikator-Puffer und die Zahl der Grafikreihen angeben. In unserem Fall gibt es zwei Linien, jede davon hat ihren eigenen Puffer – ein Daten-Array, das grafisch dargestellt wird.

#property indicator_buffers 2
#property indicator_plots   2

Bestimmen wir für jede Indikator-Linie die folgenden Eigenschaften: Typ (indicator_type), Farbe (indicator_color), Diagrammstil (indicator_style) und Beschriftung (indicator_label):

#property indicator_type1   DRAW_LINE
#property indicator_color1  C'127,191,127'
#property indicator_style1  STYLE_SOLID
#property indicator_label1  "Buy TP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  C'191,127,127'
#property indicator_style2  STYLE_SOLID
#property indicator_label2  "Sell TP"

Die grundlegenden Linientypen sind: DRAW_LINE – für Linien, DRAW_SECTION – für Abschnitte, DRAW_HISTOGRAM für Histogramme. Es gibt viele weitere Diagrammstile. Sie können die Farbe durch Bestimmung der Helligkeit ihrer drei RGB-Komponenten oder mithilfe der vordefinierten Farben festlegen, beispielsweise Rot, Grün, Blau, Weiß usw. Die Linienstile sind: STYLE_SOLID – durchgehende Linie, STYLE_DASH – gestrichelte Linie, STYLE_DOT – gepunktete Linie, STYLE_DASHDOT – Striche und Punkte abwechselnd, STYLE_DASHDOTDOT – Strich-zwei Punkte.

Abbildung 9. Beschreibung von Indikator-Linien.

Lassen Sie uns mithilfe des input-Modifikators die externen Variablen (deren Werte Sie nach dem Start des Indikators festlegen können), deren Typ und Standardwerte bestimmen:

input int             ATRper       = 5;         //ATR Period
input ENUM_TIMEFRAMES ATRtimeframe = PERIOD_D1; //Indicator timeframe

Die Namen von Parametern können in Kommentaren bestimmt werden – sie erscheinen anstatt der Namen der Variablen:

Abbildung 10. Eingabeparameter eines Indikators.

Auf globaler Ebene (die für alle Funktionen sichtbar ist) legen wir Variablen (und ihre Typen) fest, die von verschiedenen Funktionen unseres Indikators genutzt werden.

double bu[],bd[];
int hATR;

Die Arrays bu[] und bd[] werden für die obere und untere Linie des Indikators verwendet. Wir verwenden dynamische Arrays (d. h. Arrays ohne festgelegte Anzahl von Elementen), weil wir die genaue Anzahl der Elemente, die verwendet werden, nicht kennen (ihre Größe wird automatisch zugewiesen). Das Handle des integrierten technischen Indikators wird in der hATR-Variable gespeichert. Das Handle des Indikators ist für die Nutzung des Indikators notwendig.

Die Funktion OnInit wird nach der Ausführung des Indikators (nach dem Anhängen an das Diagramm) aufgerufen.

void OnInit()
  {
   SetIndexBuffer(0,bu,INDICATOR_DATA);
   SetIndexBuffer(1,bd,INDICATOR_DATA);
   hATR=iATR(NULL,ATRtimeframe,ATRper);
  }

Die Funktion SetIndexBuffer ist notwendig, um festzulegen, dass die Arrays bu[] und bd[] die Puffer des Indikators sind, die genutzt werden, um die als Linien des Indikators dargestellten Indikatorwerte zu speichern. Der erste Parameter definiert den Index des Indikatorpuffers, die Nummerierung beginnt bei 0. Der zweite Parameter legt einen Bereich fest, der dem Indikatorpuffer zugewiesen wird. Der dritte Parameter legt den Typ der im Indikatorpuffer gespeicherten Daten fest: INDICATOR_DATA – Daten für die Darstellung, INDICATOR_COLOR_INDEX – Diagrammfarbe, INDICATOR_CALCULATIONS – Hilfspuffer für Zwischenberechnungen.

Das von der iATR-Funktion ausgegebene Handle des Indikators wird in der hATR-Variable gespeichert. Der erste Parameter der iATR-Funktion ist das Handelssymbol, NULL ist das Symbol des aktuellen Diagramms. Der zweite Parameter legt den Zeitraum für das Diagramm fest, der für die Berechnung des Indikators verwendet wird. Der dritte Parameter ist der Mittelungszeitraum des ATR-Indikators.

Die OnCalculate-Funktion wird gleich nach der Beendigung der Ausführung der OnInit-Funktion und nach jedem Eingang eines neuen Angebots für das aktuelle Symbol aufgerufen. Es gibt zwei Arten, diese Funktion aufzurufen. Diejenige, die in unserem Indikator genutzt wird, sieht wie folgt aus:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])

Nach dem Aufrufen der OnCalculate-Funktion durchläuft das Client Terminal die folgenden Parameter:  rates_total – Anzahl der Bars im aktuellen Diagramm, prev_calculated – Anzahl der bereits durch den Indikator berechneten Bars, time[], open[], high[], low[], close[], tick_volume[], volume[], spread[] – Arrays, die jeweils die Zeit-, Öffnungs-, Höchst-, Mindest-, Schließungs-, tick_volume-, Volumen-, und Differenzwerte für jedes Bar beinhalten. Um die Berechnungszeit zu reduzieren, ist es nicht nötig, Indikatorwerte neu zu berechnen, die bereits berechnet und nicht verändert wurden. Nach dem Aufrufen der OnCalculate-Funktion wird die Zahl der Bars ausgegeben, die bereits berechnet wurden.

Der Code der OnCalculate-Funktion steht in Klammern. Er beginnt mit lokalen Variablen, die in der Funktion verwendet werden – ihre Typen und Namen.

  {
   int i,day_n,day_t;
   double atr[],h_day,l_day;

Die Variable i wird als Zykluszähler verwendet, die Variablen day_n und day_t werden zum Speichern und temporären Speichern der Nummer des Tages bei der Berechnung der höchsten und niedrigsten Preiswerte während des Tages verwendet. Das Array atr[] wird zum Speichern des ATR-Indikators verwendet und die Variablen h_day und l_day werden zum Speichern der höchsten und niedrigsten Preise des Tages verwendet.

Zuerst müssen wir die Werte des ATR-Indikators mithilfe der CopyBuffer-Funktion in das Array atr[] kopieren. Wir nutzen den Identifikator des ATR-Indikators als ersten Parameter dieser Funktion. Der zweite Parameter ist die Anzahl der Puffer des Indikators (Zählung beginnt bei 0), der ATR-Indikator hat nur einen Puffer. Der dritte Parameter legt die Zahl des ersten Elements fest, ab dem die Zählung begonnen wird. Die Indizierung findet von der Gegenwart zur Vergangenheit statt, das Null-Element entspricht dem aktuellen (nicht abgeschlossenen) Bar. Der vierte Parameter legt die Anzahl der zu kopierenden Elemente fest.

Kopieren wir zwei Elemente, da wir nur am vorletzten Element interessiert sind, das dem letzten (abgeschlossenen) Bar entspricht. Der letzte Parameter ist das Ziel-Array zum Kopieren der Daten.

   CopyBuffer(hATR,0,0,2,atr);

Die Richtung der Array-Indizierung hängt vom AS_SERIES-Flag ab. Falls es aktiv ist (d. h. true), gilt das Array als Zeitreihe und die Indizierung der Elemente wird von den aktuellsten zu den ältesten Daten durchgeführt. Falls es nicht aktiv ist (d. h. false), haben ältere Elemente einen niedrigeren Index, neuere einen höheren.

   ArraySetAsSeries(atr,true);

Für das Array atr[] setzen wir das AS_SERIES-Flag mithilfe der ArraySetAsSeries-Funktion auf true (der erste Parameter dieser Funktion ist das Array, für das das Flag geändert werden soll, der zweite Parameter ist der neue Flag-Wert). Nun entspricht der Index des aktuellen (nicht abgeschlossenen) Bars 0, der Index des vorletzten (abgeschlossenen) Bars 1.

Mithilfe des Operators for kann eine Schleife eingerichtet werden.

   for(i=prev_calculated;i<rates_total;i++)
     {
      day_t=time[i]/PeriodSeconds(ATRtimeframe);
      if(day_n<day_t)
        {
         day_n=day_t;
         h_day=high[i];
         l_day=low[i];
        }
        else
        {
         if(high[i]>h_day) h_day=high[i];
         if(low[i]<l_day) l_day=low[i];
        }
      bu[i]=l_day+atr[1];
      bd[i]=h_day-atr[1];
     }

Nach dem Operator for ist der erste Operator in Klammern eine Anweisung: i=prev_calculated. Als Nächstes folgt ein Ausdruck, in unserem Fall lautet er: i<rates_total. Dies ist ein Schleifenzustand – die Schleife wiederholt sich, solange sie true ist. Als Drittes folgt die Anweisung, die nach der Ausführung der Schleife ausgeführt wird. In unserem Fall ist es i++ (entspricht i=i+1 und bedeutet eine Erhöhung der Variable i um 1).

In unserer Schleife unterscheidet sich die Variable i vom prev_calculated-Wert um den Wert, der mit Schritt 1 rates_total-1 entspricht. Die historischen Daten-Arrays (time[], high[] und low[]) sind nicht standardmäßig eine Zeitreihe. Der Null-Index entspricht dem ältesten Bar in der Historie, der letzte entspricht dem aktuellen nicht abgeschlossenen Bar. In einer Schleife werden alle Bars vom ersten nicht berechneten (prev_calculated) bis zum letzten Bar (rates_total-1) verarbeitet. Für jedes dieser Bars berechnen wir die Werte unseres Indikators.

Die Zeitwerte im time[]-Array werden als Zahl der Sekunden ab dem 01.01.1970 00:00:00 gespeichert. Wenn wir diesen Wert durch die Zahl der Sekunden im Tag (oder in einem anderen Zeitraum) teilen, ist der ganzzahlige Anteil die Nummer des Tages ab dem 01.01.1970 (oder ein anderer Zeitraum). Die PeriodSeconds-Funktion gibt die Anzahl der Sekunden im Zeitraum aus, der als Parameter definiert ist. Die Variable day_t ist die Zahl des Tages und entspricht dem Bar mit dem Index i. Die Variable day_n ist die Zahl des Tages, für den die höchsten und niedrigsten Preiswerte berechnet werden.

Denken wir über den Operator if nach. Wenn der Klammerausdruck dieses Operators true ist, wird der Operator nach dem Schlüsselwort if ausgeführt. Ist er false, wird der Operator nach dem Schlüsselwort else ausgeführt. Jeder Operator kann zusammengesetzt sein, d. h. aus mehreren Operatoren bestehen. In unserem Fall stehen sie in Klammern.

Die höchsten und niedrigsten Preiswerte eines verarbeiteten Tages werden in den Variablen h_day bzw. l_day gespeichert. In unserem Fall prüfen wir die folgende Bedingung: Wenn das analysierte Bar dem neuen Tag entspricht, fangen wir die Berechnung der höchsten und niedrigsten Preiswerte neu an, ansonsten fahren wir mit der Berechnung fort. Wir berechnen die Werte für jede Indikatorlinie: Für die obere Linie verwenden wir den niedrigsten Preis des Tages, für die untere Linie verwenden wir die höchsten Preiswerte.

Am Ende der OnCalculate-Funktion gibt der Operator return die Zahl der berechneten Bars aus.

   return(rates_total);
  }

Innerhalb der Deinit-Funktion (diese wird ausgeführt, wenn der Indikator vom Diagramm entfernt wird oder wenn das Client Terminal geschlossen wird) wird der vom ATR-Indikator reservierte Speicher mithilfe der IndicatorRelease-Funktion freigegeben. Diese Funktion hat nur einen Parameter: das Handle des Indikators.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hATR);
  }

Nun ist unser Indikator vollständig. Um dies zu kompilieren, wählen Sie in MetaEditor Compile (Kompilieren) aus dem Menü File (Datei) oder drücken Sie F7. Wenn der Code fehlerfrei ist, ist die Kompilierung erfolgreich. Kompilierungsergebnisse werden in der Registerkarte Errors (Fehler) im Fenster Toolbox angezeigt. In Ihrem Fall gibt der Compiler möglicherweise die Warnung "Conversion possible loss of data" (Konvertierung, Datenverlust möglich) für den folgenden String aus:

      day_t=time[i]/PeriodSeconds(ATRtimeframe);

In dieser Zeile verwerfen wir absichtlich den fraktionellen Teil, damit dieser Datenverlust kein Fehler ist.

Ist ein Indikator vollständig und kompiliert, kann er an Diagramme im MetaTrader 5 Client Terminal angehängt oder in anderen Indikatoren, Expert Advisors oder Scripts verwendet werden. Der Quellcode dieses Indikators steht in diesem Beitrag als Anlage zur Verfügung.


Schreiben eines Expert Advisors

Nun ist es Zeit, einen Expert Advisor zu schreiben, der das oben beschriebene Handelssystem umsetzt. Wir nehmen an, dass es nur mit einem Finanzinstrument handeln wird. Damit mehrere Expert Advisors mit einem Instrument handeln können, muss der Beitrag jedes Expert Advisors zur allgemeinen Position genau analysiert werden, was den Rahmen dieses Beitrags sprengen würde.

#property copyright   "2010, MetaQuotes Software Corp."
#property version     "1.00"
#property description "This Expert Advisor places the pending orders during the"
#property description "time from StartHour till EndHour on the price levels, that"
#property description "are 1 point below/lower the current trade range."
#property description "The StopLoss levels are placed at the opposite side"
#property description "of the price range. After order execution, the TakeProfit value"
#property description "is set at the 'indicator_TP' level. The StopLoss level is moved"
#property description "to the SMA values only for the profitable orders."

Der Zweck dieser Präprozessor-Direktiven wurde bereits im Abschnitt Schreiben eines Indikators besprochen. Sie funktionieren genauso für Expert Advisors.

Legen wir die Werte der Eingabeparameter (die nach dem Start eines Expert Advisors vom Anwender definiert werden können), sowie deren Typen und Standardwerte fest.

input int    StartHour = 7;
input int    EndHour   = 19;
input int    MAper     = 240;
input double Lots      = 0.1;

Die Parameter StartHour und EndHour definieren den Zeitraum (Anfangs- und Beendigungsstunden) für ausstehende Aufträge. Der Parameter MAper definiert den Mittelungszeitraum des einfachen gleitenden Mittelwerts, der für das StopLoss-Level der geöffneten Position während des Trailings verwendet wird. Der Parameter Lots definiert das Volumen des für den Handel genutzten Finanzinstruments.

Lassen Sie uns die globalen Variablen festlegen, die für die unterschiedlichen Handelsfunktionen verwendet werden:

int hMA,hCI;

Die Variable hMA wird zur Speicherung des Handles des MA-Indikators verwendet und die Variable hCI wird zur Speicherung des Handles des benutzerdefinierten Indikators verwendet (ein Indikator, der oben geschrieben wurde).

Die OnInit-Funktion wird ausgeführt, wenn der Expert Advisor gestartet wird.

void OnInit()
  {
   hMA=iMA(NULL,0,MAper,0,MODE_SMA,PRICE_CLOSE);
   hCI=iCustom(NULL,0,"indicator_TP");
  }

In dieser Funktion erhalten wir die Handles des MA-Indikators und unseres benutzerdefinierten Indikators. Die iMA-Funktion und ihre Parameter werden auf die gleiche Weise genutzt wie die oben beschriebene iATR-Funktion.

Der erste Parameter der iCustom-Funktion ist der symbolische Name des Instruments, NULL steht für das Instrument im aktuellen Diagramm. Der zweite Parameter ist der Zeitraum des Diagramms, dessen Daten zur Berechnung des Indikators verwendet werden. 0 steht für den Zeitraum des aktuellen Diagramms. Der dritte Parameter ist der Dateiname des Indikators (ohne Erweiterung). Der Dateipfad ist relativ zum Ordner MQL5\Indicators\.

Erstellen wir die OnTick-Funktion, die nach dem Eingang jedes neuen Angebots ausgeführt wird:

void OnTick()
  {

Der Code der Funktion steht in Klammern.

Geben wir die vordefinierten Datenstrukturen an, die im Expert Advisor benutzt werden:

   MqlTradeRequest request;
   MqlTradeResult result;
   MqlDateTime dt;
Die vordefinierte Struktur MqlTradeRequest beinhaltet Auftrags- und Positionsparameter, die in Handelstätigkeiten an die OrderSend-Funktion übermittelt werden. Der Zweck der MqlTradeRequest-Struktur ist die Speicherung der Informationen über die Ergebnisse der Handelstätigkeiten, die von der OrderSend-Funktion ausgegeben werden. Der Zweck der vordefinierten Struktur MqlDateTime ist die Speicherung der Datums- und Zeitinformationen.

Legen wir die lokalen Variablen (und deren Typen) fest, die in der OnTick-Funktion verwendet werden:

   bool bord=false, sord=false;
   int i;
   ulong ticket;
   datetime t[];
   double h[], l[], ma[], atr_h[], atr_l[],
          lev_h, lev_l, StopLoss,
          StopLevel=_Point*SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL),
          Spread   =NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK) - SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits);
Die booleschen Variablen bord und sord werden als Flags verwendet, die das Vorhandensein ausstehender Buy-Stop- und Sell-Stop-Aufträge anzeigen. Falls vorhanden, hat die entsprechende Variable einen Wert, der true entspricht, andernfalls ist sie false. Die Variable i wird als Zähler in Schleifenoperatoren und zur Speicherung von Zwischendaten genutzt. Das Ticket des ausstehenden Auftrags wird in der Variable ticket gespeichert.

Die Arrays t[], h[] und l[] werden zur Speicherung der Zeit und der höchsten und niedrigsten Preiswerte für jedes Bar in den historischen Daten verwendet. Das Array ma[] wird für die Speicherung der Werte des MA-Indikators verwendet, die Arrays atr_h[] und atr_l[] werden für die Speicherung der oberen und unteren Linien des von uns erstellten benutzerdefinierten Indikators indicator_TP verwendet.

Die Variablen lev_h und lev_l werden zur Speicherung der höchsten und niedrigsten Preiswerte des aktuellen Tages und der Eröffnungspreise ausstehender Aufträge verwendet. Die Variable StopLoss wird zur temporären Speicherung des Stop-Loss-Preises einer eröffneten Position verwendet.

Die Variable StopLevel wird zur Speicherung des Wertes von STOP_LEVEL verwendet – dem Mindestabstand zwischen dem aktuellen Preis und dem Preis des ausstehenden Auftrags (in Preiseinheiten). Wir berechnen diesen Wert als Produkt des Punktpreises (die vordefinierte Variable _Point) durch den Wert der Variable STOP_LEVEL, definiert in Punkten. Der Wert von STOP_LEVEL wird durch die SymbolInfoInteger-Funktion ausgegeben. Der erste Parameter dieser Funktion ist der Symbolname, der zweite ist der Identifikator der angefragten Eigenschaft. Das Symbol (Name des Finanzinstruments) kann mithilfe der Symbol-Funktion aufgerufen werden (diese Funktion hat keine Parameter).

Der Spread-Wert wird zum Speichern der Differenz (in Preiseinheiten) verwendet. Sein Wert wird als Differenz zwischen den aktuellen Kurs- und Gebotswerten berechnet, normalisiert mithilfe der NormalizeDouble-Funktion. Der erste Parameter dieser Funktion ist der Wert des zu normalisierenden Double-Typen, der zweite ist die Anzahl der Nachkommastellen, die wir aus der vordefinierten Variable _Digits erhalten haben. Die aktuellen Kurs- und Gebotswerte können mithilfe der SymbolInfoDouble-Funktion abgerufen werden. Der erste Parameter dieser Funktion ist der Symbolname der zweite ist der Identifikator der Eigenschaft.

Lassen Sie uns die Struktur request mit Werten befüllen, die für die meisten Aufrufe der OrderSend-Funktion gleich sein werden:

   request.symbol      =Symbol();
   request.volume      =Lots;
   request.tp          =0;
   request.deviation   =0;
   request.type_filling=ORDER_FILLING_FOK;
Das Element request.symbol enthält den symbolischen Namen des zu handelnden Instruments, das Element request.volume das Volumen (Vertragsumfang) des Finanzinstruments, request.tp den numerischen Wert von TakeProfit (in bestimmten Fällen verwenden wir es nicht und definieren es als 0), request.deviation die erlaubte Preisabweichung während der Durchführung der Handelstätigkeit, request.type_filling den Auftragstyp, der einer der folgenden sein kann:

Lassen Sie uns die aktuelle Serverzeit (Zeitpunkt des letzten Angebots) mithilfe der TimeCurrent-Funktion abrufen. Der einzige Parameter dieser Funktion ist der Pointer zur Struktur mit dem Ergebnis.

   TimeCurrent(dt);

Bei all unseren Berechnungen benötigen wir nur die historischen Preisdaten für den aktuellen Tag. Die Anzahl der benötigten Bars (plus eine gewisse Reserve) kann mit dieser Formel berechnet werden: i = (dt.hour + 1)*60 – dabei ist dt.hour das strukturelle Element, das die aktuelle Stunde enthält. Die Zeit- und höchsten und niedrigsten Preiswerte werden mithilfe der Funktionen CopyTime, CopyHigh bzw. CopyLow in die Arrays t[], h[] und l[] kopiert:

   i=(dt.hour+1)*60;
   if(CopyTime(Symbol(),0,0,i,t)<i || CopyHigh(Symbol(),0,0,i,h)<i || CopyLow(Symbol(),0,0,i,l)<i)
     {
      Print("Can't copy timeseries!");
      return;
     }

Der erste Parameter der Funktionen CopyTime, CopyHigh und CopyLow ist der Symbolname, der zweite der Zeitraum des Diagramms, der dritte das zu kopierende Startelement, der vierte die Anzahl zu kopierender Elemente, der fünfte das Ziel-Array für die Daten. Jede dieser Funktionen gibt die Anzahl kopierter Elemente aus. Im Fall eines Fehlers beträgt der Wert -1.

Der Operator if wird verwendet, um die Anzahl der kopierten Elemente für alle drei Arrays zu überprüfen. Falls die Anzahl kopierter Elemente geringer ist, als für die Berechnung benötigt wird (auch wenn es nur für eines der Arrays ist), oder im Fall eines Fehlers wird die Meldung "Can't copy timeseries!" (Zeitreihe kann nicht kopiert werden) in das Log des Experts gedruckt und die Ausführung der OnTick-Funktion mithilfe des Operators return beendet. Die Meldung wird durch die Funktion Print gedruckt. Sie kann beliebige Arten von Daten drucken. Diese werden durch Kommata getrennt.

Wenn die Preisdaten in die Arrays t[], h[] und l[] kopiert wurden, setzen wir das Flag AS_SERIES mithilfe der oben beschriebenen ArraySetAsSeries-Funktion auf true. Die Array-Indizierung muss als Zeitreihe definiert werden (von den aktuellen Preisen zu den älteren Preisen):

   ArraySetAsSeries(t,true);
   ArraySetAsSeries(h,true);
   ArraySetAsSeries(l,true);

Die höchsten und niedrigsten Preiswerte des aktuellen Tages werden in den Variablen lev_h und lev_l platziert:

   lev_h=h[0];
   lev_l=l[0];
   for(i=1;i<ArraySize(t) && MathFloor(t[i]/86400)==MathFloor(t[0]/86400);i++)
     {
      if(h[i]>lev_h) lev_h=h[i];
      if(l[i]<lev_l) lev_l=l[i];
     }

Die Schleife wird nur ausgeführt, wenn die Bedingung MathFloor(t[i]/86400) == MathFloor(t[0]/86400) true ist, um die Suche auf Bars, die zum aktuellen Tag gehören, einzuschränken. Auf der linken Seite dieses Ausdrucks befindet sich die Zahl der Bars des aktuellen Tages, auf der rechten Seite die Zahl des aktuellen Tages (86400 ist die Anzahl der Sekunden in einem Tag). Die MathFloor-Funktion rundet den numerischen Wert, d. h. sie nutzt nur den ganzzahligen Teil für die positiven Werte. Der einzige Parameter dieser Funktion ist der abzurundende Ausdruck. Die Gleichheit wird in MQL5 genauso wie in MQL4 durch die Symbole "==" definiert (siehe Operationen von Beziehungen).

Die Auftragspreise werden auf die folgende Art berechnet: Für einen ausstehenden Auftrag des Typen Buy Stop fügen wir einen Punkt (die vordefinierte Variable _Point entspricht der Punktgröße in Preiseinheiten) und Spread zur Variable lev_h hinzu (lev_h+=Spread+_Point oder lev_h=lev_h+Spread*_Point). Bei ausstehenden Aufträgen des Typen Sell Stop ziehen wir einen Punkt vom Wert der Variable lev_l ab (lev_l-=_Point oder lev_l=lev_l-_Point).

   lev_h+=Spread+_Point;
   lev_l-=_Point;

Als Nächstes kopieren wir die Werte des Indikatorpuffers in Arrays mithilfe der CopyBuffer-Funktion. MA-Werte werden in das Array ma[] kopiert, Werte der oberen Linie unseres benutzerdefinierten Indikators in das Array atr_h[], Werte der unteren Linie des Indikators in das Array atr_l[]. Die CopyBuffer-Funktion wurde bereits oben beschrieben, als wir uns mit den Details des Indikators beschäftigt haben.

   if(CopyBuffer(hMA,0,0,2,ma)<2 || CopyBuffer(hCI,0,0,1,atr_h)<1 || CopyBuffer(hCI,1,0,1,atr_l)<1)
     {
      Print("Can't copy indicator buffer!");
      return;
     }

Wir benötigen den MA-Indikatorwert, der dem vorletzten (letzten vollständigen) Bar entspricht, und den Wert unseres Indikators, der dem letzten Bar entspricht. Deshalb kopieren wir diese zwei Elemente in das Array ma[] und ein Element in die Arrays atr_h[] und atr_l[]. Im Fall eines Fehlers beim Kopieren oder wenn die Zahl kopierter Werte geringer ist als die benötigte (für jedes dieser Arrays), wird die Meldung in das Log des Experts gedruckt und die OnTick-Funktion mithilfe des Operators return beendet.

Für das Array ma[] setzen wir das Flag AS_SERIES, das die Zeitreihen-Indizierung des Arrays angibt.

   ArraySetAsSeries(ma,true);

Die Arrays atr_[] und atr_l[] haben nur ein Element, deshalb ist die Zeitreihen-Indizierung unwichtig. Da der Wert atr_l[0] weiterhin genutzt wird, um das TakeProfit-Level zu bestimmen, werden Short Positions zum Kurspreis geschlossen, doch wir fügen die Differenz zum Wert von atr_l[0] hinzu, weil die Gebotspreise in den Diagrammen verwendet werden.

   atr_l[0]+=Spread;

Die PositionsTotal-Funktion gibt die Anzahl der geöffneten Positionen aus (sie hat keine Parameter). Die Indizes von Positionen beginnen bei 0. Erstellen wir eine Schleife, die alle geöffneten Positionen durchsuchen wird:

// in this loop we're checking all opened positions
   for(i=0;i<PositionsTotal();i++)
     {
      // processing orders with "our" symbols only
      if(Symbol()==PositionGetSymbol(i))
        {
         // we will change the values of StopLoss and TakeProfit
         request.action=TRADE_ACTION_SLTP;
         // long positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
           {
            // let's determine StopLoss
            if(ma[1]>PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]; else StopLoss=lev_l;
            // if StopLoss is not defined or lower than needed            
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(StopLoss-PositionGetDouble(POSITION_SL),_Digits)>0
               // if TakeProfit is not defined or higer than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(PositionGetDouble(POSITION_TP)-atr_h[0],_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLoss-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(atr_h[0]-SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_h[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // short positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            // let's determine the value of StopLoss
            if(ma[1]+Spread<PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]+Spread; else StopLoss=lev_h;
            // if StopLoss is not defined or higher than needed
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(PositionGetDouble(POSITION_SL)-StopLoss,_Digits)>0
               // if TakeProfit is not defined or lower than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(atr_l[0]-PositionGetDouble(POSITION_TP),_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(StopLoss-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK)-atr_l[0]-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_l[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // if there is an opened position, return from here...
         return;
        }
     }
Mithilfe des Operators if in der Schleife wählen wir die Positionen aus, die für das Symbol des aktuellen Diagramms geöffnet wurden. Die PositionGetSymbol-Funktion gibt das Symbol des Instruments aus und wählt automatisch die Position, mit der gearbeitet werden soll. Die Funktion hat nur einen Parameter – den Index der Position in der Liste geöffneter Positionen. Es ist gut möglich, dass es erforderlich sein wird, die StopLoss- und TakeProfit-Werte der geöffneten Position zu ändern. Fügen wir also den TRADE_ACTION_SLTP-Wert dem Element request.action hinzu. Abhängig von der Richtung werden als Nächstes die Positionen in Long und Short aufgeteilt.

Für Long Positions wird das StopLoss-Level folgendermaßen bestimmt: Falls der Wert des MA-Indikators höher ist als der Eröffnungspreis der Position, wird angenommen, dass der StopLoss-Wert dem Wert des MA-Indikatorwerts entspricht. Andernfalls wird angenommen, dass der StopLoss-Wert dem Wert der Variable lev_l entspricht. Der aktuelle StopLoss-Wert für die geöffnete Position wird mithilfe der PositionGetDouble-Funktion bestimmt, die nur einen Parameter hat – den Identifikator der Positionseigenschaft. Falls der StopLoss-Wert nicht für die geöffnete Position definiert ist (entspricht 0) oder höher ist, als er sein sollte, verändern wir die StopLoss- und TakeProfit-Werte für diese Position. Falls der TakeProfit-Wert nicht für die geöffnete Position definiert ist (entspricht 0) oder höher ist, als er sein sollte (höher als die obere Linie unseres Indikators), verändern wir die StopLoss- und TakeProfit-Werte für diese Position.

Wir müssen die Möglichkeit prüfen, die StopLoss- und TakeProfit-Werte zu verändern. Der neue StopLoss-Wert sollte niedriger als der aktuelle Kurspreis sein (mindestens um den STOP_LEVEL-Wert), der neue TakeProfit-Wert sollte höher als der aktuelle Kurspreis sein, mindestens um den STOP_LEVEL-WERT. Wir haben die normierte Differenz für den Vergleich verwendet, da die verglichenen Werte sich aufgrund der von der Konvertierung von Gleitkomma-Binärzahlen des Double-Typen in Gleitkomma-Dezimalzahlen verursachten Ungenauigkeit unterscheiden können.

Falls es erforderlich ist, die StopLoss- und TakeProfit-Levels für die geöffnete Position zu verändern und falls die neuen Werte gemäß den Handelsregeln gültig sind, nehmen wir die neuen StopLoss- und TakeProfit-Werte in die entsprechenden Elemente der Struktur auf und rufen die OrderSend-Funktion auf, um die Daten an den Handelsserver zu senden.

Die Änderung der StopLoss- und TakeProfit-Werte für Short Positions funktioniert genauso. Short Positions werden durch Briefpreise geschlossen, deshalb werden für den Vergleich die Briefpreise herangezogen. Falls es eine geöffnete Position für das aktuelle Diagramm gibt, beenden wir die Ausführung der OnTick-Funktion mit dem Operator return.

Die OrdersTotal-Funktion (keine Parameter) gibt die Anzahl ausstehender Aufträge aus. Die Indizes beginnen bei 0. Erstellen wir eine Schleife, die zur Verarbeitung aller ausstehenden Aufträge genutzt wird:

// in this loop we're checking all pending orders
   for(i=0;i<OrdersTotal();i++)
     {
      // choosing each order and getting its ticket
      ticket=OrderGetTicket(i);
      // processing orders with "our" symbols only
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         // processing Buy Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_h<atr_h[0])
              {
               // if the opening price is lower than needed
               if((NormalizeDouble(lev_h-OrderGetDouble(ORDER_PRICE_OPEN),_Digits)>0
                  // if StopLoss is not defined or higher than needed
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(OrderGetDouble(ORDER_SL)-lev_l,_Digits)!=0)
                  // is opening price close to the current price?
                  && NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting the ticket number to the structure
                  request.order=ticket;
                  // putting the new value of opening price to the structure
                  request.price=NormalizeDouble(lev_h,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_l,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passed
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Buy Stop order
            bord=true;
           }
         // processing Sell Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[0])
              {
               // if the opening price is higher than needed
               if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>0
                  // if StopLoss is not defined or lower than need
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>0)
                  // is opening price close to the current price?
                  && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting ticket of modified order to the structure
                  request.order=ticket;
                  // putting new value of the opening price to the structure
                  request.price=NormalizeDouble(lev_l,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_h,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passedе
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Sell Stop order
            sord=true;
           }
        }
     }
Mithilfe der OrderGetTicket-Funktion wählen wir die Reihenfolge für die Verarbeitung und speichern das Auftragsticket in der Variable Ticket. Diese Funktion hat nur einen Parameter – den Index der Reihenfolge in der Liste offener Aufträge. Die OrderGetString-Funktion wird verwendet, um den Namen des Symbols zu erhalten. Sie hat nur einen Parameter – den Eigenschaftsidentifikator des Auftrags. Wir vergleichen den Symbolnamen mit dem Namen des aktuellen Diagramms. Dies ermöglicht es uns, Aufträge nach Instrument auszuwählen, mit dem der Expert Advisor arbeitet. Der Auftragstyp wird durch die OrderGetInteger-Funktion mit dem entsprechenden Auftragstyp-Identifikator bestimmt. Wir verarbeiten Buy-Stop- und Sell-Stop-Aufträge separat.

Wenn sich die aktuelle Stunde im Bereich zwischen StartHour und EndHour befindet und der Eröffnungspreis des Buy-Stop-Auftrags nicht über der oberen Linie des Indikators liegt, verändern wir den Eröffnungspreis und den Wert des StopLoss-Levels (falls erforderlich). Andernfalls löschen wir den Auftrag.

Als Nächstes bestimmen wir, ob es erforderlich ist, den Eröffnungspreis oder das StopLoss-Level für den ausstehenden Auftrag zu verändern. Falls der Eröffnungspreis des Buy-Stop-Auftrags niedriger ist, als er sein sollte, oder falls das StopLoss-Level nicht definiert oder höher ist, fügen wir den TRADE_ACTION_MODIFY-Wert dem Element request.action hinzu. Das bedeutet, dass die Parameter des ausstehenden Auftrags verändert werden sollten. Außerdem fügen wir das Auftragsticket dem Element request.ticket hinzu und senden eine Handelsanfrage an den Handelsserver mithilfe der OrderSend-Funktion. Im Vergleich bestimmen wir den Fall, dass der Eröffnungspreis niedriger ist, als er sein sollte, weil der Differenzwert unterschiedlich ausfallen kann, allerdings ändern wir nicht nach jeder Veränderung der Differenz den Auftrag. Dieser wird für das höchste Niveau eingestellt, das dem maximalen Differenzwert entspricht.

Ebenso bestimmen wir im Vergleich nur den Fall, wenn der StopLoss-Wert höher ist, als er sein sollte, weil sich der Preisbereich im Laufe des Tages ausweiten kann. In diesem Fall kann es erforderlich sein, den StopLoss-Wert an neue Preis-Tiefpunkte anzupassen. Nach dem Senden der Anfrage an den Handelsserver wird die OnTick-Funktion mithilfe des Operators return beendet. Falls Buy-Stop-Aufträge vorhanden sind, wird der Wert der Variable bord auf true gesetzt.

Sell-Stop-Aufträge werden genauso verarbeitet wie Buy-Stop-Aufträge.

Lassen Sie uns nun fehlende ausstehende Buy-Stop- und Sell-Stop-Aufträge platzieren. Wir fügen den TRADE_ACTION_PENDING-Wert dem Element request.action hinzu (das bedeutet, dass der ausstehende Auftrag platziert wird).

   request.action=TRADE_ACTION_PENDING;

Liegt der Wert der aktuellen Stunde innerhalb der Zeit für die Platzierung von Aufträgen, platzieren wir die Aufträge:

   if(dt.hour>=StartHour && dt.hour<EndHour)
     {
      if(bord==false && lev_h<atr_h[0])
        {
         request.price=NormalizeDouble(lev_h,_Digits);
         request.sl=NormalizeDouble(lev_l,_Digits);
         request.type=ORDER_TYPE_BUY_STOP;
         OrderSend(request,result);
        }
      if(sord==false && lev_l>atr_l[0])
        {
         request.price=NormalizeDouble(lev_l,_Digits);
         request.sl=NormalizeDouble(lev_h,_Digits);
         request.type=ORDER_TYPE_SELL_STOP;
         OrderSend(request,result);
        }
     }
  }
Während des Platzierens von Buy-Stop- und Sell-Stop-Aufträgen überprüfen wir das Vorhandensein gleicher Aufträge, indem wir die Werte der Variablen bord und sord analysieren. Wir überprüfen auch die folgende Bedingung: Der Preis des Auftrags sollte innerhalb der Werte unseres Indikators liegen. Der normalisierte Preis des Auftrags wird im Element request.price platziert, der normalisierte StopLoss-Wert wird in der Variable request.sl platziert, der Auftragstyp (ORDER_BUY_STOP oder ORDER_SELL_STOP) wird in der Variable request.type platziert. Anschließend senden wir die Anfrage an den Handelsserver. Der Code der OnTick-Funktion endet mit einem Semikolon.

Die von Indikatoren zugewiesenen Ressourcen werden innerhalb der OnDeinit-Funktion mithilfe der IndicatorRelease-Funktion freigegeben. Auf diese Funktion sind wir weiter oben eingegangen.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

Der Expert Advisor ist vollständig, die Kompilierung sollte erfolgreich sein, wenn keine Fehler vorliegen. Nun können wir ihn ausführen, indem wir ihn an das Diagramm anhängen. Der Quellcode kann aus den Anhängen dieses Beitrags heruntergeladen werden.


Starten und Debuggen

Wenn Expert Advisor und Indikator bereit sind, gehen wir nun darauf ein, wie sie gestartet und mithilfe des integrierten MetaEditor-Debuggers debuggt werden können.

Um einen Expert Advisor zu starten, muss er aus der Gruppe Expert Advisors im Navigator-Fenster herausgesucht werden. Wählen Sie anschließend Attach to Chart (An Diagramm anhängen) aus dem Kontextmenü, das nach einem Rechtsklick erscheint:

Abbildung 11. Starten des Expert Advisors.

Es erscheint ein Fenster mit den Eingabeparametern des Expert Advisors. Sie können diese Parameter ändern, falls erforderlich. Nachdem Sie auf OK klicken, erscheint das Icon  in der oberen rechten Ecke des Diagramms. Dieses Icon zeigt an, dass der Expert Advisor aktiv ist. Um das Navigator-Fenster anzuzeigen oder zu schließen, können Sie Navigator aus dem Menü View (Ansicht) wählen oder Strg+N drücken. Die zweite Art, den Expert Advisor zu starten, ist die Auswahl im Untermenü Experts im Menü Insert (Einfügen)

Damit der Expert Advisor handeln kann, sollte AutoTrading in den Optionen des Client Terminals aktiviert werden: Tools -> Options (Optionen) -> Expert Advisors -> Allow AutoTrading (AutoTrading zulassen) sollte aktiviert werden. Damit der Expert Advisor Funktionen von DLLs abrufen kann, sollte auch die Option Allow DLL imports (DLL-Importe zulassen) aktiviert werden.

Abbildung 12. Terminal-Optionen – Zulassen von AutoTrading.

Zusätzlich können Sie Autorisierungen und Einschränkungen für Handel und Importe externer DLL-Bibliotheken für jeden Expert Advisor individuell einrichten, indem Sie die entsprechenden Optionen auswählen.

Obwohl unser Expert Advisor Indikatoren nutzt, werden die Linien der Indikatoren nicht im Diagramm dargestellt. Falls erforderlich, können Sie die Indikatoren manuell anhängen.

Das Starten von Indikatoren ist identisch mit dem Starten von Expert Advisors: Wenn Sie integrierte Indikatoren starten möchten, erweitern Sie die Baumstruktur Indicators (Indikatoren) im Fenster Navigator (bei benutzerdefinierten Indikatoren muss die Baumstruktur Custom Indicators (Benutzerdefinierte Indikatoren) erweitert werden), klicken Sie mit der rechten Maustaste, damit das Pop-up-Menü erscheint, und wählen Sie Attach to Chart (An Diagramm anhängen) aus dem Kontextmenü. Die zweite Möglichkeit ist, Indicators aus dem Menü Insert (Einfügen) auszuwählen, die Gruppe (oder Custom (Benutzerdefiniert) bei benutzerdefinierten Indikatoren) und den Indikator selbst auszuwählen.

Scripts werden auf die gleiche Weise wie Expert Advisors und Indikatoren gestartet.

Die Informationen zu Client-Terminal-Ereignissen (Verbindung/Trennung mit dem/vom Handelsserver, automatisches Update, Änderungen an Positionen und Aufträgen, ausgeführte Expert Advisors und Scripts, Fehlermeldungen) können unter der Registerkarte Journal (Logbuch) im Fenster Toolbox gefunden werden. Die von Expert Advisors, Indikatoren und Scripts gedruckten Meldungen befinden sich in der Registerkarte Experts.

Der MetaEditor verfügt über einen integrierten Debugger. Damit können Sie Programme debuggen – eine schrittweise Ausführung von Expert Advisors, Indikatoren und Scripts. Debugging hilft Ihnen, Fehler im Programmcode zu finden und die Prozesse bei der Ausführung von Expert Advisors, Indikatoren und Scripts zu beobachten. Um ein Programm im Debug-Modus auszuführen, müssen Sie Start im Menü Debug wählen oder F5 drücken. Das Programm wird kompiliert und im Debug-Modus in einem separaten Diagramm ausgeführt. Zeitraum und Symbol können in der Registerkarte Debugging in der Ansicht Options (Optionen) im MetaEditor festgelegt werden.

Abbildung 13. Editor-Optionen – Debugging.

Sie können die Haltepunkte durch Drücken von F9, durch Doppelklicken auf die linke Seite der Zeile oder durch Auswählen von Toggle Breakpoint (Haltepunkt festlegen) im Fenster Debug festlegen. Im Debug-Modus wird die Ausführung des Programms vor dem Operator mit Haltepunkt angehalten. Nach dem Anhalten erscheint die Registerkarte Debug im Fenster Toolbox (Abbildung 14). Auf der linken Seite befindet sich ein Aufruf-Stack-Panel. Dort werden Datei, Funktion und Zeilennummer angezeigt. Auf der rechten Seite befindet sich ein Beobachtungs-Panel. Dort werden Werte beobachteter Variablen angezeigt. Um eine Variable zur Beobachtungsliste hinzuzufügen, rechtsklicken Sie auf das Panel und wählen Sie Add (Hinzufügen) oder drücken Sie die Taste Einfügen.

Abbildung 14. Programm-Debug.

Die schrittweise Ausführung des Programms kann durch Drücken der Tasten F11, F10 oder Umschalten+F11 angestoßen werden. Nach dem Drücken von F11 oder Auswählen von Step Into (Schritt hinein) aus dem Menü Debug wird ein Schritt der Programmausführung mit allen aufgerufenen Funktionen ausgeführt. Nach dem Drücken von F10 oder Auswählen von Step Over (Schritt über) aus dem Menü Debug wird ein Schritt der Programmausführung ohne aufgerufene Funktionen ausgeführt. Nach dem Drücken von Umschalten+F11 oder Auswählen von Step Out (Schritt hinaus) aus dem Menü Debug wird die Ausführung eines Programmschritts eine Ebene höher angestoßen. Der grüne Pfeil auf der linken Seite des Codes kennzeichnet die auszuführende Codezeile.


Fazit

In diesem Beitrag werden ein Beispiel für das Schreiben eines einfachen Expert Advisors vorgestellt und die Grundlagen der Programmiersprache MQL5 beschrieben. Das hier bereitgestellte Handelssystem dient als Beispiel. Der Autor übernimmt keine Verantwortung für dessen Verwendung im realen Handel.