English
preview
Разработка самовосстанавливающегося советника в MQL5 (Часть 2): Виртуальная защита сделок, устойчивая к перезапуску

Разработка самовосстанавливающегося советника в MQL5 (Часть 2): Виртуальная защита сделок, устойчивая к перезапуску

MetaTrader 5Эксперты |
79 0
Chacha Ian Maroa
Chacha Ian Maroa

Введение

В Части 1 мы заложили основу для сохранения данных нашего самовосстанавливающегося советника, используя SQLite в MetaTrader 5. Это позволяет советнику постоянно хранить рабочие состояния сделок и беспрепятственно восстанавливать их после перезапуска или сбоя терминала.

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

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

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

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

В этой части мы расширяем такую архитектуру, вводя виртуальную защиту сделок, безопасную при перезапуске. Цель этой статьи заключается в создании системы восстановления, способной:
  • Обнаруживать управляемые позиции после перезапуска.
  • Восстанавливать скрытые уровни виртуальной защиты из SQLite.
  • Проверять, были ли виртуальные уровни выхода пересечены, пока советник был отключен.
  • Безопасно продолжать управление сделками в рабочем режиме после восстановления.

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


Разбор сценария отказа виртуальной защиты

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

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

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

  1. Советник открывает сделку на покупку.
  2. Виртуальный стоп-лосс хранится внутри системы.
  3. Терминал выключается.
  4. Цена пересекает скрытый стоп-лосс.
  5. Советник перезапускается. Сделка неожиданно остается открытой.

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

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

Это создает опасное несоответствие между фактической ситуацией на рынке и последним известным состоянием во время работы советника. В Части 1 мы решили проблему персистентности, храня восстанавливаемую информацию о состоянии сделки в SQLite. Однако, одной персистентности всё же недостаточно. После перезапуска советник должен сверить восстановленные уровни виртуальной защиты с текущей рыночной ценой, прежде чем возобновить обычное управление сделками.


Расширение архитектуры восстановления для виртуальной защиты сделок

В этой статье мы расширяем архитектуру восстановления, разработанную в Части 1. Ранее мы использовали SQLite для создания основы для сохранения состояния, необходимой для восстановления после перезапуска. Благодаря этой конфигурации советник может сохранять и восстанавливать состояния сделок после выключения терминала.

Эта статья опирается на основу, созданную в Части 1. Сначала пройдите Часть 1, поскольку остальная часть серии основана на той же кодовой базе. Теперь наше внимание переключается на безопасную виртуальную защиту сделок после перезапуска. Мы начнем с создания системы обнаружения позиций на стороне брокера, используемой в процессе восстановления.


Обнаружение управляемых позиций во время восстановления

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

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

Для этого процесс восстановления начинается с обнаружения позиций на стороне брокера. В частности, советнику необходимо:

  • Проверить все открытые позиции.
  • Идентифицировать позиции, принадлежащие текущему символу.
  • Отфильтровать только позиции, управляемые текущей системой восстановления.

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

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

Добавим следующую функцию ниже LoadTradeState:

//+------------------------------------------------------------------+
//| Finds one open position managed by this Expert Advisor.          |
//+------------------------------------------------------------------+
bool FindManagedPosition(ulong &ticket)
  {
//--- initialize output ticket
   ticket = 0;
//--- scan all currently open positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      //--- retrieve broker position ticket
      ulong positionTicket = PositionGetTicket(i);
      //--- skip invalid positions
      if(positionTicket == 0)
         continue;
      //--- retrieve broker position properties
      string positionSymbol = PositionGetString(POSITION_SYMBOL);
      long   positionMagic  = PositionGetInteger(POSITION_MAGIC);
      //--- ignore positions from different symbols
      if(positionSymbol != _Symbol)
         continue;
      //--- ignore positions belonging to other systems
      if(positionMagic != InpMagicNumber)
         continue;
      //--- managed position found successfully
      ticket = positionTicket;

      return(true);
     }
//--- no managed position found
   return(false);
  }

Функция начинается с инициализации выходной переменной ticket значением ноль. Это гарантирует, что система восстановления начнет работу из известного состояния до начала поиска позиции. Далее функция сканирует все открытые в данный момент позиции на стороне брокера, используя PositionsTotal.

Для каждой позиции советник получает брокерский тикет и проверяет корректность существования позиции, прежде чем продолжить работу. Затем логика восстановления фильтрует позиции, используя два условия:

  1. Текущий символ на графике.
  2. Настроенное магическое число.

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

Если после сканирования всех открытых позиций не найдена соответствующая позиция, функция возвращает false, указывая на то, что в данный момент не существует управляемой позиции для восстановления.

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


Создание тестовых сделок с контролируемым восстановлением

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

  • Восстановления виртуального стоп-лосса.
  • Восстановления виртуального тейк-профита.
  • Поведения перезапуска.
  • Непрерывности управления после восстановления.

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

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

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


Создание начального состояния виртуальной защиты

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

  • Восстановления виртуальной защиты после перезапуска.
  • Безопасного продолжения управления во время выполнения.
  • Поддержания непрерывности работы между сессиями.

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

//+------------------------------------------------------------------+
//| Builds the initial virtual protection state for an open position.|
//+------------------------------------------------------------------+
void BuildInitialTradeState(const ulong ticket, STradeState &state)
  {
//--- validate broker position existence
   if(!PositionSelectByTicket(ticket))
     {
      //--- broker position unavailable
      PrintFormat("Cannot build trade state. Position not found. Ticket: %I64u", ticket);
      return;
     }
//--- retrieve broker position properties
   state.ticket              = ticket;
   state.symbol              = PositionGetString(POSITION_SYMBOL);
   state.magic               = PositionGetInteger(POSITION_MAGIC);
   state.direction           = (int)PositionGetInteger(POSITION_TYPE);
   state.volume              = PositionGetDouble(POSITION_VOLUME);
   state.entryPrice          = PositionGetDouble(POSITION_PRICE_OPEN);
   state.lastTrailPrice      = state.entryPrice;
   state.breakevenActivated  = false;
   state.trailingActivated   = false;
   state.openTime            = (datetime)PositionGetInteger(POSITION_TIME);
   state.lastHeartbeat       = TimeCurrent();
   state.lastUpdateTime      = TimeCurrent();
//--- construct BUY virtual protection
   if(state.direction == POSITION_TYPE_BUY)
     {
      state.virtualSL = state.entryPrice - (InpVirtualSLPoints * _Point);
      state.virtualTP = state.entryPrice + (InpVirtualTPPoints * _Point);
     }
//--- construct SELL virtual protection
   else if(state.direction == POSITION_TYPE_SELL)
     {
      state.virtualSL = state.entryPrice + (InpVirtualSLPoints * _Point);
      state.virtualTP = state.entryPrice - (InpVirtualTPPoints * _Point);
     }
  }

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

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

Затем функция инициализирует флаги рабочего состояния восстановления, используемые далее на протяжении всей серии. На этом этапе:

  • Восстановление безубытка неактивно.
  • Восстановление трейлинг-стопа неактивно.
  • Последняя опорная цена для трейлинга инициализируется с использованием исходной цены входа в сделку.

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

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


Открытие тестовых сделок, ориентированных на восстановление

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

  • Восстановления виртуальной защиты.
  • Валидация перезапуска.
  • Непрерывности управления во время выполнения.

Добавим следующую функцию ниже BuildInitialTradeState:

//+------------------------------------------------------------------+
//| Opens one test position without broker-side SL or TP levels.     |
//+------------------------------------------------------------------+
bool OpenTestTrade()
  {
//--- validate test trade permissions
   if(!InpAllowTestTrade)
      return(false);
//--- validate EA runtime state
   if(g_eaState != EA_STATE_RUNNING)
      return(false);
//--- prevent multiple managed positions
   ulong existingTicket = 0;
   if(FindManagedPosition(existingTicket))
     {
      //--- managed position already exists
      PrintFormat("Test trade was not opened. Managed position already exists. Ticket: %I64u",
                  existingTicket);
      return(false);
     }
//--- configure Expert Advisor magic number
   g_trade.SetExpertMagicNumber(InpMagicNumber);
   bool result = false;
//--- open BUY test trade
   if(InpTestTradeDirection == TEST_TRADE_BUY)
      result = g_trade.Buy(InpLots,
                           _Symbol,
                           0.0,
                           0.0,
                           0.0,
                           "Self-healing EA test BUY");
//--- open SELL test trade
   else
      result = g_trade.Sell(InpLots,
                            _Symbol,
                            0.0,
                            0.0,
                            0.0,
                            "Self-healing EA test SELL");
//--- validate trade execution result
   if(!result)
     {
      //--- print broker execution failure
      PrintFormat("Failed to open test trade. Retcode: %d",
                  g_trade.ResultRetcode());
      return(false);
     }
//--- print successful trade execution
   PrintFormat("Test trade opened successfully. Order ticket: %I64u",
               g_trade.ResultOrder());
   ulong positionTicket = 0;
//--- locate managed broker position
   if(FindManagedPosition(positionTicket))
     {
      //--- construct runtime recovery state
      BuildInitialTradeState(positionTicket, g_tradeState);
      //--- activate runtime recovery tracking
      g_hasTradeState = true;
      //--- save initial recovery state into SQLite
      if(!SaveTradeState(g_tradeState))
         Print("Test trade was opened, but its initial state could not be saved.");
      else
         PrintFormat("Initial trade state saved successfully. Position ticket: %I64u",
                     positionTicket);
     }
   return(true);
  }

Функция начинается с проверки того, разрешена ли в данный момент контролируемая тестовая торговля. Это обеспечивает более безопасный контроль процесса разработки во время тестирования восстановления.

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

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

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

Обратите внимание, что обе сделки открываются со стоп-лоссом и тейк-профитом, установленными на

0.0 и 0.0,

соответственно.

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

После успешного открытия сделки советник немедленно:

  • Находит позицию на стороне брокера.
  • Формирует рабочее состояние восстановления.
  • Активирует отслеживание во время выполнения.
  • Сохраняет исходное состояние восстановления в SQLite.

На этом этапе советник может генерировать контролируемые тестовые сделки, ориентированные на восстановление, которые можно использовать в сценариях восстановления после перезапуска.


Восстановление состояния сделки после перезапуска

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

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

Добавим следующую функцию ниже OpenTestTrade:

//+------------------------------------------------------------------+
//| Recovers the active trade state from SQLite after EA restart.    |
//+------------------------------------------------------------------+
bool RecoverTradeState()
  {
//--- initialize managed position ticket
   ulong ticket = 0;
//--- check whether a managed broker position exists
   if(!FindManagedPosition(ticket))
     {
      //--- no active position requires recovery
      Print("Recovery completed. No managed open position was found.");
      g_hasTradeState = false;
      return(true);
     }
//--- load saved trade state for the managed position
   if(!LoadTradeState(ticket, g_tradeState))
     {
      //--- saved state is missing while broker position exists
      PrintFormat("Recovery failed. Managed position exists, but no active saved state was found. Ticket: %I64u",
                  ticket);
      g_hasTradeState = false;
      g_eaState       = EA_STATE_SAFE_MODE;
      return(false);
     }
//--- activate recovered runtime trade state
   g_hasTradeState = true;
//--- print restored protection levels
   PrintFormat("Recovery completed. Trade state restored. Ticket: %I64u",
               g_tradeState.ticket);
   PrintFormat("Recovered virtual SL: %.5f | Recovered virtual TP: %.5f",
               g_tradeState.virtualSL,
               g_tradeState.virtualTP);
//--- recovery completed successfully
   return(true);
  }

Функция имеет три возможных исхода. Если управляемая позиция отсутствует, восстановление проходит успешно, поскольку восстанавливать нечего. Если управляемая позиция существует и ее сохраненное состояние найдено в SQLite, советник восстанавливает эту информацию в g_tradeState и устанавливает g_hasTradeState в значение true. Если управляемая позиция существует, но сохраненное состояние не найдено, советник входит в режим EA_STATE_SAFE_MODE, поскольку он не должен управлять сделкой, у которой отсутствует состояние виртуальной защиты.

Теперь обновим OnInit так, чтобы восстановление происходило во время запуска:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set initial EA state
   g_eaState = EA_STATE_STARTING;
//--- create a connection to the SQLite database
   if(!OpenDatabase())
     {
      g_eaState = EA_STATE_ERROR;
      return(INIT_FAILED);
     }
//--- create the database table or make sure that it exists
   if(!EnsureDatabaseTables())
     {
      CloseDatabase();
      g_eaState = EA_STATE_ERROR;
      return(INIT_FAILED);
     }
//--- create timer
   EventSetTimer(InpTimerSeconds);
//--- enter recovery mode
   g_eaState = EA_STATE_RECOVERING;
//--- restore saved trade state if a managed position exists
   if(!RecoverTradeState())
     {
      Print("The EA entered Safe Mode because recovery was incomplete.");
      return(INIT_SUCCEEDED);
     }
//--- enter normal runtime state
   g_eaState = EA_STATE_RUNNING;
//--- open a test trade only when no recovered trade exists
   if(InpOpenTestTradeOnStartup && !g_hasTradeState)
      OpenTestTrade();
   return(INIT_SUCCEEDED);
  }

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

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


Безопасное закрытие управляемых позиций

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

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

Мы начинаем с добавления функции, которая обновляет сохраненную запись в базе данных после закрытия управляемой сделки. Добавим следующую функцию ниже RecoverTradeState:

//+------------------------------------------------------------------+
//| Marks a saved trade state as closed in the SQLite database.      |
//+------------------------------------------------------------------+
bool MarkTradeClosed(const ulong ticket)
  {
//--- validate database connection
   if(g_database == INVALID_HANDLE)
      return(false);
//--- prepare SQL query used to close the saved trade state
   string query = StringFormat(
                     "UPDATE trade_states SET state='CLOSED',last_update_time=%I64d "
                     "WHERE ticket=%I64u;",
                     (long)TimeCurrent(),
                     ticket
                  );
//--- execute SQL update query
   if(!DatabaseExecute(g_database, query))
     {
      //--- print SQL execution error
      PrintFormat("Failed to mark trade as closed. Error: %d", GetLastError());
      return(false);
     }
//--- trade state marked as closed successfully
   return(true);
  }

MarkTradeClosed не закрывает позицию брокера. Её единственная задача — обновить сохраненную запись SQLite после того, как позиция перестанет быть активной. Функция изменяет запись в базе данных с ACTIVE на CLOSED.

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

Далее мы добавляем функцию, которая фактически закрывает управляемую брокерскую позицию. Добавим следующую функцию ниже MarkTradeClosed:

//+------------------------------------------------------------------+
//| Closes the active managed position and records the close reason. |
//+------------------------------------------------------------------+
bool CloseManagedPosition(const string reason)
  {
//--- validate active runtime trade state
   if(!g_hasTradeState)
      return(false);
//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
     {
      //--- broker position unavailable
      PrintFormat("Cannot close managed position. Position not found. Ticket: %I64u",
                  g_tradeState.ticket);
      return(false);
     }
//--- close broker position
   if(!g_trade.PositionClose(g_tradeState.ticket))
     {
      //--- print broker close failure
      PrintFormat("Failed to close managed position. Reason: %s. Retcode: %d",
                  reason,
                  g_trade.ResultRetcode());

      return(false);
     }
//--- print successful broker close
   PrintFormat("Managed position closed. Ticket: %I64u. Reason: %s",
               g_tradeState.ticket,
               reason);
//--- mark saved recovery state as closed
   MarkTradeClosed(g_tradeState.ticket);
//--- deactivate runtime trade tracking
   g_hasTradeState = false;
//--- managed position closed successfully
   return(true);
  }

CloseManagedPosition — это управляемая функция выхода, используемая виртуальной защитой. Функция сначала проверяет, существует ли допустимое состояние сделки во время выполнения. Если у советника в данный момент нет загруженного состояния сделки, у него нет позиции для закрытия через систему восстановления.

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

При наличии позиции советник закрывает её с помощью CTrade:

g_trade.PositionClose(g_tradeState.ticket)

После успешного закрытия брокерской позиции функция вызывает:

MarkTradeClosed(g_tradeState.ticket)

Это обновляет SQLite, так что сохраненное состояние сделки больше не рассматривается как активное.

Наконец, советник устанавливает:

g_hasTradeState = false;

Это сообщает системе выполнения, что в памяти больше нет активной управляемой сделки.

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


Валидация виртуальных выходов сразу после восстановления

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

Добавим следующую функцию ниже CloseManagedPosition:

//+------------------------------------------------------------------+
//| Checks whether the active position has crossed virtual exits.    |
//+------------------------------------------------------------------+
void CheckVirtualExits()
  {
//--- validate active runtime trade state
   if(!g_hasTradeState)
      return;
//--- allow checks only during recovery or normal runtime
   if(g_eaState != EA_STATE_RUNNING && g_eaState != EA_STATE_RECOVERING)
      return;
//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
     {
      //--- broker position unavailable
      PrintFormat("Virtual exit check skipped. Position not found. Ticket: %I64u",
                  g_tradeState.ticket);
      g_hasTradeState = false;
      return;
     }
//--- retrieve current market prices
   double bid = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(g_tradeState.symbol, SYMBOL_ASK);
//--- validate BUY virtual exits
   if(g_tradeState.direction == POSITION_TYPE_BUY)
     {
      if(bid <= g_tradeState.virtualSL)
        {
         CloseManagedPosition("Recovered virtual stop loss crossed");
         return;
        }
      if(bid >= g_tradeState.virtualTP)
        {
         CloseManagedPosition("Recovered virtual take profit crossed");
         return;
        }
     }
//--- validate SELL virtual exits
   if(g_tradeState.direction == POSITION_TYPE_SELL)
     {
      if(ask >= g_tradeState.virtualSL)
        {
         CloseManagedPosition("Recovered virtual stop loss crossed");
         return;
        }
      if(ask <= g_tradeState.virtualTP)
        {
         CloseManagedPosition("Recovered virtual take profit crossed");
         return;
        }
     }
  }

Функция начинается с подтверждения наличия допустимого состояния сделки во время выполнения. Если состояние сделки в данный момент не загружено, проверять нечего.

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

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

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

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

Для позиций на продажу логика использует цену Ask, поскольку позиции на продажу закрываются по цене Ask. Если цена Ask больше или равна виртуальному стоп-лоссу, советник закрывает позицию. Если цена Ask меньше или равна виртуальному тейк-профиту, советник закрывает позицию.

Теперь обновите OnInit так, чтобы эта проверка запускалась сразу после восстановления и до открытия какой-либо новой тестовой сделки:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set initial EA state
   g_eaState = EA_STATE_STARTING;

//--- create a connection to the SQLite database
   if(!OpenDatabase())
     {
      g_eaState = EA_STATE_ERROR;
      return(INIT_FAILED);
     }

//--- create the database table or make sure that it exists
   if(!EnsureDatabaseTables())
     {
      CloseDatabase();
      g_eaState = EA_STATE_ERROR;
      return(INIT_FAILED);
     }

//--- create timer
   EventSetTimer(InpTimerSeconds);

//--- enter recovery mode
   g_eaState = EA_STATE_RECOVERING;

//--- restore saved trade state if a managed position exists
   if(!RecoverTradeState())
     {
      Print("The EA entered Safe Mode because recovery was incomplete.");
      return(INIT_SUCCEEDED);
     }

//--- enter normal runtime state
   g_eaState = EA_STATE_RUNNING;

//--- remember whether recovery restored an active trade state
   bool recoveredTradeExists = g_hasTradeState;

//--- validate recovered virtual exits immediately
   CheckVirtualExits();

//--- open a test trade only when no recovered trade exists
   if(InpOpenTestTradeOnStartup && !g_hasTradeState && !recoveredTradeExists)
      OpenTestTrade();

   return(INIT_SUCCEEDED);
  }

Добавленный флаг recoveredTradeExists имеет важное значение. Он запоминает, была ли сделка восстановлена ​​до того, как произошла проверка виртуального выхода.

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

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


Создание цикла виртуальной защиты во время выполнения

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

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

Добавим следующую функцию ниже  CheckVirtualExits:

//+------------------------------------------------------------------+
//| Manages the active trade using the saved virtual protection state.|
//+------------------------------------------------------------------+
void ManageActiveTrade()
  {
//--- allow management only during normal runtime
   if(g_eaState != EA_STATE_RUNNING)
      return;
//--- validate active runtime trade state
   if(!g_hasTradeState)
      return;
//--- validate broker position existence
   if(!PositionSelectByTicket(g_tradeState.ticket))
     {
      //--- broker position no longer exists
      PrintFormat("Managed position no longer exists. Ticket: %I64u",
                  g_tradeState.ticket);
      MarkTradeClosed(g_tradeState.ticket);
      g_hasTradeState = false;
      return;
     }
//--- check virtual stop-loss and take-profit levels
   CheckVirtualExits();
//--- stop if virtual exit closed the position
   if(!g_hasTradeState)
      return;
//--- update latest runtime modification time
   g_tradeState.lastUpdateTime = TimeCurrent();
//--- save latest active trade state
   SaveTradeState(g_tradeState);
  }

Функция начинается с проверки того, находится ли советник в обычном режиме выполнения. Если советник не работает в обычном режиме, управление сделками пропускается.

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

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

После этих проверок безопасности функция вызывает:

CheckVirtualExits();

Это позволяет циклу выполнения непрерывно отслеживать скрытые виртуальные уровни стоп-лосса и тейк-профита. Если CheckVirtualExits закрывает сделку, g_hasTradeState становится равным false, а функция немедленно прекращает работу.

Если позиция остается активной, советник обновляет lastUpdateTime и сохраняет последнее состояние сделки обратно в SQLite. Это поддерживает согласованность сохраненной записи с текущим рабочим состоянием.

Теперь обновите обработчики событий, чтобы цикл защиты во время выполнения выполнялся автоматически. Обновляем OnTick следующим образом:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- manage active virtual protection on every price tick
   ManageActiveTrade();
  }

Обновим OnTimer следующим образом:

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- manage active virtual protection at fixed intervals
   ManageActiveTrade();
  }

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

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


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

На данном этапе архитектура восстановления теперь способна:

  • Открывать контролируемые сделки с виртуальной защитой.
  • Хранить рабочее состояние восстановления в SQLite.
  • Восстанавливать это состояние после перезапуска.
  • Проверять восстановленные виртуальные выходы.
  • Безопасно продолжать управление сделками в рабочем режиме.

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

После успешного открытия сделки советник немедленно:

  • Создает начальное состояние виртуальной защиты. 
  • Активирует управление во время выполнения. 
  • Сохраняет информацию для восстановления в SQLite.

Чтобы убедиться в корректной работе уровня хранения состояния, откройте каталог данных терминала MetaTrader, выбрав:

File → Open Data Folder

Далее перейдите:

MQL5 → Files

В этом каталоге теперь должен существовать файл базы данных SQLite:

SQLite Database File

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

После открытия файла базы данных проверьте таблицу trade_states.

Trade States Table

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

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

Снова откройте терминал и прикрепите советник к тому же графику. Во время запуска советник должен:

  • Определить позицию, управляемую брокером.
  • Восстановить ранее сохраненное состояние выполнения из SQLite.
  • Восстановить уровни виртуальной защиты.
  • Проверить текущую рыночную цену.
  • Автоматически продолжить управление во время выполнения.

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

  • Восстановить сохраненное состояние сделки.
  • Обнаружить, что уровень виртуальной защиты уже был пересечен, пока терминал был отключен.
  • Автоматически закрыть управляемую позицию.
  • Обновить запись в SQLite с ACTIVE на CLOSED.

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


Заключение

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

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

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

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

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

Прикрепленные файлы |
Возможности Мастера MQL5, которые вам нужно знать (Часть 80): Использование паттернов Ишимоку и ADX Уайлдера в обучении с подкреплением TD3 Возможности Мастера MQL5, которые вам нужно знать (Часть 80): Использование паттернов Ишимоку и ADX Уайлдера в обучении с подкреплением TD3
Эта статья продолжает тему части 74, где мы рассматривали сочетание Ишимоку и ADX в рамках обучения с учителем, перенося фокус на обучение с подкреплением. Индикаторы Ишимоку и ADX дополняют друг друга: первый помогает строить карту уровней поддержки и сопротивления, а второй – определять силу тренда. В этой статье мы рассмотрим, как можно использовать алгоритм Twin Delayed Deep Deterministic Policy Gradient (TD3) с этим набором индикаторов. Как и в предыдущих частях серии, реализация выполняется в пользовательском классе сигнала, предназначенном для интеграции с Мастером MQL5, что упрощает сборку советника.
Анализ CSV-данных (Часть 3): Построение аналитического пайплайна на Python для CSV-экспорта из MetaTrader 5 Анализ CSV-данных (Часть 3): Построение аналитического пайплайна на Python для CSV-экспорта из MetaTrader 5
MetaTrader 5 предоставляет обширные данные о результатах, но ограниченные возможности структурного анализа. В статье показано, как экспортировать результаты из MQL5 в CSV и построить пять визуализаций на Python, раскрывающих согласованность параметров между активами, компромисс между задержкой и шумом, деградацию результатов при walk-forward-валидации, распределение глубины и длительности просадок, а также внутридневные кластеры по часам и дням недели. Единый модуль автоматизации запускает полный пайплайн для каждого нового файла экспорта и обеспечивает воспроизводимую диагностику.
Династический алгоритм — Dynastic Optimization Algorithm (DOA) Династический алгоритм — Dynastic Optimization Algorithm (DOA)
В статье реализован и разобран Dynastic Optimization Algorithm в рамках фреймворка C_AO на MQL5. Мы воспроизводим каноническую модель, закрываем неописанные в первоисточниках места и тестируем алгоритм на стенде с фиксированным бюджетом и ростом размерности. Показана причина деградации каноники и предложена модификация оператора с разреженной мутацией. Читатель получит рабочий класс и рекомендации по настройке для задач с дискретными параметрами.
Нейросети в трейдинге: От единственного прогноза к пространству рыночных сценариев (Окончание) Нейросети в трейдинге: От единственного прогноза к пространству рыночных сценариев (Окончание)
Материал завершает адаптацию фреймворка ORION к трейдингу: сценарии рынка формируются Market Encoder, а Актёр на их основе управляет объёмом, TP и SL с учётом состояния счёта; Критики дают оценку действий. Описаны процедуры офлайн‑ и онлайн‑обучения и контроль утечки будущего. Проверка проводится в тестере стратегий на отложенном периоде.