Orders.Erstellen aktiver MQL5-Bedienfelder für den Handel
Einleitung
Zweckdienlichkeit spielt eine entscheidende Rolle bei der Arbeit, insbesondere bei der Arbeit eines Börsenhändlers, bei der Schnelligkeit und Genauigkeit ausschlaggebend sein können. Bei der Vorbereitung des Arbeitsplatzes versucht jeder, ihn so einzurichten, dass er den eigenen Anforderungen am besten genügt, um Analysen in möglichst kurzer Zeit abzuschließen und in den Markt einzusteigen. Aber die Wirklichkeit sieht anders aus, die Entwickler können nicht alle zufrieden stellen, und einige Funktionen lassen sich nicht bis ins Kleinste individuell einstellen.
So ist zum Beispiel für Spekulanten, die auf minimale Kursänderungen, Pips, setzen, jeder Sekundenbruchteil für die Betätigung der Schaltfläche „Neuer Auftrag“ ungemein wichtig, und die anschließende Einstellung aller Parameter kann sich als zeitlich entscheidend erweisen.
Wie gehen wir damit um? Die Lösung liegt in der Erstellung eigener Bedienfelder, denn in MetaTrader 5 gibt es so wunderbare Instrumente wie „Schaltflächen“, „Eingabefelder“ und „Grafische Symbole“. Damit wollen wir uns hier beschäftigen.
2. Steuerfunktionen
Zunächst müssen wir klären, mit welchen Funktionen unser Bedienfeld ausgestattet sein soll: Das Hauptaugenmerk liegt auf dem Handel mithilfe unseres Bedienfeldes, weswegen es über folgende Funktionen verfügen muss:
- Die Eröffnung einer Position
- Die Platzierung eines bedingten Auftrages, einer „Pending Order“
- Die Änderung einer Position/eines Auftrags
- Das Schließen einer Position
- Das Löschen einer Pending Order
Es würde ebenfalls nicht schaden, die Möglichkeit zur Einstellung des Farbmodells für das Pult sowie der Schriftgröße und der Speicherung der Parameter hinzuzufügen. Wir werden die Elemente unseres künftigen Bedienfeldes eingehend darstellen. Zunächst geben wir für jede seiner Funktionen die Bezeichnung des entsprechenden Objekts, seine Art und seine Zweckbestimmung an. Jede Objektbezeichnung beginnt mit der Zeichenfolge „ActP“, dabei handelt es sich um ein eindeutiges Zeichen dafür, dass dieses Objekt zu dem Bedienfeld gehört.
2,1. Die Eröffnung einer Position
Hier werden die zur Eröffnung einer Position erforderlichen Parameter eingeführt, die Eröffnung selbst erfolgt durch Betätigung einer Schaltfläche. Zur Einrichtung der Stop Loss- und Take Profit-Grenzen benutzen wir Hilfslinien, die durch ein Kontrollkästchen aktiviert werden. Die Auswahl der Art der Ausführung erfolgt mithilfe einer Gruppe von Optionsschaltflächen.
Bezeichnung |
Art |
Beschreibung |
---|---|---|
ActP_buy_button1 | Schaltfläche |
Schaltfläche für einen Kaufvorgang |
ActP_sell_button1 |
Schaltfläche |
Schaltfläche für einen Verkaufsvorgang |
ActP_DealLines_check1 |
Kontrollkästchen |
Häkchen zum Setzen/Entfernen der Hilfslinien |
ActP_Exe_radio1 |
Optionsschaltfläche |
Gruppe von Optionsschaltflächen zur Auswahl der Art der Ausführung des Vorgangs |
ActP_SL_edit1 |
Eingabefeld |
Feld zur Eingabe der Stop Loss-Grenze |
ActP_TP_edit1 |
Eingabefeld |
Feld zur Eingabe der Take Profit-Grenze |
ActP_Lots_edit1 |
Eingabefeld |
Feld zur Eingabe des Umfangs |
ActP_dev_edit1 |
Eingabefeld |
Feld zur Eingabe der zulässigen Abweichung bei der Eröffnung |
ActP_mag_edit1 |
Eingabefeld |
Feld zur Eingabe der „magischen Zahl“ |
ActP_comm_edit1 | Eingabefeld | Feld zur Eingabe eines Kommentars |
Tabelle 1 Übersicht über die Elemente des Bedienfeldes für die „Eröffnung eines Handelsvorgangs“
2.2 Platzieren einer Pending Order
Es folgen die zur Platzierung eines bedingten Auftrags erforderlichen Parameter, die Platzierung selbst erfolgt durch Betätigung einer Schaltfläche. Zur Einrichtung der Stop Loss-, Take Profit- und Stoplimit-Grenzen sowie der Ablaufzeit benutzen wir Hilfslinien, die durch Kontrollkästchen aktiviert werden. Die Auswahl der Art der Ausführung und der Art der Ablaufzeit erfolgt mithilfe einer Gruppe von Optionsschaltflächen.
Bezeichnung |
Art |
Beschreibung |
---|---|---|
ActP_buy_button2 | Schaltfläche |
Schaltfläche zur Platzierung eines Kaufauftrags |
ActP_sell_button2 |
Schaltfläche |
Schaltfläche zur Platzierung eines Verkaufsauftrags |
ActP_DealLines_check2 |
Kontrollkästchen |
Häkchen zum Setzen/Entfernen der Hilfslinien |
ActP_lim_check2 | Kontrollkästchen | Häkchen zum Setzen/Entfernen eines Auftrags mit Stoplimit |
ActP_Exe_radio2 |
Optionsschaltfläche |
Gruppe von Optionsschaltflächen zur Auswahl der Art der Ausführung des Vorgangs |
ActP_exp_radio2 | Optionsschaltfläche | Gruppe von Optionsschaltflächen zur Auswahl der Art der Ablaufzeit für den Vorgang |
ActP_SL_edit2 |
Eingabefeld |
Feld zur Eingabe der Stop Loss-Grenze |
ActP_TP_edit2 |
Eingabefeld |
Feld zur Eingabe der Take Profit-Grenze |
ActP_Lots_edit2 |
Eingabefeld |
Feld zur Eingabe des Umfangs |
ActP_limpr_edit2 |
Eingabefeld | Feld zur Eingabe des Kurses für einen Auftrag mit Stoplimit |
ActP_mag_edit2 |
Eingabefeld |
Feld zur Eingabe der „magischen Zahl“ |
ActP_comm_edit2 | Eingabefeld | Feld zur Eingabe eines Kommentars |
ActP_exp_edit2 | Eingabefeld | Feld zur Eingabe der Ablaufzeit |
ActP_Pr_edit2 | Eingabefeld | Feld zur Eingabe des Kurses für die Ausführung des Auftrags |
Tabelle 2 Übersicht über die Elemente des Bedienfeldes zur „Platzierung von Pending Orders“
2,3. Ändern/Schließen von Vorgängen
Hier haben wir die zum Ändern und Schließen von Vorgängen erforderlichen Parameter. Zur Einrichtung der Stop Loss- und Take Profit-Grenzen benutzen wir Hilfslinien, die durch ein Kontrollkästchen aktiviert werden. Die Auswahl der Handelsvorgänge erfolgt aus der entsprechenden aufklappenden Liste.
Bezeichnung |
Art |
Beschreibung |
---|---|---|
ActP_ord_button5 | Aufklappende Liste | Liste zur Auswahl eines Handelsvorgangs |
ActP_mod_button4 | Schaltfläche |
Schaltfläche zur Änderung des Vorgangs |
ActP_del_button4 |
Schaltfläche |
Schaltfläche zum Schließen des Vorgangs |
ActP_DealLines_check4 |
Kontrollkästchen |
Häkchen zum Setzen/Entfernen der Hilfslinien |
ActP_SL_edit4 |
Eingabefeld |
Feld zur Eingabe der Stop Loss-Grenze |
ActP_TP_edit4 |
Eingabefeld |
Feld zur Eingabe der Take Profit-Grenze |
ActP_Lots_edit4 |
Eingabefeld |
Feld zur Eingabe des Umfangs |
ActP_dev_edit4 |
Eingabefeld |
Feld zur Eingabe der zulässigen Abweichung |
ActP_mag_edit4 |
Eingabefeld |
Feld zur Anzeige der „magischen Zahl“ (nur Lesezugriff) |
ActP_Pr_edit4 | Eingabefeld | Feld zur Anzeige des Eröffnungskurses (nur Lesezugriff) |
Tabelle 3. Übersicht über die Elemente des Bedienfeldes zum „Ändern/Schließen von Vorgängen“
2,4. Ändern/Entfernen von Aufträgen
Kommen wir jetzt zu den für die Änderung und Löschung von Aufträgen erforderlichen Parametern. Zur Einrichtung der Stop Loss-, Take Profit- und Stoplimit-Grenzen sowie der Ablaufzeit benutzen wir Hilfslinien, die durch Kontrollkästchen aktiviert werden. Die Auswahl der Art der Ablaufzeit erfolgt mithilfe einer Gruppe von Optionsschaltflächen. Die Auswahl der Aufträge erfolgt anhand der entsprechenden aufklappenden Liste.
Bezeichnung |
Art |
Beschreibung |
---|---|---|
ActP_ord_button5 | Aufklappende Liste | Liste zur Auswahl eines Auftrags |
ActP_mod_button3 | Schaltfläche |
Schaltfläche zur Änderung des Auftrags |
ActP_del_button3 |
Schaltfläche |
Schaltfläche zur Löschung des Auftrags |
ActP_DealLines_check3 |
Kontrollkästchen |
Häkchen zum Setzen/Entfernen der Hilfslinien |
ActP_exp_radio3 | Optionsschaltfläche | Gruppe von Optionsschaltflächen zur Auswahl der Art des Ablaufzeit des Auftrags |
ActP_SL_edit3 |
Eingabefeld |
Feld zur Eingabe der Stop Loss-Grenze |
ActP_TP_edit3 |
Eingabefeld |
Feld zur Eingabe der Take Profit-Grenze |
ActP_Lots_edit3 |
Eingabefeld |
Feld zur Anzeige des Umfangs (nur Lesezugriff) |
ActP_limpr_edit3 |
Eingabefeld | Feld zur Eingabe des Kurses für einen Auftrag mit Stoplimit |
ActP_mag_edit3 |
Eingabefeld |
Feld zur Anzeige der „magischen Zahl“ (nur Lesezugriff) |
ActP_comm_edit3 | Eingabefeld | Feld zur Eingabe eines Kommentars |
ActP_exp_edit3 | Eingabefeld | Feld zur Eingabe der Ablaufzeit |
ActP_Pr_edit3 | Eingabefeld | Feld zur Eingabe der Kurses für die Ausführung des Auftrags |
ActP_ticket_edit3 | Eingabefeld | Feld zur Anzeige des Händlerzettels zu dem Auftrag (nur Lesezugriff) |
Tabelle 4. Übersicht über die Elemente des Bedienfeldes zum „Ändern/Entfernen von Aufträgen“
2.5 Einstellungen
Hier werden die Einstellungen für die Farbe der Schaltflächen, Beschriftungen sowie der Schrift der aufklappenden Liste und die Schriftgröße festgelegt.
Bezeichnung |
Art |
Beschreibung |
---|---|---|
ActP_col1_button6 | Aufklappende Liste |
Liste zur Auswahl der Schaltflächenfarbe |
ActP_col2_button6 |
Aufklappende Liste |
Liste zur Auswahl der Farbe der Beschriftungen |
ActP_col3_button6 |
Aufklappende Liste |
Liste zur Auswahl der Schriftfarbe |
ActP_font_edit6 |
Eingabefeld |
Feld zur Festlegung der Schriftgröße |
Tabelle 5. Übersicht über die Elemente des Bedienfeldes „Einstellungen“
Außerdem fügen wir eine Schaltfläche hinzu, mit der das Bedienfeld verborgen werden kann, wenn es nicht benötigt wird. Sie werden wahrscheinlich bemerkt haben, dass es hier das Werkzeug „Hilfslinien“ gibt. Aber worum handelt es sich dabei, und wozu werden sie gebraucht? Mithilfe dieser Linien können die Stop Loss- und die Take Profit-Grenze, den Kurs für die Auslösung einer Pending Order, den Kurs für den Auftrag mit Stoplimit (waagerechte Linien) sowie die Zeit bis zum Ablaufen eines bedingte Auftrags (einer Pending Order) (senkrechte Linie) festgelegt werden, indem man sie einfach mit der Maus auf den gewünschten Kurs/die gewünschte Zeit zieht.
Die bildliche Festlegung weniger aufwendig als die in Textform (bei der der Kurs/die Zeit von Hand in die entsprechenden Felder eingegeben werden müssen). Außerdem dienen uns diese Linien zum „Hervorheben“ der Parameter des ausgewählten Auftrags, denn wenn zahlreiche Aufträge vorhanden sind, können die in den üblichen Ausgabeprogrammen zur Darstellung der Kurse verwendeten gestrichelten Linien Verwirrung stiften.
3. Das allgemeine Vorgehen beim Anlegen einer Benutzeroberfläche
Das ist also das erklärte Ziel: die Schaffung eines grafischen Hilfsmittels für den Handel. Dafür benötigen wir eine möglichst leicht zu handhabende Benutzeroberfläche. Zunächst sei klargestellt, dass alle Steuerelemente (und davon gibt es viele) programmiert, das heißt, die Lage und Größe der Objekte im Voraus ermittelt werden müssen.
Stellen Sie sich jetzt einmal vor, Sie hätten die Objektkoordinaten langwierig, mühsam und verbissen berechnet, damit sie einander nicht überlagern und gut sichtbar sind, und dann ergibt sich plötzlich die Notwendigkeit, ein weiteres Objekt hinzuzufügen, und unser ganzes schönes Modell ist für die Katz.
Wer sich mit schnellen Programmentwicklungsumgebungen (Delphi, C++ Builder usw.) auskennt, weiß wie schnell dort selbst die komplizierteste Benutzeroberfläche erstellt werden kann.
Wir versuchen, etwas Ähnliches in MQL5 umzusetzen. Zu Beginn platzieren wir die Steuerungsobjekte ganz einfach mit der Maus und passen ihre Größe an. Anschließend schreiben wir ein einfaches Skript, um die Eigenschaften aller Objekte in dem Diagramm auszulesen und in einer Datei zu speichern, sodass wir sie gegebenenfalls einfach abrufen und die Objekte in anderen Diagrammen vollständig wiederherstellen können.
Der Skriptcode kann etwa so aussehen:
//+------------------------------------------------------------------+ //| Component properties writer.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property script_show_inputs input int interfaceID=1; //input parameter - the identifier of the stored interface //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- //Open file for writing int handle=FileOpen("Active_Panel_scheme_"+IntegerToString(interfaceID)+".bin", FILE_WRITE|FILE_BIN); if(handle!=INVALID_HANDLE) { //We will go all the objects on the chart for(int i=0;i<ObjectsTotal(0);i++) { string name=ObjectName(0,i); //And write their properties in the file FileWriteString(handle,name,100); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR)); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100); FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE)); } //Close file FileClose(handle); Alert("Done!"); } } //+------------------------------------------------------------------+
Die Programmierung ist erkennbar simpel, alle Objekte werden auf das Diagramm übertragen und ihre Eigenschaften in eine binäre Datei geschrieben. Beim Auslesen der Datei kommt es da darauf an, dass die Reihenfolge der aufgezeichneten Eigenschaften beibehalten wird.
Damit ist das Skript fertig, und wir kommen zum Anlegen der Benutzeroberfläche.
Und wir beginnen wie beim Hauptmenü mit dem Aufbau anhand von Registerkarten. Weshalb brauchen wir die? Weil die Anzahl der Objekte groß ist, und es schwierig wäre, sie alle auf einmal anzuzeigen. Da die Objekte bereits ihrem jeweiligen Zweck entsprechend gruppiert wurden (s. die Tabellen oben), ist es umso einfacher jede Gruppe unter einer eigenen Registerkarte unterzubringen.
So legen wir im Menü der Anwendung auf dem Ausgabegerät unter Einfügen -> Objekte -> Grafische Objekte ganz oben im Diagramm fünf Schaltflächen an, die als unser Hauptmenü dienen werden.
Abb. 1 Registerkarten des Bedienfeldes
Vergessen Sie dabei nicht, dass die Objekte leicht zu kopieren sind, indem das benötigte ausgewählt und mit gedrückter Steuerungstaste (Strg bzw. Ctrl) mit der Maus an die gewünschte Stelle gezogen wird. Dort wird dann eine Kopie des gewünschten Objekts abgelegt.
Besondere Aufmerksamkeit gilt dabei den Objektbezeichnungen, die, wie wir wissen, ausnahmslos mit „ActP“ beginnen müssen. Außerdem fügen wir der Bezeichnung ein „main“ hinzu, um deutlich zu machen, dass das Objekt zum Hauptmenü des Bedienfeldes gehört.
Abbildung 2. Objektverzeichnis (Registerkarten des Bedienfelds)
Auf ähnliche Weise „wenden“ wir auch den Inhalt der Registerkarten auf neue Bildelemente an. Der Inhalt der einzelnen Registerkarten muss jeweils in einem eigenem Bildelement untergebracht werden!
Registerkarte „Börse“:
Abbildung 3. Elemente der Registerkarte „Börse“
Abbildung 4. Elemente der Registerkarte „Pending“
Registerkarte „Einstellungen“:
Abbildung 5. Elemente der Registerkarte „Einstellungen“
Die Registerkarte „Ändern/Schließen“ ist anders, sie dient der Änderung/Schließung bedingter Aufträge sowie der Änderung/Schließung von Handelsvorgängen. Es ist sinnvoll, die Verarbeitung der Handelsvorgänge und der Aufträge auf zwei getrennte Unterregisterkarten zu verteilen. Zunächst legen wir eine Schaltfläche zum Aufklappen der Liste an, aus der wir den Auftrag oder Handelsvorgang zur Bearbeitung auswählen.
Abbildung6. Die Elemente der Registerkarte „Ändern/Schließen“
Danach legen wir die Unterregisterkarten an. Zur Verarbeitung von Handelsvorgängen:
Abbildung 7. Unterregisterkarte zur Verarbeitung von Handelsvorgängen;
Und zur Verarbeitung von Aufträgen:
Abbildung 8. Unterregisterkarte zur Verarbeitung von Aufträgen
Das wär‘s, die Benutzeroberfläche ist fertig.
Wir wenden das Skript auf jedes der Bildelemente an, um jede Registerkarte in einer gesonderten Datei zu speichern. Dazu muss der Eingangsparameter „interfaceID“ bei jeder Registerkarte ein anderer sein:
- 0 - Hauptmenü
- 1 - Börse
- 2 - Pending
- 3 - Schaltfläche zum Aufklappen der Auswahlliste für Handelsvorgänge/Aufträge
- 4 - Einstellungen
- 6 - Unterregisterkarte zur Verarbeitung von Handelsvorgängen
- 7 - Unterregisterkarte zur Verarbeitung von Aufträgen
Die Registerkarte 5 entspricht der Schaltfläche „Verbergen“ des Hauptmenüs, da sich unter dieser Registerkarte keine Objekte befinden, können wir sie übersprungen.
Nach diesen Umstellungen befinden sich unter „Programme -> MQL5 ->“ folgende Dateien im Dateiverzeichnis der Anwendung auf dem Ausgabegerät (Terminal):
Abbildung 9. Dateiverzeichnis der Bedienfeldmuster
4. Laden der Elemente der Benutzeroberfläche
Die Elemente der Benutzeroberfläche sind also in Dateien gespeichert und warten nur darauf, dass wir sie an die Arbeit lassen. Zunächst bestimmen wir die Stelle, an der unser Bedienfeld platziert werden soll. Wenn es unmittelbar im Hauptdiagramm angezeigt werden soll, verdeckt es das Kursdiagramm, was nicht besonders günstig ist. Deshalb ist es ratsamer, das Bedienfeld in einem Unterfenster des Hauptdiagramms anzulegen. Das Unterfenster kann von einem Indikator angelegt werden.
Wir stellen ihn zusammen:
#property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property indicator_separate_window //place the indicator in a separate window int OnInit() { //--- indicator buffers mapping //Set the short name of the indicator IndicatorSetString(INDICATOR_SHORTNAME, "AP"); //--- return(0); } int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); }
Der Code ist überaus einfach, da die Grundfunktion dieses Indikators, die Erstellung von Unterfenstern keinerlei Berechnungen ausführen muss. Das Einzige, was zu tun ist, besteht in der Festlegung einer „Kurzbezeichnung“ für den Indikator, unter der wir ihn in dem Unterfenster finden können. Wenn wir ein Diagramm mit dem Indikator erstellen und aufrufen, erscheint sein Fenster.
Widmen wir uns jetzt dem Bedienfeld für ein Expert-System. Dazu legen wir ein neues Expert-System an.
Die Funktion OnInit() erhält folgende Operatoren:
double Bid,Ask; //variables for current prices datetime time_current; //time of last tick int wnd=-1; //index of the window with an indicator bool last_loaded=false; //flag indicating whether it's a first initialization or not //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //Start the timer at intervals of 1 second EventSetTimer(1); //Get the latest prices get_prices(); //Define the window with an indicator wnd=ChartWindowFind(0,"AP"); //If the first initialization - create interface if(!last_loaded) create_interface(); //--- return(0); }
Hier starten wir einen Zeitgeber (Timer) (weshalb, wird weiter unten erläutert), rufen die letzten Börsenkurse ab, finden mithilfe der Funktion ChartWindowFind das Indikatorfenster und speichern es in einer Variablen. Die Auszeichnung (Flag) „last_loaded“ weist aus, ob es sich um den ersten Aufruf des Expert-Systems handelt oder nicht. Dadurch das erneute Laden der Benutzeroberfläche bei einer wiederholten Bereitstellung vermieden.
Die Funktion create_interface() sieht folgendermaßen aus:
//+------------------------------------------------------------------+ //| Function of the interface creation | //+------------------------------------------------------------------+ void create_interface() { //if reset settings is selected if(Reset_Expert_Settings) { //Reset GlobalVariableDel("ActP_buttons_color"); GlobalVariableDel("ActP_label_color"); GlobalVariableDel("ActP_text_color"); GlobalVariableDel("ActP_font_size"); } //Create the main menu interface ApplyScheme(0); //Create the interface tab "Market" ApplyScheme(1); //Set all objects as unmarked Objects_Selectable("ActP",false); //redraw the chart ChartRedraw(); }
Vor allem anderen prüfen wir, ob der Eingangsparameter „Einstellungen zurücksetzen“ vorhanden ist, falls ja, löschen wir alle für die Einstellungen zuständigen globalen Variablen. Welche Auswirkungen das auf das Bedienfeld hat, sehen wir weiter unten. Des Weiteren erstellt die Funktion ApplyScheme() die Benutzeroberfläche aus einer Datei.
//+------------------------------------------------------------------+ //| The function for the interface loading | //| ID - ID of the saved interface | //+------------------------------------------------------------------+ bool ApplyScheme(int ID) { string fname="Active_Panel_scheme_custom_"+IntegerToString(ID)+".bin"; //download the standard scheme if there isn't saved scheme if(!FileIsExist(fname)) fname="Active_Panel_scheme_"+IntegerToString(ID)+".bin"; //open file for reading int handle=FileOpen(fname,FILE_READ|FILE_BIN); //file opened if(handle!=INVALID_HANDLE) { //Loading all objects while(!FileIsEnding(handle)) { string obj_name=FileReadString(handle,100); int _wnd=wnd; //the auxiliary lines are in the main window if(StringFind(obj_name,"line")>=0) _wnd=0; ENUM_OBJECT obj_type=FileReadInteger(handle); //creating object ObjectCreate(0, obj_name, obj_type, _wnd, 0, 0); //and apply the properties ObjectSetInteger(0,obj_name,OBJPROP_XDISTANCE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_YDISTANCE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_XSIZE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_YSIZE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_COLOR,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_STYLE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_BACK,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_READONLY,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_STATE,FileReadInteger(handle)); ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle)); ObjectSetString(0,obj_name,OBJPROP_TEXT,FileReadString(handle,100)); ObjectSetString(0,obj_name,OBJPROP_FONT,FileReadString(handle,100)); ObjectSetString(0,obj_name,OBJPROP_BMPFILE,0,FileReadString(handle,100)); ObjectSetString(0,obj_name,OBJPROP_BMPFILE,1,FileReadString(handle,100)); ObjectSetDouble(0,obj_name,OBJPROP_PRICE,FileReadDouble(handle)); //Set color for the objects if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON) ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color")); if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL) ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color")); if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON)) ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color")); if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL)) ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size")); //Set global variable font size if(obj_name=="ActP_font_edit6" && GlobalVariableCheck("ActP_font_size")) ObjectSetString(0,obj_name,OBJPROP_TEXT,IntegerToString(GlobalVariableGet("ActP_font_size"))); } //Close file FileClose(handle); return(true); } return(false); }
Daran ist wieder nichts kompliziert. Die Funktion öffnet die erforderliche Datei mit der zuvor angelegten Musterbenutzeroberfläche und legt sie in dem angegebenen Fenster an, das wir zuvor festgelegt haben (das Indikatorfenster). Außerdem wählen wir aus den globalen Variablen der Anwendung auf dem Ausgabegerät die Farbe der Objekte und die Schriftgröße aus.
Die Funktion Objects_Selectable() sperrt mit Ausnahme der Hilfslinien alle Objekte, um die Schaltflächenanimation einzuschalten und nicht versehentlich irgendein Objekt zu löschen.
//+------------------------------------------------------------------+ //| Function of setting objects as unselectable | //+------------------------------------------------------------------+ void Objects_Selectable(string IDstr,bool flag) { //Check all the objects for(int i=ObjectsTotal(0);i>=0;i--) { string n=ObjectName(0,i); //If the object belongs to the panel if(StringFind(n,IDstr)>=0) { //Lines remain untouched if(!flag) if(StringFind(n,"line")>-1) continue; //Set everything unselectable except the lines ObjectSetInteger(0,n,OBJPROP_SELECTABLE,flag); } } }
Kommen wir nun zu der Funktion OnTick(). Sie dient uns ausschließlich zum Bezug der aktuellen Börsenkurse.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //Get the latest prices get_prices(); }
So sieht die Funktion get_prices() aus:
//+------------------------------------------------------------------+ //| Function obtain information on tick | //+------------------------------------------------------------------+ void get_prices() { MqlTick tick; //if the tick was if(SymbolInfoTick(Symbol(),tick)) { //obtain information Bid=tick.bid; Ask=tick.ask; time_current=tick.time; } }
Und nicht zu vergessen OnDeinit():
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- //if the deinitialisation reason isn't the timeframe or symbol change if(reason!=REASON_CHARTCHANGE) { //reset initialization flag last_loaded=false; //Delete all panel objects ObjectsDeleteAll_my("ActP"); //Delete files with the saved state of the tabs FileDelete("Active_Panel_scheme_custom_1.bin"); FileDelete("Active_Panel_scheme_custom_2.bin"); FileDelete("Active_Panel_scheme_custom_3.bin"); FileDelete("Active_Panel_scheme_custom_4.bin"); FileDelete("Active_Panel_scheme_custom_5.bin"); } //otherwise set a flag else last_loaded=true; //stop the timer EventKillTimer(); }
Zunächst prüfen wir den Grund für die Bereinigung, wenn es sich dabei um einen Wechsel des Zeitraums und/oder des Kürzels handelt, werden die Bedienfeldelemente nicht gelöscht. In allen anderen Fällen wird mithilfe der Funktion ObjectsDeleteAll_my() alles gelöscht.
//+------------------------------------------------------------------+ //| The function deletes all panel objects | //| IDstr - object identifier | //+------------------------------------------------------------------+ void ObjectsDeleteAll_my(string IDstr) { //check all the objects for(int i=ObjectsTotal(0);i>=0;i--) { string n=ObjectName(0,i); //if the name contains the identifier - remove the object if(StringFind(n,IDstr)>=0) ObjectDelete(0,n); } }
Nach dem Zusammenstellen und Starten des Expert-Systems erhalten wir folgendes Ergebnis:
Abbildung 10. Beispiel für ein Expert-System
Aber was nützt alle Schönheit, solange wir nicht versuchen, die Objekte dazu zu bringen, auf unsere Veränderungen an ihnen zu reagieren.
5. Die Verarbeitung von Ereignissen
Die Benutzeroberfläche ist fertig, jetzt müssen wir sie ans Laufen bringen. Alle Operationen mit Objekten erzeugen bestimmte Ereignisse. Die Funktion OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) ist die Verarbeitungsroutine für die Ereignisgruppe ChartEvent . Von allen Ereignissen interessieren uns die folgenden:
- CHARTEVENT_CLICK - Anklicken des Diagramms
- CHARTEVENT_OBJECT_ENDEDIT - Abschluss der Bearbeitung des Eingabefeldes
- CHARTEVENT_OBJECT_CLICK - Anklicken des grafischen Objekts
In unserem Beispiel verweist der Parameter der Funktion „id“ auf den Bezeichner des Ereignisses und „sparam“ auf die Bezeichnung des das Ereignis hervorrufenden Objektes, alle übrigen Parameter interessieren uns nicht.
Somit ist das erste Ereignis, mit dem wir uns befassen, das Anklicken einer Schaltfläche des Hauptmenüs.
5,1. Die Verarbeitung vor Ereignissen des Hauptmenüs
Ich erinnere daran, dass das Hauptmenü fünf Schaltflächen umfasst. Wird eine von ihnen angeklickt, wechselt sie in die „gedrückte“ Stellung, und die Benutzeroberfläche aus der entsprechenden Registerkarte wird geladen. Danach müssen alle übrigen Schaltflächen des Menüs anklickbar bleiben.
//+------------------------------------------------------------------+ //| Event handlers | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //main menu button click if(sparam=="ActP_main_1") {Main_controls_click(1); ChartRedraw(); return;} //Here we execute the corresponding operators if(sparam=="ActP_main_2") {Main_controls_click(2); ChartRedraw(); return;} if(sparam=="ActP_main_3") {Main_controls_click(3); ChartRedraw(); return;} if(sparam=="ActP_main_4") {Main_controls_click(4); ChartRedraw(); return;} if(sparam=="ActP_main_5") {Main_controls_click(5); ChartRedraw(); return;} ... } ... }
Nach Anklicken einer Menüschaltfläche wird die Funktion Main_controls_click() ausgeführt. Das Diagramm wird mithilfe der Funktion ChartRedraw() neu gezeichnet und die Ausführung der Funktion abgeschlossen. Wir müssen die Ausführung der beenden, weil stets nur ein Objekt angeklickt werden kann, weswegen die weitere Ausführung der Funktion lediglich Prozessorkapazität verschwenden würde.
//+------------------------------------------------------------------+ //| Tab processor | //| ID - index of clicked tab | //+------------------------------------------------------------------+ void Main_controls_click(int ID) { int loaded=ID; //we will go all tabs for(int i=1;i<6;i++) { //for all except the selected set inactive if(i!=ID) { //also remember the last active tab if(ObjectGetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE)==1) loaded=i; ObjectSetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE,0); } } //if(loaded==ID) return; //set an active state for the selected ObjectSetInteger(0,"ActP_main_"+IntegerToString(ID),OBJPROP_STATE,1); //delete the drop-down lists DeleteLists("ActP_orders_list_"); DeleteLists("ActP_color_list_"); //and set the list buttons to the unpressed state ObjectSetInteger(0,"ActP_ord_button5",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_col1_button6",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_col2_button6",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_col3_button6",OBJPROP_STATE,0); //save state of the last active tab SaveScheme(loaded); //remove old tab DeleteScheme("ActP"); //and load a new ApplyScheme(ID); //Set all objects as unselected Objects_Selectable("ActP",false); }
Die Funktionen Objects_Selectable() und ApplyScheme() haben wir bereits kennen gelernt, und auf die Funktion DeleteLists() kommen wir später noch zu sprechen.
Die Funktion SaveScheme() speichert die Benutzeroberfläche in eine Datei, damit die Objekte bei einem erneuten Laden noch dieselben Eigenschaften aufweisen:
//+------------------------------------------------------------------+ //| Interface saving function | //+------------------------------------------------------------------+ void SaveScheme(int interfaceID) { //open file for writing int handle=FileOpen("Active_Panel_scheme_custom_"+IntegerToString(interfaceID)+".bin",FILE_WRITE|FILE_BIN); //if file opened if(handle!=INVALID_HANDLE) { //go all the chart objects for(int i=0;i<ObjectsTotal(0);i++) { string name=ObjectName(0,i); //if the object belongs to the panel if(StringFind(name,"ActP")<0) continue; //and it isn't a tab if(StringFind(name,"main")>=0) continue; //write the object properties to a file FileWriteString(handle,name,100); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE)); FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR)); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100); FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100); FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE)); } //Close file FileClose(handle); } }
Mit der Funktion DeleteScheme() werden die Registerkartenobjekte entfernt.
//+------------------------------------------------------------------+ //| Function to delete all the panel objects, except tabs | //+------------------------------------------------------------------+ void DeleteScheme(string IDstr) { //we will go through all the objects for(int i=ObjectsTotal(0);i>=0;i--) { string n=ObjectName(0,i); //remove everything but the tab if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n); } }
Durch die Ausführung der Funktion Main_controls_click() entfernen wir als die „alte“ Registerkarte, nachdem wir sie zuvor gespeichert haben, und laden eine neue.
Nach dem Zusammenstellen des Expert-Systems werden wir die Ergebnisse sehen.
Jetzt laden wir mit einem Klick auf eine der Schaltflächen des Hauptmenüs neue Registerkarten, während wir den Zustand der alten speichern.
Abbildung 11. Die Elemente der Registerkarte „Pending“
Abbildung 12. Die Elemente der Registerkarte „Ändern/Schließen“
Abbildung 13. Elemente der Registerkarte „Einstellungen“
Damit kann die Verarbeitung von Ereignissen des Hauptmenüs abgeschlossen werden, da es seine Funktionen jetzt in vollem Umfang erfüllt.
5,2. Die Verarbeitung von Ereignissen der Kontrollkästchenkomponente
Die Festlegung der Hilfslinien und der Aufträge mit Stoplimit erfolgt mithilfe der Komponente „Kontrollkästchen“, aber eine solche ist in dem Verzeichnis der grafischen Objekte von MT5 nicht vorhanden, also müssen wir sie erstellen. Es gibt das Objekt „grafisches Symbol“, dabei handelt es sich faktisch um eine Abbildung, die den Zustand „Ein“ oder „Aus“ aufweisen kann. Der Zustand kann mit einem Klick auf das Objekt geändert werden. Jedem Zustand ist jeweils ein eigenes „Bild“ zuzuordnen. Wir entscheiden uns für folgende Abbildungen:
- Ein
- Aus
Jetzt legen wir diese Bildelemente in den Eigenschaften des Objektes an:
Abbildung 14. Festlegung der Eigenschaften des Elements „Kontrollkästchen“
Ich erinnere daran, dass sich die Bilder, um in dem Verzeichnis zugänglich zu sein, in dem Ordner „Programme -> MQL5-> Bilder“ befinden und die Erweiterung „.bmp“ aufweisen müssen.
Wir nehmen uns der Verarbeitung eines beim Anklicken eines Objektes eintretenden Ereignisses an. Dazu verwenden wir als Beispiel das für die Festlegung der Hilfslinien bei Eröffnung eines Geschäftsvorgangs zuständige Kontrollkästchen.
//click on the flag of the setting of auxiliary lines during transaction opening if(sparam=="ActP_DealLines_check1") { //Check the flag state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //If the flag is set if(selected) { //Retrieve the value of the stop loss and take profit from the input fields string SL_txt=ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT); string TP_txt=ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT); double val_SL, val_TP; //If the Stop field is not empty //save the value if(SL_txt!="") val_SL=StringToDouble(SL_txt); //if empty else { //Take the max. and min. prices of chart double pr_max=ChartGetDouble(0, CHART_PRICE_MAX); double pr_min=ChartGetDouble(0, CHART_PRICE_MIN); //Set the stop at the 1/3 of the chart price range val_SL=pr_min+(pr_max-pr_min)*0.33; } //Similarly processes the Take if(TP_txt!="") val_TP=StringToDouble(TP_txt); else { double pr_max=ChartGetDouble(0, CHART_PRICE_MAX); double pr_min=ChartGetDouble(0, CHART_PRICE_MIN); val_TP=pr_max-(pr_max-pr_min)*0.33; } //Move the line to new positions ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, val_SL); ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, val_TP); } //If the flag is unset else { //remove the lines ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, 0); ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, 0); } //redraw the chart ChartRedraw(); //and finish the function return; }
Auf ähnliche Weise wird auch mit den Kontrollkästchen zur Festlegung der Hilfslinien unter den Registerkarten zur „Platzierung einer Pending Order“ sowie zum „Ändern/Schließen eines Auftrags“ verfahren. Deshalb werden sie in diesem Beitrag nicht behandelt. Wer mag, kann sie sich im Programmcode des Expert-Systems ansehen.
Die Verarbeitungsroutine für das Kontrollkästchen zur Platzierung eines Auftrags mit Stoplimit unter der Registerkarte „Pending“ könnte etwa so aussehen:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //Click on the orders stoplimit check box if(sparam=="ActP_limit_check2") { //Check the flag state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); if(selected) //if flag is set { //set the new color for the price edit ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, White); //enable it for the edit ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, false); //установим в поле значение текущей цены //Set the current price as the field value ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, DoubleToString(Bid, _Digits)); //if the auxiliary lines are allowed //move them if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1) ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, Bid); } //if flag is unset else { //set the field unavailable for editing ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, LavenderBlush); //set the field color ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, true); //and "empty" text ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, ""); //if the auxiliary lines are allowed //move them to the zero point if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1) ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, 0); } } ... } ... }
Damit sind die Kontrollkästchen abgeschlossen. Kommen wir also zum nächsten selbst erstellten Objekt, der „Gruppe von Optionsschaltflächen“.
5,3. Die Verarbeitung von Ereignissen der „Gruppe von Optionsschaltflächen“
Mithilfe dieser Komponente können wir die Art der Abwicklung eines Handelsvorgangs sowie die Art der Ablaufzeit eines Auftrags auswählen. Wie bei den Kontrollkästchen verwenden wir grafische Elemente, jetzt jedoch mit anderen Abbildungen.
- Ein
- Aus
Aber hier ist es deshalb komplizierter, weil beim Anklicken einer der Schaltflächen alle übrigen ausgeschaltet werden müssen. Wir sehen uns das am Beispiel der Optionsschaltflächen zur Auswahl der Art der Ausführung eines Auftrags an:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on radion button 1 - order execution type if(sparam=="ActP_Exe1_radio2") { //check the radio button state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //set the appropriate state ObjectSetInteger(0,sparam,OBJPROP_STATE, 1); //if it selected if(selected) { //reset the other radio buttons ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false); ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false); //redraw the chart ChartRedraw(); //finish the execution of function return; } //redraw the chart ChartRedraw(); //finish the execution of function return; } //Similarly for the radio button 2 if(sparam=="ActP_Exe2_radio2") { bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); ObjectSetInteger(0,sparam,OBJPROP_STATE, 1); if(selected) { ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false); ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false); ChartRedraw(); return; } ChartRedraw(); return; } //Similarly for the radio button 3 if(sparam=="ActP_Exe3_radio2") { bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); ObjectSetInteger(0,sparam,OBJPROP_STATE, 1); if(selected) { ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false); ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false); ChartRedraw(); return; } ChartRedraw(); return; } ... } ... }
Die Optionsschaltflächen für die Art der Ablaufzeit unterscheiden sich nur dadurch, dass man bei Anklicken der dritten eine weitere Einstellung vornehmen muss und zwar die Eingabe eines neuen Datums in das Eingabefeld für die Ablaufzeit des Auftrags:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //Click on the 3rd radio button - order expiration date if(sparam=="ActP_exp3_radio2") { //checking it state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); ObjectSetInteger(0,sparam,OBJPROP_STATE, 1); //if it selected if(selected) { //reset the remained radio buttons ObjectSetInteger(0, "ActP_exp1_radio2", OBJPROP_STATE, false); ObjectSetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE, false); //set the new date to the date edit field ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, White); ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, false); ObjectSetString(0, "ActP_exp_edit2", OBJPROP_TEXT, TimeToString(time_current)); //if auxiliary lines are allowed //set the new time line if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1) ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, time_current); ChartRedraw(); return; } //if it isn't selected else { //set the edit field as not available for editing ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, LavenderBlush); ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, true); //remove the auxiliary line if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1) ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, 0); } ChartRedraw(); return; ... } ... }
Damit wären auch die Optionsschaltflächen erledigt.
5,4. Anlegen und Verarbeiten von Ereignissen für die aufklappenden Listen
Wir wählen den zu ändernden/schließenden/löschenden Auftrag/Vorgang sowie die Farben für das Bedienfeld in aufklappenden Listen aus. Wir fangen mit der Liste der Handelsvorgänge/Aufträge an.
Das Erste, was uns unter der Registerkarte „Ändern/Schließen“ begegnet, ist eine Schaltfläche mit der Aufschrift „Auftrag auswählen -->“, sie dient uns auch als Schaltfläche zum Aufrufen unserer Liste. Durch Betätigung dieser Schaltfläche klappt sich die Liste auf, und, nachdem wir unsere Auswahl vorgenommen haben, verschwindet sie wieder. Sehen wir uns die Ereignisverarbeitungsroutine CHARTEVENT_OBJECT_CLICK dieser Schaltfläche etwas genauer an:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the drop-down list activate button (order select) if(sparam=="ActP_ord_button5") { //check status bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //the list is activated if(selected)// the list is selected { //delete interface DeleteScheme("ActP", true); //arrays for serving the information about the orders string info[100]; //array for the tickets int tickets[100]; //initialize it ArrayInitialize(tickets, -1); //get orders info get_ord_info(info, tickets); //create the list create_list(info, tickets); } //the list isn't active else { //delete it DeleteLists("ActP_orders_list_"); } //redraw the chart ChartRedraw(); //finish the function return; } ... } ... }
Unsere vordringlichste Aufgabe besteht darin festzustellen, ob auf dem Markt Vorgänge/Aufträge vorhanden sind und, falls ja, ihnen die für die Abbildung in der Liste erforderlichen Informationen zu entnehmen. Das besorgt die Funktion get_ord_info():
//+------------------------------------------------------------------+ //| The function for obtaining the information about orders | //+------------------------------------------------------------------+ void get_ord_info(string &info[],int &tickets[]) { //initialize the counter int cnt=0; string inf; //if there is an open position if(PositionSelect(Symbol())) { //combine all order infomation in a single line double vol=PositionGetDouble(POSITION_VOLUME); int typ=PositionGetInteger(POSITION_TYPE); if(typ==POSITION_TYPE_BUY) inf+="BUY "; if(typ==POSITION_TYPE_SELL) inf+="SELL "; inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots"; inf+=" at "+DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), Digits()); //write the results info[cnt]=inf; tickets[cnt]=0; //increment the counter cnt++; } //all orders for(int i=0;i<OrdersTotal();i++) { //get ticket int ticket=OrderGetTicket(i); //if order symbol is equal to chart symbol if(OrderGetString(ORDER_SYMBOL)==Symbol()) { //combine all order infomation in a single line inf="#"+IntegerToString(ticket)+" "; int typ=OrderGetInteger(ORDER_TYPE); double vol=OrderGetDouble(ORDER_VOLUME_CURRENT); if(typ==ORDER_TYPE_BUY_LIMIT) inf+="BUY LIMIT "; if(typ==ORDER_TYPE_SELL_LIMIT) inf+="SELL LIMIT "; if(typ==ORDER_TYPE_BUY_STOP) inf+="BUY STOP "; if(typ==ORDER_TYPE_SELL_STOP) inf+="SELL STOP "; if(typ==ORDER_TYPE_BUY_STOP_LIMIT) inf+="BUY STOP LIMIT "; if(typ==ORDER_TYPE_SELL_STOP_LIMIT) inf+="SELL STOP LIMIT "; inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots"; inf+=" at "+DoubleToString(OrderGetDouble(ORDER_PRICE_OPEN), Digits()); //write the results info[cnt]=inf; tickets[cnt]=ticket; //increment the counter cnt++; } } }
Sie speichert die Informationen und Händlerzettel der Aufträge und Handelsvorgänge in Datenfeldern (Arrays).
Anschließend legt die Funktion create_list() auf der Grundlage dieser Informationen eine Liste an:
//+------------------------------------------------------------------+ //| The function creates list of positions | //| info - array for the positions | //| tickets - array for the tickets | //+------------------------------------------------------------------+ void create_list(string &info[],int &tickets[]) { //get the coordinates of the list activation button int x=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_XDISTANCE); int y=ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YDISTANCE)+ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YSIZE); //get colors color col=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_COLOR); color bgcol=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_BGCOLOR); //get window height int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd); int y_cnt=0; //proceed arrays for(int i=0;i<100;i++) { //break if end reached if(tickets[i]==-1) break; //calculate the list item coordinates int y_pos=y+y_cnt*20; //if the windiow limits are reachedl, start a new column if(y_pos+20>wnd_height) {x+=300; y_cnt=0;} y_pos=y+y_cnt*20; y_cnt++; string name="ActP_orders_list_"+IntegerToString(i)+" $"+IntegerToString(tickets[i]); //create element create_button(name,info[i],x,y_pos,300,20); //and set its properties ObjectSetInteger(0,name,OBJPROP_COLOR,col); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0); ObjectSetInteger(0,name,OBJPROP_STATE,0); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol); } }
Und schließlich löscht die Funktion DeleteLists() alle Elemente der Liste:
//+------------------------------------------------------------------+ //| The function for the list deletion | //+------------------------------------------------------------------+ void DeleteLists(string IDstr) { //proceed all objects for(int i=ObjectsTotal(0);i>=0;i--) { string n=ObjectName(0,i); //delete lists if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n); } }
Jetzt erfolgt also bei Betätigung der Aktivierungsschaltfläche die Erstellung einer Liste. Wir werden sie zwingen, etwas zu tun, denn beim Anklicken der Listenelemente sollen ja bestimmte Vorgänge ausgeführt werden. Und zwar: das Laden der Benutzeroberfläche für die Bearbeitung eines Auftrags sowie deren Füllung mit den entsprechenden Angaben.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... // Event - click on a graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //Click not on an item of order selection list if(StringFind(sparam, "ActP_orders_list_")<0) { //Remove it DeleteLists("ActP_orders_list_"); //Set the activation button to "unpressed" ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0); //redraw chart ChartRedraw(); } //Click on the order selection list item else { //Set a new name for the activation button ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, ObjectGetString(0, sparam, OBJPROP_TEXT)); //Set the activation button to "unpressed" ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0); //get ticket from the list item description int ticket=StringToInteger(StringSubstr(sparam, StringFind(sparam, "$")+1)); //Load the interface SetScheme(ticket); //and delete the list DeleteLists("ActP_orders_list_"); //chart redraw ChartRedraw(); } ... } ... }
Hier müssen wir clever sein. Weil wir vorher weder den Umfang der Liste noch die Bezeichnungen ihrer Objekte kennen, müssen wir diese Informationen zunächst ermitteln, indem wir die Bezeichnungen der Listenelemente untersuchen. Die Funktion SetScheme() richtet die benötigte Benutzeroberfläche ein, entweder zur Verarbeitung eines Geschäftsvorgangs oder eines bedingten Auftrags, einer Pending Order:
//+------------------------------------------------------------------+ //| The function sets the interface depending on type: | //| position or pending order | //| t - ticket | //+------------------------------------------------------------------+ void SetScheme(int t) { //if position if(t==0) { //check for its presence if(PositionSelect(Symbol())) { //delete old scheme DeleteScheme("ActP",true); //and apply new ApplyScheme(6); //set position parameters SetPositionParams(); //the objects are unavailable for the selection Objects_Selectable("ActP",false); } } //if order if(t>0) { //check for its presence if(OrderSelect(t)) { //delete old scheme DeleteScheme("ActP",true); //and apply new ApplyScheme(7); //set order parameters SetOrderParams(t); //the objects are unavailable for the selection Objects_Selectable("ActP",false); } } }
Die Funktionen SetPositionParams() und SetOrderParams() übernehmen die Einstellung der erforderlichen Eigenschaften der geladenen Benutzeroberfläche:
//+------------------------------------------------------------------+ //| Set position parameters for the objects | //+------------------------------------------------------------------+ void SetPositionParams() { //if position is exists if(PositionSelect(Symbol())) { //get its parameters double pr=PositionGetDouble(POSITION_PRICE_OPEN); double lots=PositionGetDouble(POSITION_VOLUME); double sl=PositionGetDouble(POSITION_SL); double tp=PositionGetDouble(POSITION_TP); double mag=PositionGetInteger(POSITION_MAGIC); //and set new values to the objects ObjectSetString(0,"ActP_Pr_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(pr))); ObjectSetString(0,"ActP_lots_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(lots))); ObjectSetString(0,"ActP_SL_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(sl))); ObjectSetString(0,"ActP_TP_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(tp))); if(mag!=0) ObjectSetString(0,"ActP_mag_edit4",OBJPROP_TEXT,IntegerToString(mag)); //redraw chart ChartRedraw(); } //if there isn't position, show message else MessageBox("There isn't open position for "+Symbol()); } //+------------------------------------------------------------------+ //| Set pending order parameters for the objects | //| ticket - order ticket | //+------------------------------------------------------------------+ void SetOrderParams(int ticket) { //if order exists if(OrderSelect(ticket) && OrderGetString(ORDER_SYMBOL)==Symbol()) { //get its parameters double pr=OrderGetDouble(ORDER_PRICE_OPEN); double lots=OrderGetDouble(ORDER_VOLUME_CURRENT); double sl=OrderGetDouble(ORDER_SL); double tp=OrderGetDouble(ORDER_TP); double mag=OrderGetInteger(ORDER_MAGIC); double lim=OrderGetDouble(ORDER_PRICE_STOPLIMIT); datetime expir=OrderGetInteger(ORDER_TIME_EXPIRATION); ENUM_ORDER_TYPE type=OrderGetInteger(ORDER_TYPE); ENUM_ORDER_TYPE_TIME expir_type=OrderGetInteger(ORDER_TYPE_TIME); //of order type is stoplimit, modify the interface if(type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT) { //set new value to the order price edit ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,DoubleToString(lim,_Digits)); ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,White); //set order price available for edit ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,false); } //if order type isn't stoplimit, modify the interface else { ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,""); ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,LavenderBlush); ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,true); } //check expiration type //and set interface elements switch(expir_type) { case ORDER_TIME_GTC: { ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,1); ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0); break; } case ORDER_TIME_DAY: { ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,1); ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0); break; } case ORDER_TIME_SPECIFIED: { ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0); ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,1); //in addition, set new value to the edit ObjectSetString(0,"ActP_exp_edit3",OBJPROP_TEXT,TimeToString(expir)); break; } } //set new values for the objects ObjectSetString(0,"ActP_Pr_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(pr))); ObjectSetString(0,"ActP_lots_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(lots))); ObjectSetString(0,"ActP_SL_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(sl))); ObjectSetString(0,"ActP_TP_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(tp))); ObjectSetString(0,"ActP_ticket_edit3",OBJPROP_TEXT,IntegerToString(ticket)); if(mag!=0) ObjectSetString(0,"ActP_mag_edit3",OBJPROP_TEXT,IntegerToString(mag)); ChartRedraw(); } //if there isn't such order, show message else MessageBox("There isn't an order with ticket "+IntegerToString(ticket)+" for "+Symbol()); }
Und zu guter Letzt müssen die Listen beim Anklicken des Diagramms gelöscht werden, dazu dient das Ereignis CHARTEVENT_CLICK:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - is click on the chart if(id==CHARTEVENT_CLICK) { //delete all lists DeleteLists("ActP_orders_list_"); DeleteLists("ActP_color_list_"); //Set the activate buttons to the unpressed state ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0); ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0); ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0); ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0); ChartRedraw(); return; } ... }
Im Ergebnis erhalten wir diese sympathisch aussehende Liste:
Abbildung15. Beispiel einer aufklappenden Liste im Bedienfeld „Ändern/Schließen“
Bleibt noch die Erstellung einer Liste zur Auswahl der Farbe unter der Registerkarte „Einstellungen“.
Sehen wir uns die Verarbeitungsroutinen der Aktivierungsschaltflächen an:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the chart if(id==CHARTEVENT_OBJECT_CLICK) { ... //Click on the button to activate the colors drop-down list if(sparam=="ActP_col1_button6") { //check state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //the list is active if(selected)//the list is active { //creat list create_color_list(100, "ActP_col1_button6", 1); //Set the position of the remaining buttons to "unpressed" ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0); ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0); //delete other lists DeleteLists("ActP_color_list_2"); DeleteLists("ActP_color_list_3"); } //the list isn't selected else { //delete it DeleteLists("ActP_color_list_"); } //redraw chart ChartRedraw(); //finish the execution of function return; } ... } ... }
Wir gehen hierbei ganz ähnlich vor wie bei der Liste zur Auswahl des Auftrags.
Die Funktion zur Erstellung der Liste unterscheidet sich etwas:
//+------------------------------------------------------------------+ //| Function for creating the colors list | //| y_max - maximal list widthа | //| ID - list ID | //| num - interface number | //+------------------------------------------------------------------+ void create_color_list(int y_max,string ID,int num) { //Get the coordinates of the list activation button int x=ObjectGetInteger(0,ID,OBJPROP_XDISTANCE); int y=ObjectGetInteger(0, ID, OBJPROP_YDISTANCE)+ObjectGetInteger(0, ID, OBJPROP_YSIZE); //get color color col=ObjectGetInteger(0,ID,OBJPROP_COLOR); //and window width int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd); y_max+=y; int y_cnt=0; //We will go through the colors array for(int i=0;i<132;i++) { color bgcol=colors[i]; //calculate list item coordinates int y_pos=y+y_cnt*20; //if we reached the boundaries of the window, start new column if(y_pos+20>wnd_height || y_pos+20>y_max) {x+=20; y_cnt=0;} y_pos=y+y_cnt*20; y_cnt++; //create new element string name="ActP_color_list_"+IntegerToString(num)+ID+IntegerToString(i); create_button(name,"",x,y_pos,20,20); //and set its properties ObjectSetInteger(0,name,OBJPROP_COLOR,col); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0); ObjectSetInteger(0,name,OBJPROP_STATE,0); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol); } }
Außerdem bearbeiten wir das Anklicken eines Listenelements:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on chart if(id==CHARTEVENT_OBJECT_CLICK) { ... //click isn't on the color list button if(StringFind(sparam, "ActP_color_list_1")<0) { //delete list DeleteLists("ActP_color_list_1"); //set color list activation button to "unpressed" ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0); //redraw chart ChartRedraw(); } //click on the color list else { //get color from the list color col=ObjectGetInteger(0, sparam, OBJPROP_BGCOLOR); //set it for all the buttons SetButtonsColor(col); //set button to unpressed ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0); //delete list DeleteLists("ActP_color_list_1"); //redraw chart ChartRedraw(); } ... } ... }
Die Funktion SetButtonsColor() legt die Farbe der Schaltflächen fest:
//+------------------------------------------------------------------+ //| The function sets color for all buttons | //| col - color | //+------------------------------------------------------------------+ void SetButtonsColor(color col) { //We will go through all the objects for(int i=ObjectsTotal(0);i>=0;i--) { string n=ObjectName(0,i); //If the object belongs to the panel and its has a button type //set color if(StringFind(n,"ActP")>=0 && ObjectGetInteger(0,n,OBJPROP_TYPE)==OBJ_BUTTON) ObjectSetInteger(0,n,OBJPROP_BGCOLOR,col); } //set global variable GlobalVariableSet("ActP_buttons_color",col); }
Werten wir das Ergebnis aus:
Abbildung16. Festlegung der Schaltflächenfarbe
Die Listen zur Auswahl der Farbe für die Beschriftung und der Schriftfarbe funktionieren ähnlich. Im Ergebnis lässt sich das Bedienfeld mit ein paar Mausklicks ganz leicht einfärben:
Abbildung 17. Geänderte Farben des Bedienfelds, der Schaltflächen und der Schrift
Jetzt haben wir auch die Listen erledigt. Machen wir also mit den Eingabefeldern weiter.
5,5. Die Verarbeitung von Eingabefeldereignissen
Eingabefelder erzeugen ein CHARTEVENT_OBJECT_ENDEDIT-Ereignis, das bei Abschluss der Bearbeitung des Texts in dem Feld erscheint. Der einzige Grund, aus dem wir dieses Ereignis verarbeiten müssen, ist die Einrichtung der Hilfslinien für die Kurse, die denen der Eingabefelder entsprechen.
Sehen wir uns als Beispiel die Festlegung einer Stoplinie an:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //End edit event if(id==CHARTEVENT_OBJECT_ENDEDIT)//end edit event { ... //if edit field is SL field if(sparam=="ActP_SL_edit1") { //and auxiliary lines are enabled if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1) { //get text from the field double sl_val=StringToDouble(ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT)); //move lines at new position ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, sl_val); } //redraw chart ChartRedraw(); //it ins't necessary to proceed the other objects, because the event from the one return; } ... } ... }
Die Bearbeitung der übrigen Eingabefelder verläuft ähnlich.
5.6 Die Verarbeitung von Zeitgeberereignissen
Der Zeitgeber (Timer) dient zur Überwachung der Hilfslinien, damit bei einer Verschiebung der Linien die Werte der Kurse, für die sie festgelegt wurden, automatisch in das Eingabefeld übertragen werden. Bei jeder Änderung des Zeitgebers wird die Funktion OnTimer() ausgeführt.
Wir sehen uns als Beispiel die Einrichtung der Überwachung der Stop Loss- und Take Profit-Linien unter der Registerkarte „Börse“ an:
void OnTimer()// Timer handler { //panel 1 is active if(ObjectGetInteger(0, "ActP_main_1", OBJPROP_STATE)==1) { //if auxiliary lines are allowed if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1) { //set new values to the edit fields double sl_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_SL_line1", OBJPROP_PRICE), _Digits); //stop loss ObjectSetString(0, "ActP_SL_edit1", OBJPROP_TEXT, DoubleToString(sl_pr, _Digits)); //take profit double tp_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_TP_line1", OBJPROP_PRICE), _Digits); ObjectSetString(0, "ActP_TP_edit1", OBJPROP_TEXT, DoubleToString(tp_pr, _Digits)); } } ... //redraw chart ChartRedraw(); } //+------------------------------------------------------------------+
Die Überwachung der anderen Linien wird ähnlich eingerichtet.
6. Ausführung von Handelsoperationen
So, alle erforderlichen Eingabefelder sind ausgefüllt und die Kontrollkästchen, Linien und Optionsschaltflächen sind angelegt. Jetzt ist es höchste Zeit, auf der Grundlage dieser Daten zu handeln.
6,1. Die Eröffnung eines Handelsvorgangs
Unter der Registerkarte „Börse“ haben wir die Schaltflächen „Buy“ (Kaufen) und „Sell“ (Verkaufen). Wenn alle Felder korrekt ausgefüllt sind, wird der Abschluss beim Anklicken der entsprechenden Schaltfläche ausgeführt.
Werfen wir einen Blick auf die Verarbeitungsroutinen dieser Schaltflächen:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the object on the chart if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the Buy button if(sparam=="ActP_buy_button1") { //check its state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //if it "pressed" if(selected) { //try to perform a deal deal(ORDER_TYPE_BUY); //and set the button to the unpressed state ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //and finish the function execution return; } //****************************************** //the similar for the sell button if(sparam=="ActP_sell_button1") { bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); if(selected) { deal(ORDER_TYPE_SELL); ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } ChartRedraw(); return; } ... } ... }
Wie wir sehen, wird die Funktion deal() ausgeführt:
//+------------------------------------------------------------------+ //| Deal function | //+------------------------------------------------------------------+ int deal(ENUM_ORDER_TYPE typ) { //get the data from the objects double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit1",OBJPROP_TEXT)); double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT)); double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit1",OBJPROP_TEXT)); int mag=StringToInteger(ObjectGetString(0, "ActP_Magic_edit1", OBJPROP_TEXT)); int dev=StringToInteger(ObjectGetString(0, "ActP_Dev_edit1", OBJPROP_TEXT)); string comm=ObjectGetString(0,"ActP_Comm_edit1",OBJPROP_TEXT); ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK; if(ObjectGetInteger(0,"ActP_Exe2_radio1",OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC; //prepare request MqlTradeRequest req; MqlTradeResult res; req.action=TRADE_ACTION_DEAL; req.symbol=Symbol(); req.volume=lots; req.price=Ask; req.sl=NormalizeDouble(SL, Digits()); req.tp=NormalizeDouble(TP, Digits()); req.deviation=dev; req.type=typ; req.type_filling=filling; req.magic=mag; req.comment=comm; //send order OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Da ist nichts Übersinnliches. Wir lesen zunächst die erforderlichen Daten aus den Objekten aus und erstellen auf ihrer Grundlage eine Handelsanfrage.
Schauen wir uns an, was wir getan haben:
Abbildung 18. Handelsoperationen - Ergebnis des Abschlusses eines Kaufvorgangs
Und siehe da, der Kaufvorgang wurde erfolgreich abgeschlossen.
6,2. Platzieren einer Pending Order
Mit den Schaltflächen „Buy“ und „Sell“ der Registerkarte „Pending“ lassen sich bedingte Aufträge (Pending Orders) platzieren.
Wir sehen uns die Verarbeitungsroutinen an:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the chart object if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the pending order set button if(sparam=="ActP_buy_button2") { //check the button state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //if it pressed if(selected) { ENUM_ORDER_TYPE typ; //get the pending order from the edit double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits()); //if it isn't stoplimit order if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0) { //if the order price is below the current price, set limit order if(Ask>pr) typ=ORDER_TYPE_BUY_LIMIT; //overwise - stop order else typ=ORDER_TYPE_BUY_STOP; } //if stoplimit order is specified else { //set operation type typ=ORDER_TYPE_BUY_STOP_LIMIT; } //try to place order order(typ); //set button to the unpressed state ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //and finish the execution of function return; } //****************************************** //similar for the sell pending order if(sparam=="ActP_sell_button2") { bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); if(selected) { ENUM_ORDER_TYPE typ; double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits()); if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0) { if(Bid<pr) typ=ORDER_TYPE_SELL_LIMIT; else typ=ORDER_TYPE_SELL_STOP; } else { typ=ORDER_TYPE_SELL_STOP_LIMIT; } order(typ); ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } ChartRedraw(); return; } ... } ... }
Hier erfolgt die Bestimmung der Art des künftigen Auftrages aufgrund der Lage des aktuellen Kurses relativ zu dem festgelegten Kurs, nach dessen Erreichen die Funktion order() den Auftrag platziert:
//+------------------------------------------------------------------+ //| The function places an order | //+------------------------------------------------------------------+ int order(ENUM_ORDER_TYPE typ) { //get the order details from the objects double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit2",OBJPROP_TEXT)); double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit2",OBJPROP_TEXT)); double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit2", OBJPROP_TEXT)); double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit2", OBJPROP_TEXT)); double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit2",OBJPROP_TEXT)); datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit2",OBJPROP_TEXT)); int mag=StringToInteger(ObjectGetString(0,"ActP_Magic_edit2",OBJPROP_TEXT)); string comm=ObjectGetString(0,"ActP_Comm_edit2",OBJPROP_TEXT); ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK; if(ObjectGetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC; if(ObjectGetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_RETURN; ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC; if(ObjectGetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY; if(ObjectGetInteger(0, "ActP_exp3_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED; //prepare request MqlTradeRequest req; MqlTradeResult res; req.action=TRADE_ACTION_PENDING; req.symbol=Symbol(); req.volume=lots; req.price=NormalizeDouble(pr,Digits()); req.stoplimit=NormalizeDouble(stoplim,Digits()); req.sl=NormalizeDouble(SL, Digits()); req.tp=NormalizeDouble(TP, Digits()); req.type=typ; req.type_filling=filling; req.type_time=expir_type; req.expiration=expir; req.comment=comm; req.magic=mag; //place order OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Schauen wir uns an, was wir getan haben:
Abbildung 19. Handelsoperationen - Ergebnis der Platzierung einer Pending Order
Der Auftrag mit Buy Stoplimit wurde erfolgreich platziert.
6,3. Ändern eines Vorgangs
Die Schaltfläche „Ändern“ unter der Registerkarte „Ändern/Schließen“ dient zur Vornahme der Änderungen an dem ausgewählten Vorgang:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the graphic object on the chart if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the modify position button if(sparam=="ActP_mod_button4") { //check the button state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //if it pressed if(selected)//if pressed { //modify position modify_pos(); //delete the elements of the scheme DeleteScheme("ActP" ,true); //and reset it (update the interface) SetScheme(0); //set the button to the unpressed state ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //finish the execution of function return; } ... } ... }
Unmittelbar verantwortlich für die Änderung ist die Funktion modify_pos():
//+------------------------------------------------------------------+ //| The function modifies the position parameters | //+------------------------------------------------------------------+ int modify_pos() { if(!PositionSelect(Symbol())) MessageBox("There isn't open position for symbol "+Symbol(),"Message"); //get the details from the edit objects double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit4",OBJPROP_TEXT)); double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit4", OBJPROP_TEXT)); int dev=StringToInteger(ObjectGetString(0,"ActP_dev_edit4",OBJPROP_TEXT)); //prepare request MqlTradeRequest req; MqlTradeResult res; req.action=TRADE_ACTION_SLTP; req.symbol=Symbol(); req.sl=NormalizeDouble(SL, _Digits); req.tp=NormalizeDouble(TP, _Digits); req.deviation=dev; //send request OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Ergebnis:
Abbildung20. Handelsoperationen - Ergebnis der Änderung der Eigenschaften eines Handelsvorgangs (Festlegen der Take Profit- und Stop Loss-Grenzen)
Die Stop Loss- und Take Profit-Grenzen wurden erfolgreich geändert.
6,4. Schließen eines Vorgangs
Die Schaltfläche „Schließen“ unter der Registerkarte „Ändern/Schließen“ dient zum (möglicherweise teilweisen) Schließen dieses Vorgangs:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the chart object if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the close button if(sparam=="ActP_del_button4") { //check the button state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //if pressed if(selected) { //try to close position int retcode=close_pos(); //if successful if(retcode==10009) { //delete scheme elements DeleteScheme("ActP" ,true); //set the new text for the list activisation ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select order -->"); } //set button state to unpressed ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //finish the execution of function return; } ... } ... }
Die Funktion close_pos() ist für die Änderung zuständig:
//+------------------------------------------------------------------+ //| Closes the position | //+------------------------------------------------------------------+ int close_pos() { if(!PositionSelect(Symbol())) MessageBox("There isn't open position for symbol "+Symbol(),"Message"); //get the position details from the objects double lots=StringToDouble(ObjectGetString(0,"ActP_lots_edit4",OBJPROP_TEXT)); if(lots>PositionGetDouble(POSITION_VOLUME)) lots=PositionGetDouble(POSITION_VOLUME); int dev=StringToInteger(ObjectGetString(0, "ActP_dev_edit4", OBJPROP_TEXT)); int mag=StringToInteger(ObjectGetString(0, "ActP_mag_edit4", OBJPROP_TEXT)); //prepare request MqlTradeRequest req; MqlTradeResult res; //the opposite deal is dependent on position type if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { req.price=Bid; req.type=ORDER_TYPE_SELL; } else { req.price=Ask; req.type=ORDER_TYPE_BUY; } req.action=TRADE_ACTION_DEAL; req.symbol=Symbol(); req.volume=lots; req.sl=0; req.tp=0; req.deviation=dev; req.type_filling=ORDER_FILLING_FOK; req.magic=mag; //send request OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Das Ergebnis: geschlossen wurden 1,5 von drei Posten des ausgewählten Geschäftsvorgangs:
Abbildung21. Handelsoperationen - Teilschließung einer Position
6,5. Änderung einer „Pending Order“
Die Schaltfläche „Ändern“ unter der Registerkarte „Ändern/Schließen“ dient zur Vornahme der Änderungen an dem ausgewählten Auftrag:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the chart graphic object if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the order modify button if(sparam=="ActP_mod_button3") { bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); if(selected) { //get the order ticket from the edit string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT); long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1)); //modifying an order modify_order(ticket); //update interface DeleteScheme("ActP" ,true); SetScheme(ticket); //set button to unpressed state ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //and finish the execution of function return; } ... } ... }
Verantwortlich für die Änderung ist die Funktion modify_order():
//+------------------------------------------------------------------+ //| The function modifies an order | //| ticket - order ticket | //+------------------------------------------------------------------+ int modify_order(int ticket) { //get the order details from the corresponding chart objects double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit3",OBJPROP_TEXT)); double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit3",OBJPROP_TEXT)); double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit3", OBJPROP_TEXT)); double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit3", OBJPROP_TEXT)); double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit3",OBJPROP_TEXT)); datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit3",OBJPROP_TEXT)); ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC; if(ObjectGetInteger(0, "ActP_exp2_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY; if(ObjectGetInteger(0, "ActP_exp3_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED; //prepare request to modify MqlTradeRequest req; MqlTradeResult res; req.action=TRADE_ACTION_MODIFY; req.order=ticket; req.volume=lots; req.price=NormalizeDouble(pr,Digits()); req.stoplimit=NormalizeDouble(stoplim,Digits()); req.sl=NormalizeDouble(SL, Digits()); req.tp=NormalizeDouble(TP, Digits()); req.type_time=expir_type; req.expiration=expir; //send request OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Hier sehen wir das Ergebnis - der Auftrag wurde erfolgreich geändert:
Abbildung 21. Änderung einer „Pending Order“
6,6. Das Löschen einer Pending Order
Die Schaltfläche „Löschen“ unter der Registerkarte „Ändern/Schließen“ dient zum Löschen des ausgewählten Auftrags:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... //Event - click on the graphic object on the chart if(id==CHARTEVENT_OBJECT_CLICK) { ... //click on the order delete button if(sparam=="ActP_del_button3") { //check the button state bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE); //if pressed if(selected) { //get the ticket from the list string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT); long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1)); //try to delete order int retcode=del_order(ticket); //if successful if(retcode==10009) { //delete all objects of the scheme DeleteScheme("ActP" ,true); //set new text for the list activation button ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select an order -->"); } //set button state to unpressed ObjectSetInteger(0, sparam, OBJPROP_STATE, 0); } //redraw chart ChartRedraw(); //and finish the execution of function return; } ... } ... }
Die Funktion del_pos() ist verantwortlich für die Löschung des Auftrags:
//+------------------------------------------------------------------+ //| The function for pending order deletion | //| ticket - order ticket | //+------------------------------------------------------------------+ int del_order(int ticket) { //prepare request for deletion MqlTradeRequest req; MqlTradeResult res; req.action=TRADE_ACTION_REMOVE; req.order=ticket; //send request OrderSend(req,res); //show message with the result MessageBox(RetcodeDescription(res.retcode),"Message"); //return retcode return(res.retcode); }
Schauen wir uns das Ergebnis an - der Auftrag wurde gelöscht:
Abb. 23 Handelsoperationen - Löschen einer Pending Order
Fazit
Es wurden alle Funktionen des Bedienfeldes geprüft und für gut befunden.
Ich hoffe, dass die Kenntnisse, die Ihnen die Lektüre dieses Artikels vermittelt hat, Ihnen bei der Entwicklung aktiver Bedienfelder als unersetzliche Helfer bei der Arbeit an der Börse nützlich sein werden.
Zur Aufnahme der Arbeit mit einem Bedienfeld muss zunächst die gepackte Archivdatei in den Ordner mit den Programm entpackt, anschließend der Indikator AP auf das Diagramm angewendet und erst dann das Active Panel-Bedienfeld des Expert-Systems gestartet werden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/62
- 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.