Der Prototyp eines automatischen Handelssystems

--- | 12 Januar, 2016

Einleitung

Der Lebenszyklus eines jeden Handelssystems läuft auf das Öffnen und Schließen von Positionen hinaus. Das wird von niemandem bezweifelt. Aber wenn es darum geht, den Algorithmus umzusetzen, gibt es, wie man sagt, ebenso viele Meinungen wie Programmierer. Jeder ist in der Lage, ein und dieselbe Aufgabe auf seine jeweils eigene Art und Weise zu lösen, aber alle mit demselben Endergebnis.

In vielen Jahren praktischer Programmiertätigkeit wurden verschiedene Ansätze zur Schaffung einer Logik und eines Aufbaus von automatischen Handelssystemen, Trading Robots oder Expert Advisors ausprobiert. Derzeit kann behauptet werden, dass ein eindeutiges Muster-Template vorliegt, das in allen Programmcodes Anwendung findet.

Dieser Ansatz ist nicht zu 100 % allgemeingültig, aber er kann unser Vorgehen bei der Entwicklung der Logik für automatische Handelssysteme verändern. Dabei geht es nicht darum, welche Möglichkeiten zur Auftragsverarbeitung sie in dem entsprechenden Expert-System verwenden möchten, es geht im Wesentlichen um das Grundprinzip, dem die Erstellung eines Handelssystems folgt.


1. Grundsätze bei der Auslegung von Handelssystemen sowie die Arten von Ereignisquellen

Der grundlegende mehrheitlich angewandte Ansatz zur Aufstellung des Algorithmus‘ besteht in der Verfolgung der Entwicklung einer Position vom Augenblick ihrer Eröffnung bis zu ihrer Schließung. Das ist ein linearer Ansatz. Und wenn im Code Änderungen vorgenommen werden müssen, führt das häufig zu erheblichen Komplikationen, da eine große Zahl von Bedingungen auf den Plan tritt und der Code um neue Analysezweige erweitert wird.

Die beste Lösung zur Entwicklung eines Modells für ein automatisches Handelssystem besteht darin, „auf Zustände zu reagieren“. Dabei ist das Grundprinzip die Analyse nicht der Umstände, die zu dem jeweiligen Zustand des Expert-Systems und seiner Positionen und Aufträge geführt haben, sondern der dessen, was mit ihnen jetzt zu tun ist. Dieses Grundprinzip ändert die Verwaltung von Handelsvorgängen von Grund auf und vereinfacht die Code-Entwicklung.

Wir werden das noch ausführlicher behandeln.

1.1. Der Grundsatz, „auf Zustände zu reagieren“

Wie bereits erwähnt muss das Expert-System nicht wissen, wie es zu einem bestimmten Zustand gekommen ist. Es muss allerdings wissen, wie es entsprechend seiner Umgebungsbedingungen (Parameterwerten, gespeicherten Auftragseigenschaften usw.) mit diesem Zustand zu verfahren hat.

Dieser Grundsatz ist unmittelbar damit verbunden, dass das Expert-System von Zyklus zu Zyklus (meistens von Kursänderung zu Kursänderung) lebt und sich nicht darum kümmern muss, was mit den Aufträgen im Moment der vorherigen Kursänderung passiert ist. Folglich müssen wir bei der Auftragsverwaltung einen ereignisbezogenen Ansatz verfolgen. Das heißt, das Expert-System speichert seinen Zustand zum Zeitpunkt der jeweils aktuellen Kursänderung, und dieser Zustand wird zum Ausgangspunkt für die Entscheidung bei der nächsten Kursänderung.

Wir müssen zum Beispiel alle ausstehenden Aufträge des Expert-Systems löschen und können erst danach mit der Analyse der Indikatoren und der Platzierung neuer Aufträge fortfahren. Der Großteil der Codebeispiele, die wir gesehen haben, verwenden einen periodischen Durchlauf des Typs „while (true) {Versuch zu löschen}“ oder etwas weniger streng „while (k < 1000) {Versuch zu löschen; k++;}“. Wir überspringen die Variante, in der ein einmaliger Aufruf des Löschbefehls ohne Fehleranalyse erfolgt.

Dieses Vorgehen ist linear, dabei „hängt“ das Expert-System für unbestimmte Zeit.

Aus diesem Grund wird es weniger richtig sein, das Expert-System in einen Kreislauf zu versetzen, als den Befehl zur Löschung der Aufträge zu speichern, um diesen ihn anhand des Versuchs, einen ausstehenden Auftrag zu löschen, bei jeder neuen Kursänderung zu überprüfen. In diesem Fall weiß das Expert-System beim Ablesen der Zustandsparameter, dass es die Aufträge in diesem Augenblick löschen muss. Und es wird versuchen sie zu löschen. Kommt es zu einem Handelsfehler, setzt das Expert-System die weitere Analyse und Arbeit einfach bis zum nächsten Zyklus aus.

1.2. Das zweite Grundprinzip bei der Auslegung ist die größtmögliche Abstraktion von der betrachteten Richtung der Position (Kaufen/Verkaufen), der Währung und dem Diagramm. Alle Funktionen des automatischen Handelssystems müssen so umgesetzt werden, dass die erkennbare Analyse der geprüften Richtung oder des betrachteten Kürzels nur in den seltenen Fällen erfolgt, in denen dies tatsächlich unvermeidlich ist (z. B. bei der Betrachtung einer günstigen Entwicklung des Preises für eine offene Position, obwohl auch dabei Möglichkeiten zum Verzicht auf exakte Details bestehen). Versuchen Sie stets, eine derart unterklassige Auslegung zu vermeiden. Dadurch werden der Programmcode und der Programmiervorgang der Funktionen selbst mindestens halbiert. Und beide werden „unabhängig vom Handel“. 

Die Umsetzung dieses Grundsatzes besteht in der Ersetzung der erkennbaren Analyse der Art der Aufträge, der Parameter des betreffenden Kürzels sowie der davon abhängigen Rechenparameter durch Makrofunktionen. Im weiteren Verlauf dieses Beitrags werden wir ihre Umsetzung ausführlich betrachten.

1.3. Dritter Grundsatz: Die Zerlegung des Algorithmus‘ in logische Lexeme (eigenständige Module)

In der Praxis können wir sagen, dass der beste Ansatz in der Aufgliederung der Operationen des Expert-Systems in einzelne Funktionen besteht. Ich denke, wir sind uns einig, dass es schwierig ist, den gesamten Algorithmus für das Expert-System in eine einzige Funktion zu schreiben, und dass die anschließende Analyse und Bearbeitung dadurch erschwert würden. Ebenso wenig sollten wir dies in MQL5 tun, wo wir jetzt praktisch die volle Kontrolle über unsere aktuelle Umgebung haben.

Deshalb müssen die logischen Lexeme (z. B. die Eröffnung, Nachverfolgung (Trailing) und Schließung von Aufträgen) voneinander getrennt umgesetzt werden mit einer vollständigen Analyse der Umgebungsparameter und -ereignisse. Dieser Ansatz macht das Expert-System anpassungsfähig in seiner Entwicklung. Ihm können leicht neue eigenständige Module hinzugefügt werden, ohne die bereits angelegten zu berühren, bzw. vorhandene Module können ohne Änderung des Hauptcodes entfernt werden.

Diese drei Grundsätze ermöglichen die Schaffung eines einheitlichen, leicht zu modifizierenden und auf jede konkrete Aufgabe einstellbaren Prototypen für alle automatischen Handelssysteme.

Ereignisquellen für automatische Handelssysteme sind:


1. Indikatoren. Ein Beispiel dafür ist die Analyse der Werte der Indikatorlinien, ihrer Überschneidungen, Kombinationen usw. Solche Indikatoren können sein: die aktuelle Zeit, aus dem Internet bezogene Daten usf. In der Mehrzahl der Fälle dienen Indikatorereignisse als Signale für die Eröffnung oder Schließung von Aufträgen. Seltener zu deren Korrektur (für gewöhnlich eines Trailing-Stop-Loss oder eines ausstehenden Auftrages bei dem jeweiligen Indikator).  

Zum Beispiel kann die Umsetzung der Arbeit mit Indikatoren als ein Expert-System bezeichnet werden, das die Überschneidung des schnellen und des langsameren MA analysiert und anschließend eine der Richtung der Überschneidung entsprechende Position eröffnet. 

2. Bestehende Aufträge, Positionen und deren Zustand. Zum Beispiel der aktuelle Verlust oder der Umgang des Gewinns, das Vorliegen/Nichtvorhandensein von Positionen oder ausstehenden Aufträgen, der Gewinn geschlossener Positionen usw. Die praktische Umsetzung dieser Ereignisse ist wesentlich umfassender und vielschichtiger, da es für ihre wechselseitigen Beziehungen erheblich mehr Varianten gibt als für die Indikatorereignisse.

Das elementarste Beispiel für ein ausschließlich auf einem Handelsereignis beruhendes Expert-System ist das Nachschießen zur Mittelung einer vorhandenen Position und ihre Überführung zum gewünschten Gewinn. Das heißt, bei Vorliegen eines Verlusts in einer vorhandenen Position ist das auslösende Ereignis für die Platzierung eines neuen Mittelungsauftrages.

Oder nehmen wir das Beispiel für ein Trailing-Stop-Loss. Bei dieser Funktion wird das Ereignis geprüft, wenn der Preis sich um eine vorgegebene Punktezahl von dem vorherigen Stop-Loss-Zustand in Richtung Gewinn wegbewegt. Im Ergebnis zieht das Expert-System den Stop-Loss hinter den Preis.

3. Externe Ereignisse. Obwohl in einem reinen automatischen Handelssystem ein solches Ereignis für gewöhnlich nicht vorkommt, sollte es generell für die Entscheidungsfindung berücksichtigt werden. Dazu gehört die Korrektur von Aufträgen und Positionen durch den Benutzer sowie die Bearbeitung von Handelsfehlern und von Diagrammereignissen (Das Verschieben/Anlegen/Löschen von Objekten, die Betätigung von Schaltflächen usw.) Im Großen und Ganzen handelt es sich dabei um Ereignisse, die sich der historischen Überprüfung entziehen und lediglich auftreten, während das Expert-System arbeitet.

Ein aussagekräftiges Beispiel für derartige Expert-Systeme sind Handelsinformationssysteme mit einer grafischen Handelssteuerung.

All die unterschiedlichen automatischen Handelssysteme beruhen auf einer Kombination aus diesen drei Ereignisquellen.

2. Die Basisklasse CExpertAdvisor – der Systemerbauer

Wie sieht die Arbeit eines automatischen Handelssystems, eines Expert Advisors, aus? Eine allgemeine Darstellung der Wechselbeziehungen in einem MQL-Programm liefert die folgende Abbildung.

Abbildung 1. Eine allgemeine Darstellung der Wechselbeziehungen zwischen den Bestandteilen eines MQL-Programms

Abbildung 1. Eine allgemeine Darstellung der Wechselbeziehungen zwischen den Bestandteilen eines MQL-Programms

Wie aus der Abbildung ersichtlich wird, steht im Vordergrund der Eintritt in den Verarbeitungszyklus (dieser kann durch eine Kursänderung oder ein Timersignal ausgelöst werden). In diesem Stadium besteht im ersten Programmblock die Möglichkeit, diese Kursänderung herauszufiltern ohne sie zu verarbeiten. Das geschieht in den Fällen, in denen die Arbeit des Expert-Systems nicht bei jeder Kursänderung sondern lediglich bei Auftreten eines neuen Balkens erforderlich ist, oder wenn das Expert-System schlicht nicht arbeiten darf.

Anschließend geht die Programmausführung zum zweiten Block, zu den Modulen für die Arbeit mit Aufträgen und Positionen, über, und erst danach werden die Blöcke zur Verarbeitung von Ereignissen aus den Modulen aufgerufen. Jedes Modul kann nur das es betreffende Ereignis abrufen.  

Diese Abfolge kann als ein Modell mit direkter Logik bezeichnet werden, da in ihr zuerst festgelegt wird, WAS das Expert-System tut (welche Module zur Verarbeitung von Ereignissen verwendet werden), und erst danach, WIE und WARUM es das tut (Empfang der Ereignissignale).

Die direkte Logik deckt sich mit unserer Wahrnehmung der Welt und dem gesunden Menschenverstand. Denn der Mensch denkt zunächst in konkreten Begriffen, die er dann verallgemeinert und erst danach systematisch ordnet und wechselseitige Beziehungen herstellt.

Die Entwicklung von Expert-Systemen bildet in dieser Hinsicht keine Ausnahme. Zunächst wird angegeben, was ein Expert-System tun soll (Positionen eröffnen und schließen, Stop-Loss-Grenzen ziehen), und erst dann wird dargestellt, bei welchen Ereignissen und wie es das tun soll. In gar keinem Fall jedoch umgekehrt: Erst ein Signal empfangen und dann darüber nachdenken, wo und wie es verarbeitet wird. Das ist die umgekehrte Logik, und es ist besser, sie nicht zu nutzen, weil das Ergebnis ein platzraubender Code mit einer immensen Zahl von Bedingungszweigen sein wird.

Es folgt ein Beispiel für umgekehrte und direkte Logik. Wir nehmen dazu die Eröffnung/Schließung durch das RSI-Signal.

Wenn Sie das Expert-System jetzt etwas komplizierter gestalten möchten, wird Ihnen das in der zweiten Variante wesentlich leichter fallen als in der ersten. Es reicht, ein neues Modul zur Verarbeitung von Ereignissen anzulegen.

In der ersten Variante muss der Aufbau der Signalverarbeitung überarbeitet oder in eine eigene Funktion eingefügt werden.

Empfehlung: Beginnen Sie bei der Beschreibung eines Handelssystems nicht mit den Worten: „1. Signaleingang... Auftrag eröffnen“, sondern unterteilen Sie ihn sofort in Abschnitte: „a) Die Bedingung für die Eröffnung von Aufträgen; b) Die Bedingungen für die Begleitung von Aufträgen...“, wobei in jedem bereits die Analyse der erforderlichen Signale zu erfolgen hat.

Zum besseren Verständnis dieses Ansatzes führen wir unterschiedliche Arbeitsabläufe im Kontext vier unterschiedlicher automatischer Handelssysteme vor.

Abbildung 2. Beispiele für die Umsetzung automatischer Handelssysteme

Abbildung 2. Beispiele für die Umsetzung automatischer Handelssysteme

a). Ein lediglich auf den Signalen irgendeines Indikators beruhendes Expert-System. Es kann Positionen bei einer Signaländerung eröffnen und schließen. Beispiel: ein MA-Expert-System.
b). Ein Expert-System mit grafischer Handelssteuerung.
c). Ein auf Indikatoren beruhendes Expert-System, jedoch bereits mit Trailing-Stop-Loss und Laufzeitvorgabe. Beispiel - Scalping aufgrund von Nachrichten mit Eröffnung einer Position in den Trend nach dem Indikator MA.
d). Ein indikatorloses Expert-System mit Positionsmittelung, das die Positionsparameter nur einmal bei Eröffnung eines neuen Balkens prüft. Beispiel: ein mittelndes Expert-System.

Wie aus den Abläufen ersichtlich wird, lässt sich jedes Handelssystem mithilfe der direkten Logik ganz einfach darstellen.


3. Umsetzung der Klasse Expert Advisor

Wir legen eine Klasse entsprechend allen vorgenannten Regeln und Vorgaben an, sie wird als Grundlage für alle weiteren Expert-Systeme dienen.

Der Mindestumfang der Funktionen, die die Klasse CExpertAdvisor aufweisen muss, sieht folgendermaßen aus:

1. Bereitstellung

  • Eintragung der Indikatoren;
  • Einstellung der Anfangswerte der Parameter;
  • Ausrichtung auf das erforderliche Kürzel und den Zeitrahmen.

2. Signalempfangsfunktionen

  • Zulässige Laufzeit (Handelsintervalle);
  • Festlegung des Signals zum Eröffnen/Schließen von Positionen oder Aufträgen;
  • Festlegung des Filters (Trend, Zeit usw.);  
  • Start/Stopp-Zeitgeber (Timer).

3. Zusatzfunktionen

  • Berechnung des Eröffnungspreises, der SL- und TP-Ebenen und des Auftragsumfangs;
  • Verschicken von Handelsanfragen (Eröffnung, Schließung, Änderung).

4. Handelsmodule

  • Verarbeitung der Signale und Filter;
  • Überwachung der Positionen und Aufträge;
  • Arbeit in den Expert-System-Funktionen: OnTrade(), OnTimer(), OnTester() und OnChartEvent().

5. Bereinigung

  • Ausgabe von Meldungen und Berichten;
  • Entleeren des Diagramms, Austragen der Indikatoren.

Alle Funktionen der Klasse verteilen sich auf drei Gruppen. Die allgemeine Darstellung der Verschachtelung der Funktionen sowie ihre Beschreibungen werden nachfolgend vorgestellt:

Abbildung 3. Verschachtelung der Funktionen eines Expert-Systems

Abbildung 3. Verschachtelung der Funktionen eines Expert-Systems

1. Makrofunktionen 

Diese kleine Gruppe von Funktionen bildet die Grundlage für die Arbeit mit Auftragsarten, Parametern von Kürzeln und Kurswerten zur Platzierung von Aufträgen (Eröffnung und Stopp). Diese Makrofunktionen erfüllen in vollem Umfang den zweiten Auslegungsgrundsatz, die Abstraktion. Sie arbeiten im Kontext des Kürzels, an dem auch das Expert-System selbst arbeitet.

Makrofunktionen zur Umwandlung der Arten arbeiten mit dem Begriff der Marktrichtung: Kaufen oder Verkaufen. Deshalb ist es besser die vorhandenen Konstanten, ORDER_TYPE_BUY und ORDER_TYPE_SELL, zu verwenden, als eigene anzulegen. Es folgen einige Beispiele für die Verwendung von Makrofunktionen sowie für die Ergebnisse ihrer Arbeit.

   //--- Type conversion macro
   long       BaseType(long dir);        // returns the base type of order for specified direction
   long       ReversType(long dir);      // returns the reverse type of order for specified direction
   long       StopType(long dir);        // returns the stop-order type for specified direction
   long       LimitType(long dir);       // returns the limit-order type for specified direction

   //--- Normalization macro
   double     BasePrice(long dir);       // returns Bid/Ask price for specified direction
   double     ReversPrice(long dir);     // returns Bid/Ask price for reverse direction

   long dir,newdir;
   dir=ORDER_TYPE_BUY;
   newdir=ReversType(dir);               // newdir=ORDER_TYPE_SELL
   newdir=StopType(dir);                 // newdir=ORDER_TYPE_BUY_STOP
   newdir=LimitType(dir);                // newdir=ORDER_TYPE_BUY_LIMIT
   newdir=BaseType(newdir);              // newdir=ORDER_TYPE_BUY

   double price;
   price=BasePrice(dir);                 // price=Ask
   price=ReversPrice(dir);               // price=Bid

Bei der Entwicklung von Expert-Systemen ermöglichen die Makrofunktionen den Verzicht auf die Angabe der Verarbeitungsrichtung und verhelfen Ihnen zu einem kürzeren Code.

2. Zusatzfunktionen

Diese Funktionen wurden für die Arbeit mit Aufträgen und Positionen konzipiert. Sie weisen wie die Mikrofunktionen eine niedrige Ebene auf. Unter gewissen Bedingungen können sie in zwei Kategorien unterteilt werden: Informationsfunktionen und ausführende Funktionen. Sie führen alle jeweils nur eine Handlung aus, ohne irgendwelche Ereignisse zu analysieren. Sie führen die Anweisungen übergeordneter Routinen des Expert-Systems aus.

Beispiele für Informationsfunktionen: Ermittlung des höchsten Eröffnungskurses der aktuellen ausstehenden Aufträge; Ermittlung der Art des Schlusses der entsprechenden Position, mit Gewinn oder Verlust; Abfrage der Anzahl und der Aufstellung der Händlerzettel für die Aufträge des Expert-Systems usw.

Beispiele für ausführende Funktionen: Schließen der angegebenen Aufträge; Ändern der Stop Loss-Grenzen in der angegebenen Position usw.

Diese Gruppe ist die größte. Es handelt sich genau um den Funktionsbestand, auf dem die gesamte Routinearbeit eines Expert-Systems beruht. Eine große Anzahl von Beispielen für diese Funktionen bietet das Forum unter: https://www.mql5.com/ru/forum/107476. Daneben enthält jedoch auch die Standardbibliothek von MQL5 bereits Klassen, die einen Teil der Arbeit zur Verwaltung von Aufträgen und Positionen übernehmen und zwar insbesondere die Klasse CTrade.

Allerdings wird jede Ihrer Aufgaben die Vornahme neuer Umsetzungen erfordern oder zumindest eine geringfügige Anpassung der vorhandenen.

3. Ereignisverarbeitende Module

Bei der Gruppe dieser Funktionen handelt es sich bereits über einen höheren Überbau oberhalb der beiden ersten Gruppen. Wie bereits gesagt sind es die einsatzfertigen Blöcke, aus denen sich unser Expert-System zusammensetzt. Im Allgemeinen gehören insbesondere sie zu den ereignisverarbeitenden Funktionen eines MQL-Programms: OnStart(), OnTick(), OnTimer(), OnTrade() und OnChartEvent(). Diese Gruppe ist nicht sehr groß, und der Inhalt der vorhandenen Module kann nach Bedarf angepasst werden. Aber grundsätzlich sind keine Änderungen erforderlich.  

Innerhalb der Module muss alles abstrakt angelegt sein (in Erfüllung des zweiten Auslegungsgrundsatzes), damit ein und dasselbe Modul sowohl für Kaufaufträge als auch für Verkäufe aufgerufen werden kann. Das erreichen wir selbstverständlich mithilfe Makrofunktionen.

Also, kommen wir zur Umsetzung!

1. Bereitstellung, Bereinigung

class CExpertAdvisor
  {
protected:
   bool              m_bInit;       // flag of correct initialization
   ulong             m_magic;       // magic number of expert
   string              m_smb;       // symbol, on which expert works
   ENUM_TIMEFRAMES      m_tf;       // working timeframe
   CSymbolInfo      m_smbinf;       // symbol parameters
   int               m_timer;       // time for timer

public:
   double              m_pnt;       // consider 5/3 digit quotes for stops
   CTrade            m_trade;       // object to execute trade orders
   string              m_inf;       // comment string for information about expert's work

Hierbei handelt es sich um die für die Verwendung der Funktionen des Expert-Systems erforderliche Mindestausstattung mit Parametern.

Die Parameter m_smb und m_tf wurden eigens in die Eigenschaften des Expert-Systems eingetragen, dem Expert-System ohne weiteren Aufwand anzuzeigen, in welcher Währung und welchem Zeitraum es arbeiten soll. Wenn m_smb beispielsweise USDJPY zugeschrieben wird, nimmt das Expert-System die Arbeit zu diesem Kürzel unabhängig davon auf, für welches Kürzel er gestartet wurde. Setzen wir tf = PERIOD_H1, dann werden alle Signale und Indikatoranalysen in dem Stundendiagramm (H1) abgebildet. 

Die Methoden der Klasse gehen noch weiter. Die ersten drei Methoden beziehen sich auf die Bereitstellung und die Bereinigung des Expert-Systems.

public:
   //--- Initialization
   void              CExpertAdvisor();                               // constructor
   void             ~CExpertAdvisor();                               // destructor
   virtual bool      Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization

Der Konstruktor und der Destruktor in der Basisklasse tun nichts.

Die Methode Init() führt die anfängliche Bereitstellung der Parameter des Expert-Systems anhand des Kürzels, des Zeitrahmens und der Magischen Zahl aus.

//------------------------------------------------------------------ CExpertAdvisor
void CExpertAdvisor::CExpertAdvisor()
  {
   m_bInit=false;
  }
//------------------------------------------------------------------ ~CExpertAdvisor
void CExpertAdvisor::~CExpertAdvisor()
  {
  }
//------------------------------------------------------------------ Init
bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf)
  {
   m_magic=magic; m_smb=smb; m_tf=tf;         // set initializing parameters
   m_smbinf.Name(m_smb);                      // initialize symbol
   m_pnt=m_smbinf.Point();                    // calculate multiplier for 5/3 digit quote
   if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10;  
   m_trade.SetExpertMagicNumber(m_magic);     // set magic number for expert

   m_bInit=true; return(true);                // trade allowed
  }

2. Signalempfangsfunktionen

Diese Funktionen analysieren den Markt und die Indikatoren.

   bool              CheckNewBar();                          // check for new bar
   bool              CheckTime(datetime start,datetime end); // check allowed trade time
   virtual long      CheckSignal(bool bEntry);               // check signal
   virtual bool      CheckFilter(long dir);                  // check filter for direction

Die ersten beiden Funktionen verfügen über eine recht konkrete Umsetzung und können in den Ablegern dieser Klasse weiter verwendet werden.

//------------------------------------------------------------------ CheckNewBar
bool CExpertAdvisor::CheckNewBar()          // function of checking new bar
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)      // copy bar
     { Print("CopyRates of ",m_smb," failed, no history"); return(false); }
   if(rt[1].tick_volume>1) return(false);   // check volume 
   return(true);
  }
//---------------------------------------------------------------   CheckTime
bool CExpertAdvisor::CheckTime(datetime start,datetime end)
  {
   datetime dt=TimeCurrent();                          // current time
   if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range
   if(start>=end) if(dt>=start|| dt<end) return(true);
   return(false);
  }

Das zweite Paar hängt stets von den jeweils verwendeten Indikatoren ab. Diese Funktionen für alle Fälle einzurichten, ist schlicht unmöglich.

Das Wichtigste ist, zu verstehen, dass die Signalfunktionen CheckSignal() und CheckFilter() absolut jeden Indikator und sämtliche Indikatorkombinationen analysieren können! Das heißt, Handelsmodule, in die diese Indikatoren später eingehen, sind unabhängig von den eigentlichen Quellen.

Das ermöglicht die Verwendung eines einmal programmierten Expert-Systems als Muster oder Template für weitere nach ähnlichen Grundsätzen arbeitende Systeme. Es reicht, einfach die zu analysierenden Indikatoren zu ändern oder neue Filterbedingungen hinzufügen.

3. Zusatzfunktionen

Wie bereits gesagt handelt es sich bei dieser Gruppe von Funktionen um die umfangreichste. Für unsere, in diesem Beitrag betrachteten Praxisbeispiele genügt es, vier dieser Funktionen umzusetzen:

   double         CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk
   ulong          DealOpen(long dir,double lot,int SL,int TP);     // execute deal with specified parameter
   ulong          GetDealByOrder(ulong order);                     // get deal ticket by order ticket
   double         CountProfitByDeal(ulong ticket);                 // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk
double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk
  {
   if(dist==0 || risk==0) return(lot);
   m_smbinf.Refresh();
   return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue())));
  }
//------------------------------------------------------------------ DealOpen
ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP)
  {
   double op,sl,tp,apr,StopLvl;
   // determine price parameters
   m_smbinf.RefreshRates(); m_smbinf.Refresh();
   StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level
   apr     = ReversPrice(dir); 
   op      = BasePrice(dir);                         // open price
   sl      = NormalSL(dir, op, apr, SL, StopLvl);    // stop loss
   tp      = NormalTP(dir, op, apr, TP, StopLvl);    // take profit

   // open position
   m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp);
   ulong order = m_trade.ResultOrder(); 
   if(order<=0) return(0);                           // order ticket
   return(GetDealByOrder(order));                    // return deal ticket
  }
//------------------------------------------------------------------ GetDealByOrder
ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket
  {
   PositionSelect(m_smb);
   HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER));
   uint total=HistoryDealsTotal();
   for(uint i=0; i<total; i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(order==HistoryDealGetInteger(deal,DEAL_ORDER))
         return(deal);                            // remember deal ticket
     }
   return(0);
  }
//------------------------------------------------------------------ CountProfit
double CExpertAdvisor::CountProfitByDeal(ulong ticket)  // position profit by deal ticket
  {
   CDealInfo deal; deal.Ticket(ticket);                 // deal ticket
   HistorySelect(deal.Time(),TimeCurrent());            // select all deals after this
   uint total  = HistoryDealsTotal();
   long pos_id = deal.PositionId();                     // get position id
   double prof = 0;
   for(uint i=0; i<total; i++)                          // find all deals with this id
     {
      ticket = HistoryDealGetTicket(i);
         if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue;
      prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit
     }
   return(prof);                                        // return profit
  }

4. Handelsmodule

Zu guter Letzt verschmilzt diese Gruppe von Funktionen den gesamten Handelsvorgang durch die Verarbeitung der Signale und Ereignisse sowie durch die Verwendung von Zusatz- und der Makrofunktionen. Die logischen Lexeme der Handelsoperationen sind nicht sehr zahlreich und hängen von den jeweiligen Aufgaben ab. Dennoch lassen sich einige allgemeine Begriffe ausmachen, die in fast allen Expert-Systemen anzutreffen sind.

   virtual bool      Main();                            // main module controlling trade process
   virtual void      OpenPosition(long dir);            // module of opening position
   virtual void      CheckPosition(long dir);           // check position and open additional ones
   virtual void      ClosePosition(long dir);           // close position
   virtual void      BEPosition(long dir,int BE);       // moving Stop Loss to break-even
   virtual void      TrailingPosition(long dir,int TS); // trailing position of Stop Loss
   virtual void      OpenPending(long dir);             // module of opening pending orders
   virtual void      CheckPending(long dir);            // work with current orders and open additional ones
   virtual void      TrailingPending(long dir);         // move pending orders
   virtual void      DeletePending(long dir);           // delete pending orders

In den folgenden Beispielen betrachten wir einige ausführlichere Umsetzungen dieser Funktionen:

Das Hinzufügen neuer Funktionen ist keine besondere Leistung, da wir von Anfang an den richtigen Weg eingeschlagen und den Grundaufbau des Expert-Systems angelegt haben. Wenn Sie genau dieses Modell verwenden, erfordert die Auslegung einen minimalen Arbeits- und Zeitaufwand, und der Code wird auch nach einem Jahr noch leicht zu lesen sein.

Ihre Expert-Systeme sind selbstredend nicht nur darauf beschränkt. In der Klasse CExpertAdvisor haben wir lediglich die wichtigsten Methoden deklariert. In abgeleiteten oder Kindklassen können neue Routinen hinzugefügt und vorhandene ausgetauscht sowie eigene Module erweitert werden, um so eine eigene einheitliche Bibliothek anzulegen. Bei Vorhandensein einer solchen Bibliothek benötigen Sie zur Auslegung eines einsatzbereiten Expert-Systems nur noch eine halben Stunde bis zu zwei Tagen.


4. Anwendungsbeispiel für die Klasse CExpertAdvisor

4.1. Beispiel für die Arbeit auf Grundlage der Indikatorsignale

Beginnen wir als erstes Beispiel mit der einfachsten Aufgabe, der Betrachtung des Expert Advisors MovingAverage (das grundlegende Beispiel aus MetaTrader 5) unter Verwendung der Klasse CExpertAdvisor. Wir gestalten lediglich seinen Funktionsumfang etwas komplizierter.

Algorithmus:

a) Bedingung für die Eröffnung einer Position

  • Wenn der Preis den MA von unten nach oben schneidet, dann wird eine Kaufposition eröffnet.
  • Wenn der Preis den MA von oben nach unten schneidet, dann wird eine Verkaufsposition eröffnet.
  • Festlegung von SL (Stop Loss) und TP (TakeProfit).
  • Der Posten der Position wird anhand des Parameters "Risk" berechnet - wie hoch ist der Einlageverlust bei der Auslösung von Stop Loss.

b) Bedingung für das Schließen einer Position

  • Wenn der Preis den MA von unten nach oben schneidet, dann wird die Verkaufsposition geschlossen.
  • Wenn der Preis den MA von oben nach unten schneidet, dann wird die Kaufposition geschlossen.

c) Beschränkungen

  • Die tägliche Laufzeit des Expert-Systems wird beschränkt auf HourStart bis HourEnd.
  • Das Expert-System führt lediglich bei Auftreten neuer Balken Handelsoperationen aus.

d) Positionspflege

  • Verwendung eines einfachen Trailing Stops im Abstand zu TS.

Zum Betrieb unseres Expert-Systems benötigen wir sieben Funktionen aus der Klasse CExpertAdvisor:

Die Funktion CheckSignal() und die Module sind in der abgeleiteten Klasse zur Lösung der dieser gestellten Aufgabe festzulegen. Darüber hinaus muss die Bereitstellung des Indikators hinzugefügt werden.

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"

input double Risk      = 0.1; // Risk
input int    SL        = 100; // Stop Loss distance
input int    TP        = 100; // Take Profit distance
input int    TS        =  30; // Trailing Stop distance
input int    pMA       =  12; // Moving Average period
input int    HourStart =   7; // Hour of trade start
input int    HourEnd   =  20// Hour of trade end
//---
class CMyEA : public CExpertAdvisor
  {
protected:
   double            m_risk;          // size of risk
   int               m_sl;            // Stop Loss
   int               m_tp;            // Take Profit
   int               m_ts;            // Trailing Stop
   int               m_pMA;           // MA period
   int               m_hourStart;     // Hour of trade start
   int               m_hourEnd;       // Hour of trade end
   int               m_hma;           // MA indicator
public:
   void              CMyEA();
   void             ~CMyEA();
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual bool      Main();                              // main function
   virtual void      OpenPosition(long dir);              // open position on signal
   virtual void      ClosePosition(long dir);             // close position on signal
   virtual long      CheckSignal(bool bEntry);            // check signal
  };
//------------------------------------------------------------------ CMyEA
void CMyEA::CMyEA() { }
//----------------------------------------------------------------- ~CMyEA
void CMyEA::~CMyEA()
  {
   IndicatorRelease(m_hma); // delete MA indicator
  }
//------------------------------------------------------------------ Init
bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false);  // initialize parent class

   m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA;  // copy parameters
   m_hourStart=HourStart; m_hourEnd=HourEnd;

   m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator
   if(m_hma==INVALID_HANDLE) return(false);            // if there is an error, then exit
   m_bInit=true; return(true);                         // trade allowed
  }
//------------------------------------------------------------------ Main
bool CMyEA::Main()                            // main function
  {
   if(!CExpertAdvisor::Main()) return(false); // call function of parent class

   if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars
   
   if(!CheckNewBar()) return(true);           // check new bar

   // check each direction
   long dir;
   dir=ORDER_TYPE_BUY;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);
   dir=ORDER_TYPE_SELL;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);

   return(true);
  }
//------------------------------------------------------------------ OpenPos
void CMyEA::OpenPosition(long dir)
  {
   if(PositionSelect(m_smb)) return;     // if there is an order, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00"))) return;
   if(dir!=CheckSignal(true)) return;    // if there is no signal for current direction
   double lot=CountLotByRisk(m_sl,m_risk,0);
   if(lot<=0) return;                    // if lot is not defined then exit
   DealOpen(dir,lot,m_sl,m_tp);          // open position
  }
//------------------------------------------------------------------ ClosePos
void CMyEA::ClosePosition(long dir)
  {
   if(!PositionSelect(m_smb)) return;                 // if there is no position, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00")))
     { m_trade.PositionClose(m_smb); return; }        // if it's not time for trade, then close orders
   if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction
   if(dir!=CheckSignal(false)) return;                // if the close signal didn't match the current position
   m_trade.PositionClose(m_smb,1);                    // close position
  }
//------------------------------------------------------------------ CheckSignal
long CMyEA::CheckSignal(bool bEntry)
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)
     { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); }

   double ma[1];
   if(CopyBuffer(m_hma,0,0,1,ma)!=1)
     { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); }

   if(rt[0].open<ma[0] && rt[0].close>ma[0])
      return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell

   return(WRONG_VALUE);                                // if there is no signal
  }

CMyEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.Main();                  // process incoming tick
  }

Lassen Sie uns jetzt den Aufbau der Funktion Main() untersuchen. Üblicherweise ist sie zweigeteilt.

Im ersten Teil wird die übergeordnete Funktion aufgerufen. In dieser „Elternfunktion“ werden die Parameter bearbeitet, die allgemeine Auswirkungen auf die Arbeit des Expert-Systems haben können. Dazu gehört die Überprüfung der Handelsbefugnis des Expert-Systems sowie der Gültigkeit der Verlaufsdaten.

Im zweiten Teil erfolgt die unmittelbare Verarbeitung der Marktereignisse.

Der FilterCheckNewBar(), Prüfung auf Vorliegen eines neuen Balkens, wird überprüft. Anschließend werden nacheinander die Module für die zwei Handelsrichtungen aufgerufen.

In den Modulen ist alles recht abstrakt angeordnet (der zweite Auslegungsgrundsatz). Es erfolgt kein unmittelbarer Aufruf der Eigenschaften eines Kürzels. Und die drei Module, OpenPosition(), ClosePosition() und TrailingPosition(), operieren nur mit den Parametern, die von außen zu ihnen gelangen. Das ermöglicht aber auch, diese Module sowohl zur Überprüfung von Kauf- als auch von Verkaufsaufträgen aufzurufen. 


4.2. Anwendungsbeispiel für den CExpertAdvisor, ein indikatorloses Expert-System mit Zustands- und Ergebnisanalyse für die Position

Zur Veranschaulichung ziehen wir ein System heran, das nur bei Kursrückgängen der Position handelt, wobei es den Posten nach einem Verlust erhöht (Expert-Systeme dieser Art werden üblicherweise als „Martingale“ bezeichnet).

a) Platzierung des Anfangsauftrags

b) Eröffnen von Folgepositionen

Zum Betrieb unseres Expert-Systems sind drei Funktionen aus der Klasse CExpertAdvisor erforderlich:

Da das Expert-System keine Indikatoren analysiert, sondern lediglich die Abschlussergebnisse, wird es für die Leistungsfähigkeit das Beste sein, das Ereignis OnTrade() zu verwenden. Das bedeutet, dass das Expert-System nach Platzierung des ersten Eröffnungskaufauftrages alle Folgeaufträge erst nach Schließung dieser Position platzieren wird. Deshalb erfolgt die Eröffnung des Anfangsauftrages in der Routine OnTick() und die weitere Arbeit in der Routine OnTrade() des Expert-Systems.

Die Funktion Init() stellt wie üblich nur die Klassenparameter mit den äußeren Parametern des Expert-Systems bereit.

Das Modul OpenPosition() eröffnet die Anfangsposition und wird mit der Markierung m_first gesperrt.

Das Modul CheckPosition() überwacht die Position auf weitere Kursrückgänge.

Folgende Module werden von den entsprechenden Funktionen des Expert-Systems aufgerufen: OnTick() und OnTrade().

//+------------------------------------------------------------------+
//|                                                       eMarti.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"
#include <Trade\DealInfo.mqh>

input double Lots    = 0.1; // Lot
input double LotKoef = 2;   // lot multiplier for loss
input int    Dist    = 60;  // distance to Stop Loss and Take Profit
//---
class CMartiEA : public CExpertAdvisor
  {
protected:
   double            m_lots;       // Lot
   double            m_lotkoef;    // lot multiplier for loss
   int               m_dist;       // distance to Stop Loss and Take Profit
   CDealInfo         m_deal;       // last deal
   bool              m_first;      // flag of opening the first position
public:
   void              CMartiEA() { }
   void             ~CMartiEA() { }
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual void      OpenPosition();
   virtual void      CheckPosition();
  };
//------------------------------------------------------------------ Init
bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class
   m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist;       // copy parameters
   m_deal.Ticket(0); m_first=true;
   m_bInit=true; return(true);                        // trade allowed
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::OpenPosition()
  {
   if(!CExpertAdvisor::Main()) return;                       // call parent function
   if(!m_first) return;                                      // if already opened initial position
   ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position
   if(deal>0) { m_deal.Ticket(deal); m_first=false; }        // if position exists
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::CheckPosition()
  {
   if(!CExpertAdvisor::Main()) return;           // call parent function
   if(m_first) return;                           // if not yet placed initial position 
   if(PositionSelect(m_smb)) return;             // if position exists

   // check profit of previous position
   double lot=m_lots;                            // initial lot
   long dir=m_deal.Type();                       // previous direction
   if(CountProfitByDeal(m_deal.Ticket())<0)      // if there was loss
     {
      lot=NormalLot(m_lotkoef*m_deal.Volume());  // increase lot
      dir=ReversType(m_deal.Type());             // reverse position
     }
   ulong deal=DealOpen(dir,lot,m_dist,m_dist);   // open position
   if(deal>0) m_deal.Ticket(deal);               // remember ticket
  }

CMartiEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.OpenPosition();          // process tick - open first order
  }
//------------------------------------------------------------------ OnTrade
void OnTrade()
  {
   ea.CheckPosition();         // process trade event
  }


5. Arbeiten mit Ereignissen

In diesem Beitrag haben wir Beispiele für die Verarbeitung der Ereignisse NewTick und Trade kennengelernt, die anhand der Funktionen OnTick() bzw. OnTrade() vorgeführt wurden. In den meisten Fällen kommen genau diese Funktionen ständig zum Einsatz.

Es gibt für Expert-Systeme aber noch vier weitere Funktionen zur Verarbeitung von Ereignissen:

Dabei dürfen wir nicht vergessen, dass es stets zweckdienlich ist, Ereignisse und deren Kombinationen zur Erfüllung einer gestellten Aufgabe zu nutzen.


Nachbetrachtung

Wie wir sehen, erfordert die Programmierung eines Expert-Systems nicht viel Zeit, sofern man ein richtig zusammengestelltes Modell zur Hand hat. Dank der von MQL5 gebotenen neuen Möglichkeiten zur Verarbeitung von Ereignissen erweitert sich die Struktur zur Verwaltung von Handelsvorgängen. Aber dieser ganze Reichtum wird nur dann zu einem wirklich leistungsfähigen Werkzeug, wenn wir unsere Handelsalgorithmen richtig zusammenstellen.

In diesem Beitrag wurden die drei Grundprinzipien für ihre Aufstellung vorgestellt: Ereignisfülle, Abstraktion, Modularität. Sie können sich Ihren Handel erheblich erleichtern, wenn Sie Ihre Expert-Systeme auf diese „drei Pfeiler“ gründen.