
Spontane Änderung der Expert-Advisor-Parameter vom Bedienfeld aus
Inhalt
Einleitung
1. Behandelte Themen
2. Struktur des Expert Advisors
3. Interaktion mit dem Bedienfeld
Fazit
Einleitung
Bei der Erstellung eines Expert Advisors kann die Anzahl externer Parameter sehr groß sein. Die Einstellungen müssen oft manuell geändert werden, was den gesamten Prozess aufgrund der vielen Parameter zeitaufwendig macht. Es ist natürlich möglich, Einstellungen im Vorhinein vorzubereiten und sie zu speichern, aber das ist in manchen Fällen möglicherweise nicht das Richtige. In solchen Fällen ist MQL5 sehr praktisch.
Erstellen wir also ein Bedienfeld, das es uns erlaubt, die Parameter eines Expert Advisors spontan zu ändern. Das ist für Benutzer interessant, die manuell oder semi-automatisch handeln wollen. Nach jeder Änderung werden die Parameter in eine Datei geschrieben, von der aus sie dann vom Expert Advisor gelesen werden, um dann auf dem Bedienfeld angezeigt zu werden.
1. Behandelte Themen
Zu Demonstrationszwecken werden wir einen einfachen Expert Advisor erstellen, der eine Position im Verzeichnis des JMA-Indikators öffnet. Der Expert Advisor wird an fertiggestellten Balken des aktuellen Symbols und Zeitrahmens arbeiten. Externe Parameter sind Indicator Period, Stop Loss, Take Profit, Reverse und Lot. Diese Optionen sind für unser Beispiel ausreichend.
Fügen wir zwei zusätzliche Parameter hinzu, um das Feld an- und auszuschalten (On/Off Info Panel) und die Parametereinstellungen der Expert-Advisor zu aktivieren/deaktivieren ("On The Fly" Setting). Bei einer großen Anzahl an Parametern ist es immer einfacher, zusätzliche Optionen am Anfang oder Ende der Liste anzubringen, um einen einfachen und schnellen Zugriff zu garantieren.
Abb. 1. Bedienfeld mit Parametern des Expert Advisors
Spontane Einstellungen sind standardmäßig deaktiviert. Bei der ersten Aktivierung dieser Einstellung erstellt der Expert Advisor eine Datei, um alle aktuellen Parameter zu speichern. Das gleiche trifft zu, wenn die Datei versehentlich gelöscht wird. Der Expert Advisor erkennt, dass etwas gelöscht wurde und stellt die Datei wieder her. Wenn die spontane Einstellung deaktiviert ist, wird der Expert Advisor von externen Parametern gesteuert.
Wenn diese Einstellung aktiviert ist, wird der Expert Advisor die Parameter der Datei lesen und Sie können entweder den gewünschten Wert auswählen, indem Sie auf einen beliebigen Parameter des Bedienfelds klicken, oder einen neuen Wert im Pop-Up-Fenster eingeben. Die Daten in der Datei werden mit jeder neuen Auswahl eines Wertes aktualisiert.
2. Struktur des Expert Advisors
Obwohl das Programm klein ist und alle Funktionen leicht in eine Datei passen, ist es immer noch einfacher, die Projektinformationen zu managen, wenn diese richtig kategorisiert sind. Daher ist es am besten, die Funktionen nach Typen zu kategorisieren und sie von Anfang an in verschiedenen Dateien zu speichern, um sie später in die Stammdatei aufzunehmen. Die untere Abbildung zeigt einen gemeinsamen Projektordner mit dem Spontanen Expert Advisor und allen "Include"-Dateien. Die "Include"-Dateien werden in einem separaten Ordner gespeichert (Include).
Abb. 2. Projektdateien im Navigationsfenster des MetaEditor
Wenn die "Include"-Dateien im gleichen Ordner sind wie die Stammdatei lautet der Code wie folgt:
//+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh"
Weitere Informationen über die Inklusion von Dateien können im Nachschlagewerk MQL5 gefunden werden.
Wir brauchen globale Variablen - Kopien von externen Parametern. Ihre Werte werden entweder von externen Parametern zugeteilt, oder von der Datei, je nach Einstellung des Expert Advisors. Diese Variablen werden im gesamten Programm-Code benutzt, zum Beispiel bei der Darstellung von Werten im Bedienfeld, bei Handelsfunktionen, etc.
// COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0;
Wie bei allen anderen Expert Advisors haben wir folgende Hauptfunktionen: OnInit, OnTick und OnDeinit. Und es gibt auch die OnTimer-Funktion. Jede Sekunde wird sie die Existenz der Parameter-Datei kontrollieren und wiederherstellen, falls sie versehentlich gelöscht wurde. Da wir mit dem Bedienfeld arbeiten müssen, verwenden wir auch die OnChartEvent-Funktion. Diese Funktion wird mit anderen verwandten Funktionen in eine separate Datei gespeichert (!OnChartEvent.mqh).
Der Kern-Code der Stammdatei lautet folgendermaßen:
#define szArrIP 5 // Size of the parameter array #define NAME_EXPERT MQL5InfoString(MQL5_PROGRAM_NAME) // Name of EA #define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder that contains the terminal data //+------------------------------------------------------------------+ //| STANDARD LIBRARIES | //+------------------------------------------------------------------+ #include <Trade/SymbolInfo.mqh> #include <Trade/Trade.mqh> //+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh" //+------------------------------------------------------------------+ //| CREATING CLASS INSTANCES | //+------------------------------------------------------------------+ CSymbolInfo mysymbol; // CSymbolInfo class object CTrade mytrade; // CTrade class object //+------------------------------------------------------------------+ //| EXTERNAL PARAMETERS | //+------------------------------------------------------------------+ input int Period_Ind = 10; // Indicator Period input double TakeProfit = 100; // Take Profit (p) input double StopLoss = 30; // Stop Loss (p) input bool Reverse = false; // Reverse Position input double Lot = 0.1; // Lot //--- input string slash=""; // * * * * * * * * * * * * * * * * * * * sinput bool InfoPanel = true; // On/Off Info Panel sinput bool SettingOnTheFly = false; // "On The Fly" Setting //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ int hdlSI=INVALID_HANDLE; // Signal indicator handle double lcheck=0; // For the check of parameter values bool isPos=false; // Position availability //--- COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0; //+------------------------------------------------------------------+ //| EXPERT ADVISOR INITIALIZATION | //+------------------------------------------------------------------+ void OnInit() { if(NotTest()) { EventSetTimer(1); } // If it's not the tester, set the timer //--- Init_arr_vparams(); // Initialization of the array of parameter values SetParameters(); // Set the parameters GetIndicatorsHandles(); // Get indicator handles NewBar(); // New bar initialization SetInfoPanel(); // Info panel } //+------------------------------------------------------------------+ //| CURRENT SYMBOL TICKS | //+------------------------------------------------------------------+ void OnTick() { // If the bar is not new, exit if(!NewBar()) { return; } else { TradingBlock(); } } //+------------------------------------------------------------------+ //| TIMER | //+------------------------------------------------------------------+ void OnTimer() { SetParameters(); SetInfoPanel(); } //+------------------------------------------------------------------+ //| EXPERT ADVISOR DEINITIALIZATION | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Get the deinitialization reason code if(NotTest()) { { Print(getUnitReasonText(reason)); } //--- // When deleting from the chart if(reason==REASON_REMOVE) { // Delete all objects created by the Expert Advisor DeleteAllExpertObjects(); //--- if(NotTest()) { EventKillTimer(); } // Stop the timer IndicatorRelease(hdlSI); // Delete the indicator handle } }
Ich habe auch ein paar weitere Funktionen in die Stammdatei eingearbeitet.
- GetIndicatorsHandles – holt den Indikator-Identifikator.
- NewBar – bestimmt das Ereignis "Neuer Balken".
- SetParameters – stellt Parameter je nach Modus ein.
- iZeroMemory – stellt einige Variablen und Arrays auf Null
//+------------------------------------------------------------------+ //| SETTING PARAMETERS IN TWO MODES | //+------------------------------------------------------------------+ // If this variable is set to false, the parameters in the file are read from the array // where they are saved for quick access after they have been read for the first time. // The variable is zeroed out upon the change in the value on the panel. bool flgRead=false; double arrParamIP[]; // Array where the parameters from the file are saved //--- void SetParameters() { // If currently in the tester or // in real time but with the "On The Fly" Setting mode disabled if(!NotTest() || (NotTest() && !SettingOnTheFly)) { // Zero out the variable and parameter array flgRead=false; ArrayResize(arrParamIP,0); //--- // Check the Indicator Period for correctness if(Period_Ind<=0) { lcheck=10; } else { lcheck=Period_Ind; } gPeriod_Ind=(int)lcheck; //--- gStopLoss=StopLoss; gTakeProfit=TakeProfit; gReverse=Reverse; //--- // Check the Lot for correctness if(Lot<=0) { lcheck=0.1; } else { lcheck=Lot; } gLot=lcheck; } else // If "On The Fly" Setting mode is enabled { // Check whether there is a file to write/read parameters to/from the file string lpath=""; //--- // If the folder exists if((lpath=CheckCreateGetPath())!="") { // Write or read the file WriteReadParameters(lpath); } } }
Der Quellcode der Funktion "SetParameters" ist einfach. Werfen wir einen genaueren Blick auf die Funktion "WriteReadParameters". Alles ist ziemlich einfach hier. Als erstes kontrollieren wir, ob die Datei mit Parametern existiert. Wenn dem so ist, lesen wir die Datei und schreiben Parameterwerte auf einen Array mit der Funktion "GetValuesParamsFromFile". Wenn die Datei noch nicht existiert, wird sie mit aktuellen externen Parametern erstellt.
Unten ist der Code mit detaillierten Kommentaren für die Implementierung der oben beschriebenen Ereignisse:
//+------------------------------------------------------------------+ //| WRITE DATA TO FILE | //+------------------------------------------------------------------+ void WriteReadParameters(string pth) { string nm_fl=pth+"ParametersOnTheFly.ini"; // File name and path //--- // Get the file handle to read the file int hFl=FileOpen(nm_fl,FILE_READ|FILE_ANSI); //--- if(hFl!=INVALID_HANDLE) // If the handle has been obtained, the file exists { // Get parameters from the file if(!flgRead) { // Set the array size ArrayResize(arrParamIP,szArrIP); //--- // Fill the array with values from the file flgRead=GetValuesParamsFromFile(hFl,arrParamIP); } //--- // If the array size is correct,... if(ArraySize(arrParamIP)==szArrIP) { // ...set the parameters to the variables //--- // Check the Indicator Period for correctness if((int)arrParamIP[0]<=0) { lcheck=10; } else { lcheck=(int)arrParamIP[0]; } gPeriod_Ind=(int)lcheck; //--- gTakeProfit=arrParamIP[1]; gStopLoss=arrParamIP[2]; gReverse=arrParamIP[3]; //--- // Check the Lot for correctness if(arrParamIP[4]<=0) { lcheck=0.1; } else { lcheck=arrParamIP[4]; } gLot=lcheck; } } else // If the file does not exist { iZeroMemory(); // Zero out variables //--- // When creating the file, write current parameters of the Expert Advisor //--- // Get the file handle to write to the file int hFl2=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,""); //--- if(hFl2!=INVALID_HANDLE) // If the handle has been obtained { string sep="="; //--- // Parameter names and values are obtained from arrays in the ARRAYS.mqh file for(int i=0; i<szArrIP; i++) { FileWrite(hFl2,arr_nmparams[i],sep,arr_vparams[i]); } //--- FileClose(hFl2); // Close the file //--- Print("File with parameters of the "+NAME_EXPERT+" Expert Advisor created successfully."); } } //--- FileClose(hFl); // Close the file }
Die Funktionen "WriteReadParameters" und "GetValuesParamsFromFile" sind in der Datei FILE_OPERATIONS.mqh.
Einige der Funktionen wurden bereits in meinem vorigen Artikel, "Wie erstelle ich MetaTrader5-Angebote für andere Applikationen", beschrieben und werden daher hier nicht noch einmal erwähnt. Sie sollten keinerlei Schwierigkeiten mit den Handelsfunktionen haben, da diese sehr einfach und gut dokumentiert sind. Wir befassen uns nun mit dem Hauptthema des Artikels.
3. Interaktion mit dem Bedienfeld
Die Datei "!OnChartEvent.mqh" enthält Funktionen für Interaktionen mit dem Bedienfeld. Variablen und Arrays, die in vielen Funktionen verwendet werden, werden im globalen Umfang gleich am Anfang festgestellt.
// Current value on the panel or // entered in the input box string currVal=""; bool flgDialogWin=false; // Flag for panel existence int szArrList=0,// Size of the option list array number=-1; // Parameter number in the panel list string nmMsgBx="", // Name of the dialog window nmValObj=""; // Name of the selected object //--- // Option list arrays in the dialog window string lenum[],lenmObj[]; //--- // colors of the dialog window elements color clrBrdBtn=clrWhite, clrBrdFonMsg=clrDimGray,clrFonMsg=C'15,15,15', clrChoice=clrWhiteSmoke,clrHdrBtn=clrBlack, clrFonHdrBtn=clrGainsboro,clrFonStr=C'22,39,38';
Danach folgt die Hauptfunktion, die Ereignisse identifiziert. In unserem Beispiel müssen wir zwei Ereignisse identifizieren:
- Das Ereignis CHARTEVENT_OBJECT_CLICK – linker Mausklick auf das graphische Objekt.
- Das Ereignis CHARTEVENT_OBJECT_EDIT – Ende der Textbearbeitung im graphischen Objekt "Bearbeitung".
Lesen Sie mehr über weitere MQL5-Ereignisse im Nachschlagewerk MQL5.
Setzen wir zuerst eine Kontrolle für die Identifikation von Ereignissen in Echtzeit fest, unter der Bedingung, dass Spontane Einstellungen aktiviert sind (SettingOnTheFly). Die Identifikation von Ereignissen wird von separaten Funktionen ausgeführt: ChartEvent_ObjectClick und ChartEvent_ObjectEndEdit.
//+------------------------------------------------------------------+ //| USER EVENTS | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // If the event is real time and the "On The Fly" setting mode is enabled if(NotTest() && SettingOnTheFly) { //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectClick(id,lparam,dparam,sparam)) { return; } //--- //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectEndEdit(id,lparam,dparam,sparam)) { return; } } //--- return; }
Wenn Sie auf das zu der Liste gehörende Objekt klicken, wird ein Dialogfenster auf dem Infofeld auftauchen, mit dem Sie weitere Werte auswählen oder einen neuen Wert in das Input-Feld eingeben können.
Abb. 3. Dialogfenster für Wertänderungen der ausgewählten Parameter
Werfen wir einen genaueren Blick auf die Funktionsweise. Wenn ein graphisches Objekt angeklickt wurde, verwendet das Programm zuerst die Funktion ChartEvent_ObjectClick um mit dem Ereignis-Identifikator zu kontrollieren, ob tatsächlich auf ein graphisches Objekt geklickt wurde.
Wenn Sie wollen, dass sich das Dialogfenster in der Mitte des Diagramms öffnet, müssen Sie die Diargammgröße kennen. Diese bekommen Sie, indem Sie die Eigenschaften "Diagrammbreite in Pixeln" (CHART_WIDTH_IN_PIXELS) und "Diagrammhöhe in Pixeln" (CHART_HEIGHT_IN_PIXELS) in die Funktion ChartGetInteger eingeben. Das Programm schaltet dann um zu DialogWindowInfoPanel. Im Nachschlagewerk MQL5 können Sie sich mit allen Diagrammeigenschaften vertraut machen.
Unten ist der Code für die Implementierung der obigen Aktionen:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectClick(int id,long lparam,double dparam,string sparam) { // If there was an event of clicking on a graphical object if(id==CHARTEVENT_OBJECT_CLICK) // id==1 { Get_STV(); // Get all data on the symbol //--- string clickedChartObject=sparam; // Name of the clicked object //--- // Get the chart size width_chart=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0); height_chart=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0); //--- DialogWindowInfoPanel(clickedChartObject); } //--- return(false); }
Mit der Funktion DialogWindowInfoPanel kontrollieren wir zuerst, ob das Dialogfenster gerade offen ist. Wenn das Fenster nicht gefunden wurde, kontrolliert die Funktion GetNumberClickedObjIP ob der Klick in Bezug zu einem Objekt der Liste des Infofeldes war. Wenn das angeklickte Objekt das Objekt von der Liste ist, wird die Funktion die relevante Elementnummer des Array der Objekte zurückgeben. Mit dieser Nummer bestimmt die Funktion InitArraysAndDefault schließlich die Größe der Liste im Dialogfenster und Standardwerte. Wenn alle Ereignisse erfolgreich waren, wird das Dialogfenster erscheinen.
Wenn die Funktion DialogWindowInfoPanel bestimmt, dass das Dialogfenster immer offen ist, wird das Programm überprüfen, ob ein Objekt im Dialogfenster angeklickt wurde. Zum Beispiel, nach dem Öffnen des Dialogfensters wird die Zeile, dessen Wert gerade in dem Feld angezeigt wird, als ausgewählt erscheinen. Wenn Sie eine weitere Option in der Liste auswählen, wird das Programm die Funktion SelectionOptionInDialogWindow ausführen, die die Dialogfensterliste auswählt.
Wenn Sie auf die Listenoption klicken, die gerade ausgewählt ist, wird das Objekt als ein Objekt identifiziert, das bearbeitet werden muss. Ein Eingabefeld wird erscheinen, damit ein neuer Wert eingegeben werden kann, wenn Sie auf das Feld klicken. Die Funktion SetEditObjInDialogWindow ist für die Einstellung des Eingabefeldes verantwortlich.
Und schließlich wird das Programm, wenn die Apply-Taste geklickt wurde, überprüfen, ob der Wert geändert wurde. Wenn dem so ist, wird der neue Wert auf dem Bedienfeld erscheinen und in die Datei geschrieben.
Der Code der Hauptfunktion des Dialogfensters ist unten gegeben:
//+------------------------------------------------------------------+ //| DIALOG WINDOW OF THE INFO PANEL | //+------------------------------------------------------------------+ void DialogWindowInfoPanel(string clickObj) { // If there is currently no dialog window if(!flgDialogWin) { // Get the object number in the array // Exit if none of the parameters displayed on the panel has been clicked if((number=GetNumberClickedObjIP(clickObj))==-1) { return; } //--- // Initialization of default values //and determination of the list array size if(!InitArraysAndDefault()) { return; } //--- // Set the dialog window SetDialogWindow(); //--- flgDialogWin=true; // Mark the dialog window as open ChartRedraw(); } else // If the dialog window is open { // Set the input box for the modification of the value SetEditObjInDialogWindow(clickObj); //--- // If one of the buttons in the dialog box is clicked if(clickObj=="btnApply" || clickObj=="btnCancel") { // If the Apply button is clicked if(clickObj=="btnApply") { // Compare values on the panel with the ones on the list // If the value on the list is different from the one that is currently displayed on the panel // (which means it is different from the one in the file), // ...change the value on the panel and update the file if(currVal!=ObjectGetString(0,nmValObj,OBJPROP_TEXT)) { // Update the value on the panel ObjectSetString(0,nmValObj,OBJPROP_TEXT,currVal); ChartRedraw(); //--- // Read all data on the panel and write it to the file WriteNewData(); } } //--- DelDialogWindow(lenmObj); // Delete the dialog window iZeroMemory(); // Zero out the variables //--- // Update the data SetParameters(); GetHandlesIndicators(); SetInfoPanel(); //--- ChartRedraw(); } else // If neither Apply nor Cancel has been clicked { // Selection of the dialog window list option SelectionOptionInDialogWindow(clickObj); //--- ChartRedraw(); } } }
Jedes Mal wenn ein neuer Wert in das Inputfeld eingegeben wird, wird das Ereignis CHARTEVENT_OBJECT_EDIT erstellt und das Programm schaltet um zur Funktion ChartEvent_ObjectEndEdit. Wenn der Wert des Dialogfensters verändert wurde, wird der eingegebene Wert gespeichert, auf Richtigkeit überprüft und den Objekten in der Liste zugeteilt. Unten ist es detaillierter in Code geschrieben:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectEndEdit(int id,long lparam,double dparam,string sparam) { if(id==CHARTEVENT_OBJECT_ENDEDIT) // id==3 { string editObject=sparam; // Name of the edited object //--- // If the value has been entered in the input box in the dialog window if(editObject=="editValIP") { // Get the entered value currVal=ObjectGetString(0,"editValIP",OBJPROP_TEXT); //--- // (0) Period Indicator if(number==0) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,currVal); } //--- // (4) Lot if(number==4) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal=DS(SS.vol_min,2); } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,DS2(SD(currVal))); } //--- // (1) Take Profit (p) // (2) Stop Loss (p) if(number==1 || number==2) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB1",OBJPROP_TEXT,currVal); } //--- DelObjbyName("editValIP"); ChartRedraw(); } } //--- return(false); }
Der Expert Advisor in Action ist in diesem Video zu sehen:
Fazit
Die komprimierten Dateien am Ende des Artikels können für nähere Betrachtung heruntergeladen werden.
Ich hoffe, dieser Artikel wird mit diesen einfachen Beispielen MQL5-Neulingen helfen, schnelle Antworten zu finden. Ich habe absichtlich einige Kontrollen der zur Verfügung gestellten Codeschnipsel ausgelassen.
Wenn Sie zum Beispiel die Diagrammhöhe oder -breite ändern, wenn das Dialogfenster offen ist, wird das Dialogfenster nicht automatisch in der Mitte sein. Und wenn Sie eine weitere Option von der Liste auswählen, wird das Objekt, mit dem die relevante Zeile ausgewählt wurde, erheblich verschoben. Dies ist die Hausaufgabe. Programmieren zu üben ist sehr wichtig, und je mehr man übt, desto besser wird man.
Viel Erfolg!
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/572





- 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.