English Русский
preview
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5

Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5

MetaTrader 5Handelssysteme |
32 0
Omega J Msigwa
Omega J Msigwa

Inhalt


Einführung

Dateioperationen sind für jede Programmiersprache unerlässlich. Sie helfen unseren Programmen, über den Code mit externen Dateien zu interagieren, und unterstützen uns beim Import und Export von Informationen. Bei Hunderten, wenn nicht Tausenden Dateitypen, die in moderner Software zur Verfügung stehen, benötigen wir bessere und effektivere Möglichkeiten, Informationen in und aus diesen Dateien zu verarbeiten (zu lesen und zu schreiben).

Die Programmiersprache MQL5 verfügt über verschiedene integrierte Möglichkeiten zum Lesen und Schreiben von unzähligen Dateitypen, die jedoch nicht immer ausreichen.

Anders als in MQL5, wo die Dateioperationen expliziter und Flag-gesteuert sind, kann dies dazu führen, dass einfache und regelmäßige Aufgaben wie das Lesen von CSV-Dateien kompliziert und fehleranfällig werden. In der Programmiersprache Python sind die Dateieingabe und -ausgabe dank einer umfangreichen Standardbibliothek, die viele Details auf niedriger Ebene, mit denen MQL5-Entwickler konfrontiert sind, abstrahiert, einfach und äußerst flexibel. Siehe das folgende Beispiel zum Lesen derselben TEXT-Datei sowohl in MQL5 als auch in Python:

In MQL5:

void OnStart()
  {
//---
    
    string filename = "readme.txt";
    int handle = FileOpen(filename,FILE_READ|FILE_TXT|FILE_ANSI, "", CP_UTF8);
    if (handle == INVALID_HANDLE)
      {
         printf("Failed to open '%s' Error = %d",filename,GetLastError());
         return;
      }
    
    while (!FileIsEnding(handle))
      {
         string data = FileReadString(handle);
         Print(data);
      }
  }

In Python:

with open(f"{files_path}\\readme.txt", "r") as file:
    for line in file:
        print(line.rstrip())

Das Lesen der gleichen Datei in Python war mühelos und viel effektiver, da die Nutzer im Gegensatz zu MQL5 die Kontrolle über die aus der Datei gewonnenen Zeilen hatten.

In diesem Artikel werden wir untersuchen, wie Dateieingabe/-ausgabe in MQL5 im Vergleich zu Python funktioniert und wie wir übergeordnete (Python-ähnliche) Abstraktionen auf der nativen API entwickeln können. Ziel ist es, einen einfachen, aber effektiven und sicheren Ansatz für E/A-Operationen in der Programmiersprache MQL5 zu bieten.


Verstehen der Funktion für E/A-Operationen in Python

Um eine Funktion für E/A-Operationen in MQL5 wie in Python zu erstellen, müssen wir die innere Funktionsweise einer Funktion namens open verstehen.

Die integrierte Funktion open() in Python wird verwendet, um eine Datei zu öffnen und ein entsprechendes Dateiobjekt zurückzugeben. Mit dieser Funktion können Sie aus Dateien lesen oder in Dateien schreiben, wobei verschiedene Optionen für den Dateimodus (z. B. Text/Binär) und die Kodierung zur Verfügung stehen.

Funktionssignatur.

open(
    file, 
    mode="r", 
    buffering=-1,
    encoding=None,
    errors=None,
    newline=None,
    closefd=True,
    opener=None
)

Argumente.

ArgumentBeschreibungStandardwert
DateiEin pfadähnliches Objekt, das den Pfadnamen der zu öffnenden Datei angibt.Erforderlich.
ModusEine Zeichenkette zur Angabe des Modus, in dem die Datei geöffnet werden soll (z. B. 'r', 'w', 'b' usw.).'r'.
PufferungEine ganze Zahl, die zur Festlegung der Pufferungsrichtlinie verwendet wird.-1
KodierungDer Name der Kodierungsmethode, die zur Kodierung oder Dekodierung der Datei verwendet wird.Nein
ZeilenumbruchEine Zeichenkette, die festlegt, wie neue Zeichen aus dem Stream geparst werden sollen.Nein
closefdEin boolescher Wert, der festlegt, ob ein Dateideskriptor geschlossen werden soll.true
ÖffnerEin aufrufbares Programm, mit dem nutzerdefiniert die Zieldatei geöffnet werden soll.Nein

In unserer äquivalenten MQL5-Funktion gibt es eine Reihe von Variablen, die nützlich sein könnten.

int CFileIO::open(const string filename, 
                  const string mode, 
                  uint cp_encoding = CP_UTF8, 
                  const bool common = false, 
                  const string newline = "", 
                  bool is_unicode=false);

Weitere Variablen wie „common“ (zur Auswahl, ob sich die Datei im Verzeichnis „common“ oder im MQL5-Datenpfad befindet) und die Variable „is_unicode“ (zur Auswahl, ob die Datei Unicode-Zeichen enthält – bei „true“ handelt es sich um Zeichenfolgen vom Typ Unicode (Zweibyt-Zeichen), bei „false“ um Zeichenfolgen vom Typ ANSI (Ein-Byte-Zeichen)).

Das interessanteste Argument der Funktion open ist mode.

Datei-Modi in Python

Der Dateimodus teilt Python mit, welche Art von Operationen (Lesen, Schreiben usw.) Sie mit der Datei durchführen wollen.

ModusBeschreibung
'r'Schreibgeschützt. Gibt einen E/A-Fehler aus, wenn die Datei nicht existiert.
'r+'Lesen und Schreiben. Gibt einen E/A-Fehler aus, wenn die Datei nicht existiert.
'w'Nur Schreibzugriff. Überschreibt die Datei, wenn sie vorhanden ist; andernfalls wird eine neue Datei erstellt.
'w+'Lesen und Schreiben. Überschreibt die Datei oder erstellt eine neue Datei.
'a'Nur anhängen. Fügt Daten am Ende hinzu. Erzeugt eine Datei, wenn sie nicht existiert.
'a+'Lesen und Anhängen. Zeiger wird am Ende platziert. Erzeugt eine Datei, wenn sie nicht existiert.
'rb'Lesen im Binärmodus. Es muss eine Datei vorhanden sein.
'rb+'Lesen und Schreiben im Binärmodus. Die Datei muss existieren.
'wb'Schreiben in binärer Form. Überschreibt oder erstellt neu.
'wb+' Lesen und Schreiben im Binärformat. Überschreibt oder erstellt neu. 
'ab' Anhängen in binärer Form. Erzeugt eine Datei, wenn sie nicht existiert. 
'ab+' Lesen und Anhängen im Binärformat. Erzeugt eine Datei, wenn sie nicht existiert. 

Damit sich unsere MQL5-Funktion wie ihr Python-Pendant verhält, wenn es darum geht, eine beliebige Datei zu öffnen, unabhängig davon, was sie benötigt, brauchen wir eine Funktion, die uns hilft, die Flags automatisch zu erzeugen, je nach Modus einer Datei.


Automatisches Auswählen von Datei-Flags 

Da die in MQL5 integrierte Funktion FileOpen in hohem Maße auf die sogenannten Datei-Flags angewiesen ist, benötigen wir eine Möglichkeit, diese automatisch entsprechend dem/den oben beschriebenen Dateimodus(en) zu erzeugen.

int CFileIO::flagsgen(const string file_mode, bool &is_append)
  {
//--- default flag(s) for txt files

   int flags = 0;
   string mode = file_mode;
   StringToLower(mode);

   for(int i = 0; i < (int)mode.Length(); i++)
     {
      switch(StringGetCharacter(mode, i))
        {
         case 'r':
            flags |= (FILE_READ | FILE_SHARE_READ);
            break;
         case 'w':
            flags |= (FILE_WRITE | FILE_SHARE_WRITE);
            break;
         case 'a':
           {
            flags |= FILE_WRITE;
            is_append = true;
            break;
           }
         case '+':
            flags |=  FILE_READ | FILE_WRITE | FILE_SHARE_READ | FILE_SHARE_WRITE;
            break;
         case 'b':
            flags |= FILE_BIN;
            break;
         case 'x':
            flags |= (FILE_REWRITE | FILE_WRITE | FILE_SHARE_WRITE);
            break;
        }
     }

   return flags;
  }

Die Variable is_append ist nützlich für den Aufruf der Methode FileSeek beim Anhängen der Informationen am Ende einer Datei.

Beachten Sie, dass wir FILE_SHARE_READ haben, wenn es ein FILE_READ-Flag gibt, und FILE_SHARE_WRITE, wenn es ein FILE_WRITE-Flag gibt. 

Damit soll der Prozess des Lesens und Schreibens in eine Datei, die von anderen Programmen verwendet wird, verstärkt werden.

Um dies noch besser zu machen, können wir eine optionale Variable shared_IO verwenden (wenn sie auf true gesetzt ist, bedeutet dies, dass wir I/O-Operationen auf Dateien durchführen können, die von anderen Programmen verwendet werden, und andere Programme können dasselbe tun, wenn eine Datei in MetaTrader 5 geöffnet wird).

int CFileIO::flagsgen(const string file_mode, bool &is_append, bool shared_IO=true)
  {
//--- default flag(s) for txt files

   int flags = 0;
   string mode = file_mode;
   StringToLower(mode);

   for(int i = 0; i < (int)mode.Length(); i++)
     {
      switch(StringGetCharacter(mode, i))
        {
         case 'r':
            flags |= FILE_READ;
            
            if (shared_IO)
               flags |= FILE_SHARE_READ;
            break;
         case 'w':
            flags |= FILE_WRITE;
            
            if (shared_IO)
               flags |= FILE_SHARE_WRITE;
            break;
         case 'a':
           {
            flags |= FILE_WRITE;
            is_append = true;
            break;
           }
         case '+':
            flags |=  FILE_READ | FILE_WRITE;
            
            if (shared_IO)
               flags |= FILE_SHARE_READ | FILE_SHARE_WRITE;
            break;
         case 'b':
            flags |= FILE_BIN;
            break;
         case 'x':
            flags |= FILE_REWRITE | FILE_WRITE;
            
            if (shared_IO)
               flags |= FILE_SHARE_WRITE;
            break;
        }
     }

   return flags;
  }

Der Wert wird direkt von der Funktion namens „open“ übergeben.

   static int        open(const string filename, 
                          const string mode, 
                          uint cp_encoding = CP_UTF8, 
                          const bool common = false, 
                          const string newline = "", 
                          bool is_unicode=false,
                          bool shared_IO=true);


Python-ähnliche offene Methode in MQL5

Mithilfe der Flags, die je nach Modus einer Datei erzeugt werden, können wir nun jede beliebige Datei öffnen.

int CFileIO::open(const string filename, 
                  const string mode, 
                  uint cp_encoding = CP_UTF8, 
                  const bool common = false, 
                  const string newline = "", 
                  bool is_unicode=false,
                  bool shared_IO=true)
  {
//---

   bool is_append = false;
   int flags = flagsgen(mode, is_append, shared_IO);
   string file_extension = getFileExtension(filename);
   
//---

   if (file_extension=="")
     return INVALID_HANDLE;
   
//--- we add select a file from the common folder if commo=true

   if(common)
      flags |= FILE_COMMON;

//---
   
   bool is_binary = (flags & FILE_BIN) != 0;
   if (!is_binary) //Avoid unicode and ANSI flags during a binary mode
    {
      if (is_unicode)
         flags |= FILE_UNICODE;
      else
         flags |= FILE_ANSI;
    }
   
//--- Open a file for either reading or writing

   int h = FileOpen(filename, flags, newline, cp_encoding);
   if(h == INVALID_HANDLE)
     {
      printf("Failed to read '%s', Error = %s", filename, fileErrorsDescription(GetLastError()));
      return INVALID_HANDLE;
     }

//---

   if(is_append)
      FileSeek(h, 0, SEEK_END);

   return h;
  }

Das Erzeugen von Flags in Abhängigkeit vom Dateimodus reicht nicht aus; wir müssen einige sehr nützliche Flags an die primären Flags anhängen. Diese Flags helfen bei:

01: der Identifikation, wo sich die Datei befindet (entweder im MQL5-Datenpfad oder im Ordner „common“)

   if(common)
      flags |= FILE_COMMON;

02: der Entscheidung, ob ANSI oder UNICODE zum Lesen von Bytesymbolen verwendet werden soll.

   bool is_binary = (flags & FILE_BIN) != 0;
   if (!is_binary) //Avoid unicode and ANSI flags during a binary mode
    {
      if (is_unicode)
         flags |= FILE_UNICODE;
      else
         flags |= FILE_ANSI;
    }

Es scheint, dass MQL5 eine Datei als Bytestream behandelt, wenn ein Binärflag FILE_BIN vorhanden ist; man muss sich nicht viel um UNICODE- und ANSI-Flags kümmern.

Jetzt können wir diese universelle Funktion verwenden, um verschiedene Arten von Dateien in MetaTrader 5 zu öffnen.

#include <PyMQL5\\fileIO\\fileIO.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    CFileIO::open("readme.txt", "r+"); //open the file in read/write mode
    CFileIO::open("MT5.log", "r"); //readonly 
    CFileIO::open("mydata.csv", "r+"); //read/write mode for a CSV file
    CFileIO::open("mydata.xlsx", "r"); //A less common filetype
    CFileIO::open("tiny-cat.jpg", "rb"); //an image file, readonly binary file mode
    CFileIO::open("array.bin", "w+b"); //Read and write mode for a binary file
 }

Alle Dateien wurden erfolgreich in MetaTrader 5 geöffnet, da im Terminal keine Fehler angezeigt wurden, wie es bei einem Fehlschlagen der Funktion zu erwarten wäre.

Die Funktion open gibt ein Handle zu einer geöffneten Datei zurück. Sie können die Datei immer noch mit den nativen MQL5-Funktionen für die Dateibearbeitung bearbeiten, einschließlich des Schließens der Datei, wenn Sie sie nicht mehr benötigen.

Die Rückgabe eines Handles bedeutet jedoch, dass wir es immer noch manuell verwalten müssen; es wäre ideal, wenn es eine Klasse zurückgeben würde, die alle Eigenschaften und Methoden einer bestimmten Datei enthält.

Die Klasse CFile (Objekt)

class CFile
  {
protected:

   int               m_handle;
   string            m_filename;
   int               m_flags;

   bool              isHandleOk(string func)
     {
      if(m_handle == INVALID_HANDLE)
        {
         printf("%s Invalid file handle received", func);
         return false;
        }
      return true;
     }

public:
                     CFile(void)
     {
      m_handle = INVALID_HANDLE;
      m_flags  = 0;
      m_filename = "";
     };

                    ~CFile(void)
     {

     };

   //--- configurations

   void              Config(const string filename, const int handle, const int flags)   
     {
      m_filename = filename;
      m_handle = handle;
      m_flags = flags;
     }

   void              close();
};

Dies sollte uns nun eine problemlose Handhabung und Manipulation der geöffneten Datei ermöglichen.

void OnStart()
  {
//---

    CFile f = CFileIO::open("readme.txt", "r"); //open the file in read-only mode
    f.close(); //closing after you are done with it

    f = CFileIO::open("MT5.log", "r"); //readonly 
    f.close();

    f = CFileIO::open("mydata.csv", "r+"); //read/write mode for a CSV file
    f.close();

    f = CFileIO::open("array.bin", "wb+");
    f.close();  
  }


Lesen von Daten/Informationen aus Dateien

Das Tolle an Dateioperationen in Python ist, dass sie dem Nutzer die Kontrolle über das Lesen und Interpretieren der von den Dateien erhaltenen Informationen geben.

import csv

files_path = r"C:\Users\omega\AppData\Roaming\MetaQuotes\Terminal\FB9A56D617EDDDFE29EE54EBEFFE96C1\MQL5\Files"

with open(f"{files_path}\\readme.txt", "r") as file:
    for line in file: # reading a file line by line
        print(line.rstrip())
        

with open(f"mydata.csv", "r", encoding="utf-8-sig", newline='') as csvfile:
    csvreader = csv.reader(csvfile, delimiter=',')
    for row in csvreader: # reading a csv file row by row
        print(row)

Ausgabe:

hello, this is a readme file with plenty of information to read from.

This is a third line after a space.
['DateTime', 'Open', 'High', 'Low', 'Close']
['12/27/2023 19:00', '2081.72', '2082.53', '2079.52', '2081.94']
['12/27/2023 18:00', '2078.97', '2082.41', '2076.73', '2081.69']
['12/27/2023 17:00', '2070.29', '2081.88', '2069.01', '2078.93']
['12/27/2023 16:00', '2068.33', '2071.62', '2066.6', '2070.3']

MQL5 bietet uns zwar auch eine Möglichkeit, die Informationen über die Zeilen einer Datei in einer while-Schleife zu verfolgen, die durch alle Zeilen einer Datei geht, aber der Code in Python fühlt sich viel besser an. Lassen Sie uns eine ähnliche Funktionalität in MQL5 implementieren.

Da MQL5 mehrere Funktionen zum Lesen von Daten aus Dateien hat, wie z.B. FileReadString, FileReadDouble, FileReadLong, usw., können wir eine Vorlage verwenden, um die Funktion mit allen unterstützten Datentypen arbeiten zu lassen, sodass sich der Nutzer über den Typ der Variablen, die er per Referenz übergibt, Gedanken machen muss, da er den resultierenden Datentyp auf der Grundlage des Variablentyps erhält.

template <typename T>
T CFile::__readline__()
  {
   T datatype = T(0);

// string
   if(typename(T) == typename(string))
      datatype = (T)FileReadString(m_handle);

// int
   if(typename(T) == typename(int))
      datatype = (T)FileReadInteger(m_handle);

// long
   if(typename(T) == typename(long))
      datatype = (T)FileReadLong(m_handle);

// double
   if(typename(T) == typename(double))
      datatype = (T)FileReadDouble(m_handle);

// float (read as double and cast)
   if(typename(T) == typename(float))
      datatype = (T)FileReadDouble(m_handle);

// bool (read as int and cast)
   if(typename(T) == typename(bool))
      datatype = (T)FileReadInteger(m_handle);

// datetime (read as long and cast)
   if(typename(T) == typename(datetime))
      datatype = (T)FileReadLong(m_handle);

   return datatype;
  }

Diese Funktion kann dann innerhalb einer öffentlichen Funktion namens „readline“ vererbt werden.

template <typename T>
bool CFile::readline(T &line)
  {

   if(!isHandleOk(__FUNCTION__))
      return false;

//---

   while(!FileIsEnding(m_handle))
     {
      line = __readline__<T>();
      return true;
     }

   return false;
  }

Beispiel für die Verwendung:

#include <PyMQL5\\fileIO\\fileIO.mqh>
#include <PyMQL5\\fileIO\\csv.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Reading a text file

    CFile f = CFileIO::open("readme.txt", "r"); //open the file in read-only mode
    
    string text;
    while(f.readline(text))
       Print(text);
    
    f.close(); //closing after you are done with it
 }

Ausgabe:

ND      0       22:34:35.591    Test file IO (EURUSD,H1)        hello, this is a readme file with a plenty of information to read from.
DD      0       22:34:35.591    Test file IO (EURUSD,H1)        
GG      0       22:34:35.591    Test file IO (EURUSD,H1)        This is a third line after a space.

Diese Funktion ist sogar in der Lage, Binärdateien zu lesen.

void OnStart()
  {
    f = CFileIO::open("array.bin", "wb+");
    
    int value, count = 0;
    while (f.readline(value)) 
     {
       printf("array[%d]: %d",count,value);
       count++;
     }
      
    f.close();  
 }

Ausgabe:

PI      0       17:27:44.966    Test file IO (EURUSD,H1)        array[0]: 1
RR      0       17:27:44.966    Test file IO (EURUSD,H1)        array[1]: 2
PK      0       17:27:44.966    Test file IO (EURUSD,H1)        array[2]: 3
ND      0       17:27:44.966    Test file IO (EURUSD,H1)        array[3]: 4
PM      0       17:27:44.966    Test file IO (EURUSD,H1)        array[4]: 5
RF      0       17:27:44.966    Test file IO (EURUSD,H1)        array[5]: 6

Diese Funktion, readline, funktioniert für verschiedene Dateitypen wie ein Zauber. Bei der Arbeit mit CSV-Dateien benötigen wir einige spezielle Funktionen zum Parsen der Zeilen und zum sicheren Extrahieren des Inhalts aus allen Zeilen.

In Python gibt es ein kleines Modul namens csv, das für das Lesen und Schreiben von bzw. in CSV-Dateien zuständig ist.

import csv

with open(f"mydata.csv", "r", encoding="utf-8-sig", newline='') as csvfile:
    csvreader = csv.reader(csvfile, delimiter=',')
    for row in csvreader: # reading a csv file row by row
        print(row)

Ausgabe:

['DateTime', 'Open', 'High', 'Low', 'Close']
['12/27/2023 19:00', '2081.72', '2082.53', '2079.52', '2081.94']
['12/27/2023 18:00', '2078.97', '2082.41', '2076.73', '2081.69']
['12/27/2023 17:00', '2070.29', '2081.88', '2069.01', '2078.93']
['12/27/2023 16:00', '2068.33', '2071.62', '2066.6', '2070.3']
['12/27/2023 15:00', '2067.68', '2069.73', '2066.15', '2068.38']

Haben Sie bemerkt, dass Python alle Werte aus jeder Zeile einer CSV-Datei als Zeichenketten liest?

Das ist großartig, denn von allen Variablen lassen sich Zeichenketten am sichersten in andere Variablen umwandeln, ganz zu schweigen davon, dass CSV-Dateien in der Regel verschiedene Datentypen enthalten. Es ist eine gute Idee, sie alle zusammen in einem Array vom Typ string zu speichern.

Wir können eine ähnliche Klasse in MQL5 erstellen.

#include "fileIO.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSVReader
  {
protected:
   int               m_handle;
   string            m_delimiter;
   char              m_quote;
   bool              m_doublequote;
   bool              m_skipinitialspace;
   char              m_escape;
   uint              cols_found;
   
   string StringTrim(string s)
     { 
       StringTrimLeft(s); 
       StringTrimRight(s); 
       return s; 
     }
   
   void ParseCSVLine(string line, string &fields[]);
   
public:
                     CSVReader(CFile &file,
                               const string delimiter = ",",
                               const char quotechar = '"',
                               const char escapechar = '\\',
                               const bool doublequote = true,
                               const bool skipinitialspace = false
                              );

                    ~CSVReader(void);
                    bool readRow(string &row[]);
  };

Um zu verhindern, dass große Dateien geöffnet werden, können wir einige Kontrollen einbauen.

Wir prüfen, ob eine Datei eine bestimmte Größe überschreitet.

#define MAX_FILE_SIZE_MB 200

//--- Getting the file size in MegaBytes

   double file_size_MB = (double)FileSize(m_handle) / (double)1e6;
   printf("%s Filesize in ~ MB [%.3f]", __FUNCTION__, file_size_MB);

   if((uint)file_size_MB > MAX_FILE_SIZE_MB)
     {
      printf("%s Failed, CSV filesize [%.3f] in MBs is greater than the maximum file size accepted [%I64u] in MBs. To pass this limit, change the variable 'MAX_FILE_SIZE_MB'", 
             __FUNCTION__, file_size_MB, MAX_FILE_SIZE_MB);
      return;
     }

Wir prüfen auch, ob genügend Speicherplatz für die zu öffnende Datei vorhanden ist.

//--- Ensuring the CSV file size doesn't exceed available memory for the Terminal

   ulong free_ram_MB = (ulong)TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE);
   printf("Free Terminal RAM ~ %I64u MB", free_ram_MB);

//--- The CSV file isn't supposed to be greater in size than half of the available memory

   if(file_size_MB >= free_ram_MB)
     {
      printf("Filesize in MB [%.3f] is greater than available memory [%I64u] in the Terminal", file_size_MB, free_ram_MB);
      return;
     }

Alle diese Prüfungen finden in einem Klassenkonstruktor statt.

CSVReader::CSVReader(CFile &file,
                     const string delimiter = ",",
                     const char quotechar = '"',
                     const char escapechar = '\\',
                     const bool doublequote = true,
                     const bool skipinitialspace = false)
  {
//---

   m_handle = file.getHandle();
   m_delimiter = delimiter;
   m_quote = quotechar;
   m_doublequote = doublequote;
   m_skipinitialspace = skipinitialspace;
   m_escape = escapechar;
   
//--- Getting the file size in MegaBytes

   double file_size_MB = (double)FileSize(m_handle) / (double)1e6;
   printf("%s Filesize in ~ MB [%.3f]", __FUNCTION__, file_size_MB);

   if((uint)file_size_MB > MAX_FILE_SIZE_MB)
     {
      printf("%s Failed, CSV filesize [%.3f] in MBs is greater than the maximum file size accepted [%I64u] in MBs. To pass this limit, change the variable 'MAX_FILE_SIZE_MB'", 
             __FUNCTION__, file_size_MB, MAX_FILE_SIZE_MB);
      return;
     }

//--- Ensuring the CSV file size doesn't exceed available memory for the Terminal

   ulong free_ram_MB = (ulong)TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE);
   printf("Free Terminal RAM ~ %I64u MB", free_ram_MB);

//--- The CSV file isn't supposed to be greater than half of the available memory

   if(file_size_MB >= free_ram_MB)
     {
      printf("Filesize in MB [%.3f] is greater than available memory [%I64u] in the Terminal", file_size_MB, free_ram_MB);
      return;
     }
  }

Konstruktor-Argumente

ArgumentBeschreibungStandard
m_handleEin gültiges CSV-Datei-Handle, das von FileOpen() zurückgegeben wird. Sie bezieht sich auf eine bereits geöffnete CSV-Datei. Der Leser arbeitet direkt mit diesem Handle und verwaltet nicht das Öffnen oder Schließen der Datei.Erforderlich
m_delimiterEine Zeichenkette, die zur Trennung von Feldern innerhalb einer Zeile verwendet wird. Übliche Werte sind "," (Komma), ";" (Semikolon) und "\t" (Tabulator).","
m_quotecharDas Zeichen, das für Anführungszeichen in Feldern verwendet wird, die Begrenzungszeichen oder Sonderzeichen enthalten. 
Alles, was sich innerhalb passender Anführungszeichen befindet, wird als literale Daten behandelt.
' " '
m_escapecharDas Zeichen, das verwendet wird, um Sonderzeichen innerhalb eines Feldes mit Anführungszeichen zu umgehen. Mit \" kann zum Beispiel ein Anführungszeichen innerhalb eines in Anführungszeichen gesetzten Wertes erscheinen. '\\'
m_doublequoteDas steuert, wie Anführungszeichen innerhalb von Anführungszeichenfeldern behandelt werden. Wenn true, werden zwei aufeinanderfolgende Anführungszeichen ("") als ein einzelnes wörtliches Anführungszeichen interpretiert, was dem Standardverhalten von CSV entspricht.""
m_skipinitialspaceWenn diese Option aktiviert ist, werden Leerzeichen unmittelbar nach dem Begrenzungszeichen ignoriert. Dies ist nützlich für das Parsen von lose formatierten CSV-Dateien wie „A,B,C“ anstelle von „A, B, C“.false

Nachdem wir eine CSV-Datei geöffnet haben, erstellen wir ein CSVReader-Objekt und weisen es einer Variablen namens „reader“ zu. Dann erstellen wir ein Array namens row[], und alle Zeilen aus einer CSV-Datei werden iterativ in diesem Array gespeichert.

void OnStart()
  {
    int csv_file = CFileIO::open("mydata.csv", "r+"); //read/write mode for a CSV file
    
    CSVReader reader(csv_file, ",");
    
    string row[];
    while(reader.readRow(row))
      ArrayPrint(row);
    
    CFileIO::close(csv_file);
  }

Ausgabe:

CP      0       00:51:24.810    Test file IO (EURUSD,H1)        CSVReader::CSVReader Filesize in ~ MB [0.001]
CD      0       00:51:24.815    Test file IO (EURUSD,H1)        Free Terminal RAM ~ 32245 MB
IJ      0       00:51:24.816    Test file IO (EURUSD,H1)        "ÿDateTime"      "Open"           "High"           "Low"            "Close"          "Strings Column"
HS      0       00:51:24.816    Test file IO (EURUSD,H1)        [0] "12/27/2023 19:00"                       "2081.72"                                "2082.53"                               
GJ      0       00:51:24.816    Test file IO (EURUSD,H1)        [3] "2079.52"                                "2081.94"                                "Yes, this column has text with commas."
DM      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 18:00" "2078.97"          "2082.41"          "2076.73"          "2081.69"          "None"            
DQ      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 17:00" "2070.29"          "2081.88"          "2069.01"          "2078.93"          "None"            
DH      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 16:00" "2068.33"          "2071.62"          "2066.6"           "2070.3"           "Some value"      
PK      0       00:51:24.816    Test file IO (EURUSD,H1)        [0] "12/27/2023 15:00"          "2067.68"                   "2069.73"                  
NE      0       00:51:24.816    Test file IO (EURUSD,H1)        [3] "2066.15"                   "2068.38"                   "Another value, with comma"
PI      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 14:00" "2068.21"          "2070.29"          "2064.37"          "2067.69"          "None"            
CM      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 13:00" "2064.73"          "2068.87"          "2064.62"          "2068.19"          "None"            
EL      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 12:00" "2068.38"          "2068.72"          "2061.51"          "2064.75"          "Some value"      
HE      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 11:00" "2067.39"          "2069.28"          "2067.31"          "2068.38"          "None"            
DH      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 10:00" "2066.09"          "2068.31"          "2065.85"          "2067.38"          "None"            
CM      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 9:00" "2065.06"         "2066.38"         "2064.81"         "2066.09"         "None"           
KO      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 8:00" "2064.7"          "2067.43"         "2064.44"         "2065.07"         "None"           
GR      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 7:00" "2065.88"         "2066.26"         "2064.42"         "2064.7"          "None"           
KE      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 6:00" "2064.6"          "2066"            "2064.11"         "2065.88"         "None"           
NI      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 5:00" "2065.44"         "2066.59"         "2064.44"         "2064.62"         "None"           
CK      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 4:00" "2066.74"         "2067.28"         "2064.8"          "2065.44"         "None"           
HO      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 3:00" "2065.58"         "2067.89"         "2064.95"         "2066.74"         "None"           
RQ      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 2:00" "2066.2"          "2066.52"         "2063.97"         "2065.63"         "None"           
RD      0       00:51:24.816    Test file IO (EURUSD,H1)        "12/27/2023 1:00" "2068.08"         "2068.62"         "2066.05"         "2066.2"          "None"           


Schreiben von Daten/Informationen in die Dateien

Beim Schreiben von Daten in die Dateien wird ein etwas anderer Ansatz verfolgt als beim Lesen.

Wir können eine Methode namens FileWrite verwenden, die Variablen eines beliebigen Datentyps annimmt.

template <typename T>
static bool CFileIO::write(int file_handle, T info)
  {
   if(FileWrite(file_handle, info) == 0)
     {
      printf("%s failed to write to a file. Error = %s", __FUNCTION__, fileErrorsDescription(GetLastError()));
      return false;
     }

   return true;
  }

Versuchen wir, neue Daten an das Ende einer bestehenden Datei zu schreiben.

Im Dateimodus: r steht für Lesen, + steht für Lesen und Schreiben und a steht für Ergänzen (Anhängen) neuer Informationen am Ende einer Datei.

void OnStart()
  {
    CFile f = CFileIO::open("readme.txt", "r+a");
    f.write("Newly added data | "+string(TimeLocal()));
    f.close();
  }

Nachdem ich das Skript mehrmals ausgeführt hatte, wurde die Datei readme.txt mit neuen Datenzeilen angezeigt.

hello, this is a readme file with a plenty of information to read from.

This is a third line after a space.
Newly added data | 2025.12.22 06:32:35
Newly added data | 2025.12.22 06:33:05
Newly added data | 2025.12.22 06:33:19

Die Methode FileWrite kann, wenn sie eine dynamische (Template-Variable) erhält, mit allen außer Array-Variablen arbeiten. 

Um Arrays mit Daten in eine Datei zu schreiben, können wir die Funktion FileWriteArray verwenden.

template <typename T>
bool  CFile::write(T &info[])
  {
   if(!isHandleOk(__FUNCTION__))
      return false;

//---

   if(FileWriteArray(m_handle, info) == 0)
     {
      printf("%s failed to write an array to a file. Error = %s", __FUNCTION__, fileErrorsDescription(GetLastError()));
      return false;
     }

   return true;
  }

Obwohl die Funktion FileWriteArray für Binärdateien gedacht ist, können wir sie dazu zwingen, ein Array in eine Textdatei zu schreiben.

void OnStart()
  {
    CFile f = CFileIO::open("array.txt", "wt");
    
    string data[] = {"data01", "data02", "data03", "data04"};
    f.write( data);
    f.close();
 }

Ausgabe:

2025.12.22 06:46:16.324 Test file IO (EURUSD,H1)        CFile::write<string> failed to write an array to a file. Error = The file must be opened as a text

Wir erhalten die Fehlermeldung, dass unsere Datei als Textdatei geöffnet werden sollte. 

Das liegt daran, dass wir zwar in der Lage waren, Textdateien zu lesen und zu schreiben, sie aber nie mit einem FILE_TXT-Flag geöffnet haben; wir haben noch keine Möglichkeit, dies im Dateimodus-Argument zu behandeln.

Für Textdateien müssen wir den Buchstaben 't' akzeptieren, der dann das Flag FILE_TXT auslöst.

int CFileIO::flagsgen(const string file_mode, bool &is_append, bool shared_IO = true)
  {
//--- default flag(s) for txt files

   int flags = 0;
   string mode = file_mode;
   StringToLower(mode);

   for(int i = 0; i < (int)mode.Length(); i++)
     {
      switch(StringGetCharacter(mode, i))
        {
         case 'r':
            flags |= FILE_READ;

            if(shared_IO)
               flags |= FILE_SHARE_READ;
            break;
         case 'w':
            flags |= FILE_WRITE;

            if(shared_IO)
               flags |= FILE_SHARE_WRITE;
            break;

         //--- other cases

         case 't': //Additional text mode 
            flags |= FILE_TXT;
            break;
        }
     }

   return flags;
  }

Um Fehler wie den obigen zu umgehen, müssen Sie beim Öffnen einer Text- oder textbasierten Datei lediglich das 't' angeben.

void OnStart()
  {
    CFile f = CFileIO::open("array.txt", "wt");
    
    string data[] = {"data01", "data02", "data03", "data04"};
    f.write( data);
    f.close();
 }

Ausgabe:

Schreiben in eine CSV-Datei

Da eine CSV-Datei eine zweidimensionale Datenspeicherung ist, müssen wir sie anders behandeln, wenn wir neue Daten in sie schreiben wollen. Beim Lesen einer solchen Datei haben wir einen CSV-Reader verwendet; dieses Mal werden wir einen CSV-Writer einsetzen.

Die Klasse benötigt ähnliche Argumente wie der CSV-Reader.

class CSVWriter
{
protected:
   int    m_handle;
   string m_delimiter;
   char   m_quote;
   char   m_escape;
   bool   m_doublequote;

   string EscapeField(const string value);

public:

   CSVWriter(CFile &file,
             const string delimiter = ",",
             const char quotechar = '"',
             const char escapechar = '\\',
             const bool doublequote = true);

   bool writeRow(const string &row[]);
};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSVWriter::CSVWriter(CFile &file,
                     const string delimiter,
                     const char quotechar,
                     const char escapechar,
                     const bool doublequote)
{
   m_handle      = file.getHandle();
   m_delimiter   = delimiter;
   m_quote       = quotechar;
   m_escape      = escapechar;
   m_doublequote = doublequote;
}

Wir müssen alle erhaltenen Einträge (Felder) entschlüsseln, bevor wir sie sicher in eine CSV-Datei schreiben.

string CSVWriter::EscapeField(const string value)
{
   bool must_quote = false;
   string out = "";

   int len = StringLen(value);
   for(int i = 0; i < len; i++)
   {
      char ch = (char)StringGetCharacter(value, i);

      // Detect if quoting is needed
      if(ch == m_quote ||
         ch == '\n' ||
         ch == '\r' ||
         CharToString(ch) == m_delimiter)
      {
         must_quote = true;
      }

      // Quote escaping
      if(ch == m_quote)
      {
         if(m_doublequote)
            out += CharToString(m_quote) + CharToString(m_quote); // ""
         else
            out += CharToString(m_escape) + CharToString(m_quote); // \"
      }
      else
      {
         out += CharToString(ch);
      }
   }

   if(must_quote)
      return CharToString(m_quote) + out + CharToString(m_quote);

   return out;
}

Die Funktion writeRow ist für das Schreiben von Werten in eine CSV-Datei zuständig.

bool CSVWriter::writeRow(const string &row[])
{
   string line = "";
   int cols = ArraySize(row);

   for(int i = 0; i < cols; i++)
   {
      if(i > 0)
         line += m_delimiter;

      line += EscapeField(row[i]);
   }

   FileWriteString(m_handle, "\n"+line);
   return true;
}

Lassen Sie uns versuchen, neue Zeilen in die Datei mydata.csv einzufügen.

void OnStart()
  {
   CFile f = CFileIO::open("mydata.csv","w+a");
   CSVWriter writer(f, ",");
   
   double open = iOpen(Symbol(), Period(), 0);
   double high = iHigh(Symbol(), Period(), 0);
   double low = iLow(Symbol(), Period(), 0);
   double close = iClose(Symbol(), Period(), 0);
   
   string row[] = {string(TimeCurrent()), (string)open, (string)high, (string)low, (string)close};
   
   writer.writeRow(row);
   f.close();
  }

Ausgabe:


Zusätzliche Methoden

Es gibt mehrere nützliche Methoden in Pythons integriertem E/A-Mechanismus, die das Lesen und Schreiben mühelos machen.

Übrigens sind nicht alle Methoden, die hier und im gesamten Artikel aufgeführt sind, von Python übernommen worden; einige sind von der MQL5-Sprache selbst inspiriert.

01: Die Methode read()

Diese Methode wird verwendet, um alles, was in einer Datei steht, als Zeichenkette zu lesen.

with open(f"{files_path}\\readme.txt", "r") as file:
    print(file.read())    

Ausgabe:

(venv) python main.py
hello, this is a readme file with a plenty of information to read from.

This is a third line after a space.

In MQL5 lesen wir alle Daten aus einer Textdatei (standardmäßig) und fügen die Werte in eine große Zeichenkette (string) ein, die durch einen neuen Zeilencode („\n“) getrennt ist.

string CFile::read(int size = -1)
  {
   if(!isHandleOk(__FUNCTION__))
      return "";

//---

   string result = "";
   if(size < 0) // read entire file
     {
      while(!FileIsEnding(m_handle))
        {
         result += FileReadString(m_handle);

         if(FileIsLineEnding(m_handle))
            result += "\n";
        }
     }
   else
     {
      result = FileReadString(m_handle, size);
     }

   return result; //but not here
  }

Beispiel für die Verwendung:

void OnStart()
  {
   CFile f = CFileIO::open("readme.txt", "rt");
   Print(f.read());
   
   f.close();
 }

Ausgabe:

NQ      0       08:32:45.949    Test file IO (EURUSD,H1)        hello, this is a readme file with a plenty of information to read from.
DQ      0       08:32:45.949    Test file IO (EURUSD,H1)        
GJ      0       08:32:45.949    Test file IO (EURUSD,H1)        This is a third line after a space.

02: Die Methode tell()

Diese Funktion gibt die aktuelle Position des Dateideskriptors in Bytes vom Anfang der Datei zurück.

int CFile::tell()
  {
   if(!isHandleOk(__FUNCTION__))
      return -1;

   return (int)FileTell(m_handle);
  }

03: Die Methode flush()

Schreibt alle im Puffer der Ein- und Ausgabedatei verbleibenden Daten auf eine Festplatte.

void CFile::flush()
  {
   if(!isHandleOk(__FUNCTION__))
      return;

   FileFlush(m_handle);
  }

04: Die Methode seek()

Die Funktion verschiebt die Position des Dateizeigers um eine bestimmte Anzahl von Bytes relativ zur angegebenen Position.

void CFile::seek(const long offset, const ENUM_FILE_POSITION origin)
  {
   //--- check handle
   if (!isHandleOk(__FUNCTION__))
     return;
   
   FileSeek(m_handle,offset,origin);
  }

05: Prüfung, ob die Datei lesbar und beschreibbar ist

Diese beiden kleinen Funktionen können uns helfen, bevor wir uns entscheiden, einige Informationen aus und in die Dateien zu lesen oder zu schreiben.

Bei isreadable() wird geprüft, ob das Flag FILE_READ unter den Datei-Flags vorhanden ist.

bool              isreadable() { return (m_flags & FILE_READ) != 0; }

Bei iswritable() wird geprüft, ob das Flag FILE_WRITE unter den Datei-Flags vorhanden ist. 

bool              iswritable() { return (m_flags & FILE_WRITE) != 0; }

Beispiel für die Verwendung:

void OnStart()
  {
    CFile f = CFileIO::open("readme.txt", "r"); //open the file in read-only mode
    
    printf("Reading a text file line by line....");
    string text;
    while(f.readline(text))
       Print(text);
    
    Print("is writable: ", f.iswritable());
    Print("is readable: ", f.isreadable());  
    
    f.close(); //closing after you are done with it
  }

Ausgabe:

CG      0       16:54:21.159    Test file IO (EURUSD,H1)        is writable: false
FQ      0       16:54:21.159    Test file IO (EURUSD,H1)        is readable: true


Abschließende Überlegungen

Datei-E/A-Operationen müssen nicht immer so kompliziert sein, wie sie in MQL5 oft erscheinen. Dieser Artikel zeigt, dass es mit einer sorgfältigen Abstraktion und einem klaren Designziel möglich ist, einen sauberen, zuverlässigen und Python-ähnlichen Ansatz zum Lesen von und Schreiben in Dateien zu entwickeln und dabei die Einschränkungen der MetaTrader 5-Umgebung zu respektieren.

Wir haben die Grundlagen der Dateieingabe und -ausgabe für gängige Anwendungsfälle wie Text- und CSV-Dateien behandelt, Dateimodi, Kodierungsüberlegungen, das Verhalten beim Anhängen und das sichere Lese-/Schreibmuster untersucht und gezeigt, wie Konstrukte auf höherer Ebene über die nativen MQL5-Dateifunktionen gelegt werden können. Indem wir diese Details in einem wiederverwendbaren Modul kapseln, verringern wir die Anzahl der Standardformulare, minimieren häufige Fehler und erleichtern es, die Dateivorgänge zu verstehen und zu pflegen.

Mit freundlichen Grüßen.


Tabelle der Anhänge

DateinameBeschreibung und Verwendung
Include\PyMQL5\fileIO\fileIO.mqhEnthält sowohl CFile- als auch CFileIO-Klassen für die Arbeit mit allen Dateitypen in MetaTrader 5.
Include\PyMQL$\fileIO\csv.mqhEs hat zwei Klassen, CSVReader und CSVWriter, zum Lesen bzw. Schreiben von CSV-Dateien.
Test file IO.mq5Ein endgültiges Skript (Spielwiese) für alle in diesem Artikel besprochenen Methoden.
Files\* Sie enthält alle Dateien, die wir zum Testen unseres Codes benötigen.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20695

Beigefügte Dateien |
Attachments.zip (22.35 KB)
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen
In diesem Artikel erweitern wir den Indikator auf Basis von Messuhren in MQL5, um mehrere Oszillatoren zu unterstützen und dem Nutzer die Auswahl durch eine Enumeration für einzelne oder kombinierte Anzeigen zu ermöglichen. Wir führen sektorale und runde Messuhren-Stile über abgeleitete Klassen eines Basis-Messuhren-Systems ein und verbessern die Falldarstellung mit Bögen, Linien und Polygonen für ein verfeinertes visuelles Erscheinungsbild.
Larry Williams Marktgeheimnisse (Teil 1): Aufbau eines Swing-Struktur-Indikators in MQL5 Larry Williams Marktgeheimnisse (Teil 1): Aufbau eines Swing-Struktur-Indikators in MQL5
Ein praktischer Leitfaden zum Aufbau eines Marktstrukturindikators im Stil von Larry Williams in MQL5, der die Einrichtung von Puffern, die Erkennung von Umkehrpunkten (swing-points), die Konfiguration von Darstellungen und die Anwendung des Indikators in der technischen Marktanalyse durch Händler umfasst.
Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur
Lernen Sie, wie Sie die Marktstrukturkonzepte von Larry Williams in MQL5 automatisieren können, indem Sie einen vollständigen Expert Advisor erstellen, der Umkehrpunkte liest, Handelssignale erzeugt, das Risiko verwaltet und eine dynamische Trailing-Stop-Strategie anwendet.
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 2): Bau eines RSI-Displays im Stil einer Messuhr mit Leinwand und Nadelmechanik Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 2): Bau eines RSI-Displays im Stil einer Messuhr mit Leinwand und Nadelmechanik
In diesem Artikel entwickeln wir einen RSI-Indikator in MQL5, der die Werte des Relative-Strength-Index auf einer kreisförmigen Skalierung mit einer dynamischen Nadel, farbcodierten Bereichen für überkaufte und überverkaufte Niveaus und einer anpassbaren Legende visualisiert. Wir verwenden die Canvas-Klasse zum Zeichnen von Elementen wie Bögen, Skalenstrichen und Tortendiagrammen, um eine reibungslose Aktualisierung bei neuen RSI-Daten zu gewährleisten.