Знакомство с языком MQL5 (Часть 40): Руководство для начинающих по работе с файлами в MQL5 (II)
Введение
И снова приветствуем вас в Части 40 серии "Знакомство с языком MQL5"! В предыдущей статье мы заложили основу для работы с файлами в MQL5, разобрав, как создавать и открывать файлы с помощью FileSelectDialog и FileOpen. Мы также создали структуру простого торгового журнала и безопасно, в упорядоченном виде, записали в файл основные сведения о счете. Во второй части мы продолжим работу на этой основе и сосредоточимся на экспорте реальной торговой истории в файл журнала. Вы узнаете, как получать доступ к истории счета за заданный период, а также как извлекать важные сведения о сделке, такие как номер тикета, символ, тип ордера, размер лота, время открытия и закрытия, цены, прибыль и результат сделки. Затем эти записи будут внесены в журнал в структурированном и удобном для чтения виде.
Эта статья, как и другие статьи этой серии, подходит в том числе для начинающих. Логика каждого действия подробно разбирается, каждый шаг детально описывается. Расширяя скрипт торгового журнала, мы продолжим использовать тот же проектный подход. К концу этой статьи вы получите практическое представление о том, как читать историю сделок и хранить ее в файлах так, чтобы данные сохранялись между запусками программы. Это важно для ведения журналов, анализа, отчетности и оценки стратегий в практических приложениях MQL5.
Создание заголовков столбцов для торгового журнала
В предыдущей статье мы узнали, как использовать функцию FileWrite в MQL5 для записи данных в файлы. Это стало основой для записи данных из наших программ в файл. В этой главе мы разовьем это понимание, создав строку заголовков для торгового журнала. Строка заголовков, задающая названия столбцов, является первой строкой файла. Эти заголовки служат метками для каждого элемента данных, что облегчает дальнейшее чтение и анализ торговой информации.
Заголовками нашего торгового журнала будут "TradeID", "Symbol", "OrderType", "LotSize", "OpenTime", "OpenPrice", "StopLoss", "TakeProfit", "CloseTime", "ClosePrice", "Profit in dollars" и "Result". Каждый из этих заголовков обозначает важный параметр сделки. "OrderType" показывает, является ли сделка покупкой или продажей, "TradeID" хранит номер тикета, а "Symbol" обозначает торговый инструмент. "LotSize" показывает объем сделки, а "OpenTime" и "OpenPrice" – время и цену ее открытия. "CloseTime" и "ClosePrice" показывают время и цену закрытия сделки, а "StopLoss" и "TakeProfit" – защитные уровни, установленные для нее. Краткое представление об итогах сделки дают прибыль в долларах и результат.
Поскольку эта строка гарантирует, что все последующие данные о сделках будут располагаться под нужными столбцами, записать ее первой особенно важно. В результате торговый журнал получается упорядоченным и выглядит профессионально. Когда записи о сделках будут добавлены позже, каждая из них окажется точно под своим заголовком. Используя функции MQL5, разобранные в предыдущей статье, мы покажем, как на следующих этапах добавить эту строку заголовков в файл. В результате ваш торговый журнал будет легко читать, а со временем его можно будет без труда дополнять новыми данными.
Пример:input datetime start_time = D'2026.01.01 00:00:00'; // Show history from this date input datetime end_time = D'2026.01.31 00:00:00'; // To this date //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string arrays_filename[]; string default_filename = "Trading_Journal.csv"; int result = FileSelectDialog("Select a File", NULL,"CSV files|*.csv", FSD_WRITE_FILE,arrays_filename,default_filename); string filename; if(result == 0) { Print("No file selected"); } else if(result > 0) { Print("The trading journal will be saved in ",arrays_filename[0]); filename = arrays_filename[0]; } else { Print(GetLastError()); } int handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_SHARE_READ|FILE_ANSI, ','); if(handle == INVALID_HANDLE) { Print("Error opening file for writing. Error code: ", + GetLastError()); } else if(handle != INVALID_HANDLE) { Print("File Sucessfully Opened"); string account_name = AccountInfoString(ACCOUNT_NAME); double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); long account_login = (long)AccountInfoInteger(ACCOUNT_LOGIN); FileWrite(handle, "Account Name: " + account_name); FileWrite(handle, "Account Balnce: " + DoubleToString(account_balance)); FileWrite(handle, "Account Login: " + IntegerToString(account_login)); FileWrite(handle, "Start Time: ", start_time); FileWrite(handle, "End Time: ", end_time); FileWrite(handle, "Last Update: ", TimeCurrent()); FileWrite(handle, "\nTradeID","Symbol","OrderType", "LotSize", "OpenTime","OpenPrice","StopLoss", "TakeProfit", "CloseTime", "ClosePrice" ,"Profit($)","Result"); FileClose(handle); } }
Вывод:

Пояснение:
В этой строке кода с помощью функции FileWrite записывается строка заголовков торгового журнала. Первый аргумент, handle, указывает MQL5, в какой файл нужно записывать. Если бы хэндл не был действительным, программа не знала бы, к какому файлу обращаться, поэтому это должен быть хэндл ранее открытого или созданного файла. Первым значением в строчке идет "TradeID", а "\n" обозначает символ новой строчки. Это гарантирует, что заголовок и предыдущее содержимое не окажутся на одной строчке. Переход на новую строчку визуально отделяет сведения о счете от раздела с записями о сделках.
Оставшиеся значения – это заголовки столбцов торгового журнала: "Symbol", "OrderType", "LotSize", "OpenTime", "OpenPrice", "StopLoss", "TakeProfit", "CloseTime", "ClosePrice", "Profit($)" и "Result". Каждый из этих элементов обозначает данные, которые будут записываться для каждой сделки. Когда все эти значения передаются в FileWrite, программа записывает их ровно в том порядке, в каком они указаны в функции. Эта строка задает структуру для всех последующих сделок, потому что становится первой строкой раздела с торговыми данными. Проще говоря, она создает в файле первую строку таблицы. Программа и любой, кто будет читать файл, смогут понять, какие данные будут показаны под каждым заголовком столбца. Без этого заголовка файл содержал бы только даты и числа без пояснений, из-за чего его было бы труднее читать и понимать.
Аналогия:
Файл можно сравнить с аккуратно оформленным блокнотом. Чтобы каждая следующая запись имела понятный смысл, вверху страницы вы пишете заголовки столбцов. Например, первая строка ведомости успеваемости может содержать заголовки "Имя ученика", "Естествознание", "Математика" и "Английский язык". Любому, кто читает эту таблицу, сразу понятно назначение значений под этими заголовками. Аналогичным образом, строка с FileWrite записывает верхние заголовки столбцов файла. Первое значение, "TradeID", записывает первую метку "TradeID" и начинает новую строку. Чтобы обозначить остальные столбцы, далее записываются значения "Symbol", "OrderType", "LotSize", "OpenTime", "OpenPrice", "StopLoss", "TakeProfit", "CloseTime", "ClosePrice", "Profit($)" и "Result".
Эта строка заголовков делает торговый журнал упорядоченным и удобным для чтения. Все последующие сделки будут отображаться в своих столбцах. Без этого заголовка файл содержал бы необработанные даты и числа, из-за чего его было бы трудно понять. Когда заголовки записываются первыми, журнал выглядит понятным, упорядоченным и профессиональным.
Проход по историческим данным для определения количества сделок
При работе с несколькими сделками их общее количество в рамках заданного таймфрейма заранее неизвестно. Чтобы до начала обработки определить общее количество сделок, важно пройтись по историческим данным счета. Зная общее количество, мы можем создать цикл, который будет безопасно и эффективно обрабатывать каждую сделку. Это позволит алгоритму анализировать каждую сделку, извлекать нужные данные и определять, соответствует ли она выбранному таймфрейму или нашим требованиям. Представьте, что вы просматриваете блокнот, в котором записаны предыдущие сделки. Сначала вы не знаете, сколько в нем всего записей, но при этом, просматривая строчку за строчкой, вы сможете определить их количество и извлечь нужные данные. Это гарантирует, что при обработке ничего не будет упущено и каждая сделка будет учтена. Кроме того, это исключает ошибки, которые могли бы возникнуть, если бы мы исходили из фиксированного количества сделок и попытались обратиться к несуществующей сделке.
Создание надежного торгового журнала начинается с корректного подсчета количества сделок. Когда общее количество сделок определено, можно построить цикл, который обрабатывает каждую сделку по отдельности и собирает такие данные, как номер тикета, символ, тип ордера, размер лота, время открытия и закрытия, цены, прибыль и результат. Этот метод гарантирует, что торговый журнал будет точным и полным и будет включать все актуальные сделки за указанный период.
Пример:FileWrite(handle, "Account Name: " + account_name); FileWrite(handle, "Account Balnce: " + DoubleToString(account_balance)); FileWrite(handle, "Account Login: " + IntegerToString(account_login)); FileWrite(handle, "Start Time: ", start_time); FileWrite(handle, "End Time: ", end_time); FileWrite(handle, "Last Update: ", TimeCurrent()); FileWrite(handle, "\nTradeID","Symbol","OrderType", "LotSize", "OpenTime","OpenPrice","StopLoss", "TakeProfit", "CloseTime", "ClosePrice" ,"Profit($)","Result"); bool success = HistorySelect(start_time, end_time); //Total Deals int totalDeal = 0; if(success) { for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { totalDeal++; } } } FileClose(handle);
Пояснение:
Программа начинает с отбора исторических сделок, совершенных на заданном таймфрейме. Получая время начала и время окончания, функция HistorySelect() выбирает в истории счета только те сделки, которые были совершены в этом диапазоне. Чтобы программа знала, была ли выборка успешной, результат сохраняется в логической переменной success. Значение true означает, что сделки доступны для обработки, а false – что либо за этот период сделок не было, либо произошла ошибка. Это гарантирует, что программа будет работать только с корректными историческими данными.
Затем программа инициализирует переменную для хранения общего количества сделок. Изначально ей присваивается значение 0. Затем программа использует оператор if, чтобы проверить, была ли выборка истории успешной. Если выборка успешна, программа проходит по всем сделкам; если нет, она пропускает этот шаг. Это не дает программе обращаться к несуществующим сделкам. Следующим шагом становится проход по всем возможным сделкам в выбранных исторических данных. Цикл начинается с индекса 0 и продолжается, пока не достигнет количества сделок, возвращаемого HistoryDealsTotal(). Программа может обрабатывать каждую сделку по отдельности, потому что каждая итерация представляет одну сделку. Так программа гарантирует, что ни одна сделка в пределах указанного таймфрейма не будет пропущена.
Внутри цикла программа использует HistoryDealGetTicket(i), чтобы получить уникальный номер тикета текущей сделки. У каждой сделки в MetaTrader 5 есть номер тикета, который служит идентификатором. Подобно тому как серийный номер помогает найти конкретную запись в архиве, этот тикет позволяет программе точно определить сделку и извлечь ее данные. Затем с помощью HistoryDealGetInteger(ticket, DEAL_ENTRY) программа определяет характер сделки. Сделки, которые открывают позиции, определяются как сделки входа с помощью константы DEAL_ENTRY_IN. Этот шаг важен, потому что мы не хотим учитывать другие типы сделок, такие как закрывающие сделки или изменения, которые могут присутствовать в истории счета. Чтобы гарантировать, что в итог будут включены только сделки, которые действительно открывали позицию, программа сравнивает тип сделки с DEAL_ENTRY_IN.
Наконец, каждый раз, когда условие выполняется, программа увеличивает общее количество сделок на один. К концу цикла в переменной totalDeal хранится общее количество сделок входа за заданный таймфрейм. От этого общего количества зависят последующие шаги, такие как запись сделок в торговый журнал или повторный проход для сбора подробной информации по каждой сделке. При обработке исторических данных программа сначала подсчитывает сделки, что гарантирует точность и помогает избежать ошибок.
Аналогия:
Каждая коробка символизирует одну сделку, которая когда-либо была совершена на счете; представьте, что ваша торговая история – это большой архивный зал, полный коробок с документами. Первое, что делает программа, – выбирает, какие коробки нужно проверить. Функция HistorySelect() выступает как помощник: "Принеси мне только коробки, датированные периодом с января по март". Либо помощник вернется с пустыми руками, либо принесет нужные коробки. Результат хранится в переменной success, которая лишь показывает, нашел ли помощник что-нибудь для работы. Если success имеет значение true, значит, коробки лежат на столе. Если это не так, программа понимает, что двигаться дальше вслепую не стоит, потому что проверять нечего.
Затем программа заводит счетчик – это можно сравнить с тем, как вы достаете блокнот и пишете наверху 0 перед началом подсчета. Этот счетчик отслеживает количество найденных реальных сделок. После этого программа проверяет, действительно ли помощник принес какие-либо коробки, прежде чем начинать подсчет. Если коробки не вернулись, программа избегает пустой работы и ошибок, потому что считать было бы нечего. После того как подтверждено, что коробки доступны, программа начинает проверять каждую из них по отдельности. Она начинает с первой коробки и продолжает, пока не будут проверены все коробки за этот период. Это можно сравнить с тем, как вы стоите у стола и просматриваете документы один за другим, чтобы ничего не пропустить. Каждый шаг цикла символизирует момент, когда вы берете очередную коробку и заглядываете внутрь.
Программа проверяет метку каждой коробки, на которой указан номер тикета сделки. Этот тикет работает как серийный код или номер дела, указанный на папке. Он однозначно идентифицирует сделку и позволяет программе извлекать нужную информацию, не путая ее с другой сделкой. После того как программа определяет сделку, она выясняет тип документа. Одни документы показывают открытие сделки, другие – ее закрытие или изменение. Проверку DEAL_ENTRY_IN можно сравнить с вопросом "Этот документ относится к открытию сделки?" Учитываются только те документы, которые отвечают "да". Документы, относящиеся к чему-то другому, игнорируются.
Каждый раз, когда программа находит документ, который символизирует открытие сделки, она прибавляет единицу к счетчику. После того как все коробки проверены, счетчик показывает общее количество сделок, открытых на выбранном таймфрейме. Это итоговое число важно, потому что показывает программе, сколько сделок ей нужно будет в дальнейшем обработать – будь то для извлечения более подробных данных или для записи их в торговый журнал. Подсчет на первом этапе помогает сохранять порядок и избегать ошибок в дальнейшем.
Заполнение столбца Trade ID в файле на основе истории сделок
Теперь, когда мы знаем, как получить общее количество сделок, совершенных на заданном таймфрейме, следующим шагом будет пройти по этим сделкам и извлечь отдельные номера тикетов. Эти номера тикетов, которые записываются в столбец Trade ID файла, служат уникальными идентификаторами каждой сделки. Мы начинаем с первой сделки в выбранном периоде и шаг за шагом продвигаемся к последней, потому что сделки хранятся в хронологическом порядке. В ходе этого процесса мы по одному извлекаем тикеты сделок и подготавливаем их к записи в файл.
Это гарантирует, что каждая сделка будет корректно записана и связана с нужной строкой в торговом журнале. Последовательно извлекая тикеты, мы сохраняем четкое и упорядоченное соответствие между историей сделок и данными в файле. Этот метод поддерживает точность и упорядоченность журнала и при этом упрощает последующее добавление других сведений о сделке, таких как символ, тип ордера, цены и прибыль.
Сначала мы объявим динамический массив типа ulong, в котором будут храниться номера тикетов для каждой сделки. Поскольку каждая сделка в MetaTrader 5 имеет уникальный тикет, этот массив дает нам упорядоченный способ сначала собрать все идентификаторы сделок, а уже потом записать их в файл.
Пример://Trade ID ulong trade_id[]; ArrayResize(trade_id,totalDeal); int j = 0; if(success) { for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { trade_id[j] = ticket; j++; // Increment the array index for the next valid ticket } } } for(int i = 0; i < totalDeal; i++) { FileWrite(handle, trade_id[i]); } FileClose(handle);
Вывод:

Пояснение:
Сначала объявляется ulong trade_id[] – динамический массив для хранения номеров тикетов сделок. То, что этот массив динамический, означает, что при объявлении его размер заранее не задается. Это удобно, когда до выполнения программы неизвестно, сколько сделок будет обнаружено. Несмотря на такую гибкость, прежде чем добавлять значения, массиву все равно нужно задать размер. MetaTrader выдаст ошибку "array out of range", если программа попытается заполнить массив, не изменив его размер заранее, потому что в этом случае она будет обращаться к невыделенной области памяти.
Поэтому требуется вызвать ArrayResize(trade_id, totalDeal);. Чтобы каждый тикет сделки надежно хранился в массиве, программа изменяет его размер в соответствии с общим количеством сделок, подсчитанным ранее. Проще говоря, массив можно сравнить с пустой полкой. Полка создается в момент объявления, а количество ячеек на ней задается при изменении размера. Если ячеек окажется недостаточно, попытка положить на полку новые предметы приведет к ошибке.
По мере того как программа проходит по истории сделок и находит корректные сделки входа, тикет каждой такой сделки сохраняется в массиве. Чтобы в массив попадали только сделки, связанные с открытием позиций, это делается внутри условия, которое проверяет DEAL_ENTRY_IN. Здесь важную роль играет переменная j. Хотя переменная цикла i проходит по всем прошлым сделкам, не каждая из них считается сделкой входа. Только когда находится корректная сделка, переменная j увеличивается как отдельный индекс для записи. Это гарантирует, что при последовательном сохранении тикеты не будут перезаписываться и между ними не появятся пропуски в массиве. j увеличивается каждый раз, когда в массив добавляется корректный тикет. Благодаря смещению позиции записи вперед следующий подходящий тикет попадает в следующую свободную ячейку. Без j программа либо нарушила бы выравнивание данных, либо перезаписала бы ранее сохраненные тикеты, из-за чего записи в журнале оказались бы неточными или неполными.
После того как все тикеты сделок собраны и сохранены в массиве, программа использует известное количество сделок, чтобы пройти по массиву. Во время этого цикла для записи каждого тикета в файл используется FileWrite(handle, trade_id[i]);. Поскольку размер массива был задан правильно и он был заполнен по порядку, эта процедура автоматически записывает каждый тикет сделки один за другим в столбец Trade ID файла. Это гарантирует, что все идентификаторы сделок будут внесены в торговый журнал четко, упорядоченно и надежно.
Аналогия:
Представьте, что динамический массив – это пустой блокнот, в который вы собираетесь записывать номера тикетов сделок. Первичное объявление массива похоже на то, как вы открываете новый блокнот, еще не зная, сколько страниц в нем понадобится. Хотя вы знаете, что будете записывать номера тикетов, вам пока неизвестно количество сделок, которое удастся найти. Именно такая гибкость делает массив динамическим. Однако, прежде чем начинать запись, нужно определить, сколько страниц потребуется блокноту. Если начать писать, не оставив достаточно страниц, место закончится и возникнет проблема. Именно поэтому нужно изменить размер массива. Используя общее количество сделок, полученное на предыдущем шаге подсчета, вы как будто говорите блокноту, сколько страниц нужно подготовить. Это помогает избежать ошибок, которые могли бы возникнуть, если бы вы попытались записать больше, чем помещается в отведенном пространстве.
Пока программа просматривает историю сделок, ей встречается много документов, и не все из них полезны. Одни записи показывают закрытия или изменения, другие – открытие сделок. Каждый раз, когда программа обнаруживает корректную сделку открытия, номер тикета записывается на следующую пустую страницу блокнота. Переменная j работает как закладка страницы. Она указывает на следующую свободную страницу, куда нужно записать корректный тикет. Без этого указателя блокнот стал бы беспорядочным и ненадежным, потому что программа могла бы перезаписать предыдущие записи или оставить между ними пустые страницы.
Как только будут внесены все корректные номера тикетов, программа начнет читать блокнот с первой страницы до последней. Затем номер каждого тикета, строчка за строчкой, записывается в столбец Trade ID файла. Поскольку блокнот был подготовлен с нужным количеством страниц и заполнен в правильной последовательности, в файле получается четкий и полный список идентификаторов сделок. В результате торговый журнал получается точным, удобным для чтения и готовым к следующим этапам, на которых будут добавляться другие сведения о сделках.
Заполнение в файле столбцов Symbol и OrderType
После того как значения идентификатора сделки корректно записаны в файл, нужно заполнить столбцы Symbol и OrderType. Эти два элемента данных важны, потому что показывают, чем именно торговали и в каком направлении была совершена сделка. Символ указывает на конкретный рынок, тип ордера показывает, была ли сделка покупкой или продажей, а Trade ID однозначно идентифицирует саму сделку. Чтобы получить эти данные, мы используем те же исторические данные о сделках, которые были выбраны ранее. Так как тикет каждой сделки у нас уже есть, мы можем использовать его, чтобы получить дополнительную информацию именно по этой сделке. Поскольку символ берется прямо из записи о сделке, мы можем определить, была ли она совершена по EURUSD, GBPUSD или какому-либо другому инструменту, доступному на счете.
Тип ордера указывает направление сделки – была ли это покупка или продажа. Это различие дает ценную информацию при анализе истории сделок. Это позволяет трейдеру оценивать различия в работе длинных и коротких позиций, лучше понимать свое поведение на рынке и улучшать торговую стратегию в целом.
Пример:
ulong trade_id[]; ArrayResize(trade_id,totalDeal); string symbol[]; string order_type[]; ArrayResize(symbol,totalDeal); ArrayResize(order_type,totalDeal); int j = 0; if(success) { for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { trade_id[j] = ticket; symbol[j] = HistoryDealGetString(ticket, DEAL_SYMBOL); if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_BUY) { order_type[j] = "BUY"; } if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_SELL) { order_type[j] = "SELL"; } j++; // Increment the array index for the next valid ticket } } } for(int i = 0; i < totalDeal; i++) { FileWrite(handle, trade_id[i],symbol[i],order_type[i]); } FileClose(handle);
Вывод:

Пояснение:
В начале процедуры объявляются два динамических массива строк для хранения информации о типе ордера и именах символов. Поскольку количество сделок зависит от выбранной истории и может меняться, изначально у этих массивов нет фиксированного размера. Когда общее количество сделок определено, размер массивов приводится в соответствие с ним. Используя ArrayResize для выделения нужного объема памяти, программа гарантирует, что символ и тип ордера для каждой сделки будут записаны корректно, и исключает ошибки выхода за границы массива.
Когда массивы готовы, программа извлекает символ для каждой сделки. Для этого используется HistoryDealGetString с атрибутом DEAL_SYMBOL. Поскольку символ, например, EURUSD или GBPUSD, сохраняется как строка, здесь используется функция, работающая со строками. Используя HistoryDealGetString, программа может напрямую извлечь читаемый текст из записи о сделке. Чтобы сохранить соответствие с идентификатором сделки и другими сведениями о ней, извлеченный символ затем сохраняется в массиве символов по нужному индексу.
Затем программа определяет тип ордера для каждой сделки. Направление сделки внутри платформы хранится как целочисленное значение. Используя HistoryDealGetInteger с полем DEAL_TYPE, программа может определить, была ли сделка покупкой или продажей. Затем с помощью условия это внутреннее значение преобразуется в понятный человеку формат. Если тип сделки соответствует DEAL_TYPE_BUY, программа добавляет в массив order_type текст "BUY". Если тип сделки соответствует DEAL_TYPE_SELL, программа присваивает текст "SELL".
Эти условия важны, потому что возвращаемые платформой необработанные значения представляют собой внутренние константы, предназначенные для логики программы, а не для вывода на экран. Если бы эти условия не использовались, в записываемый в файл вывод могли бы попасть значения вроде DEAL_TYPE_BUY или DEAL_TYPE_SELL, которые неудобны для чтения и неаккуратно выглядят в торговом журнале. Преобразование этих внутренних констант в простые текстовые метки делает файл более понятным, профессиональным и полезным для анализа.
Запись в файл данных о входе в сделку
После того как записаны "Trade ID", "Symbol" и "Order Type", следующим шагом нужно внести в файл сведения о входе в сделку. К таким данным относятся "Stop Loss", "Take Profit", "Open Time", "Open Price" и "Lot Size". Собрать эти данные важно, потому что они дают полную картину исполнения каждой сделки, включая размер позиции, время, ценовые уровни и настройки управления рисками. Мы можем получить эти сведения непосредственно из истории счета по каждому тикету сделки, который хранится в массиве. "Lot Size" показывает размер сделки, а "Open Time" и "Open Price" – время и цену входа в нее. "Stop Loss" и "Take Profit" показывают уровни, заданные для ограничения риска и возможной прибыли. Записывая эти значения, вы гарантируете, что торговый журнал будет содержать все ключевые сведения о входе в каждую из сделок.
Пример:
ulong trade_id[]; ArrayResize(trade_id,totalDeal); string symbol[]; string order_type[]; ArrayResize(symbol,totalDeal); ArrayResize(order_type,totalDeal); double lot_size[]; datetime open_time[]; double open_price[]; double stop_l[]; double take_p[]; ArrayResize(lot_size,totalDeal); ArrayResize(open_time,totalDeal); ArrayResize(open_price,totalDeal); ArrayResize(stop_l,totalDeal); ArrayResize(take_p,totalDeal); int j = 0; if(success) { for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { trade_id[j] = ticket; symbol[j] = HistoryDealGetString(ticket, DEAL_SYMBOL); if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_BUY) { order_type[j] = "BUY"; } if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_SELL) { order_type[j] = "SELL"; } lot_size[j] = HistoryDealGetDouble(ticket,DEAL_VOLUME); open_time[j] = (datetime)HistoryDealGetInteger(ticket,DEAL_TIME); open_price[j] = HistoryDealGetDouble(ticket,DEAL_PRICE); stop_l[j] = HistoryDealGetDouble(ticket,DEAL_SL); take_p[j] = HistoryDealGetDouble(ticket,DEAL_TP); j++; // Increment the array index for the next valid ticket } } } for(int i = 0; i < totalDeal; i++) { FileWrite(handle, trade_id[i],symbol[i],order_type[i],lot_size[i],open_time[i],open_price[i],stop_l[i],take_p[i]); } FileClose(handle);
Вывод:

Пояснение:
Для "Lot Size", "Open Time", "Open Price", "Stop Loss" и "Take Profit" мы создаем отдельные динамические массивы, чтобы аккуратно организовать данные по сделкам. У каждого массива тип данных соответствует тому типу данных, который в нем хранится. Поскольку цены и размер лота требуют дробной точности, они объявляются как double, а для "Open Time" используется тип datetime, который точно представляет данные времени. Если эти массивы объявлены как динамические, программа может обрабатывать произвольное количество сделок за выбранный период, даже если точное их число заранее неизвестно.
Однако даже если массивы динамические, для них все равно нужно заранее выделить место, чтобы избежать проблем. Для каждого массива мы используем ArrayResize и задаем размер, равный totalDeal, то есть количеству сделок, подсчитанному ранее. Изменение размера гарантирует, что памяти хватит для хранения значений по каждой из сделок. Без этого шага программа пыталась бы записывать данные в невыделенную область памяти, и при попытке сохранить информацию о сделке возникла бы ошибка "array out of range". Например, когда вы объявляете массив, у вас появляются пустые ящики, а когда изменяете его размер, программа понимает, сколько таких ящиков вам действительно нужно. Как только массивы подготовлены, программа извлекает нужные данные по каждому корректному тикету сделки.
С помощью HistoryDealGetDouble(ticket, DEAL_VOLUME) программа получает размер лота сделки и сохраняет его в массиве lot_size. Чтобы получить время открытия, используется HistoryDealGetInteger(ticket, DEAL_TIME);. Затем это значение приводится к типу datetime и сохраняется в массиве open_time. Для получения цены открытия используется HistoryDealGetDouble(ticket, DEAL_PRICE). Для получения уровня стоп-лосса используется HistoryDealGetDouble(ticket, DEAL_SL), а для получения уровня тейк-профита – HistoryDealGetDouble(ticket, DEAL_TP). Чтобы информация о входе для каждой сделки соответствовала нужному тикету, каждое из этих значений сохраняется по текущему индексу j в своем массиве.
Используя эти массивы, программа может сначала сохранить в памяти данные о входе, а затем записать их в файл. Сначала собрав данные, мы можем последовательно пройти по каждой сделке и внести информацию в нужные разделы торгового журнала. Этот метод гарантирует, что для каждой сделки вся информация о входе будет записана с точностью и в правильном порядке, при этом сохранится структура файла.
Запись в файл данных о выходе из сделки
После того как сведения о входе записаны, следующим шагом имеет смысл добавить информацию о выходе из сделки. Чтобы каждая запись о сделке была полной, в нее добавляются прибыль, результат, время закрытия и цена закрытия. Эти данные показывают итог сделки и дают информацию, необходимую для оценки эффективности. Учитывая данные как о входе, так и о выходе, трейдеры могут оценивать стабильность результатов, выявлять сильные и слабые стороны своих подходов и точно отслеживать рост счета.
Программа может получить данные о выходе прямо из истории счета, используя тикеты сделок, которые хранятся в массиве. "Close Price" показывает цену, по которой была закрыта позиция, а "Close Time" – точный момент закрытия сделки. "Profit" показывает финансовый результат сделки, а столбец "Result" – была ли сделка прибыльной, убыточной или закрылась в ноль. Собирая и записывая эти значения, мы делаем журнал полным и готовым к анализу, потому что у каждой сделки есть сведения и о входе, и о выходе.
Пример:datetime close_time[]; double close_price[]; double profit[]; string result[]; ArrayResize(close_time,totalDeal); ArrayResize(close_price,totalDeal); ArrayResize(profit,totalDeal); ArrayResize(result,totalDeal); int j = 0; int h = 0; if(success) { for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { trade_id[j] = ticket; symbol[j] = HistoryDealGetString(ticket, DEAL_SYMBOL); if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_BUY) { order_type[j] = "BUY"; } if(HistoryDealGetInteger(ticket,DEAL_TYPE) == DEAL_TYPE_SELL) { order_type[j] = "SELL"; } lot_size[j] = HistoryDealGetDouble(ticket,DEAL_VOLUME); open_time[j] = (datetime)HistoryDealGetInteger(ticket,DEAL_TIME); open_price[j] = HistoryDealGetDouble(ticket,DEAL_PRICE); stop_l[j] = HistoryDealGetDouble(ticket,DEAL_SL); take_p[j] = HistoryDealGetDouble(ticket,DEAL_TP); j++; // Increment the array index for the next valid ticket } if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { trade_id[h] = ticket; close_time[h] = (datetime)HistoryDealGetInteger(ticket,DEAL_TIME); close_price[h] = HistoryDealGetDouble(ticket,DEAL_PRICE); profit[h] = HistoryDealGetDouble(ticket,DEAL_PROFIT); if(profit[h] > 0) { result[h] = "WIN"; } else if(profit[h] < 0) { result[h] = "LOSS"; } else { result[h] = "Break Even"; } h++; } } } for(int i = 0; i < totalDeal; i++) { FileWrite(handle, trade_id[i],symbol[i],order_type[i],lot_size[i],open_time[i],open_price[i],stop_l[i],take_p[i],close_time[i],close_price[i],profit[i] ,result[i]); } FileClose(handle);
Вывод:

Пояснение:
Сначала мы создаем динамические массивы для четырех параметров выхода, которые хотим записывать: результат, прибыль, время закрытия и цена закрытия. "Close Time" записывается как тип datetime, потому что оно фиксирует точный момент закрытия сделки. Поскольку прибыль и цена закрытия являются числами, для них используется тип double, а результат сохраняется как строка, чтобы можно было записывать текстовые итоги вроде "WIN", "LOSS" или "Break Even". Эти массивы объявляются как динамические, потому что точное количество сделок заранее неизвестно.
Мы используем ArrayResize, чтобы выделить память для каждого массива в размере totalDeal, то есть количества сделок, определенного ранее при подсчете, и тем самым надежно сохранить данные по каждой сделке. Изменение размера помогает избежать проблем, которые могли бы возникнуть, если бы мы попытались записывать данные за пределами массива, и гарантирует, что для сведений о выходе по каждой сделке будет достаточно места. Представьте, что вы готовите несколько подписанных коробок для хранения данных о выходе по сделкам.
После этого переменной h присваивается значение 0. Данные о выходе сохраняются в массивах, а эта переменная используется как индексный маркер. После того как данные каждой сделки, указывающей на закрывающее действие, сохраняются в массивах по текущему индексу h, мы увеличиваем h, чтобы перейти к следующей свободной ячейке. Это важно, потому что не все сделки в массиве истории соответствуют закрывающим сделкам. Часть сделок в истории уже относится к сделкам входа. Если не использовать отдельный индекс h, в массивах выхода могут появиться пропуски или может произойти перезапись ранее сохраненных данных.
После того как с помощью HistoryDealGetInteger(ticket, DEAL_ENTRY) программа определяет тип каждой сделки, она обрабатывает только те из них, которые помечены как DEAL_ENTRY_OUT. Массивы входа уже содержат сделки открытия, обозначенные как DEAL_ENTRY_IN. Благодаря этому разграничению сохраняются точность и упорядоченность журнала, а на этом этапе обрабатываются только сделки выхода.
Используя HistoryDealGetInteger(ticket, DEAL_TIME), программа получает время закрытия каждой закрывающей сделки и приводит его к типу datetime. С помощью HistoryDealGetDouble(ticket, DEAL_PRICE) получаем цену закрытия, а с помощью HistoryDealGetDouble(ticket, DEAL_PROFIT) – прибыль. Каждое из этих значений сохраняется в соответствующем массиве по индексу h. Наконец, программа определяет результат сделки. Если прибыль больше нуля, результат записывается как "WIN". Если прибыль меньше нуля, результат записывается как "LOSS". Если прибыль точно равна нулю, результат записывается как "Break Even". Преобразуя числовые значения прибыли в понятную человеку информацию, этот этап упрощает быстрый анализ и понимание торгового журнала.
К моменту завершения этого процесса массивы будут содержать все сведения о выходе в правильной последовательности и в привязке к соответствующим сделкам. Этот последовательный подход гарантирует, что каждая запись в журнале будет содержать необходимые данные как о входе, так и о выходе и будет готова к записи в файл, формируя прозрачный и профессионально оформленный торговый журнал.
Заключение
В этой статье мы рассмотрели, как работать с файлами торговых данных в MQL5, записывая как сведения о входе, так и сведения о выходе, включая "Trade ID", "Symbol", "Order Type", "Lot Size", "Open Time", "Open Price", "Stop Loss", "Take Profit", "Close Time", "Close Price", "Profit" и "Result". Мы разобрали, как хранить данные в динамических массивах, получать сведения о сделках из истории счета и последовательно записывать их в файл, сохраняя точность и порядок. Выполнив эти шаги, вы создали основу для работы с файлами в MQL5 и сформировали упорядоченный и удобный торговый журнал, который может хранить все ключевые данные о сделках для дальнейшего просмотра и анализа.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21267
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Искусство работы с логами (Часть 10): Подавление повторяющихся логов (suppression)
Моделирование рынка (Часть 22): Первые шаги на SQL (V)
Неопределенность как модель (Часть 1): Случайные величины — язык неопределенности
Искусство работы с логами (Часть 9): Применяем паттерн Builder и настраиваем конфигурации по умолчанию
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования