Das MQL5-Kochbuch: Entwickeln eines Grundgerüsts für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie
Anatoli Kazharski | 10 Mai, 2016
Einleitung
Während ihrer Suche nach oder ihrer Entwicklung von Handelssystemen dürften viele Händler von der Drei-Bildschirme-Strategie von Dr. Alexander Elder gehört haben. Viele bewerten diese Strategie negativ. Doch viele andere sind überzeugt, dass sie zum Gewinn beitragen kann. Sie dürfen keiner der beiden Meinungen blind glauben. Alles sollte immer erst geprüft werden. Wenn Sie Programmierung studieren, liegt alles in Ihren Händen, da Sie die Performance der Handelsstrategie mithilfe von Backtesting prüfen können.
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". Somit wird dieser Beitrag auch demonstrieren, wie Sie Muster vorhandener Programme einfach modifizieren können.
Der Expert Advisor aus dem vorherigen Beitrag bietet bereits die Möglichkeit, die Stop-Loss-/Take-Profit- und Trailing-Stop-Ebenen zu aktivieren/deaktivieren, Volumina von Positionen zu erhöhen und Positionen auf dem entgegengesetzten Signal umzukehren. Alle benötigten Funktionen befinden sich bereits auf ihrem vorgesehenen Platz. Unsere Aufgabe ist es also, die Liste der externen Parameter zu verändern, indem wir zusätzliche Optionen hinzufügen, und einige bestehende Funktionen zu modifizieren.
Zu Illustrationszwecken richten wir die Erzeugung von Signalen auf drei Timeframes anhand des Indikators Moving Average ein. Beim späteren Experimentieren mit dem fertig entwickelten Grundgerüst können Sie jeden beliebigen anderen Indikator einsetzen, indem Sie den Code ein wenig abändern. Wir werden außerdem die Möglichkeit einbauen, Timeframes für jeden Bildschirm festzulegen. Wenn der für den Zeitraum des Indikators verantwortliche Parameter einen Nullwert hat, bedeutet dies, dass der entsprechende Bildschirm nicht verwendet wird. In anderen Worten: Das System kann mit einem oder zwei Timeframes eingerichtet werden.
Bevor Sie beginnen, erstellen Sie eine Kopie des Ordners, der die Dateien des Expert Advisors aus dem vorherigen Beitrag enthält, und benennen Sie sie um.
Entwicklung des Expert Advisors
Beginnen wir mit den externen Parametern. Nachfolgend sehen Sie den Code der aktualisierten Liste. Neue Zeilen sind hervorgehoben. Timeframes werden mit dem Aufzählungstyp ENUM_TIMEFRAMES deklariert. Sie können jeden beliebigen Timeframe aus der Dropdown-Liste auswählen.
//--- External parameters of the Expert Advisor sinput long MagicNumber=777; // Magic number sinput int Deviation=10; // Slippage //--- input ENUM_TIMEFRAMES Screen01TimeFrame=PERIOD_W1; // Time frame of the first screen input int Screen01IndicatorPeriod=14; // Indicator period of the first screen //--- input ENUM_TIMEFRAMES Screen02TimeFrame=PERIOD_D1; // Time frame of the second screen input int Screen02IndicatorPeriod=24; // Indicator period of the second screen //--- input ENUM_TIMEFRAMES Screen03TimeFrame=PERIOD_H4; // Time frame of the third screen input int Screen03IndicatorPeriod=44; // Indicator period of the third screen //--- input double Lot=0.1; // Lot input double VolumeIncrease=0.1; // Position volume increase input double VolumeIncreaseStep=10; // Step for position volume increase input double StopLoss=50; // Stop Loss input double TakeProfit=100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse=true; // Position reversal sinput bool ShowInfoPanel=true; // Display of the info panel
Der Parameter IndicatorSegments sowie die Variable AllowedNumberOfSegments und die Funktion CorrectInputParameters() wurden entfernt, um das Beispiel zu vereinfachen. Wer an dieser Bedingung interessiert ist, kann versuchen, sie selbst umzusetzen. Ebenso muss die Aufzählung der Indikatoren in der Datei Enums.mqh entfernt werden, da dieser Expert Advisor nur einen Indikator einsetzt.
Da es einen separaten Indikator auf jedem Timeframe geben wird, brauchen wir eine gesonderte Variable, um ein Handle jedes der Indikatoren zu erhalten:
//--- Indicator handles int Screen01IndicatorHandle=INVALID_HANDLE; // Indicator handle on the first screen int Screen02IndicatorHandle=INVALID_HANDLE; // Indicator handle on the second screen int Screen03IndicatorHandle=INVALID_HANDLE; // Indicator handle on the third screen
Die Prüfung auf neue Balken wird mithilfe des Mindest-Timeframes durchgeführt. Beim Bestimmen des Mindest-Timeframes in den externen Parametern müssen wir keine bestimmte Reihenfolge befolgen, d. h. Maximum, Mittelwert, Minimum. Die umgekehrte Reihenfolge funktioniert ebenso wie jede andere Reihenfolge. Wir brauchen also eine Funktion, die den Mindest-Timeframe unter allen angegebenen Timeframes erkennt.
Da der Expert Advisor für die Arbeit auf drei Timeframes eingerichtet werden kann sowie auf einem oder zwei, müssen beim Bestimmen des Mindest-Timeframes alle Optionen berücksichtigt werden. Nachfolgend sehen Sie den Code der Funktion GetMinimumTimeframe():
//+------------------------------------------------------------------+ //| Determining the minimum time frame for the new bar check | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES GetMinimumTimeframe(ENUM_TIMEFRAMES timeframe1,int period1, ENUM_TIMEFRAMES timeframe2,int period2, ENUM_TIMEFRAMES timeframe3,int period3) { //--- Default minimum time frame value ENUM_TIMEFRAMES timeframe_min=PERIOD_CURRENT; //--- Convert time frame values to seconds for calculations int t1= PeriodSeconds(timeframe1); int t2= PeriodSeconds(timeframe2); int t3= PeriodSeconds(timeframe3); //--- Check for incorrect period values if(period1<=0 && period2<=0 && period3<=0) return(timeframe_min); //--- Conditions for a single time frame if(period1>0 && period2<=0 && period3<=0) return(timeframe1); if(period2>0 && period1<=0 && period3<=0) return(timeframe2); if(period3>0 && period1<=0 && period2<=0) return(timeframe3); //--- Conditions for two time frames if(period1>0 && period2>0 && period3<=0) { timeframe_min=(MathMin(t1,t2)==t1) ? timeframe1 : timeframe2; return(timeframe_min); } if(period1>0 && period3>0 && period2<=0) { timeframe_min=(MathMin(t1,t3)==t1) ? timeframe1 : timeframe3; return(timeframe_min); } if(period2>0 && period3>0 && period1<=0) { timeframe_min=(MathMin(t2,t3)==t2) ? timeframe2 : timeframe3; return(timeframe_min); } //--- Conditions for three time frames if(period1>0 && period2>0 && period3>0) { timeframe_min=(int)MathMin(t1,t2)==t1 ? timeframe1 : timeframe2; int t_min=PeriodSeconds(timeframe_min); timeframe_min=(int)MathMin(t_min,t3)==t_min ? timeframe_min : timeframe3; return(timeframe_min); } return(WRONG_VALUE); }
Zum Speichern des Wertes des Mindest-Timeframes erstellen wir eine weitere globale Variable:
//--- Variable for determining the minimum time frame ENUM_TIMEFRAMES MinimumTimeframe=WRONG_VALUE;
Die Funktion GetMinimumTimeframe() muss aufgerufen werden, wenn der Expert Advisor in der Funktion OnInit() initialisiert wird.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Determine the minimum time frame for the new bar check MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- Get indicator handles GetIndicatorHandles(); //--- Initialize the new bar CheckNewBar(); //--- Get the properties GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); }
Der Wert der Variable MinimumTimeframe wird dann in den Funktionen CheckNewBar() und GetBarsData() genutzt.
Die Funktion GetIndicatorHandle() sieht nun aus, wie unten dargestellt. Die Periode und der Timeframe werden für jeden Indikator festgelegt.
//+------------------------------------------------------------------+ //| Getting indicator handles | //+------------------------------------------------------------------+ void GetIndicatorHandles() { //--- Get handles of the indicators specified in the parameters if(Screen01IndicatorPeriod>0) Screen01IndicatorHandle=iMA(_Symbol,Screen01TimeFrame,Screen01IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen02IndicatorPeriod>0) Screen02IndicatorHandle=iMA(_Symbol,Screen02TimeFrame,Screen02IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); if(Screen03IndicatorPeriod>0) Screen03IndicatorHandle=iMA(_Symbol,Screen03TimeFrame,Screen03IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE); //--- If the indicator handle for the first time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 1!"); //--- If the indicator handle for the second time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 2!"); //--- If the indicator handle for the third time frame could not be obtained if(Screen01IndicatorHandle==INVALID_HANDLE) Print("Failed to get the indicator handle for Screen 3!"); }
Ferner müssen wir Arrays zum Abrufen von Indikatorwerten hinzufügen (separat für jeden Timeframe):
//--- Arrays for values of the indicators double indicator_buffer1[]; double indicator_buffer2[]; double indicator_buffer3[];
Die Funktion GetIndicatorsData() zum Abrufen von Indikatorwerten sieht nun aus, wie unten dargestellt. Die erhaltenen Handles werden auf Genauigkeit geprüft. Wenn alles in Ordnung ist, werden die Arrays mit Indikatorwerten befüllt.
//+------------------------------------------------------------------+ //| Getting indicator values | //+------------------------------------------------------------------+ bool GetIndicatorsData() { //--- Number of indicator buffer values for determining the trading signal int NumberOfValues=3; //--- If indicator handles have not been obtained if((Screen01IndicatorPeriod>0 && Screen01IndicatorHandle==INVALID_HANDLE) || (Screen02IndicatorPeriod>0 && Screen02IndicatorHandle==INVALID_HANDLE) || (Screen03IndicatorPeriod>0 && Screen03IndicatorHandle==INVALID_HANDLE)) //--- try to get them again GetIndicatorHandles(); //--- If the time frame of the first screen is used and the indicator handle has been obtained if(Screen01TimeFrame>0 && Screen01IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer1,true); //--- Get indicator values if(CopyBuffer(Screen01IndicatorHandle,0,0,NumberOfValues,indicator_buffer1)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer1 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- If the time frame of the second screen is used and the indicator handle has been obtained if(Screen02TimeFrame>0 && Screen02IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer2,true); //--- Get indicator values if(CopyBuffer(Screen02IndicatorHandle,0,0,NumberOfValues,indicator_buffer2)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer2 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- If the time frame of the third screen is used and the indicator handle has been obtained if(Screen03TimeFrame>0 && Screen03IndicatorHandle!=INVALID_HANDLE) { //--- Reverse the indexing order (... 3 2 1 0) ArraySetAsSeries(indicator_buffer3,true); //--- Get indicator values if(CopyBuffer(Screen03IndicatorHandle,0,0,NumberOfValues,indicator_buffer3)<NumberOfValues) { Print("Failed to copy the values ("+ _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer3 array! Error ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); //--- return(false); } } //--- return(true); }
Die Funktionen GetTradingSignal() und GetSignal() sollten gemäß der vorliegenden Aufgabe modifiziert werden. Nachfolgend sehen Sie den Code dieser Funktionen.
//+------------------------------------------------------------------+ //| Determining trading signals | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetTradingSignal() { //--- If there is no position if(!pos.exists) { //--- A Sell signal if(GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); //--- A Buy signal if(GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); } //--- If the position exists if(pos.exists) { //--- Get the position type GetPositionProperties(P_TYPE); //--- Get the last deal price GetPositionProperties(P_PRICE_LAST_DEAL); //--- A Sell signal if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL); if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_SELL && close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_SELL); //--- A Buy signal if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY); if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_BUY && close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) return(ORDER_TYPE_BUY); } //--- No signal return(WRONG_VALUE); }
Die Funktion GetSignal() berücksichtigt genauso wie beim Bestimmen des Mindest-Timeframes alle möglichen Varianten der Zustände der externen Parameter in Bezug auf Bedingungen zum Öffnen von Positionen. Der Code der Funktion ist nachfolgend aufgeführt:
//+------------------------------------------------------------------+ //| Checking the condition and returning a signal | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetSignal() { //--- A SELL SIGNAL: the current value of the indicators on completed bars is lower than on the previous bars //--- Conditions for a single time frame if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } //--- if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- Conditions for two time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer3[1]<indicator_buffer3[2]) return(ORDER_TYPE_SELL); } //--- Conditions for three time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]<indicator_buffer1[2] && indicator_buffer2[1]<indicator_buffer2[2] && indicator_buffer3[1]<indicator_buffer3[2] ) return(ORDER_TYPE_SELL); } //--- A BUY SIGNAL: the current value of the indicators on completed bars is higher than on the previous bars //--- Conditions for a single time frame if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- Conditions for two time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer3[1]>indicator_buffer3[2]) return(ORDER_TYPE_BUY); } //--- Conditions for three time frames if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0) { if(indicator_buffer1[1]>indicator_buffer1[2] && indicator_buffer2[1]>indicator_buffer2[2] && indicator_buffer3[1]>indicator_buffer3[2] ) return(ORDER_TYPE_BUY); } //--- No signal return(WRONG_VALUE); }
Nun müssen wir nur noch kleine Änderungen an den Funktionen OnInit() und OnDeinit() vornehmen. Die Änderungen wurden im unten dargestellten Code markiert:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Determine the minimum time frame for the new bar check MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); //--- Get indicator handles GetIndicatorHandles(); //--- Initialize the new bar CheckNewBar(); //--- Get the properties GetPositionProperties(P_ALL); //--- Set the info panel SetInfoPanel(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) { //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); //--- Delete the indicator handles IndicatorRelease(Screen01IndicatorHandle); IndicatorRelease(Screen02IndicatorHandle); IndicatorRelease(Screen03IndicatorHandle); } }
Das Grundgerüst für Handelssysteme auf Basis der Drei-Bildschirme-Strategie ist fertig. Es kann jederzeit modifiziert werden, indem Sie die Indikatoren ändern oder bei Bedarf zusätzliche Bedingungen hinzufügen.
Optimieren von Parametern und Testen des Expert Advisors
Fahren wir mit der Optimierung der Parameter fort und sehen uns die Ergebnisse an. Der Strategietester wird eingestellt, wie nachfolgend abgebildet (stellen Sie sicher, dass Sie den geringsten der drei Timeframes angeben):
Abb. 1. Einstellungen des Strategietesters.
Die Parameter des Expert Advisors für die Optimierung wurden festgelegt, wie nachfolgend dargestellt. Timeframes können zum Optimieren eingestellt werden, aber ich bevorzuge es, sie manuell einzustellen.
Abb. 2. Einstellungen des Expert Advisors.
Die Optimierung wurde auf einem Dual-Core-Prozessor in etwa 30 Minuten abgeschlossen. Nachfolgend sehen Sie das Optimierungsdiagramm:
Abb. 3. Optimierungsdiagramm.
Die Testergebnisse der maximalen Bilanz weisen einen geringeren Wertverlust auf als die Testergebnisse des maximalen Erholungsfaktors, deshalb werden die Testergebnisse der maximalen Bilanz für Demonstrationszwecke genutzt:
Abb. 4. Testergebnisse der maximalen Bilanz.
Abb. 5. Testdiagramm der maximalen Bilanz.
Fazit
Der Beitrag hat demonstriert, dass der Expert Advisor ziemlich schnell modifiziert werden kann, wenn die Hauptfunktionen verfügbar sind. Sie können ein neues Handelssystem erhalten, indem Sie einfach den Signalblock und die Indikatoren austauschen. Im Anhang des Beitrags finden Sie ein Archiv mit den Quellcodes des hier beschriebenen Expert Advisors für Ihre eigenen Studien sowie eine set-Datei mit den Einstellungen der Eingabeparameter.