English
preview
Использование Экономического календаря MQL5 для фильтрации новостей (Часть 3): Сохранение работы при перезапусках терминала во время новостного окна

Использование Экономического календаря MQL5 для фильтрации новостей (Часть 3): Сохранение работы при перезапусках терминала во время новостного окна

MetaTrader 5 — Трейдинг |
75 0
Solomon Anietie Sunday
Solomon Anietie Sunday

Введение

Проблема — потеря состояния при перезапуске

В части 2 этой серии уже был реализован новостной фильтр, который временно снимает SL/TP и восстанавливает их после новостного окна. Однако существующая реализация хранит состояние только в памяти: в массиве savedStops[] и флаге newsSuspended. Если между вызовами SuspendStops(true) и SuspendStops(false) происходит перезапуск или перекомпиляция, это состояние теряется. Советник больше не может определить, у каких позиций были удалены стоп-уровни. Результат: открытые позиции могут остаться незащищенными, а их стопы — так и не быть восстановлены. Без механизма восстановления система не сможет безопасно восстановить прежнее состояние.

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

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

State loss visual illustration

Решение — постоянное хранилище на основе глобальных переменных терминала

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

Это именованные переменные типа double, хранящиеся в самом терминале, доступные из любого советника и сохраняющиеся даже после закрытия и повторного запуска терминала. Благодаря им при перезапуске советника можно полностью реконструировать состояние приостановки.


Проектирование модели постоянного хранилища

Прежде чем реализовать механизм сохранения состояния, необходимо четко определить, что именно нужно сохранять, где будут храниться данные и как будет организована их структура. Цель состоит не только в сохранении данных, но и в обеспечении возможности восстановления точного рабочего состояния советника после перезапуска.

Зачем нужны глобальные переменные терминала?

В MQL5 существует несколько способов хранения данных: в файлах, во входных параметрах либо в статических или глобальных переменных в памяти. Однако в данной реализации используются глобальные переменные терминала. Это специальные пары "ключ-значение", хранящиеся в самом торговом терминале.

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

Что необходимо хранить?

Чтобы полностью восстановить работу системы после перезапуска, необходимо сохранить три критически важных аспекта: приостановлены ли сделки, какие сделки затронуты и каковы исходные значения стоп-лосса (SL) и тейк-профита (TP) для каждой сделки. Без всех трех этих составляющих восстановление будет неполным.

Для каждой сделки, по которой стоп-лосс и тейк-профит были приостановлены, необходимо сохранить три элемента данных:

  • номер тикета для идентификации позиции;
  • исходный стоп-лосс для последующего восстановления;
  • исходный тейк-профит для последующего восстановления.

Также сохраняются количество приостановленных сделок и флаг активного цикла приостановки, хотя его можно вывести из сохраненных данных.

Компоненты хранилища

Сохраняемое состояние разделяется на две части:

  1. Флаг состояния системы: он сообщает советнику, находилась ли система в окне приостановки до перезапуска. Например, можно использовать переменную Harbinger_NewsSuspended, где значение 1 означает приостановку системы, а 0 — обычный режим работы.
  2. Хранение стопов по каждой позиции: для каждой приостановленной позиции необходимо сохранить ее тикет, SL и TP. Поскольку глобальные переменные терминала хранят только значения типа double, каждое поле будет храниться отдельно.
Соглашение об именовании

Для глобальных переменных терминала будет использоваться структурированная схема именования с сериализованной индексацией. Для индекса позиции i, начиная с 0, создаются три переменные:

Harbinger_Stop_<i>_Ticket

Harbinger_Stop_<i>_SL

Harbinger_Stop_<i>_TP

Такой подход обеспечивает простую индексацию, удобное восстановление, четкое соответствие между сделками и стоп-уровнями и не требует сложной сериализации.

Пример:

Если приостановлены две сделки, структура выглядит наглядно:

// Trade 1
Harbinger_Stop_0_Ticket   = 123456
Harbinger_Stop_0_SL       = 1.08500
Harbinger_Stop_0_TP       = 1.09500

// Trade 2
Harbinger_Stop_1_Ticket   = 123789
Harbinger_Stop_1_SL       = 1.38500
Harbinger_Stop_1_TP       = 1.39500

Все значения хранятся в типе double. Хотя тикет является целым числом, его можно безопасно преобразовывать в double и обратно: глобальные переменные MT5 имеют двойную точность и точно представляют числа до 2^53, что значительно превышает любой номер тикета.

Дополнительная управляющая переменная

Чтобы знать, сколько сделок сохранено, также определяется дополнительная переменная-счетчик:

Harbinger_Stop_Count

При перезапуске советник считывает Harbinger_Stop_Count и выполняет цикл от i = 0 до count - 1, чтобы восстановить savedStops[].

Пояснение — префикс "Harbinger"

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

Рабочий процесс с сохранением состояния

Теперь жизненный цикл приостановки на время новостей выглядит следующим образом.

Новостное окно активно — вызывается функция SuspendStops(true):
  • функция очищает ранее сохраненный массив стопов;
  • перебирает открытые позиции, сохраняя тикет, SL и TP каждой из них в savedStops[] и снимая стоп-уровни;
  • сразу после этого вызывается SaveStopsToGlobals(), записывающая весь массив в глобальные переменные терминала.

Терминал перезапускается во время новостного окна — советник перезагружается:

  • в функции OnInit() вызывается LoadStopsFromGlobals();
  • если существует глобальная переменная-счетчик, все сохраненные стоп-уровни считываются в savedStops[], а переменной newsSuspended присваивается true;
  • теперь советник точно знает, для каких позиций действует приостановка и какими были их исходные стоп-уровни.

Новостное окно завершается — вызывается SuspendStops(false):

  • функция использует savedStops[] для восстановления стоп-уровней каждой сделки с помощью RestoreStops();
  • после восстановления функция очищает массив и вызывает ClearSavedStopsFromGlobals(), чтобы удалить все связанные глобальные переменные.

Благодаря этой модели советник может возобновить работу именно с того места, на котором остановился, независимо от момента перезапуска.

Почему бы не использовать файлы как постоянное хранилище?

Некоторые разработчики могут задаться вопросом, почему глобальные переменные терминала были выбраны вместо хранения данных в файлах, например в CSV- или INI-файле. Использование файлов — приемлемый способ постоянного хранения данных, однако для данной задачи оно создает ненужную сложность.

  • Простота: глобальными переменными напрямую управляет терминал MetaTrader. Не требуется обрабатывать пути к файлам, проблемы с правами доступа или пользовательские форматы сериализации.
  • Скорость: чтение и запись глобальных переменных значительно быстрее файлового ввода-вывода. Это важно, хотя операции выполняются лишь при переходе состояния, один раз за новостное окно: накладные расходы все равно следует минимизировать.
  • Автоматическая синхронизация: терминал автоматически сохраняет глобальные переменные на диск, и они сохраняются после перезапуска. При работе с файлами необходимо вручную управлять сбросом буферов, закрытием хэндлов и повторным открытием файлов после перезапуска.
  • Отсутствие внешних зависимостей: код остается самодостаточным.

Для нескольких экземпляров одного советника на разных графиках схему именования можно расширить, включив магическое число, например Harbinger_Stop_<magic>_<i>_Ticket. Глобальные переменные терминала остаются подходящим инструментом; использование файлов лишь усложнило бы задачу.

Ограничения и замечания

  1. Глобальные переменные терминала действуют на уровне всего терминала. Если несколько экземпляров одного советника запущены на разных графиках, они используют одни и те же глобальные переменные терминала. Этого можно избежать, добавив магическое число в имена переменных. В данной реализации предполагается один экземпляр на символ; при необходимости схему можно расширить, например: Harbinger_Stops_<magic>_<i>_Ticket.
  2. Ограничение по типу данных: глобальные переменные терминала хранят только значения типа double. Тикеты хранятся как значения типа double. Это безопасно, поскольку номера тикетов являются целыми числами и представляются в double без потери точности.
  3. Очистка: глобальные переменные терминала следует удалять после завершения новостного окна, чтобы избежать накопления устаревших данных. За это отвечает ClearSavedStopsFromGlobals().
  4. Восстановление при перезапуске: при загрузке сохраненных стоп-уровней в OnInit() они не восстанавливаются автоматически — восстанавливается только состояние советника в памяти. Фактическое восстановление происходит только при вызове SuspendStops(false) после завершения новостного окна. Это сделано намеренно, поскольку неизвестно, активно ли новостное окно в данный момент; на следующем тике это обработает штатная логика перехода состояний.

Эти ограничения подчеркивают необходимость слоя постоянного хранения, способного восстанавливать состояние системы после непредвиденных сбоев.

Что рассматривается в статье

Эта статья посвящена только добавлению сохранения состояния в систему приостановки стоп-уровней из части 2. Механизм обнаружения новостей, логика переходов и правила восстановления не изменяются. Добавляются только:

  • три новые функции — SaveStopsToGlobals(), LoadStopsFromGlobals() и ClearSavedStopsFromGlobals();
  • вызовы этих функций в соответствующих местах SuspendStops() и OnInit();
  • незначительные корректировки, обеспечивающие правильное восстановление флага состояния newsSuspended.

Все остальные части советника остаются без изменений.



Реализация

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

Функция SaveStopsToGlobals()

Шаг 1 — запись данных в глобальные переменные терминала

Первый шаг — сохранить состояние приостановки и данные стоп-уровней в момент приостановки сделок. В MQL5 для этого используется:

GlobalVariableSet(name, value);

Эта функция создает или обновляет переменную уровня терминала, сохраняющуюся после завершения работы советника.

Шаг 2 — сохранение состояния приостановки

Перед сохранением отдельных сделок сначала сохраняется общее состояние системы. После перезапуска это сообщает советнику: "Система находилась в окне приостановки".

GlobalVariableSet("Harbinger_NewsSuspended", 1);

// If the system is not suspended
GlobalVariableSet("Harbinger_NewsSuspended", 0);

Шаг 3 — сохранение стоп-уровней по каждой сделке и формирование имен переменных

Теперь сохраняются тикет, стоп-лосс и тейк-профит каждой сделки. Выполняется перебор массива savedStops[], уже сформированного при вызове SuspendStops(true).

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

Пример:

string base = "Harbinger_Stop_" + IntegerToString(i);

// then
base + "_Ticket"
base + "_SL"
base + "_TP"

Реализация функции SaveStopsToGlobals() состоит из трех этапов. Теперь все объединяется в структурированную функцию:

//+------------------------------------------------------------------+
//| Global Variable Management for Persistent Stops, Function 1/3    |
//+------------------------------------------------------------------+
void SaveStopsToGlobals()
  {
   int count = ArraySize(savedStops);

   GlobalVariableSet("Harbinger_Stop_Count", count);
   GlobalVariableSet("Harbinger_NewsSuspended", newsSuspended ? 1 : 0);

   for(int i = 0; i < count; i++)
     {
      string base = "Harbinger_Stop_" + IntegerToString(i);

      GlobalVariableSet(base + "_Ticket", (double)savedStops[i].ticket);
      GlobalVariableSet(base + "_SL", savedStops[i].sl);
      GlobalVariableSet(base + "_TP", savedStops[i].tp);
     }
     Print("Persistent stop state saved.");
  }

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

На данном этапе лишь создается точка восстановления, а система получает состояние, сохраняющееся вне времени выполнения.

Функция LoadStopsFromGlobals()

К этому моменту решена половина проблемы сохранения состояния. Состояние приостановки и данные о сделках можно сохранить, но система останется незавершенной, пока это состояние нельзя восстановить после перезапуска.

Само по себе сохранение данных не делает систему устойчивой к перезапуску. После перезапуска, когда советник выполняет OnInit(), переменные в памяти сбрасываются. Поэтому советник теряет исторический контекст, хотя терминал по-прежнему хранит необходимые данные:

Harbinger_Stop_Count
Harbinger_Stop_<i>_Ticket
Harbinger_Stop_<i>_SL
Harbinger_Stop_<i>_TP
Harbinger_NewsSuspended

Советник должен явно считать и восстановить свое внутреннее состояние. Без этого шага стоп-уровни останутся удаленными, массив savedStops[] останется пустым, а newsSuspended по умолчанию будет равно false. Это возвращает к исходной проблеме потери состояния.

LoadStopsFromGlobals() определяет наличие сохраненных данных, заново формирует savedStops[] и подготавливает советник к продолжению работы. Давайте реализуем следующие шаги.

Шаг 1 — обнаружение существующих сохраненных данных

Сначала проверяется, была ли активная приостановка перед перезапуском. Для этого используется переменная-счетчик:

if(!GlobalVariableCheck("Harbinger_Stop_Count"))
  {
  Print("No persistent stop data found.");
  return;
  }

Если эта переменная отсутствует, во время перезапуска приостановка не была активна и восстанавливать нечего.

Шаг 2 — восстановление массива savedStops[]

Сначала считывается сохраненное значение счетчика, если переменная существует. Это позволяет определить, сколько сделок было затронуто и сколько записей необходимо восстановить. Затем изменяется размер массива, выполняется перебор каждой сохраненной сделки и восстановление массива в памяти:

// Retrieve variable if it exists
int count = (int)GlobalVariableGet("Harbinger_Stop_Count");

// Resize the array
ArrayResize(savedStops, count);

// Loop through each stored trade and rebuild array
for (int i = 0; i < count; i++)
  {
  string base = "Harbinger_Stop_" + IntegerToString(i);
  
  savedStops[i].ticket = (ulong)GlobalVariableGet(base + "_Ticket");
  savedStops[i].sl = GlobalVariableGet(base + "_SL");
  savedStops[i].tp = GlobalVariableGet(base + "_TP");
  }

Советник полностью восстановил все приостановленные сделки, и все позиции снова представлены во внутреннем состоянии программы.

Шаг 3 — восстановление состояния приостановки

Наконец, восстанавливается флаг системного уровня:

if (GlobalVariableCheck("Harbinger_NewsSuspended"))
  {
  newsSuspended = (GlobalVariableGet("Harbinger_NewsSuspended") == 1);
  }

Теперь советник знает, находится ли он по-прежнему в цикле приостановки. Теперь все это объединяется в структурированную реализацию:

//+------------------------------------------------------------------+
//| Global Variable Management for Persistent Stops, Function 2/3    |
//+------------------------------------------------------------------+
void LoadStopsFromGlobals()
  {
   if(!GlobalVariableCheck("Harbinger_Stop_Count"))
     {
      Print("No persistent stop data found.");
      return;
     }

   int count = (int)GlobalVariableGet("Harbinger_Stop_Count");
   if(count <= 0)
     {
      Print("Persistent stop count is zero.");
      return;
     }

   ArrayResize(savedStops, count);
   for(int i = 0; i < count; i++)
     {
      string base = "Harbinger_Stop_" + IntegerToString(i);
      savedStops[i].ticket = (ulong)GlobalVariableGet(base + "_Ticket");
      savedStops[i].sl = GlobalVariableGet(base + "_SL");
      savedStops[i].tp = GlobalVariableGet(base + "_TP");
     }

   if(GlobalVariableCheck("Harbinger_NewsSuspended"))
      newsSuspended = (GlobalVariableGet("Harbinger_NewsSuspended") == 1);
   Print("Persistent stop state loaded. Count: ", count);
  }

Эта функция вызывается в OnInit(). Стоп-уровни не восстанавливаются сразу после загрузки: восстанавливаются только данные в памяти и системный флаг состояния, поскольку неизвестно, активно ли еще новостное окно, а немедленное восстановление может нарушить логику. Вместо этого советник ожидает обычного хода работы, а после завершения новостного окна восстановление выполняет SuspendStops(false). Благодаря этому система остается детерминированной, согласованной и соответствует исходной рабочей логике.

По завершении этого этапа система способна выдерживать перезапуск терминала, точно восстанавливать приостановленные сделки и сохранять логическую непрерывность. Далее необходимо очистить сохраняемые данные с помощью ClearSavedStopsFromGlobals(), то есть корректно удалить глобальные переменные терминала.

Функция ClearSavedStopsFromGlobals()

На данном этапе создан цикл с сохранением состояния: данные о приостановке сохраняются до наступления периода повышенного риска, а после перезапуска загружаются, и советник корректно возобновляет работу. Однако если сохраненные глобальные переменные терминала не удалить, советник может обнаружить устаревшие данные даже при неактивной приостановке. В результате может быть заново сформирован устаревший savedStops[], а операции будут выполняться с несуществующими позициями. Глобальные переменные терминала сохраняются на неопределенный срок, если их явно не удалить. Чтобы этого не произошло, необходимо обеспечить немедленное удаление всех сохраняемых данных после восстановления стоп-уровней. Это гарантирует, что каждый цикл приостановки будет самодостаточным, без утечки состояния в следующие циклы.

Теперь ClearSavedStopsFromGlobals() реализуется в три шага: удаляются все сохраненные переменные по сделкам, переменная-счетчик и сбрасывается флаг приостановки.

Шаг 1 — получение сохраненного значения счетчика

Прежде чем что-либо удалять, необходимо узнать, сколько записей существует. Как и на этапе загрузки, сначала проверяется переменная-счетчик.

void ClearSavedStopsFromGlobals()
  {
   if(!GlobalVariableCheck("Harbinger_Stop_Count"))
     {
      Print("No persistent data to clear.");
      return;
     }

Если переменная отсутствует, очищать нечего, и функция безопасно завершает работу.

Шаг 2 — удаление переменных по каждой сделке

После подтверждения наличия данных считывается значение счетчика и выполняется перебор всех сохраненных записей:

int count = (int)GlobalVariableGet("Harbinger_Stop_Count");

   for(int i = 0; i < count; i++)
     {
      string base = "Harbinger_Stop_" + IntegerToString(i);

      GlobalVariableDel(base + "_Ticket");
      GlobalVariableDel(base + "_SL");
      GlobalVariableDel(base + "_TP");
     }

Каждая запись по сделке удаляется отдельно, чтобы в терминале не оставалось остаточных привязок.

Шаг 3 — удаление управляющих переменных

После очистки данных по отдельным сделкам необходимо также удалить управляющие переменные, определяющие состояние системы:

GlobalVariableDel("Harbinger_Stop_Count");
GlobalVariableDel("Harbinger_NewsSuspended");

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

Теперь все эти шаги объединяются в полноценную функцию:

//+------------------------------------------------------------------+
//| Global Variable Management for Persistent Stops, Function 3/3    |
//+------------------------------------------------------------------+
void ClearSavedStopsFromGlobals()
  {
   if(!GlobalVariableCheck("Harbinger_Stop_Count"))
     {
      Print("No persistent data to clear.");
      return;
     }

   int count = (int)GlobalVariableGet("Harbinger_Stop_Count");

   for(int i = 0; i < count; i++)
     {
      string base = "Harbinger_Stop_" + IntegerToString(i);

      GlobalVariableDel(base + "_Ticket");
      GlobalVariableDel(base + "_SL");
      GlobalVariableDel(base + "_TP");
     }

   GlobalVariableDel("Harbinger_Stop_Count");
   GlobalVariableDel("Harbinger_NewsSuspended");

   Print("Persistent stop data cleared.");
  }

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



Интеграция постоянного хранения в новостной фильтр

Предусмотрены две точки интеграции.

  1. В SuspendStops() добавляется механизм сохранения состояния.
  2. В OnInit() добавляется восстановление состояния.

В SuspendStops() SaveStopsToGlobals() вызывается после удаления стоп-уровней, а ClearSavedStopsFromGlobals() — после их восстановления.

//+------------------------------------------------------------------+
//| Suspend Stops During News                                        |
//+------------------------------------------------------------------+
void SuspendStops(bool suspend)
  {
   if(suspend == newsSuspended)
      return; // Already in the desired state


   newsSuspended = suspend;
   if(suspend)
     {
      ArrayResize(savedStops, 0); // Clear previous list
      for(int i = PositionsTotal() - 1; i >= 0; i--)
        {
         ulong ticket = PositionGetTicket(i);
         if(PositionSelectByTicket(ticket))
           {
            // Save current SL/TP
            int idx = ArraySize(savedStops);
            ArrayResize(savedStops, idx + 1);
            savedStops[idx].ticket = ticket;
            savedStops[idx].sl = PositionGetDouble(POSITION_SL);
            savedStops[idx].tp = PositionGetDouble(POSITION_TP);

            // Remove stops from broker side
            if(trade.PositionModify(ticket, 0.0, 0.0))
              {
               PrintFormat("Suspended trade #%I64u: SL/TP removed for news.", ticket);
              }
           }
        }

      // PERSISTENT: SAVE STATE IMMEDIATELY AFTER SUSPENSION
      SaveStopsToGlobals();
     }
   else // Unfreeze
     {
      for(int k = 0; k < ArraySize(savedStops); k++)
        {
         RestoreStops(savedStops[k].ticket,
                      savedStops[k].sl,
                      savedStops[k].tp);
        }
      ArrayResize(savedStops, 0);

      // PERSISTENT: CLEAR SAVED STOPS AFTER RESTORATION
      ClearSavedStopsFromGlobals();
     }
  }

Далее функция OnInit() дополняется вызовом LoadStopsFromGlobals().

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("News Filter Part1 initialized");
   
   // LOAD PERSISTENT STATE AFTER RESTART
   LoadStopsFromGlobals();
   if(ArraySize(savedStops) > 0)
   {
      Print("Recovered suspended trades from previous session.");
   }
   
// Pre-load calendar if enabled to avoid lag on first tick
   if(EnableNewsFilter)
      LoadTodayCalendarEvents();
   return INIT_SUCCEEDED;
  }

После интеграции советник с новостным фильтром сохраняет состояние при перезапуске терминала или самого советника. Например:

Начинается новостное окно, и если вызывается SuspendStops(true):

  • стоп-уровни снимаются;
  • данные сохраняются в памяти;
  • данные из памяти сохраняются в глобальных переменных терминала.

Терминал перезапускается:

  • OnInit() вызывает LoadStopsFromGlobals();
  • система возобновляет работу с полностью восстановленным состоянием приостановки.

Новостное окно завершается, вызывается SuspendStops(false):

  • стоп-уровни восстанавливаются;
  • сохраненные данные удаляются из постоянного хранилища.

Если советник удаляется во время приостановки, состояние системы сохраняется, и восстановление по-прежнему возможно.

Как обрабатываются пограничные случаи

Хорошо продуманная система должна работать корректно даже в нестандартных ситуациях. Механизм приостановки стоп-уровней с сохранением состояния обрабатывает несколько пограничных случаев:

  1. Приостановлено несколько позиций: массив savedStops[] хранит каждую сделку отдельно. При сохранении перебираются все позиции, а при загрузке воссоздается точный список.
  2. Сделки, вручную закрытые во время приостановки: если позиция закрыта трейдером или другим советником в течение окна приостановки, она уже не будет существовать при попытке восстановить ее стоп-уровни. RestoreStops() сначала проверяет PositionSelectByTicket(); если тикет недействителен, соответствующая запись пропускается. После восстановления массив savedStops[] очищается, чтобы не сохранялись устаревшие ссылки.
  3. Перезапуск после завершения новостного окна: если советник перезапускается после завершения новостного окна, глобальные переменные уже удалены функцией ClearSavedStopsFromGlobals() в конце предыдущего цикла приостановки. Следовательно, LoadStopsFromGlobals() не найдет сохраненных данных, и советник запустится с newsSuspended = false — именно так, как и должно быть.
  4. Сбой терминала во время приостановки: реализация статьи непосредственно обрабатывает этот случай.
  5. Цена проходит через исходный стоп-уровень до его восстановления: эта ситуация уже обрабатывается защитной корректировкой в RestoreStops(). Если цена уже пересекла сохраненные SL/TP, функция устанавливает стоп-уровень на ближайшем допустимом уровне перед ценой с учетом правил брокера о минимальном расстоянии.
Эти проектные решения позволяют системе надежно работать в реальных условиях.

Тестирование реализации

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

Установите советник на график с EnableNewsFilter = true, SuspendStopsDuringNews = true и небольшим новостным окном, например NewsMinutesBefore = 1 и NewsMinutesAfter = 1 для быстрого тестирования. Автор использовал 15 минут до и после новости, чтобы получить достаточно большое окно для оценки ситуации. Убедитесь, что у советника есть хотя бы одна открытая позиция с видимыми уровнями стоп-лосса и тейк-профита.

Setting up News Filter EA for testing

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

News Filter gets initialized

Когда начинается соответствующее новостное окно, у всех открытых позиций снимаются уровни SL/TP.

Пока новостное окно активно, имитируется перезапуск: советник перекомпилируется через Навигатор → клик правой кнопкой по советнику → "Изменить" → "Скомпилировать" либо перезапускается терминал. Автор просто изменил входной параметр, а затем перекомпилировал советник. На изображении ниже видно, что стоп-уровни были удалены на время новостного окна, а позднее советник повторно инициализировался уже внутри активного новостного окна.

Expert log showing SL/TP suspension

Дождитесь окончания новостного окна. В логе будут показаны восстановленные SL/TP — этого бы не произошло без постоянного хранения значений стоп-уровней. Ниже показано изображение с восстановленными стоп-уровнями:

News window ends, and stops are restored

Для полного представления все этапы объединены в один скриншот, показывающий весь процесс:

Full cycle log

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



Заключение

Проблема потери состояния устранена сохранением снимка состояния приостановки в глобальных переменных терминала. Механизм восстановления устроен следующим образом: SaveStopsToGlobals() записывает состояние, LoadStopsFromGlobals() восстанавливает его, а ClearSavedStopsFromGlobals() удаляет его после восстановления. SaveStopsToGlobals() вызывается сразу после завершения SuspendStops(true), LoadStopsFromGlobals() выполняется в OnInit(), а ClearSavedStopsFromGlobals() — после восстановления SL/TP функцией SuspendStops(false).

Практический результат: терминал можно перезапустить, советник можно перекомпилировать или VPS можно перезагрузить во время активного новостного окна, а советник восстановит точный список приостановленных тикетов и их исходные SL/TP. Советник не восстанавливает стоп-уровни автоматически при загрузке; он лишь восстанавливает внутреннее состояние, чтобы обычный жизненный цикл приостановки и защитные проверки, такие как пропуск закрытых тикетов или корректировка недопустимых уровней, выполнялись детерминированно. Для воспроизводимости проверьте наличие глобальных переменных Harbinger_* в терминале, восстановление savedStops[] в OnInit() и окончательную очистку глобальных переменных после восстановления.

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

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

Прикрепленные файлы |
Разработка системы оценки портфеля из нескольких советников с учетом корреляции в MQL5 Разработка системы оценки портфеля из нескольких советников с учетом корреляции в MQL5
Большинство алгоритмических трейдеров оптимизируют советники по отдельности, но никогда не оценивают, как они работают вместе на одном счете. Коррелированные стратегии усиливают просадки, а не снижают их, пробелы в покрытии создают в портфеле слепые зоны на целые торговые сессии. В этой статье мы создадим полноценный оценщик портфеля на MQL5. Он считывает дневные P&L из CSV-файлов бэктестов, вычисляет полную матрицу корреляции Пирсона, строит карту торговой активности по часам и дням недели, оценивает диверсификацию по классам активов и выдает сводную оценку от A+ до F. Весь исходный код прилагается, внешние библиотеки не требуются.
Разработка научно обоснованного сеточного советника в MQL5: Почему большинство сеточных советников терпят неудачу и что доказал Таранто Разработка научно обоснованного сеточного советника в MQL5: Почему большинство сеточных советников терпят неудачу и что доказал Таранто
В этой статье реализуется сеточный советник, адаптирующийся к режиму рынка и основанный на докторском исследовании Альдо Таранто. Представлен сеточный советник, адаптирующийся к режиму рынка и ограничивающий риск с помощью перезапускаемых циклов и защитных механизмов на основе эквити. Мы объясняем, почему наивные сетки терпят неудачу (из-за роста дисперсии и почти верного разорения), выводим выводим формулу убытка для расчета текущей экспозиции и реализуем фильтрацию запуска с учетом режима рынка, динамический шаг сетки на основе ATR и аварийный выключатель, работающий в реальном времени. Читатели получат математические инструменты и пригодные для рабочей эксплуатации подходы, необходимые для безопасной разработки, тестирования и эксплуатации сеточной стратегии с ограничениями.
От новичка до эксперта: Автоматизация внутридневных стратегий От новичка до эксперта: Автоматизация внутридневных стратегий
Мы переводим идею ретеста EMA(50) в советник для внутридневной торговли, основанный на поведении цены. В исследовании формализуются трендовый уклон, взаимодействие с EMA (проколоть/пробить и закрыться), подтверждение реакции и дополнительные фильтры, после чего они реализуются в MQL5 с использованием модульных функций и дескрипторов с безопасным управлением ресурсами. Визуальное тестирование в Тестере стратегий позволяет проверить корректность сигналов. В результате получается понятный шаблон для кодирования дискреционных отскоков.
Единый пайплайн валидации для защиты от переобучения бэктеста Единый пайплайн валидации для защиты от переобучения бэктеста
В статье объясняется, почему стандартный метод пошагового тестирования (walk-forward) и k-блочная кросс-валидация завышают результаты на финансовых данных, а затем показано, как это исправить. Валидация внутри валидации (V-in-V) обеспечивает строгое разбиение данных и пошаговое тестирование с фиксированным началом (anchored) между окнами. Комбинаторно очищенная кросс-валидация (CPCV) устраняет утечки данных с помощью очистки и эмбарго, одновременно агрегируя результативность на уровне траекторий бэктеста. Комбинаторно симметричная кросс-валидация (CSCV) измеряет вероятность переобучения бэктеста (PBO). Практикующие специалисты получают целостную систему для оценки устойчивости к рыночным режимам и надежности отбора кандидатов.