English
preview
Знакомство с языком MQL5 (Часть 43): Руководство для начинающих по работе с файлами в MQL5 (V)

Знакомство с языком MQL5 (Часть 43): Руководство для начинающих по работе с файлами в MQL5 (V)

MetaTrader 5Торговые системы |
46 0
ALGOYIN LTD
Israel Pelumi Abioye

Введение

Вы запускаете советник в MT5, настраиваете параметры и накапливаете состояние во время работы – а затем терминал закрывается, график обновляется или советник удаляется, и все, что хранилось в памяти, теряется. Советник перезапускается "с нуля": сохраненные настройки, вычисленные значения и согласованные конфигурации исчезают, что особенно неприятно, когда один и тот же советник работает на нескольких терминалах и должен опираться на единый набор общих параметров. Вам нужно простое, быстрое и воспроизводимое хранилище, которое сохраняется между перезапусками и может совместно использоваться несколькими терминалами на одном ПК. В этой статье показано, как упаковать параметры советника в простую структуру MQL5 и надежно сохранять и загружать ее из общего бинарного файла (FILE_COMMON + FILE_BIN) без ручного преобразования в текст.

 

Понимание структур в MQL5

В этом разделе мы разберем основы структур в MQL5 и выясним, что они собой представляют. Важно понять, как работают структуры и почему они полезны в программировании, прежде чем переходить к работе с ними в файлах. Поскольку далее в статье структуры будут использоваться при работе с бинарными файлами в MQL5, их роль здесь особенно важна. При хранении или извлечении данных – особенно в бинарных файлах – они должны быть организованы единообразно. Структуры дают удобный способ сгруппировать связанные переменные, чтобы записывать их в файл и затем считывать обратно в том же формате.

Если вы понимаете, как определяются структуры, в каком порядке расположены их члены и как к ним обращаться, дальнейшие операции с файлами станут проще. Поэтому сначала мы разберем базовые принципы структур, а уже потом будем использовать их в реальных MQL5-приложениях, работающих с файлами.

Что такое структура в MQL5?

Структура (struct) – это специальный тип данных, который позволяет объединять связанные переменные в одну сущность. Каждая переменная внутри структуры, называемая членом, может иметь свой тип данных, будь то целое число, число с плавающей точкой, булево значение, строка и даже другая структура.

Возможно, вы уже знакомы с массивами. Но чем структуры отличаются от массивов? Массивы используются для хранения нескольких значений одного и того же типа данных. Например, целочисленный массив (int) может содержать только целые числа, а массив double – только значения типа double. Поэтому массивы удобны, когда нужно работать с несколькими однотипными элементами данных.

Пример:
double prices[3] = {1.1050, 1.1075, 1.1030};

В отличие от массива, структура позволяет объединять переменные разных типов данных в один контейнер. Это означает, что в структуре можно объявить поля типов int, double, bool и string.

Синтаксис:

struct StructName      // Name of the structure
  {
   double            member1;  // First member
   int               member2;  // Second member
   string            member3;  // Third member
  };

Еще одно отличие связано с синтаксисом объявления. Для массивов размер задается в квадратных скобках, а структуры объявляются с помощью ключевого слова struct и блока с членами. Иначе говоря, квадратные скобки задают размер массива, а после ключевого слова struct следует блок, в котором перечислены все члены структуры. После объявления структуру можно использовать во всей программе как пользовательский тип данных. После этого можно создавать переменные этого типа и обращаться к их членам через оператор точки.

Создание переменной структуры

Как же на практике создается переменная структуры в MQL5? Разберем это по шагам, чтобы было проще следить за логикой. Сначала используется ключевое слово struct. Почему так? Потому что struct сообщает MQL5, что сейчас будет объявлен новый тип данных, а не просто обычная переменная. Это похоже на использование int или double, только здесь вы создаете специальный тип, который может содержать несколько связанных значений.

struct

Далее вы задаете структуре имя. Например, назовем ее EAParameters. По этому имени вы будете обращаться к структуре позже – при создании переменных и работе с ее членами.

struct EAParameters

После имени открывается блок в фигурных скобках { }. Зачем нужны фигурные скобки? Потому что внутри них перечисляются все члены структуры. Это конкретные поля данных, входящие в структуру. После закрывающей фигурной скобки ставится точка с запятой. Эта точка с запятой нужна, чтобы сообщить MQL5, что объявление структуры завершено.

struct EAParameters
  {
  
  };

Теперь можно указать, из чего состоит структура. У каждого члена есть имя и тип. Каждый член записывается внутри фигурных скобок и заканчивается точкой с запятой (;).

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;
  };

После того как структура определена, как использовать ее на практике? Здесь и нужны переменные структуры. Переменная объявляется по имени структуры так же, как переменная любого другого типа данных:

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;
  };
EAParameters myEA;

Доступ к членам структуры

Теперь, когда мы создали переменную структуры EAParameters myEA;, возникает следующий вопрос: как использовать ее на практике? Как получать или задавать отдельные значения, хранящиеся внутри этой структуры? Здесь в дело вступают члены структуры. К ним обращаются с помощью оператора точки (.). Несмотря на то что переменные объединены в одной структуре, они остаются независимыми. Оператор точки дает прямой доступ к любому конкретному члену, а структура служит контейнером, который объединяет их вместе.

Пример:
myEA.TakeProfit = 50.0;
myEA.StopLoss = 20.0;
myEA.LotSize = 0.1;
myEA.RSI_Level = 14;
myEA.MaxTrades = 5;

Вот что здесь происходит:

  • myEA.TakeProfit = 50.0; присваивает члену TakeProfit значение 50.0;
  • myEA.RSI_Level = 14; присваивает члену RSI_Level значение 14;
  • каждая строка обращается к конкретному члену переменной myEA.

Но этот синтаксис используется не только для записи значений. Таким же образом значения можно и читать:

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;
  };
EAParameters myEA;

myEA.TakeProfit = 50.0;
myEA.StopLoss = 20.0;
myEA.LotSize = 0.1;
myEA.RSI_Level = 14;
myEA.MaxTrades = 5;

Print("EA Take Profit is ", myEA.TakeProfit);
Print("EA Max Trades allowed: ", myEA.MaxTrades);

Так проще проверять данные из структуры и использовать их, выводя текущие значения ее членов. Оператор точки работает со всеми типами членов. При таком доступе к членам параметры советника остаются читаемыми, хорошо организованными и удобными для сохранения в файл или чтения из него как одной структурированной записи.

Простые и сложные структуры в MQL5

В MQL5 разные структуры обрабатываются по-разному. Важно понимать разницу между простыми и сложными структурами, потому что от этого зависит, как их использовать, особенно при чтении из файлов и записи в них. Простые структуры – это структуры без динамических массивов, указателей, объектов классов и строк. В них входят только базовые типы данных, такие как int, double и bool, а также вложенные простые структуры. Почему это важно? Простые структуры можно передавать в функции DLL и хранить в бинарных файлах, поэтому они хорошо подходят для постоянного хранения параметров советника.

Например, структура, в которой тейк-профит, стоп-лосс, размер лота и уровень RSI хранятся как числа, считается простой. Только простые структуры в MQL5 можно напрямую записывать в бинарный файл (.bin) с помощью FileWriteStruct и затем считывать обратно с помощью FileReadStruct. Поскольку представление сложных структур в памяти не фиксировано, их нельзя напрямую сохранять в бинарных файлах. К сложным структурам, напротив, относятся структуры, содержащие строки, объекты, указатели и динамические массивы. Из-за нефиксированного расположения в памяти их сложнее сохранять в бинарных файлах, и их нельзя напрямую передавать в функции DLL. При сохранении и чтении сложных структур требуется дополнительная обработка, например отдельная запись строк или контроль размеров массивов.

Вот главный вывод: если ваша цель – сохранять настройки советника в файле, структура должна быть простой. Если вы хотите записывать и читать всю структуру целиком через бинарный файл, все ее члены должны иметь фиксированный размер. Например, переменная типа int всегда занимает в памяти 4 байта независимо от значения.

Пример:
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int a = 10;
   int b = 1000;
   int c = -500000;

   Print("Size of a: ", sizeof(a), " bytes");
   Print("Size of b: ", sizeof(b), " bytes");
   Print("Size of c: ", sizeof(c), " bytes");
  }

Вывод:

Figure 1. Int Size

Сложные структуры включают типы переменной длины, такие как строки, объекты, указатели и динамические массивы. В отличие от числовых типов фиксированного размера, строка занимает в памяти объем, зависящий от ее длины. В MQL5 выражение sizeof(string) не возвращает размер сохраненного текста; оно возвращает только размер ссылки. Поэтому напрямую записывать сложные структуры в бинарные файлы, не контролируя содержимое строки, рискованно. Чтобы определить фактическую длину строки, используйте функцию StringLen().

Пример:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   string s1 = "Hi";
   string s2 = "Hello, World!";
   string s3 = "This is a longer string";

   Print("Pointer size of s1: ", sizeof(s1), " bytes, actual length: ", StringLen(s1));
   Print("Pointer size of s2: ", sizeof(s2), " bytes, actual length: ", StringLen(s2));
   Print("Pointer size of s3: ", sizeof(s3), " bytes, actual length: ", StringLen(s3));
  }

Вывод:

Figure 2. String Size

Главный вывод таков: в MQL5 с бинарными файлами могут работать только простые структуры. Если вы собираетесь хранить в бинарном файле параметры, данные сеанса или любую другую постоянную информацию, структура не должна содержать строки, динамические массивы, указатели и объекты классов. При использовании простых структур расположение данных в памяти фиксировано и предсказуемо, что делает файловые операции надежными.


Запись структуры в файл

При работе с советниками часто возникает проблема: если советник останавливается, все переменные, хранившиеся в памяти, теряются. Если терминал закрывается, график обновляется или советник удаляется, программа перезапускается уже без прежних значений. Если сохранять структуру в файл, советник сможет записать важные данные перед завершением работы и восстановить их при следующем запуске. Это позволяет программе сохранять состояние между сессиями вместо сброса при загрузке. Например, советник может записывать в бинарный файл структуру с торговыми настройками, вычисленной статистикой и другой служебной информацией. При следующем запуске советник просто считывает структуру из файла и продолжает работать с теми же данными.

Еще одно важное преимущество хранения структур в файлах заключается в том, что для программы появляется базовый внешний механизм хранения данных. Файл превращается в надежное постоянное хранилище, в котором данные можно безопасно сохранять и при необходимости считывать, а не полагаться только на временные переменные внутри советника. Когда в работе участвует несколько советников, этот подход становится еще полезнее. Для разных советников, работающих в разных терминалах MetaTrader 5, бинарный файл со структурированными данными может выступать общим хранилищем. Каждый советник может использовать одну и ту же конфигурацию или данные для принятия решений и считывать параметры из одного и того же файла.

Файлы CSV, то есть простые текстовые файлы, которые легко просматривать в Excel или любом текстовом редакторе, уже рассматривались в предыдущей статье. Файлы CSV удобны при экспорте результатов для последующего анализа или при мониторинге истории сделок, но это не лучший вариант для хранения структурированных данных советника, которые должны оставаться согласованными между сессиями. Основная причина в том, что структуру нельзя напрямую сохранить в CSV как единый элемент. При записи каждый компонент структуры нужно отдельно преобразовать в текст, а при чтении – обратно в исходный тип. Такой подход требует больше кода, подвержен ошибкам и может приводить к несоответствиям, если тип или порядок полей сохраняются неправильно. Такой объем ручной работы неэффективен и ненадежен для советников, которые хранят наборы параметров, собирают состояние во время выполнения или используются на нескольких терминалах.

Для сохранения структурированных данных советника бинарные файлы подходят лучше. В отличие от CSV, структуру с членами фиксированного размера можно записать в бинарный файл напрямую как единый блок памяти, а затем восстановить в исходном виде без какой-либо конвертации. Это гарантирует, что тип и порядок каждого поля будут сохранены, а сама процедура чтения и записи будет быстрее, безопаснее и менее подвержена ошибкам. Кроме того, при использовании общего хранилища FILE_COMMON хранение в бинарном формате позволяет нескольким терминалам обращаться к одному и тому же файлу, что упрощает согласование настроек и совместное использование наборов параметров между сессиями.

Хотя бинарные файлы не предназначены для чтения человеком, это свойство фактически повышает надежность, поскольку снижает вероятность случайных изменений. Они хорошо подходят для хранения конфигурации программы, рабочих данных и параметров советника, поскольку занимают меньше места и быстрее читаются и записываются. Это означает, что в MQL5 можно создать структуру, присвоить значения ее членам, а затем сохранить ее в бинарный файл, чтобы обеспечить постоянное хранение данных. После того как все поля получат значения, структуру можно сохранить, а затем снова загрузить в точности в том виде, в каком она была. Это позволяет сохранять состояние даже после перезапуска терминала, обновления графика или удаления советника. Этот подход лежит в основе более сложных систем серии, где критически важно надежное и согласованное хранение параметров.

Пример:

input double  TP    = 50.0;
input double  SL    = 20.0;
input double  LOT   = 0.1;
input int     RSI   = 14;
input int     MAX_T = 5;

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;

  };


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   EAParameters myEA;

   myEA.TakeProfit = TP;
   myEA.StopLoss = SL;
   myEA.LotSize = LOT;
   myEA.RSI_Level = RSI;
   myEA.MaxTrades = MAX_T;   

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Пояснение:

В этом примере мы сначала задаем набор входных переменных, которые представляют стандартные настройки советника. Если советник запускается впервые или ранее сохраненные данные недоступны, эти значения используются как исходные настройки. При первом запуске, когда бинарного файла еще нет, советник использует эти входные параметры как рабочие значения.

Далее мы определяем структуру с именем EAParameters. Внутри структуры объявляется несколько членов, представляющих параметры, которые может использовать советник: тейк-профит, стоп-лосс, размер лота, уровень RSI и максимально допустимое число сделок. После объявления структуры мы создаем на ее основе переменную с именем myEA. После этого каждому члену присваиваются значения с помощью оператора точки. На этом этапе в структуре уже хранится полный набор параметров.

Чтобы данные хранились вне программы, возникает ключевой вопрос: как сохранить эту структуру в бинарный файл? Первый шаг – открыть файл, в который будет записана структура. Для этого в языке MQL5 используется функция FileOpen. Эта функция устанавливает связь между программой и файлом, который находится в файловом каталоге терминала.

Это необходимо, потому что перед записью данных программа должна знать, в какой файл записывать и как к нему обращаться. Если файл еще не существует, нужно ли его создать? Стоит ли записывать данные в бинарном формате? Эти решения принимаются при открытии файла. После успешного открытия файла программа получает его хэндл – ссылку, через которую выполняются дальнейшие операции. Затем этот хэндл используют другие функции работы с файлами для записи структуры в бинарный файл. 

Процедура выглядит так:

  1. определить структуру;
  2. создать переменную и присвоить значения ее членам;
  3. открыть бинарный файл с помощью функции FileOpen;
  4. записать структуру с помощью функции FileWriteStruct.

 Затем с помощью соответствующей функции записи структуру можно напрямую записать в файл.

Пример:

input double  TP    = 50.0;
input double  SL    = 20.0;
input double  LOT   = 0.1;
input int     RSI   = 14;
input int     MAX_T = 5;

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;

  };

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   EAParameters myEA;

   myEA.TakeProfit = TP;
   myEA.StopLoss = SL;
   myEA.LotSize = LOT;
   myEA.RSI_Level = RSI;
   myEA.MaxTrades = MAX_T;

   string file_name = "EA_parameters.bin";
   int handle = FileOpen(file_name, FILE_WRITE|FILE_BIN|FILE_COMMON);

   if(handle == INVALID_HANDLE)
     {
      Print("ERROR: Cannot open file for writing! Error: ", GetLastError());
      return;

     }

   else
      if(handle != INVALID_HANDLE)
        {
         Print("File successfully opened");

         int file_size = (int)FileSize(handle);

         if(file_size == 0)
           {
            FileWriteStruct(handle, myEA);
           }
         FileClose(handle);
        }

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Пояснение:

Когда работа советника завершается, в MQL5 автоматически вызывается специальный обработчик события OnDeinit(). Это может произойти при закрытии терминала MetaTrader 5, обновлении графика или удалении советника с графика. OnDeinit() дает последнюю возможность выполнить завершающие действия или сохранить важные данные, поскольку в этот момент все данные, хранящиеся в памяти, будут уничтожены. Чтобы данные можно было восстановить при следующем запуске советника, в этой ситуации текущие параметры советника сохраняются в файл.

Далее создается переменная для хранения имени файла, в котором будут сохраняться данные. В этом примере переменной file_name присваивается значение "EA_parameters.bin". По суффиксу .bin этот файл определяется как бинарный. Бинарный файл хранит данные в виде сырых байтов – в точности в том виде, в каком они представлены в памяти. Хранение имени файла в переменной упрощает повторный доступ к нему. Это избавляет от необходимости многократно прописывать имя файла в разных файловых операциях: для открытия, чтения или записи достаточно обратиться к переменной. После открытия файла специальная функция возвращает хэндл файла. Этот хэндл действует как ссылка и позволяет программе обращаться к файлу, указанному в переменной имени файла, и изменять его. Все последующие операции с файлом, например запись или закрытие, выполняются с использованием этого хэндла.

При открытии файла можно задать множество флагов, которые определяют режим доступа к нему. Первый флаг, FILE_WRITE, указывает, что программа будет записывать данные в файл. Если файла еще нет, MetaTrader 5 сразу его создаст. Если файл уже существует, программа сможет записывать данные в него. Благодаря флагу FILE_BIN файл обрабатывается как бинарный, а не как текстовый. Это необходимо для сохранения структур, поскольку позволяет записывать структуру в файл как сырой блок памяти. Двоичный режим необходим функциям вроде FileWriteStruct, чтобы они могли корректно сохранять структурированные данные.

Расположение файла определяется третьим флагом, FILE_COMMON. Этот флаг сохраняет файл в общей директории, доступной всем терминалам MetaTrader на компьютере, а не в файловой папке локального терминала. Это позволяет нескольким советникам, работающим в нескольких установках MetaTrader 5, обращаться к одному и тому же файлу. В более сложных системах такой общий файл может служить центральным хранилищем или даже простейшей формой общей памяти между советниками.

Figure 3. File Directory

После FileOpen код проверяет, является ли хэндл корректным. Если хэндл корректен, значит файл был успешно открыт и доступен для работы. Когда файл готов к дальнейшим действиям, в терминал выводится подтверждающее сообщение. На следующем шаге определяется размер файла, то есть сколько байт он сейчас содержит. Это значение сохраняется в целочисленной переменной. Проверка размера файла важна, потому что позволяет определить, пуст файл или уже содержит данные.

Следующее условие if проверяет, равен ли размер файла нулю. Если да, значит файл пуст и в нем нет ранее сохраненных данных. В этом случае после записи структуры функцией FileWriteStruct структура myEA сохраняется в бинарный файл как первая запись. Если файл уже содержит сохраненные структуры, такая проверка не дает программе стереть существующие данные. Это простая мера защиты целостности данных, особенно если файл может быть общим или использоваться несколькими советниками. В этом примере для простоты и наглядности структура записывается только тогда, когда файл пуст, чтобы сосредоточиться на базовых принципах ее сохранения и загрузки. Это избавляет от дополнительной сложности, связанной с обработкой нескольких записей или обновлений, и позволяет наглядно понять, как структурированные данные записываются в файл и считываются из него.

В конце файл закрывается с помощью FileClose(). На этом важном этапе освобождается хэндл файла, а все записанные данные корректно сбрасываются и сохраняются на диск. Если файл не закрыть, часть данных может остаться в буферах памяти и не записаться полностью, что приведет к неполным или поврежденным файлам. Кроме того, при закрытии файла освобождается ресурс, поэтому к нему можно будет безопасно обратиться позднее. Тем самым мы гарантируем, что при следующем открытии файла сохраненные данные будут согласованными и читаемыми.

Примечание: Если FileWriteStruct используется со структурой, содержащей строку, возникнет ошибка. Это происходит потому, что строки являются динамическими, и их размер в памяти может изменяться. Этот метод поддерживает только простые структуры, состоящие из типов данных фиксированного размера, поскольку он напрямую записывает в бинарный файл память фиксированного размера.

Пример:
struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;
   string            lll;
  };

Figure 4. String


Чтение структуры из бинарного файла

В предыдущей главе мы рассмотрели, как создать структуру и записать ее в бинарный файл, превратив этот файл в надежное хранилище параметров советника. Теперь, когда мы умеем сохранять данные, логично разобраться, как извлекать структурированные данные из файла. Это важно, поскольку советник или скрипт сможет использовать сохраненные параметры только в том случае, если умеет корректно к ним обращаться. Функция корректно работает только с простыми структурами фиксированного размера, поскольку напрямую записывает в бинарный файл данные фиксированного размера.

В этой главе будет показано, как с помощью FileReadStruct прочитать ранее сохраненную структуру, открыть бинарный файл и убедиться, что он был успешно открыт. Мы также рассмотрим, как обеспечить целостность данных, подтвердить успешность чтения и безопасно закрыть файл. К концу этой главы вы сможете снова открыть файл EA_parameters.bin, получить сохраненные параметры советника и использовать их в коде MQL5, чтобы советник мог использовать механизм постоянного хранения данных между сессиями.

Пример:

input double  TP    = 50.0;
input double  SL    = 20.0;
input double  LOT   = 0.1;
input int     RSI   = 14;
input int     MAX_T = 5;

struct EAParameters
  {
   double            TakeProfit;
   double            StopLoss;
   double            LotSize;
   int               RSI_Level;
   int               MaxTrades;

  };

string file_name = "EA_parameters.bin";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   EAParameters myEA_read;

   if(FileIsExist(file_name,FILE_COMMON) == true)
     {
      int handle = FileOpen(file_name, FILE_READ|FILE_BIN|FILE_COMMON);

      if(handle == INVALID_HANDLE)
        {
         Print("ERROR: Cannot open file for reading! Error: ", GetLastError());
         return 0;

        }
      else
         if(handle != INVALID_HANDLE)
           {

            Print("File successfully opened for reading");
            Print("---- Printing File Content ----");

            FileReadStruct(handle, myEA_read);

            Print("Take Profit: ", myEA_read.TakeProfit);
            Print("Stop Loss: ", myEA_read.StopLoss);
            Print("LotSize: ", myEA_read.LotSize);
            Print("RSI LEVEL: ",  myEA_read.RSI_Level);
            Print("Max Trades: ",  myEA_read.MaxTrades);

            FileClose(handle);
           }
     }

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   EAParameters myEA;

   myEA.TakeProfit = TP;
   myEA.StopLoss = SL;
   myEA.LotSize = LOT;
   myEA.RSI_Level = RSI;
   myEA.MaxTrades = MAX_T;

   int handle = FileOpen(file_name, FILE_WRITE|FILE_BIN|FILE_COMMON);

   if(handle == INVALID_HANDLE)
     {
      Print("ERROR: Cannot open file to save new version! Error: ", GetLastError());
      return;
     }

   else
      if(handle != INVALID_HANDLE)
        {
         Print("File successfully opened");

         int file_size = (int)FileSize(handle);

         if(file_size == 0)
           {
            FileWriteStruct(handle, myEA);
           }
         FileClose(handle);
        }

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }

Пояснение:

Имя файла присваивается переменной, чтобы упростить чтение и запись. Если поместить это объявление в глобальную область видимости, оно будет доступно всем обработчикам событий советника, включая OnInit и OnDeinit. Такой подход обеспечивает единообразное использование файла и избавляет от необходимости заново задавать его имя в разных участках кода. На этапе инициализации советник создает структуру, в которую могут быть загружены данные из файла. Далее проверяется, существует ли файл. Если файл существует, советник открывает его и загружает в структуру ранее сохраненные параметры. Благодаря этому при перезапуске советник может восстановить предыдущую конфигурацию, а не полагаться только на входные параметры по умолчанию.

Если файл уже существует, мы открываем его в режиме чтения с помощью FileOpen и нужных флагов. Мы открываем файл с помощью функции FileOpen. Для этой функции нужны имя файла и набор флагов, определяющих режим его использования. В этом случае используются FILE_READ, FILE_BIN и FILE_COMMON. Ключевое различие здесь в том, что используется FILE_READ, а не FILE_WRITE. Это указывает MQL5, что данные из файла нужно только читать.

Флаг FILE_COMMON помещает файл в общую директорию, чтобы к нему могли обращаться несколько терминалов, а флаг FILE_BIN позволяет платформе обрабатывать файл как необработанные данные, а не как текст. Сообщения журнала показывают, что файл готов и что после успешного открытия его содержимое будет выведено. В следующей строке структура считывается из файла с помощью FileReadStruct. В эту функцию передаются два аргумента: хэндл файла и переменная-структура myEA_read. После чтения блока необработанных данных из файла функция заполняет члены myEA_read соответствующими значениями в том же порядке и с теми же типами, в каком они были записаны.

После чтения данных программа с помощью Print выводит каждый член myEA_read. Так можно проверить, что значения были корректно считаны из бинарного файла. Поскольку в каждой строке выводится имя члена и его значение, можно увидеть Take Profit, Stop Loss, Lot Size, RSI Level и Max Trades в точности в том виде, в каком они были сохранены. Для проверки сохраненных значений после чтения данных программа выводит в терминал каждый член структуры. После этого файл закрывается, чтобы освободить хэндл файла и гарантировать, что позже его можно будет безопасно прочитать.

Вывод:

Figure 5. Read Binary File


Заключение

Мы разобрали простой и практичный механизм постоянного хранения данных советника в MQL5. Ключевые моменты:

  • Контейнер параметров должен представлять собой простую структуру из типов фиксированного размера (double, int, bool, вложенные простые структуры). Для прямого бинарного ввода-вывода не используйте строки, динамические массивы, указатели и объекты классов.
  • Сохраняйте структуру в общий бинарный файл как необработанные данные (например, EA_parameters.bin в FILE_COMMON) с помощью FileOpen(…, FILE_WRITE|FILE_BIN|FILE_COMMON) и FileWriteStruct().
  • Считывайте ее обратно с помощью FileOpen(…, FILE_READ|FILE_BIN|FILE_COMMON) и FileReadStruct(), следя за тем, чтобы структура для чтения имела тот же порядок полей и те же типы полей (имена могут отличаться).
  • Всегда проверяйте INVALID_HANDLE, при необходимости обрабатывайте FileSize и вызывайте FileClose(), чтобы предотвратить повреждение данных.

Соблюдение этих правил дает надежный и повторяемый механизм постоянного хранения – бинарный файл и соответствующие процедуры чтения и записи на основе структур, благодаря которым несколько терминалов могут совместно использовать параметры советника между сеансами.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21728

Прикрепленные файлы |
Рыночные секреты Ларри Уильямса (Часть 1): Создание индикатора свинговой структуры рынка в MQL5 Рыночные секреты Ларри Уильямса (Часть 1): Создание индикатора свинговой структуры рынка в MQL5
Практическое руководство по созданию индикатора рыночной структуры в стиле Ларри Уильямса на MQL5: настройка буферов, определение свинговых точек, настройка графических построений и применение индикатора трейдерами в техническом анализе рынка.
Разработка инструментария для анализа Price Action (Часть 64): Синхронизация вручную построенных трендовых линий с автоматическим мониторингом Разработка инструментария для анализа Price Action (Часть 64): Синхронизация вручную построенных трендовых линий с автоматическим мониторингом
Мониторинг построенных вручную трендовых линий требует постоянного наблюдения за графиком, поэтому легко пропустить важные взаимодействия цены с ними. В этой статье разрабатывается советник для мониторинга трендовых линий, который синхронизирует построенные вручную трендовые линии с логикой автоматического мониторинга на MQL5 и генерирует алерты, когда цена приближается к отслеживаемой линии, касается ее или пробивает ее.
Создание  профессиональной торговой системы на основе Heikin Ashi (Часть 1): Разработка пользовательского индикатора Создание профессиональной торговой системы на основе Heikin Ashi (Часть 1): Разработка пользовательского индикатора
Эта статья — первая часть серии из двух материалов, предназначенной для освоения практических навыков и лучших практик написания пользовательских индикаторов на MQL5. На практическом примере Heikin Ashi в статье рассматривается теория графиков Heikin Ashi, объясняется, как рассчитываются свечи Heikin Ashi, и показывается их применение в техническом анализе. Центральная часть материала — пошаговое руководство по разработке полнофункционального индикатора Heikin Ashi с нуля, с понятными пояснениями, которые помогают читателям разобраться, что именно писать в коде и почему. Эти базовые знания подготовят почву для второй части, где мы создадим советник, торгующий на основе логики Heikin Ashi.
Инжиниринг признаков для машинного обучения (Часть 3): Временные признаки с учетом торговых сессий на рынке Forex Инжиниринг признаков для машинного обучения (Часть 3): Временные признаки с учетом торговых сессий на рынке Forex
В статье рассматривается потеря временной информации в ML-конвейерах: периодические временные переменные кодируются гармониками Фурье, а структура Forex-сессий добавляется отдельными признаками. Реализуются флаги сессий и перекрытий, лагированная сессионная волатильность и календарные эффекты, после чего признаки отбираются с учетом таймфрейма. Функция get_time_features возвращает выровненный по индексу, готовый для ML набор временных признаков, пригодный для объединения с ценовыми сигналами.