English Русский 中文 Español 日本語 Português
MQL5 Grundlagen der Programmierung: Dateien

MQL5 Grundlagen der Programmierung: Dateien

MetaTrader 5Beispiele | 9 November 2016, 13:23
3 563 0
Dmitry Fedoseev
Dmitry Fedoseev

Inhalt

Einleitung

Wie viele andere Programmiersprachen besitzt auch MQL5 Funktionen für das Arbeiten mit Dateien. Das Arbeiten mit Dateien ist zwar keine sehr häufige Aufgabe bei der Entwicklung von MQL5 Expert Advisors und Indikatoren, aber jeder Entwickler wird früher oder später damit konfrontiert. Die Palette der Themen, die das Arbeiten mit Dateien erfordern ist breit genug. Freuen Sie sich auf benutzerdefinierte Trading-Berichte, das Herstellen von Spezialdateien mit komplexen Parametern für ein EA oder Indikator, Marktdaten (z. B. ein News-Kalender), lesen, generieren etc. . Dieser Artikel deckt alle Funktionen für das Arbeiten mit Dateien in MQL5 ab. Jeder von ihnen wird durch eine einfache praktische Aufgabe, die darauf abzielt, Ihre Fähigkeiten zu verbessern, begleitet. Abgesehen von Aufgaben besitzt der Artikel eine Reihe von nützlichen Funktionen, die in der Praxis angewendet werden können.

Die MQL5 Dokumentation enthält Beschreibungen der Dateifunktionen hier</a0  

Lesen einer Textdatei

Die einfachste und am häufigsten genutzte Funktion ist das Lesen von Text-Dateien. Lassen Sie uns direkt zur Praxis springen. Öffnen Sie den MetaEditor. Wählen Sie Datei - Öffnen Sie den Ordner "Data". MQL5 Ordner in einem neuen Fenster öffnen. Danach öffnen Sie den Ordner "Files". Dieser Ordner ist derjenige, der Dateien für die Verarbeitung durch die Dateifunktionen in MQL5 enthält. Diese Beschränkung sorgt für Datensicherheit. MetaTrader Benutzer teilen sehr aktiv MQL5 Anwendungen. Ohne diese Beschränkung wäre es für Eindringlinge zu einfach, auf Ihrem PC Schaden anzurichten, indem Sie Daten löschen, wichtige Dateien beschädigen oder persönliche Daten stehlen.

Erstellen Sie eine Textdatei im neu eröffneten MQL5/Ordner. Um dies zu tun, klicken Sie irgendwo in den Ordner und wählen Sie neu-Text-Dokument. Benennen Sie die Datei "test". Der vollständige Name sollte "test.txt" sein. Ich empfehle, dass Sie die Anzeige der Datei-Erweiterungen auf Ihrem PC aktivieren.

Öffnen Sie die Datei nach der Umbenennung. Sie wird im Notepad-Editor geöffnet. Schreiben Sie 2 bis 3 Textzeilen in die Datei und speichern Sie sie. Stellen Sie sicher, dass ANSI-Codierung in der Dropdown-Liste am unteren Rand das Fenster "speichern unter" (Abb. 1) ausgewählt ist.


Abbildung 1. Speichern einer Text-Datei im Windows-Editor. Der rote Pfeil zeigt die ausgewählte Datei Codierung

Wir werden nun die Datei mittels MQL5 lesen. Erstellen Sie ein Skript in MetaEditor und nennen Sie es sTestFileRead.

Die Datei sollte vor dem Lesen oder Schreiben geöffnet und danach wieder geschlossen werden. Die Datei wird von der FileOpen() -Funktion mit zwei Pflichtparameter geöffnet. Die Erste ist der Name der Datei. Wir sollten hier "test.txt" angeben. Bitte beachten Sie, dass wir den Pfad aus dem MQL5/Dateien-Ordner und nicht den vollständigen Pfad angeben. Der zweite Parameter ist die Kombination von Flags welche die Art der Arbeit mit Dateien definieren. Wir werden die Datei lesen, daher sollten wir das FILE_READ-Flag angeben. "test.txt" ist eine Textdatei mit ANSI-Codierung, das heißt, wir sollten zwei weitere Flags verwenden: FILE_TXT und FILE_ANSI. Die Flags werden über die logische Operation "oder" verknüpft, das "|" Symbol.

Die FileOpen()-Funktion gibt das Dateihandle zurück. Wir gehen nicht im Detail auf die Funktion des Handles ein. Lasst uns einfach sagen, dass es einen numerischen Wert (Int) anstelle von den Namen der Datei (Zeichenfolge ) darstellt. Der Name der Datei wird beim Öffnen einer Datei verwendet, während das Handle danach zum Ausführen von Aktionen mit dieser Datei dient.

Wir öffnen die Datei (Wir schreiben den Code in der OnStart() Funktion des Skripts sTestFileRead):

int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);

Danach stellen Sie sicher, dass die Datei tatsächlich geöffnet ist. Dies geschieht durch den Wert des empfangenen Handles:

if(h==INVALID_HANDLE){
   Alert("Error opening file");
   return; 
}

Der "Datei öffnen"-Fehler ist durchaus üblich. Wenn die Datei bereits geöffnet ist, kann sie nicht zum zweiten Mal geöffnet werden. Die Datei könnte von einer Drittanbieter-Anwendung geöffnet worden sein. Beispielsweise kann die Datei im Windows-Editor und MQL5 gleichzeitig geöffnet werden. Aber wenn sie mit Microsoft Excel geöffnet ist, dann kann sie anderswo auch noch geöffnet werden.  

Das Lesen von Daten aus einer Textdatei (geöffnet mit dem FILE_TXT-Flag) erfolgt durch die FileReadString() -Funktion. Das Auslesen erfolgt zeilenweise. Ein Funktionsaufruf liest eine einzelne Zeile der Datei. Lassen Sie uns eine Zeile lesen und in dem Meldungsfeld anzeigen:

string str=FileReadString(h);
Alert(str);

Schließen Sie die Datei:

FileClose(h);

Bitte beachten Sie, dass der Aufruf der Funktionen FileReadString() und FileClose() mit dem Handle h durchgeführt wird, welches wir beim beim Öffnen der Datei durch die FileOpen()-Funktion erhalten haben.

Jetzt können Sie das sTestFileRead-Skript ausführen. Wenn etwas schief geht, vergleichen Sie Ihren Code mit der sTestFileRead-Datei, die unten angefügt ist. Das Fenster mit der ersten Zeile aus der Datei "test.txt" sollte aufgrund des Skript-Vorgangs (Abb. 2) erscheinen.

 
Abbildung 2. Das Ergebnis des sTestFileRead Skriptes

Wir haben bisher nur eine Zeile aus der Datei "test.txt" gelesen. Um die zwei verbleibenden zu lesen, können wir die FileReadString()-Funktion zwei weitere Male aufrufen, aber in der Praxis ist die Anzahl der Zeilen der Datei in der Regel nicht im Voraus bekannt. Um dieses Problem zu lösen, sollten wir die FileIsEnding() -Funktion und den while operatorverwenden. Wenn wir während des Lesens das Ende einer Datei erreichen, gibt die FileIsEnding()-Funktion "true" zurück. Lassen Sie uns eine benutzerdefinierte Funktion für das Lesen aller Zeilen der Datei mit der FileIsEnding()-Funktion schreiben und die Zeilen in einer Messagebox darstellen. Diese kann für eine Vielzahl von pädagogischen Experimenten rund um das Arbeiten mit Dateien nützlich sein. Wir erhalten die folgende Funktion:

void ReadFileToAlert(string FileName){
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   Alert("=== Start ===");   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      Alert(str);   
   }
   FileClose(h);

 Lassen Sie uns das sTestFileReadToAlert-Skript erstellen und kopieren Sie für den Aufruf die Funktion in die OnStart() Funktion des Skriptes:

void OnStart(){
   ReadFileToAlert("test.txt");
}

Die Message Box mit der "=== starten ===" Linie und alle drei Zeilen der Datei "test.txt" sollten erscheinen. Die Datei wird jetzt komplett gelesen (Abb. 3). 


Fig. 3. Wir haben die FileIsEnding()-Funktion und die "do while"-Schleife angewandt, um die gesamte Datei zu lesen.

Erzeugen einer Textdatei

Um eine Datei zu erstellen, öffnen wir sie mithilfe der FileOpen()-Funktion. Öffnen Sie die "test.txt" Datei mit dem FILE_READ-Flag anstelle des FILE_WRITE:

int h=FileOpen("test.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);

Stellen Sie nach dem Öffnen der Datei sicher, das Handle wie beim Lesen der Datei zu überprüfen. Wenn die Funktion erfolgreich ausgeführt wurde, wird die neue Datei "test.txt" erstellt. Wenn die Datei bereits existierte, wird sie vollständig gelöscht. Seien Sie vorsichtig beim Öffnen von Dateien zum schreiben, damit Sie nicht wertvolle Daten verlieren.  

Schreiben in die Textdatei erfolgt durch die FileWrite() -Funktion. Der erste Parameter legt das Dateihandle fest, während letzterer die Zeile darstellt, die in die Datei geschrieben wird. Die neue Zeile wird bei jedem Aufruf der Funktion FileWrite() geschrieben.

Lassen Sie uns zehn Zeilen in einer Schleife in die Datei schreiben. Der finale Script-Code (sTestFileCreate) sieht wie folgt aus:

void OnStart(){
   int h=FileOpen("test.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   for(int i=1;i<=10;i++){
      FileWrite(h,"Line-"+IntegerToString(i));
   }
   FileClose(h);
   Alert("File created");
}

Nach dem Ausführen des Codes, sollte die Datei "test.txt" zehn Zeilen enthalten. Um den Inhalt der Datei zu überprüfen, öffnen Sie sie in dem Editor oder führen Sie das sTestFileReadToAlert-Skript aus.

Beachten Sie die FileWrite()-Funktion. Sie kann mehr als zwei Argumente haben. Sie können mehrere String-Variablen an die Funktion übergeben, und sie werden beim schreiben in einer Zeile zusammengefasst. In dem angegebenen Code kann der Aufruf der Funktion FileWrite() wie folgt geschrieben werden:

FileWrite(h,"Line-",IntegerToString(i));

Die Funktion wird automatisch die Zeilen beim Schreiben verbinden.

Bis zum Ende einer Textdatei schreiben

Manchmal ist es notwendig, an die vorhandene Datei weiteren Inhalt, in Form einer oder mehreren neuen Textzeilen, hinzuzufügen. Um diese Aktionen durchzuführen, sollte die Datei zum Lesen und schreiben gleichzeitig geöffnet werden. Dies bedeutet, dass die beiden Flags (FILE_READ und FILE_WRITE) angegeben werden müssen, wenn die FileOpen()-Funktion aufgerufen wird.

int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);

Wenn die Datei mit dem angegebenen Namen nicht vorhanden ist, wird sie erstellt. Wenn sie bereits vorhanden ist, wird sie geöffnet, während dessen Inhalt intakt gehalten wird. Wenn wir jedoch anfangen in die Datei zu schreiben, wird ihr früherer Inhalt gelöscht, da das Schreiben vom Anfang der Datei an gestartet wird.

Wenn Sie mit Dateien arbeiten, gibt es so etwas wie einen "Zeiger" — numerischer Wert, mit welchem Sie die Position angeben können, wo sie den nächsten Eintrag lesen oder schreiben wollen. Beim Öffnen der Datei wird der Zeiger automatisch auf den Anfang der Datei festgelegt. Während des Lesens oder Schreibens von Daten, wird er automatisch durch die Größe der geschriebenen oder gelesenen Daten verschoben. Sie können den Zeiger selbst bei Bedarf verschieben. Zu diesem Zweck verwenden Sie die FileSeek() -Funktion.  

Um den vorherigen Inhalt zu behalten und den Neuen bis zum Ende der Datei hinzuzufügen, verschieben Sie den Zeiger vor dem Schreiben bis zum Ende der Datei.

FileSeek(h,0,SEEK_END);

Die drei Parameter werden an die FileSeek()-Funktion übergeben: Handle, die neue Position des Zeigers und die Position, von welcher aus die Verschiebung berechnet wird. In diesem Beispiel bedeutet die SEEK_END konstante das Ende der Datei. So wird der Zeiger um 0 Bytes vom Ende der Datei positioniert (Dieses entspricht dem Ende der Datei).

Die finale Script-Code für das Hinzufügen ist wie folgt:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   FileSeek(h,0,SEEK_END);
   FileWrite(h,"Additional line");
   FileClose(h);
   Alert("Added to file");
}

Dieses Skript ist auch unten (sTestFileAddToFile) angefügt. Starten Sie das Skript und überprüfen Sie den Inhalt der Datei test.txt. Jeder Aufruf des Skripts sTestFileAddToFile fügt eine Zeile hinzu.

Die angegebene Zeile einer Textdatei ändern

Bei Textdateien kann der frei bewegbare Zeider nur für das Hinzufügen von Zeilen verwendet werden, nicht aber für Veränderungen von Zeilen. Es ist unmöglich, in einer bestimmten Zeile der Datei Änderungen vornehmen zu können, da eine Zeile der Datei nur ein Konzept ist, die Datei selbst ist eine fortlaufende Serie von Daten. Manchmal enthalten diese Serien Sonderzeichen, die in einem Text-Editor nicht sichtbar sind. Sie weisen darauf hin, dass die folgende Informationen in einer neuen Zeile angezeigt werden sollen. Wenn wir den Mauszeiger am Anfang der Linie positionieren und mit dem Schreiben beginnen, bleiben vorherige Daten in der Zeile, wenn die Größe der geschriebenen Daten die der vorhandenen Linie unterschreitet. Andernfalls werden die Symbole für "neue Zeile" zusammen mit einem Teil der nächsten Zeilendaten gelöscht.

Lassen Sie uns versuchen, die zweite Zeile in test.txt zu ersetzen. Öffnen Sie die Datei zum Lesen und schreiben, Lesen Sie eine Zeile verschieben Sie den Zeiger an den Anfang der zweiten Zeile und schreiben dann eine neue Zeile, bestehend aus zwei Buchstaben "AB" (sTestFileChangeLine2-1 Skript unten angehängt):

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   string str=FileReadString(h);
   FileWrite(h,"AB");
   FileClose(h);
   Alert("Done");
}

Die erhaltenen test.txt-Datei sieht nun wie folgt aus (Abb. 4):

 
Fig. 4. Der Inhalt der Textdatei nach dem Versuch, eine Zeile zu ändern 

Nun haben wir zwei Linien anstelle von einer: "AB" und "-2". "-2" ist, was aus der zweiten Zeile bleibt wo vier Zeichen gelöscht wurden. Der Grund ist, dass wenn Sie eine Linie mit der FileWrite()-Funktion schreiben, es neue Liniensymbole bis zum Ende des geschriebenen Textes hinzufügt. In Windows-Betriebssystemen besteht die "neue Zeile"-Symbol aus zwei Zeichen Wenn wir diese beiden Zeichen in der "AB"-Linie hinzufügen, verstehen wir, warum die vier Zeichen in der resultierenden Datei gelöscht wurden.  

Führen Sie das sTestFileCreate-Skript für die Wiederherstellung der test.txt Datei aus und versuchen Sie die zweite Zeile durch einen längere zu ersetzen. Lassen Sie uns "Line-12345" hinzufügen (sTestFileChangeLine2-2 Skript unten angefügt):

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   string str=FileReadString(h);
   FileWrite(h,"Line-12345");
   FileClose(h);
   Alert("Done");
}

Schauen Sie sich die resultierende Datei an(Abb. 5):

 
Fig. 5. Ergebnisse des zweiten Versuchs, die Zeile der Textdatei zu ändern 

Da die neue Zeile länger als der vorherige ist, ist die dritte Zeile auch betroffen.  

Die einzige Möglichkeit, Text-Dateien zu ändern ist das vollständige Lesen und neu Schreiben. Wir sollten die Datei in ein Array einlesen, Änderungen an der notwendigen Array-Position vornehmen und dann Zeile für Zeile in eine andere Datei speichern, die alte Datei löschen und die neue Version umbenennen. Manchmal ist ein Array nicht erforderlich: Während des Lesens der Zeilen aus einer Datei, kann sie gleichzeitig in eine Andere geschrieben werden. An einem bestimmten Punkt werden dann Änderungen vorgenommen in der notwendigen Zeile gespeichert. Danach wird die alte Datei gelöscht und die neue Version wird umbenannt.

Lassen Sie uns die zweite Option verwenden (ohne die Verwendung des Arrays). Wir sollten zunächst eine temporäre Datei erstellen. Wir schreiben eine Funktion für den Erhalt eines eindeutigen Name der temporären Datei. Der Dateiname und die Erweiterung werden an die Funktion übergeben. Die Prüfung, ob die Datei vorhanden ist, wird (von der FileIsExists() -Standardfunktion) innerhalb der Funktion selbst durchgeführt. Wenn die Datei vorhanden ist, wird eine Zahl hinzugefügt bis keine Datei mit diesem Namen erkannt wird. Die Funktion sieht wie folgt aus:

string TmpFileName(string Name,string Ext){
   string fn=Name+"."+Ext; // forming name
   int n=0;
   while(FileIsExist(fn)){ // if the file exists
      n++;
      fn=Name+IntegerToString(n)+"."+Ext; // add a number to the name
   }
   return(fn);
}

Wir erstellen das Skript sTestFileChangeLine2-3, kopieren Sie die Funktion hinein und platzieren Sie den folgenden Code in der OnStart()-Funktion.

Test.txt zum Lesen zu öffnen:

int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);

Abfrage des Namens der temporären Datei und öffnen der Datei:

string tmpName=TmpFileName("test","txt");

int tmph=FileOpen(tmpName,FILE_WRITE|FILE_ANSI|FILE_TXT);

Lesen Sie die Datei Zeile für Zeile und zählen Sie sie. Alle gelesenen Zeilen werden in die temporäre Datei gesendet, und die zweite Zeile wird ersetzt:

   int cnt=0;
   while(!FileIsEnding(h)){
      cnt++;
      string str=FileReadString(h);
      if(cnt==2){
         // ersetzen der Zeile
         FileWrite(tmph,"New line-2");
      }
      else{
         // Schreiben der Zeile ohne Änderungen
         FileWrite(tmph,str);
      }
   }

Schließen Sie beide Dateien:

FileClose(tmph);
FileClose(h);

Jetzt ist alles, was wir noch tun müssen, die Original-Datei zu löschen und die temporäre Datei umzubenennen. Die standard- FileDelete() -Funktion dient zum löschen.

FileDelete("test.txt");

Um die Datei umzubenennen, sollten wir die Standardfunktion FileMove() verwenden, die für das verschieben und für das Umbenennen von Dateien entwickelt worden ist. Die Funktion empfängt die vier obligatorischen Parameter: Name der verschobenen Datei (Quelldatei), das Flag für die Position der Datei, der neue Dateinamen (Ziel-Flag), und das Flag für das Überschreiben der Datei. Es sollte alles klar sein, was Dateinamen angeht, daher ist es jetzt an der Zeit, einen genaueren Blick auf den zweiten und vierten Parameter zu werfen (Flags). Der zweite Parameter definiert den Speicherort der Quelldatei. Dateien für die Verarbeitung in MQL5 können sich nicht nur in MQL5/Ordner des Terminals, sondern sie können sich auch im Ordner "Allgemein" von allen Terminals befinden. Wir werden dies später genauer betrachten. Das Fleisch Lassen Sie uns erstmal den Wert 0 verwenden. Der letzte Parameter definiert den Zielspeicherort für die Datei. Dieser kann auch zusätzliche Flags enthalten, die die Aktionen definieren, wenn die Zieldatei bereits vorhanden ist. Da wir die Quelldatei (Zieldatei) gelöscht haben, wird der vierte Parameter auf 0 gesetzt:

FileMove(tmpName,0,"test.txt",0);

Vor der Ausführung des Skripts sTestFileChangeLine2-3 stellen Sie die Datei test.txt mit dem sTestFileCreate-Skript wieder her. Nach der Operation des sTestFileChangeLine2-3 Skriptes, sollte die Datei text.txt folgende Inhalte haben (Abb. 6):

 
Abbildung 6. Der Inhalt der Datei nach dem Austausch der Zeile

Kehren wir zurück zu der FileMove()-Funktion. Wenn wir das FILE_REWRITE-Flag (es ermöglicht uns, die Zieldatei neu zu schreiben) als vierten Parameter festlegen:

FileMove(tmpName,0,"test.txt",FILE_REWRITE);

ist es nicht notwendig, die Quelldatei durch das Skript zu löschen. Diese Option wird in dem sTestFileChangeLine2-3-Skript (unten angehängt) verwendet. 

Anstelle der FileMove()-Funktion können wir eine andere standard-Funktion FileCopy() verwenden, aber in diesem Fall müssen wir die temporäre Datei löschen:

FileCopy(tmpName,0,"test.txt",FILE_REWRITE);
FileDelete(tmpName); 

Lesen einer Textdatei in ein Array

Eine nützliche Funktion wurde bereits in diesem Artikel (Erhalt eines unbenutzten Dateinamens) beschrieben. Lassen Sie uns nun noch eine weitere Funktion, die oft verwendet wird, wenn Sie mit Dateien arbeiten, entwickeln – das Lesen einer Datei in ein Array. Der Dateiname und das Zeilen-Array werden an die Funktion übergeben. Das Array wird über einen Link weitergeleitet und in der Funktion mit dem Inhalt der Datei gefüllt. Die Funktion gibt True/False in Abhängigkeit von seinen Betriebs-Ergebnissen zurück. 

bool ReadFileToArray(string FileName,string & Lines[]){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error opening file %s # %i",FileName,ErrNum);
      return(false);
   }
   int cnt=0; // use the variable to count the number of file lines
   while(!FileIsEnding(h)){
      string str=FileReadString(h); // read the next line from the file
      // Entfernen von Leerzeichen links und rechts um Leerzeilen zu vermeiden
      StringTrimLeft(str); 
      StringTrimRight(str);
      if(str!=""){ 
         if(cnt>=ArraySize(Lines)){ // array filled completely
            ArrayResize(Lines,ArraySize(Lines)+1024); // Vergrößere das Array um 1024 Elemente
         }
         Lines[cnt]=str; // Füge dem Array die gelesene Zeile hinzu
         cnt++; // Erhöhe den Zähler für die gelesenen Zeilen
      }
   }
   ArrayResize(Lines,cnt);
   FileClose(h);
   return(true);
}

Wir werden diese Funktion nicht im Detail besprechen, da alles bereits klar sein sollte. Außerdem wurde es bereits ausführlich kommentiert. Wir erwähnen nur einige Nuancen. Nach dem Lesen der Zeile aus der Datei in die Variable str , werden Leerzeichen an den Rändern der Linie durch die StringTrimLeft() und StringTrimRight() Funktionen gelöscht. Dann wird die Prüfung durchgeführt, ob die str Zeichenfolge nicht leer ist. Dieses wird getan, um unnötige Leerzeilen zu überspringen. Wenn das Array voll ist, wird es in Blöcken von 1024 Elemente vergrößert und nicht durch ein einziges Element erhöht. Die Funktion arbeitet auf diese Weise viel schneller. Zu guter Letzt wird das Array nach der tatsächlichen Anzahl der gelesenen Zeilen skaliert.

Die Funktion finden Sie im sTestFileReadFileToArray Skript (unten angehängt).

Erstellen einer Textdatei mit Trennzeichen

Bisher haben wir nur einfache Textdateien betrachtet. Allerdings gibt es eine andere Art von Text-Dateien, die mit Separatoren. In der Regel haben sie CSV-Erweiterung (kurz für "kommagetrennte Werte(comma separated values)"). In der Tat handelt es sich um reine Textdateien, die in Text-Editoren geöffnet werden können. Sie können gelesen und manuell bearbeitet werden. Ein bestimmtes Zeichen (nicht unbedingt ein Komma) dient als Trennzeichen in den Zeilen. Dadurch können Sie einige verschiedenen Aktionen im Vergleich zu einfachen Textdateien durchführen. Der Hauptunterschied ist, dass mit der FileRedaString()-Funktion in einer einfachen Textdatei eine ganze Zeile gelesen wird, während in Dateien mit Separatoren das Lesen bis zu einem Separator oder einem Zeilenende durchgeführt wird. Die FileWrite()-Funktion funktioniert auch anders: alle geschriebenen Variablen in der Funktion werden nicht einfach in einer Zeile angefügt. Stattdessen wird ein Trennzeichen zwischen ihnen hinzugefügt. 

Lassen Sie uns versuchen, eine Csv-Datei zu erstellen. Öffnen Sie die Textdatei für das Schreiben, wie wir es bereits getan haben, unter Angabe des FILE_CSV Flags anstelle des FILE_TXT Flags. Der dritte Parameter ist ein Symbol, das als Trennzeichen verwendet wird:

int h=FileOpen("test.csv",FILE_WRITE|FILE_ANSI|FILE_CSV,";");

Lassen Sie uns in die Datei zehn Zeilen mit drei Feldern pro Zeile schreiben:

   for(int i=1;i<=10;i++){
      string str="Line-"+IntegerToString(i)+"-";
      FileWrite(h,str+"1",str+"2",str+"3");
   }

Achten Sie darauf, die Datei am Ende zu schließen. Den Code finden Sie in dem sTestFileCreateCSV-Skript (unten angehängt). Als Ergebnis wird die "test.csv" Datei erstellt. Der Inhalt der Datei ist in Abb. 7 dargestellt. Wie wir sehen können, bilden die FileWrite() Funktionsparameter nun eine Zeile mit einem Separator zwischen den Werten.

 
Abbildung 7. Inhalt einer Datei mit Trennzeichen.

Lesen einer Textdatei mit Trennzeichen

Lassen Sie uns nun versuchen, die Csv-Datei auf die gleiche Weise wie die Textdatei am Anfang dieses Artikels zu lesen. Lassen Sie uns eine Kopie des sTestFileReadToAlert Skriptes mit dem Namen sTestFileReadToAlertCSV erstellen. Ändern Sie die erste Zeichenfolge in der ReadFileToAlert()-Funktion: 

int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");

Benennen Sie die ReadFileToAlert()-Funktion in ReadFileToAlertCSV() um und ändern Sie den Namen der Datei die an die Funktion übergeben wird:

void OnStart(){
   ReadFileToAlertCSV("test.csv");
}

Das Skript-Ergebnis zeigt, dass eine Feld der Datei gelesen wurde. Es wäre gut festzustellen zu können, wann die Felder einer Zeile gelesen und die neue Zeile beginnt. Die FileIsLineEnding() -Funktion wird dafür angewendet.

Lassen Sie uns eine Kopie der sTestFileReadToAlertCSV Skript mit dem Namen sTestFileReadToAlertCSV2 erstellen und die ReadFileToAlertCSV-Funktion in ReadFileToAlertCSV2 umbenennen. Fügen Sie die FileIsLineEnding()-Funktion hinzu: Wenn es "true" zurückgibt, zeigen wir die Trennlinie "---". 

void ReadFileToAlertCSV2(string FileName){
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   Alert("=== Start ===");   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      Alert(str);
      if(FileIsLineEnding(h)){
         Alert("---");
      }
   }
   FileClose(h);
}

 Nun, sind die Felder in dem Nachrichtenfenster durch das Skript in Gruppen (Abb. 8) aufgeteilt.


Fig. 8. "---" Trennzeichen zwischen Feldgruppen einer Einzeldatei-Zeile 

Lesen einer Datei mit Trennzeichen in ein Array

Jetzt, wo wir uns mit der Arbeit mit Csv-Dateien vertraut gemacht haben, entwickeln wir noch eine weitere nützliche Funktion für das Lesen einer Csv-Datei in ein Array. Das Lesen erfolgt in eine Structure Array, wobei jedes Element einer Zeile entspricht. Die Struktur enthält eine Zeilen-Array wobei jedes seiner Elemente einem einzigem Feld der Zeile entspricht. 

Structure:

struct SLine{
   string line[];
};

Function:

bool ReadFileToArrayCSV(string FileName,SLine & Lines[]){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error opening file %s # %i",FileName,ErrNum);
      return(false);
   }   
   int lcnt=0; // variable for calculating lines 
   int fcnt=0; // variable for calculating line fields    
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      // neue Zeile (neues Element der Array-Struktur)
      if(lcnt>=ArraySize(Lines)){ // structure array completely filled
         ArrayResize(Lines,ArraySize(Lines)+1024); // Vergrößere das Array um 1024 Elemente
      }
      ArrayResize(Lines[lcnt].field,64);// change the array size in the structure
      Lines[lcnt].field[0]=str; // assign the first field value
      // beginne mit dem Lesen der anderen Felder in der Zeile 
      fcnt=1; // Bis ein Element in den Zeilen Array belegt ist
         while(!FileIsLineEnding(h)){ // Lese den Rest der Felder in der Zeile
            str=FileReadString(h);
            if(fcnt>=ArraySize(Lines[lcnt].field)){ // Das Array-Feld ist komplett gefüllt
               ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+64); // Erhöhe die Größe des Arrays um 64 Elemente
            }     
            Lines[lcnt].field[fcnt]=str; // Weise den Wert des nächsten Feldes zu
            fcnt++; // Erhöhe den Zähler für die Zeilen
         }
      ArrayResize(Lines[lcnt].field,fcnt); // Verändere die Größe des Arrays entsprechend der aktuellen Anzahl der Felder
      lcnt++; // Erhöhe den Zähler für die Zeilen
   }
   ArrayResize(Lines,lcnt); // Verändere das Arrays der Strukturen (Zeilen) entsprechend der aktuellen Anzahl der Zeilen
   FileClose(h);
   return(true);
}

Wir werden diese Funktion nicht im Detail besprechen, sondern gehen nur auf die kritischsten Punkte ein. Ein Feld wird am Anfang von der while(!FileIsEnding(h)) Schleife gelesen. Hier bekommen wir ein Element, dass dem Struktur-Array hinzugefügt werden soll. Überprüfen der Arraygröße und vergrößern bei Bedarf um 1024 Elemente Ändern der Größe des Arrays auf einmal. Eine Größe von 64 Elementen wird anfänglich festgelegt, und der Wert des ersten Feldes der Zeile aus der Datei wird dem Element mit dem 0-Index zugewiesen. Danach lesen Sie die restlichen Felder in der while(!FileIsLineEnding(h)) Schleife Überprüfen Sie nach dem Lesen eines weiteren Feldes die Arraygröße und vergrößern Sie bei Bedarf das Array und fügen Sie die aus der Datei gelesene Zeile dem Array hinzu Nach dem Lesen der Zeilen bis zum Ende (beenden Sie die while(!FileIsLineEnding(h)) Schleife), ändern Sie die Feld Array-Größe entsprechend ihrer tatsächlichen Anzahl an Elementen. Ganz am Ende passen Sie die Größe des Line-Arrays an die tatsächlichen Anzahl der gelesenen Zeilen an. 

Die Funktion finden Sie im sTestFileReadFileToArrayCSV Skript (unten angehängt) Das Skript liest die Datei test.csv in das Array und zeigt das Array in dem Nachrichtenfenster an. Das Ergebnis ist das gleiche wie es in Abb. 8 dargestellt ist. 

Schreiben eines Arrays in eine Textdatei mit Trennzeichen

Die Aufgabe ist ganz einfach, wenn die Anzahl der Felder in der Zeile im Voraus bekannt ist. Eine ähnliche Aufgabe wurde bereits im Abschnitt "Erstellen einer Textdatei mit Trennzeichen" gelöst. Wenn die Anzahl der Felder nicht bekannt ist, können alle Felder in einer einzigen Zeile mit Trennzeichen in einer Schleife gesammelt werden, während die Zeile dann in die geöffnete Datei mit dem FILE_TXT-Flag geschrieben wird.

Öffnen Sie die Datei: 

int h=FileOpen("test.csv",FILE_WRITE|FILE_ANSI|FILE_TXT);

Sammeln Sie alle Felder (Array-Elemente) in einer einzigen Zeile, unter Verwendung eines Trennzeichens. Es darf kein Trennzeichen am Ende der Zeile geben, sonst wird es ein redundantes leeres Feld in der Zeile geben:

   string str="";
   int size=ArraySize(a);
   if(size>0){
      str=a[0];
      for(int i=1;i<size;i++){
         str=str+";"+a[i]; // merge fields using a separator 
      }
   }

Schreiben Sie die Zeile in die Datei und schließen Sie sie:  

FileWriteString(h,str);
FileClose(h);

Dieses Beispiel finden Sie im sTestFileWriteArrayToFileCSV Skript (unten angehängt).

UNICODE-Dateien

Bis jetzt wurde beim Öffnen von Dateien das FILE_ANSI-Flag immer angegeben, um ihre Codierung zu definieren. In dieser Codierung entspricht ein Byte einem Zeichen, also das gesamte Set ist auf 256 Zeichen beschränkt. Allerdings ist die UNICODE-Codierung heutzutage weit verbreitet. In dieser Codierung wird ein Symbol durch mehrere Bytes beschrieben und eine Text-Datei kann eine Vielzahl von Zeichen, darunter Zeichen von verschiedenen Alphabeten, Hieroglyphen und andere grafische Symbole enthalten.

Lassen Sie uns einige Experimente durchführen. Öffnen Sie das sTestFileReadToAlert-Skript im Editor, speichern Sie es unter dem Namen sTestFileReadToAlertUTF und ersetzen Sie das FILE_ANSI Flag mit dem FILE_UNICODE Flag:

int h=FileOpen(FileName,FILE_READ|FILE_UNICODE|FILE_TXT);

Da die Datei test.txt in ANSI gespeichert ist, enthält das neue Fenster verstümmelten Text (Abb. 9).

  
Abbildung 9 Verstümmelter Text, der auftreten kann, wenn eine Datei mit einer anderen Kodierung geöffnet wird, als sie ursprünglich erzeugt wurde.

Offensichtlich geschieht dies, weil die ursprüngliche Codierung, beim Öffnen einer Datei nicht mit der Codierung des Editors übereinstimmt.

Öffnen Sie das sTestFileCreate-Skript im Editor, speichern Sie es unter dem Namen sTestFileCreateUTF und ersetzen Sie das FILE_ANSI Flag mit dem FILE_UNICODE Flag:

int h=FileOpen("test.txt",FILE_WRITE|FILE_UNICODE|FILE_TXT);

Starten Sie zum Erstellen einer neuen Datei das sTestFileCreateUTF-Skript. Nun, zeigt das sTestFileReadToAlertUTF Skript lesbaren Text (Abb. 10).

 
Abbildung 10. Verwendung des sTestFileReadToAlertUTF - Skriptes zum Lesen der Datei die durch das sTestFileCreateUTF -Skript generiert wurde.

Öffnen Sie die Datei Test.txt im Editor und führen Sie den Befehl "Speichern unter..." im Hauptmenü aus. Beachten Sie, dass Unicode in der Encoding-Liste am unteren Rand des Fensters "Speichern unter" ausgewählt ist. Notepad hat irgendwie die Dateikodierung definiert. Unicode-Dateien beginnen mit einem Standardsatz von Symbolen, die sogenannten BOM (Byte Order Mark). Später werden wir wieder darauf zurückkommen und und die Funktion für die Definition eines Text-Datei-Typs (ANSI oder UNCODE) schreiben. 

Zusätzliche Funktionen für die Arbeit mit Textdateien die Trennzeichen beinhalten

Aus der Vielzahl von Datei-Funktionen für das Arbeiten mit dem Inhalt von Textdateien (einfache, und diejenigen mit Trennzeichen), brauchen wir eigentlich nur zwei: FileWrite() und FileReadString(). Abgesehen von anderem dient die FileReadString()-Funktion auch für die Arbeit mit Binärdateien (mehr darüber finden sie unten). Neben der FileWrite()-Funktion kann die FileWriteString()-Funktion verwendet werden, aber das ist nicht entscheidend. 

Bei der Arbeit mit Textdateien mit Trennzeichen können ein paar andere Funktionen verwendet werden, die das Arbeiten bequemer machen: FileReadBool(), FileReadNumber() und FileReadDatetime(). Die FileReadNumber()-Funktion wird verwendet, um Zahlen zu lesen. Wenn wir im Voraus wissen, dass das Feld einer Datei nur eine Zahl enthält, können wir diese Funktion anwenden. Seine Wirkung ist identisch mit dem Lesen einer Zeile mit der FileReadString()-Funktion und dem anschließenden Konvertieren mit der StringToDouble() -Funktion. Ebenso kann die FileReadBool()-Funktion verwendet werden, um Bool-Werte zu lesen. Die Zeichenfolge kann true/false oder 0/1enthalten. Die FileReadDatetime()-Funktion dient zum Lesen der Daten im Zeilenformat und wandelt es in einen numerischen Datetime-Wert um. Seine Wirkung ist ähnlich wie eine das Lesen einer Zeile und anschließendem konvertieren mithilfe der StringToTime() -Funktion.  

Binär-Dateien

Der Inhalt der oben beschriebenen Textdateien ist einfach zu lesen und sieht in unserem Programm genau so aus, wie wenn Sie die Dateien mit einem Text Editor öffnen. Sie können die Programmergebnisse mit einem Datei-Editor überprüfen. Falls erforderlich, kann die Datei manuell geändert werden. Die Nachteile von Text-Dateien liegen in ihren begrenzten Möglichkeiten. (Dies wird deutlich, wenn wir z.b. die Schwierigkeiten aufzeigen, eine einzelne Zeile ersetzen zu wollen).

Wenn eine Text-Datei klein ist, ist sie gut anzuwenden. Aber je größer sie wird, desto länger dauert es, um mit ihr zu arbeiten. Wenn Sie schnell große Datenmengen verarbeiten müssen, verwenden Sie besser binäre-Dateien.

Beim Öffnen einer Datei im Binärmodus ist statt FILE_TXT oder FILE_CSV das FILE_BIN-Flag anzugeben. Es macht keinen Sinn, FILE_ANSI oder FILE_UNCODE-encoding-Dateien anzugeben, da die binäre-Datei eine Datei mit Zahlen ist.

Natürlich können wir einen Blick auf den Inhalt der binären Datei im Notepad Texteditor werfen. Manchmal können wir auch Buchstaben und einen lesbaren Text sehen, aber das hat mehr mit dem Editor selbst, anstatt mit dem Inhalt der Datei zu tun.

Wie auch immer, Sie auf keinen Fall die Datei in einem Texteditor bearbeiten, weil sie dabei beschädigt wird. Wir gehen jetzt nicht ins Detail. Lassen Sie uns diese Tatsache einfach akzeptieren. Natürlich gibt es spezielle binäre Datei-Editoren, aber die Bearbeitung ist noch nicht intuitiv.

Binäre Dateien, Variablen

Die meisten Funktionen in MQL5, sind für das Arbeiten mit Dateien im Binär-Modus gedacht. Es gibt Funktionen zum lesen/schreiben von Variablen der verschiedenen Arten:

FileReadDouble() FileWriteDouble()
FileReadFloat() FileWriteFloat()
FileReadInteger() FileWriteInteger()
FileReadLong() FileWriteLong()
FileReadString() FileWriteString()
FileReadStruct() FileWriteStruct()

Wir werden hier nicht alle Funktionen beschreiben. Wir brauchen nur eine, weil alle anderen sind genauso verwendet werden. Wir experimentieren hier mit den Funktionen FileWriteDouble() und FileReadDouble.

Zuerst erstellen Sie eine Datei, schreiben Sie drei Variablen hinein und wir lesen sie dann in zufälliger Reihenfolge. 

Öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);

Schreiben Sie drei double Variablen mit den Werten 1,2, 3.45, 6.789 hinein:

FileWriteDouble(h,1.2);
FileWriteDouble(h,3.45);
FileWriteDouble(h,6.789);

Vergessen Sie nicht, die Datei zu schließen.

Der Code finden Sie in dem angefügten sTestFileCreateBin-Skript. Infolgedessen erscheint die test.bin Datei im Ordner "MQL5/Dateien". Werfen Sie einen Blick auf den Inhalt im Editor (Abb. 11). Öffnen Sie den Editor, und ziehen Sie die Datei darauf:

 
Abbildung 11. Binäre Datei im Editor

Wie wir sehen können, gibt es keinen Sinn, solche Dateien im Editor anzusehen.

Lassen Sie uns die Datei nun auslesen. Natürlich sollte die FileReadDouble()-Funktion zum Lesen verwendet werden. Öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Deklarieren Sie für die gelesenen Werte drei Variablen und zeigen sie in einer Messagebox an:

double v1,v2,v3;
   
v1=FileReadDouble(h);
v2=FileReadDouble(h);
v3=FileReadDouble(h);
   
Alert(DoubleToString(v1)," ",DoubleToString(v2)," ",DoubleToString(v3));
  

Vergessen Sie nicht, die Datei zu schließen. Den Code finden Sie in dem angefügten sTestFileReadBin-Skript. Wir erhalten die folgende Meldung: 1.20000000 3.45000000 6.78900000

Bei der Kenntnis des Aufbaus der Binär-Dateien, ist es möglich, begrenzte Änderungen an ihnen vornehmen. Lasst uns versuchen, die zweite Variable zu ändern, ohne die gesamte Datei neu zu schreiben.

Öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_READ|FILE_WRITE|FILE_BIN);

Nach dem öffnen, bewegen Sie den Zeiger an die angegebene Position. Die sizeof() Funktion empfiehlt sich für die Positionsberechnung. Sie gibt die Größe des angegebenen Datentyps zurück. Es wäre auch gut, mit den Datentypen und ihren Größen bestens vertraut zu sein. Bewegen Sie den Zeiger an den Anfang der zweiten Variable:

FileSeek(h,sizeof(double)*1,0);

Für mehr Klarheit, implementieren wir den Sizeof (double) * 1 Multiplikator, damit klar ist, dass dieses das Ende der ersten Variablen ist. Wenn es notwendig wäre, die dritte Variable zu ändern, müssten wir mit 2 multiplizieren

Schreiben Sie den neuen Wert: 

FileWriteDouble(h,12345.6789);

Der Code finden Sie in dem angefügten sTestFileChangeBin-Skript. Nach Ausführung des Skripts, rufen sie das sTestFileReadBin-Skript auf und Sie erhalten: 1.20000000 12345.67890000 6.78900000.

Auf die gleiche Weise können Sie eine bestimmte Variable (anstatt die gesamte Datei) lesen. Schauen wir uns den Code zum Lesen der dritten double-Variable aus test.bin an.

Öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Bewegen des Zeigers (Pointers), lesen der Werte und Anzeigen der gelesenen Werte in einer Messagebox:

FileSeek(h,sizeof(double)*2,SEEK_SET);
double v=FileReadDouble(h);
Alert(DoubleToString(v));

Dieses Beispiel finden Sie im sTestFileReadBin2 Skript (unten angehängt). Dadurch erhalten wir die folgende Meldung: 6.78900000 – die dritte Variable Ändern Sie den Code um die zweite Variable zu lesen.

Sie können die Variablen der anderen Arten und deren Kombinationen auf die gleiche Weise speichern und lesen. Es ist dabei wichtig die genaue Datei-Struktur zu kennen, um die Position des Zeigers korrekt zu berechnen zu können. 

Binäre Dateien, Strukturen

Wenn Sie mehrere Variablen von verschiedenen Arten in einer Datei schreiben wollen, ist es viel bequemer, die Struktur zu Lesen/Schreiben, anstelle die Variablen eine nach der anderen auszulesen. Die Datei beginnt in der Regel mit der Beschreibung der Struktur die den Speicherort der Daten in der Datei beschreibt (Dateiformat), gefolgt von den eigentlichen Daten. Es gibt jedoch eine Einschränkung: die Struktur sollte keine dynamische Arrays und Zeilen besitzen, da ihre Größe unbekannt ist.

Wir experimentieren nun damit, eine Struktur in eine Datei zu schreiben und diese auch zu lesen. Beschreibung der Struktur mit mehreren Variablen von verschiedenen Arten:

struct STest{
   long ValLong;
   double VarDouble;
   int ArrInt[3];
   bool VarBool;
};

Den Code finden Sie in der angefügten sTestFileWriteStructBin-Skript Datei. Deklarieren Sie zwei Variablen und füllen sie diese mit unterschiedlichen Werten in der OnStart()-Funktion:

STest s1;
STest s2;
   
s1.ArrInt[0]=1;
s1.ArrInt[1]=2; 
s1.ArrInt[2]=3;
s1.ValLong=12345;
s1.VarDouble=12.34;
s1.VarBool=true;
         
s2.ArrInt[0]=11;
s2.ArrInt[1]=22; 
s2.ArrInt[2]=33;
s2.ValLong=6789;
s2.VarDouble=56.78;
s2.VarBool=false;  

Nun öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);

Schreiben Sie die beiden Strukturen:

FileWriteStruct(h,s1);
FileWriteStruct(h,s2);

Vergessen Sie nicht, die Datei zu schließen. Führen Sie das Skript um eine Datei zu erstellen aus.

Lassen Sie uns die Datei nun auslesen. Lesen Sie die zweite Struktur.

Öffnen Sie die Datei:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Bewegen Sie den Zeiger an den Anfang der zweiten Struktur:

FileSeek(h,sizeof(STest)*1,SEEK_SET);

Deklarieren Sie die Variable (Fügen Sie die STest Strukturbeschreibung an den Anfang der Datei hinzu) und lesen Sie die Daten aus der Datei:

STest s;
FileReadStruct(h,s);

Beschreiben Sie die Werte der Felder im Fenster:

Alert(s.ArrInt[0]," ",s.ArrInt[1]," ",s.ArrInt[2]," ",s.ValLong," ",s.VarBool," ",s.VarDouble);   

Infolgedessen sehen wir die folgende Zeile in der Message-Box: 11 22 33 6789 false 56,78. Die Linie entspricht der zweiten Datenstruktur.

Den Code des Beispiels finden Sie in dem sTestFileReadStructBin-Skript (unten angehängt).

Schreiben von Strukturen mit Variablen

In MQL5 folgen die Strukturfelder aufeinander ohne Verschiebung (Ausrichtung), daher ist es möglich, bestimmte Felder ohne Schwierigkeiten zu finden/lesen.

Lesen Sie den Wert der double-Variable aus der zweiten Struktur in der test.bin Datei. Es ist wichtig, die Position für die Einstellung des Zeigers zu berechnen: 

FileSeek(h,sizeof(STest)+sizeof(long),SEEK_SET);

Der Rest ist ähnlich dem, was wir bereits viele Male in diesem Artikel getan haben: Öffnen der Datei, lesen, schließen. Den Code des Beispiels finden in dam sTestFileReadStructBin2-Skript (unten angehängt).

Definition von UNICODE-Datei, FileReadInteger Funktion

Nachdem wir uns ein bisschen mit Binär-Dateien vertraut gemacht haben, können wir eine nützliche Funktion für die Definition einer UNICODE-Datei erstellen. Diese Dateien können durch den Wert des ersten Byte (gleich 255) erkannt werden. Code 255 entspricht einem nicht druckbaren Symbol, daher kann es in einer normalen ANSI-Datei eigentlich nicht anwesend sein.

Das bedeutet, wir sollten ein Byte aus der Datei lesen und seinen Wert überprüfen. Die FileReadInteger() -Funktion dient dazu, verschiedenen Integer-Variablen, mit Ausnahme von long zu lesen, da dieser Parameter die Größe einer zu lesenden Variablen erhält. Lesen Sie ein Byte aus der Datei in die Variable V :

uchar v=FileReadInteger(h,CHAR_VALUE);

Jetzt müssen wir nur den Wert den Variablen überprüfen. Die volle Funktionscode ist nachfolgend dargestellt:

bool CheckUnicode(string FileName,bool & Unicode){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error opening file %s # %i",FileName,ErrNum);
      return(false);
   }
   uchar v=FileReadInteger(h,CHAR_VALUE);
   Unicode=(v==255);
   FileClose(h);
   return(true);
}

Die Funktion gibt, in Abhängigkeit davon, ob die Überprüfung erfolgreich war,true/false zurück. Der Dateiname wird als erster Parameter an die Funktion übergeben, während letztere (übergeben als Link) true für UNICODE-Dateien und false für ANSI-Dateien enthält, nachdem die Funktion ausgeführt wurde. 

Der Funktionscode und das Beispiel für seinen Aufruf finden Sie im sTestFileCheckUnicode Skript (unten angehängt). Starten Sie das Skript sTestFileCreate und überprüfen Sie ihren Typ mit dem sTestFileCheckUnicode-Skript. Danach starten Sie das sTestFileCreateUTF-Skript, und führen das sTestFileCheckUnicode-Skript erneut aus. Sie erhalten ein anderes Ergebnis.  

Binäre Dateien, Arrays, Struktur-Arrays

Der Hauptvorteil der binären Dateien bemerkt man beim Arbeiten mit großen Datenmengen. Die Daten befinden sich in der Regel in Arrays (da es schwierig ist, große Mengen mit getrennten Variablen zu verwalten) und in Strings. Arrays können aus Standard-Variablen und Strukturen, die den oben genannten Anforderungen entsprechen sollten, bestehen. Sie sollten keine dynamische Arrays und Strings enthalten.

Arrays werden in die Datei mithilfe der FileWriteArray() -Funktion geschrieben. Das Dateihandle wird an die Funktion als ersten Parameter übergeben, gefolgt vom Arraynamen. Die beiden folgenden Parameter sind optional. Wenn Sie nicht das gesamte Array speichern wollen, geben Sie den Index des ersten Elementes und die Anzahl der zu speichernden Elemente an. 

Arrays werden über die FileReadArray() -Funktion gelesen, die Funktionsparameter sind identisch mit den FileWriteArray() Funktionsparametern.

Lassen Sie uns die Int -Variable, bestehend aus drei Elemente, in die Datei schreiben: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   int a[]={1,2,3};   
   FileWriteArray(h,a);   
   FileClose(h);
   Alert("File written");
}

Den Code finden Sie in der sTestFileWriteArray-Datei (unten angehängt).

Jetzt lesen wir (mit dem sTestFileReadArray-Skript) und zeigen das Ergebnis im Fenster an:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   int a[];   
   FileReadArray(h,a);   
   FileClose(h);
   Alert(a[0]," ",a[1]," ",a[2]);   
}

Als Ergebnis erhalten wir die "1 2 3" Zeile, entsprichend dem zuvor angegebenen Array Beachten Sie, dass die Array-Größe ist nicht definiert ist, und sie nicht angegeben wurde, als Sie die FileReadArray()-Funktion aufgerufen haben. Stattdessen wurde die gesamte Datei gelesen. Aber die Datei besitzt möglicherweise mehrere Arrays von verschiedenen Arten. Daher wäre es sinnvoll, auch die Größe der Datei zu speichern. Lassen Sie uns die Int und double -Arrays in die Datei schreiben, beide beginnend mit einer Int -Variable, die die Größe angibt:

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   // zwei Arrays
   int a1[]={1,2,3}; 
   double a2[]={1.2,3.4};
   
   // definieren Sie die Größe der Arrays
   int s1=ArraySize(a1);
   int s2=ArraySize(a2);
   
   // Schreiben Sie das Array 1
   FileWriteInteger(h,s1,INT_VALUE); // write the array size
   FileWriteArray(h,a1); // write the array
   
   // Schreiben Sie das Array 2
   FileWriteInteger(h,s2,INT_VALUE); // write the array size
   FileWriteArray(h,a2); // write the array   
      
   FileClose(h);
   Alert("File written");
}

Den Code finden Sie in dem sTestFileWriteArray2-Skript (unten angehängt). 

Nun, beim Lesen der Datei, lesen Sie zuerst die Arraygröße und dann lesen Sie das Array mit der angegebenen Anzahl von Elementen:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   int a1[];
   double a2[];
   int s1,s2;
   
   s1=FileReadInteger(h,INT_VALUE); // read the size of the array 1
   FileReadArray(h,a1,0,s1); // read the number of elements set in s1 to the array 
   
   s2=FileReadInteger(h,INT_VALUE); // read the size of the array 2
   FileReadArray(h,a2,0,s2); // read the number of elements set in s2 to the array    

   FileClose(h);
   Alert(ArraySize(a1),": ",a1[0]," ",a1[1]," ",a1[2]," :: ",ArraySize(a2),": ",a2[0]," ",a2[1]);   
}

Den Code finden Sie in dem sTestFileReadArray2-Skript (unten angehängt).

Als Ergebnis zeigt das Skript folgende Meldung an: 3: 1 2 3 - 2: 1.2 3.4 entsprechend der Größe und dem Inhalt der Arrays, die zuvor in die Datei geschrieben wurden.

Beim Lesen des Arrays mithilfe der FileReadArray()-Funktion wird das Array automatisch skaliert. Skalierung erfolgt jedoch nur, wenn die aktuelle Größe kleiner als die Anzahl der gelesenen Elemente ist. Wenn die Größe des Arrays die Anzahl überschreitet, bleibt es unverändert. Stattdessen wird nur ein Teil des Arrays gefüllt.

Arbeiten mit Struktur-Arrays ist völlig identisch mit dem Arbeiten mit Arrays von Standard-Typen, da die Strukturgröße korrekt definiert ist (es gibt keine dynamische Arrays und Strings). Wir geben hier kein Beispiel für ein Struktur-Array. Sie können mit ihnen auf eigene Faust experimentieren.

Beachten Sie auch, dass wir überall in der Datei den Zeiger bewegen können und es somit möglich ist, nur eines der Elemente des Arrays oder einen Teil des Arrays zu lesen. Es ist wichtig, die zukünftigen Zeigerposition korrekt zu berechnen. Ein Beispiel für das Auslesen von Einzelelementen ist auch hier nicht aufgeführt, um die Artikellänge zu kürzen. Sie können es auch auf eigene Faust versuchen.

Binäre Dateien, Strings, Line-arrays

Die FileWriteString() -Funktion wird verwendet, um eine Zeichenfolge in eine binäre Datei schreiben. Die beiden obligatorischen Parameter werden an die Funktion übergeben: Datei-Handle und eine Zeile, die in die Datei geschrieben werden soll. Der erste Parameter ist optional: Sie können die Anzahl der zu schreibenden Symbole festlegen, wenn nur ein Teil der Zeile geschrieben werden soll. 

Die Zeile wird mit der FileReadString() -Funktion gelesen. In dieser Funktion ist der erste Parameter ein Handle, während der Zweite (optional) zum Festlegen der Anzahl der zu lesenden Zeichen dient.

Im Allgemeinen ist das Schreiben/Lesen der Zeilen sehr ähnlich zu dem Arbeiten mit einem Array: eine Zeile ist ähnlich wie das gesamte Array, während ein Zeichen einer Zeile viel Gemeinsamkeit mit einem einzelnen Array-Element hat, daher werden wir hier kein Beispiel dazu aufführen. Wir werden stattdessen ein etwas komplexeres Beispiel zeigen: schreiben und lesen eines String-Arrays. Zuerst lassen Sie uns die Int -Variable mit der Arraygröße in die Datei schreiben, dann schreiben Sie separate Elemente in einer Schleife hinzu, mit der Int -Variable, die die Größe zu Beginn eines jeden Elementes angibt: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   string a[]={"Line-1","Line-2","Line-3"}; // geschriebenes Array 

   FileWriteInteger(h,ArraySize(a),INT_VALUE); // schreiben der Größe des Arrays
   
   for(int i=0;i<ArraySize(a);i++){
      FileWriteInteger(h,StringLen(a[i]),INT_VALUE); // Schreiben der Größe der Zeile(Ein einzelnes Array-Element)
      FileWriteString(h,a[i]);
   }

   FileClose(h);
   Alert("File written");
}

Den Code finden Sie in der angefügten sTestFileWriteStringArray-Skript Datei.

Beim Lesen, lesen Sie zuerst die Array-Größe, dann ändern Sie seine Größe und lesen einzelne Elemente:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   string a[]; // Datei in das Array lesen
   
   int s=FileReadInteger(h,INT_VALUE); // Lesen der Größe des Arrays
   ArrayResize(a,s); // Ändern der Größe des Arrays
   
   for(int i=0;i<s;i++){ // Über jedes Element des arrays
      int ss=FileReadInteger(h,INT_VALUE); // Lese die Größe der Zeile
      a[i]=FileReadString(h,ss); // Lese die Zeile
   }

   FileClose(h);

   // Zeige das gelesene Array an
   Alert("=== Start ===");
   for(int i=0;i<ArraySize(a);i++){
      Alert(a[i]);
   }

}

Den Code finden Sie in der angefügten sTestFileReadStringArray-Skript-Datei. 

Freigegebenen Ordner für Dateien

Bis jetzt haben wir die Dateien in dem Verzeichnis/MQL5 behandelt. Dies ist jedoch nicht der einzige Ort, wo die Dateien gefunden werden können Führen Sie im Hauptmenü den MetaEditor "Datei" aus - "gemeinsamen Daten-Ordner öffnen". Der Ordner mit dem Dateiverzeichnis wird geöffnet. Dieses Verzeichnis kann auch Dateien von in MQL5 entwickelten Anwendungen enthalten. Notieren Sie den Pfad (Abb. 12):


Fig. 12. Pfad zu den gemeinsamen Daten-Ordner 

Der Pfad zum Ordner "allgemein Daten" hat nichts zu tun mit dem Pfad zum Terminal und dem Verzeichnis, welches wir in diesem Artikel bisher behandelt haben. Egal wieviele Terminals Sie gestartet haben (einschließlich die, die mit dem Key "/ portable" laufen), es wird immer der gleiche freigegebene Ordner für alle geöffnet.

Der Pfad zu den Ordnern kann programmgesteuert festgelegt werden. Der Pfad zu dem Datenordner (mit der MQL5/Files-Verzeichnis mit dem wir in dem gesamten Artikel gearbeitet haben):

TerminalInfoString(TERMINAL_DATA_PATH);

Pfad zum freigegebenen Datenordner (mit dem Dateiverzeichnis):

TerminalInfoString(TERMINAL_COMMONDATA_PATH);

Ebenso können Sie den Pfad zum Terminal ermitteln (Root-Verzeichnis, in welchem das Terminal installiert ist):

TerminalInfoString(TERMINAL_PATH);

Ähnlich wie bei dem MQL5/Verzeichnis, gibt es keine Notwendigkeit bei der Arbeit mit Dateien aus dem freigegebenen Ordner den vollständigen Pfad anzugeben. Stattdessen müssen Sie nur der Kombination von Flags, die an die Funktion FileOpen() übergeben wird, das FILE_COMMON-Flag hinzufügen. Einige Dateifunktionen haben einen bestimmten Parameter für das freigegebenen Ordner-Flag. Dies sind FileDelete(), FileMove(), FileCopy() und einige andere Funktionen.

Kopieren Sie die test.txt Datei aus dem MQL5/Ordner in den gemeinsamen Datenordner.

   if(FileCopy("test.txt",0,"test.txt",FILE_COMMON)){
      Alert("File copied");
   }
   else{
      Alert("Error copying file");
   }

Den Code finden Sie in dem angefügten sTestFileCopy-Skript. Nach dem Ausführen des Skripts, erscheint die Datei test.txt im Ordner "gemeinsame Dateien". Wenn wir das Skript zum zweiten Mal starten, erhalten wir eine Fehlermeldung. Um dies zu vermeiden, erlauben Sie das Überschreiben der Datei, indem Sie das FILE_REWRITE-Flag hinzufügen:

FileCopy("test.txt",0,"test.txt",FILE_COMMON|FILE_REWRITE)

Nun kopieren Sie die Datei aus dem freigegebenen Ordner im gleichen Ordner mit einem anderen Namen (sTestFileCopy2-Skript):

FileCopy("test.txt",FILE_COMMON,"test_copy.txt",FILE_COMMON)

Schließlich, kopieren Sie die Datei aus dem Ordner "common" in MQL5/Dateien (sTestFileCopy3-Skript):

FileCopy("test.txt",FILE_COMMON,"test_copy.txt",0)

Die FileMove()-Funktion wird genauso aufgerufen, aber hier wird keine Kopie erstellt. Stattdessen wird die Datei verschoben (oder umbenannt).

Dateien im Tester

Bis zu diesem Zeitpunkt bezog sich unsere Arbeit mit Dateien nur auf MQL5 Programme (Skripte, EAs, Indikatoren) eines Kontos (Start in einem Diagramm). Jedoch ist alles anders, beim Starten eines EA in einem Tester. Der MetaTrader 5 Tester hat die Fähigkeit, verteilte Tests mit Remote-Agenten (Cloud) durchzuführen. Grob gesagt, die Optimierung 1-10 (Zahlen sind bedingte) läuft auf einem PC, 11-20 auf eine Anderem. Dies kann zu Schwierigkeiten führen und das Arbeiten mit Dateien beeinflussen. Betrachten wir diese Funktionen und Formen die Grundsätze, die Sie beim Arbeiten mit Dateien in dem Tester beachten sollten.

Wenn Sie mit Dateien arbeiten, greift die FileOpen()-Funktion auf die Dateien die sich im MQL5/Verzeichnis im Ordner "Gerätedaten" befinden zu. Beim Testen, greift die Funktion auf die MQL5 Verzeichnisse/Dateien im Ordner "Test Agent" zu. Wenn zum Beispiel während eines einzigen Optimierungslaufes (oder bei einzelnen Tests), die Dateien benötigt werden, um Daten einer Position oder einer pending-Orders zu speichern, dann müssen Sie vor dem nächsten Durchlauf die Dateien löschen.Zum Beispiel mit der Initialisierung des EA. Wenn es sich um eine manuell erzeugte Datei handelt und sie für den EA Betriebsparameter bestimmt, wird sie im MQL5/Verzeichnis des Terminal-Data-Ordner abgelegt. Dies bedeutet, dass der Tester nicht in der Lage ist, diese Datei zu finden. Damit der EA Zugriff auf die Datei hat, muss sie dem Agenten übergeben werden. Dies geschieht in dem EA über die Festlegung der "#property Tester_file"-Eigenschaft Somit ist es möglich, beliebig vieler Dateien zu senden:

#property tester_file "file1.txt"
#property tester_file "file2.txt"
#property tester_file "file3.txt"

Auch wenn die Datei mit "#property Tester_file" angegeben ist, schreibt der EA noch eine Kopie der Datei, die sich im Test Agent-Verzeichnis befindet. Die Datei im Ordner "Gerätedaten" bleibt unverändert. Das weitere Lesen der Datei durch den EA wird aus dem Ordner "Agent" durchgeführt Das heißt, wird eine geänderte Datei gelesen. Daher ist das Speichern von Daten nicht anwendbar, wenn Sie Daten zur weiteren Analyse abspeichern müssen. Verwenden Sie stattdessen Frames .

Wenn Sie Remote-Agenten nicht verwenden, dann arbeiten Sie mit Dateien aus dem freigegebenen Ordner (legen Sie das FILE_COMMON-Flag beim Öffnen einer Datei fest). In diesem Fall gibt es keine Notwendigkeit, den Dateinamen in den EA-Eigenschaften anzugeben und der EA ist in der Lage, in die Datei zu schreiben. Kurzum, bei der Verwendung von dem Daten-Ordner "Allgemein" ist das Arbeiten mit Dateien vom Tester viel einfacher, abgesehen von der Tatsache, dass Sie keine Remote-Agenten verwenden sollten. Lassen Sie uns auch ein Auge auf die Dateinamen werfen. Der testende EA darf die Datei von dem aktuell arbeitenden EA nicht beschädigen. Das Arbeiten im Tester kann programmgesteuert festgelegt werden:

MQLInfoInteger(MQL5_TESTER)

Wenn Sie testen, verwenden Sie andere Dateinamen.

Gemeinsamer Zugriff auf Dateien

Wie schon erwähnt, wenn die Datei bereits geöffnet ist, kann sie nicht zum zweiten Mal geöffnet werden. Wenn eine Datei bereits von einer Anwendung verarbeitet wird, hat ein anderes Programm keinen Zugriff darauf, bis die Datei geschlossen wird. MQL5 bietet jedoch die Möglichkeit, Dateien zu teilen. Beim Öffnen einer Datei, legen Sie die zusätzliche FILE_SHARE_READ (gemeinsames Lesen) oder FILE_SHARE_WRITE (gemeinsames schreiben) Flags fest. Verwenden Sie die Flags mit Sorgfalt. Heutige Betriebssysteme verfügen über Multi-tasking. Daher gibt es keine Garantie, dass das Schreiben — Lesefolge korrekt ausgeführt wird. Wenn Sie schreiben und lesen freigegeben, kann es vorkommen, dass ein Programm Daten schreibt, während ein anderes Programm die gleichen (unvollendeten) Daten zur gleichen Zeit liest. Wir sollten daher zusätzliche Maßnahmen zum Synchronisieren des Zugriffs auf die Datei aus verschiedenen Programmen treffen. Dies ist eine komplizierte Aufgabe, die deutlich über den Rahmen dieses Artikels hinausreichen würde. Darüber hinaus ist es gut möglich, auch ohne Synchronisation und file-sharing auszukommen. (Dies wird unten gezeigt, wenn es zum Datenaustausch zwischen den Terminal-Dateien kommt).

Der einzige Zeitpunkt, zu welchem Sie die Datei sicher öffnen können, die mit dem gemeinsamen Lesen (FILE_SHARE_READ) Flag versehen ist und wo eine solche Freigabe gerechtfertigt ist, ist, wenn die Datei als Parameter-Datei für einen EA oder Indikator verwendet wird. Eine manuell erzeugte Datei oder eine Datei, die durch ein Script erzeugt wurde, kann von verschiedenen Instanzen eines EAs oder Indikators während der Initialisierung gelesen werden. Während der Initialisierung können mehrere EAs fast gleichzeitig versuchen die Datei zu öffnen, was Sie auch erlauben sollten. Zur gleichen Zeit gibt es eine Garantie, dass lesen und schreiben nicht gleichzeitig ststtfindet.  

Verwendung von Dateien für den Datenaustausch zwischen den Terminals

Sie können einen Datenaustausch zwischen den Terminals organisieren, indem Sie die Dateien in dem freigegebenen Ordner speichern. Natürlich ist dieses nicht die beste Art für eine Datenaustausch, aber in vielen Fällen ist es eine hilfreiche Lösung. Die Lösung ist ganz einfach: es wird kein gemeinsamer Zugriff auf die Datei verwendet. Stattdessen wird die Datei wie gewohnt geöffnet. Niemand sonst kann die Datei öffnen, solange der Schreibvorgang andauert. Nachdem das Schreiben abgeschlossen ist, wird die Datei geschlossen. Nachfolgend ist der Code der Funktion des sTestFileTransmitter-Skripts für das Schreiben von Daten:

bool WriteData(string str){
   for(int i=0;i<30 && !IsStopped();i++){ // Mehrere Versuche
      int h=FileOpen("data.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
      if(h!=INVALID_HANDLE){ // Die Datei wurde erfolgreich geöffnet
         FileWriteString(h,str); // Schreiben der Daten  
         FileClose(h); // Schreiben der Datei
         Sleep(100); // Erhöhen sie die Pause um anderen Programmen 
		     // Das Lesen zu ermöglichen
         return(true); // Gibt 'true' im Erfolgsfall zurück
      }
      Sleep(1); // Eine minimale Pause um andere Programme 
                // Das Abschließen des lese Vorgangs zu ermöglichen und den Moment 
                // Einzufangen, wo die Datei wieder verfügbar ist
   }
   return(false); // Falls das Schreiben von Daten fehlgeschlagen ist
}

Mehrere Versuche zum Öffnen der Datei erfolgten. Das Öffnen der Datei wird gefolgt vom Schreiben, schließen und einer relativ langen Pause (Sleep(100) Funktion), um anderen Programmen Zeit zum Lesen zu lassen. Im Falle eines Fehlers beim Öffnen, wird eine kurze Pause eingelegt (Sleep(1) Funktion), um den Moment zu fangen, wenn die Datei verfügbar ist.

Die Annahme (Lesefunktion) folgt dem gleichen Prinzip. Das sTestFileReceiver-Skript (unten angehängt) verfügt über eine solche Funktion Gewonnene Daten werden von der Comment() Funktion angezeigt. Starten Sie das Sender-Skript auf einem Chart und das Empfänger-Skript auf einem anderen (oder in einer anderen Instanz im Terminal). 

Einige zusätzliche Funktionen

Wir haben bereits fast alle Funktionen betrachtet, die für das Arbeiten mit Dateien wichtig sind, mit Ausnahme ein paar seltener gebrauchten Funktionen: FileSize(), FileTell() und FileFlush(). Die Funktion FileSize() gibt die Größe einer geöffneten Datei in Bytes zurück:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   ulong size=FileSize(h);
   FileClose(h);
   Alert("File size "+IntegerToString(size)+" (bytes)");
}

Den Code finden Sie in dem angefügten sTestFileSize-Skript. Wenn das Skript ausgeführt wird, wird das Nachrichtenfenster mit einer Dateigröße geöffnet. 

Die FileTell()-Funktion gibt die Position des Dateizeigers einer geöffneten Datei zurück. Die Funktion wird so selten verwendet, dass es schwierig ist, ein geeignetes Beispiel zu finden. Merken Sie sich einfach, dass es diese Funktion gibt.

Die FileFlush()-Funktion ist nützlicher. Wie in der Dokumentation erwähnt, sendet die Funktion alle Daten, die sich noch in dem Datei Eingabe/Ausgabe Puffer befinden, auf die Festplatte. Die Wirkung des Funktionsaufrufs ist ähnlich dem Schließen und wieder Öffnen der Datei. (Dieses ist effizienter und der Dateizeiger bleibt an seiner ursprünglichen Position). Wie wir wissen, werden die Dateien als Einträge auf einem Datenträger gespeichert. Das Schreiben wird in den Puffer anstelle auf der Festplatte ausgeführt, bis die Datei geöffnet wird. Das Schreiben auf die Festplatte erfolgt, wenn die Datei geschlossen wird. Daher werden die Daten im Falle eines Notfall-Programmabbruchs nicht gespeichert. Wenn wir nach jedem Schreiben in die Datei FileFlush() aufrufen, werden die Daten auf der Festplatte gespeichert und Programmabstürze verursachen keine Probleme mehr.

Arbeiten mit Ordnern

Neben der Arbeit mit Dateien, verfügt MQL5 über eine Reihe von Funktionen für das Arbeiten mit Ordnern: FolderCreate(), FolderDelete() und FolderClean(). Die FolderCreate-Funktion wird verwendet, um einen Ordner zu erstellen. Alle Funktionen verfügen über zwei Parameter. Die erste ist obligatorisch für einen Ordnernamen. Das zweite ist für das FILE_COMMON Flag (für Arbeiten mit Ordnern im gemeinsamen Ordner "Daten"). 

FolderDelete() löscht einen angegebenen Ordner. Nur ein leerer Ordner kann gelöscht werden. Jedoch ist es kein Problem, den Inhalt des Ordners zu löschen, da die FolderClean()-Funktion dafür verwendet werden kann. Der gesamte Inhalt, einschließlich der Unterordner und Dateien werden gelöscht. 

Die Liste der Dateien empfangen

Manchmal, erinnerst man sich nicht genau an den Namen einer Datei, die benötigt wird. Sie erinnern sich vielleicht an den Anfang, aber nicht an die numerische Endung, z. B. Datei1.txt Datei2.txt usw.. In diesem Fall erhalten Sie Dateinamen mit einer Maske und den FileFindFirst(), FileFindNext(), FileFindClose() Funktionen. Diese Funktionen suchen Dateien und Ordner. Ein Ordnername kann gegenüber einem Dateinamen durch einen umgekehrten Schrägstrich am Ende unterschieden werden.

Schreiben wir nun eine nützliche Funktion für den Erhalt einer Liste von Dateien und Ordnern. Wir sammeln Dateinamen in einem Array, während und in einem anderen Ordnernamen:

void GetFiles(string folder, string & files[],string & folders[],int common_flag=0){

   int files_cnt=0; // Zähler für die Dateien
   int folders_cnt=0; // Zähler für die Ordner   
   
   string name; // Variable für den Erhalt eines Datei- oder Ordner namens

   long h=FileFindFirst(folder,name,common_flag); // Erhalt eines Such-Handles und eines Namens 
                                      // der ersten Datei/des Ordners (falls verfügbar)
   if(h!=INVALID_HANDLE){ // Es gibt mindestens eine Datei oder ein Verzeichnis
      do{
         if(StringSubstr(name,StringLen(name)-1,1)=="\\"){ // Ordner
            if(folders_cnt>=ArraySize(folders)){ // Überprüfe die Größe des Arrays, 
                                                 // Erhöhe, falls notwendig
               ArrayResize(folders,ArraySize(folders)+64);
            }
            folders[folders_cnt]=name; // Sende den Namen des Ordners an das Array
            folders_cnt++; // Zähle die Ordner        
         }
         else{ // Datei
            if(files_cnt>=ArraySize(files)){ // Überprüfe die Größe des Arrays, 
                                             // Erhöhe, falls notwendig
               ArrayResize(files,ArraySize(files)+64);
            }
            files[files_cnt]=name; // Sende den Dateinamen anders Array
            files_cnt++; // Zähle die Dateien
         }
      }
      while(FileFindNext(h,name)); // Erhalte den Namen der nächsten Datei oder des nächsten Ordners
      FileFindClose(h); // Ende der Suche
   }
   ArrayResize(files,files_cnt); // Ändere die Größe des Arrays entsprechend
                                 // Der aktuellen Anzahl an Dateien
   ArrayResize(folders,folders_cnt); // Ändere die Größe des Arrays entsprechend
                                        // Der aktuellen Anzahl von Ordnern
}

Experimentieren Sie mit dieser Funktion. Wir rufen sie aus dem Skript wie folgt auf: 

void OnStart(){

   string files[],folders[];

   GetFiles("*",files,folders);
   
   Alert("=== Start ===");
   
   for(int i=0;i<ArraySize(folders);i++){
      Alert("Folder: "+folders[i]);
   }      
   
   for(int i=0;i<ArraySize(files);i++){
      Alert("File: "+files[i]);
   }

}

 Das sTestFileGetFiles-Skript ist unten beigefügt. Beachten Sie die "*" Suchmaske:

GetFiles("*",files,folders);

Die Maske ermöglicht die Suche nach alle Dateien und Ordner im MQL5/Verzeichnis.

Um alle Dateien und Ordner beginnend mit "Test" zu finden, können Sie die Maske "Test *" verwenden Benötigen Sie Txt-Dateien, benötigen Sie den "*.txt" Maske, etc.. Erstellen Sie einen Ordner (z. B. "folder1") mit einer Anzahl von Dateien. Sie können die Maske "folder1\\ *" verwenden, um die Liste der Dateien zu erhalten, die das Verzeichnis enthält.  

Сode Seite

In diesem Artikel wird die FileOpen()-Funktion im Beispiel-Codes oft angewendet. Betrachten wir einen Parameter, den wir noch nicht beschrieben haben- Codepage. Eine Codepage ist eine Umrechnungstabelle für Textsymbole und deren numerische Werte. Werfen Sie einen Blick auf die ANSI-Codierung für mehr Klarheit. Die Codierungs-Zeichentabelle enthält nur 256 Zeichen, was bedeutet, dass eine separate Codepage in den Betriebssystem-Einstellungen für jede Sprache verwendet wird. Die CP_ACP-Konstante, von der die FileOpen()-Funktion standardmäßig aufgerufen wird entspricht die Codepage. Es ist sehr unwahrscheinlich, dass jemals jemand eine andere Codepage verwenden muss, daher macht es keinen Sinn, auf Einzelheiten zu diesem Thema einzugehen. Allgemeinwissen ist hier völlig ausreichend.  

Arbeiten mit Dateien ohne Einschränkungen

Manchmal möchten Sie mit Dateien außerhalb der Terminal "Sandbox" arbeiten (außerhalb MQL5/Dateien oder freigegebenen Ordner). Dies kann die Funktionalität von MQL5 Anwendungen deutlich erweitern, so dass Sie Quellcode-Dateien verarbeiten, automatisch ändern, Bilddateien für die grafische Oberfläche on the Fly generieren können etc.. Wenn Sie es für sich selbst tun, oder einen treuen Programmierer beauftrage, können Sie das machen. Lesen Sie mehr dazu in dem Artikel "Datei-Operationen über WinAPI"</ Es gibt auch einen einfacheren Weg. MQL5 verfügt über alle erforderlichen Mittel um mit Dateien zu arbeiten, daher können Sie die Datei in die Terminal "Sandbox" verschieben, alle erforderlichen Aktionen ausführen und dann wieder zurück bewegen. Eine einzelne WinAPI-Funktion (CopyFile) ist dafür ausreichend.

Die Anwendung der WinAPI Funktionen müssen aktiviert sein, damit eine MQL5 Anwendung sie verwenden kann. Die Berechtigung wird in den Terminal-Einstellungen (Main Menü - Extras - Optionen - Expert Advisors - DLL Importe ermöglichen) aktiviert. In diesem Fall ist die Berechtigung für alle anschließend gestarteten Programme aktiviert. Statt der allgemeinen Berechtigung, können Sie die Berechtigung nur für ein Programm starten. Wenn die Anwendung auf WinAPI-Funktionen oder andere DLLs zugreifen will, erscheint auf der Registerkarte das "Abhängigkeiten"-Tab mit der Option "Allow DLL-Import".

Es gibt zwei Versionen der Funktion CopyFile: CopyFileA() und die modernere CopyFileW(). Sie können beide benutzen. Jedoch wenn Sie die CopyFileA()-Funktion verwenden, müssen Sie String-Argumente zuerst konvertieren. Lesen Sie den "Calling-API-Funktionen" Abschnitt des Artikels "MQL5 Programmierung Grundlagen: Strings" für mehr Details. Ich empfehle die aktuellere CopyFileW() Funktion. In diesem Fall werden die Zeichenfolgen so akzeptiert wie sie sind und es sind keine Umwandlungen erforderlich. 

Um die CopyFileW()-Funktion verwenden, sollten Sie diese zuerst importieren. Sie finden sie in der kernel32.dll-Bibliothek:

#import "kernel32.dll"
   int CopyFileW(string,string,int);
#import

Den Code finden Sie in dem angefügten sTestFileWinAPICopyFileW-Skript.

Das Skript kopiert die Datei, die den Quellcode enthält, nach MQL5/Dateien:

void OnStart(){
   
   string src=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Scripts\\"+MQLInfoString(MQL_PROGRAM_NAME)+".mq5";
   string dst=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"+MQLInfoString(MQL_PROGRAM_NAME)+".mq5";
   
   if(CopyFileW(src,dst,0)==1){
      Alert("File copied");
   }
   else{
      Alert("Failed to copy the file");   
   }
}

Im Erfolgsfall gibt CopyFileW() 1 zurück, sonst 0. Der dritte Funktionsparameter gibt an, ob eine Datei überschrieben werden kann, wenn es die Zieldatei schon gibt: 0 – aktiviert, 1 — deaktiviert. Starten Sie das Skript. Wenn es erfolgreich funktioniert, überprüfen Sie den MQL5/Dateien Ordner. 

Bitte beachten Sie, dass das Betriebssystem Beschränkungen für das Kopieren von Dateien besitzen kann. Es gibt die so genannten "Benutzer Konto Steuerungsparameter". Wenn sie aktiviert sind, können Dateien nach/von einigen Standorten nicht kopiert werden. Beispielsweise ist es unmöglich, eine Datei auf das Hauptverzeichnis des Systemlaufwerks zu kopieren.

Einige nützliche Skripte

Neben der Schaffung von nützlichen Funktionen für das Arbeiten mit Dateien, erstellen wir ein paar nützliche Skripte für mehr Praxiserfahrung. Wir entwickeln die Skripte für einen Export von Kursen in eine Csv-Datei und den Export von Trading-Ergebnissen.

Das Skript für den Export von Kursen besitzt Parameter, welche den Startzeitpunkt und den Endzeitpunkt definieren, sowie einen Parameter der definiert, ob diese Zeitangaben benutzt werden sollen oder ob alle Daten exportiert werden sollen. Legen Sie die erforderlichen Eigenschaft des Skriptes fest:

#property script_show_inputs

Danach werden die externen Parameter deklariert:

input bool     UseDateFrom = false; // Festlegen der Verwendung des Start-Zeitpunktes
input datetime DateFrom=0; // Startzeitpunkt
input bool     UseDateTo=false; // Festlegung der Verwendung des Endzeitpunktes
input datetime DateTo=0; // Endzeitpunkt


Schreiben Sie den Code in die OnStrat()-Script-Funktion. Definieren Sie die Daten nach den Skriptparametern:

   datetime from,to;
   
   if(UseDateFrom){
      from=DateFrom;
   }
   else{
      int bars=Bars(Symbol(),Period());
      if(bars>0){
         datetime tm[];
         if(CopyTime(Symbol(),Period(),bars-1,1,tm)==-1){
            Alert("Error defining data start, please try again later");
            return;
         }
         else{
            from=tm[0];
         }
         
      }
      else{
         Alert("Timeframe is under construction, please try again later");
         return;
      }
   }
   
   if(UseDateTo){
      to=DateTo;
   }
   else{
      to=TimeCurrent();
   }   

Wenn die Datum-definierenden Variablen verwendet werden, dann werden deren Werte verwendet. Andernfalls wird TimeCurrent() für das Enddatum verwendet, während das Startdatum der Zeitpunkt der ersten Bar ist. 

Jetzt haben wir die Zeitpunkte, für das Kopieren der Kurse in das MqlRates Array:

   MqlRates rates[];
   
   if(CopyRates(Symbol(),Period(),from,to,rates)==-1){
      Alert("Error copying quotes, please try again later");
   }

Speichern der Array-Daten in die Datei:

   string FileName=Symbol()+" "+IntegerToString(PeriodSeconds()/60)+".csv";
   
   int h=FileOpen(FileName,FILE_WRITE|FILE_ANSI|FILE_CSV,";");
   
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   
   // Schreiben von Daten in die Datei im Format: Zeit, Open, High, Low, Close, Volumen, Ticks
   
   // die erste Zeile für die Position
   FileWrite(h,"Time","Open","High","Low","Close","Volume","Ticks");  
   
   for(int i=0;i<ArraySize(rates);i++){
      FileWrite(h,rates[i].time,rates[i].open,rates[i].high,rates[i].low,rates[i].close,rates[i].real_volume,rates[i].tick_volume);
   }
   
   FileClose(h);

   Alert("Save complete, see the file "+FileName);   

Im Erfolgsfall öffnet das Skript die entsprechende Meldung darüber, dass die Datei erfolgreich gespeichert wurde. Andernfalls wird die Fehlermeldung angezeigt. Das fertige sQuotesExport-Skript ist unten beigefügt.

Nun, wir entwickeln das ist Skript zum Abspeichern der Handelshistorie. Der Anfang ist ungefähr gleich: externe Variablen kommen zuerst, wobei die Implementierung der Zeit viel einfacher ist, da Start Zeit 0 beim Anfordern von historischen Daten ausreicht:

   datetime from,to;
   
   if(UseDateFrom){
      from=DateFrom;
   }
   else{
      from=0;
   }
   
   if(UseDateTo){
      to=DateTo;
   }
   else{
      to=TimeCurrent();
   }  

Allocate history: 

   if(!HistorySelect(from,to)){
      Alert("Error allocating history");
      return;
   }

Öffnen Sie die Datei:

   string FileName="history.csv";
   
   int h=FileOpen(FileName,FILE_WRITE|FILE_ANSI|FILE_CSV,";");
   
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }

Schreiben der ersten Zeile mit den Feldnamen:

   FileWrite(h,"Time","Deal","Order","Symbol","Type","Direction","Volume","Price","Comission","Swap","Profit","Comment");     

Bei Durchlauf durch alle Transaktionen, schreiben wir Kauf- und Verkauf-Trades in die Datei:

   for(int i=0;i<HistoryDealsTotal();i++){
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0){         
         long type=HistoryDealGetInteger(ticket,DEAL_TYPE);         
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL){      
            long entry=HistoryDealGetInteger(ticket,DEAL_ENTRY);      
            FileWrite(h,(datetime)HistoryDealGetInteger(ticket,DEAL_TIME),
                        ticket,
                        HistoryDealGetInteger(ticket,DEAL_ORDER),
                        HistoryDealGetString(ticket,DEAL_SYMBOL),
                        (type==DEAL_TYPE_BUY?"buy":"sell"),
                        (entry==DEAL_ENTRY_IN?"in":(entry==DEAL_ENTRY_OUT?"out":"in/out")),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_VOLUME),2),
                        HistoryDealGetDouble(ticket,DEAL_PRICE),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_COMMISSION),2),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_SWAP),2),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_PROFIT),2),
                        HistoryDealGetString(ticket,DEAL_COMMENT)                     
            );
         }
      }
      else{
         Alert("Error allocating a trade, please try again");
         FileClose(h);
         return;
      }
   }

Beachten Sie, dass die Handels-Typen (buy/sell) und Richtungen (in/out), in Strings konvertiert werden, während einige Variablen vom Typ double in Strings mit 2 Nachkommastellen konvertiert werden. 

Am Ende wird die Datei geschlossen und eine Meldung angezeigt 

   FileClose(h);
   Alert("Save complete, see the file "+FileName); 

 Das sHistoryExport-Skript ist unten beigefügt.

Mehr zum Thema

Die Artikel-Sektion enthält eine riesige Menge an bemerkenswerte Materialien in Bezug auf die Art und Weise wie man mit Dateien arbeitet:

Schlussfolgerung

In diesem Artikel haben wir alle Funktionen für das Arbeiten mit Dateien in MQL5 betrachtet. Trotz des scheinbar schmalen Themas des Artikel erwies sich die Ausarbeitung als ziemlich groß. Jedoch wurden einige themenbezogene Fragen eher provisorisch und ohne genügend Beispiele aus der Praxis untersucht. Jedenfalls wurden die häufigsten Aufgaben im Detail, einschließlich der Arbeit mit Dateien im Tester, diskutiert. Darüber hinaus haben wir eine Reihe von nützlichen Funktionen entwickelt und alle Beispiele sind praktisch und logisch abgeschlossen. Alle Codes sind unten als Skripts angefügt.

Anhänge

  1. sTestFileRead – eine Zeile aus der ANSI-Textdatei lesen und in der Messagebox anzeigen.
  2. sTestFileReadToAlert — alle Zeilen aus der ANSI-Textdatei lesen und in das der Messagebox anzeigen.
  3. sTestFileCreate — die ANSI-Text-Datei erstellen.
  4. sTestFileAddToFile — der ANSI-Text-Datei eine Zeile hinzufügen.
  5. sTestFileChangeLine2-1 - Ungültiger Versuch, eine einzelne Zeile in der ANSI-Text-Datei zu ändern.
  6. sTestFileChangeLine2-2 — noch eine anderer ungültiger Versuchen, eine einzelne Zeile in der ANSI-Text-Datei zu ändern.
  7. sTestFileChangeLine2-3 – eine einzelne Zeile in der ANSI-Textdatei durch Neuschreiben der gesamten Datei ersetzen.
  8. sTestFileReadFileToArray – eine nützliche Funktion um die ANSI-Textdatei in ein Array zu lesen.
  9. sTestFileCreateCSV — die ANSI-CSV-Datei erstellen.
  10. sTestFileReadToAlertCSV — lesen der Felder der ANSI-CSV-Datei.
  11. sTestFileReadToAlertCSV2 — lesen der ANSI-CSV-Datei in die MessageBox mit Zeilentrennung. 
  12. sTestFileReadFileToArrayCSV — die ANSI CSV-Datei in das Struktur-Array einlesen
  13. sTestFileWriteArrayToFileCSV — das Array als eine einzige Zeile in die CSV-ANSI-Datei schreiben.
  14. sTestFileReadToAlertUTF — die UNICODE-Text-Datei lesen und in der Messagebox anzeigen.
  15. sTestFileCreateUTF — die UNICODE-Text-Datei erstellen.
  16. sTestFileCreateBin — die binäre-Datei erstellen und drei double-Variablen schreiben.
  17. sTestFileReadBin — drei double-Variablen aus der Binärdatei lesen.
  18. sTestFileChangeBin — die zweite double-Variable in der Binärdatei ändern
  19. sTestFileReadBin2 – die dritte double-Variable aus der Binärdatei lesen. 
  20. sTestFileWriteStructBin – die Struktur in die Binär-Datei schreiben.
  21. sTestFileReadStructBin – die Struktur von der Binär-Datei lesen.
  22. sTestFileReadStructBin2 — eine einzelne Variable aus der Binärdatei mit Strukturen lesen.
  23. sTestFileCheckUnicode - überprüfen des Dateityps (ANSI oder UNCODE).
  24. sTestFileWriteArray — das Array in die Binärdatei schreiben.
  25. sTestFileReadArray — das Array aus der Binärdatei lesen.
  26. sTestFileWriteArray2 — zwei Arrays in die Binär-Datei schreiben.
  27. sTestFileReadArray2 — zwei Arrays aus der Binär-Datei lesen.
  28. sTestFileWriteStringArray - String-Array in die Binär-Datei schreiben.
  29. sTestFileReadStringArray — das String-Array aus der Binärdatei lesen.
  30. sTestFileCopy - Kopieren der Datei aus MQL5/Dateien in den freigegebenen Ordner.
  31. sTestFileCopy2 – die Datei in den freigegebenen Ordner kopieren.
  32. sTestFileCopy3 – kopieren einer Datei aus dem freigegebenen Ordner nach MQL5/Dateien. 
  33. sTestFileTransmitter - Skript zur Übertragung von Daten über Dateien im freigegebenen Ordner.
  34. sTestFileReceiver - Skript zum Empfangen von Daten über die Datei im Ordner "freigegebene Daten".
  35. sTestFileSize – definieren der Größe der Datei.
  36. sTestFileGetFiles – erhalten einer Liste der Dateien über eine Such-Maske.
  37. sTestFileWinAPICopyFileW - Beispiel für die Verwendung der Funktion WinAPI CopyFileW().
  38. sQuotesExport - Skript für den Export von Kursen.
  39. sHistoryExport - Skript für das Abspeichern der Transaktionen.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2720

Beigefügte Dateien |
files.zip (27.47 KB)
Grundlagen der Programmierung in MQL5: Globale Variablen des  MetaTrader 5 Terminals Grundlagen der Programmierung in MQL5: Globale Variablen des MetaTrader 5 Terminals
Globale Variablen des Terminals sind ein unverzichtbares Hilfsmittel für die Entwicklung komplexer und zuverlässiger Experten und Berater. Sobald Sie die Verwendung globaler Variablen beherrschen, können Sie sich die Entwicklung von EAs ohne sie nicht mehr vorstellen.
Vergleich von MQL5 und QLUA - warum sind Transaktionen in MQL5 bis zu 28 Mal schneller? Vergleich von MQL5 und QLUA - warum sind Transaktionen in MQL5 bis zu 28 Mal schneller?
Viele Trader machen sich keine Gedanken darüber, wie schnell ihre Order die Börse erreicht, wie schnell sie da ausgeführt wird und wie viel Zeit das Terminal braucht, um das Ergebnis zu erhalten. Wir uns vorgenommen, die Geschwindigkeit der Ausführung von Transaktionen zu vergleichen, denn noch keiner hat vor uns solche Messungen mithilfe von MQL5- und QLUA-Programmen durchgeführt.
Das Handelssystem 'Turtle Soup' und seine Modifikation 'Turtle Soup Plus One' Das Handelssystem 'Turtle Soup' und seine Modifikation 'Turtle Soup Plus One'
In diesem Artikel wurden Regeln der Handelsstrategien Turtle Soup und Turtle Soup Plus One aus dem Buch Street Smarts: High Probability Short-Term Trading Strategies von Linda Raschke und Laurence Connors formuliert und programmiert. Die im Buch beschriebenen Strategien sind relativ populär, man sollte aber beachten, dass die Autoren diese Strategien anhand eines 15...20 Jahre alten Marktverhaltens entwickelt haben.
Grafische Interfaces X: Updates für die Easy And Fast Bibliothek (Build 3) Grafische Interfaces X: Updates für die Easy And Fast Bibliothek (Build 3)
In diesem Artikel wird die nächste Version der Easy And Fast-Bibliothek (Version 3) vorgestellt. Es wurden Fehler behoben und neue Features hinzugefügt. Mehr Details dazu finden Sie in dem Artikel.