MQL5 Cookbook: Position-Eigenschaften im MetaTrader 5 Strategietester analysieren
Einleitung
In diesem Beitrag verändern wir den im vorangegangenen Beitrag "MQL5 Cookbook: Position-Eigenschaften auf dem Angepassten Info-Panel" erzeugten Expert Advisor und beschäftigen uns mit folgenden Themen:
- Nach neuen Bar-Ereignissen auf dem aktuellen Symbol suchen;
- Daten von Bars bekommen
- Eine Handelsklasse der Standard-Library in eine Datei aufnehmen;
- Eine Suchfunktion für Handelssignale erzeugen;
- Eine Funktion zur Ausführung von Handelsoperationen erzeugen;;
- Handelsereignisse in der OnTrade() Funktion festlegen.
Eigentlich verdient jedes dieser Themen einen eigenen Beitrag, doch meiner Meinung nach würde dadurch das Verstehen und Lernen der Sprache nur noch komplizierter werden.
Ich werde Ihnen anhand von sehr einfachen Beispielen zu zeigen versuchen, wie diese Features implementiert werden können. Mit anderen Worten: Die Implementierung jede der oben aufgezählten Aufgaben passt buchstäblich in eine einfache und klare Funktion. Wenn wir in den weiteren Beiträgen dieser Reihe dann eine gewisse Vorstellung erhalten haben, machen wir diese Funktionen nach und nach komplexer, je nach und im Umfang der anstehenden Aufgabe.
Kopieren wir also zunächst den Expert Advisor aus dem vorangegangenen Beitrag, da wir all seine Funktionen brauchen.
Einen Expert Advisor entwickeln
Wir beginnen damit, die CTrade Klasse aus der Standard-Library in unsere Datei aufzunehmen. Diese Klasse besitzt alle zur Ausführung von Handelsoperationen notwendigen Funktionen. Als Anfang können wir sie leicht nutzen, ohne uns ihr Innenleben anzusehen. Und genauso machen wir's erst einmal auch.
Um die Klasse mit aufzunehmen, müssen wir folgendes schreiben:
//--- Include a class of the Standard Library #include <Trade/Trade.mqh>
Sie können diese Code ganz an den Anfang der Datei platzieren, damit Sie ihn später leichter wieder finden, z.B. nach der #define Direktive. Die #include Anmerkung gibt an, dass dieTrade.mqh Datei aus <MetaTrader 5 terminal directory>\MQL5\Include\Trade\ geholt werden muss. Auf die gleiche Weise kann jede andere Datei, die Funktionen erhält, mit aufgenommen werden. Das ist insbesondere hilfreich, wenn die Masse des Projektcodes immer größer wird und eine Navigation zunehmend schwieriger wird.
Um Zugriff auf alle Funktionen der Klasse zu haben, müssen wir jetzt eine Instanz der Klasse erzeugen. Dies geschieht, indem man den Namen der Instanz nach dem Namen der Klasse schreibt:
//--- Load the class
CTrade trade;
In unserer Version des Expert Advisors verwenden wir nur von alle in der CTrade Klasse zur Verfügung stehenden Funktionen nur eine Handelsfunktion, nämlich die PositionOpen() Funktion mit deren Hilfe eine Position geöffnet wird. Mit dieser Funktion kann auch eine bestehende offene Position umgekehrt werden. Wie diese Funktion von der Klasse aus aufgerufen werden kann, erfahren Sie später in diesem Beitrag, wenn wir eine Funktion erzeugen, die für die Ausführung von Handelsoperationen verantwortlich ist.
Zudem müssen wir zwei dynamische Arrays in globalem Umfang hinzufügen. Diese Arrays werden Bar-Werte aufnehmen.
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar)
Als Nächstes erzeugen wir eine CheckNewBar() Funktion mit deren Hilfe das Programm nach neuen Bar-Ereignissen sucht, das Handelsoperationen nur auf abgeschlossenen Bars ausgeführt werden.
Der Code für CheckNewBar() Funktion samt detailliert Anmerkungen steht unten:
//+------------------------------------------------------------------+ //| CHECKING FOR THE NEW BAR | //+------------------------------------------------------------------+ bool CheckNewBar() { //--- Variable for storing the opening time of the current bar static datetime new_bar=NULL; //--- Array for getting the opening time of the current bar static datetime time_last_bar[1]={0}; //--- Get the opening time of the current bar // If an error occurred when getting the time, print the relevant message if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1) { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); } //--- If this is a first function call if(new_bar==NULL) { // Set the time new_bar=time_last_bar[0]; Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"][" +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]"); return(false); // Return false and exit } //--- If the time is different if(new_bar!=time_last_bar[0]) { new_bar=time_last_bar[0]; // Set the time and exit return(true); // Store the time and return true } //--- If we have reached this line, then the bar is not new, return false return(false); }
Wie Sie im oben stehenden Code sehen können, liefert die CheckNewBar() Funktion bei einem neuen Bar 'true' und 'false', wenn es noch keinen neuen Bar gibt. Auf diese Weise können Sie die Situation beim Handeln/Testen kontrollieren und nur Handelsoperationen auf abgeschlossen Bars durchführen.
Ganz am Anfang der Funktion, deklarieren wir eine statische Variable und ein statisches Array des Typs datetime. Statische lokale Variablen behalten ihre Werte, selbst wenn man die Funktion verlassen hat. Bei jedem späteren Funktionsaufruf besitzen solche statischen Variablen die Werte, die sie beim vorherigen Aufruf der Funktion angenommen haben.
Das Weiteren möchte ich explizit auf die CopyTime() Funktion hinweisen. Mit ihr erhalten wie die Uhrzeit des letzten Bars im time_last_bar Array. Vergessen Sie nicht, sich die Funktionssyntax in den MQL5 Referenzhinweisen genau anzusehen.
Sie bemerken sicherlich auch die benutzerdefinierte Funktion TimeframeToString(), die in dieser Beitragsreihe bisher noch nie angesprochen worden ist. Sie wandelt Zeitrahmenwerte in einen String um, der für den Benutzer glasklar ist:
string TimeframeToString(ENUM_TIMEFRAMES timeframe) { string str=""; //--- If the passed value is incorrect, take the time frame of the current chart if(timeframe==WRONG_VALUE || timeframe == NULL) timeframe = Period(); switch(timeframe) { case PERIOD_M1 : str="M1"; break; case PERIOD_M2 : str="M2"; break; case PERIOD_M3 : str="M3"; break; case PERIOD_M4 : str="M4"; break; case PERIOD_M5 : str="M5"; break; case PERIOD_M6 : str="M6"; break; case PERIOD_M10 : str="M10"; break; case PERIOD_M12 : str="M12"; break; case PERIOD_M15 : str="M15"; break; case PERIOD_M20 : str="M20"; break; case PERIOD_M30 : str="M30"; break; case PERIOD_H1 : str="H1"; break; case PERIOD_H2 : str="H2"; break; case PERIOD_H3 : str="H3"; break; case PERIOD_H4 : str="H4"; break; case PERIOD_H6 : str="H6"; break; case PERIOD_H8 : str="H8"; break; case PERIOD_H12 : str="H12"; break; case PERIOD_D1 : str="D1"; break; case PERIOD_W1 : str="W1"; break; case PERIOD_MN1 : str="MN1"; break; } //--- return(str); }
Wie die Funktion CheckNewBar() verwendet wird wird später in diesem Beitrag beschrieben, dann, wenn wir alle anderen notwendigen Funktionen fertig haben. Werfen wir jetzt einen Blick auf die Funktion GetBarsData(), die die Werte der geforderten Anzahl an Bars annimmt.
//+------------------------------------------------------------------+ //| GETTING BAR VALUES | //+------------------------------------------------------------------+ void GetBarsData() { //--- Number of bars for getting their data in an array int amount=2; //--- Reverse the time series ... 3 2 1 0 ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); //--- Get the closing price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the opening price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
Den o.g. Code sollten wir uns genauer ansehen. In der Variable 'Menge' geben wir zunächst die Anzahl der Bars an, deren Daten wir erhalten müssen. Dann richten wir mit Hilfe der ArraySetAsSeries() Funktion die Indizierungsreihenfolge des Arrays so ein, dass der Wert des letzten (aktuellen) Bars im Null-Index des Arrays ist. Wenn Sie also z.B. den Wert des letzten Bars in Ihrer Berechnung verwenden wollen, kann dies folgendermaßen geschrieben werden, falls durch den Eröffnungskurs veranschaulicht: open_price[0]. Die Schreibweise für den zweiten bis letzten Bar ist analog: open_price[1].
Der Mechanismus zum Abrufen der Schluss- und Eröffnungskurse ist ähnlich dem der Funktion CheckNewBar(), wo wir die Uhrzeit des letzten Bars erhalten mussten. In diesem Fall verwenden wir halt nur die CopyClose() und CopyOpen() Funktionen. Und analog werden CopyHigh() und CopyLow() dazu verwendet, die Kurse des hohen bzw. niedrigen Bars zu bekommen.
Gehen wir weiter und betrachten uns ein ganz einfaches Beispiel anhand dessen klar wird, wie man Signale zur Eröffnung/Umkehrung einer Position festlegt. Kurs-Arrays speichern Daten für zwei Bars (den aktuellen und den vorherigen, abgeschlossenen Bar). Wir arbeiten mit den Daten des abgeschlossenen Bars.
- Ein Kaufen-Signal taucht auf, wenn der Abschlusskurs über dem Eröffnungskurs liegt (Hausse-Bar);
- Ein Verkaufen-Signal taucht auf, wenn der Abschlusskurs unter dem Eröffnungskurs liegt (Baisse-Bar).
Der Code zur Implementierung dieser einfachen Bedingungen steht unten:
//+------------------------------------------------------------------+ //| DETERMINING TRADING SIGNALS | //+------------------------------------------------------------------+ int GetTradingSignal() { //--- A Buy signal (0) : if(close_price[1]>open_price[1]) return(0); //--- A Sell signal (1) : if(close_price[1]<open_price[1]) return(1); //--- No signal (3): return(3); }
Sie sehen - alles ganz einfach. Man kann sich hier leicht vorstellen, wie man mit komplexeren Situationen auf ähnlich simple Weise umgehen kann. Die Funktion liefert '0', wenn ein abgeschlossener Bar oben ist oder '1', wenn ein abgeschlossener Bar unten ist. Sollte es auch irgendeinem Grund kein Signal gaben, liefert die Funktion '3'.
Jetzt müssen wir nur noch eine TradingBlock() Funktion zur Implementierung der Handelsaktivitäten erzeugen. Unten steht der Code dieser Funktion samt seiner detaillierten Anmerkungen:
//+------------------------------------------------------------------+ //| TRADING BLOCK | //+------------------------------------------------------------------+ void TradingBlock() { int signal=-1; // Variable for getting a signal string comment="hello :)"; // Position comment double start_lot=0.1; // Initial volume of a position double lot=0.0; // Volume for position calculation in case of reverse position double ask=0.0; // Ask price double bid=0.0; // Bid price //--- Get a signal signal=GetTradingSignal(); //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- If it is a Buy signal if(signal==0) { //--- Get the Ask price ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment)) { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a SELL position if(pos_type==POSITION_TYPE_SELL) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } //--- If there is a Sell signal if(signal==1) { //-- Get the Bid price bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a BUY position if(pos_type==POSITION_TYPE_BUY) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } }
Bis zum dem Punkt, an dem eine Position eröffnet wird, sollte meiner Meinung nach jetzt alles klar sein. Wie Sie im obigen Code erkennen können, folgt dem (Handel)-Zeiger ein Punkt, dem seinerseits die PositionOpen() Methode folgt. So kann man eine bestimmte von der Klasse aufrufen. Nachdem Sie den Punkt gesetzt haben, sehen Sie eine Liste mit allen Klassenmethoden. Sie müssen nur noch die erforderliche Methode aus der Liste auswählen:
Abb. 1 Eine Klassenmethode aufrufen.
In der TradingBlock() Funktion gibt es zwei wichtige Blöcke - kaufen und verkaufen. Gleich nachdem wir die Richtung des Signals ermittelt haben, bekommen wir den Briefkurs im Falle eines Kauf-Signals und den Geldkurs im Falle eines Verkauf-Signals.
Alle in Handels-Orders benutzten Kurse/Stufen müssen mit Hilfe der NormalizeDouble() Funktion normalisiert werden, denn sonst führt der Versuch, eine Position zu eröffnen oder zu ändern, zu einem Fehler. Es empfiehlt sich auch, diese Funktion bei der Berechnung des Postens einzusetzen. Beachten Sie auch zudem, dass die Parameter Stop Loss und Take Profit Null-Werte besitzen. Mehr Informationen über die Einrichtung von Handelsstufen finden Sie im nächsten Beitrag dieser Reihe.
Jetzt sind alle benutzerdefinierten Funktionen fertig. Wir können sie also in der korrekten Reihenfolge anordnen:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get position properties and update the values on the panel GetPositionProperties(); //--- 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(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If the bar is not new, exit if(!CheckNewBar()) return; //--- If there is a new bar else { GetBarsData(); // Get bar data TradingBlock(); // Check the conditions and trade } //--- Get the properties and update the values on the panel GetPositionProperties(); }
Nur eine Sache müssen wir noch berücksichtigen: die Festlegung von Handelsereignissen mit Hilfe der OnTrade() Funktion. Darauf gehen wir hier nur kurz ein, nur damit Sie eine Vorstellung davon bekommen. In unserem Fall müssen wir das folgende Szenario implementieren: beim manuellen Eröffnen/Schließen/Verändern einer Position müssen die Werte in der Liste der Position-Eigenschaften auf dem Info-Panel aktualisiert werden, sobald der Vorgang abgeschlossen ist und nicht wenn man eine neue Kursschwankung (Tick) erhält. Zu diesem Zweck müssen wir nur den folgenden Code hinzufügen:
//+------------------------------------------------------------------+ //| TRADE EVENT | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(); }
So im Grunde ist nun alles fertig und wir können uns dem Testen zuwenden. Mit dem Strategietester können Sie rasch einen Test im Visualisierungsmodus durchführen und Fehler finden, sollten welche vorhanden sein. Die Verwendung des Strategietesters ist auch deshalb von Vorteil, weil Sie Ihre Programme auch an Wochenenden weiter entwickeln können, wenn alle Märkte geschlossen sind.
Richten Sie den Strategietester ein, aktivieren Sie den Visualisierungsmodus und klicken auf 'Start'. Der Expert Advisor beginnt mit dem Handel im Strategietester und Sie sehen ein Bild, das dem unten gezeigten sehr ähnlich ist:
Abb. 2 Visualisierungsmodus im MetaTrader 5 Strategietester.
Das Testen im Visualisierungsmodus können Sie jederzeit unterbrechen und es dann Schritt für Schritt wieder aufnehmen, indem Sie F12 drücken. Der Schritt entspricht einem Bar, wenn Sie den Strategietester auf den Modus 'Nur Eröffnungskurse' eingestellt haben oder einer Kursschwankung, wenn Sie den Modus 'Jede Kursschwankung' gewählt haben. Die Geschwindigkeit des Tests können Sie ebenfalls kontrollieren.
Um sicherzugehen, dass die Werte auf dem Info-Panel unmittelbar nach dem manuellen Öffnen/Schließen einer Position oder dem Hinzufügen/Ändern der Stop Loss/Take Profit-Stufen aktualisiert werden, sollte der Expert Advisor im Echtzeit-Modus getestet werden. Warten Sie nicht zu lange, lassen Sie den Expert Advisor einfach auf einem Zeitrahmen von 1 Minute laufen, sodass alle 60 Sekunden Handelsoperationen durchgeführt werden.
Übrigens habe ich das Info-Panel um ein weiteres Array ergänzt - für die Namen der Position-Eigenschaften:
// Array of position property names string pos_prop_texts[INFOPANEL_SIZE]= { "Symbol :", "Magic Number :", "Comment :", "Swap :", "Commission :", "Open Price :", "Current Price :", "Profit :", "Volume :", "Stop Loss :", "Take Profit :", "Time :", "Identifier :", "Type :" };
Im vorherigen Beitrag habe ich davon gesprochen, dass wir dieses Array zur Reduzierung des SetInfoPanel() Funktionscodes bräuchten. Jetzt sehen Sie, wie dies geht, wenn Sie es nicht bereits selbst schon implementiert oder herausgefunden haben. Die neue Implementierung der Liste zur Erzeugung der Objekte in Zusammenhang mit den Position-Eigenschaften lautet so:
//--- List of the names of position properties and their values for(int i=0; i<INFOPANEL_SIZE; i++) { //--- Property name CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2); //--- Property value CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2); }
Zu Anfang der SetInfoPanel() Funktion fällt Ihnen bestimmt die folgende Zeile auf:
//--- Testing in the visualization mode if(MQL5InfoInteger(MQL5_VISUAL_MODE)) { y_bg=2; y_property=16; }
Sie übermittelt dem Programm, dass Y-Koordinaten der Objekte auf dem Info-Panel angepasst werden müssen, wenn das Programm derzeit im Visualierungsmodus getestet wird. Das liegt daran, dass beim Testen im Visualisierungsmodus des Strategietesters der Name des Expert Advisors in der oberen Ecke des Charts nicht so wie in Echtzeit angezeigt wird. Die unnötige Einrückung kann daher auf diese Weise gelöscht werden.
Fazit
Das ist für den Moment alles. Im nächsten Beitrag konzentrieren wir uns auf die Einrichtung und Veränderung von Handelsstufen. Unten können Sie den Quellcode, PositionPropertiesTesterEN.mq5, für den Expert Advisor herunterladen.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/642
- 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.