Внедрение в MQL5 практических модулей из других языков (Часть 06): Операции файлового ввода-вывода в MQL5, как в Python
Разделы
- Введение
- Понимание функции для операций ввода-вывода в Python
- Автоматический выбор флагов файла
- MQL5-метод Open, аналогичный такому же методу в Python
- Чтение данных/информации из файлов
- Запись данных/информации в файлы
- Дополнительные методы
- Заключение
Введение
Файловые операции играют важную роль для любого языка программирования. Они помогают нашим программам взаимодействовать с внешними файлами посредством кода, помогая нам импортировать и экспортировать фрагменты информации. В современном программном обеспечении доступны сотни, если не тысячи, типов файлов, поэтому нам необходимы более совершенные и эффективные способы обработки (чтения и записи) информации в эти файлы и из них.

Язык программирования MQL5 содержит множество встроенных способов чтения и записи для бесчисленного количества типов файлов, но их не всегда достаточно.
В отличие от MQL5, где операции с файлами более явные и основаны на флагах, это может сделать простые и очень распространенные задачи, такие как чтение CSV-файлов, сложными и подверженными ошибкам. В языке программирования Python файловый ввод-вывод прост и очень гибок благодаря богатой стандартной библиотеке, которая упрощает многие низкоуровневые детали, с которыми приходится сталкиваться разработчикам MQL5. См. пример ниже, демонстрирующий чтение одного и того же текстового файла в MQL5 и в Python:
В 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); } }
В Python:
with open(f"{files_path}\\readme.txt", "r") as file: for line in file: print(line.rstrip())
Чтение того же самого файла в Python было простым и гораздо более эффективным, предоставляя пользователям контроль над строками, полученными из файла, в отличие от MQL5.
В этой статье мы рассмотрим, как работает файловый ввод-вывод в MQL5 по сравнению с Python и как можно проектировать высокоуровневые (аналогичные предлагаемым в языке Python) абстракции поверх нативного API. Цель состоит в том, чтобы предложить простой, но эффективный и более безопасный подход к операциям ввода-вывода в языке программирования MQL5.
Понимание функции для операций ввода-вывода в Python
Для создания MQL5-функции для операций ввода-вывода, как в Python, нам необходимо понять внутреннюю работу функции с именем open.
Встроенная функция open() в языке Python используется для открытия файла и возврата соответствующего файлового объекта. Эта функция позволяет считывать данные из файлов и записывать их в файлы, предлагая различные варианты режимов отображения файлов (например, текстовый или бинарный) и кодировки.
Сигнатура функции.
open( file, mode="r", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None )
Аргументы.
| Аргумент | Описание | Значение по умолчанию |
|---|---|---|
| file | Объект, подобный пути и содержащий путь к файлу, который необходимо открыть. | Необходимый. |
| mode | Строка для указания режима открытия файла (например, 'r', 'w', 'b' и т. д.). | 'r'. |
| buffering | Целочисленное значение, используемое для настройки правил буферизации. | -1 |
| encoding | Название метода кодирования, используемого для кодирования или декодирования файла. | Нет. |
| newline | Строка, определяющая способ обработки новых символов из потока. | Нет. |
| closefd | Логическое значение, определяющее, следует ли закрывать хэндл файла. | Верно. |
| opener | Вызываемая функция, используемая в качестве пользовательского средства открытия целевого файла. | Нет. |
В нашей соответствующей функции MQL5 есть пара переменных, которые могут оказаться полезными.
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);
Дополнительные переменные, такие как common (для выбора, находится ли файл в каталоге common или в пути к данным MQL5), и переменная is_unicode (для выбора, является файл юникодным, то есть содержит строки типа UNICODE (двухбайтовые символы) при значении true или содержит строки типа ANSI (однобайтовые символы) при значении false).
Наиболее интересный аргумент функции open является mode.
Режим работы с файлом указывает языку Python, какие операции (чтение, запись и т. д.) вы хотите выполнить с файлом.
| Режим | Описание |
|---|---|
| 'r' | Только чтение. Вызывает ошибку ввода-вывода, если файл не существует. |
| 'r+' | Чтение и запись. Вызывает ошибку ввода-вывода, если файл не существует. |
| 'w' | Только запись. Перезаписывает файл, если он существует; в противном случае создает новый. |
| 'w+' | Чтение и запись. Перезаписывает файл или создает новый. |
| 'a' | Только добавление. Добавляет данные в конец. Создает файл, если он не существует. |
| 'a+' | Чтение и добавление. Указатель в конце. Создает файл, если он не существует. |
| 'rb' | Чтение в бинарном режиме. Файл должен существовать. |
| 'rb+' | Чтение и запись в бинарном режиме. Файл должен существовать. |
| 'wb' | Запись в бинарном режиме. Перезаписывает или создает новый файл. |
| 'wb+' | Чтение и запись в бинарном режиме. Перезаписывает или создает новый файл. |
| 'ab' | Добавление в бинарном режиме. Создает файл, если он не существует. |
| 'ab+' | Чтение и добавление данных в бинарном режиме. Создает файл, если он не существует. |
Теперь, чтобы наша функция MQL5 вела себя аналогично своей версии в языке Python при открытии любого файла независимо от его типа, нам нужна функция, которая поможет нам автоматически генерировать флаги в зависимости от режима файла.
Автоматический выбор флагов файла
Поскольку встроенная функция MQL5 FileOpen в значительной степени полагается на так называемые флаги файлов, нам нужен способ автоматического создания этих файлов в соответствии с указанными выше режимами файлов.
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; }
Переменная is_append полезна для вызова метода FileSeek при добавлении информации в конец файла.
Обратите внимание, что у нас есть FILE_SHARE_READ всякий раз при наличии флага FILE_READ и FILE_SHARE_WRITE — всякий раз, когда присутствует флаг FILE_WRITE.
Это делается для того, чтобы закрепить процесс чтения и записи в файл, используемый другими программами.
Чтобы сделать этот процесс еще эффективнее, можем добавить необязательную переменную shared_IO (если ее значение установлено как true, это означает, что мы можем выполнять операции ввода-вывода с файлами, используемыми другими программами, и другие программы могут делать то же самое, если файл открыт в MetaTrader 5).
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; }
Значение передается непосредственно из функции с именем open.
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);
MQL5-метод Open, аналогичный такому же методу в Python
Используя сгенерированные в зависимости от режима файла флаги, мы теперь можем открыть любой доступный файл.
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; }
Недостаточно генерировать флаги в зависимости от режима файла; к основным нам необходимо добавить еще несколько очень полезных флагов. Вот где пригодятся эти флаги:
01. Определение местоположения файла (либо в пути передачи данных MQL5, либо в общей папке)
if(common) flags |= FILE_COMMON;
02. Принятие решения о необходимости использовать кодировку ANSI или UNICODE для чтения байтовых символов.
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; }
Похоже, что при наличии бинарного флага FILE_BIN язык MQL5 обрабатывает файл как поток байтов; нет необходимости особо беспокоиться о флагах UNICODE и ANSI.
Теперь мы можем использовать эту универсальную функцию для открытия файлов различных типов в MetaTrader 5.
#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 }
Все файлы были успешно открыты в MetaTrader 5, поскольку в терминале не отображалось никаких ошибок, что и ожидалось в случае сбоя функции.
Функция open возвращает хэндл файла, который она открыла. Вы по-прежнему можете управлять файлами, используя встроенные функции MQL5, включая закрытие файла после завершения его использования.
Однако возвращение хэндла означает, что нам все равно приходится управлять им вручную; в идеале было бы, если бы он возвращал класс, содержащий все свойства и методы конкретного файла.
Класс (объект) CFile
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(); };
Теперь у нас должен быть удобный способ обработки и манипулирования открытым файлом.
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(); }
Чтение данных/информации из файлов
Самое замечательное в файловых операциях в Python — это то, что они предоставляют пользователям контроль над чтением и интерпретацией информации, получаемой из файлов.
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)
Результаты.
hello, this is a readme file with plenty of information to read from. Это третья строка после пробела. ['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 также предоставляет нам способ отслеживать информацию по строкам файла внутри цикла while, который проходит по всем строкам файла, код на Python представляется гораздо более удобным. Реализуем аналогичную функциональность в MQL5.
Поскольку в MQL5 имеется несколько функций для чтения данных из файлов, таких как FileReadString, FileReadDouble, FileReadLong и т. д., мы можем использовать шаблон, чтобы функция работала со всеми поддерживаемыми типами данных, что избавляет пользователя от необходимости вручную подбирать функцию чтения для типа передаваемой по ссылке переменной, поскольку они будут получать тип результирующих данных в зависимости от типа такой переменной.
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; }
Затем эта функция может быть унаследована в публичной функции под названием readline.
template <typename T> bool CFile::readline(T &line) { if(!isHandleOk(__FUNCTION__)) return false; //--- while(!FileIsEnding(m_handle)) { line = __readline__<T>(); return true; } return false; }
Пример использования:
#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 }
Результаты.
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.
Эта функция даже способна читать бинарные файлы.
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(); }
Результаты.
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
Эта функция (readline) прекрасно работает с различными типами файлов. При работе с CSV-файлами нам необходимы специальные функции для анализа строк и безопасного извлечения содержимого из всех строк.
В языке Python есть небольшой модуль csv, который отвечает за чтение и запись в CSV-файлы соответственно.
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)
Результаты.
['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']
Обратили внимание, как Python считывает все значения из каждой строки CSV-файла как строковые данные?
Это замечательно, потому что из всех переменных строковые безопаснее всего преобразовывать в другие переменные, не говоря уже о том, что CSV-файлы обычно содержат данные разных типов. Отличная идея — хранить их все вместе в массиве, отформатированном в виде строки.
Мы можем создать аналогичный класс в MQL5.
#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[]); };
Чтобы предотвратить открытие некоторых больших файлов, мы можем добавить ряд проверок.
Проверяем, превышает ли размер файла какой-либо заранее определенный размер.
#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; }
Кроме того, мы проверяем, достаточно ли памяти для хранения файла, который мы пытаемся открыть.
//--- 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; }
Все эти проверки находятся внутри конструктора класса.
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; } }
Аргументы конструктора
| Аргумент | Описание | По умолчанию |
|---|---|---|
| csv_handle | Действительный хэндл CSV-файла, возвращаемый функцией FileOpen(). Речь идет об уже открытом CSV-файле. Программа считывания работает непосредственно с этим хэндлом и не управляет открытием или закрытием файла. | Req |
| delimiter | Строковый символ, используемый для разделения полей внутри строки. К распространенным значениям относятся запятая (,), точка с запятой (;) и знак табуляции (\t). | "," |
| quotechar | Символ, используемый для полей с кавычками, содержащих разделители или специальные символы. Все, что находится внутри совпадающих символов котировок, рассматривается как литеральные данные. | ' " ' |
| escapechar | Символ, используемый во избежание специальных символов внутри заключенного в кавычки поля. Например, символ \" позволяет разместить символ кавычек внутри значения, заключенного в кавычки. | '\\' |
| doublequote | Этот символ управляет обработкой кавычек внутри полей, заключенных в кавычки. При значении true. Два последовательных символа кавычек ("") интерпретируются как одна литеральная кавычка, что соответствует стандартному поведению CSV-файлов. | "" |
| skipinitialspace | Если эта функция включена, следующие непосредственно за разделителем пробелы игнорируются. Это полезно для анализа CSV-файлов с нестрогим форматированием, таких как "A,B,C" вместо "A, B, C". | false |
После открытия CSV-файла мы создадим объект CSVReader и присвоим его переменной с именем reader. Затем создадим массив с именем row[], и все строки из CSV-файла будут итеративно сохраняться в этот массив.
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); }
Результаты.
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"
Запись данных/информации в файлы
Запись данных в файлы требует несколько иного подхода, чем чтение.
Мы можем использовать метод FileWrite, который принимает переменные любого типа данных.
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; }
Попробуем записать новые данные в конец уже существующего файла.
В файловом режиме: символ r предназначен для чтения, + предназначен для чтения и записи и a для вставки (добавления) новой информации в конец файла.
void OnStart() { CFile f = CFileIO::open("readme.txt", "r+a"); f.write("Newly added data | "+string(TimeLocal())); f.close(); }
После нескольких запусков скрипта получился приведенный ниже файл readme.txt с новыми строками данных.
hello, this is a readme file with a plenty of information to read from. Это третья строка после пробела. 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
Метод FileWrite, если ему передана динамическая (шаблонная) переменная, может работать со всеми переменными, кроме переменных массива.
Для записи массивов с данными в файл можно использовать функцию FileWriteArray.
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; }
Несмотря на то что функция FileWriteArray предназначена для бинарных файлов, мы можем принудительно записать массив в текстовый файл.
void OnStart() { CFile f = CFileIO::open("array.txt", "wt"); string data[] = {"data01", "data02", "data03", "data04"}; f.write( data); f.close(); }
Результаты.
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
Получаем сообщение об ошибке, указывающее на то, что наш файл следует открывать как текстовый файл.
Это связано с тем, что, хотя мы и могли читать и записывать данные в текстовые файлы, мы никогда фактически не открывали их с флагом FILE_TXT; у нас пока нет способа обрабатывать это в аргументе file mode.
Для текстовых файлов нам необходимо принимать букву 't', что затем активирует флаг FILE_TXT.
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; }
Таким образом, чтобы обойти ошибки, подобные описанной выше, достаточно указать символ "t" при открытии текстового файла или файла на основе текста.
void OnStart() { CFile f = CFileIO::open("array.txt", "wt"); string data[] = {"data01", "data02", "data03", "data04"}; f.write( data); f.close(); }
Результаты.

Запись в CSV-файл
Поскольку в CSV-файле используется двухмерный подход к хранению данных, нам необходимо по-другому подходить к записи в него новых данных. Для чтения подобных файлов мы использовали программу для чтения CSV-файлов; на этот раз мы воспользуемся программой для записи CSV-файлов.
В классе используются те же аргументы, что и в случае с программой для чтения CSV-файлов.
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; }
Перед безопасной записью в CSV-файл необходимо экранировать все полученные данные (поля).
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; }
Функция writeRow отвечает за запись значений в CSV-файл.
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; }
Попробуем вставить новые строки в файл mydata.csv.
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(); }
Результаты.

Дополнительные методы
Встроенный механизм ввода-вывода в языке Python содержит несколько полезных методов, которые значительно упрощают чтение и запись данных.
Следует отметить, что не все методы, перечисленные здесь и во всей статье, заимствованы из Python; разработка некоторых из них вдохновлена самим языком MQL5.
01. Метод read()
Этот метод используется для чтения всего содержимого файла в виде строки.
with open(f"{files_path}\\readme.txt", "r") as file: print(file.read())
Результаты.
(venv) python main.py
hello, this is a readme file with a plenty of information to read from.
Это третья строка после пробела.В MQL5 мы считываем все данные из текстового файла (по умолчанию), добавляя значения в одну большую строку, разделенную кодом новой строки ("\n").
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 }
Пример использования.
void OnStart() { CFile f = CFileIO::open("readme.txt", "rt"); Print(f.read()); f.close(); }
Результаты.
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. Метод tell()
Эта функция возвращает текущую позицию файлового дескриптора в байтах от начала файла.
int CFile::tell() { if(!isHandleOk(__FUNCTION__)) return -1; return (int)FileTell(m_handle); }
03. Метод flush()
Записывает на диск все данные, оставшиеся в буфере входного/выходного файла.
void CFile::flush() { if(!isHandleOk(__FUNCTION__)) return; FileFlush(m_handle); }
04. Метод seek()
Функция перемещает положение указателя файла на указанное количество байтов относительно заданной позиции.
void CFile::seek(const long offset, const ENUM_FILE_POSITION origin) { //--- check handle if (!isHandleOk(__FUNCTION__)) return; FileSeek(m_handle,offset,origin); }
05. Проверка доступности файла для чтения и записи
Эти две небольшие функции могут помочь нам, прежде чем мы примем решение о чтении какой-либо информации из файлов или ее записи в них.
Для isreadable() проверяем, присутствует ли флаг FILE_READ среди флагов файла.
bool isreadable() { return (m_flags & FILE_READ) != 0; }
Для функции iswritable() мы проверяем, присутствует ли флаг FILE_WRITE среди флагов файла.
bool iswritable() { return (m_flags & FILE_WRITE) != 0; }
Пример использования.
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 }
Результаты.
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
Заключительные мысли
Операции ввода-вывода файлов не всегда должны быть такими сложными, какими они часто представляются в языке MQL5. В этой статье показано, что при тщательном абстрагировании и постановке четкой цели проектирования можно создать чистый, надежный и похожий на методы Python подход к чтению и записи файлов, соблюдая при этом ограничения среды MetaTrader 5.
Мы рассмотрели основы файлового ввода-вывода для распространенных сценариев использования, таких как текстовые и CSV-файлы, изучили режимы работы с файлами, особенности кодирования, поведение при добавлении данных и безопасные шаблоны чтения и записи, а также показали, как конструкции более высокого уровня могут быть наложены поверх встроенных функций MQL5 для работы с файлами. Инкапсулируя эти детали в модуль многократного использования, мы сокращаем количество шаблонного кода, сводим к минимуму распространенные ошибки и упрощаем понимание и сопровождение операций с файлами.
С наилучшими пожеланиями.
Таблица вложений
| Имя файла | Описание и использование |
|---|---|
| Include\PyMQL5\fileIO\fileIO.mqh | Содержит классы CFile и CFileIO для работы со всеми типами файлов в MetaTrader 5. |
| Include\PyMQL$\fileIO\csv.mqh | Он содержит два класса, CSVReader и CSVWriter, для чтения и записи CSV-файлов соответственно. |
| Test file IO.mq5 | Итоговый скрипт (тестовая площадка) для всех методов, рассмотренных в этой статье. |
| Files\* | Здесь собраны все файлы, необходимые для тестирования нашего кода. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20695
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка инструментария для анализа Price Action (Часть 27): Инструмент выявления снятия ликвидности с MA-фильтром
Код, слёзы и Algo Forge
Торговые инструменты на MQL5 (Часть 19): Создание интерактивной палитры инструментов графической разметки
Самооптимизирующиеся советники на MQL5 (Часть 12): Построение линейных классификаторов с использованием факторизации матриц
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования