
MQL5 beherrschen, vom Anfänger bis zum Profi (Teil IV): Grundlagen der Entwicklung von Expert Advisors
Einführung
Endlich haben wir die Phase der Erstellung von Expert Advisors (EAs) erreicht. In gewisser Weise haben wir den Rubikon überschritten.
Um diesen Artikel optimal nutzen zu können, sollten Sie mit den folgenden Konzepten bereits vertraut sein:
- Variablen (lokal und global),
- Funktionen und ihre Parameter (sowohl durch Verweis als auch durch Wert),
- Arrays (einschließlich eines grundlegenden Verständnisses der Arrays von Zeitreihen),
- Grundlegende Operatoren, einschließlich logischer, arithmetischer, bedingter („if“, „switch“, „ternary“) und Schleifenoperatoren (vor allem „for“, aber Vertrautheit mit „while“ und „do...while“ ist ebenfalls nützlich).
Aus der Sicht eines Programmierers sind Expert Advisors nicht viel komplexer als Indikatoren, die wir im vorherigen Artikel dieser Serie besprochen haben. Die Handelslogik umfasst ebenfalls die Überprüfung bestimmter Bedingungen und, wenn diese Bedingungen erfüllt sind, die Durchführung einer Aktion (in der Regel das Senden eines Handelsauftrags an den Server). Der Schlüssel liegt darin, die Struktur von Handelsaufträgen zu verstehen, die Funktionen für die Übermittlung dieser Aufträge zu kennen und in der Lage zu sein, auf die für den Handel erforderlichen Daten zuzugreifen.
Wichtig! Alle in diesem Artikel vorgestellten Expert Advisors dienen ausschließlich der Veranschaulichung von Programmierprinzipien und nicht dem realen Handel oder der Gewinnerzielung. Wenn Sie vorhaben, diesen Code auf Live-Konten zu verwenden, müssen Sie wahrscheinlich die Entscheidungsalgorithmen verfeinern. Andernfalls riskieren Sie, Verluste zu erleiden.
In der Tat ist der Code für die hier bereitgestellten EAs nicht für den realen Handel geeignet, auch wenn die Einstiegslogik verbessert wurde. Das erste Beispiel enthält keine Fehlerbehandlung: weder für die Übermittlung der Anfrage noch für die Serverantwort. Dies geschieht absichtlich, um den Code zu vereinfachen, damit er leichter zu verstehen ist, aber es schränkt den EA für die schnelle Erstellung von Prototypen oder das Testen grundlegender Strategielogik ein. Der zweite EA enthält ein wenig mehr Validierung... Aber selbst das reicht nicht aus für die Veröffentlichung auf dem Markt oder für einen zuverlässigen Live-Handel, bei dem Probleme behandelt werden müssen, sobald sie auftreten (wenn sie überhaupt behandelt werden können, anstatt einfach gemeldet und abgeschaltet zu werden).
Ein voll funktionsfähiger EA, der technisch zur Veröffentlichung auf dem Markt zugelassen werden könnte, wird im nächsten Artikel behandelt. Dieser EA enthält die notwendigen Validierungen und eine etwas komplexere Logik als das, was wir hier behandeln werden. In diesem Artikel konzentriere ich mich auf die Grundlagen der Handelsautomatisierung. Wir werden zwei EAs erstellen: einen ohne Indikatoren und einen, der den eingebauten Indikator Gleitender Durchschnitt verwendet. Im ersten Fall wird mit schwebenden Aufträgen gearbeitet, während im zweiten Fall der Handel zum Marktpreis ausgeführt wird.
Expert Advisor Vorlage
Jeder Expert Advisor beginnt mit der Erstellung einer leeren Vorlage, in der Regel mit dem MQL5-Assistenten (Abbildung 1).
Abbildung 1. MQL-Assistent - erster Bildschirm
Der MQL-Assistent bietet zwei Hauptoptionen: die Erstellung eines Expert Advisors aus einer Vorlage (oberste Option) oder die Erstellung einer fortgeschrittenen, strukturierten Version. Programmieranfängern empfehle ich dringend, die erste Option, d. h. die Vorlage, zu wählen.
Die erweiterte generierte Version ist objektorientiert und auf mehrere Dateien verteilt. Ohne zusätzliche Tools oder Erfahrung kann es ziemlich schwierig sein, sie zu verstehen, und noch schwieriger, sie an die eigene Handelslogik anzupassen. Deshalb empfehle ich, diese Version erst dann zu verwenden , wenn Sie ein solides Verständnis der Konzepte und Praktiken der objektorientierten Programmierung (OOP) erworben haben. Die Überprüfung des generierten Codes kann lehrreich sein, um zu sehen, was möglich ist, aber bedenken Sie, dass es sich nur um eine von vielen möglichen Implementierungen handelt, die für die automatische Generierung optimiert wurden. Wenn Sie bereit sind, die Feinheiten der Klassenstruktur vollständig zu verstehen, werden Sie es wahrscheinlich vorziehen, Ihre eigenen Codevorlagen zu schreiben. Natürlich ist es viel einfacher, den eigenen Code zu bearbeiten, als den eines anderen zu entschlüsseln. Und die Chancen stehen gut, dass Ihre eigenen Vorlagen genauso gut, wenn nicht sogar besser sind als die des Assistenten.
Die meisten der optionalen Funktionen, die der Assistent hinzufügen kann (Abbildungen 2 und 3), sind nicht unbedingt erforderlich, aber oft sehr hilfreich. Mit den Funktionen des dritten Assistentenbildschirms (Abbildung 2) können Sie beispielsweise Ereignisse behandeln, die während der Handelsoperationen ausgelöst werden, z. B. wenn der Server ein Signal empfängt, eine Position eröffnet wird usw. (OnTrade und OnTradeTransaction), sowie Timer-Ereignisse (OnTimer), Chart-Interaktionen wie das Drücken von Schaltflächen oder die Erstellung von Objekten (OnChartEvent) und Orderbuch-Aktualisierungen (OnBookEvent).
Abbildung 2. Erstellen eines Expert Advisors - dritter Bildschirm des Assistenten (zusätzliche EA-Funktionen)
Es gibt auch spezielle Funktionen, die ausschließlich innerhalb des Strategietesters verwendet werden, aber nicht im normalen Betrieb (Abbildung 3). Diese sind vor allem für Demo-Versionen nützlich, die nur im Tester und nicht auf Live-Konten funktionieren. Manchmal benötigen Sie während der Tests ausführlichere Protokolle oder möchten Daten aus anderen Quellen abrufen. Ich persönlich verwende diese Funktionen nur selten, aber im richtigen Kontext können sie nützlich sein.
Abbildung 3. Erstellen eines Expert Advisors - vierter Bildschirm des Assistenten (Funktionen für den reinen Testbetrieb)
Einige der in Abbildung 2 dargestellten Funktionen werden in späteren Artikeln ausführlicher behandelt, während die in Abbildung 3 dargestellten Funktionen von Ihnen selbst erforscht werden können.
Wenn Sie einen EA mit dem Assistenten erstellen, enthält die generierte Datei immer mindestens drei Funktionen (Beispiel 1):
//+------------------------------------------------------------------+ //| FirstExpert.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Beispiel 1. Minimale Vorlage des vom Assistenten erstellten EA
- OnInit - eine bekannte Funktion aus der Indikatorentwicklung, die für die Ersteinrichtung verwendet wird. Sie wird einmal beim Start des Programms ausgeführt.
- OnDeinit - auch diese Funktion dürfte bekannt sein und wird aufgerufen, wenn der Expert Advisor beendet wird. Sie ist für Aufräumarbeiten gedacht: Entfernen von grafischen Objekten, die vom EA erstellt wurden, Schließen von Dateien, Freigeben von Indikatorressourcen und Durchführen anderer Abschlussaufgaben.
- OnTick - wird bei jedem Tick ausgeführt (ähnlich wie OnCalculate in Indikatoren). Hier läuft die Kernlogik Ihres EA.
Von diesen ist nur OnTick für einen Expert Advisor obligatorisch. Sie können OnInit und OnDeinit weglassen, wenn Ihr EA einfach ist.
Schlüsselbegriffe im MetaTrader 5 Automatisierten Handel
Bei der Entwicklung von automatischen Handelssystemen in MetaTrader 5 gibt es bestimmte Begriffe und Konzepte, die jeder Programmierer verstehen muss. Diese Begriffe sind mit der Handelslogik und den Funktionsnamen verbunden. Wir wollen sie erkunden.
Order - eine an den Server gesendete Auftrag, die Ihre Absicht anzeigt, ein bestimmtes Instrument zu einem bestimmten Preis zu kaufen oder zu verkaufen.
Jede Änderung beim Handel - sei es die Platzierung einer Marktorder oder die Änderung eines Stop-Loss-Levels - erfolgt über Handelsaufträge. Aufträge können entweder sofort ausgeführt werden (z. B. Kauf/Verkauf zum Marktpreis) oder schwebend, d. h. der Handel soll in der Zukunft ausgeführt werden, wenn die Preisbedingungen erfüllt sind (z. B. Stop- oder Limit-Aufträge).
Zu den schwebenden Aufträgen gehören Stop Loss, Take Profit, Buy/Sell Stop, Buy/Sell Limit und Buy/Sell Stop Limit. Zu den Beispielfunktionen im Zusammenhang mit der Auftragsabwicklung gehören OrderSend (sendet einen Auftrag an den Server) und OrderGetInteger (liefert ganzzahlige Parameter des Auftrags, z. B. Ticketnummer oder Erstellungszeit).
Deal - die tatsächliche Ausführung eines Auftrags.
Ein Deal in MetaTrader 5 ist im Wesentlichen mit der Geschichte verbunden. Dies ist der Punkt, an dem ein Auftrag ausgeführt wird. Sie können einen Deal nicht direkt beeinflussen, da es auf dem Server stattfindet, sobald ein Auftrag ausgeführt wird. Sie können jedoch historische Informationen über Handelsgeschäfte abrufen, z. B. den Ausführungspreis und die Ausführungszeit. Beispielfunktionen: HistoryDealGetDouble (liefert einen Parameter vom Typ double eines Handelsgeschäfts, wie z.B. den Preis), HistoryDealsTotal (liefert die Gesamtzahl der Handelsgeschäfte in der Historie).
Position - der sich ergebende Status Ihres Portfolios nach einem oder mehreren Handelsgeschäften mit einem bestimmten Symbol.
MetaTrader 5 war ursprünglich so konzipiert, dass jedes Symbol nur eine Position haben konnte. Das tatsächliche Verhalten hängt jedoch von der Art des Kontos ab. Bei Netting-Konten resultieren alle Abschlüsse in einer einzige Position. Bei Hedging-Konten erstellt jeder Handelsauftrag eine eigene Position (es sei denn, es handelt sich um eine Stop-Order). Dies kann zu mehreren Positionen auf demselben Symbol und sogar in entgegengesetzten Richtungen führen. In diesem Fall kann bei Bedarf die Gesamtposition für Kauf und Verkauf berechnet werden. Positionen können mit Handelsaufträgen geändert werden - ganz oder teilweise geschlossen oder in Bezug auf Stop-Loss- und Take-Profit-Levels angepasst werden. Beispiele für Funktionen, die mit Positionen arbeiten, sind: PositionSelectByTicket (Auswahl einer Position anhand ihres Tickets) und PositionGetString (Abrufen von String-Parametern wie Symbolname oder Nutzerkommentar).
Jedes Handelsgeschäft resultiert aus der Ausführung eines Handelsauftrags, und jede Position spiegelt den kumulativen Effekt von einem oder mehreren Handelsgeschäften wider.
Ereignis - jedes signifikante Ereignis in der Programmumgebung.
Das Einreichen eines Handelsantrags ist ein Ereignis. Der Server, der die Anfrage annimmt, ein Nutzer, der auf das Chart klickt, eine Änderung der Skalierung des Charts, ein neuer Tick - all dies sind ebenfalls Ereignisse. Einige von ihnen werden über Standardfunktionen der Ereignisbehandlung bearbeitet, die mit On beginnen - wie OnInit (ausgelöst durch das Init-Ereignis) oder OnTick (für Tick-Ereignisse).
Andere Ereignistypen werden mit konstanten Bezeichnern verarbeitet. Dies bedeutet, dass eine der globalen Ereignisbehandlungsfunktionen zuerst ausgelöst werden muss. OnChartEvent wird zum Beispiel bei jedem Chartereignis aufgerufen. Innerhalb dieser Funktion bestimmen Sie den genauen Ereignistyp, indem Sie die Ereigniscode-Variable mit bekannten Konstanten vergleichen. Die an diese Funktionen übergebenen Parameter helfen bei der Identifizierung der Ereignisdetails. In diesem Artikel werden wir nicht auf die Einzelheiten dieser kleineren Ereignisse eingehen.
Grundprinzipien des automatisierten Handels in MetaTrader 5
Werfen wir einen Blick darauf, wie der Handel im MetaTrader 5 tatsächlich funktioniert. Beginnen wir mit einer wichtigen Tatsache.
Die Terminalsoftware, die auf Ihrem Computer läuft, und die Serversoftware, die den Handel mit Ihrem Geld ausführt, sind zwei separate Programme. Sie kommunizieren ausschließlich über das Netz (in der Regel das Internet).
Wenn Sie also auf „Buy“ (Kaufen) oder „Sell“ (Verkaufen) klicken, findet die folgende Abfolge von Ereignissen statt:
- Ihr Terminal erzeugt ein spezielles Datenpaket, das die spezielle Struktur MqlTradeRequest enthält.
- Diese gefüllte Struktur wird mittels OrderSend (synchroner Modus) oder OrderSendAsync (asynchroner Modus) an den Server gesendet und bildet einen Handelsauftrag.
- Der Server empfängt das Paket und prüft, ob es alle Anforderungen erfüllt: ob passende Preise verfügbar sind, ob Ihr Guthaben ausreicht usw.
- Wenn alles in Ordnung ist, wird der Auftrag zusammen mit den Aufträgen anderer Händler in die Auftragswarteschlange gestellt und wartet darauf, ausgeführt zu werden.
- Eine Bestätigungsmeldung wird an Ihr Terminal zurückgesendet.
- Erreicht der Markt das gewünschte Preisniveau, führt der Server das Handelsgeschäft aus und vermerkt das Ereignis in seinem Protokoll.
- Der Server sendet die Ergebnisse an Ihr Terminal zurück.
- Das Terminal erhält dieses Ergebnis in Form der Struktur MqlTradeResult und erzeugt entsprechende Ereignisse wie Trade und TradeTransaction.
- Das Terminal prüft, ob auf der Serverseite Fehler aufgetreten sind (dies geschieht durch Überprüfung des Feldes „retcode“ der Struktur MqlTradeResult).
- Wenn alles in Ordnung ist, aktualisiert das Terminal seine internen Variablen, Protokolleinträge und das grafische Chart.
- Infolgedessen erscheint in Ihrem Portfolio eine neue (oder aktualisierte) Position für das betreffende Instrument.
Dieser gesamte Prozess kann in einem vereinfachten Diagramm dargestellt werden, wie in Abbildung 4 gezeigt:
Abbildung 4. Diagramm zur Abwicklung von Handelsaufträgen
Asynchroner Datenübertragungsmodus
Sie haben vielleicht bemerkt, dass das Terminal während der Interaktion zwischen dem Terminal und dem Server mindestens zweimal über das Netz kommunizieren muss: einmal, um Daten zu senden, und ein zweites Mal, um eine Antwort zu erhalten. Bei Verwendung der Funktion OrderSend ist dieser Prozess im Wesentlichen synchron, d. h. der EA wartet auf eine Antwort und beansprucht dabei Systemressourcen wie Internet-Bandbreite und möglicherweise CPU-Zeit.
Aus der Sicht des Prozessors ist der Netzbetrieb jedoch sehr langsam. Ein Handelsskript kann in der Regel in einigen hundert Mikrosekunden ausgeführt werden (z. B. 200 μs = 2e-4 Sekunden), aber die Datenübertragung im Netz wird in der Regel in Millisekunden gemessen (z. B. 20 ms = 2e-2 Sekunden), was mindestens 100 Mal langsamer ist. Hinzu kommen die serverseitige Verarbeitungszeit und manchmal unerwartete Verzögerungen aufgrund von Wartungsarbeiten oder technischen Problemen... Im schlimmsten Fall kann die Zeitspanne zwischen dem Senden einer Handelsanfrage und dem Erhalt einer Antwort Sekunden oder sogar Minuten betragen. Wenn mehrere EAs während dieser Zeit untätig warten, werden eine Menge CPU-Ressourcen unproduktiv verschwendet.
Um diese Ineffizienz zu beheben, bietet MetaTrader einen speziellen asynchronen Handelsmodus. Das Wort asynchron bedeutet, dass der EA eine Handelsanfrage senden und mit anderen Aufgaben fortfahren kann - warten („sleep“), Berechnungen durchführen oder etwas anderes - ohne auf eine Antwort zu warten. Wenn die Serverantwort schließlich eintrifft, erzeugt das Terminal ein TradeTransaction-Ereignis (und anschließend ein Trade-Ereignis). Der EA kann dann „aufwachen“ und die Antwort verarbeiten, um weitere Handelsentscheidungen zu treffen. Die Vorteile dieses Ansatzes liegen auf der Hand.
Sowohl im synchronen als auch im asynchronen Modus werden Handelsfehler mit der Funktion OnTradeTransaction behandelt. Dadurch wird der Code nicht komplexer - einige Logik wird einfach von OnTick zu OnTradeTransaction verschoben. Und wenn Sie diesen Code in separate Funktionen auslagern, wird der Aufruf und die Weitergabe keinerlei Probleme verursachen. Daher hängt die Wahl zwischen synchronen und asynchronen Handelsmodi ganz von Ihren Präferenzen und der jeweiligen Aufgabe ab. Die in beiden Modi verwendeten Datenstrukturen sind identisch.
Einstieg in den Handel
Nehmen wir an, wir wollen einen Expert Advisor für den FOREX-Markt erstellen, der nach Inside-Balken sucht. Zur Erinnerung: Ein Inside-Balken ist eine Kerze, deren Höchst- und Tiefstwerte vollständig innerhalb des Bereichs der vorherigen (größeren) Kerze liegen. Der EA arbeitet einmal pro Kerze, und wenn er das Muster eines Inside-Balkens erkennt, platziert er gleichzeitig zwei schwebende Aufträge:
- Ein Buy Stop, der einige Punkte (konfigurierbar) über dem Hoch der größeren Kerze liegt,
- Ein Verkaufsstopp im gleichen Abstand unter dem Tiefpunkt dieser Kerze,
- Jeder Auftrag hat eine Lebensdauer von zwei Kerzen. Wird sie nicht innerhalb dieser Frist ausgelöst, wird sie gelöscht,
- Der Stop Loss für beide Aufträge wird in der Mitte der größeren Kerze platziert,
- Die Gewinnmitnahme wird auf 7/8 des Bereichs der größeren Kerze festgelegt,
- Das Handelsvolumen entspricht der zulässigen Mindestmenge.
In dieser ersten Version des EA wird auf zusätzliche Bedingungen verzichtet, um den Code leichter verständlich zu halten. Wir werden ein Grundgerüst aufbauen, das später erweitert werden kann. Wir beginnen mit der Erstellung der EA-Vorlage mithilfe des Assistenten und lassen im dritten Fenster alle Kontrollkästchen deaktiviert (da wir in dieser Version keine Serverantworten verarbeiten werden). Der resultierende Code ähnelt dem von Beispiel 1. Um den EA konfigurierbar und optimierbar zu machen, werden wir vier Eingabeparameter definieren: Abstand vom Hoch/Tief zum Platzieren von Pending Orders (inp_PipsToExtremum), Abstand zum Platzieren von Stop Loss und Take Profit (inp_StopCoefficient und inp_TakeCoefficient) und die Anzahl der Balken, nach denen nicht ausgelöste Orders gelöscht werden (inp_BarsForOrderExpired). Zusätzlich geben wir eine magische Zahl für den EA an - dies hilft, „unsere“ Aufträge von denen anderer EAs oder manueller Aufträge zu unterscheiden.
//--- declare and initialize input parameters input int inp_PipsToExtremum = 2; input double inp_TakeCoeffcient = 0.875; input double inp_StopCoeffcient = 0.5; input int inp_BarsForOrderExpired = 2; //--- declare and initialize global variables #define EXPERT_MAGIC 11223344
Szenario 2. Beschreibung der EA-Eingabeparameter und der magischen Zahl
Nur zur Erinnerung: Der Code aus Beispiel 2 muss ganz oben in der EA-Datei stehen, unmittelbar nach den #property-Anweisungen.
Der Rest des Codes in diesem Beispiel wird innerhalb der Funktion OnTick platziert. Alle anderen Funktionen lassen wir vorerst leer. Hier ist der Code, der in den OnTick-Body eingefügt werden muss:
/**************************************************************** * Please note: this Expert Advisor uses standard functions * * to access price/time data. Therefore, it's convenient to * * work with series as arrays (time and prices). * ****************************************************************/ string symbolName = Symbol(); ENUM_TIMEFRAMES period = PERIOD_CURRENT; //--- Define a new candlestick (Operations only at the start of a new candlestick) static datetime timePreviousBar = 0; // Time of the previous candlestick datetime timeCurrentBar; // Time of the current candlestick // Get the time of the current candlestick using the standard function timeCurrentBar = iTime( symbolName, // Symbol name period, // Period 0 // Candlestick index (remember it's series) ); if(timeCurrentBar==timePreviousBar) { // If the time of the current and previous candlesticks match return; // Exit the function and do nothing } // Otherwise the current candlestick becomes the previous one, // so as not to trade on the next tick timePreviousBar = timeCurrentBar; //--- Prepare data for trading double volume=SymbolInfoDouble(symbolName,SYMBOL_VOLUME_MIN); // Volume (lots) - get minimum allowed volume // Candlestick extrema double high[],low[]; // Declare arrays // Declare that arrays are series ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); // Fill arrays with values of first two closed candlesticks // (start copying with index 1 // as we only need closed candlesticks; use 2 values) CopyHigh(symbolName,period,1,2,high); CopyLow(symbolName,period,1,2,low); double lengthPreviousBar; // The range of the "long" bar MqlTradeRequest request; // Request structure MqlTradeResult result; // Server response structure if( // If the first closed bar is inside high[0]<high[1] && low[0]>low[1] ) { // Calculate the range lengthPreviousBar=high[1]-low[1]; // Timeseries have right-to-left indexing //--- Prepare data for a buy order request.action =TRADE_ACTION_PENDING; // order type (pending) request.symbol =symbolName; // symbol name request.volume =volume; // volume deal request.type =ORDER_TYPE_BUY_STOP; // order action (buy) request.price =high[1] + inp_PipsToExtremum*Point(); // buy price // Optional parameters request.deviation =5; // acceptable deviation from the price request.magic =EXPERT_MAGIC; // EA's magic number request.type_time =ORDER_TIME_SPECIFIED; // Parameter is required to set the lifetime request.expiration =timeCurrentBar+ PeriodSeconds()*inp_BarsForOrderExpired; // Order lifetime request.sl =high[1]-lengthPreviousBar*inp_StopCoeffcient; // Stop Loss request.tp =high[1]+lengthPreviousBar*inp_TakeCoeffcient; // Take Profit //--- Send a buy order to the server OrderSend(request,result); // For asynchronous mode you need to use OrderSendAsync(request,result); //--- Clear the request and response structures for reuse ZeroMemory(request); ZeroMemory(result); //--- Prepare data for a sell order. Parameers are the same as in the previous function. request.action =TRADE_ACTION_PENDING; // order type (pending) request.symbol =symbolName; // symbol name request.volume =volume; // volume request.type =ORDER_TYPE_SELL_STOP; // order action (sell) request.price =low[1] - inp_PipsToExtremum*Point(); // sell price // Optional parameters request.deviation =5; // acceptable deviation from the price request.magic =EXPERT_MAGIC; // EA's magic number request.type_time =ORDER_TIME_SPECIFIED; // Parameter is required to set the lifetime request.expiration =timeCurrentBar+ PeriodSeconds()*inp_BarsForOrderExpired; // Order lifetime request.sl =low[1]+lengthPreviousBar*inp_StopCoeffcient; // Stop Loss request.tp =low[1]-lengthPreviousBar*inp_TakeCoeffcient; // Take Profit //--- Send a sell order to the server OrderSend(request,result); }
Beispiel 3. Die Funktion OnTick dieses EAs enthält die gesamte Handelslogik.
Die Standardfunktion Point gibt die Punktgröße für das aktuelle Chart zurück. Wenn der Broker zum Beispiel fünfstellige Kurse anbietet, entspricht ein Punkt für EURUSD 0,00001 und für USDJPY 0,001. Mit den Funktionen iTime, iHigh und iLow können Sie die Zeit, den höchsten und den niedrigsten Preis einer bestimmten Kerze abrufen (nach ihrem Index von rechts nach links, wobei 0 der aktuelle Balken ist). In diesem Beispiel haben wir iTime nur verwendet, um zu prüfen, ob ein neuer Balken vorliegt, indem wir die aktuelle Zeit abgerufen haben. Um die hohen und niedrigen Werte zu erhalten, haben wir die Array-Kopierfunktionen CopyHigh und CopyLow verwendet.
Der Code ist in zwei Hauptabschnitte unterteilt: Prüfung auf einen neuen Balken und Handel (der mit einer Vorbereitungsphase beginnt). Der Handelsblock ist außerdem in zwei nahezu identische Segmente unterteilt: eines für den Kauf und eines für den Verkauf. Der Aufbau der Struktur und die Logik der Auftragsübermittlung sind in beiden Fällen so ähnlich, dass es sinnvoll wäre, sie in eine separate Funktion umzuwandeln, die die allgemeinen Felder ausfüllt und nur für Besonderheiten wie Auftragsart und Ausführungspreise (Preis, tp, sl) verzweigt. In diesem Beispiel wurde jedoch die Kompaktheit und Wiederverwendbarkeit des Codes absichtlich zugunsten der Klarheit und Lesbarkeit geopfert.
Jede Stufe des Prozesses wird mit einem Kommentar unter Verwendung von //--- gekennzeichnet, während die Kommentare innerhalb jeder Stufe in einfacher Schrift ohne Bindestriche geschrieben sind. Die Handelslogik besteht aus zwei Hauptteilen: dem Ausfüllen der Anforderungsstruktur und dem Versenden der Anforderung. Beim Ausfüllen der Antragsstruktur sind nur die ersten fünf Felder obligatorisch.
Es ist wichtig zu beachten, dass Sie sowohl request.type_time als auch request.expiration ausfüllen müssen, wenn Sie, wie in diesem Beispiel gezeigt, die Ablaufzeit der Bestellung verwenden wollen. Wenn Sie das erste Feld nicht ausfüllen, wird das zweite Feld standardmäßig ignoriert.
Um die Funktionsweise dieses Expert Advisors zu testen, können Sie ihn auf einem Demokonto auf einem beliebigen Zeitrahmen ausführen (er funktioniert sogar auf Minutencharts, obwohl die tatsächliche Leistung vom Spread für das gewählte Symbol abhängt). Alternativ können Sie auch <Strg>+<F5> im MetaEditor drücken, um einen Backtest mit historischen Daten im Strategy Tester zu starten. Der vollständige Quellcode ist in der beigefügten Datei TrendPendings.mq5 zu finden.
Verwendung von Standardindikatoren
Der Expert Advisor in Beispiel 3 hat keine Indikatoren verwendet, aber das wird nicht immer der Fall sein. Für Strategien, die auf Standardindikatoren basieren, gibt es zwei Hauptansätze: die Verwendung eingebauter Indikatorfunktionen oder die Arbeit mit Indikatorklassen. Wir werden beide Methoden behandeln und mit den eingebauten Funktionen beginnen.
Nehmen wir an, wir möchten einen Expert Advisor erstellen, der auf einem einfachen gleitenden Durchschnitt basiert. Zur Erinnerung: Das Ziel dieses Artikels ist es nicht, eine profitable Strategie zu entwickeln, sondern die fundamentale Handelslogik zu demonstrieren. Deshalb werde ich den Code so einfach und lesbar wie möglich halten. Auf dieser Grundlage werden wir unsere Handelsregeln wie folgt definieren:
- Wie im vorherigen EA werden Trades nur berücksichtigt, wenn sich ein neuer Balken bildet;
- Um zu kaufen, muss die vorherige Kerze über dem gleitenden Durchschnitt schließen;
- Um zu verkaufen, muss die vorherige Kerze unter dem gleitenden Durchschnitt schließen;
- Als Filter verwenden wir die Steigung des gleitenden Durchschnitts: Wenn der Durchschnitt vom vorletzten Balken bis zum letzten geschlossenen Balken ansteigt, kaufen wir; wenn er fällt, verkaufen wir;
- Wir verlassen die Position, wenn ein entgegengesetztes Signal erscheint;
- Ein schützender Stop-Loss wird am Hoch (bei Verkaufssignalen) oder Tief (bei Kaufsignalen) der Signalkerze platziert;
- Es ist nur eine offene Position pro Symbol erlaubt, auch bei Hedging-Konten; wenn ein Signal erscheint, aber bereits eine Position offen ist, wird es übersprungen.
Abbildung 5 veranschaulicht das in diesem EA verwendete Filterprinzip.
Abbildung 5. Das Prinzip der Kerzen-Filterung in einem EA mit gleitendem Durchschnitt
In diesem EA werde ich mich weiterhin bemühen, den Code so sauber und verständlich wie möglich zu halten. Ich werde jedoch damit beginnen, mehr Fehlerprüfungen einzubauen, um die Implementierung näher an reale Standards heranzuführen.
Alle Parameter des gleitenden Durchschnitts sowie die maximal zulässige Preisabweichung vom gewünschten Auftragspreis werden zu den Eingabeparametern hinzugefügt. Außerdem werden wir eine globale Variable für den Indikator-Handle deklarieren (ich werde das gleich erklären) und auch die magische Zahl des EAs definieren.
//--- declare and initialize global variables #define EXPERT_MAGIC 3345677 input int inp_maPeriod = 3; // MA period input int inp_maShift = 0; // Shift input ENUM_MA_METHOD inp_maMethod = MODE_SMA; // Calculation mode input ENUM_APPLIED_PRICE inp_maAppliedPrice = PRICE_CLOSE; // Applied price input int inp_deviation = 5; // Max price deviation from the request price in points //--- MA indicator handle int g_maHandle;
Beispiel 4. EA's globale Variablen für den Handel mit gleitenden Durchschnitten
Bevor wir einen Indikator in einem EA oder einem anderen Indikator verwenden, müssen wir drei Dinge tun:
-
Wir initialisieren den Indikator und erhalten einen Handle auf ihn. Dies geschieht in der Regel mit integrierten Indikatorfunktionen (wie iMA für den gleitenden Durchschnitt) oder iCustom für nutzerdefinierte Indikatoren. Diese Initialisierung wird in der Regel innerhalb der Funktion OnInit durchgeführt.
In der Programmierung ist ein Handle so etwas wie ein Verweis oder Zeiger auf eine Ressource, mit der Ihr Code interagieren kann. Betrachten wir sie als eine Ticketnummer, mit der Sie bei Bedarf auf den Indikator zugreifen und seine Daten abfragen können. - Bevor Sie die Indikatorwerte verwenden können, müssen Sie die aktuellsten Daten abrufen. Dazu wird in der Regel ein Array pro Indikatorpuffer erstellt und diese Arrays mit der Funktion CopyBuffer aufgefüllt.
- Wir verwenden die Daten aus gefüllten Feldern.
- Um Speicherlecks oder unnötigen Ressourcenverbrauch zu vermeiden, ist es wichtig, das Indikator-Handle freizugeben, wenn das Programm endet. Dies geschieht in der Funktion OnDeinit unter Verwendung von IndicatorRelease.
In diesem speziellen EA sind die Funktionen OnInit und OnDeinit recht einfach und enthalten keine ungewöhnliche oder komplexe Logik:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Before an action that could potentially cause an error, reset // built-in _LastError variable to default // (assuming there's no error yet) ResetLastError(); //--- The standard iMA function returns the indicator handle g_maHandle = iMA( _Symbol, // Symbol PERIOD_CURRENT, // Chart period inp_maPeriod, // MA period inp_maShift, // MA shift inp_maMethod, // MA calculation method inp_maAppliedPrice // Applied price ); // inp_maAppliedPrice in general case can be // either a price type as in this example, // (from ENUM_APPLIED_PRICE), // or a handle of another indicator //--- if the handle is not created if(g_maHandle==INVALID_HANDLE) { //--- report failure and output error code PrintFormat("Failed to crate iMA indicator handle for the pair %s/%s, error code is %d", _Symbol, EnumToString(_Period), GetLastError() // Output error code ); //--- If an error occurs, terminate the EA early return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release resources occupied by the indicator if(g_maHandle!=INVALID_HANDLE) IndicatorRelease(g_maHandle); }
Beispiel 5. Initialisierung und Deinitialisierung von Indikatoren in einem Expert Advisor
Eine Nuance, auf die ich Ihre Aufmerksamkeit lenken möchte, ist die Verwendung des Funktionspaares ResetLastError und GetLastError innerhalb der Initialisierungsfunktion. Erstere setzt die Systemvariable _LastError auf den Zustand „kein Fehler“ zurück, während letztere es Ihnen ermöglicht, den Code des letzten Fehlers abzurufen, falls ein solcher aufgetreten ist.
Abgesehen davon ist alles ziemlich einfach. Initialisierungsfunktionen für Indikatoren (einschließlich iMA) geben entweder ein gültiges Indikator-Handle zurück oder eine spezielle Konstante INVALID_HANDLE, wenn das Handle nicht ermittelt werden konnte. Mit diesem Mechanismus können wir erkennen, wenn etwas schief gelaufen ist, und den Fehler entsprechend behandeln - in unserem Fall, indem wir eine Fehlermeldung ausgeben. Wenn OnInit INIT_FAILED zurückgibt, wird der Expert Advisor (oder Indikator) nicht gestartet. Und in der Tat, wenn wir keinen gültigen Verweis auf den gleitenden Durchschnittsindikator erhalten, ist es die einzig richtige Vorgehensweise, die Ausführung zu stoppen.
Wir werden die Funktion OnTick Schritt für Schritt aufschlüsseln. Der erste Teil umfasst die Deklaration und Initialisierung von Variablen.
//--- Declare and initialize variables MqlTradeRequest requestMakePosition; // Request structure for opening a new position MqlTradeRequest requestClosePosition; // Request structure for closing an existing position MqlTradeResult result; // Structure for receiving the server's response MqlTradeCheckResult checkResult; // Structure for validating the request before sending bool positionExists = false; // Flag indicating if a position exists bool tradingNeeds = false; // Flag indicating whether trading is allowed ENUM_POSITION_TYPE positionType; // Type of currently open position ENUM_POSITION_TYPE tradingType; // Desired position type (used for comparison) ENUM_ORDER_TYPE orderType; // Desired order type double requestPrice=0; // Entry price for the future position /* The MqlRates structure contains all candle data: open, close, high, and low prices, tick volume, real volume, spread, and time. In this example, I decided to demonstrate how to fill the entire structure at once, instead of retrieving each value separately. */ MqlRates rates[]; // Array of price data used for evaluating trading conditions double maValues[]; // Array of MA values // Declare data arrays as series ArraySetAsSeries(rates,true); ArraySetAsSeries(maValues,true);
Beispiel 6. Lokale Variablen der Funktion OnTick
Wir haben die gleiche Prüfung, ob ein Balken erschienen ist:
//--- Check whether there's a new bar static datetime previousTime = iTime(_Symbol,PERIOD_CURRENT,0); datetime currentTime = iTime(_Symbol,PERIOD_CURRENT,0); if(previousTime==currentTime) { return; } previousTime=currentTime;
Beispiel 7. Prüfen, ob es ein neuer Balken ist
Als Nächstes erhalten wir alle benötigten Daten mit Hilfe spezieller Funktionen. Hier gehen wir davon aus, dass es zu Fehlern kommen kann, z.B. dass das Terminal keine Zeit hatte, die notwendigen Daten zu laden, sodass wir diese möglichen Fehler mit dem Verzweigungsoperator bearbeiten.
//--- Prepare data for processing // Copy the quotes of two bars, starting from the first one if(CopyRates(_Symbol,PERIOD_CURRENT,1,2,rates)<=0) { PrintFormat("Data error for symbol %s, error code is %d", _Symbol, GetLastError()); return; } // Copy the values of the moving average indicator buffer if(CopyBuffer(g_maHandle,0,1,2,maValues)<=0) { PrintFormat("Error getting indicator data, error code is %d", GetLastError()); return; }
Beispiel 8. Kopieren von aktuellen Indikatordaten und Kursen in lokale Arrays
Und jetzt wählen wir eine offene Position für das aktuelle Symbol mit der Standardfunktion PositionSelect aus. Wir haben beschlossen, dass es nur eine Position für ein Symbol geben kann, sodass es keine Probleme geben sollte, aber wir denken trotzdem sorgfältig darüber nach, was schief gehen könnte... Zumindest müssen wir überprüfen, ob die Position von unserem EA eröffnet wurde:
//--- Determine if there is an open position if(PositionSelect(_Symbol)) { // Set the open position flag - for further processing positionExists = true; // Save the type of the open position positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Check if the position has been opened by our EA requestClosePosition.magic = PositionGetInteger(POSITION_MAGIC); // I didn't create a separate variable for // the existing position magic number if(requestClosePosition.magic!= EXPERT_MAGIC) { // Some other EA started trading our symbol. Let it do so... return; } // if(requestClosePosition.magic!= EXPERT_MAGIC) } // if(PositionSelect(_Symbol))
Beispiel 9. Abrufen der aktuellen Positionsdaten
Jetzt können wir die Handelsbedingungen überprüfen. In diesem Beispiel speichere ich die Ergebnisse der Prüfung in separaten Variablen und verwende diese Variablen dann bei der endgültigen Entscheidung: kaufen oder verkaufen. Bei großen und komplexen Lösungen rechtfertigt sich dieser Ansatz zum einen durch seine Flexibilität und zum anderen durch die Tatsache, dass der endgültige Code kürzer wird. Hier habe ich diese Technik hauptsächlich aus dem zweiten Grund verwendet: Da der endgültige Algorithmus nicht sehr klar ist, versuche ich, die Klarheit auf verschiedene Weise zu verbessern.
//--- Check trading conditions, if( // Conditions for BUY rates[0].close>maValues[0] // If the first candlestick closed above MA && maValues[0]>maValues[1] // and the MA slope is upwards ) { // Set the trade flag tradingNeeds = true; // and inform the EA about the direction (here - BUY) tradingType = POSITION_TYPE_BUY; // to check the direction of the open direction orderType = ORDER_TYPE_BUY; // to trade in the right direction // calculate the deal price requestPrice = SymbolInfoDouble(_Symbol,SYMBOL_ASK); } else if( // conditions for SELL rates[0].close<maValues[0] && maValues[0]<maValues[1] ) { tradingNeeds = true; tradingType = POSITION_TYPE_SELL; orderType = ORDER_TYPE_SELL; requestPrice = SymbolInfoDouble(_Symbol,SYMBOL_BID); }
Beispiel 10. Überprüfung der Handelsbedingungen
Der nachstehende Code bestimmt, ob ein Handelsgeschäft zum aktuellen Zeitpunkt ausgeführt werden soll. Die Entscheidung basiert auf drei Schlüsselfragen:
- Gibt es einen Handelsplan? Mit anderen Worten: Hat die Kerze über dem gleitenden Durchschnitt geschlossen? Diese Bedingung wird über die Variable tradingNeeds geregelt. Wenn die Antwort „nein“ lautet (tradingNeeds == false), sollte kein Handel getätigt werden.
- Gibt es bereits eine offene Stelle? Dies wird mit Hilfe der Variablen positionExists überprüft. Wenn es keine offene Position gibt, handeln Sie einfach. Ist dies der Fall, fahren Sie mit der nächsten Prüfung fort.
- Ist die bestehende Position auf das neue Handelssignal ausgerichtet oder umgekehrt? Dies wird durch den Vergleich von tradingType und positionType ermittelt. Wenn sie gleich sind, wird die Position an das neue Signal angepasst, sodass kein neuer Handel eröffnet wird. Weichen sie voneinander ab, so liegt die aktuelle Position in der entgegengesetzten Richtung und muss vor dem Öffnen einer neuen Position geschlossen werden.
Diese Entscheidungslogik ist in dem Flussdiagramm (Abbildung 6) dargestellt.
Abbildung 6. Flussdiagramm, das die wichtigsten Entscheidungspunkte des Handelsalgorithmus darstellt
In MQL5 wird sowohl beim Schließen als auch beim Eröffnen einer Position ein Marktauftrag gesendet. Der Ansatz ist derselbe wie der, mit dem Sie bereits vertraut sind: Füllen Sie eine Struktur für Handelsanfragen aus und senden Sie sie an den Server. Der Unterschied zwischen diesen beiden Strukturen besteht darin, dass Sie beim Schließen einer Position deren Ticket angeben und die Parameter der bestehenden Position genau in die Anfrage kopieren müssen. Bei der Eröffnung einer neuen Position muss nichts kopiert werden, sodass wir freier sind, und es gibt kein altes Positionsticket, sodass nichts übergeben werden muss.
Im Vergleich zu dem früheren Beispiel mit den schwebenden Aufträgen unterscheidet sich der Code hier in zwei Feldern:
- Das Aktionsfeld, das zuvor den Wert TRADE_ACTION_PENDING enthielt, enthält nun TRADE_ACTION_DEAL.
- Das Feld „Typ“, das nun einen direkten Marktauftrag (ORDER_TYPE_BUY oder ORDER_TYPE_SELL) und nicht mehr einen schwebenden Auftrag darstellt.
Um die Entsprechung zwischen den Codefragmenten und dem Flussdiagramm in Abbildung 6 leichter nachvollziehen zu können, wurde der Beispielcode entsprechend der im Diagramm dargestellten Verzweigungslogik farblich kodiert.
Es gibt zwei weitere bemerkenswerte Unterschiede zu Beispiel 3. Vor dem Senden einer Handelsanfrage wird die Struktur mit OrderCheck überprüft. Dadurch kann das Programm falsch ausgefüllte Felder erkennen und sowohl einen Rückgabecode (Retcode) als auch eine textliche Erklärung (Kommentar) liefern. Nach dem Absenden der Anfrage prüfen wir, ob der Server sie angenommen hat. Wenn ein Fehler auftritt, meldet das Programm diesen mit einer entsprechenden Meldung.
// If the setup is to trade if(tradingNeeds) { // If there is a position if(positionExists) { // And it is opposite to the desired direction of trade if(positionType != tradingType) { //--- Close the position //--- Clear all participating structures, otherwise you may get an "invalid request" error ZeroMemory(requestClosePosition); ZeroMemory(checkResult); ZeroMemory(result); //--- set operation parameters // Get position ticket requestClosePosition.position = PositionGetInteger(POSITION_TICKET); // Closing a position is just a trade requestClosePosition.action = TRADE_ACTION_DEAL; // position type is opposite to current trading direction, // therefore, for the closing deal, we can use the current order type requestClosePosition.type = orderType; // Current price requestClosePosition.price = requestPrice; // Operation volume must match the current position volume requestClosePosition.volume = PositionGetDouble(POSITION_VOLUME); // Set acceptable deviation from the current price requestClosePosition.deviation = inp_deviation; // Symbol requestClosePosition.symbol = Symbol(); // Position magic number requestClosePosition.magic = EXPERT_MAGIC; if(!OrderCheck(requestClosePosition,checkResult)) { // If the structure is filled incorrectly, display a message PrintFormat("Error when checking an order to close position: %d - %s",checkResult.retcode, checkResult.comment); } else { // Send order if(!OrderSend(requestClosePosition,result)) { // If position closing failed, report PrintFormat("Error closing position: %d - %s",result.retcode,result.comment); } // if(!OrderSend) } // else (!OrderCheck) } // if(positionType != tradingType) else { // Position opened in the same direction as the trade signal. Do not trade return; } // else(positionType != tradingType) } // if(positionExists) //--- Open a new position //--- Clear all participating structures, otherwise you may get an "invalid request" error ZeroMemory(result); ZeroMemory(checkResult); ZeroMemory(requestMakePosition); // Fill the request structure requestMakePosition.action = TRADE_ACTION_DEAL; requestMakePosition.symbol = Symbol(); requestMakePosition.volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); requestMakePosition.type = orderType; // While waiting for position to close, the price could have changed requestMakePosition.price = orderType == ORDER_TYPE_BUY ? SymbolInfoDouble(_Symbol,SYMBOL_ASK) : SymbolInfoDouble(_Symbol,SYMBOL_BID) ; requestMakePosition.sl = orderType == ORDER_TYPE_BUY ? rates[0].low : rates[0].high; requestMakePosition.deviation = inp_deviation; requestMakePosition.magic = EXPERT_MAGIC; if(!OrderCheck(requestMakePosition,checkResult)) { // If the structure check fails, report a check error PrintFormat("Error when checking a new position order: %d - %s",checkResult.retcode, checkResult.comment); } else { if(!OrderSend(requestMakePosition,result)) { // If position opening failed, report an error PrintFormat("Error opening position: %d - %s",result.retcode,result.comment); } // if(!OrderSend(requestMakePosition // Trading completed, reset flag just in case tradingNeeds = false; } // else (!OrderCheck(requestMakePosition)) } // if(tradingNeeds)
Beispiel 11. Der Haupthandelscode (der meiste Platz wird für das Auffüllen der Struktur und die Überprüfung auf Fehler benötigt)
Der vollständige Quellcode dieses Beispiels ist in der beigefügten Datei enthalten: MADeals.mq5.
Verwendung von Indikatorklassen aus der Standardbibliothek
Die Klassen für Standardindikatoren befinden sich im Ordner <Include\Indicators>. Sie können alle auf einmal einbeziehen, indem Sie die Datei <Include\Indicators\Indicators.mqh> importieren (beachten Sie das 's' am Ende des Dateinamens), oder Sie können sie gruppenweise laden - z. B., „Trend.mqh“, „Oszillatoren.mqh“, „Volumes.mqh“ oder „BillWilliams.mqh“. Es gibt auch separate Dateien, die Klassen für den Zugriff auf Zeitreihen („TimeSeries.mqh“) und eine Klasse für die Arbeit mit nutzerdefinierten Indikatoren („Custom.mqh“) enthalten.
Bei den übrigen Dateien in diesem Ordner handelt es sich um Hilfsmodule, die für diejenigen, die mit objektorientierter Programmierung nicht vertraut sind, wahrscheinlich von geringem Nutzen sein werden. Jede „funktionale“ Datei im Ordner enthält in der Regel mehrere verwandte Klassen. Diese Klassen werden in der Regel nach einer einheitlichen Konvention benannt: dem Präfix C folgt der gleiche Name, der auch in der Funktion zur Erstellung des Indikators verwendet wird. Die Klasse für die Arbeit mit gleitenden Durchschnitten heißt beispielsweise CiMA und ist in „Trend.mqh“ zu finden.
Die Arbeit mit diesen Klassen ist der Arbeit mit den nativen MQL5-Indikatorfunktionen sehr ähnlich. Zu den Hauptunterschieden gehören die Methodenaufrufe und ihre Benennung. In der ersten Phase - der Erstellung - rufen wir die Methode Create auf und geben die erforderlichen Parameter für den Indikator ein. In der zweiten Phase - dem Abrufen der Daten - verwenden wir die Methode Refresh, normalerweise ohne Parameter. Bei Bedarf können Sie z. B. angeben, welche Zeiträume aktualisiert werden sollen: (OBJ_PERIOD_D1 | OBJ_PERIOD_H1). Bei der Verwendung verwenden wir die Methode GetData, meist mit zwei Parametern: der Puffernummer und dem Kerzenindex (beachten Sie, dass die Indexierung dem Zeitreihenmodell folgt und von rechts nach links zunimmt).
In Beispiel 12 stelle ich einen minimalen Expert Advisor vor, der die CiMA-Klasse verwendet. Dieser EA gibt einfach den Wert des gleitenden Durchschnitts bei der ersten geschlossenen Kerze aus. Wenn Sie sehen möchten, wie dieser klassenbasierte Ansatz in einer tatsächlichen Handelsstrategie verwendet werden kann, kopieren Sie den Expert Advisor aus dem vorherigen Abschnitt (MADeals.mq5) in eine neue Datei und ersetzen Sie die entsprechenden Zeilen mit denen aus Beispiel 12.
#include <Indicators\Indicators.mqh> CiMA g_ma; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create the indicator g_ma.Create(_Symbol,PERIOD_CURRENT,3,0,MODE_SMA,PRICE_CLOSE); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get data g_ma.Refresh(); //--- Use Comment( NormalizeDouble( g_ma.GetData(0,1), _Digits ) ); } //+------------------------------------------------------------------+
Beispiel 12. Verwendung der Klasse CiMA (gleitender Durchschnittsindikator)
Schlussfolgerung
Nach der Lektüre dieses Artikels sollten Sie nun in der Lage sein, einfache Expert Advisors (EAs) zu schreiben, mit denen Sie schnell Prototypen für jede einfache Handelsstrategie erstellen können - unabhängig davon, ob diese nur auf Kerzen-Daten basieren oder Standardindikatoren enthalten, die ihre Signale über Indikatorpuffer (und nicht über eine grafische Darstellung) beziehen. Ich hoffe, das Thema war nicht zu komplex. Sollte dies jedoch der Fall sein, könnte es sich lohnen, das Material früherer Artikel erneut zu lesen, um ein besseres Verständnis zu erlangen.
Im nächsten Artikel werde ich einen Expert Advisor vorstellen, der technisch bereit für die Veröffentlichung auf dem Markt ist. Dieser EA wird noch mehr Validierungsprüfungen enthalten als das zweite Beispiel in diesem Artikel. Diese Kontrollen machen den EA robuster und zuverlässiger. Auch seine Struktur wird sich leicht verändern. Die Funktion OnTick wird nicht mehr als alleiniges Zentrum der Handelsgeschäftslogik dienen. Zusätzliche Funktionen werden erscheinen, um den Code besser zu organisieren. Vor allem aber wird der EA in die Lage versetzt, Fehler bei der Auftragserteilung (z. B. Requotes) zu behandeln. Um dies zu erreichen, werden wir OnTick so umstrukturieren, dass auf jede „Phase“ der EA-Operation (z.B. Platzierung eines Trades, Warten auf einen neuen Balken, Berechnung der Losgröße...) direkt zugegriffen werden kann, ohne dass andere Phasen durchlaufen werden müssen. Wir werden auch das TradeTransaction-Ereignis verwenden, um Serverantworten zu verfolgen. Das Ergebnis wird eine funktional organisierte, leicht modifizierbare Vorlage sein, mit der Sie Ihre eigenen EAs beliebiger Komplexität erstellen können - ohne tief in OOP einzutauchen, aber voll funktionsfähig und produktionsbereit.
Liste der früheren Artikel der Reihe:
- MQL5 beherrschen, vom Anfänger bis zum Profi (Teil I): Erste Schritte der Programmierung
- MQL5 beherrschen, vom Anfänger zum Profi (Teil II): Grundlegende Datentypen und Verwendung von Variablen
- MQL5 beherrschen, vom Anfänger bis zum Profi (Teil IIII): Komplexe Datentypen und Include-Dateien
- MQL5 beherrschen, vom Anfänger bis zum Profi (Teil IV): Über Arrays, Funktionen und globale Terminalvariablen
- MQL5 beherrschen, vom Anfänger zum Profi (Teil V): Grundlegende Kontrollflussoperatoren (in diesem Artikel werden auch die Grundsätze für die Erstellung nutzerdefinierter Indikatoren erörtert)
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15727
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- 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.
ausgezeichnete klare Artikel und eine Menge Dinge erklärt werden - vielen Dank. Vor allem am Ende, wie man Indikatoren durch Klassen verwenden! Cool! Ich werde in Betracht ziehen, Prototypen in meiner Entwicklung von einfachen TS zu testen.