
Das MQL5-Kochbuch: Mehrwährungsfähiger Expert Advisor – eine einfache, saubere und schnelle Herangehensweise
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:
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.
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.
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.
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.
Die Ergebnisse für NZDUSD sehen so aus:
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.
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 Ltd.
Originalartikel: https://www.mql5.com/ru/articles/648





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.