MetaTrader 5 herunterladen

Das MQL5-Kochbuch: Mehrwährungsfähiger Expert Advisor – eine einfache, saubere und schnelle Herangehensweise

10 Mai 2016, 10:43
Anatoli Kazharski
0
312

Einleitung

In diesem Beitrag wird die Umsetzung einer einfachen Herangehensweise an einen mehrwährungsfähigen Expert Advisor beschrieben. Das heißt, Sie werden in der Lage sein, den Expert Advisor für das Testen/den Handel unter identischen Bedingungen aber mit unterschiedlichen Parametern je Symbol einzurichten. Als Beispiel erstellen wir ein Muster für zwei Symbole auf eine Weise, mit der Sie nach Bedarf zusätzliche Symbole hinzufügen können, indem Sie kleine Änderungen am Code vornehmen.

Ein Muster für Mehrwährungsfähigkeit kann in MQL5 auf verschiedene Arten umgesetzt werden:

  • Wir können ein Muster nutzen, bei dem sich ein Expert Advisor an der Zeit ausrichtet und somit genauere Prüfungen in den in der Funktion OnTimer() festgelegten Zeitintervallen durchführen kann.

  • Genauso kann die Prüfung wie bei allen Expert Advisors, die in den vorherigen Beiträgen dieser Serie vorgestellt wurden, in der Funktion OnTick() ausgeführt werden. In diesem Fall hängt die Arbeit des Expert Advisors von Ticks für das aktuelle Symbol, auf dem er arbeitet, ab. Falls es also einen abgeschlossenen Balken auf einem anderen Symbol gibt, während es noch keinen Tick für das aktuelle Symbol gibt, führt der Expert Advisor erst dann eine Prüfung durch, sobald es einen neuen Tick für das aktuelle Symbol gibt.

  • Es gibt noch eine weitere interessante Option, die von ihrem Verfasser Konstantin Gruzdev (Lizar) vorgeschlagen wird. Diese nutzt ein ereignisbasiertes Modell: Mithilfe der Funktion OnChartEvent() ruft der Expert Advisor Ereignisse ab, die durch Indikator-Agenten, die sich in den Symboldiagrammen befinden, die am Testen/Handel beteiligt sind, reproduziert werden. Indikator-Agenten können die Ereignisse neuer Balken und Ticks der Symbole, an die sie angehängt sind, reproduzieren. Diese Art von Indikator (EventsSpy.mq5) kann am Ende dieses Beitrags heruntergeladen werden. Wir werden sie für die Arbeit des Expert Advisors brauchen.


Entwicklung des Expert Advisors

Der im vorherigen Beitrag "Das MQL5-Kochbuch: Verwendung von Indikatoren zum Festlegen von Handelsbedingungen in Expert Advisors" behandelte Expert Advisor dient als Vorlage. Ich habe bereits alles aus ihm entfernt, was mit der Informationsleiste zusammenhängt, und habe auch die Bedingung zum Öffnen von Positionen, wie sie im vorherigen Beitrag "Das MQL5-Kochbuch: Entwickeln eines Grundgerüsts für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie" umgesetzt wird, vereinfacht. Da es unser Ziel ist, einen Expert Advisor für zwei Symbole zu erschaffen, benötigt jedes von ihnen einen eigenen Satz von externen Parametern:

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

Die externen Parameter werden in Arrays platziert, deren Größen von der Anzahl der verwendeten Symbole abhängen. Die Anzahl der im Expert Advisor verwendeten Symbole wird durch den Wert der Konstante NUMBER_OF_SYMBOLS bestimmt, die wir am Anfang der Datei erstellen müssen:

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

Erstellen wir die Arrays, die wir zum Speichern der externen Parameter benötigen:

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

Funktionen zum Initialisieren von Arrays werden innerhalb der Include-Datei InitArrays.mqh platziert. Zum Initialisieren des Arrays Symbols[] erstellen wir die Funktion GetSymbol(). Sie ruft den Symbolnamen aus den externen Parametern ab und falls ein solches Symbol in der Symbolliste auf dem Server vorhanden ist, wird es in der Marktübersicht ausgewählt. Andernfalls, falls das erforderliche Symbol nicht auf dem Server gefunden wird, gibt die Funktion einen leeren String aus und das Logbuch des Expert Advisors wird entsprechend aktualisiert.

Nachfolgend sehen Sie den Code der Funktion GetSymbol():

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

Das Array Symbols[] wird in der Funktion GetSymbols() initialisiert:

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

Wir gestalten die Umsetzung so, dass ein leerer Wert in den externen Parametern eines bestimmten Symbols bedeutet, dass der entsprechende Block nicht am Test/Handel beteiligt sein wird. Dies ist erforderlich, um die Parameter separat für jedes Symbol zu optimieren, während der Rest davon vollständig ausgeschlossen wird.

Alle anderen Arrays von externen Parametern werden genauso initialisiert. In anderen Worten: Wir müssen für jedes Array eine eigene Funktion erstellen. Die Codes all dieser Funktionen sind nachfolgend aufgeführt:

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

Lassen Sie uns nun eine Funktion erstellen, die uns dabei hilft, alle Arrays von externen Parametern bequem gleichzeitig zu initialisieren: die Funktion InitializeInputParameters():

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

Nach der Initialisierung der Arrays von externen Parametern können wir mit dem Hauptteil fortfahren. Einige Verfahren, beispielsweise der Abruf von Indikator-Handles, ihrer Werte und Preisinformationen sowie die Prüfung auf neue Balken usw., werden konsekutiv in Schleifen für jedes Symbol ausgeführt. Deshalb wurden die Werte der externen Parameter in Arrays angeordnet. Das bedeutet, alles wird in Schleifen ausgeführt, wie folgt:

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

Doch bevor wir mit dem Modifizieren der bestehenden Funktionen und der Erstellung neuer Funktionen beginnen, müssen wir auch Arrays erstellen, die in diesem Muster benötigt werden.

Wir benötigen zwei Arrays für Indikator-Handles:

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

Diese beiden Arrays werden zunächst mit ungültigen Werten initialisiert:

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

Der Zugriff auf Arrays von Preisdaten und Indikatorwerten geschieht nun über Strukturen:

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

Wenn Sie nun den Indikatorwert auf dem letzten abgeschlossenen Balken des ersten Symbols in der Liste abrufen müssen, müssen Sie Folgendes schreiben:

double indicator_value=indicator[0].value[1];

Wir müssen außerdem Arrays anstelle der Variablen erstellen, die vorher in der Funktion CheckNewBar() verwendet wurden:

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

Somit wären wir mit den Arrays fertig. Nun müssen wir gemäß den oben vorgenommen Änderungen eine Reihe von Funktionen modifizieren. Beginnen wir mit der Funktion GetIndicatorHandles():

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

Nun bleibt der Code der Funktion unabhängig von der Anzahl der am Test/Handel verwendeten Symbole unverändert.

Ebenso erstellen wir eine weitere Funktion, GetSpyHandles(), zum Abrufen der Handles von Indikator-Agenten, die Ticks aus anderen Symbolen übertragen werden. Doch vorher fügen wir eine weitere Aufzählung aller Ereignisse nach Symbol hinzu, ENUM_CHART_EVENT_SYMBOL, angeordnet als Flags in der Datei Enums.mqh:

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

Diese Aufzählung wird für die Arbeit mit dem benutzerdefinierten Indikator EventsSpy.mq5 (an den Beitrag angehängt) in der Funktion GetSpyHandles() benötigt, deren Code Sie nachfolgend sehen:

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

Bitte beachten Sie den letzten Parameter in der Funktion iCustom(): In diesem Fall wurde der Identifikator CHARTEVENT_TICK zum Abrufen von Tick-Ereignissen verwendet. Doch falls erforderlich, kann er modifiziert werden, um die Ereignisse von neuen Balken abzurufen. Wenn Sie beispielsweise die nachfolgend abgebildete Zeile nutzen, ruft der Expert Advisor die Ereignisse neuer Balken auf Minuten- (M1) und Stunden- (H1) Timeframes ab:

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

Zum Abrufen aller Ereignisse (Tick- und Balkenereignisse auf allen Timeframes) müssen Sie den Identifikator CHARTEVENT_ALL festlegen.

Alle Arrays werden in der Funktion OnInit() initialisiert:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

Wie bereits am Anfang des Beitrags erwähnt, werden Ereignisse aus den Indikator-Agenten in der Funktion OnChartEvent() empfangen. Nachfolgend sehen Sie den Code, der in dieser Funktion verwendet wird:

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

In der Funktion CheckSignalAndTrade() (markierte Zeile im oben abgebildeten Code) haben wir eine Schleife, in der alle Symbole nacheinander auf das Ereignis eines neuen Balkens und Handelssignale geprüft werden, wie es vorher in der Funktion OnTick() umgesetzt wurde:

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

Alle Funktionen, die externe Parameter nutzten, sowie Daten von Symbolen und Indikatoren müssen gemäß den obigen Änderungen modifiziert werden. Zu diesem Zweck müssen wir die Nummer des Symbols als ersten Parameter hinzufügen und alle Variablen und Arrays innerhalb der Funktion durch die neuen, oben beschriebenen Arrays ersetzen.

Zur Illustration werden nachfolgend die überarbeiteten Codes der Funktionen CheckNewBar(), TradingBlock() und OpenPosition() abgebildet.

Code der Funktion CheckNewBar():

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

Code der Funktion TradingBlock():

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

Code der Funktion OpenPosition():

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

Also erhält nun jede Funktion die Nummer des Symbols (symbol_number). Bitte beachten Sie auch die in Build 803 eingeführte Änderung:

Ab Build 803 können Stop Loss und Take Profit beim Öffnen einer Position im Modus SYMBOL_TRADE_EXECUTION_MARKET festgelegt werden.

Die überarbeiteten Codes der anderen Funktionen finden Sie in den angehängten Dateien. Nun müssen wir nur noch die Parameter optimieren und das Testing durchführen.


Optimieren von Parametern und Testen des Expert Advisors

Wir optimieren zuerst die Parameter für das erste Symbol und anschließend für das zweite. Beginnen wir mit EURUSD.

Nachfolgend sehen sie die Einstellungen des Strategietesters:

Abb. 1. Einstellungen des Strategietesters

Abb. 1. Einstellungen des Strategietesters.

Der Expert Advisor muss eingestellt werden, wie unten dargestellt (bequemlichkeitshalber sind die .set-Dateien mit den Einstellungen für jedes Symbol an diesen Beitrag angehängt). Um ein bestimmtes Symbol von der Optimierung auszuschließen, lassen Sie das Parameterfeld mit dem Symbolnamen einfach leer. Die separate Optimierung der Parameter für jedes Symbol beschleunigt außerdem den Optimierungsprozess.

Abb. 2. Einstellungen des Expert Advisors für die Optimierung der Parameter: EURUSD

Abb. 2. Einstellungen des Expert Advisors für die Optimierung der Parameter: EURUSD.

Die Optimierung dauert auf einem Dual-Core-Prozessor etwa eine Stunde. Die Testergebnisse des maximalen Erholungsfaktors sehen so aus:

Abb. 3. Testergebnisse des maximalen Erholungsfaktors für EURUSD

Abb. 3. Testergebnisse des maximalen Erholungsfaktors für EURUSD.

Legen Sie nun NZDUSD als zweite Symbol fest. Lassen Sie die Zeile mit dem Symbolnamen für den ersten Parameterblock für die Optimierung leer.

Alternativ können Sie einfach einen Bindestrich am Ende des Symbolnamens einfügen. Der Expert Advisor findet kein Symbol mit diesem Namen in der Symbolliste und initialisiert den Array-Index als leeren String.

Die Ergebnisse für NZDUSD sehen so aus:

Abb. 4. Testergebnisse des maximalen Erholungsfaktors für NZDUSD

Abb. 4. Testergebnisse des maximalen Erholungsfaktors für NZDUSD.

Nun können wir zwei Symbole zusammen testen. In den Einstellungen des Strategietesters können Sie ein beliebiges Symbol festlegen, auf dem der Expert Advisor gestartet wird, da die Ergebnisse identisch sein werden. Es kann sogar ein Symbol sein, das nicht am Handel/Test beteiligt ist.

Nachfolgend sehen Sie die Ergebnisse für den gleichzeitigen Test von zwei Symbolen:

Abb. 5. Testergebnisse für zwei Symbole: EURUSD und NZDUSD

Abb. 5. Testergebnisse für zwei Symbole: EURUSD und NZDUSD.


Fazit

Das ist alles. Die Quellcodes sind unten angehängt und können für ein vertieftes Studium des oben Dargelegten heruntergeladen werden. Versuchen Sie für die Praxis, ein oder mehrere Symbole auszuwählen oder die Bedingungen für die Öffnung einer Position mithilfe anderer Indikatoren zu verändern.

Legen Sie den Ordner MultiSymbolExpert nach dem Entpacken der Dateien in das Verzeichnis MetaTrader 5\MQL5\Experts. Außerdem muss der Indikator EventsSpy.mq5 im Ordner MetaTrader 5\MQL5\Indicators abgelegt werden.

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

Das MQL5-Kochbuch: Entwickeln eines Grundgerüsts für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie Das MQL5-Kochbuch: Entwickeln eines Grundgerüsts für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie

In diesem Beitrag entwickeln wir ein Grundgerüst für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie in MQL5. Der Expert Advisor wird dabei nicht von Grund auf neu entwickelt. Stattdessen modifizieren wir einfach das Programm aus dem vorherigen Beitrag Das "MQL5-Kochbuch: Verwendung von Indikatoren zum Festlegen von Handelsbedingungen in Expert Advisors", das unseren Zwecken bereits hervorragend dient. Somit wird dieser Beitrag auch demonstrieren, wie Sie Muster vorhandener Programme einfach modifizieren können.

Universeller Expert Advisor: Benutzerstrategien und Hilfsklassen (Teil 3) Universeller Expert Advisor: Benutzerstrategien und Hilfsklassen (Teil 3)

In diesem Artikel werden wir mit der Analyse der Algorithmen der Klasse CStrategy Trading Engine fortfahren. Der dritte Teil der Serie enthält die detaillierte Analyse von Beispielen, wie bestimmte Handelsstrategien mit diesem Ansatz entwickelt werden können. Ein besonderes Augenmerk wird auf die Hilfsalgorithmen gelegt — Ein Expert Advisor Protokollierungs-System (logging) und der Datenzugriff über gewöhnliche Indexe (Close[1], Open[0] etc.)

Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern

In diesem Beitrag werden wir ein Muster erstellen, das einen einzelnen Satz von Parametern für die Optimierung eines Handelssystems nutzt und gleichzeitig eine unbegrenzte Anzahl von Parametern ermöglicht. Die Liste der Symbole wird in einer Standard-Textdatei (*.txt) erstellt. Die Eingabeparameter jedes Symbols werden ebenfalls in Dateien gespeichert. Auf diese Weise können wir die Terminal-seitige Begrenzung der Anzahl von Eingabeparametern eines Expert Advisors umgehen.

Das MQL5-Kochbuch: Schreiben der Historie von Abschlüssen in eine Datei und Erstellen von Bilanzdiagrammen für jedes Symbol in Excel Das MQL5-Kochbuch: Schreiben der Historie von Abschlüssen in eine Datei und Erstellen von Bilanzdiagrammen für jedes Symbol in Excel

Bei der Kommunikation in diversen Foren nutze ich oft Beispiele meiner Testergebnisse in der Darstellung in Form von Screenshots von Excel-Diagrammen. Ich werde häufig gebeten, zu erklären, wie solche Diagramme erstellt werden können. Nun habe ich endlich etwas Zeit gefunden, alles in diesem Beitrag zu erklären.