MetaTrader 5 herunterladen

MetaEditor: Vorlagen als Verlässlicher Punkt

10 März 2016, 14:27
MetaQuotes Software Corp.
0
389


Gebt mir einen Punkt, wo ich hintreten kann, und ich bewege die Erde.
Archimedes

Einführung

Als Programmiersprache stellt MQL4 eine große Mehrheit der Anwendungen für einen einmal und geschriebenen und korrigierten Code dar. Diese Anwendungen umfassen:

  • die include .mqh Dateien. Sie können in diesen Dateien alle notwendigen Funktionen und Konstanten speichern, die Ihrem Code hinzugefügt werden können mit der #include Anweisung,
  • Funktion-Bibliotheken, die als normale MQL4 Programme kompiliert werden können un Ihrem Code im Echtzeit-Modus hinzugefügt werden können mit der #import Anweisung, und
  • benutzerdefinierte Indikatoren zum Ausführen ökonomischer Berechnungen auf Zeitreihen Arrays. Sie werde im Echtzeit-Modus aufgerufen mit der Funktion iCustom().

Allerdings wissen nicht alle Entwickler davon, und demzufolge verwenden nicht alle von ihnen solch einen leistungsfähigen Mechanismus zum einfachen und zuverlässigen Schreiben eines Expert Advisor, als vorgefertigte Vorlagen erstellt mit Expert Advisor Wizard. Dieser Artikel enthüllt einige Vorteile dieses Werkzeugs.



Was ist eine Vorlage?

Was ist eine Vorlage in Bezug auf MetaEditor? Vorlagen sind diese .mqt Dateien, die in dem Ordner mit dem Namen Templates gespeichert sind, in dem Terminal Root_Verzeichnis_MetaEditor_4/Experts/Templates.

In dem obigen Bild sehen wir 10 solcher Dateien. Die grundlegenden sind:
  • Expert.mqt — eine Vorlage zum Erstellen von Expert Advisors,
  • Script.mqt —- eine Vorlage zum Erstellen von Skripten,
  • Include.mqt — eine Vorlage zum Erstellen von Skripten,
  • indicator.mqt — eine Vorlage zum Erstellen von Indikatoren,
  • Library.mqt — eine Vorlage zum Erstellen einer Bibliothek.

Die anderen Vorlagen (Alligator.mqt, usw.) sind an die Erstellung von Indikatoren gerichtet, entsprechend dem im Vorlagen-Namen angegebenen Indikator-Namen. Öffnen wir beispielsweise die Vorlage Libary.mqt mit dem MetaEditor. Dazu müssen wir "All Files(*.*)" in dem Feld "Dateityp" festlegen:

Wir werden sehen, dass der Inhalt der Datei nicht sehr groß ist.

<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

Die ersten drei Zeilen informieren darüber, welche Art von Dateien zu der Vorlage gehören:

<expert>
type=LIBRARY_ADVISOR
</expert>

Die Zeile type=LIBRARY_ADVISOR informiert MetaEditor augenscheinlich darüber, dass diese Datei eine Bibliothek-Vorlage ist. MetaEditor wird die benötigte Vorlage Ihrer Auswahl entsprechend verwenden: EA, benutzerdefinierter Indikator, usw..


Dann folgt das stellvertretende Makro #header#, das, in der Tat, von dem Namen ersetzt wird, den Sie selbst gewählt haben, als Sie den Anweisungen des Expert Advisor Wizard gefolgt sind.


Zum Beispiel, wenn Sie Ihren EA My_Best_Expert_Advisor benennen, werden die folgenden Zeilen anstatt des #header# Makro gebildet:

//+------------------------------------------------------------------+
//|                                       My_Best_Expert_Advisor.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+

In diesem Block aus Kommentaren, sehen wir die Informationen über den EA-Namen, den Autor und den Link zu der Webseite. Alle diese Daten wurden in die entsprechenden Felder des Expert Advisor Wizard eingegeben. Die nächsten Zeilen:

#property copyright "#copyright#"
#property link      "#link#"

enthalten die Makros #copyright# und #link#, die offensichtlich den Feldern im Expert Advisor Wizard entsprechen.



Wie verwendet man es?

Wir sind vor allem an der Möglichkeit interessiert in eine Vorlage unseren eigenen Code einzufügen, der bei jedem Erstellen eines EA automatisch erzeugt wird. Zum Beispiel, wenn Sie einige Erfahrung in der Erstellung von Skripten haben, wissen Sie, dass Skripte in einem Chart nur einmal gestartet werden sollen. Manchmal ist es notwendig einige externe Parameter für den Skript-Algorithmus zu bestimmen, womit es notwendig werden kann diese Parameter im Moment des Starts des Skripts zu ändern. Expert Advisor Wizard bietet diese Möglichkeit nicht standardmäßig. Dies kann jedoch mit einer Anweisung für den Compiler erreicht werden:

#property show_inputs

Es ist ausreichend diese Zeile zu jedem Skript-Code hinzuzufügen, und ein Fenster mit Parametern wird angezeigt. Erläutern wir wie es zu tun ist, dass diese Zeile automatisch durch den Expert Advisor Wizard in jedem neu erstellten Skript hinzugefügt wird. Dazu öffnen wir eine EA-Vorlage:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

und fügen nur eine Zeile vor dem Makro ein, das externe Parameter ersetzt (#extern_variables#). Unten ist der Code, den wir erhalten:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Dann speichern wir die Änderungen und beginnen ein Skript mit den Expert Advisor Wizard zu schreiben. Nennen wir das Skript TestScript und, nach dem Drücken von "Fertig"

erhalten wir das Ergebnis
//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Das neue Skript unterscheidet sich von dem durch die alte Vorlage erstellten nur in einer Zeile, aber es hilft uns sehr viel. Jetzt müssen wir die Zeile:

#property show_inputs

nicht manuell in jedes Skript eingeben, sie wird automatisch eingefügt. Auf jeden Fall wird es viel einfacher sein sie zu kommentieren (wenn wir sie nicht brauchen), als sie jedes Mal neu einzugeben. Fügen wir manuell nur eine Zeile hinzu, die einen Parameter mit dem Namen entryParam enthält.

//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Nun kompilieren wir es und starten es auf dem Chart. Das Skript läuft jetzt nicht sofort, aber es ermöglicht uns externe Parameter zu ändern:


Dies ist das einfachste Beispiel, wie man Vorlagen verwendet. Die Idee besteht darin, dass wir einfach die Code-Zeilen einer Vorlage hinzufügen, die wir am meisten bei der Erstellung eines neuen MQL4 Programms brauchen. Dann werden diese Code-Zeilen jedes Mal automatisch hinzugefügt, wenn Sie den Expert Advisor Wizard verwenden.

Wichtig: Alle Vorlagen werden neu geschrieben, wenn Sie das MetaTrader 4 Client Terminal installieren. Sie müssen also selber Kopien Ihrer Vorlagen machen.


Wo hilft uns das?

Die Möglichkeit vorgefertigte Vorlagen zu verwenden, ermöglicht uns wirklich bei dem Schreiben eines EA so genau wie möglich anzusetzen. Wie schreiben Einsteiger ihre EAs? Betrachten wir beispielsweise eine Strategie, die auf dem Schnittpunkt von zwei Moving Averages basiert. Unten ist eine einfache technische Anforderung, um einen EA auf dieser Strategie zu erstellen:

  1. Erhalten der Werte von kurzer-Periode und langer-Periode Moving Averages.
  2. Überprüfen, ob sie sich kreuzen.
  3. Wenn die kurze die lange von unten nach oben kreuzt, dann kaufen (Buy), StopLoss=N Punkte.
  4. Wenn die kurze die lange von oben nach unten kreuzt, dann verkaufen (Sell), StopLoss=N Punkte.
  5. Wenn eine Long-Position offen ist, schließen bei einem Verkaufen-Signal.
  6. Wenn eine offene Short-Position vorhanden ist, schließen bei einem Kaufen-Signal.

Nun, der EA ist fertig und optimiert. Nun kommt der Gedanke einen neuen Parameter hinzuzufügen, zum Beispiel TakeProfit = S Punkte. Oder einen Handelsalarm-Block zu ersetzen: mit den Werten aus Stochastik anstatt denen des Moving Average Schnittpunkts. Und so weiter. Für jede neue Alternative für den EA, werden wir einige Änderungen in dem Code machen müssen, und plötzlich stellt sich heraus, dass dies nicht sehr einfach ist. An einem bestimmten Punkt erkennen wir, dass wir einfach einen neuen EA mit all diesen Funktionen schreiben müssen. Natürlich, erfahrene Expert Advisor Autoren sind auf diesem Weg bereits weise geworden, und verwenden ihre bevorzugten Methoden und Ansätze. Die am Anfang dieses Artikels aufgeführten Techniken helfen ihnen, nämlich (wiederholen wir):

  • include Dateien,
  • Bibliotheken,
  • benutzerdefinierte Indikatoren.

Sie erzeugen jedoch nicht ihre maximale Wirkung ohne die Verwendung einer Vorlage. Nun, was ist ein Vorlage-EA und was sollte er sein um effektiv zu sein? Nach meiner Interpretation ist ein Vorlage-EA ein universeller Prototyp eines echten EA, jedoch ohne besondere Eigenschaften. Tatsächlich ist es eine bestimmte SuperClass, die einige rein virtuelle Funktionen (im Sinne der objektorientierten Programmierung) oder Schnittstellen (auf Java Basis) hat. Die Vorlage beschreibt, was der EA macht, aber er beschreibt nicht wie er es macht. Aus diesem Grund müssen wir, bevor wir eine Vorlage erstellen, die gewünschte Funktionalität analysieren. Wir müssen zuerst die Struktur unseres EA erstellen.



Expert Advisor Struktur

Nun, was sollte ein Expert Advisor machen? In der aller ersten Einschätzung, ein EA handelt, was bedeutet, er führt Handelsoperationen aus - macht Handelsanfragen zum Öffnen oder Schließen von Handelspositionen. Darüber hinaus modifiziert ein EA die Ebenen von StopLoss und TakeProfit der bestehenden offenen Positionen (wenn nötig), platziert oder löscht Pending Orders, modifiziert die Ebenen von StopLoss und TakeProfit für Pending Orders. Eine einfache Struktur beginnt zu entstehen:

  1. Block zum Empfang von Handelssignalen.
  2. Öffnen einer Markt-Order oder platzieren einer schwebenden (Pending).
  3. Schließen offener Positionen und Schließen der schwebenden (Pending).

Somit haben wir die allgemeine Vorstellung über automatisiertes Trading in drei Teilprobleme zerlegt. Wir wissen jetzt, dass wir eine Funktion (Block 1) umsetzen müssen, die Entscheidungen auf das aktuelle Signal trifft: kaufen (buy), verkaufen (sell) oder aus dem Markt bleiben (keine Handelssignal). Dann (Block 2) auf Basis des Handelssignals, können wir eine Position öffnen oder eine Pending Order platzieren. Warum "können" nicht "müssen"? Weil der Block zum Öffnen von Positionen die Handelssignale betrachtet, aber nicht verpflichtet ist denen zu folge und Trades zu machen. Zum Beispiel, wir haben bereits einige offene Positionen. Dann kann das Öffnen einer neuen das Handelskonto einem übermäßigen Risiko aussetzen. Nun, die letzte, aber nicht unwichtigste, Funktionalität eines EA (Block 3) - Schließen offener Positionen und löschen von Pending Odern - ist auch unabhängig. Wir können Positionen über das Handelssignal schließen oder aus anderen Überlegungen (Haltedauer der Position, Ende des Handelstags, usw.). Somit erscheint die Unterteilung der EA-Logik in drei Teile nicht unbegründet.



Weitere Verfeinerung

Wenn wir darüber nachdenken, können wir zu dem Schluss kommen, dass die obige Struktur nicht vollständig ist. Nehmen wir an jedem Block weitere Verfeinerungen vor.

Erstens, wir können Handelssignale für jeden eingehenden Tick berechnen (ein Tick ist die Tatsache der Kursänderung) oder diese Berechnung nur beim Öffnen von jedem neuen Balken durchführen. Im Moment kann das unwichtig sein, aber es kann schwierig werden den Code zukünftig zu ändern, wenn wir das nicht im Voraus bereitstellen.

Zweitens, wir können auch den Zeitrahmen dieser Balken ändern: Obwohl wir darauf ausgerichtet sind, dass unser EA im Moment auf 1-Stunde Charts handelt, könnten wir es in der Zukunft ändern wollen, so dass er z.B. auf 4-Stunden Charts handeln wird. Wir sollten diesen Punkt auch berücksichtigen.

Nun, drittens, sollten wir die Handelssignale vereinheitlichen. Wir benötigen eine strenge Systematisierung. Da wir nur drei Typen von Handelssignalen haben, lassen wir den Block, der sie uns anzeigt, streng beschriebene Konstanten verwenden, am besten in Bezug auf MQL4:

  • OP_BUY — Kaufsignal,
  • OP_SELL — Verkaufsignal
  • OP_BALANCE — kein Signal, aus dem Markt bleiben.

Somit wird der erste Block wie folgt aussehen:

/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/

Dann benötigen wir einen Block für die Berechnung wichtiger Ebenen zum Öffnen neuer Ordern oder dem Ändern von bestehenden. Dies ist auch eine Funktionalität, die nicht von anderen Teilen des Codes abhängt.

/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/

Wir zeigen hier nicht die Berechnung der Positionsgröße, Lot, obwohl dies auch ein Parameter ist. Es ist jedoch möglich, dass wir die Funktion zur Berechnung der zu-öffnenden-Positionsgröße in einem separaten Block platzieren möchten, den wir ganz konventionell Money-Management Block nennen können.

Der nächste Block ist das Ändern der Order-Ebenen. Er muss ebenfalls unabhängig sein, weil wir beide Ebenen, StopLoss und TakeProfit, ändern können. Darüber hinaus können Sie sei bei jedem Tick oder nur beim Öffnen eines neuen Balken ändern. Wenn wir dem EA diese Flexibilität bereitstellen, wird es für uns viel einfacher ihn in der Zukunft zu optimieren.

/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/

Bitte beachten Sie, dass wir den geänderten Zeitrahmen beim Öffnen eines neuen Balken auf die gleiche Weise wie in Block #1 (Empfangen der Handelssignale) angeben können.

Der nächste Block, ist der Block zum Schließen von Ordern. Wir können Ordern aus zwei Gründen schließen: Zeit und Signal. In diesem Fall meinen wir beides, den Erhalt eines Handelssignals in die entgegengesetzte Richtung und die Änderung der Bedingungen, welche die Notwendigkeit des Haltens der Position implizieren.

/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/

Ich habe den Block zum Schließen von Pending Ordern von dem Block zum Schließen offener Positionen getrennt, weil die Priorität zum schließen offener Positionen manchmal viel höher ist (zum Beispiel, der Kurs beginnt sich stark gegen die offene Position zu bewegen und wir müssen sie so schnell wie möglich schließen, alles andere in die Zukunft verschiebend), als das Löschen einer Pending Order (in der Regel steht die Pending Order in einem eher großen Abstand zum aktuellen Kurs).

/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/

Alles ist absolut klar im Block der Pending Ordern, also sind keine zusätzlichen Kommentare nötig.

Der letzte Block ist der zum öffnen und platzieren von Pending Ordern.

/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

Seltsam, aber er stellt sich als der letzte heraus. Wenn wir darüber nachdenken, ist diese EA-Struktur ziemlich logisch: wir sollten zuerst alle Vorbereitungen treffen (alles schließen, was geschlossen sein muss, und alle redundanten Pending Ordern löschen), um keine Probleme in der Zukunft zu erhalten, und erstellen dann nur neue Ordern und akive Eingriffe in den Markt.

/**
      7. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

Allerdings enthält dieser Block auch einige Variationen. Sie können Positionen zum Marktpreis öffnen (Kaufen zum Ask-Kurs oder Verkaufen zum Bid-Kurs), Sie können eine Pending Order mit Ablaufdatum und Zeit (die abgelaufene Pending Order wird durch den Handelsserver gelöscht) oder ohne jeden Ablauf bis zur Stornierung (bis wir sie selbst löschen).

Alle obigen Ergebnisse in der folgenden EA-Vorlage Struktur:

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/
 
 
/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/
 
/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/
 
 
/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/
 
//----
   return(0);
  }
//+------------------------------------------------------------------+

Wir können unsere Vorlage, so wie sie jetzt ist bereits speichern. Diese Vorlage kann hilfreich sein, auch wenn sie keine zusätzlichen Funktionen enthält. Es ist eine textuelle Beschreibung aller notwendigen Blöcke zum Schreiben eines EA. Jeder Expert Advisor Autor kann seine oder ihre Version der Struktur hinzufügen, oder sie sogar vollständig verändern. In jedem Fall jedoch, wenn sie einen neuen EA mit dem EA Wizard erstellen, werden Sie eine textuelle Beschreibung der Struktur sehen, wodurch Sie nicht vergessen wie und in welcher Reihenfolge Sie Ihren Code umsetzen sollten. Aber wir können zu noch größeren Längen gehen.



Aufnahme der Realisierung

Jetzt können wir anfangen die EA-Vorlage-Struktur mit verallgemeinerten Funktionen zu füllen. Diese Funktionen werden nicht die Umsetzung notwendiger Funktionen beschreiben, dennoch werden sie die Standard-Interaktion zwischen der Vorlagen-Struktur und den endgültigen Funktionen des Benutzers beschreiben. Somit werden wir keine bestimmte Funktion zum Erhalt eines Handelssignals beschreiben, sondern wir werden beschreiben wo, in einer verallgemeinerten Funktion, der Benutzer den bestimmten Funktonsaufruf einfügen muss-



Funktion des Erscheinens neuer Balken für den Vorgegebenen Zeitrahmen

Da wir bereits entschieden haben, dass Handelssignale in Block #1 angezeigt werden, entweder bei jedem neuen Tick oder dem Erscheinen eines neuen Balken auf einem durch den Benutzer vorgegebenen Zeitrahmen, benötigen wir zuerst eine Funktion, die uns über das Ereignis informiert: "ein neuer Balken ist erschienen im Zeitrahmen". Die Funktion ist einfach und sieht aus wie folgt:

//+----------------------------------------------------------------------+
//|  It returns the sign of a new bar appearance for the given timeframe |
//+----------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // the array contains open time of the current (zero) bar
   // for 7 (seven) timeframes
   static datetime _sTime[7];  
   int i=6;
 
   switch (timeFrame) 
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);   
   }


Einstellung des Zeitrahmen und Überprüung auf Richtigkeit

Die Funktion isNewBar() übernimmt den Zeitrahmen-Wert als Parameter und als die Anzahl der Minuten. Führen wir eine neue externe Variable ein, TradeSignalBarPeriod, die den benötigten Zeitrahmen angeben wird:

extern int  TradeSignalBarPeriod       = 0;

Standardmäßig ist sie gleich Null. Das bedeutet, der EA wird neue Balken im Trading immer auf "seinem" Zeitrahmen verfolgen, d.h., auf dem Zeitrahmen des Charts, an dem der EA angehangen ist, wenn er im Echtzeit-Modus handelt, oder gleich dem Zeitrahmen, auf dem er getestet wird. Der Benutzer kann jede vordefinierte Konstante zur Auswahl eines Zeitrahmens angeben. Allerdings kann es Fehler enthalten. Also fügen wir eine Funktion hinzu, die den Wert der Variable TradeSignalBarPeriod prüft und, wenn notwendig, Fehler korrigiert, wenn der Wert falsch ist.

//+------------------------------------------------------------------+
//|  if the timeframe is specified incorrect, it returns zero        |
//+------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // allowed timeframe, don't do anything
      case PERIOD_H4:  break;  // allowed timeframe, don't do anything
      case PERIOD_H1:  break;  // allowed timeframe, don't do anything
      case PERIOD_M30: break;  // allowed timeframe, don't do anything
      case PERIOD_M15: break;  // allowed timeframe, don't do anything
      case PERIOD_M5:  break;  // allowed timeframe, don't do anything
      case PERIOD_M1:  break;  // allowed timeframe, don't do anything
      default: res=Period();   // incorrect timeframe, set a default one
      }
//----
   return(res);      
   }

Wenn keine Fehler auftreten, wird die Funktion nichts tun. Wir verwenden sie nur einmal, direkt am Anfang der EA Initialisierung. So setzen wir ihren Aufruf in die Funktion init():

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // remind the EA settings
//----
   return(0);
  }

Wie Sie außerdem sehen können, ist dort eine neue benutzerdefinierte Funktion StartMessage() in dem Block init(). Sie wird weiter unten beschrieben, ich werde hier nur erklären wofür sie ist. Expert Advisors sammeln große Mengen an Setup-Parametern, die man nach einer bestimmten Zeit einfach vergisst. Die Funktion StartMessage() dient dem Bereitstellen einer minimalen Beschreibung der EA-Parameter bei der Aufnahme seiner Arbeit auf einem Handelskonto oder bei Backtests. Sie können diese Parameter später in der Protokolldatei des Terminals oder des Testers lesen. Ich vermute, eine solche Funktion ist in jedem EA nützlich, weshalb ich sie auch in die EA-Vorlage eingefügt habe.



Erhalten einer Handelsbenachrichtigung

Jetzt können wir zu dem Block kommen, der Handelsbenachrichtigungen erzeugt. Allerdings, bevor wir weitermachen, betrachten wir einige weitere Situationen. Wenn wir einen EA erstellt haben, möchten wir manchmal wissen, wie er an bestimmten Tagen der Woche arbeitet. Zum Beispiel, wir möchten nur am Mittwoch handeln, also muss der EA 'ruhig bleiben'. So können wir die Möglichkeit bieten Handelstage in unserem EA im Voraus zu wählen. Erstellen wir eine externe Integer-Variable TradeDay. Ist der Wert gleich Null (Sonntag=0, Montag=1, usw.), handeln wir wie immer. Ist er allerdings anders als Null, dann handeln wir nur an dem bestimmten Tag der Woche.

Darüber hinaus möchten wir vielleicht eine entgegengesetzte Operation ausführen. Dann müssen wir den Tag der Woche bestimmen, an dem es unserem EA verboten ist zu handeln. Erstellen wir eine weitere logische externe Variable ReversDay. Ist sie gleich 'false', dann ist die Logik normal (TradeDay punktet an dem Handelstag). Ist sie 'true', werden die Bedingungen umgekehrt und TradeDay wird an dem Tag der Woche punkten an dem wir nicht handeln. Zum Beispiel, TradeDay=5 und ReversDay=true bedeutet, dass wir am Freitag nicht handeln (Freitag=5).

Nun, der letzte Trick, den wir bereitstellen müssen, ist die Umkehr des Handelssystems. Zum Beispiel, vielleicht wollen wir unsere Strategie in der Zukunft vollständig umkehren (Tauschen von Kaufen für Verkaufen und Verkaufen für Kaufen) und beobachten was passiert. Wenn wir die Möglichkeit von Anfang an bereitstellen, sind wir in der Lage dies einige Tage zu nutzen. Dafür ist es ausreichend einfach eine weitere logische externe Variable ReverseTradeSignal einzuführen. Ist sie gleich 'false' (standardmäßig), arbeitet die ursprüngliche Strategie. Ist ihr Wert jedoch auf 'true' gesetzt, wird das System umgekehrt. Somit sehen die hinzugefügten externen Variablen wie folgt aus:

// constant "off market"
#define OP_BALANCE 6
 
// trade day setting
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// trading system reverse
extern bool ReversTradeSignal          = false;
 
//  trading frequency settings
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

Ich habe die OP_BALANCE Konstante hinzugefügt, die bedeutet, dass kein Handelssignal vorhanden ist. Darüber hinaus gibt es eine weitere logische Variable, TradeSignalEveryTick. Ist sie wahr (true), bedeutet dies den Erhalt von Handelssignalen bei jedem Tick. Wie Sie sehen können, haben wir noch keine Code-Zeile unseres Handelssystems geschrieben, aber wir haben bereits einige Variablen eingeführt, deren Zweck nach einem bestimmten Zeitraum vergessen werden kann. Aus diesem Grund haben wir außerdem eine informierende Funktion geschrieben, StartMessage():

//+------------------------------------------------------------------+
//| It shows a message about EA settings                             |
//+------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("Expert Advisor ",WindowExpertName()," has the following settings:");
 
   if (TradeSignalEveryTick)
      array[1]="1) trade signals are considered at every tick; ";
   else       
      array[1]=StringConcatenate("1)trade signals are considered at each bar with the period of ",
                        TradeSignalBarPeriod," minutes;");
 
   if (TradeDay==0)   // trade day is not specified
      array[2]="2)trading is allowed on any day of week; ";
   else 
      {
      if (ReversDay) //  no trading allowed on the specified day
         array[2]=StringConcatenate("2)trading is allowed on all days but day number ",TradeDay);
      else           //  trading is allowed only on the specified day of week
         array[2]=StringConcatenate("2)trading is allowed only on day number ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

Diese Funktion zeigt den EA-Namen und das Minimum an Informationen über seine Einstellungen. Sie können weitere Zeilen in das Array 'array[]' hinzufügen, wenn Sie einen bestimmten EA schreiben.

Nun sind wir bereit eine Funktion zu erstellen, die das Handelssignal erzeugt. Die folgenden Eistellungen werden an diese Funktion übergeben:

  • Berechnen eines Handelssignals bei jedem Tick oder Öffnen eines neuen Balkens auf dem bestimmten Zeitrahmen,
  • ob das Handelssignal umgekehrt wird oder nicht,
  • ob der Wochentag berücksichtigt wird oder nicht,
  • ob der Handelstag in einen nicht-Handelstag gedreht wird oder nicht.
//+------------------------------------------------------------------+
//| receive a trade signal                                           |
//+------------------------------------------------------------------+
// tradeDay - day of week, on which we trade; if it is equal to zero, then we trade all days
//
// useReversTradeDay - if it is equal to 'true', then trading days become non-trading days
//
// everyTick  - if it is equal to zero, the function will calculate a signal for every tick
//
// period - if everyTick==false, then it is calculated as soon as a new bar with this period appears
int getTradeSignal(int tradeDay,          // it is usually equal to 0
                   bool useReversTradeDay,// it is usually equal to 'false'
                   bool everyTick,        // signal is calculated at every tick
                    int period            // working period for indicators and signals
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // we will consider days of week
      {
      // day, on which we don't trade
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // we trade on all days, except the day equal to tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // if we don't take trade signals at every tick
      { // nor we have any new bar on the timeframe of 'period' minutes,
      if (!isNewBar(period)) return(signal); // then exit with an empty signal
      }
 
// Fill function yourFunction() with your code/algorithm
   signal=yourFunction(period);
 
 
   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);   
   }

Wie Sie sehen können, haben wir eine mehrzeilige Bindung um eine Codezeile gebaut:

  signal=yourFunction();

Wenn wir unsere eigene Strategie schreiben, werden wir unsere eigene Funktion schreiben (anstatt yourFunction()), die einen der drei folgenden Werte zurückgeben muss:

  • OP_BUY — Kaufen
  • OP_SELL —- Verkaufen
  • OP_BALANCE — kein Signal

Block Identifizierung "FREUND oder FEIND "

Ein weiterer Stolperpunkt, wenn Sie einen EA schreiben, ist das Erkennen des Tickets der Order, mit denen der EA arbeitet. Dieses Problem wird in der Regel mit den Mitteln von Zyklen for() gelöst, in denen ein Ticket mit der OrderSelect() Funktion ausgewählt wird. Sie sollten allerdings daran denken, dass wir "freundliche" Ordern vielleicht mehr als einmal brauchen. In einigen Fällen, müssen wir StopLoss ändern. In anderen Fällen, müssen wir Markt-Ordern schließen. In dritten Fällen müssen wir vielleicht einige Pending Ordern löschen.

Aus Gründen der standardmäßigen "freundlichen" Order-Verarbeitung, haben wir beschlossen das Array der "freundlichen" Ordern mit allen notwendigen Parametern zu füllen, jedes Mal am Anfang der start() Funktion. For the reasons of standard "friendly" orders processing, we decided to fill the array of "friendly" orders with all necessary parameters every time at the beginning of the start() function. Dazu setzen wir zwei Arrays auf globaler Ebene ein:

double Tickets[][9];// array to store information about "friendly" orders:
// Tickets[][0] - ticket number
// Tickets[][1] - order type
// Tickets[][2] - lots
// Tickets[][3] - open price
// Tickets[][4] - Stop Loss
// Tickets[][5] - TakeProfit
// Tickets[][6] - MagicNumber
// Tickets[][7] - expiration time
// Tickets[][8] - open time
// 
 
string CommentsTicket[1000][2];         //array to store symbols and comments on orders. 1000 lines would be enough
// CommentsTicket[][0] - symbol name
// CommentsTicket[][1] - comment on the order

Die Funktion, die diese Arrays füllt, ist eher einfach:

//+------------------------------------------------------------------+
//| It prepares the array of "friendly" orders                       |
//+------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // filling counter
   
   // let's make the array size large not to allocate memory for it every time
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// if the special function has not detected the order as "friendly" one,
            // make usual checks
 
            // check for Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  other checks...
            // ....
         
            // last check, that for MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // we haven't been stopped anywhere, so this is a "friendly" order
         //  fill out the array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // let's increase the counter of filled "friendly" orders
         count++;
         }
      }
   
   // and now let's truncate the array sizes to the minimum essential ones 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;   
   }

Als Beispiel habe ich eine minimale Beschreibung von Kriterien gegeben, die es uns ermöglichen "freundliche" Ordern von "feindlichen" zu unterscheiden, in einer verallgemeinerten Funktion mit OrderMagicNumber() und OrderSymbol(). Wenn wir alle Ordern in dem Zyklus durchsuchen, überspringen wir die "feindlichen" und füllen die Arrays mit den Daten von "Freunden". Um die Funktion flexibler zu machen, rufen wir eine weitere benutzerdefinierte Funktion in ihr auf, in der Sie einige andere Kriterien beschreiben können, um zu erkennen, ob die Order "Freund oder Feind" ist. In diesem Fall, habe ich eine Überprüfung der globalen Variablen hinzugefügt: Ist eine globale Variable mit dem Namen unseres EAs im Terminal und ihr Wert ist gleich dem Ticket einer bestimmten Order, wird die Order auch als ein "Freund" betrachtet. Dies wird hilfreich sein, wenn die Position beispielsweise manuell geöffnet wurde und wir möchten sie nun von unserem EA verarbeiten lassen.

//| It returns 'true', if a Global variable with this name is available|
//+--------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // we don't use global variables in test mode!
   if (IsTesting()) return(true);// immediately return the positive result
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// there is such a global variable
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // found a GV with the value equal to 'ticket'
               res=true;  // so it is a "friendly" order
               break;
               }
         }
      }
//----
   return(res);   
   }

Wir haben einen großen Teil unserer Vorbereitungen bereits abgeschlossen. Nun sieht der Anfang der start() Funktion aus wie folgt:

 
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always update the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain the arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Each bar of the preset period    (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/



Berechnung der Stop Loss/Take Profit Ebenen für Bestehende Ordern

Jetzt können wir in die nächste Phase gehen, Wir haben ein zweidimensionales Array von "freundlichen" Ordern erhalten, Tickets[][9]. Berechnen wir nun die neuen Ebenen von SL und Tp, mit dem Wissen über die aktuellen Eigenschaften von jeder Order. Die berechneten neuen Werte sollten irgendwo gespeichert werden, also erstellen wir zu diesem Zweck ein weiteres globales Array:

double newSL_and_TP[][5];// array to store the new values of SL and TP
// newSL_and_TP[][0] - ticket number to be controlled
// newSL_and_TP[][1] - new values of SL
// newSL_and_TP[][2] - new values of TP
// newSL_and_TP[][3] - new Open price (for pending orders)
// newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Erläuterungen sollten nur benötigt werden für die Parameter von:

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Wir werden diese Parameter zum Ändern von Markt- und Pending-Ordern in separaten Funktionen benötigen. Nun, erstellen wir eine Funktion die Ticketsp[][9] übernimmt und das Zeichen der Systemumkehr und das neue Array newSL_and_TP[][5] mit Werten füllt (die Skalierung in der zweiten Dimension ist in den Klammern angegeben). Die Funktion wird wie folgt erscheinen:

   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

Der erste Parameter, ReversTradeSignal, kann die Werte 'true' (d.h., das System ist umgekehrt) oder 'false' haben. Der zweite Parameter ist das Array "freundlicher" Ordern (wenn es keine Ordern gibt, ist er gleich Null). Der dritte Parameter ist ein Array, das mit dieser Funktion ausgefüllt wird. Die Funktion selbst ist unten angegeben:

//+------------------------------------------------------------------+
//|  It creates an array with the new values of SL and TP            |
//+------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // system reverse 
                      double arrayTickets[][9], // array of "friendly" orders
                      double &amp; arraySL_TP[][5]  // new values of SL, TP and openPrice
                      )
   {
   // first of all, zeroize the obtained array !!
   ArrayResize(arraySL_TP,0);
   // if the order array is empty, then exit   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;    
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      if (ReversTrade) //  reverse all levels considering the spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, obtain a new open price
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }         
//----
   return;
   }

Zunächst legen wir die Null-Größe des Array fest, dass die neuen Werte von SL und TP enthalten wird. Dann sollten wir die Größe des Arrays erfahren, das "freundliche" Ordern enthält.

   int   size=ArrayRange(arrayTickets,0);
   // if the order array is empty, then exit   
   if (size==0) return;

Wenn es keine Ordern gibt, muss dort nichts getan werden, also verlassen wir mit einem durch das Array arraySL_TP[][] Ausgangssignal der Null-Größe. Die Arrays der Null-Größe deuten an, nicht mit diesen Arrays zu machen. Dann organisieren wir einen Zyklus, der alle zur Verarbeitung übergebenen Ordern anzeigt:

 for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, then receive a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

Wir berechnen darin die Werte von oldSL, oldTP and oldOpenPrice (weil wir die Eröffnungsebenen von Pending Ordern ändern können) und übergeben die Werte als Parameter in Funktionen, um die neuen Werte von SL, TP und OpenPrice zu berechnen.

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // if it is a pending order, obtain a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

dann ist alles was wir tun müssen, die Größe des Arrays der neuen Werte von SL, TP, und OpenPrice um eins zu erhöhen, wenn mindestens eine diese Ebenen nicht gleich Null ist (d.h., die Order muss modifiziert werden).

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

Nun müssen wir die Möglichkeit der Umkehrung des Handelssystems in Betracht ziehen. Was bedeutet eine Umkehr? Es bedeutet, dass Kaufe in Verkaufen gewandelt wird, und umgekehrt. Der Eröffnungskurs ändert sich durch den Wert des Spread, das wir kaufen zum Ask-Preis und verkaufen zum Bid-Preis. Außerdem tauschen sich die Ebenen von SL und TP aus, nochmal, unter Berücksichtigung des Spread. Wir können es auf eine solche Weise codieren, um alles zu berücksichtigen:

 if (ReversTrade) //  reverse all levels considering spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

Ich stelle hier nicht den vollständigen Code der an der Funktion CalculateSL_and_TP() gemachten Änderungen bereit, weil diese Funktion sehr groß ist. Sie können es in der angehängten Datei Template_EA.mqt sehen. Wir können also sagen, dass wir es geschafft haben die Aufgabe der Berechnung neuer Werte für Stop Loss, Take Profit und OpenPrice zu lösen. Nun müssen wir Funktionen erstellen, die von diesem Block der Berechnungen aufgerufen werden. Dies sind leere Funktionen, die gefüllt werden, wenn ein bestimmter EA erstellt wird, wir werden sie 'Formular Funktionen' nennen.



Formular Funktionen

Im Folgenden sind die Funktionen:

//+------------------------------------------------------------------+
//|  It calculates the level of Stop Loss                            |
//+------------------------------------------------------------------+
double getNewSL(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Stop Loss
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the level of  Take Profit                         |
//+------------------------------------------------------------------+
double getNewTP(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Take Profit
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the new open price for a pending order            |
//+------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // type of the order, for which we calculate it
                              double lots,      // volume, it can be useful
                              double openPrice, // open price
                              double stopLoss,  // current level of Stop Loss
                              double takeProfit // current level of Take Profit
                              )
   {
   double res=-1;
//----
//  here is the code of calculations for open price 
 
//----
   return(res);   
   }

Diese drei Funktionen sind sehr ähnlich untereinander, jede erhält den gleichen Satz an Eingaben und gibt einen Wert des double Typs in die Variable res zurück. Diese Variable wird sofort mit einem negativen Wert initialisiert und, wenn wir nicht unseren eigenen Code zur Berechnung einfügen, es ist dieser negative Wert, der zurückgegeben wird. Dies impliziert das Fehlen der berechneten Ebene, weil der Kurs immer positiv ist.

Außerdem können wir hier auch ein Formular für die Funktion yourFunction() schreiben, um aus dem Block zur Berechnung von Handelssignalen der Funktion getTradeSignal() aufgerufen zu werden:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
//  (there must be a code producing trade signals considering timeframe workPeriod here)
//----
   return (res);   
   }

Diese Funktion ruft workPeriod auf - eine Periode, die wir verwenden müssen, wenn wir benutzerdefinierte oder Standard-indikatoren aufrufen. Die Rückgabe der Variable res wird initialisiert mit dem Wert von OP_BALANCE. Wenn wir keinen eigenen Code einfügen, der den Wert der Variable ändert, ist dies der Wert, den die Funktion zurückgeben wird. Dies wird als kein-Handelssignal empfangen werden.

Es gibt keine weiteren benutzerdefinierten Funktionsformulare in dieser Vorlage. Nachdem die Technologien ihrer Verwendung aufgespürt wurden, werden Sie in der Lage sein, alle notwendigen Formulare an den geeigneten Stellen des Codes einzufügen.



Berechnung der Parameter für eine Neue Order

Nun müssen wir die Werte von SL, TP, Volumen und anderer benötigter Parameter erhalten, um eine neue Markt- oder Pending Order zu platzieren. Dazu erstellen wir Variablen, deren Namen für sich selbst sprechen.

 double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

Diese Variablen erhalten ihre Werte in zwei Funktionen: eine Funktion berechnet die Werte für eine Markt-Order, eine andere macht das gleicht für eine Pending-Order. Diese Unterteilung ermöglicht es uns, unseren Code auf eine einfachere Weise zu schreiben und später zu ändern. Zuerst betrachten wir die Funktion CalculateNewMarketValues():

//+------------------------------------------------------------------+
//|  It calculates the data for opening a new order                  |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
//----
   return;   
   }

Die Funktion erhält das aktuelle Handelssignal, trSignal, und berechnet alle Rückgabeparameter auf seiner Grundlage.

Bitte beachten Sie, dass hier und überall sonst, alle Parameter mit sicheren Werten initialisiert werden müssen.

Wenn wir nicht unseren eigenen Code einfügen, wird die Variable marketType den Wert von -1 (minus Eins) behalten, was bedeutet, es besteht nicht die Absicht eine Order zu öffnen. Es sollte hier daran erinnert werden, dass die Konstanten von Handelsoperationen nicht-negative Werte haben. Die Berechnung von Eröffnungsparametern für eine Pending Order ist sehr ähnlich:

Der einzige Unterschied ist, dass sie einen Parameter mehr berechnet - den Pending-Order Eröffnungskurs.



Änderungen von Ordern

Die nächste Operation in der Arbeit eines EA, ist die Änderung von "freundlichen" Ordern. Zwei separate Funktionen werden zu diesem Zweck verwendet.

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

Sie sind sehr ähnlich, jede übernimmt die folgenden Parameter als Eingaben:

  • ReversTradeSignal - Zeichen, dass das Handelssystem umgekehrt ist,
  • ModifyMarketOrderEveryTick oder ModifyPendingEveryTick - Zeichen der Order-Modifikation bei jedem Tick,;
  • ModifyMarketBarPeriod oder ModifyPendingBarPeriod - Zeitrahmen in Minuten, auf dem die Modifikation ausgeführt wird, wenn es nicht notwendig ist die Kursebene bei jedem Tick zu ändern,
  • Array newSL_and_TP[][5] das die Tickets der zu ändernden Ordern enthält, und die neuen Werte von SL, TP (und Eröffnungskurs bei Pending Ordern).

Betrachten wir die erste Funktion - ModifyMarkets():

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

Die erste Prüfung ist bereits Standard - es ist eine Überprüfung für Null-Größen des Arrays newSL_and_TP[][] zur Änderung. Die zweite Überprüfung: zuerst prüfen der Notwendigkeit zur Änderung bei jedem Tick. Gibt es keine Notwendigkeit dies zu tun (ModifyEveryTick=false), dann prüfen auf das Erscheinen eines neuen Balkens auf dem Zeitrahmen von ModifyBarPeriod Minuten. Wurde die Prüfung nicht bestanden, dann verlassen und nichts tun:

   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }

Wurden die einleitenden Prüfungen jedoch erfolgreich bestanden, können wir anfangen Ordern zu ändern. Gleichzeitig dürfen wir nicht vergessen, zwei Arten der Systemarbeit zu berücksichtigen: direkt und umgekehrt.

   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a direct system
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a reversed system
         }
      }

Der einzige Unterschied zwischen ihnen ist, dass die Werte von newSL_and_TP[i][1] und newSL_and_TP[i][2] (StopLoss und TakeProfit) umgekehrt sind in der Funktion OrderSend().

Die verwendete Funktion zum Ändern von Pending Ordern ist sehr ähnlich, aber wir haben ihr den Empfang des Pending Order Eröffnungskurs hinzugefügt:

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

Ich möchte Ihre Aufmerksamkeit außerdem auf die Tatsache ziehen, dass der Order-Typ in beiden Funktionen geprüft wird:

         type=newSL_and_TP[i][4];

und, entsprechend dem Wert der Variable 'type' (0 oder1), entscheidet das Programm, ob es das Ticket verarbeitet oder überspringt. Das ist alles über die Funktionen zum Ändern von Ordern.



Schließen einer Markt-Order

Jetzt müssen wir das Schließen von Markt-Ordern codieren. Dafür werden wir zwei Arrays verwenden, die Informationen über zuschließende Ordern enthalten, und zwei Funktionen zur Arbeit mit diesen Arrays:

/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);

Das Array ticketsToClose[][2] speichert den Wert des Tickets und den Typ der zu schließenden Order, das Array lotsToClose[] enthält Informationen über die Menge der zu schließenden Lots für jede zu schließende Position. Die Funktion PrepareTicketsToClose() erhält als Eingabe das Array der Ordern, Tickets[][], und den Wert des aktuellen Handelssignals. Ein Kaufen Handelssignal kann auch eine Anweisung zum Schließen von Verkaufen-Ordern sein. Die Funktion PrepareTicketsToClose() wurde in einem kleineren Volumen geschrieben.

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to close orders                     |
//+------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a market order, then skip it
      if (type>OP_SELL) continue;
 
      if (Revers) // reverse the order type
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // here we will decide the fate of each open order
      //  whether we retain it in the market or add to the array of orders to be closed
      if (type==OP_BUY)
         {
         //  
         // code that allows to retain Buy
         
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // code that allows to retain Sell
         
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // order type
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // here we will specify the amount of lots to be closed
      lots[closeSize] = arrayTickets[i][2]; // volume to be closed
      // they can be closed partially, then you should rewrite the code line above
      }
//----
   return;   
   }

Sie sollten Ihre eigenen Bedingungen zu ihr hinzufügen, die die eine Order enthalten würde oder eine andere in die Liste der zu schließenden. Wenn die Größe des Eingabe-Array arrayTickets Null ist (d.h., wir haben keine Ordern zur Verarbeitung), dann verlassen Sie die Funktion, wie gewöhnlich.

Die Funktion CloseMarketOrders() stellt keinerlei Schwierigkeiten dar:

//+------------------------------------------------------------------+
//|  It closes orders with the given tickets                         |
//+------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal(); 
   Print("",size," orders should be closed, orders opened:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Close order #",ticket," type=",type," ",lots," lots" );
      Print(" ");
      RefreshRates(); // just in case, update the data of the market environment
 
      // buy closing block
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Buy order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      //  sell closing block
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Sell order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      } 
//----
   return;
   }

In dem Zyklus, um den das Array sich dreht, wird die Marktumgebung aktualisiert mit der Funktion RefreshRates(), und das Programm versucht die Order zu dem Kurs zu schließen, der dem Order-Typ entspricht. Fehler beim Schließen werden im kleinstmöglichen Umfang analysiert, Sie sollten Ihren eigenen Algorithmus hinzufügen, um die Situation zu verarbeiten.



Löschen von Pending Ordern

Der Vorgang zum Löschen von Pending Ordern ist sehr ähnlich wir der zum Schließen von Markt-Ordern. Es benötigt lediglich kein Array mit Volumen, da wir nur das Ticket einer Pending Order kennen müssen, um sie zu löschen:

/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);

Entsprechend ist die Syntax dieses Blocks von Funktionen sehr ähnlich, also gebe ich sie hier ohne weitere Erklärung an:

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to delete pending orders            |
//+------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a pending order, then skip
      if (type<=OP_SELL) continue;
      
      if (Revers) // reverse the order type
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // here we will decide the fate of each pending order
      //  whether we retain it or add to the array of orders to be deleted
      //  here we will give an example of a buying signal retaining 
      // pending orders OP_BUYLIMIT and OP_BUYSTOP
      // the same for selling signals
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // code that allows to retain Buy
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // code that allows to retain Sell
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;   
   }



Öffnen einer Position am Markt und Platzieren einer Pending Order

Somit verbleiben zwei letzte Operationen. Wir haben bereits ein Handelssignal erhalten, eine Liste mit Ordern vorbereitet, modifiziert alle notwendigen Ordern geschlossen und gelöscht. Nun öffnen wir eine Position am Markt, wenn es sinnvoll ist, oder platzieren eine Pending Order.

/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

Die erste Funktion, OpenMarketOrder(), erhält alle notwendigen Eingaben, einschließlich des Zeichens der Systemumkehr.


///+------------------------------------------------------------------+
//|  It opens a position by market                                    |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// sign of system reverse
                     int    Type,       // order type - OP_BUY or OP_SELL
                     double lots,       // volume of the position to be opened
                     double SL,         // level of StopLoss
                     double TP,         // level of TakeProfit
                     string comment)    // comment on the order
   {
 
   //Print("Open order Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // reverse the signals
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)  
         {
         openType = OP_SELL; // Buy will become Sell
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL) 
         {
         openType = OP_BUY;  // Sell will become Buy
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY) 
      {
      price = Ask;
      Color = Blue; 
      }
   if (openType==OP_SELL) 
      {
      price=Bid;
      Color = Red; 
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (ticket>0)
      {
      Print("Failed to open a position by market");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  further processing of the situation, code independently
      }
//----
   return;
   }

Es ist nichts Kompliziertes in dieser Funktion ausgenommen die Verarbeitung der Situation der Umkehr. There is nothing complicated in this function, except processing the situation of reverse. Für die Systemumkehr, sollten Sie SL und TP bewahren und eine Verschiebung gleich dem Spread-Wert hinzufügen. Die wichtigste Sache ist hier, nicht zu vergessen, dass kein Wert für Stop Loss oder Take Profit gleich Null sein kann.

Die Funktion SendPendingOrder() ist nur ein wenig komplizierter, da wir die Tatsache berücksichtigen müssen, dass dort zwei Arten von Pending Ordern zum Kaufen und zwei zum Verkaufen sein können. Der Rest ist ähnlich wie OpenMarketOrder().

//+------------------------------------------------------------------+
//|  It places a pending order                                       |
//+------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// sign of system reverse
                      int    Type,
                      double OpenPrice, 
                      double Lots, 
                      double SL, 
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // reverse order type and levels
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Invalid order type Type=",Type," in function SendPendingOrder()!!!");                           
         
         }
      }
   else openType = Type;   
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (!res)
      {
      Print("Failed placing pending order");
      //  further processing of the situation, code independently
      }
 
//----
   return;
   }

In beiden Funktionen ist die Handelsanforderung-Fehlerverarbeitung minimal, Sie können dafür Ihren eigenen Code einfügen.



Endgültige Version der Funktion start()

Sobald wir alle notwendigen Funktionen erstellt haben, können wir einen weiteren Blick auf die Funktion start() werfen, die von einer Text-Vorlage an einen vollwertigen Code übergeben wurde:

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always zeroize the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Every bar in the preset period   (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*   
   if (trSignal==OP_BUY) Print("Buy signal");                    
   if (trSignal==OP_SELL) Print("Sell signal");                    
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("The current signal is equal to ",trSignal);
*/
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 
 
   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
               (ModifyMarketOrderEveryTick = true)
               
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
               (ModifyPendingEveryTick = true)
        
        c) Modification of each open order on each new bar in the preset period (SL and TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modification of each pending order on each new bar in the preset period (OpenPrice, SL and TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);
 
/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE) 
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, 
            pendingTP,pendingComment);
//----
   return(0);
  }
//+------------------------------------------------------------------+

Die Betriebslogik des EAs ist vollständig sichtbar, die Umsetzungsdetails sind in den entsprechenden Funktionen versteckt. Bei der Programm-Fehlerbehebung wissen wir genau, wo wir nach Fehlern suchen müssen. Darüber hinaus wird die gesamte Logik des Expert Advisors klar getrennt: wir wissen, in welcher Funktion wir den Code ändern müssen, wenn wir den Algorithmus zum ermitteln "freundlicher" Ordern ändern möchten, wir wissen, wo wir die Bedingungen zum Schließen oder Öffnen von Positionen einfügen und in wir den Trailing Stop, wenn nötig, einsetzen müssen. Die einzige Sache, die zu tun bleibt, ist die Verwendung der fertigen Vorlage in der Praxis.



Anwendungsbeispiel

Die erhaltene Vorlage, Template_EA.mqt, ist an den Artikel angehangen. Sie können sie als Expert.mqt in dem Ordner C:\Program Files\MetaTrader 4\experts\templates\ speichern. In diesem Fall, wenn Sie einen neuen EA erstellen. wird diese Datei immer als Vorlage für ihn verwendet werden, und Sie haben alle oben aufgeführten Funktionen automatisch eingefügt. Es gibt eine andere Alternative - speichern Sie sie in dem gleichen Ordner, ohne ihren Namen zu ändern. Dann sind Sie in der Lage diese Datei manuell als Vorlage zu bestimmen, wenn Sie einen neuen Expert Advisor erstellen.


Jetzt wählen wir unsere Vorlage, Template_EA, und klicken auf "Weiter". Nehmen wir an, wir wollen eine einfache Strategie schreiben:

  • ein Kaufsignal wird gebildet, wenn die Stochastik Signallinie aus dem überverkauften Bereich heraustritt und die voreingestellte Ebene des DownLevel von unten nach oben durchbricht (standardmäßig ist es 10);
  • ein Verkaufssignal wir gebildet, wenn sie Stochastik Signallinie aus den überkauften Bereich heraustritt und die voreingestellte Ebene des UpLevel von oben nach unten durchbricht (standardmäßig ist es 90);
  • schützender StopLoss wird im Abstand von 100 Punkten von dem Eröffnungskurs platziert (kann geändert werden);
  • Take Profit wird in einem Abstand von 100 Punkten von dem Eröffnungskurs platziert (kann geändert werden);
  • die Parameter der Stochastik werden durch externe Parameter des EA festgelegt und können auch geändert werden.

Nennen wir unseren neuen EA Osc_test und bringen die notwendigen externen Parameter ein:


Drücken Sie "Fertig" und Sie sehen, dass der Expert Advisor Wizard diese Parameter in unseren auf Basis der obigen Vorlage erstellten EA eingefügt hat.

Somit haben wir die benötigten Parameter bei der Erstellung eines EA hinzugefügt:

//+------------------------------------------------------------------+
//|                                                     Osc_test.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- input parameters
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constant "off market"
#define OP_BALANCE 6

Jetzt geben wir in yourFunction() den Code ein, der Kauf- und Verkauf-Signale erzeugt:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);   
   }

Dies benötigt nur vier Zeilen. Es muss nur noch die Funktion CalculateNewMarketValue() präzisiert werden, die die Daten zum Öffnen einer Position am Markt vorbereitet.

//+------------------------------------------------------------------+
//|  It calculates the data to open a new order                      |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;   
   }

Wie Sie sehen können, benötigt auch die nur 5 (fünf!) Zeilen. Damit haben wir nur so viel Code geschrieben, wie wir brauchen, um unsere einfache Strategie zu beschreiben. Das ist der erste Vorteil von diesem Ansatz.

Das Erstellen einer Vorlage verlangt von Ihnen die Ausarbeitung aller Einzelheiten einer typischen EA-Umsetzung. Dies wird sich jedoch in der Zukunft bezahlt machen, wenn Sie neue Strategien erstellen.



Zusätzliche Vorteile

Nun, es gibt noch mehr. Beginnen wir mit dem Test des erhaltenen EA auf irgendeinem Symbol und irgendeinem Zeitrahmen, Nehmen wir EURUSD H1 mit Standardeinstellungen:


In diesem Fall spielt es keine Rolle, ob er Gewinn macht oder nicht. Sehen wir uns den Testbericht an:


Die Gesamtanzahl an Trades ist 430, 241 von ihnen sind Verkauf (Sell-Positionen) und 189 von ihnen sind Kaufen (Buy-Positionen). Nun kehren wir das System um: wo Sell-Trades waren, beginnen wir mit Buy-Trades, und wo Buy-Trades waren, beginnen wir mit Sell Trades. Dafür bestimmen wir den Wert 'true' in dem Parameter ReversTradeSignal. Dies ist das Zeichen der Systemumkehr:


Beginnen Sie den Test ohne Änderungen an irgendwelchen anderen Parametern. Unten ist der erhaltene Testbericht:


Tatsächlich, jetzt haben wir 241 Buy-Trades und 198 Sell-Trades. Nun, die Anzahl von Sell- und Buy-Trades wurde umgekehrt. Der Prozentsatz an gewonnenen Trades wurde auch umgedreht. Wir müssen keinen neuen EA schreiben, um zu prüfen wie ein umgekehrter arbeitet!

Allerdings ist dies ebenfalls nicht die ganze Geschichte. Wir haben solche Parameter wie TradeDay, erinnern Sie sich? Standardmäßig ist er gleich Null, Aber was, wenn wir möchten, dass unser EA nur freitags arbeitet? Legen Sie seinen Wert auf 5 fest:


Beginnen Sie den Test ohne andere Parameter anzufassen. Sehen Sie das Ergebnis. Wir sehen den Tester-Bericht, der uns zeigt wie es wäre, wenn der EA nur Freitags mit umgekehrten System gehandelt hätte:


Wir sehen, dass nur 81 Trades von ursprünglich 430 übrig sind. Das bedeutet, dass die anderen Trades an anderen Tagen der Woche waren. In diesem Fall sind wir nicht allzu besorgt, dass das Ergebnis noch immer rentabel ist. Nehmen wir an, wir wollen wissen wie der EA an allen Tagen handelt, ausgenommen Freitage. Zu diesem Zweck haben wir den Parameter ReverseDay - setzen Sie ihn einfach auf 'true'.


Beginnen wir mit dem Test und schauen uns den Bericht an:


Es waren 430 Trades, wir haben die Freitag-Trades abgezogen (81) und 349 erhalten. In einer Rechnung: 430-81=349. Somit haben wir die Handelstage korrekt umgekehrt, und wir müssen an dem EA nichts neu programmieren.



Fazit

Ich selbst sehe zwei gravierende Nachteile in diesem Artikel. Auf der einen Seite, ist er zu kurzgefasst und gibt keine detaillierte Beschreibung der Funktionen. Auf der anderen Seite, ist es etwas zu lang für das erste Lesen. Ich hoffe jedoch, dass eventuell eine Art von Lobby auftaucht, die diesen Ansatz zum Erstellen von EAs in MQL4 unterstützt. Dieser Ansatz ist besser zur Teamarbeit geeignet als jeder andere: Es wäre sinnvoller benötigte Vorlagen durch Brainstorming zu erstellen, als einen Trading-Algorithmus auf fortlaufender Basis zu verbessern.

Sie können spezielle Vorlagen für das gleichzeitige Trading mit vielen Symbolen erstellen, Sie können einen Code der besonderen Fehlerverarbeitung für durch Handelsfunktionen zurückgegebene Fehler hinzufügen. Sie können auch Informations-Funktionen, Protokoll-Funktionen, Funktionen die spezielle Berichte zu Testergebnissen und Echtzeit-Trading erstellen hinzufügen. Sie können Beschränkungen für den Handel hinzufügen, nicht nur Wochentage, sondern auch Stunden, die während der Handelssitzungen ausgewählt wurden. Außerdem können Sie alle Service-(Schnittstellen) Funktionen in einer *.mqh Datei zusammenfassen, um nur die zu verfeinernden Funktionen zu sehen. Es gibt wirklich viele Möglichkeiten mit diesem Ansatz.

Der wichtigste Vorteil ist, dass, wenn sie einmal entwickelt ist, die Vorlage fortlaufend verwendet werden kann. Gleichzeitig verringert sich auch die Wahrscheinlichkeit, dass Fehler in Ihre EAs auftauchen.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/1514

Beigefügte Dateien |
Osc_test.mq4 (36.96 KB)
Template_EA.mqt (35.82 KB)
Komfortables Scalping Komfortables Scalping

Der Artikel beschreibt das Verfahren für die Erstellung eines Werkzeuges für komfortables Scalping. Allerdings kann ein solcher Ansatz zum Öffnen von Positionen in jedem anderen Handel angewandt werden.

MetaTrader 4 für eine Zeitbasierte Muster Analyse verwenden MetaTrader 4 für eine Zeitbasierte Muster Analyse verwenden

Zeitbasierte Musteranalyse kann im Devisenmarkt verwendet werden, um eine bessere Zeit für den Einstieg in einen Trade zu bestimmen, oder eine Zeit, in der Trading insgesamt vermieden werden sollte. Hier verwenden wir MetaTraer 4 zum analysieren historischer Marktdaten und erzeugen Optimierungsergebnisse, die nützlich für den Einsatz in mechanischen Handelssystemen sein können

Trend-Jagd Trend-Jagd

Der Artikel beschreibt einen Algorithmus der Volumenerhöhung eines Gewinn-Trades. Seine Umsetzung mit MQL4 Mitteln wird in dem Artikel beschrieben.

Expert Advisors Basierend auf Beliebten Handelssystemen und Alchemie der Handelsroboter Optimierung Expert Advisors Basierend auf Beliebten Handelssystemen und Alchemie der Handelsroboter Optimierung

Dieser Artikel befasst sich mit der Umsetzung des Algorithmus von einfachsten Handelssystemen. Der Artikel wird nützlich sein für beginnende Trader und EA-Autoren.