
Wie erstelle ich MetaTrader 5-Angebote für andere Applikationen
Inhalt
Einleitung1. Themenbereiche
2. Datenformat
3. Externe Programmparameter
4. Von Nutzern eingegebene Parameter überprüfen
5. Globale Variablen
6. Informationsfenster
7. Applikationshauptblock
8. Ordner anlegen und Daten ablegen
Fazit
Einleitung
Bevor ich damit begann, mich mit MQL5 vertraut zu machen, hatte ich mich bereits mit einigen anderen Apps zur Entwicklung von Trading-Systemen beschäftigt. Ich kann nicht behaupten, dass ich meine Zeit verschwendet habe. Einige von ihnen enthalten durchaus nützliche Tools, die Nutzern Zeit sparen, viele verschiedene Themengebiete abdecken, Mythen dekonstruieren und Entwicklungsimpulse geben, ohne dass es der Kenntnisse von Programmiersprachen bedarf.
Diese Apps bedürfen historischer Daten. Aufgrund des Fehlens gewisser Datenstandardformate mussten Sie allerdings oft vor ihrer Benutzung editiert werden (mittels Excel beispielsweise), damit sie dem zulässigen Format eines entsprechenden Programms entsprechen. Selbst wenn es Ihnen gelingt, alle notwendigen Details zu verstehen, so müssen Sie sich dennoch um viele wichtige Dinge selbst kümmern. Nutzer können sich verschiedener Skriptversionen bedienen, um die Angebote von MetaTrader 4 in das notwendige Format zu überführen. Falls es eine entsprechende Nachfrage geben sollte, so könnten auch wir eine MQL5-Skriptversion entwickeln.
1. Themenbereiche
Dieser Artikel befasst sich mit den folgenden Themenschwerpunkten:
- mit der Symbolleiste des Market Watch-Fensters und der üblichen Server-Symbolleiste arbeiten
- das Überprüfen der Datentiefe und das eventuell notwendige Herunterladen fehlender Datenmengen, um verschiedene Situationen sicher zu meistern
- das Anzeigen von Informationen betreffend die Anforderung von Daten im Custom Panel-Chart und dem Journal
- das Präparieren von Daten, um sie in einem nutzerfreundlichen Format abzulegen
- das Anlegen von Verzeichnisstrukturen anlegen
- das Ablegen von Daten
2. Datenformat
Ich werde ein Beispiel zur Präparierung von Daten präsentieren, die in NeuroShell DayTrader Professional (NSDT) verwendet werden sollen. Ich habe beide Versionen getestet - NSDT 5 und NSDT 6 - und fand heraus, dass beide unterschiedliche Datenformate bevorzugen. Die NSDT v.5-Daten und -Zeitdaten sollten sich in verschiedenen Spalten befinden. Die erste Zeile in der Datei sollte folgendermaßen aussehen:
„Datum" „Zeit" „Offen" „Hoch" „Niedrig" „Geschlossen" „Volumen“
Die Kopfzeile in NSDT v.6 weist ein anderes Aussehen auf, das es der Applikation erlaubt, die Datei zu akzeptieren. Das bedeutet, dass Datum und Zeit sich in der selben Spalte befinden sollten.
Datum,Offen,Hoch,Niedrig,Geschlossen,Volumen
MetaTrader 5 erlaubt es Nutzern, Angebote in *.csv Dateien zu speichern. Die Daten sehen in der Datei dabei wie folgt aus:
Abb. 1. Vom MetaTrader 5-Terminal gespeicherte Daten
Jedoch können wir die Kopfzeile nicht einfach so editieren, da das Datum ein anderes Format aufweisen muss. Für NSDT v.5:
TT,MM.JJJJ,hh:mm,Offen,Hoch,Niedrig,Geschlossen,VolumenFür NSDT v.6:
TT/MM/JJJJ,hh:mm,Offen,Hoch,Niedrig,Geschlossen,Volumen
Dropdown-Listen werden in den externen Scriptparametern verwendet, mit deren Hilfe Nutzer notwendige Formate auswählen können. Neben der Auswahl der Header- und Datumsformate werden wir dem Nutzer die Möglichkeit geben, eine Reihe von Symbolen und Daten auszuwählen, mit denen sie Dateien beschreiben können. Um dies zu tun, werden wir drei Versionen bereitstellen:
- Die Daten nur auf das aktuelle Symbol schreiben - in dem Chart (NUR DAS AKTUELLE SYMBOL), in dem das Script ausgeführt worden ist.
- Die Daten auf das Symbol schreiben, das sich im Market Watch-Fenster befindet (MARKETWATCH-SYMBOLE).
- Die Daten auf alle Symbole schreiben, die auf dem Server zur Verfügung stehen (ALLE SYMBOLE DER LSITE).
Lassen Sie uns den folgende Code vor den externen Parametern im Scriptcode eingeben, um solch eine Liste zu erstellen:
//_________________________________ // HEADER_FORMATS_ENUMERATION enum FORMAT_HEADERS { NSDT_5 = 0, // "Date" "Time" "Open" "High" "Low" "Close" "Volume" NSDT_6 = 1 // Date,Open,High,Low,Close,Volume }; //--- //___________________________ // ENUMERATION_OF_DATA_FORMATS enum FORMAT_DATETIME { SEP_POINT1 = 0, // dd.mm.yyyy hh:mm SEP_POINT2 = 1, // dd.mm.yyyy, hh:mm SEP_SLASH1 = 2, // dd/mm/yyyy hh:mm SEP_SLASH2 = 3 // dd/mm/yyyy, hh:mm }; //--- //____________________________ // ENUMERATION_OF_FILING_MODES enum CURRENT_MARKETWATCH { CURRENT = 0, // ONLY CURRENT SYMBOLS MARKETWATCH = 1, // MARKETWATCH SYMBOLS ALL_LIST_SYMBOLS = 2 // ALL LIST SYMBOLS };
Mehr Hinweise zu Aufzählungen finden Sie unter MQL5-Verweise.
3. Externe Programmparameter
Nun können wir eine vollständige Liste aller externen Scriptparameter erstellen:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| EXTERNAL_PARAMETERS | //+------------------------------------------------------------------+ input datetime start_date = D'01.01.2011'; // Start Date input datetime end_date = D'18.09.2012'; // End Date input FORMAT_HEADERS format_headers = NSDT_5; // Format Headers input FORMAT_DATETIME format_date = SEP_POINT2; // Format Datetime input CURRENT_MARKETWATCH curr_mwatch = CURRENT; // Mode Write Symbols input bool clear_mwatch = true; // Clear Market Watch input bool show_progress = true; // Show Progress (%)
Externe Parameter erfüllen folgende Funktionen:
- Nutzer können ein Datumsintervall mithilfe der beiden Parameter Datum Starten (start_date) und Datum Beenden (end_date) definieren.
- Die Header Formatieren-Dropdown-Liste (format_headers) erlaubt es Nutzern, ein Headerformat auszuwählen.
- Die Datum Formatieren-Dropdown-Liste (format_date) erlaubt es Nutzern, einen Zeit- und Datumsformat auszuwählen.
- Die Write Symbols-Dropdown-Liste (curr_mwatch) erlaubt es Nutzern, die Anzahl an Symbolen zum Ablegen auszuwählen.
- Falls der Market Watch Löschen-Parameter (clear_mwatch) auf wahr eingestellt ist, können Nutzer alle Symbole des Market Watch-Fensters löschen, nachdem diese abgelegt wurden. Dies betrifft nur die Chart-Symbole, die momentan nicht aktiv sind.
- Der Fortschritte Anzeigen-Parameter (%) (show_progress) zeigt den Prozess des Ablegens im Datenfeld an. Das Ablegen geht schneller vonstatten, falls dieser Parameter deaktiviert ist.
Nachfolgend sehen Sie den Zustand der externen Parameter während des Startvorgangs.
Abb. 2. Externe Parameter der Applikation
4. Von Nutzern eingegebene Parameter überprüfen
Lassen Sie uns eine Funktion kreieren, die die Parameter überprüft, die seitens der Nutzer vor dem Basiscode eingegeben worden sind. Beispielsweise sollte das Startdatum im Datum Starten-Parameter ein früheres als das des Datum Beenden-Parameters sein. Das Kopfzeilenformat sollte mit den Zeit- und Datumsformaten übereinstimmen. Falls ein Nutzer beim Einstellen der Parameter einen Fehler begangen hat, wird die folgende Warnung auftauchen und das Programm stoppen:
Beispielhafter Warnhinweis:
Abb. 3. Beispielhafter Warnhinweis betreffend einen unkorrekt spezifizierten Parameter
ValidationParameters() function:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| CHECKING_CORRECTNESS_OF_PARAMETERS | //+------------------------------------------------------------------+ bool ValidationParameters() { if(start_date>=end_date) { MessageBox("The start date should be earlier than the ending one!\n\n" "Application cannot continue. Please retry.", //--- "Parameter error!",MB_ICONERROR); //--- return(true); } //--- if(format_headers==NSDT_5 && (format_date==SEP_POINT1 || format_date==SEP_SLASH1)) { MessageBox("For the headers of the following format:\n\n" "\"Date\" ""\"Time\" ""\"Open\" ""\"High\" ""\"Low\" ""\"Close\" ""\"Volume\"\n\n" "Date/time format can be selected out of two versions:\n\n" "dd.mm.yyyy, hh:mm\n" "dd/mm/yyyy, hh:mm\n\n" "Application cannot continue. Please retry.", //--- "Header and date/time formats do not match!",MB_ICONERROR); //--- return(true); } //--- if(format_headers==NSDT_6 && (format_date==SEP_POINT2 || format_date==SEP_SLASH2)) { MessageBox("For the headers of the following format:\n\n" "Date,Open,High,Low,Close,Volume\n\n" "Date/time format can be selected out of two versions:\n\n" "dd.mm.yyyy hh:mm\n" "dd/mm/yyyy hh:mm\n\n" "Application cannot continue. Please retry.", //--- "Header and date/time formats do not match!",MB_ICONERROR); //--- return(true); } //--- return(false); }
5. Globale Variablen
Als Nächstes sollten wir alle globalen Variablen und Arrays bestimmen, die für das Script genutzt werden:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| GLOBAL_VARIABLES_AND_ARRAYS | //+------------------------------------------------------------------+ MqlRates rates[]; // Array for copying data //--- string symbols[]; // Symbol array //--- // Array of graphic object names string arr_nmobj[22]= { "fon","hd01", "nm01","nm02","nm03","nm04","nm05","nm06","nm07","nm08","nm09","nm10", "nm11","nm12","nm13","nm14","nm15","nm16","nm17","nm18","nm19","nm20" }; //--- // Array of displayed text containing graphic objects string arr_txtobj[21]; //--- string path=""; // File path int cnt_symb=0; // Number of symbols int sz_arr_symb=0; // Symbol array size int bars=0; // Number of bars according to the specified TF int copied_bars=0; // Number of bars copied for writing double pgs_pcnt=0; // Writing progress int hFl=INVALID_HANDLE; // File handle //--- string // Variables for data formatting sdt="", // Date line dd="", // Day mm="", // Month yyyy="", // Year tm="", // Time sep=""; // Separator //--- int max_bars=0; // Maximum number of bars in the terminal settings //--- datetime first_date=0, // First available data in a specified period first_termnl_date=0, // First available data in the terminal's database first_server_date=0, // First available data in the server's database check_start_date=0; // Checked correct date value
6. Informationsfenster
Nun sollten wir uns den Elementen zuwenden, die im Informationsfenster angezeigt werden. Dabei können drei Typen von grafischen Objekten als Hintergrund verwendet werden.
- Bei dem einfachsten dieser drei handelt es sich um das "Rectangle Label“ (OBJ_RECTANGLE_LABEL).
- Falls Sie Ihrem Interface einen persönlicheren Touch verleihen wollen, sollten Sie das „Bitmap“-Objekt (OBJ_BITMAP) in Erwägung ziehen.
- Das „Edit“-Objekt (OBJ_EDIT) kann ebenso als Hintergrund dienen. Aktivieren Sie die „Nur lesen“-Eigenschaft, um zu verhindern, dass Texte eingegeben werden können. Das „Edit“-Objekt hat noch einen anderen Vorteil. Falls Sie ein Informationsfenster in einem Expert Advisior kreiert haben und Sie wollen, dass es während der Tests (Visualisierungsmodus) den gleich Look aufweist, so wird Ihnen dies lediglich durch den letzten Typ ermöglicht. Das heißt, dass weder OBJ_RECTANGLE_LABEL, noch OBJ_BITMAP während der Tests via Visualisierungsmodus angezeigt werden.
Obwohl in unserem Fall nur ein Script und kein Expert Advisor entwickelt wird, wird uns ein OBJ_EDIT-Hintergrund als Beispiel dienen. Die untere Abbildung zeigt das Ergebnis:
Abb. 4. Informationsfenster
Lassen Sie uns all die im Fenster abgebildeten Daten betrachten:
- Symbol (momentan/gesamt) – Symbol, auf das die Daten momentan heruntergeladen, kopiert oder geschrieben werden. Die linke Nummer in den Klammern zeigt die aktuelle Symbolanzahl an. Die rechte Nummer gibt die allgemeine Nummer an Symbolen an, mit denen das Script arbeiten wird.
- Pfad-Symbol – Symbolpfad oder -kategorie, zu dem es gehört. Wenn Sie das Kontextmenü via Rechtsklick im Market Watch-Fenster aufrufen und „Symoble...“ auswählen, wird ein Fenster erscheinen, das alle Symbole auflistet. Mehr Informationen finden Sie im Benutzerhandbuch des Terminals.
- Zeitrahmen – Periode (Zeitrahmen). Gibt den Zeitrahmen an, in welchem das Script gestartet wird.
- Startzeitpunkt – Von einem Nutzer mittels Script-Parametern spezifiziertes Startdatum.
- Erstes Datum (H1) – Gibt das erste mögliche Datum (Balken) des aktuellen Zeitrahmens an.
- Erstes Terminal-Datum (M1) – Gibt das erste mögliche Datum des M1-Zeitrahmens des Terminals an.
- First Server-Datum (M1) – Gibt das erste mögliche Datum des M1-Zeitrahmens des Servers an.
- Max. Optionsterminalbalken – Zeigt die maximale Anzahl an Balken an, die im Chart angezeigt werden sollen. Kann in den Terminaleinstellungen geändert werden.
- Kopierte Balken – Anzahl der kopierten Balken zum Schreiben.
- Aktueller Symbol-Fortschrittswert – Zeigt den prozentualen Wert der geschriebenen Daten des Symbols an.
Nachstehend finden Sie den Code eines derartigen Informationsfensters.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| INFORMATION_PANEL | //|------------------------------------------------------------------+ void InfoTable(int s) { int fnt_sz=8; // Font size string fnt="Calibri"; // Header font color clr=clrWhiteSmoke; // Color //--- int xH=300; int height_pnl=0; int yV1=1,yV2=12,xV1=165,xV2=335,xV3=1; //--- string sf="",stf="",ssf=""; bool flg_sf=false,flg_stf=false,flg_ssf=false; //--- if(show_progress) { height_pnl=138; } else { height_pnl=126; } //--- flg_sf=SeriesInfoInteger(symbols[s],_Period,SERIES_FIRSTDATE,first_date); flg_stf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_termnl_date); flg_ssf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date); //--- if(flg_sf) { sf=TSdm(first_date); } else { sf="?"; } if(flg_stf) { stf=TSdm(first_termnl_date); } else { stf="?"; } if(flg_ssf) { ssf=TSdm(first_server_date); } else { ssf="?"; } //--- if(cnt_symb==0) { cnt_symb=1; } //--- int anchor1=ANCHOR_LEFT_UPPER,anchor2=ANCHOR_RIGHT_UPPER,corner=CORNER_LEFT_UPPER; //--- string path_symbol=SymbolInfoString(symbols[s],SYMBOL_PATH); path_symbol=StringSubstr(path_symbol,0,StringLen(path_symbol)-StringLen(symbols[s])); //--- arr_txtobj[0]="INFO TABLE"; arr_txtobj[1]="Symbol (current / total) : "; arr_txtobj[2]=""+symbols[s]+" ("+IS(s+1)+"/"+IS(cnt_symb)+")"; arr_txtobj[3]="Path Symbol : "; arr_txtobj[4]=path_symbol; arr_txtobj[5]="Timeframe : "; arr_txtobj[6]=gStrTF(_Period); arr_txtobj[7]="Input Start Date : "; arr_txtobj[8]=TSdm(start_date); arr_txtobj[9]="First Date (H1) : "; arr_txtobj[10]=sf; arr_txtobj[11]="First Terminal Date (M1) : "; arr_txtobj[12]=stf; arr_txtobj[13]="First Server Date (M1) : "; arr_txtobj[14]=ssf; arr_txtobj[15]="Max. Bars In Options Terminal : "; arr_txtobj[16]=IS(max_bars); arr_txtobj[17]="Copied Bars : "; arr_txtobj[18]=IS(copied_bars); arr_txtobj[19]="Progress Value Current Symbol : "; arr_txtobj[20]=DS(pgs_pcnt,2)+"%"; //--- Create_Edit(0,0,arr_nmobj[0],"",corner,fnt,fnt_sz,clrDimGray,clrDimGray,345,height_pnl,xV3,yV1,2,C'15,15,15'); //--- Create_Edit(0,0,arr_nmobj[1],arr_txtobj[0],corner,fnt,8,clrWhite,C'64,0,0',345,12,xV3,yV1,2,clrFireBrick); //--- Create_Label(0,arr_nmobj[2],arr_txtobj[1],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2,0); Create_Label(0,arr_nmobj[3],arr_txtobj[2],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2,0); //--- Create_Label(0,arr_nmobj[4],arr_txtobj[3],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*2,0); Create_Label(0,arr_nmobj[5],arr_txtobj[4],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*2,0); //--- Create_Label(0,arr_nmobj[6],arr_txtobj[5],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*3,0); Create_Label(0,arr_nmobj[7],arr_txtobj[6],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*3,0); //--- Create_Label(0,arr_nmobj[8],arr_txtobj[7],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*4,0); Create_Label(0,arr_nmobj[9],arr_txtobj[8],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*4,0); //--- Create_Label(0,arr_nmobj[10],arr_txtobj[9],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*5,0); Create_Label(0,arr_nmobj[11],arr_txtobj[10],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*5,0); //--- Create_Label(0,arr_nmobj[12],arr_txtobj[11],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*6,0); Create_Label(0,arr_nmobj[13],arr_txtobj[12],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*6,0); //--- Create_Label(0,arr_nmobj[14],arr_txtobj[13],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*7,0); Create_Label(0,arr_nmobj[15],arr_txtobj[14],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*7,0); //--- Create_Label(0,arr_nmobj[16],arr_txtobj[15],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*8,0); Create_Label(0,arr_nmobj[17],arr_txtobj[16],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*8,0); //--- Create_Label(0,arr_nmobj[18],arr_txtobj[17],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*9,0); Create_Label(0,arr_nmobj[19],arr_txtobj[18],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*9,0); //--- if(show_progress) { Create_Label(0,arr_nmobj[20],arr_txtobj[19],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*10,0); Create_Label(0,arr_nmobj[21],arr_txtobj[20],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*10,0); } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| CREATING_LABEL_OBJECT | //+------------------------------------------------------------------+ void Create_Label(long chrt_id, // chart id string lable_nm, // object name string rename, // displayed name long anchor, // anchor point long corner, // attachment corner string font_bsc, // font int font_size, // font size color font_clr, // font color int x_dist, // X scale coordinate int y_dist, // Y scale coordinate long zorder) // priority { if(ObjectCreate(chrt_id,lable_nm,OBJ_LABEL,0,0,0)) // creating object { ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,rename); // set name ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc); // set font ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr); // set font color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ANCHOR,anchor); // set anchor point ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner); // set attachment corner ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size); // set font size ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist); // set X coordinates ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist); // set Y coordinates ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false); // unable to highlight the object, if FALSE ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder); // Higher/lower priority ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n"); // no tooltip, if "\n" } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| CREATING_EDIT_OBJECT | //+------------------------------------------------------------------+ void Create_Edit(long chrt_id, // chart id int nmb_win, // window (subwindow) index string lable_nm, // object name string text, // displayed text long corner, // attachment corner string font_bsc, // font int font_size, // font size color font_clr, // font color color font_clr_brd, // font color int xsize, // width int ysize, // height int x_dist, // X scale coordinate int y_dist, // Y scale coordinate long zorder, // priority color clr) // background color { if(ObjectCreate(chrt_id,lable_nm,OBJ_EDIT,nmb_win,0,0)) // creating object { ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text); // set name ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner); // set attachment corner ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc); // set font ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ALIGN,ALIGN_CENTER); // center alignment ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size); // set font size ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr); // font color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BORDER_COLOR,font_clr_brd); // background color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BGCOLOR,clr); // background color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XSIZE,xsize); // width ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YSIZE,ysize); // height ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist); // set X coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist); // set Y coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false); // unable to highlight the object, if FALSE ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder); // Higher/lower priority ObjectSetInteger(chrt_id,lable_nm,OBJPROP_READONLY,true); // Read only ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n"); // no tooltip if "\n" } }
Nachdem die Script-Operation beendet wurde oder das Script durch einen Nutzer vorzeitig gelöscht wurde, sollten alle vom Script kreierten Grafikobjekte gelöscht werden. Mithilfe der folgenden Instruktionen finden Sie heraus, wie das funktioniert.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| DELETE_ALL_GRAPHICAL_OBJECTS_CREATED_BY_THE_SCRIPT | //+------------------------------------------------------------------+ void DelAllScriptObjects() { // Receive the size of graphical object names array int sz_arr1=ArraySize(arr_nmobj); //--- // Delete all objects for(int i=0; i<sz_arr1; i++) { DelObjbyName(arr_nmobj[i]); } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| DELETE_OBJECTS_BY_NAME | //+------------------------------------------------------------------+ int DelObjbyName(string Name) { int nm_obj=0; bool res=false; //--- nm_obj=ObjectFind(ChartID(),Name); //--- if(nm_obj>=0) { res=ObjectDelete(ChartID(),Name); //--- if(!res) { Print("Object deletion error: - "+ErrorDesc(Error())+""); return(false); } } //--- return(res); }
7. Applikationshauptblock
Die Hauptaufgabe des Scripts ist die folgende: OnStart(). Diese Funktion dient dazu, anderen Funktion das Ausführen zu befehlen. Die Details der Programmoperationen können im Code eingesehen werden:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| SCRIPT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> | //+------------------------------------------------------------------+ void OnStart() { // If user-defined parameters are incorrect, // error message is shown and the program is closed if(ValidationParameters()) { return; } //--- max_bars=TerminalInfoInteger(TERMINAL_MAXBARS); // Receive available number of bars in the window //--- GetSymbolsToArray(); // Filling symbol array with names sz_arr_symb=ArraySize(symbols); // Receive symbol array size //--- SetSeparateForFormatDate(); // Set a separator for date format //--- // Revise all symbols and write their data to file for(int s=0; s<=sz_arr_symb-1; s++) { copied_bars=0; // Reset copied bars variable to zero for writing pgs_pcnt=0.0; // Reset variable of the symbol data writing progress //--- InfoTable(s); ChartRedraw(); //--- // Receive current symbol data int res=GetDataCurrentSymbol(s); //--- if(res==0) { BC } // If zero, break the loop or start the next iteration //--- if(res==2) // Program operation interrupted by user { DelAllScriptObjects(); // Deleted objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; } //--- // Receive the path for creating the file and create directories for them // If the string is empty, break the loop or start the next iteration if((path=CheckCreateGetPath(s))=="") { BC } //--- WriteDataToFile(s); // Write data to file } //--- // Delete symbols from Market Watch window if necessary DelSymbolsFromMarketWatch(); //--- // Delete objects created by the script from the chart Sleep(1000); DelAllScriptObjects(); }
Lassen Sie uns die Funktionen ansehen, in den sich die Hauptaktivitäten ereignen.
Das Array des Symbols (symbols[]) ist bis zum Rand mit Symbolnamen gefüllt (GetSymbolsToArray()-Funktion). Die Größe des Arrays als auch die Anzahl seiner Symbole basiert auf dem vom Nutzer spezifizierten Write-Symbols-Parameter (curr_mwatch).
Für den Fall, dass ein Nutzer lediglich die Daten eines einzelnen Symbols besitzt, beträgt diese Größe 1.
ArrayResize(symbols,1); // Set the array size to be equal to 1 symbols[0]=_Symbol; // Specify the current symbol's name
Falls ein Nutzer die Daten aller Symbole des Market Watch-Fensters oder aller möglichen Symbole erhalten will, wird die Array-Größe durch die folgende Funktion definiert:
int SymbolsTotal( bool selected // true – only MarketWatch symbols );
Um zu vermeiden, dass zwei Blöcke für zwei Varianten mit beinahe identischem Code kreiert werden, werden wir uns der MWatchOrAllList() pointer-Funktion bedienen, die entweder ein wahr oder falsch ausgibt. Dieser Wert definiert woher die Symbolleiste stammen soll - nur vom Market Watch-Fenster (wahr) oder von der allgemeinen Liste aller Symbole (falsch).
//____________________________________________________________________ //+------------------------------------------------------------------+ //| POINTER_TO_MARKET_WATCH_WINDOW_OR_TO_COMMON_LIST | //+------------------------------------------------------------------+ bool MWatchOrAllList() { if(curr_mwatch==MARKETWATCH) { return(true); } if(curr_mwatch==ALL_LIST_SYMBOLS) { return(false); } //--- return(true); }
Nachdem die Anzahl der Symbole (Loop) erhalten wurde, sollten wir die gesamte Liste durcharbeiten und die Symbolnamen in das Array eingeben, wobei jede Iteration die Größe um eins anwachsen lassen wird. Der Symbolname wird seinerseits von der Indexzahl bezogen, indem die SymbolName()-Funktion angewendet wird.
int SymbolName( int pos, // list index number bool selected // true – only MarketWatch symbols );
Die MWatchOrAllList() pointer-Funktion wird ferner in der SymbolName()-Funktion dafür benutzt, die Symbolliste auszuwählen. Der gesamte Code der GetSymbolsToArray()-Funktion:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| FILLING_SYMBOL_ARRAY_WITH_NAMES | //+------------------------------------------------------------------+ void GetSymbolsToArray() { // If only the current symbol data is required if(curr_mwatch==CURRENT) { ArrayResize(symbols,1); symbols[0]=_Symbol; } //--- // If data on all symbols from Market Watch window or // or the entire symbol list is required if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { // Receive the number of symbols in Market Watch window cnt_symb=SymbolsTotal(MWatchOrAllList()); //--- for(int i=0; i<=cnt_symb-1; i++) { string nm_symb=""; //--- ArrayResize(symbols,i+1); // Increase the array size by one once again //--- // Receive a name of a symbol from Market Watch window nm_symb=SymbolName(i,MWatchOrAllList()); symbols[i]=nm_symb; // Put the symbol name into the array } } }
Die SetSeparateForFormatDate()-Funktion ist relativ simpel. Sie wird dafür benutzt, um zu definieren, welcher Separator im Datum genutzt werden soll - abhängig von der Wahl des Nutzers betreffend den Datum Formatieren-Parameter (format_date) der Dropdown-Liste.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| DEFINING_SEPARATOR_FOR_DATE_FORMAT | //+------------------------------------------------------------------+ void SetSeparateForFormatDate() { switch(format_date) { case SEP_POINT1 : case SEP_POINT2 : sep="."; break; // Full point as a separator case SEP_SLASH1 : case SEP_SLASH2 : sep="/"; break; // Slash as a separator } }
Anschließend kommt es zu einer Schleife samt verschiedener Überprüfungen. Sollten diese erfolgreich sein, so werden die Daten in die Datei hineingeschrieben. Andererseits, falls die Schleife bricht, so werden entweder alle Objekte des Charts gelöscht und das Script (im Falle von einem Symbol) entfernt oder aber (im Falle von mehr als einem Symbol) die nächste Iteration beginnt. Jedes Symbol des symbols[]-Arrays wird regelmäßig in der Schleife aufgerufen. Die Indexzahl wird dabei jeder Funktion der Schleife zugesandt. Auf diese Weise wird die Genauigkeit der Funktion gewährleistet.
Die Daten des aktuellen Symbols der Schleife werden zum Beginn jeder Iteration vom Schleifentextkörper empfangen. Dies ist die Aufgabe der GetDataCurrentSymbol()-Funktion. Sehen wir uns an, was mit dieser Funktion passiert.
Bevor die Symboldaten in das rate[]-Array kopiert werden, wird zunächst die Datenverfügbarkeit mithilfe der CheckLoadHistory()-Funktion überprüft. Die Entwickler stellen diese Funktion als Anschauungsobjekt zur Verfügung. Die ursprüngliche Version kann hierbei unter MQL5-Verweise eingesehen werden. Ich habe lediglich einige geringfügige Verbesserung vorgenommen, die die Scriptnutzung erleichtern sollen. Da das Verweise-Verzeichnis eine detaillierte Beschreibung enthält (ich schlage an dieser Stelle vor, diese zu studieren), werde ich meine - beinahe identische - Beschreibung nicht noch einmal zusätzlich hinzufügen. Sie kann außerdem im Code samt detaillierter Kommentare gefunden werden.
Eine Sache, die es noch zu erwähnen gilt: Die CheckLoadHistory()-Funktion gibt einen Fehler oder einen erfolgreichen Ausführungscode aus, durch den die entsprechende Nachricht vom switch-Operatorblock im Journal gespeichert wird. Abhängig vom empfangenen Code wird die GetDataCurrentSymbol()-Funktion ihre Operation fortsetzen, oder aber den Code wieder ausgeben.
Falls alles läuft wie geplant, werden die Verlaufsdaten durch die CopyRates()-Funktion kopiert. Die Array-Größe wird als globale Variable gespeichert. Dann wird die Funktion beendet und der Code 1 zurückgegeben. Falls ein Problem auftritt, stoppt die Funktion ihre Switch Operator-Operationen und gibt den Code 0 oder 2 zurück.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| RECEIVE_SYMBOL_DATA | //+------------------------------------------------------------------+ int GetDataCurrentSymbol(int s) { Print("------\n№"+IS(s+1)+" >>>"); // Save a symbol number in the journal //--- // Check and download the necessary amount of requested data int res=CheckLoadHistory(s,_Period); //--- InfoTable(s); ChartRedraw(); // Update the data in the data table //--- switch(res) { case -1 : Print("Unknown symbol "+symbols[s]+" (code: -1)!"); return(0); case -2 : Print("Number of requested bars exceeds the maximum number that can be displayed on a chart (code: -2)!...\n" "...The available amount of data will be used for writing."); break; //--- case -3 : Print("Execution interrupted by user (code: -3)!"); return(2); case -4 : Print("Download failed (code: -4)!"); return(0); case 0 : Print("All symbol data downloaded (code: 0)."); break; case 1 : Print("Time series data is sufficient (code: 1)."); break; case 2 : Print("Time series created based on existing terminal data (code: 2)."); break; //--- default : Print("Execution result is not defined!"); } //--- // Copy data to the array if(CopyRates(symbols[s],_Period,check_start_date,end_date,rates)<=0) { Print("Error when copying symbol data "+symbols[s]+" - ",ErrorDesc(Error())+""); return(0); } else { copied_bars=ArraySize(rates); // Receive array size //--- Print("Symbol: ",symbols[s],"; Timeframe: ",gStrTF(_Period),"; Copied bars: ",copied_bars); } //--- return(1); // Return 1, if all is well }
Danach befindet sich das Programm wieder im Schleifentextköprer der OnStart()-Funktion. Der Code wird durch die lokale Variable res zugewiesen und hinsichtlich seines Wertes überprüft. Der Wert 0 steht an dieser Stelle für einen Fehler. Das bedeutet, dass die Daten des aktuellen Symbols in der Schleife nicht geschrieben werden können. Der Fehler wird im Journal notiert, worauf eine Entscheidung getroffen werden muss, ob die Schleife mit einem Bruch (Bruch) zu versehen ist, oder aber ob die nächste Iteration beginne soll (Fortsetzen).
if(res==0) { BC } // If zero, the loop is interrupted or the next iteration starts
Die oben befindliche Codezeile zeigt an, dass diese Auswahl durch einige mysteriöse BC-Zeichen ausgeführt wird. Hierbei handelt es sich um eine Macro-Erweiterung. Mehr Informationen hierzu finden Sie unter MQL5-Verweise. Im vorliegenden Artikel sei lediglich erwähnt, dass alle Ausdrücke (in einer Zeile eingegeben) in einen kurzen Eintrag kopiert werden können - siehe obiges Beispiel (BC). In manchen Fällen ist diese Methode sogar noch komfortabler als eine Funktion. Im vorliegenden Fall sieht dies wie folgt aus:
// Macro expansion with further action selection #define BC if(curr_mwatch==CURRENT) { break; } if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { continue; }
Unten sehen wir andere Beispiele für Macro-Erweiterungen, die in diesem Script benutzt werden.
#define nmf __FUNCTION__+": " // Macro expansion of the function name before sending the message to the journal //--- #define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder for storing the terminal data
Falls GetDataCurrentSymbol() 2 wiedergibt, wurde das Programm durch den Nutzer gelöscht. MQL5 besitzt eine sogenannte IsStopped()-Funktion, die solch einen Fall erkennen kann. Diese Funktion hilft Schleifen dabei, die Operation des Programms korrekt und rechtzeitig zu stoppen. Falls die Funktion den Wert wahr zurückgibt, existiert ein Zeitfenster von ungefähr drei Sekunden, um alle Aktionen durchzuführen, bevor das Programm gewaltsam gelöscht wird. In unserem Fall werden alle grafischen Objekte entfernt und die folgende Nachricht wird im Journal verzeichnet:
if(res==2) // Program execution interrupted by user { DelAllScriptObjects(); // Delete all objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; }
8. Ordner anlegen und Daten ablegen
Die Funktion CheckCreateGetPath() überprüft die Existenz eines root-data-Ordners. Lassen Sie uns diesen DATA_OHLC nennen und in C:\Metatrader 5\MQL5\Files platzieren. Er wird letztendlich verschiedene Ordner mit Symbolnamen enthalten. Die Dateien, um die Daten zu schreiben, werden hier angelegt.
Falls der root-Ordner oder der Ordner für das aktuelle Symbol in der Loop nicht existiert, wird die Funktion ihn entsprechend anlegen. Falls alles wie geplant funktioniert, gibt die Funktion eine Zeichenkette aus, die den Pfad zum Kreieren einer Datei enthält. Die Funktion gibt eine leere Zeichenkette aus, falls ein Fehler vorliegt oder der Nutzer den Versuch unternimmt, das Programm aus dem Chart zu löschen.
Der unten stehende Code enthält detaillierte Informationen, um diesen Sachverhalt verständlicher zu machen.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| CHECK_DIRECTORY_AND_CREATE_NECESSARY_DATA_FOLDERS | //+------------------------------------------------------------------+ string CheckCreateGetPath(int s) { int i=1; long search=-1; string ffname="",lpath=""; string file="*.csv",folder="*"; string root="DATA_OHLC\\", // Root data folder fSmb=symbols[s]+"\\", // Symbol name fTF=gStrTF(_Period)+"\\"; // Symbol time frame //--- bool flgROOT=false,flgSYMBOL=false; //--- //+------------------------------------------------------------------+ //| SEARCHING_FOR_DATA_OHLC_ROOT_FOLDER | //+------------------------------------------------------------------+ lpath=folder; search=FileFindFirst(lpath,ffname); // Set search handle in Metatrader 5\MQL5\Files //--- Print("Directory: ",TRM_DP+"\\MQL5\\Files\\"); //--- // Set the flag if the first folder is a root one if(ffname==root) { flgROOT=true; Print("Root folder "+root+" present"); } //--- if(search!=INVALID_HANDLE) // If search handle received { if(!flgROOT) // If the first folder is not a root one { // Sort out all files searching for the root folder while(FileFindNext(search,ffname)) { if(IsStopped()) // Execution interrupted by user { // Delete objects created by the script from the chart DelAllScriptObjects(); //--- Print("------\nUser deleted the script!"); return(""); } //--- if(ffname==root) // Set the flag if found { flgROOT=true; Print("Root folder "+root+" present"); break; } } } //--- FileFindClose(search); search=-1; // Close root folder search handle } else { Print("Error when receiving the search handle or directory "+TRM_DP+" is empty: ",ErrorDesc(Error())); } //--- //+------------------------------------------------------------------+ //| SEARCHING_SYMBOL_FOLDER | //+------------------------------------------------------------------+ lpath=root+folder; //--- // Set search handle in the root folder ..\Files\DATA OHLC\ search=FileFindFirst(lpath,ffname); //--- // Set the flag if the first folder of the current symbol if(ffname==fSmb) { flgSYMBOL=true; Print("Symbol folder "+fSmb+" present"); } //--- if(search!=INVALID_HANDLE) // If search handle is received { if(!flgSYMBOL) // If the first folder is not of the current symbol { // Sort out all the files in the root folder searching the symbol folder while(FileFindNext(search,ffname)) { if(IsStopped()) // Execution interrupted by user { // Delete objects created by the script from the chart DelAllScriptObjects(); //--- Print("------\nUser deleted the script!"); return(""); } //--- if(ffname==fSmb) // Set the flag if found { flgSYMBOL=true; Print("Symbol folder"+fSmb+" present"); break; } } } //--- FileFindClose(search); search=-1; // Close symbol folder search handle } else { Print("Error when receiving search handle or the directory "+path+" is empty"); } //--- //+------------------------------------------------------------------+ //| CREATE_NECESSARY_DIRECTORIES_ACCORDING_TO_CHECK_RESULTS | //+------------------------------------------------------------------+ if(!flgROOT) // If there is no DATA_OHLC... root folder { if(FolderCreate("DATA_OHLC")) // ...we should create it { Print("..\DATA_OHLC\\ root folder created"); } else { Print("Error when creating DATA_OHLC: root folder",ErrorDesc(Error())); return(""); } } //--- if(!flgSYMBOL) // If there is no folder of the symbol, the values of which should be received... { if(FolderCreate(root+symbols[s])) // ...we should create it { Print("..\DATA_OHLC\\" symbol folder created+fSmb+""); //--- return(root+symbols[s]+"\\"); // Return the path for creating the file for writing } else { Print("Error when creating ..\DATA_OHLC\\ symbol folder"+fSmb+"\: ",ErrorDesc(Error())); return(""); } } //--- if(flgROOT && flgSYMBOL) { return(root+symbols[s]+"\\"); // Return the path for creating the file for writing } //--- return(""); }
Falls die CheckCreateGetPath()-Funktion eine leere Zeile ausgibt, dann ist entweder die Schleife unterbrochen, oder die nächste Wiederholung startet mit einer bereits bekannten Makro-Erweiterung (BC):
// Receive the path for creating a file and create directories for them // If the line is empty, the loop is interrupted or the next iteration starts if((path=CheckCreateGetPath(s))=="") { BC }
Falls Sie bis hierher gekommen sind, so wurden die Daten erfolgreich kopiert und die Variable der Zeichenkette des Pfads enthält den Pfad, um die Datei anzulegen, die die Daten des aktuellen Symbols in die Loop schreibt.
Kreieren Sie nun die WriteDataToFile()-Funktion, um die Datei mit Daten zu versehen. [Path]+[file name] werden zu Beginn der Funktion generiert. Der Name einer Datei besteht aus dem Namen eines Symbols und dem aktuellen Zeitfenster. Beispiel: EURUSD_H1.csv. Falls eine Datei mit diesem Namen bereits existiert, so ist sie gerade geöffnet und kann beschrieben werden. Zuvor geschriebene Daten werden gelöscht werden. Stattdessen werden neue Daten geschrieben werden. Falls die Datei erfolgreich geöffnet/angelegt wurde, so wird die FileOpen()-Funktion den Handle wieder ausgeben, der zum Zugriff auf diese Datei nötig ist.
Kommen wir zum Überprüfen des Handels. Falls existent, wird die Kopfzeile geschrieben. Das Schreiben der entsprechenden Zeile hängt dabei davon ab, welcher Header seitens des Nutzers ausgewählt wurden. Danach beginnt die Hauptschleife, die die Verlaufsdaten schreibt.
Vor dem Schreiben der nächsten Zeile sollte der Nutzer eine Konvertierung in ein entsprechendes Format anordnen. Um dies zu bewerkstelligen, sollten wir den Offene Zeit-Balken erhalten und Tag, Monat, Jahr und Zeit separat mittels der Funktion StringSubstr() aussortieren. Dann sollten wir definieren, ob - abhängig vom spezifizierten Benutzerformat - Datum und Zeit in einer einzigen Spalte oder in separaten Spalten stehen sollen. Dann werden alle Teile in eine Zeile zusammengefügt, indem wir uns die StringConcatenate()-Funktion zunutze machen. Nachdem alle Zeilen geschrieben wurden, wird die Datei durch Benutzung der FileClose()-Funktion geschlossen.
Der gesamte Code der WriteDataToFile()-Funktion findet sich unten:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| WRITE_DATA_TO_FILE | //+------------------------------------------------------------------+ void WriteDataToFile(int s) { // Number of decimal places in the symbol price int dgt=(int)SymbolInfoInteger(symbols[s],SYMBOL_DIGITS); //--- string nm_fl=path+symbols[s]+"_"+gStrTF(_Period)+".csv"; // File name //--- // Receive file handle for writing hFl=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,','); //--- if(hFl>0) // If the handle is received { // Write the headers if(format_headers==NSDT_5) { FileWrite(hFl,"\"Date\" ""\"Time\" ""\"Open\" ""\"High\" ""\"Low\" ""\"Close\" ""\"Volume\""); } //--- if(format_headers==NSDT_6) { FileWrite(hFl,"Date","Open","High","Low","Close","Volume"); } //--- // Write the data for(int i=0; i<=copied_bars-1; i++) { if(IsStopped()) // If program execution interrupted by a user { DelAllScriptObjects(); // Delete objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; } //--- sdt=TSdm(rates[i].time); // Bar open time //--- // Divide the date by year, month and time yyyy=StringSubstr(sdt,0,4); mm=StringSubstr(sdt,5,2); dd=StringSubstr(sdt,8,2); tm=StringSubstr(sdt,11); //--- string sep_dt_tm=""; // Separator of Date and Time columns //--- // Join the data with the separator in the necessary order if(format_date==SEP_POINT1 || format_date==SEP_SLASH1) { sep_dt_tm=" "; } if(format_date==SEP_POINT2 || format_date==SEP_SLASH2) { sep_dt_tm=","; } //--- // Join everything in one line StringConcatenate(sdt,dd,sep,mm,sep,yyyy,sep_dt_tm,tm); //--- FileWrite(hFl, sdt,// Date-time DS_dgt(rates[i].open,dgt), // Open price DS_dgt(rates[i].high,dgt), // High price DS_dgt(rates[i].low,dgt), // Low price DS_dgt(rates[i].close,dgt), // Close price IS((int)rates[i].tick_volume)); // Tick volume price //--- // Update writing progress value for the current symbol pgs_pcnt=((double)(i+1)/copied_bars)*100; //--- // Update data in the table InfoTable(s); if(show_progress) { ChartRedraw(); } } //--- FileClose(hFl); // Close the file } else { Print("Error when creating/opening file!"); } }
Dies war die letzte Funktion der einfachen Schleife der OnStart()-Funktion. Fall es sich nicht um das letzte Symbol gehandelt haben sollte, so beginnt alles noch einmal von vorn für das nächste Symbol. Ansonsten erfolgt ein Bruch der Schleife. Falls der Nutzer spezifiziert hat, die Symbolliste im Market Watch-Fenster in den Scriptparametern zu löschen, werden die Symbole, die momentan keinerlei aktive Charts aufweisen, durch die DelSymbolsFromMarketWatch()-Funktion gelöscht. Im Anschluss daran werden alle vom Script kreierten grafischen Objekte gelöscht und das Programm stoppt. Die Daten können nun verwendet werden.
Fazit
Egal, welches Programm ich auch verwendet habe, um meine Trading-Strategien zu entwickeln, ich stieß wieder und wieder auf Limitierungen, die sich meinen Ideen in den Weg stellten. Schlussendlich gelangte ich zu der Erkenntnis, dass die Programmierung entscheidend ist. MQL5 stellt daher die beste Lösung für all jene dar, die tatsächlich erfolgreich sein wollen. Gleichwohl kann man natürlich auch von anderen Programmen, die sich mit Datenanalysen und der Entwicklung von Trading-Strategien beschäftigen, Impulse für neue Ideen erhalten. Wenn ich mich nur eines einzigen Tools bedient hätte, hätte ich wesentlich mehr Zeit gebraucht, um auf all diese Ideen zu kommen.
Viel Erfolg!
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/502





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