Знакомство с языком MQL5 (Часть 41): Руководство для начинающих по работе с файлами в MQL5 (III)
Введение
Приветствуем вас в Части 41 серии "Знакомство с языком MQL5"! В этой статье мы продолжаем руководство для начинающих по работе с файлами в MQL5 и опираемся на все, что уже изучили. В предыдущих частях мы разобрали, как создавать файлы, записывать в них структурированные данные о сделках и организовывать в читаемом торговом журнале сведения о входе и выходе. На этом этапе вы уже понимаете, как работают файлы в MQL5 и как безопасно хранить торговые данные в формате, который сохраняется между запусками.
В предыдущей статье мы в основном сосредоточились на записи и отправке данных из MetaTrader 5 в файлы. Запись данных – это лишь одна часть процесса. Но что делать, если нужно прочитать содержимое файла и затем использовать его для статистического анализа или торговли? В этой статье вы узнаете, как системно и с практической пользой извлекать сохраненную торговую информацию, считывая данные из файлов в MQL5. Как и в других статьях этой серии, мы будем использовать проектный подход, чтобы сделать обучение более понятным и практичным. Вместо того чтобы изучать чтение файлов в отрыве от практики, мы сразу применим знания в реальной задаче, которая часто возникает у трейдеров и разработчиков.
Проект, к которому мы будем двигаться, – это индикатор, который считывает CSV-файл с вашей торговой историей и использует данные о прибыли и убытках за заданный период, чтобы построить кривую баланса. Такая кривая баланса наглядно показывает, как меняется баланс счета в зависимости от результатов вашей торговли.


Сначала мы сосредоточимся на том, как в MQL5 работает чтение файлов, потому что сразу переходить к разработке всего индикатора для новичков было бы слишком сложно. В центре этой статьи – эффективная организация и корректное чтение данных из файла. Собрав все значения TradeID в один массив, а все значения LotSize – в другой, вы увидите, как разносить связанные значения по отдельным массивам. Затем вы повторите тот же процесс для остальных столбцов.
Если вы внимательно следили за этой серией, то, возможно, вспомните Часть 32, где мы говорили о работе с API и группировке данных, считанных из файла. Тогда главной целью было понять работу API, а не подробно разбирать работу с файлами, и такой подход был сознательно ориентирован на начинающих. Поэтому там использовались более простые решения – статические массивы и отдельные переменные для каждого набора данных. В этой статье, используя динамические массивы, мы перейдем к более гибкому и масштабируемому подходу. Благодаря этому мы сможем хранить столько данных, сколько потребуется, не создавая новую переменную для каждого значения, и тем самым заложим более прочную основу для удобной и эффективной работы с файлами в MQL5.
Подсчет общего количества записей в файле
Прежде чем читать данные из файла, программа должна определить, сколько записей в нем доступно. Это гарантирует безопасный и эффективный доступ к каждой записи и помогает правильно задать границы цикла. Иначе программа может обратиться к несуществующим данным, а это приведет к некорректным или неполным результатам. Поэтому подсчет записей в файле – необходимый первый шаг при любой операции чтения.
И тут возникает закономерный вопрос. Что именно мы считаем в файле? Мы считаем целые предложения, слова или символы? В отличие от человека, который воспринимает CSV-файл как текст, программа видит его иначе. Для нее данные – это отдельные элементы. В таком формате файла переносы строк работают как разделители строк, а каждое значение, отделенное запятой, считается отдельным элементом данных. В Excel CSV-файл выглядит аккуратно и упорядоченно, потому что каждое значение точно попадает в свой столбец.
CSV-файл в Excel выглядит структурированным и упорядоченным: каждый элемент данных точно расположен в своем столбце. Благодаря такому визуальному представлению пользователь может легче и быстрее понять содержимое файла. Но в своей основе это по-прежнему обычный текстовый файл. В отличие от электронных таблиц, где данные показываются как сетка ячеек, MetaTrader считывает их как текст, в котором формат CSV задает разделители между значениями и строками.

Но именно так программа на самом деле видит те же данные внутри CSV-файла:
Account Name: Abioye Israel Pelumi, Account Balance: 7404.68000000, Account Login: 31670702, Start Time:, 2025.11.01 00:00:00, End Time:, 2026.01.28 00:00:00, Last Update:, 2026.02.09 18:23:33, Total Trades:, 68,
Что касается внутреннего процесса, MetaTrader использует собственные разделители для чтения файла, разбирая его по одному элементу за раз – по столбцам и строкам. Каждое значение в столбце представляет отдельный элемент данных, а каждая новая строка образует отдельную запись. Такой подход показывает, что система подсчитывает структурированные ячейки по строкам и столбцам, а не длину текста или количество слов.
По сути, при подсчете элементов файла считаются все пригодные ячейки с данными, включая ячейки заголовков и неторговые данные, такие как сведения о счете. Даже пустая строка все равно считается одним элементом, пусть в ней и не видно данных; при чтении файла это увеличивает общее число элементов на единицу.

Это различие важно понимать. То, что пользователь видит как аккуратные строки и столбцы, для программы на самом деле представляет собой последовательность элементов. Если мы точно подсчитаем эти элементы, то сможем безопасно пройти по файлу в цикле, выделить динамические массивы нужного размера и извлечь только ту информацию, которая нам нужна, например, статистику прибыли и убытков по сделкам. Этот этап помогает сохранить точность, предотвращая ошибки выхода за пределы массива при обработке файла для последующего построения кривой баланса.
Помните, мы раньше говорили о FileOpen? Это первый этап любой работы с файлами. Ваша программа использует хэндл файла, который возвращает эта функция, как уникальный идентификатор для работы с файлом. Чтобы указать, нужно ли открыть файл для чтения, записи или другой цели, необходимо одновременно выбрать правильный режим доступа.
Пример://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string Filename = "Trading_Journal.csv"; int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ','); if(file_handle == INVALID_HANDLE) { Print("Failed to open file. Error: ", GetLastError()); } else if(file_handle != INVALID_HANDLE) { FileClose(file_handle); } }
Пояснение:
Сначала была создана строковая переменная с именем Filename, и ей было присвоено значение "Trading_Journal.csv". Ее задача – хранить имя файла, к которому будет обращаться программа. Такой подход делает код более упорядоченным и гарантирует, что при необходимости имя файла можно будет легко изменить. После того как имя файла задано, мы вызываем FileOpen и присваиваем переменной file_handle возвращаемое значение. Хэндл файла, представленный возвращаемым целочисленным значением, дает файлу уникальный идентификатор внутри программы. Программа использует этот хэндл каждый раз, когда читает файл. Если при вызове FileOpen задан флаг FILE_READ, это дает программе право просматривать файл и читать его содержимое. В этом случае флаг FILE_WRITE не используется, потому что никаких изменений или обновлений не требуется. Это снижает вероятность случайно изменить уже сохраненные данные.
Так мы сообщаем MetaTrader, что файл содержит столбцовые данные в формате CSV. Каждое значение отделяется заданной запятой, а параметр FILE_ANSI задает стандарт кодировки символов. Вместе эти настройки гарантируют, что файл будет успешно открыт и обработан так, как задумано. После попытки открыть файл важно проверить, прошла ли операция успешно. Затем проверяется, не равно ли значение переменной file_handle недопустимому значению. Если вызов FileOpen завершается неудачно, корректное значение хэндла не возвращается. Вместо этого функция возвращает недопустимый хэндл, показывая, что попытка прочитать файл не удалась.
Если файл не удается открыть, программа выводит сообщение об ошибке и связанный с ней идентификатор проблемы. Этот идентификатор помогает понять причину ошибки – ограничение доступа, использование файла другой программой или его отсутствие. Такие диагностические данные полезны, потому что подсказывают правильное направление для решения проблемы. Когда файл успешно открыт, программа может безопасно продолжить работу. Поскольку на этом этапе нужно лишь проверить доступность файла, после этого он закрывается. Это помогает освободить системные ресурсы и гарантирует, что файл не останется в открытом виде и будет доступен для дальнейшей работы.
Аналогия:
Сначала это похоже на ситуацию, когда вы пишете на бланке запроса название нужной книги, объявляя переменную Filename и присваивая ей значение "Trading_Journal.csv". Вы просто записываете название в определенном месте, а не повторяете его вслух на каждом шаге. Если название книги изменится, достаточно будет обновить только этот бланк, а остальное обновлять не нужно. Далее сравним использование функции FileOpen с тем, как вы запрашиваете книгу в библиотеке. Когда библиотекарь находит книгу, он выдает вам квиток. Этот квиток и есть аналог хэндла файла. Он служит подтверждением того, что именно у вас сейчас есть доступ к этой книге. Каждый раз, когда вы хотите читать эту книгу, вам нужно предъявить этот квиток. Без него доступа к книге не будет.
Когда мы указываем FILE_READ, это все равно что сказать библиотекарю: "Я хочу только читать эту книгу прямо здесь, в библиотеке". Вы не просите разрешения менять ее содержимое или оставлять в ней пометки. Поэтому FILE_WRITE здесь не используется. Мы ничего не меняем – мы только читаем. Это защищает исходное содержимое от непреднамеренных изменений. Флаг FILE_CSV можно сравнить с тем, как если бы вы сообщили библиотекарю, что книга организована в табличном виде – по строкам и столбцам. Флаг FILE_ANSI указывает, в какой кодировке записан текст, а библиотекарь благодаря этому понимает, как правильно передать вам содержимое книги. Запятая-разделитель словно подсказывает, что каждый элемент информации в книге отделен определенным символом.
Всегда проверяйте, нашел ли библиотекарь нужную вам книгу. Если библиотекарь говорит, что найти ее не удалось, значит, возникла проблема. У вас может не быть доступа к книге: она может быть в данный момент выдана кому-то другому или вовсе не существовать. Если выяснить причину, станет понятнее, как решить проблему. Если же библиотекарь выдает вам квиток, значит, все в порядке. Теперь вы можете читать книгу. Когда закончите, вы вернете квиток и сдадите книгу обратно. Именно это и делает FileClose. Помимо освобождения ресурсов, это сообщает системе, что вы закончили работу, и книга больше не должна оставаться закрепленной за вами.
Теперь, когда файл открыт, следующим шагом мы определим общее количество элементов – это значительно упростит дальнейшее чтение и обработку файла.
Пример:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string Filename = "Trading_Journal.csv"; int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ','); if(file_handle == INVALID_HANDLE) { Print("Failed to open file. Error: ", GetLastError()); } else if(file_handle != INVALID_HANDLE) { int total_elements_count = 0; while(!FileIsEnding(file_handle)) { FileReadString(file_handle); total_elements_count++; } Print(total_elements_count); FileClose(file_handle); } }
Пояснение:
Первым шагом нужно объявить переменную с именем total_elements_count и присвоить ей значение 0. Эта переменная нужна для отслеживания общего количества элементов, прочитанных из файла. Поскольку программа еще не прочитала ни одного элемента, начальное значение равно 0. По мере того как программа считывает элементы из файла, значение этой переменной будет постепенно увеличиваться и показывать общее количество. Пока в файле остаются данные, цикл продолжает чтение. Чтобы понять, остались ли еще данные, специальная функция проверяет текущее положение указателя в файле. Если данные еще есть, цикл продолжается; когда файл заканчивается, функция дает сигнал остановиться.
Внутри цикла программа считывает следующий элемент из файла. Эта функция не только получает данные в строковом формате, но и автоматически сдвигает указатель файла вперед. Поскольку файл организован в формате CSV, каждый вызов обычно считывает один элемент, отделенный заданным разделителем. Использование этого метода гарантирует, что программа будет обрабатывать каждый элемент по отдельности, а не пытаться прочитать весь файл сразу. После чтения каждого элемента программа увеличивает значение total_elements_count на единицу. Этот шаг важен, потому что именно здесь отслеживается количество элементов, которые удалось успешно прочитать. Каждый раз, когда считывается новый элемент, счетчик увеличивается и отражает текущее общее количество.
Когда цикл завершается, это значит, что программа дошла до конца файла и подсчитала все доступные элементы. Последним шагом будет вывести значение total_elements_count. Этот вывод подтверждает, что чтение прошло успешно, и показывает общее количество элементов, содержащихся в файле.
Аналогия:
Представьте, что вы входите в библиотеку с блокнотом и хотите определить, сколько книг стоит на конкретной полке. Сначала вы записываете 0 вверху страницы блокнота. Этот ноль показывает, сколько книг вы насчитали к этому моменту. Поскольку пока еще ничего не подсчитано, ноль здесь вполне логичен. Это эквивалентно объявлению переменной total_elements_count и присвоению ей значения 0. С этого вы и начинаете.
Затем вы начинаете двигаться вдоль полок слева направо. Прежде чем взять следующую книгу, вы каждый раз смотрите на полку, чтобы понять, остались ли там еще книги. Если книги еще есть, вы переходите к следующей. Если полка пуста и книг больше не осталось, вы прекращаете проверку. Именно это и делает FileIsEnding. Это похоже на проверку того, дошли ли вы до конца полки. Если книги еще есть, функция как бы отвечает: "Нет, вы еще не дошли до конца", – и вы продолжаете считать. Если книг больше нет, вы останавливаетесь, когда слышите: "Вы дошли до конца".
Теперь, прежде чем взять книгу и двинуться дальше вдоль полки, вы проверяете, что она действительно там есть. Эта процедура похожа на чтение файла элемент за элементом: при каждом чтении вы получаете один элемент данных, после чего процесс автоматически переходит дальше. Столбцовая структура файла, похожая на формат CSV, позволяет обрабатывать элементы последовательно. Рассматривая каждую книгу, вы увеличиваете текущее число в блокноте на единицу. Счет идет от 0 к 1, затем к 2, затем к 3 и т.д. Здесь лишь отслеживается количество встреченных объектов – так же как счетчик увеличивается каждый раз, когда обрабатывается новый элемент.
В конце концов вы доходите до конца полки. Книги больше считать не нужно. В этот момент вы останавливаетесь и смотрите на число в блокноте. Эта последняя цифра и показывает точное количество книг на полке. Аналогичным образом, программа выводит total_elements_count в конце цикла. Это число показывает, сколько элементов всего содержится в файле, и подтверждает, что подсчет прошел успешно.
Группировка всех элементов файла в единый динамический массив
Когда общее количество элементов уже определено, мы проходим по каждому из них и сохраняем все в один динамический массив. Программа может быстро и последовательно получать любые данные, потому что каждый элемент индексируется по своему положению в файле. Динамические массивы особенно удобны для файлов разного размера, потому что могут расширяться так, чтобы вместить столько элементов, сколько потребуется. После того как элементы помещены в массив, вы можете быстро получать нужные данные, например TradeID, LotSize или любой другой столбец торгового журнала, обращаясь к элементам по индексу.
Пример:
if(file_handle == INVALID_HANDLE) { Print("Failed to open file. Error: ", GetLastError()); } else if(file_handle != INVALID_HANDLE) { int total_elements_count = 0; while(!FileIsEnding(file_handle)) { FileReadString(file_handle); total_elements_count++; } // Print(total_elements_count); FileSeek(file_handle, 0, SEEK_SET); string TotalElements[]; ArrayResize(TotalElements,total_elements_count); for(int i = 0; i < total_elements_count; i++) { TotalElements[i] = FileReadString(file_handle); } Print(TotalElements[1]); FileClose(file_handle); }
Пояснение:
Чтобы можно было прочитать каждый элемент, сначала мы возвращаем указатель файла в начало. Если пропустить этот шаг, после подсчета указатель останется в конце; следовательно, прочитать уже ничего не получится. Это похоже на перемотку кассеты перед повторным воспроизведением, если вы хотите увидеть все с самого начала. После этого программа использует цикл for, чтобы пройти по каждому элементу. Начиная с первого элемента, то есть с 0, цикл продолжается, пока не будет достигнуто общее количество подсчитанных элементов. При таком подходе каждый элемент файла обрабатывается по отдельности и последовательно.
На каждой итерации цикла программа считывает следующий доступный элемент и записывает его в массив по текущему индексу. Затем процедура автоматически переходит к следующему элементу для следующей итерации цикла. Благодаря тому что элементы сохраняются в динамическом массиве, к любому из них впоследствии можно будет обратиться системно и эффективно. На каждой итерации цикла программа берет из файла один элемент и добавляет его в массив по соответствующему индексу. Независимо от того, заголовок это, текст или число, каждый элемент обрабатывается по отдельности. Чтобы в дальнейшем не возникало повторений, после каждого шага механизм чтения автоматически переходит к следующему элементу.
Программа упорядочивает данные, отводя каждому считанному элементу отдельное место в динамическом массиве. Это позволит впоследствии обратиться к любому элементу по его индексу. Например, первый элемент хранится по индексу 0, второй – по индексу 1 и т.д. Динамический массив удобен тем, что не нужно заранее гадать, сколько элементов будет в файле. После того как размер массива изменен так, чтобы вместить все элементы, в нем можно безопасно сохранить все данные. Это гарантирует, что все данные будут сохранены и останутся доступны для дальнейшего анализа, группировки или обработки. В конце мы выводим второй элемент массива, чтобы убедиться, что элементы сохранены правильно. Такая быстрая проверка показывает, что индексация работает корректно, а содержимое файла сохранилось в массиве именно так, как ожидалось.
Аналогия:
Мы начинаем с использования FileSeek, чтобы переместить указатель файла в начало. Это похоже на перемотку VHS-кассеты или аудиокассеты перед повторным воспроизведением. Без этого шага ничего не прочитается, потому что после подсчета всех элементов указатель остается в конце. FileSeek гарантирует, что чтение начнется с первого элемента файла. Затем программа использует цикл for, чтобы пройти по каждому элементу. Представьте конвейерную ленту, по которой коробки движутся одна за другой. Начиная с первой коробки, цикл постепенно проходит до последней. Каждая коробка представляет один элемент файла. Чтобы не пропустить ни одного элемента, программа проходит по каждой из коробок в отдельности.
В конце каждой итерации цикла считывается следующий элемент файла и добавляется в массив по соответствующему индексу. Представьте, что вы снимаете товары с конвейера и раскладываете их по подписанным контейнерам. Первый предмет попадает в контейнер с номером 0, а каждый следующий предмет – в следующий по номеру контейнер. Когда конвейер сам движется вперед, следующий предмет автоматически подготавливается к следующей итерации. Сохраняя элементы в динамическом массиве, программа назначает каждому из них свое место. Это похоже на нумерацию полок на складе, чтобы потом можно было легко найти любой предмет. Использование динамического массива избавляет от необходимости заранее оценивать количество элементов, то есть это похоже на склад, который может расширяться и вмещать столько товаров, сколько у вас есть.
Группировка данных файла по столбцам в динамические массивы
После того как мы извлечем все нужные элементы файла в один динамический массив, в этом разделе перейдем к следующему шагу. Хотя хранить все элементы в одном массиве удобно, для обработки и анализа некоторых типов данных, таких как TradeID, LotSize или OpenPrice, это не слишком практично. Чтобы упростить работу с данными, мы разнесем все элементы под одним заголовком столбца по отдельным динамическим массивам. Например, один массив будет содержать все значения TradeID, другой – все значения LotSize и т.д. Этот подход позволяет быстро получать, анализировать и использовать каждый тип данных по отдельности.
Представьте это как организацию картотеки. Сначала все документы могут лежать в одной большой стопке, и из-за этого трудно найти нужные. Для каждой категории, например счетов, квитанций или отчетов, можно завести отдельную папку, чтобы потом легко находить и использовать любой документ. Аналогичным образом, данные файла можно сгруппировать в массивы по столбцам, чтобы у каждого типа информации была своя папка в памяти. Это сделает программу более упорядоченной, эффективной и готовой к дальнейшей обработке и анализу.
Сначала давайте соберем все значения TradeID в одну группу. Каждый элемент этого массива, который станет нашим первым массивом для отдельного столбца, соответствует одному значению TradeID из файла. Отделив TradeID в отдельный динамический массив, мы получим системный способ быстро обращаться к уникальному идентификатору каждой сделки. Прежде всего нужно проверить, содержит ли файл общее количество сделок. После этого нужно найти индекс этого значения в массиве.

Этот шаг важен, потому что общее количество сделок напрямую определяет, сколько значений TradeID будет сохранено в динамическом массиве. Понимание этого числа помогает правильно выделить массив и избежать ошибок, например, когда в него пытаются записать значения TradeID за пределами допустимого диапазона.
Пример:
FileSeek(file_handle, 0, SEEK_SET); string TotalElements[]; ArrayResize(TotalElements,total_elements_count); for(int i = 0; i < total_elements_count; i++) { TotalElements[i] = FileReadString(file_handle); } int total_deals = (int)StringToInteger(TotalElements[10]);
Пояснение:
В разделе выше каждый элемент файла был сохранен в динамический массив строк с именем TotalElements. Поскольку индексация массива начинается с нуля, общее количество сделок находится в одиннадцатом элементе, то есть по индексу 10. После преобразования из строки в целое число это значение присваивается переменной total_deals. Это числовое значение, отражающее общее количество сделок, нужно и для того, чтобы правильно задать размер массива TradeID, и для любых циклов, обрабатывающих сделки.
Аналогия:
Один из способов представить динамический массив TotalElements – это ряд пронумерованных почтовых ящиков, в каждом из которых лежит часть информации из файла. Мы начинаем отсчет с нуля, поэтому общее количество сделок находится в одиннадцатом почтовом ящике, то есть по индексу 10. Число в этом почтовом ящике пока записано как текст на листке бумаги, поэтому использовать его в вычислениях мы еще не можем. StringToInteger используется, чтобы "перевести" этот текст в число, с которым уже можно работать. Это похоже на то, как если бы вы достали листок бумаги из почтового ящика и превратили записанное на нем число в настоящую монету, которую уже можно посчитать или использовать в вычислениях. После того как число преобразовано в целое, мы можем планировать заполнение ячеек TradeID в динамическом массиве, не опасаясь, что места не хватит.
Следующим шагом нужно найти индекс в массиве TotalElements, с которого начинаются значения TradeID. Это важно, поскольку чтобы корректно извлечь все последующие торговые тикеты, нужно знать точное положение первого TradeID. После того как эта начальная позиция определена, мы можем сохранить тикеты для общего количества сделок в одном динамическом массиве типа ulong. Проходя в цикле от начального индекса и заполняя этот массив, мы обеспечиваем, что каждый TradeID из файла будет записан по порядку и будет готов либо для дальнейшей обработки, либо для обратной записи в файл.
Пример:else if(file_handle != INVALID_HANDLE) { int total_elements_count = 0; while(!FileIsEnding(file_handle)) { FileReadString(file_handle); total_elements_count++; } // Print(total_elements_count); FileSeek(file_handle, 0, SEEK_SET); string TotalElements[]; ArrayResize(TotalElements,total_elements_count); for(int i = 0; i < total_elements_count; i++) { TotalElements[i] = FileReadString(file_handle); } int total_deals = (int)StringToInteger(TotalElements[10]); ulong TradeID[]; ArrayResize(TradeID,total_deals); int id_count = 0; for(int i = 24; i < total_elements_count; i += 12) { TradeID[id_count] = (ulong)StringToInteger(TotalElements[i]); id_count++; } ArrayPrint(TradeID); FileClose(file_handle); }
Пояснение:
Сначала объявляется динамический массив типа ulong с именем TradeID. Поскольку номера тикетов в MQL5 могут быть очень большими, их сохраняют в беззнаковом целочисленном типе ulong. Использование ulong позволяет массиву надежно хранить любой возможный номер тикета и исключает переполнение или отрицательные значения. Мы используем динамический массив, потому что заранее не знаем, сколько тикетов потребуется подсчитать. После объявления массива его размер изменяется в соответствии с общим количеством сделок, которое хранится в total_deals. Это предотвращает ошибки, которые могли бы возникнуть, если попытаться добавить больше элементов, чем массив способен вместить. Позицию следующего тикета в массиве TradeID отслеживает переменная id_count, которая начинается с 0. Каждый раз, когда добавляется тикет, счетчик увеличивается и указывает на следующий свободный индекс.
Во время цикла тикеты извлекаются из массива TotalElements, начиная с индекса 24. Такая структура связана с тем, что первые 24 элемента CSV-файла занимают заголовки и данные счета. Первый TradeID находится в 25-м элементе файла, что соответствует индексу 24 в массиве, так как индексация массива начинается с нуля. Каждая сделка в файле представлена 12 отдельными элементами. Поэтому на каждой итерации цикл сдвигается на 12, чтобы попадать точно в начало следующей сделки. Этот этап сохраняет выравнивание чтения относительно структуры файла.

Внутри цикла программа сохраняет элемент по текущему индексу из TotalElements в массив TradeID по позиции, на которую указывает id_count. После сохранения id_count увеличивается на единицу, чтобы следующий тикет занял следующую позицию в массиве. Этот процесс повторяется, пока не будут сохранены все тикеты всех сделок.
Аналогия:
Массив TradeID можно сравнить с рядом пустых ячеек, каждая из которых готова принять один торговый тикет. Подобно уникальному ключу или удостоверению личности, каждая сделка в вашем аккаунте имеет номер тикета, и эти номера могут быть довольно большими. Использование ulong похоже на выбор системы ячеек, которая достаточно велика, чтобы надежно хранить все ключи без переполнения и поломки. Мы делаем ряд ячеек динамическим, чтобы он мог расширяться и вмещать ровно столько ключей, сколько потребуется, поскольку заранее не знаем, сколько у нас будет сделок. Перед тем как начать вставлять ключи в ячейки, мы увеличиваем этот ряд в соответствии с общим количеством сделок. Это похоже на то, как вы заранее измеряете ряд ячеек, чтобы для каждого ключа точно хватило места.
Если мы неправильно рассчитали количество ячеек, часть ключей может просто выпасть – в программировании это равносильно ошибке выхода за пределы диапазона. Переменная id_count служит счетчиком и подсказывает, в какую ячейку нужно положить следующий ключ. Когда мы вставляем ключ, счетчик переходит к следующей ячейке.
Теперь представьте длинную таблицу, в которой данные о сделках расположены по строкам. В каждой строке TradeID находится в первом из 12 отсеков. Поскольку первые 24 отсека заняты заголовками и данными аккаунта, цикл начинается с первого TradeID, который находится на 25-й позиции таблицы. Чтобы перейти к TradeID в следующей строке, мы движемся по таблице, перескакивая каждый раз на 12 отсеков вперед. Каждый встретившийся TradeID мы берем и помещаем в следующий свободный слот массива TradeID. В итоге все тикеты оказываются в своих ячейках. Теперь, зная нужную ячейку, вы можете быстро найти любой тикет.
После того как все значения TradeID собраны в динамический массив, можно переходить к оставшимся столбцам. Symbol, OrderType, LotSize, OpenTime, OpenPrice, StopLoss, TakeProfit, CloseTime, ClosePrice, Profit и Result – это лишь часть столбцов CSV-файла, данные из которых можно извлечь и сохранить в отдельные динамические массивы. Если сгруппировать вместе все элементы одного столбца, порядок сделок сохранится, а получить любой нужный фрагмент данных впоследствии станет гораздо проще.
Пример://symbol string symbol[]; ArrayResize(symbol,total_deals); int sym_count = 0; for(int i = 25; i < total_elements_count; i += 12) { symbol[sym_count] = TotalElements[i]; sym_count++; } //OrderType string OrderType[]; ArrayResize(OrderType,total_deals); int order_type_count = 0; for(int i = 26; i < total_elements_count; i += 12) { OrderType[order_type_count] = TotalElements[i]; order_type_count++; } //LotSize double LotSize[]; ArrayResize(LotSize,total_deals); int lot_count = 0; for(int i = 27; i < total_elements_count; i += 12) { LotSize[lot_count] = StringToDouble(TotalElements[i]); lot_count++; } //OpenTime datetime OpenTime[]; ArrayResize(OpenTime,total_deals); int open_time_count = 0; for(int i = 28; i < total_elements_count; i += 12) { OpenTime[open_time_count] = StringToTime(TotalElements[i]); open_time_count++; } //OpenPrice double OpenPrice[]; ArrayResize(OpenPrice,total_deals); int open_price_count = 0; for(int i = 29; i < total_elements_count; i += 12) { OpenPrice[open_price_count] = StringToDouble(TotalElements[i]); open_price_count++; } //StopLoss double StopLoss[]; ArrayResize(StopLoss,total_deals); int StopLoss_count = 0; for(int i = 30; i < total_elements_count; i += 12) { StopLoss[StopLoss_count] = StringToDouble(TotalElements[i]); StopLoss_count++; } // TakeProfit double TakeProfit[]; ArrayResize(TakeProfit,total_deals); int TakeProfit_count = 0; for(int i = 31; i < total_elements_count; i += 12) { TakeProfit[TakeProfit_count] = StringToDouble(TotalElements[i]); TakeProfit_count++; } //CloseTime datetime CloseTime[]; ArrayResize(CloseTime,total_deals); int close_time_count = 0; for(int i = 32; i < total_elements_count; i += 12) { CloseTime[close_time_count] = StringToTime(TotalElements[i]); close_time_count++; } //ClosePrice double ClosePrice[]; ArrayResize(ClosePrice,total_deals); int close_price_count = 0; for(int i = 33; i < total_elements_count; i += 12) { ClosePrice[close_price_count] = StringToDouble(TotalElements[i]); close_price_count++; } //Profit double Profit[]; ArrayResize(Profit,total_deals); int Profit_count = 0; for(int i = 34; i < total_elements_count; i += 12) { Profit[Profit_count] = StringToDouble(TotalElements[i]); Profit_count++; } //Result string Result[]; ArrayResize(Result,total_deals); int Result_count = 0; for(int i = 35; i < total_elements_count; i += 12) { Result[Result_count] = TotalElements[i]; Result_count++; } ArrayPrint(Result);
Пояснение:
Первый столбец, который мы рассмотрим, – это Symbol. Чтобы хранить символы всех сделок, сначала мы объявляем динамический массив строк с именем symbol. Теперь, когда общее количество сделок уже известно, мы изменяем размер массива до total_deals, чтобы он мог надежно хранить все символы. Также создается счетчик sym_count, которому присваивается значение 0. Этот счетчик отслеживает текущий индекс в массиве, по которому будет записан следующий символ. Поскольку Symbol идет сразу после идентификатора сделки по индексу 24, цикл начинается с индекса 25 массива TotalElements. Цикл идет с шагом 12, потому что каждая сделка состоит из 12 элементов. В конце каждой итерации текущий элемент копируется в массив symbol. После сохранения переменная sym_count увеличивается и указывает на следующий свободный слот в массиве.
Следующим идет столбец OrderType. Мы объявляем еще один динамический массив строк и задаем для OrderType размер total_deals. Также используется счетчик order_type_count. Поскольку OrderType идет в CSV-файле сразу после Symbol, цикл for начинается с индекса 26. Как и в случае с символами, на каждой итерации цикл увеличивается на 12 и переходит к значению OrderType для следующей сделки. Каждое значение сохраняется в массиве OrderType, после чего счетчик увеличивается. Это гарантирует, что тип сделки будет записан в правильной последовательности. Поскольку размеры лота являются десятичными числами, мы объявляем динамический массив типа double с именем LotSize. Также используется счетчик lot_count. Начиная с индекса 27, цикл for снова идет с шагом 12. Перед сохранением в массив каждый элемент преобразуется из строки в число типа double с помощью StringToDouble. Затем счетчик увеличивается, чтобы следующее значение LotSize записалось в следующий слот.
Поскольку в столбце OpenTime хранятся дата и время, мы объявляем массив типа datetime и счетчик open_time_count. Цикл начинает итерации с индекса 28 и с шагом 12, при этом используя StringToTime для преобразования каждого элемента. Это гарантирует, что время открытия каждой сделки будет корректно сохранено в массиве как значение datetime. Для OpenPrice мы объявляем массив типа double, задаем ему нужный размер, а затем используем счетчик open_price_count. Начиная с индекса 29, цикл идет с шагом 12, преобразует каждый строковый элемент в double и затем помещает его в массив. Счетчик гарантирует, что следующая цена открытия попадет в следующий слот.
Для столбцов StopLoss и TakeProfit используется та же схема. В обоих случаях используются счетчик, массив типа double и цикл, который начинается с индекса 30 для StopLoss и с индекса 31 для TakeProfit. Счетчики отслеживают позиции, а каждый строковый элемент из файла преобразуется в double и сохраняется в соответствующем массиве. Следующим идет CloseTime, и здесь используется тот же подход, что и для OpenTime. Инициализируется счетчик close_time_count, затем создается массив типа datetime и задается его размер. Цикл начинается с индекса 32, идет с шагом 12, использует StringToTime для преобразования каждого элемента и затем сохраняет результат.
Для ClosePrice, Profit и Result мы снова используем массивы и счетчики. Перед сохранением мы преобразуем элементы файла в double, потому что ClosePrice и Profit имеют тип double. Для Result используется массив строк. Чтобы перейти к соответствующему столбцу следующей сделки, циклы начинаются с индексов 33, 34 и 35 соответственно и каждый раз идут с шагом 12. Счетчики каждого массива гарантируют, что каждый элемент будет вставлен в правильную позицию. После завершения процедуры данные каждого столбца сохранятся в отдельном динамическом массиве. В каждом массиве одна и та же сделка представлена одной и той же индексной позицией, а все массивы имеют одинаковое количество элементов – total_deals. Благодаря такой структуре можно последовательно и надежно обращаться к данным о сделках, проверять и изменять их.
Заключение
Изучив, как читать CSV-файл и раскладывать его содержимое по динамическим массивам, мы заметно продвинулись в понимании работы с файлами в MQL5. После того как мы определили общее количество элементов в файле, все данные были собраны в один массив, а затем каждый столбец был вынесен в отдельный динамический массив. Этот метод не только позволяет быстро получать доступ к каждой сделке и анализировать ее, но и закладывает основу для более сложных задач, таких как статистический анализ и построение кривой баланса. Структурируя таким образом данные файла, мы создаем надежную основу для программной работы с торговой историей и подготавливаем вас к следующей части, где эти массивы будут использоваться для визуализации торговых результатов.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21309
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Преодоление ограничений машинного обучения (Часть 6): Эффективная кросс-валидация исторической памяти рынка
Преодоление ограничений машинного обучения (Часть 5): Краткий обзор кросс-валидации временных рядов
Машинное обучение и Data Science (Часть 43): Поиск скрытых паттернов в индикаторах с помощью моделей латентных гауссовых смесей LGMM
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования