English Русский Español 日本語 Português
preview
MQL5 beherrschen, vom Anfänger bis zum Profi (Teil IV): Grundlagen der Entwicklung von Expert Advisors

MQL5 beherrschen, vom Anfänger bis zum Profi (Teil IV): Grundlagen der Entwicklung von Expert Advisors

MetaTrader 5Beispiele |
20 3
Oleh Fedorov
Oleh Fedorov

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).

MQL-Assistent - erster Bildschirm

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).

Erstellen eines Expert Advisors - dritter Bildschirm des Assistenten

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.

EA-Funktionen für Strategietester

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:

Der Handelsprozess wird zwischen dem Terminal und dem Server aufgeteilt

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.

Prinzip der Signal-Kerzen-Filterung

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:

  1. 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.
  2. 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.
  3. Wir verwenden die Daten aus gefüllten Feldern.
  4. 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.

Flussdiagramm der wichtigsten Verzweigungen der Handelslogik

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:

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15727

Beigefügte Dateien |
MADeals.mq5 (20.77 KB)
TrendPendings.mq5 (12.84 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (3)
Utkir Khayrullaev
Utkir Khayrullaev | 7 Jan. 2025 in 11:06
Vielen Dank für Ihre harte Arbeit! Viele Dinge wurden klar und einfach.
Roman Shiredchenko
Roman Shiredchenko | 19 Feb. 2025 in 14:33

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.

Oleh Fedorov
Oleh Fedorov | 19 März 2025 in 11:39
Zum Wohl! Freut mich, dass es geholfen hat.
Neuronale Netze im Handel: Verbesserung des Wirkungsgrads des Transformers durch Verringerung der Schärfe (SAMformer) Neuronale Netze im Handel: Verbesserung des Wirkungsgrads des Transformers durch Verringerung der Schärfe (SAMformer)
Das Training von Transformer-Modellen erfordert große Datenmengen und ist oft schwierig, da die Modelle nicht gut auf kleine Datensätze verallgemeinert werden können. Der SAMformer-Rahmen hilft bei der Lösung dieses Problems, indem er schlechte lokale Minima vermeidet. Dadurch wird die Effizienz der Modelle auch bei begrenzten Trainingsdaten verbessert.
Gleitender Durchschnitt in MQL5 von Anfang an: Schlicht und einfach Gleitender Durchschnitt in MQL5 von Anfang an: Schlicht und einfach
Anhand einfacher Beispiele werden wir die Grundsätze der Berechnung gleitender Durchschnitte untersuchen und lernen, wie man die Berechnung von Indikatoren, einschließlich gleitender Durchschnitte, optimieren kann.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Entwicklung eines Expert Advisors für mehrere Währungen (Teil 20): Ordnung in den Ablauf der automatischen Projektoptimierungsphasen bringen (I) Entwicklung eines Expert Advisors für mehrere Währungen (Teil 20): Ordnung in den Ablauf der automatischen Projektoptimierungsphasen bringen (I)
Wir haben bereits eine ganze Reihe von Komponenten entwickelt, die bei der automatischen Optimierung helfen. Bei der Erstellung folgten wir der traditionellen zyklischen Struktur: von der Erstellung eines minimalen funktionierenden Codes bis hin zum Refactoring und dem Erhalt eines verbesserten Codes. Es ist an der Zeit, mit dem Aufräumen unserer Datenbank zu beginnen, die auch eine Schlüsselkomponente in dem von uns geschaffenen System ist.