Schnelleinstieg oder Kurzanleitung für Anfänger

Dmitriy Parfenovich | 27 Januar, 2016

Einleitung

Liebe Leser, in diesem Artikel möchte ich Ihnen vermitteln und zeigen, wie man sich möglichst schnell und einfach die Grundlagen der Erstellung automatischer Handelssysteme, wie die Arbeit mit Indikatoren u. a., erschließt. Der Beitrag richtet sich an Neueinsteiger, in ihm kommen weder komplizierte noch schwer zu verstehende Beispiele zur Anwendung. Höchstwahrscheinlich wird er deshalb für alle, die bereits wissen, wie man solche Expert-Systeme programmiert, weniger interessant und erhellend sein.

Ein Expert-System und sein Aufbau

Ein Expert-System oder Expert Advisor ist ein in MQL geschriebenes Programm, in dem vorgegeben wird, unter welchen Bedingungen eine Handelsabschluss zu tätigen oder wann von ihm abzusehen ist.

Der Aufbau eines Expert-Systems kann grundsätzlich eine große Anzahl Codeblöcke enthalten, aber der Einfachheit des Verständnisses halber demonstriere ich lediglich das standardmäßig von dem Bearbeitungsprogramm MetaEditor angelegte Elementarbeispiel.

Optisch kann das gesamte Expert-System in 4 Teile untergliedert werden, von denen jeder seinen Teil der Arbeit ausführt.

Die grundlegenden Blöcke eines Expert-Systems 
Abb. 1. Die grundlegenden Blöcke eines Expert-Systems

  1. Der Parameterblock enthält die Informationen, die das Programm auf dem Ausgabegerät (Terminal) benötigt, um das Expert-System ordnungsgemäß arbeiten zu lassen. Die verbreitetsten Parameter sind: die Version des Expert-Systems, der Name des Herstellers sowie eine kurze Beschreibung.

  2. Der Block OnInit() übernimmt die Steuerung, sobald das Expert-System in das Programm auf dem Ausgabegerät geladen worden ist. Er kann verschiedene Informationen in Verbindung mit der Bereitstellung des Expert-Systems beinhalten: bestimmte Variablen und Datenfelder (Arrays), den Empfang der Indikatorbezeichnungen (Handles) usw. Das heißt, in diesem Block gibt es keine unmittelbar mit dem Handel verbundenen Funktionen.

  3. Der Block OnDeinit() erfüllt die OnInit() entgegengesetzte Funktion. Er wird aufgerufen, wenn das Expert-System seine Arbeit abgeschlossen hat (Schließen des Expert-Systems oder des Programms auf dem Ausgabegerät oder bei fehlgeschlagener Bereitstellung des Expert-Systems). Eine der Hauptaufgaben dieses Blocks ist die Bereinigung des von dem Expert-System belegten Speicherplatzes, sobald es nicht mehr benötigt wird. Mit anderen Worten, in ihm ist der Vorgang zum Löschen von Variablen, Datenfeldern (Arrays), Indikatorbezeichnungen (Handles) usw. angelegt.

  4. Der Block OnTick() wird jedes Mal aufgerufen, wenn von einem Server neue Angaben zu dem Kürzel (Währungspaar) eingehen. In ihm werden die Bedingungen, unter denen ein Geschäft abgeschlossen wird, sowie die Funktionen für diesen Geschäftsabschluss angegeben.

Beispiel für ein neues in dem Bearbeitungsprogramm MetaEditor standardmäßig angelegtes Dokument
Abb. 2. Beispiel für ein neues in dem Bearbeitungsprogramm MetaEditor standardmäßig angelegtes Dokument

Lassen Sie mich das anhand des obigen Beispiels erläutern. Es handelt sich hier um den Code eines „leeren“ Expert-Systems, sozusagen um eine „Gussform“ oder ein Template, das gefüllt werden muss.
Hier ist es zu sehen:

Wie bereits gesagt kann der Aufbau erheblich komplexer ausfallen und aus einer Vielzahl von Blöcken bestehen. Demgegenüber ist unser Beispiel zum besseren Verständnis sehr vereinfacht. Wenn Sie meinen, das sei zu wenig Code, können Sie gerne eigene Blöcke ergänzen.
 

Indikatoren und die Arbeit mit ihnen

Indikatoren sind kleine in MQL geschriebene Programme, die in einem Kursdiagramm oder in einem eigenen Fenster darunter angezeigt werden und die Durchführung einer technischen Analyse des Marktes ermöglichen.

Alle Indikatoren können in zwei Arten unterteilt werden: Trendfolgende Indikatoren und Oszillatoren.

Trendfolgende Indikatoren finden sich in der Regel in Kursdiagrammen und werden zur Ermittlung der Trendrichtung verwendet, Oszillatoren werden dagegen unterhalb des jeweiligen Kursdiagramms abgebildet und werden zur Bestimmung der Einstiegspunkte benötigt.

Die meisten Indikatoren verfügen über mindestens einen Indikatorpuffer genannten Zwischenspeicher, in dem Angaben zu den Werten dieses Indikators für bestimmte Zeitpunkte gespeichert werden. Wie ein Expert-System verfügt auch der Indikator über ein Kürzel und einen Zeitraum, für die er eingerichtet ist.

Den Indikatorpuffer können wir uns als eine Reihe vorstellen, deren letztes Element der jeweils aktuelle Wert ist.

Beispiel für den Indikator anhand gleitender Durchschnittswerte (MA)
Abb. 3. Beispiel für den Indikator anhand gleitender Durchschnittswerte (MA)

Bei dem Indikatorpuffer handelt es sich um ein Datenfeld (Array), dessen erstes Element (mit der Kennziffer „0“) die Daten der Kerze ganz rechts enthält, das nächste Element (Kennziffer „1“) die Daten der zweiten Kerze von rechts usf. Eine solche Anordnung der Elemente wird als Zeitreihe bezeichnet.

Schauen wir uns ein Beispiel an:
Wir nehmen an, das Währungspaar ist EUR/USD und der Zeitraum beträgt 1 Stunde.
Zunächst einmal müssen wir den Indikator mit dem Expert-System verknüpfen und seine Bezeichnung (sein Handle) beziehen.

Das Handle ist ein eindeutiger Hinweis auf den Indikator, der es ermöglicht, diesen Indikator an jeder beliebigen Stelle im Programm anzusprechen.

int iMA_handle; 
iMA_handle=iMA("EURUSD",PERIOD_H1,10,0,MODE_SMA,PRICE_CLOSE);
Sehen wir uns das einmal genauer an.

In der ersten Zeile legen wir die Variable fest, die das Indikatorhandle speichern wird. In der zweiten Zeile wird der Indikator (in unserem Fall der Indikator Moving Average) aufgerufen, seine Parameter abgegeben, und das Handle für die weitere Arbeit in der Variablen gespeichert.
Bei der Arbeit in dem Bearbeitungsprogramm MetaEditor wird nach der Eingabe von „iMA(“ oberhalb dieser Zeile eine Kurzinformation (Quickinfo) zu diesem Indikator mit den Aufrufparametern in kommagetrennter Schreibweise angezeigt.

Beispiel für die Kurzinformation (Quickinfo) zu den MA-Indikatorparametern
Abb. 4. Beispiel für die Kurzinformation (Quickinfo) zu den MA-Indikatorparametern

Von links nach rechts sehen wir folgende Parameter:

  1. die Bezeichnung des Kürzels (in der Quickinfo fett gedruckt) ist ein Textparameter, der das Währungspaar (Kürzel) angibt;
  2. den Zeitraum;
  3. den Indikatorzeitraum (in unserem Fall der Mittelungszeitraum);
  4. die Diagrammverlagerung um N Balken vor/zurück. Eine positive Zahl gibt eine Verlagerung des Diagramms um N Balken nach VORNE an, eine negative dagegen eine Verlagerung um N Balken nach hinten.
  5. die Mittelungsmethode;
  6. den zu verwendenden Kurs bzw. das Handle eines anderen Indikators.

Jeder Indikator weist ein einzigartiges Bündel von Variablen und deren Arten auf. Wenn unvermittelt ein unbekannter Indikator auftaucht, kann man sich stets in der integrierten Kontexthilfe über ihn informieren. Wenn wir beispielsweise iMA eingeben und F1 drücken, öffnet sich ein Hilfefenster mit einer ausführlichen Darstellung aller Eigenschaften des entsprechenden Indikators.

Beispiel für den Aufrufe des Hilfefensters für die Beschreibung des Indikators mithilfe der Funktionstaste F1
Abb. 5. Beispiel für den Aufrufe des Hilfefensters für die Beschreibung des Indikators mithilfe der Funktionstaste F1

Nachdem wir diesen Code geschrieben und das Expert-System im Programm auf dem Ausgabegerät aufgerufen haben, sehen wir (wenn das Expert-System in der oberen rechten Ecke des Kursdiagramms angezeigt wird), dass unser Indikator im Diagramm nicht abgebildet wird. Das ist kein Fehler, es war so gedacht. Damit er sichtbar wird, müssen wir folgende Zeile nachtragen.

ChartIndicatorAdd(ChartID(),0,iMA_handle);

Schauen wir, was sie bewirkt. Setzen wir den Mauszeiger auf den Befehl ChartIndicatorAdd und drücken F1, anschließend lesen wir in dem Hilfefenster, wozu dieser Befehl benötigt wird. Dort heißt es, dass dieser Befehl:

Dem angegebenen Diagrammfenster einen Indikator mit der angegebenen Bezeichnung hinzufügt.

Der zweite Parameter mit dem Wert „0“ ist die Nummer des Unterfensters. In den Unterfenstern unterhalb des Kursdiagramms befinden sich üblicherweise die Oszillatoren. Sie erinnern sich? Von diesen Unterfenstern kann es viele geben. Um den Indikator in einem Unterfenster angezeigt wird, reicht es aus, die Nummer des Unterfensters um den Wert 1, das heißt auf die nächste der letzten vorhandenen Nummer folgende, zu erhöhen.

Mit dem derart geänderten Code:

ChartIndicatorAdd(ChartID(),1,iMA_handle);

erscheint unser Indikator jetzt in dem unterhalb des Kursdiagramms eingeblendeten Unterfenster.

Wir wollen nun versuchen, Daten von unserem Indikator zu beziehen. Dazu deklarieren wir ein dynamisches Datenfeld, in dem wir der Einfachheit halber dieselbe Vergabe der Kennziffern anwenden wie in der Zeitreihe, und in das wir die Werte des Indikators hinein kopieren.

double iMA_buf[];
ArraySetAsSeries(iMA_buf,true);
CopyBuffer(iMA_handle,0,0,3,iMA_buf);

In unserem Beispiel haben wir das dynamische Datenfeld iMA_buf[] des Typs „double“ deklariert, das heißt, dass der Moving Average-Indikator auf den Kursen beruht, die wiederum Spitzen aufweisen.

In der nächsten Zeile wird die Art der Kennziffernvergabe für dieses Datenfeld so festgelegt, dass die Elemente mit kleineren Kennziffern die älteren Werte enthalten, die Elemente mit höheren Nummern dagegen die neueren. Dadurch kann einiges an Verwirrung vermieden werden, da die Indikatorpuffer in allen Indikatoren dieselbe Kennziffernvergabe aufweisen wie die Zeitreihen.

Mittels der letzten Zeile kopieren wir die Indikatorwerte in das Datenfeld iMA_buf[]. Diese Daten können jetzt verwendet werden. 

 

Aufträge (Orders), Geschäftsabschlüsse und Positionen

Fangen wir mit den Aufträgen an.

Bei Bestensaufträgen (Market Orders) handelt es sich um Anweisungen zum sofortigen Kauf oder Verkauf eines bestimmten Finanzinstruments zum aktuellen Marktkurs.
Pending Orders dagegen sind Anweisungen zum Abschluss des Geschäfts bei Vorliegen bestimmter Bedingungen. Diese „offenen Aufträge“ haben ein „Haltbarkeitsdatum“ auf, bei dessen Erreichen sie gelöscht werden.

Lassen Sie mich zur Veranschaulichung ein Beispiel anführen: wir eröffnen eine lange Position im Umfang von 1 Posten, das heißt, wir erteilen einen Auftrag zum aktuellen Kurs (als Beispiel) im Umfang von 1 Posten. Ist die Anfrage gültig, so wird sie zur Verarbeitung an den Server geschickt. Sobald die Verarbeitung erfolgt ist, erscheint im Ausgabegerät unter der Registerkarte „Handel“ eine Position mit den Parametern des Auftrags. Anschließend haben wir uns entschieden, noch eine weitere lange Position ebenfalls im Umfang von 1 Posten zu eröffnen. Nach der Verarbeitung des Auftrags erscheinen unter der Registerkarte „Handel“ nicht zwei Positionen sondern nur eine, dafür aber im Umfang von 2 Posten. Eine Position ist also das Ergebnis der Ausführung mehrerer Aufträge.

Kommen wir zur Praxis. Um eine Anfrage zu stellen, müssen die Felder des folgenden Gerüstes gefüllt werden:

struct MqlTradeRequest
{
ENUM_TRADE_REQUEST_ACTIONS action; // Type of action
ulong magic; // Expert Advisor ID (magic number)
ulong order; // Order ticket
string symbol; // Trade instrument
double volume; // Requested trade size in lots
double price; // Price 
double stoplimit; // StopLimit level of the order
double sl; // Stop Loss level of the order
double tp; // Take Profit level of the order
ulong deviation; // Maximum allowed deviation from the requested price
ENUM_ORDER_TYPE type; // Order type
ENUM_ORDER_TYPE_FILLING type_filling; // Order type by execution
ENUM_ORDER_TYPE_TIME type_time; // Order type by duration
datetime expiration; // Order expiration time (for orders of the ORDER_TIME_SPECIFIED type)
string comment; // Comment to the order
};

Da es unterschiedliche Aufträge gibt, besteht für jede Art ein eigener Satz von Pflichtparametern. Ich werde mich nicht lange bei diesen Feldern aufhalten. Die Webseite bietet dazu zahlreiche Informationen. Selbst wenn nur ein einziger Pflichtparameter für eine bestimmte Auftragsart nicht oder nicht korrekt angegeben wird, wird diese Anfrage nicht ausgeführt.

Ich habe dieses Gerüst nur dargestellt, um die ganze Schwierigkeit seiner Ausfüllung anschaulich vorzuführen, wie schwierig es zu füllen ist.

Stop Loss und Take Profit

Bei Stop Loss und Take Profit handelt es sich um besondere Auftragsarten, die als „Rückversicherungen“ platziert werden. Das heißt, wenn wir uns geirrt haben, oder das Expert-System eine Position eröffnet hat, die ins Minus gerät, kann ein Stop Loss-Auftrag den Verlust an einer vorgegebenen Grenze bremsen.

Take Profit hat vergleichbare Eigenschaften, nur dass hier der Gewinn begrenzt wird. Das ist vermutlich erforderlich, wenn man sich keine Gedanken darum machen möchte, wann eine Position zu schließen ist. Sich wird bei Erreichen eines bestimmten Kurses automatisch geschlossen. Mit einem Wort: diese Aufträge sind unsere „Versicherung“ für den Fall, dass der Markt gegen uns läuft, oder wir einen festgelegten Gewinn einstreichen wollen.

Aufträge dieser Art werden nicht einzeln erteilt, sie können lediglich bereits bestehende Positionen modifizieren.

Mit Standardbibliotheken arbeiten

Schon sind wir bei der Standardbibliothek angelangt. Diese Bibliothek wird mit dem Programm für das Ausgabegerät ausgeliefert, daher ihr Name: Standardbibliothek. In ihr sind Funktionen versammelt, die die Programmierung eines Expert-Systems erleichtern und zum Teil die Erledigung schwieriger Aufgaben übernehmen, zum Beispiel die korrekte Formulierung einer Handelsanfrage.

Die Handelsbibliotheken (siehe auch Handelsklassen) sind über folgenden Pfad zu finden: Include\Trade\ und werden mithilfe der Anweisung #include angebunden.
Beispiel:

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

Die in dem obigen Beispiel vorgestellten Klassen können als Basisklassen bezeichnet werden, da die Mehrzahl der Expert-Systeme unter Verwendung lediglich dieser beiden Klassen (Bibliotheken) angelegt werden können. Ich nenne diese Klassen Bibliotheken:


Manchmal kommt auch noch eine dritte Bibliothek zum Einsatz:
#include <Trade\OrderInfo.mqh>
Sie enthält Funktionen für die Arbeit mit Aufträgen, wenn unsere Strategie beispielsweise die Verwendung von Pending Orders (offenen oder bedingten Aufträgen) erfordert.

Erinnern Sie sich noch an das Gerüst für die Handelsanfrage mit der großen Zahl an Parametern, die man bei der Anwendung kennen musste?
Ich zeige Ihnen jetzt ein Beispiel für eine unter Verwendung der Bibliothek angelegte Handelsanfrage:
CTrade m_Trade;
m_Trade.Sell(lot,symbol_name,price,sl,tp,comment);

Hier gibt es insgesamt 6 Parameter, von denen nur einer obligatorisch ist (nämlich der allererste, der Auftragsumfang).
Es folgt eine ausführliche Darstellung der einzelnen Parameter:

Eine Position kann auf mehrere Weisen geschlossen werden:

  1. komplett
    CPositionInfo m_Position;
    m_Position.Select(symbol_name);
    m_Trade.PositionClose(symbol_name);
  2. durch einen gegenläufigen Auftrag (Reverse Order) in dem der Position entsprechendem Umfang
    CTrade m_Trade;
    m_Trade.Buy(lot,symbol_name,price,sl,tp,comment);
  3. auf kompliziertere Weise, bei der zunächst alle eröffneten Positionen durchsucht werden, um die nach ihren Parametern (Kürzel, Art, magischer Zahl, Positionsbezeichner usw.) erforderlichen herauszufiltern, und dann die Position zu schließen.
    Ich werde hier angesichts der Schwierigkeit, es in dieser frühen Phase bereits zu verstehen, kein Beispiel geben.

Die „Endmontage“

Jetzt ist es Zeit, unser neu erworbenes Wissen in ein Expert-System umzusetzen.

//+------------------------------------------------------------------+
//|                                           fast-start-example.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                         //include the library for execution of trades
#include <Trade\PositionInfo.mqh>                                  //include the library for obtaining information on positions

int               iMA_handle;                              //variable for storing the indicator handle
double            iMA_buf[];                               //dynamic array for storing indicator values
double            Close_buf[];                             //dynamic array for storing the closing price of each bar

string            my_symbol;                               //variable for storing the symbol
ENUM_TIMEFRAMES   my_timeframe;                             //variable for storing the time frame

CTrade            m_Trade;                                 //structure for execution of trades
CPositionInfo     m_Position;                              //structure for obtaining information of positions
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   my_symbol=Symbol();                                      //save the current chart symbol for further operation of the EA on this very symbol
   my_timeframe=PERIOD_CURRENT;                              //save the current time frame of the chart for further operation of the EA on this very time frame
   iMA_handle=iMA(my_symbol,my_timeframe,40,0,MODE_SMA,PRICE_CLOSE);  //apply the indicator and get its handle
   if(iMA_handle==INVALID_HANDLE)                            //check the availability of the indicator handle
   {
      Print("Failed to get the indicator handle");              //if the handle is not obtained, print the relevant error message into the log file
      return(-1);                                           //complete handling the error
   }
   ChartIndicatorAdd(ChartID(),0,iMA_handle);                  //add the indicator to the price chart
   ArraySetAsSeries(iMA_buf,true);                            //set iMA_buf array indexing as time series
   ArraySetAsSeries(Close_buf,true);                          //set Close_buf array indexing as time series
   return(0);                                               //return 0, initialization complete
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(iMA_handle);                             //deletes the indicator handle and deallocates the memory space it occupies
   ArrayFree(iMA_buf);                                      //free the dynamic array iMA_buf of data
   ArrayFree(Close_buf);                                    //free the dynamic array Close_buf of data
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   int err1=0;                                             //variable for storing the results of working with the indicator buffer
   int err2=0;                                             //variable for storing the results of working with the price chart
   
   err1=CopyBuffer(iMA_handle,0,1,2,iMA_buf);               //copy data from the indicator array into the dynamic array iMA_buf for further work with them
   err2=CopyClose(my_symbol,my_timeframe,1,2,Close_buf);    //copy the price chart data into the dynamic array Close_buf for further work with them
   if(err1<0 || err2<0)                                    //in case of errors
   {
      Print("Failed to copy data from the indicator buffer or price chart buffer");  //then print the relevant error message into the log file
      return;                                                               //and exit the function
   }

   if(iMA_buf[1]>Close_buf[1] && iMA_buf[0]<Close_buf[0])   //if the indicator values were greater than the closing price and became smaller
     {
      if(m_Position.Select(my_symbol))                     //if the position for this symbol already exists
        {
         if(m_Position.PositionType()==POSITION_TYPE_SELL) m_Trade.PositionClose(my_symbol);  //and this is a Sell position, then close it
         if(m_Position.PositionType()==POSITION_TYPE_BUY) return;                              //or else, if this is a Buy position, then exit
        }
      m_Trade.Buy(0.1,my_symbol);                          //if we got here, it means there is no position; then we open it
     }
   if(iMA_buf[1]<Close_buf[1] && iMA_buf[0]>Close_buf[0])  //if the indicator values were less than the closing price and became greater
     {
      if(m_Position.Select(my_symbol))                     //if the position for this symbol already exists
        {
         if(m_Position.PositionType()==POSITION_TYPE_BUY) m_Trade.PositionClose(my_symbol);   //and this is a Buy position, then close it
         if(m_Position.PositionType()==POSITION_TYPE_SELL) return;                             //or else, if this is a Sell position, then exit
        }
      m_Trade.Sell(0.1,my_symbol);                         //if we got here, it means there is no position; then we open it
     }
  }
//+------------------------------------------------------------------+

Führen wir einen Probelauf mit folgenden Parametern aus:

Da wir die Indikatorwerte und Schlusskurse ab dem ersten Balken verwenden (der „Nullbalken“ ist der aktuelle, aktive Balken), wird das Diagramm nicht neu gezeichnet. Das bedeutet, dass wir für den Handel den Modus „Nur Eröffnungskurse“ verwenden können. Dadurch wird die Qualität der Prüfung nicht beeinträchtigt, aber die Durchführung beschleunigt.

Und hier haben wir die Ergebnis einer schnellen Prüfung anhand der Kursverlaufsdaten: 

Die Prüfergebnisse unseres Expert-Systems
Abb. 6. Die Prüfergebnisse unseres Expert-Systems

Die Schwächen sind unübersehbar. Aber das Ziel dieses Beitrags bestand ja nicht darin, ein „Super-Expert-System“ zu programmieren, das bei einem Minimum an Schwächen über ein riesiges Gewinnpotenzial verfügt, sondern vielmehr darin, zu zeigen, wie einfach die Erstellung eines Expert-Systems ist, wenn man mit den Grundkenntnissen gerüstet ist.
So haben wir ein Expert-System vor uns, das aus weniger als 100 Codezeilen besteht.
 

Fazit

In diesem Beitrag wurden die wesentlichen, bei der Programmierung eines Expert-Systems zu berücksichtigenden Grundlagen erörtert. Wir haben gelernt, die in MetaEditor 5 integrierte Kontexthilfe zu nutzen, um Informationen zu unterschiedlichen Funktionen abzurufen, haben eine allgemeine Vorstellung von Aufträgen (Orders) und Positionen erhalten und die Verwendung der Standardbibliotheken behandelt.