Der Prototyp eines automatischen Handelssystems
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.
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.
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
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.
- In der umgekehrten Logik beginnt das Expert-System seine Arbeit mit dem Empfang des Indikatorwerts und prüft anschließend die Richtung des Signals und was mit der Position zu geschehen hat: Kufen eröffnen und Verkaufen schließen oder umgekehrt Verkaufen öffnen und Kaufen schließen. Das heißt, als Eintrittspunkt dient der Empfang und die Analyse des Signals.
- In der direkten Logik verhält sich alles andersherum. Das Expert-System verfügt über zwei Module, Position eröffnen und Position schließen, und es überprüft die Bedingungen für die Umsetzung dieser Module. Das heißt, nach Eintritt in das Eröffnungsmodul empfängt das Expert-System den Indikatorwert und prüft, ob es sich bei diesem um ein Signal zur Eröffnung einer Position handelt. Später, nach Eintritt in das Schließungsmodul, prüft das Expert-System, ob es das Signal zum Schließen der Position ist. Das bedeutet, dass es einen Eintrittspunkt als solchen gar nicht gibt, stattdessen liegen eigenständig arbeitende Module für die Analyse des Systemzustands vor (der erste Auslegungsgrundsatz).
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.
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
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.
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
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 IndikatorsignaleBeginnen 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 Signalfunktion - CheckSignal()
- den Kursänderungsfilter - CheckNewBar()
- den Zeitfilter - CheckTime()
- die Zusatzfunktion zur Eröffnung von Positionen - DealOpen()
- die drei Betriebsmodule - OpenPosition(), ClosePosition() und TrailingPosition()
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
- nach dem Aufrufen eröffnet das Expert-System die erste Kaufposition mit einem Anfangsposten
b) Eröffnen von Folgepositionen
- wenn die vorherige Position mit Gewinn geschlossen wurde, eröffnen wir eine weitere Position in derselben Richtung mit dem Anfangsposten
- wurde die vorherige Position mit Verlust geschlossen, eröffnen wir eine Position in entgegengesetzter Richtung mit einem um den Verhältniswert vergrößerten Posten.
Zum Betrieb unseres Expert-Systems sind drei Funktionen aus der Klasse CExpertAdvisor erforderlich:
- Position eröffnen - DealOpen()
- Abruf des Wertes des Gewinns einer geschlossenen Position gemäß Schlussnote - CountProfitByDeal()
- Betriebsmodule - OpenPosition() und CheckPosition()
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:
-
OnChartEvent ist eine Routine zur Verarbeitung großer Gruppen von Ereignissen: bei der Arbeit mit grafischen Elementen sowie mit Tastatur-, Maus- und benutzerdefinierten Ereignissen. Die Funktion wird beispielsweise zur Erstellung interaktiver Expert-Systeme oder solcher, die nach dem Prinzip der grafischen Auftragsverwaltung aufgebaut sind. Oder einfach zur Erstellung aktiver Steuerelemente für die Parameter des MQL-Programms (unter Verwendung von Schaltflächen und Eingabefeldern). Im Allgemeinen wird diese Funktion zur Verarbeitung externer Ereignisse durch das Expert-System eingesetzt.
-
OnTimer wird bei der Verarbeitung eines Ereignisses des Zeitgebers des Systems aufgerufen. Diese Funktion wird in den Fällen verwendet, in denen das MQL-Programm im Dauerbetrieb Analysen seiner Umgebung und Berechnungen der Indikatorwerte durchführen muss, oder wenn es erforderlich ist, externe Signalquellen ständig im Auge zu behalten, usf. Man könnte sagen, die Funktion OnTimer() ist eine Alternative, um nicht zu sagen der beste Ersatz für Konstrukte der Art:
while(true) { /* Analyse durchführen */; Sleep(1000); }.
Das bedeutet, dass das Expert-System bei seinem Start nicht im Dauerbetrieb arbeiten muss, sondern dass es reicht, die Aufrufe seiner Funktionen aus der Funktion OnTick() nach OnTimer() zu verlegen. - OnBookEvent ist eine Routine zur Verarbeitung von Ereignissen, die bei einer Veränderung der Markttiefe erzeugt werden. Das entsprechende Ereignis kann den externen zugeordnet und seine Verarbeitung entsprechend der gestellten Aufgabe ausgeführt werden.
- OnTester wird nach Abschluss der Prüfung des Expert-Systems in dem vorgegebenen Datenintervall vor der Funktion OnDeinit() aufgerufen, um die Möglichkeit zur Aussonderung von Testgenerationen mithilfe der genetischen Optimierung gemäß dem Parameter Custom max zu eröffnen.
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.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/132
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.