English
preview
Встраивание торговой дисциплины в код (Часть 8): Подтверждение сетапов и контроль разрешения торговли

Встраивание торговой дисциплины в код (Часть 8): Подтверждение сетапов и контроль разрешения торговли

MetaTrader 5 — Торговые системы |
10 2
Christian Benjamin
Christian Benjamin

Содержание


Введение

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

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

В статье мы создадим такой фреймворк на MQL5 с использованием классов CDisciplineLayer, CDisciplineGuardian и CDisciplinePanel, а также приведем пример советника, демонстрирующий, как встроить этот фреймворк в существующую стратегию.


Разработка модели торговой дисциплины

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

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

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

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

Модель реализована в трёх файлах:

  1. DisciplineLayer.mqh
  2. DisciplineGuardian.mqh
  3. DisciplinePanel.mqh

Определение жизненного цикла сетапа

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

Состояние Значение
NO_SETUP
Торговая возможность отсутствует.
SETUP_FORMING
Условия сетапа формируются, но подтверждение ещё не получено.
SETUP_CONFIRMED
Сетап прошёл проверку по установленным правилам.
SETUP_ACTIVE
Торговля по сетапу по-прежнему допустима.
SETUP_EXPIRED
Сетап больше не действителен для совершения сделки.

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

Определение правил разрешения торговли

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

Торговля по сетапу считается допустимой только при соблюдении следующих условий:

  • сетап подтверждён;
  • срок действия сетапа не истёк;
  • сигнал всё ещё актуален;
  • текущее время входит в разрешённый интервал торговой сессии;
  • глобальная блокировка торговли не активна.

Правило
Цель
Подтверждение сетапа
Предотвращает преждевременные входы
Контроль срока действия сетапа
Предотвращает запоздалые входы
Актуальность сигнала
Предотвращает использование устаревших сигналов
Фильтр торговых сессий
Ограничивает часы торговли
Глобальная блокировка торговли
Приостанавливает торговлю после нарушений

Эти проверки также реализованы в файле DisciplineLayer.mqh с помощью функции CanTrade(). Таким образом, уровень разрешения торговли выполняет единственную задачу: определяет, по-прежнему ли разрешена торговля по данному сетапу.

Определение требований к принудительному соблюдению правил

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

Модуль контроля соблюдения правил поддерживает три варианта реакции:

Реакция
Действие
Оповещение
Уведомить пользователя
Автоматическое закрытие
Закрыть неразрешённые позиции и удалить неразрешённые ордера
Автоматическое закрытие + блокировка
Устранить нарушение и заблокировать дальнейшую торговлю

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

Определение требований к мониторингу

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

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


Реализация на MQL5


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

Создание класса CDisciplineLayer

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

1. Определение состояний сетапа

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

//--- Setup state enumeration
enum ENUM_SETUP_STATE
  {
   NO_SETUP = 0,          // No valid setup exists
   SETUP_FORMING,         // Conditions are developing
   SETUP_CONFIRMED,       // Setup fully validated
   SETUP_ACTIVE,          // Trade window open
   SETUP_EXPIRED          // Setup no longer valid
  };

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

2. Создание структуры класса

Далее мы создадим класс CDisciplineLayer и объявим необходимые ему переменные.

//+------------------------------------------------------------------+
//| CDisciplineLayer: Trade authorization layer                      |
//+------------------------------------------------------------------+
class CDisciplineLayer
  {
private:
   ENUM_SETUP_STATE      m_state;
   datetime              m_setupDetectedTime;
   datetime              m_setupConfirmedTime;
   datetime              m_setupExpiryTime;

   //--- Session filter
   bool                  m_sessionFilterEnabled;
   int                   m_sessionStartHour;
   int                   m_sessionStartMinute;
   int                   m_sessionEndHour;
   int                   m_sessionEndMinute;

   //--- Signal freshness
   int                   m_signalFreshnessMinutes;

   //--- Global lock
   string                m_globalLockName;

   bool                  IsSetupConfirmed() const;
   bool                  IsTradeWindowValid() const;
   bool                  IsSessionAllowed() const;
   bool                  IsSignalFresh() const;

public:
                     CDisciplineLayer();
   bool                 Initialize();
   void                 SetSetupState(ENUM_SETUP_STATE state);
   void                 ConfirmSetup(datetime expiryTime);
   void                 ExpireSetup();
   bool                 CanTrade();

   void                 EnableSessionFilter(int startHour, int startMin, int endHour, int endMin);
   void                 DisableSessionFilter()                { m_sessionFilterEnabled = false; }
   void                 SetSignalFreshnessMinutes(int minutes) { m_signalFreshnessMinutes = minutes; }
   void                 SetGlobalLockName(string name)        { m_globalLockName = name; }

   //--- Getters for dashboard
   ENUM_SETUP_STATE     GetState()                      const { return m_state; }
   datetime             GetExpiryTime()                 const { return m_setupExpiryTime; }
   datetime             GetSetupConfirmedTime()         const { return m_setupConfirmedTime; }
   bool                 IsSessionFilterEnabled()        const { return m_sessionFilterEnabled; }
   void                 GetSessionTimes(int &sh, int &sm, int &eh, int &em) const;
   int                  GetSignalFreshnessMinutes()     const { return m_signalFreshnessMinutes; }
  };

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

Кроме того, мы объявляем вспомогательные функции, которые в дальнейшем будут выполнять проверки и разрешать торговлю. К ним относятся IsSetupConfirmed(), IsTradeWindowValid(), IsSessionAllowed(), IsSignalFresh() и CanTrade().

3. Инициализация класса

После определения структуры класса мы реализуем конструктор.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDisciplineLayer::CDisciplineLayer()
  {
   m_state = NO_SETUP;
   m_setupDetectedTime = 0;
   m_setupConfirmedTime = 0;
   m_setupExpiryTime = 0;
   m_sessionFilterEnabled = false;
   m_sessionStartHour = 0;
   m_sessionStartMinute = 0;
   m_sessionEndHour   = 23;
   m_sessionEndMinute   = 59;
   m_signalFreshnessMinutes = 0;
   m_globalLockName = "DISC_GLOBAL_LOCK";
  }

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

4. Сброс рабочего состояния с помощью Initialize()

Кроме того, мы предоставляем функцию Initialize(), которая позволяет сбросить состояние класса при необходимости.

//+------------------------------------------------------------------+
//| Initializes runtime variables                                    |
//+------------------------------------------------------------------+
bool CDisciplineLayer::Initialize()
  {
   m_state = NO_SETUP;
   m_setupDetectedTime = 0;
   m_setupConfirmedTime = 0;
   m_setupExpiryTime = 0;
   return true;
  }

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

5. Настройка фильтра торговых сессий

Для управления торговлей с учётом времени реализуем функции EnableSessionFilter() и GetSessionTimes().

//+------------------------------------------------------------------+
//| Enables session filter with given start and end times            |
//+------------------------------------------------------------------+
void CDisciplineLayer::EnableSessionFilter(int startHour, int startMin, int endHour, int endMin)
  {
   m_sessionFilterEnabled = true;
   m_sessionStartHour = startHour;
   m_sessionStartMinute = startMin;
   m_sessionEndHour = endHour;
   m_sessionEndMinute = endMin;
  }

//+------------------------------------------------------------------+
//| Returns session start/end times                                  |
//+------------------------------------------------------------------+
void CDisciplineLayer::GetSessionTimes(int &sh, int &sm, int &eh, int &em) const
  {
   sh = m_sessionStartHour;
   sm = m_sessionStartMinute;
   eh = m_sessionEndHour;
   em = m_sessionEndMinute;
  }

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

Здесь сохраняются параметры торговой сессии, которые затем проверяются функцией IsSessionAllowed().

6. Проверка разрешения торговли с учётом торговой сессии

После настройки фильтра торговых сессий реализуем IsSessionAllowed().

//+------------------------------------------------------------------+
//| Checks if current time is inside the allowed session             |
//+------------------------------------------------------------------+
bool CDisciplineLayer::IsSessionAllowed() const
  {
   if(!m_sessionFilterEnabled)
      return true;
   MqlDateTime tm;
   TimeCurrent(tm);
   int cur = tm.hour * 60 + tm.min;
   int start = m_sessionStartHour * 60 + m_sessionStartMinute;
   int end   = m_sessionEndHour   * 60 + m_sessionEndMinute;
   if(start <= end)
      return (cur >= start && cur <= end);
   else
      return (cur >= start || cur <= end);
  }

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

7. Проверка актуальности сигнала

Далее мы реализуем функцию IsSignalFresh().

//+------------------------------------------------------------------+
//| Checks if the confirmed setup is still "fresh"                   |
//+------------------------------------------------------------------+
bool CDisciplineLayer::IsSignalFresh() const
  {
   if(m_signalFreshnessMinutes <= 0 || m_setupConfirmedTime == 0)
      return true;
   return (TimeCurrent() - m_setupConfirmedTime) <= m_signalFreshnessMinutes * 60;
  }

Эта функция проверяет, остаётся ли подтверждённый сетап в пределах допустимого периода актуальности сигнала. Если с момента подтверждения прошло слишком много времени, сигнал считается устаревшим и отклоняется.

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

8. Проверка подтверждённого состояния сетапа

Затем мы реализуем функцию IsSetupConfirmed().

//+------------------------------------------------------------------+
//| Returns true if the setup is in CONFIRMED or ACTIVE state        |
//+------------------------------------------------------------------+
bool CDisciplineLayer::IsSetupConfirmed() const
  {
   return (m_state == SETUP_CONFIRMED || m_state == SETUP_ACTIVE);
  }

Эта функция проверяет, находится ли сетап в состоянии "подтверждён" или "активен". Если сетап ещё формируется, отсутствует или срок его действия уже истёк, функция возвращает false.

9. Проверка временного окна для торговли

Следующая функция — IsTradeWindowValid().

//+------------------------------------------------------------------+
//| Checks that the expiry time has not passed                       |
//+------------------------------------------------------------------+
bool CDisciplineLayer::IsTradeWindowValid() const
  {
   if(m_setupExpiryTime == 0)
      return true;
   return TimeCurrent() <= m_setupExpiryTime;
  }

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

Сетап остаётся доступным для торговли до истечения срока его действия.

10. Создание основной функции разрешения торговли

Основой класса является метод CanTrade().

//+------------------------------------------------------------------+
//| Master authorization function                                    |
//+------------------------------------------------------------------+
bool CDisciplineLayer::CanTrade()
  {
//--- Global lock check
   if(GlobalVariableGet(m_globalLockName) > 0.5)
      return false;
//--- Setup must be confirmed
   if(!IsSetupConfirmed())
      return false;
//--- Expiry window
   if(!IsTradeWindowValid())
     {
      if(m_state != SETUP_EXPIRED)
         ExpireSetup();
      return false;
     }
//--- Signal freshness
   if(!IsSignalFresh())
      return false;
//--- Session filter
   if(!IsSessionAllowed())
      return false;
   return true;
  }

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

11. Управление изменениями состояния сетапа

Заключительная часть класса обрабатывает переходы между состояниями сетапа с помощью методов SetSetupState(), ConfirmSetup() и ExpireSetup().

Метод SetSetupState() обновляет текущее состояние и сбрасывает прежние временные значения, когда сетап больше не действителен. Метод ConfirmSetup() фиксирует время подтверждения, сохраняет время истечения срока действия и переводит сетап в подтверждённое состояние. Метод ExpireSetup() помечает сетап как утративший силу и сбрасывает активное ограничение по сроку действия.

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

//+------------------------------------------------------------------+
//| Manually sets the state                                          |
//+------------------------------------------------------------------+
void CDisciplineLayer::SetSetupState(ENUM_SETUP_STATE state)
  {
   m_state = state;
   if(state == NO_SETUP || state == SETUP_EXPIRED)
     {
      m_setupConfirmedTime = 0;
      m_setupExpiryTime = 0;
     }
   else
      if(state == SETUP_FORMING && m_setupDetectedTime == 0)
         m_setupDetectedTime = TimeCurrent();
  }

//+------------------------------------------------------------------+
//| Called by strategy when a valid setup is confirmed               |
//+------------------------------------------------------------------+
void CDisciplineLayer::ConfirmSetup(datetime expiryTime)
  {
   m_setupConfirmedTime = TimeCurrent();
   m_setupExpiryTime = expiryTime;
   m_state = SETUP_CONFIRMED;
  }

//+------------------------------------------------------------------+
//| Expires the current setup                                        |
//+------------------------------------------------------------------+
void CDisciplineLayer::ExpireSetup()
  {
   m_state = SETUP_EXPIRED;
   m_setupExpiryTime = 0;
  }



Модуль принудительного соблюдения правил CDisciplineGuardian

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

1. Определение режимов реагирования на нарушения

Контролирующий модуль поддерживает три режима реагирования на нарушения:

  1. Режим "Только оповещения"
  2. Режим автоматического закрытия
  3. Режим автоматического закрытия и блокировки

//+------------------------------------------------------------------+
//| Violation handling modes                                         |
//+------------------------------------------------------------------+
enum ENUM_VIOLATION_MODE
  {
   MODE_ALERT_ONLY,
   MODE_AUTO_CLOSE,
   MODE_AUTO_CLOSE_LOCK
  };

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

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

2. Создание структуры CDisciplineGuardian

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

//+------------------------------------------------------------------+
//| CDisciplineGuardian: Discipline enforcement engine               |
//+------------------------------------------------------------------+
class CDisciplineGuardian
  {
private:
   string               m_globalLockPrefix;
   ENUM_VIOLATION_MODE  m_violationMode;
   bool                 m_enabled;

   void                 ClosePosition(ulong ticket);
   void                 DeleteOrder(ulong ticket);
   void                 ActivateGlobalLock();

public:
                     CDisciplineGuardian();

   void                 SetGlobalLockPrefix(string prefix)
     { m_globalLockPrefix = prefix; }

   void                 SetViolationMode(ENUM_VIOLATION_MODE mode)
     { m_violationMode = mode; }

   void                 Enable()
     { m_enabled = true; }

   void                 Disable()
     { m_enabled = false; }

   void                 Enforce(bool isTradingAllowed);
  };

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

Также мы объявляем вспомогательные функции, которые будут обеспечивать принудительное соблюдение правил. К ним относятся ClosePosition(), DeleteOrder() и ActivateGlobalLock().

3. Инициализация модуля контроля соблюдения правил

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

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDisciplineGuardian::CDisciplineGuardian()
  {
   m_globalLockPrefix = "DISC_";
   m_violationMode = MODE_AUTO_CLOSE;
   m_enabled = true;
  }

Конструктор задаёт префикс глобальной блокировки торговли и режим реагирования на нарушения по умолчанию, а также включает модуль при создании объекта.

Это задаёт исходное состояние модуля до применения пользовательских настроек.

4. Закрытие неразрешенных позиций

Первая вспомогательная функция для принудительного соблюдения правил — ClosePosition().

//+------------------------------------------------------------------+
//| Closes an unauthorised position                                  |
//+------------------------------------------------------------------+
void CDisciplineGuardian::ClosePosition(ulong ticket)
  {
   if(!PositionSelectByTicket(ticket))
      return;

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};

   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = PositionGetString(POSITION_SYMBOL);
   req.volume    = PositionGetDouble(POSITION_VOLUME);
   req.type      = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                   ? ORDER_TYPE_SELL
                   : ORDER_TYPE_BUY;
   req.position  = ticket;
   req.deviation = 10;
   req.magic     = 0;

   if(OrderSend(req,res))
      Print("[GUARDIAN] Closed unauthorised position #",ticket);
   else
      Print("[GUARDIAN] Failed to close position #",
            ticket,
            " error ",
            GetLastError());
  }

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

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

5. Удаление неразрешенных отложенных ордеров

Следующая вспомогательная функция — DeleteOrder().

//+------------------------------------------------------------------+
//| Deletes an unauthorized pending order                            |
//+------------------------------------------------------------------+
void CDisciplineGuardian::DeleteOrder(ulong ticket)
  {
   if(!OrderSelect(ticket))
      return;

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};

   req.action = TRADE_ACTION_REMOVE;
   req.order  = ticket;

   if(OrderSend(req,res))
      Print("[GUARDIAN] Deleted unauthorised pending order #",ticket);
   else
      Print("[GUARDIAN] Failed to delete order #",
            ticket,
            " error ",
            GetLastError());
  }

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

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

6. Включение глобальной блокировки торговли

Модуль контроля соблюдения правил также включает функцию ActivateGlobalLock().

//+------------------------------------------------------------------+
//| Activates the global trading lock                                |
//+------------------------------------------------------------------+
void CDisciplineGuardian::ActivateGlobalLock()
  {
   string lockName = m_globalLockPrefix + "GLOBAL_LOCK";

   GlobalVariableSet(lockName,1.0);

   Print("[GUARDIAN] Trading lock activated (",
         lockName,
         " = 1). Manual removal required.");
  }

Эта функция создаёт глобальную переменную терминала с использованием заданного префикса и присваивает ей значение "1". Затем эта глобальная переменная используется в качестве сигнала блокировки торговли.

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

7. Принудительное соблюдение правил торговой дисциплины

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

//+------------------------------------------------------------------+
//| Enforces discipline rules on positions and orders                |
//+------------------------------------------------------------------+
void CDisciplineGuardian::Enforce(bool isTradingAllowed)
  {
   if(!m_enabled)
      return;

   if(isTradingAllowed)
      return;

//--- Process open positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);

      if(ticket == 0)
         continue;

      switch(m_violationMode)
        {
         case MODE_ALERT_ONLY:
            Print("[GUARDIAN] Alert: Position #",
                  ticket,
                  " not allowed");
            break;

         case MODE_AUTO_CLOSE:
            ClosePosition(ticket);
            break;

         case MODE_AUTO_CLOSE_LOCK:
            ClosePosition(ticket);
            ActivateGlobalLock();
            break;
        }
     }

//--- Process pending orders
   for(int i = OrdersTotal() - 1; i >= 0; i--)
     {
      ulong ticket = OrderGetTicket(i);

      if(ticket == 0)
         continue;

      switch(m_violationMode)
        {
         case MODE_ALERT_ONLY:
            Print("[GUARDIAN] Alert: Pending order #",
                  ticket,
                  " not allowed");
            break;

         case MODE_AUTO_CLOSE:
            DeleteOrder(ticket);
            break;

         case MODE_AUTO_CLOSE_LOCK:
            DeleteOrder(ticket);
            ActivateGlobalLock();
            break;
        }
     }
  }

На этом этапе решение о разрешении торговли применяется к открытым позициям и ордерам.


Создание информационной панели CDisciplinePanel

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

1. Определение структуры панели

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

//+------------------------------------------------------------------+
//|                                              DisciplinePanel.mqh |
//|                              Copyright 2026, Christian Benjamin. |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Christian Benjamin."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "2.0"

#include "Trade Authorization/DisciplineLayer.mqh"
#include "Trade Authorization/DisciplineGuardian.mqh"

//+------------------------------------------------------------------+
//| Discipline dashboard                                             |
//+------------------------------------------------------------------+
class CDisciplinePanel
  {
private:
   string               m_prefix;
   int                  m_cornerX;
   int                  m_cornerY;
   int                  m_width;
   int                  m_lineHeight;
   int                  m_fontSize;
   string               m_fontName;
   color                m_bgColor;
   color                m_textColor;
   bool                 m_visible;
   int                  m_lines;

   //--- Creates the panel background
   void                 CreateBackground();

   //--- Creates or updates a label
   void                 CreateLabel(string name,
                                    string text,
                                    int line,
                                    color clr,
                                    bool bold = true);

   //--- Deletes all panel objects
   void                 DeletePanel();

public:
   //--- Constructor
                     CDisciplinePanel();

   //--- Destructor
                    ~CDisciplinePanel();

   //--- Initializes panel settings
   void                 Init(string prefix,
                             int cornerX,
                             int cornerY,
                             int width,
                             color bg,
                             color txt);

   //--- Shows the panel
   void                 Show();

   //--- Hides the panel
   void                 Hide();

   //--- Updates the panel display
   void                 Update(CDisciplineLayer &discipline,
                               ENUM_VIOLATION_MODE mode);
  };

Мы также объявляем вспомогательные функции, которые будут отвечать за создание и очистку панели. Функция CreateBackground() создаёт рамку панели, CreateLabel() выводит каждую строку текста, а DeletePanel() удаляет все объекты, созданные информационной панелью.

2. Инициализация панели

Далее мы реализуем конструктор. Конструктор задаёт значения по умолчанию для компоновки и внешнего вида панели. Он задаёт префикс, используемый для объектов графика, положение панели, ширину, высоту строки, размер шрифта, название шрифта, цвет фона, цвет текста и состояние видимости.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDisciplinePanel::CDisciplinePanel()
  {
   m_prefix     = "DispPanel_";
   m_cornerX    = 10;
   m_cornerY    = 30;
   m_width      = 380;
   m_lineHeight = 24;
   m_fontSize   = 13;
   m_fontName   = "Arial Bold";
   m_bgColor    = clrDarkSlateGray;
   m_textColor  = clrWhite;
   m_visible    = true;
   m_lines      = 0;
  }

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

//+------------------------------------------------------------------+
//| Initializes panel settings                                       |
//+------------------------------------------------------------------+
void CDisciplinePanel::Init(string prefix,
                            int cornerX,
                            int cornerY,
                            int width,
                            color bg,
                            color txt)
  {
   m_prefix    = prefix;
   m_cornerX   = cornerX;
   m_cornerY   = cornerY;
   m_width     = width;
   m_bgColor   = bg;
   m_textColor  = txt;
   m_visible   = true;
  }

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

3. Создание фона

Функция CreateBackground() отрисовывает контейнер панели.

//+------------------------------------------------------------------+
//| Creates the panel background                                     |
//+------------------------------------------------------------------+
void CDisciplinePanel::CreateBackground()
  {
   string objName = m_prefix + "bg";

   if(ObjectFind(0, objName) < 0)
     {
      ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, m_cornerX);
      ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, m_cornerY);
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, m_bgColor);
      ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, BORDER_RAISED);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrSilver);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1000);
     }

   ObjectSetInteger(0, objName, OBJPROP_XSIZE, m_width);
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, (m_lines + 2) * m_lineHeight);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1000);
  }

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

Размер фона динамически изменяется в зависимости от текущего количества отображаемых строк, чтобы размер панели соответствовал её содержимому.

4. Создание текстовых меток

Затем мы реализуем функцию CreateLabel() для вывода каждой строки текста внутри панели.

//+------------------------------------------------------------------+
//| Creates or updates a label                                       |
//+------------------------------------------------------------------+
void CDisciplinePanel::CreateLabel(string name,
                                   string text,
                                   int line,
                                   color clr,
                                   bool bold)
  {
   string objName = m_prefix + name;

   if(ObjectFind(0, objName) < 0)
     {
      ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, m_cornerX + 8);
      ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, m_cornerY + 4 + line * m_lineHeight);
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, m_fontSize);
      ObjectSetString(0, objName, OBJPROP_FONT, bold ? m_fontName : "Arial");
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1001);
     }

   ObjectSetString(0, objName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1001);
  }

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

5. Очистка панели

Функция DeletePanel() удаляет с графика все объекты информационной панели.

//+------------------------------------------------------------------+
//| Deletes all panel objects                                        |
//+------------------------------------------------------------------+
void CDisciplinePanel::DeletePanel()
  {
   for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, m_prefix) == 0)
         ObjectDelete(0, name);
     }
  }

Она перебирает объекты графика и удаляет каждый объект, имя которого начинается с префикса панели. Это гарантирует, что старые элементы информационной панели будут удалены перед её перерисовкой.

6. Обновление информационной панели

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

Ниже приведена полная реализация.

//+------------------------------------------------------------------+
//| Updates the panel display                                        |
//+------------------------------------------------------------------+
void CDisciplinePanel::Update(CDisciplineLayer &discipline,
                              ENUM_VIOLATION_MODE mode)
  {
   if(!m_visible)
      return;

   DeletePanel();

   int lineCount = 0;
   lineCount++; 
   lineCount++; 
   lineCount++; 
   lineCount++; 

   if(discipline.GetSignalFreshnessMinutes() > 0 &&
      discipline.GetSetupConfirmedTime() > 0)
      lineCount++;

   if(discipline.IsSessionFilterEnabled())
      lineCount++;

   lineCount++; 

   m_lines = lineCount + 2;
   CreateBackground();

   int line = 0;

   CreateLabel("header", "DISCIPLINE DASHBOARD", line++, clrWhite, true);
   line++;

   ENUM_SETUP_STATE state = discipline.GetState();
   string stateStr = "";
   color stateColor = clrGray;

   switch(state)
     {
      case NO_SETUP:
         stateStr  = "[ ] NO SETUP";
         stateColor = clrGray;
         break;

      case SETUP_FORMING:
         stateStr  = "[~] FORMING";
         stateColor = clrYellow;
         break;

      case SETUP_CONFIRMED:
         stateStr  = "[+] CONFIRMED";
         stateColor = clrLimeGreen;
         break;

      case SETUP_ACTIVE:
         stateStr  = "[+] ACTIVE";
         stateColor = clrLimeGreen;
         break;

      case SETUP_EXPIRED:
         stateStr  = "[X] EXPIRED";
         stateColor = clrCrimson;
         break;

      default:
         stateStr  = "[?] UNKNOWN";
         stateColor = clrGray;
         break;
     }

   CreateLabel("state", "State: " + stateStr, line++, stateColor, true);

   bool canTrade = discipline.CanTrade();
   string tradeAllowed = canTrade ? "[ALLOWED]" : "[DENIED]";
   color tradeColor = canTrade ? clrLimeGreen : clrCrimson;
   CreateLabel("canTrade", "Trade Auth: " + tradeAllowed, line++, tradeColor, true);

   datetime expiry = discipline.GetExpiryTime();
   if(expiry > 0)
     {
      int remaining = (int)(expiry - TimeCurrent());
      string expiryStr = (remaining > 0)
                         ? TimeToString(expiry, TIME_MINUTES) + " (" + IntegerToString(remaining) + " sec)"
                         : "EXPIRED";
      CreateLabel("expiry", "Setup Expiry: " + expiryStr, line++, clrWhite, false);
     }
   else
      CreateLabel("expiry", "Setup Expiry: none", line++, clrWhite, false);

   int freshness = discipline.GetSignalFreshnessMinutes();
   if(freshness > 0 && discipline.GetSetupConfirmedTime() > 0)
     {
      int secondsOld = (int)(TimeCurrent() - discipline.GetSetupConfirmedTime());
      int remaining = freshness * 60 - secondsOld;
      string freshStr = (remaining > 0)
                        ? StringFormat("Signal fresh: %d sec", remaining)
                        : "Signal stale";
      CreateLabel("fresh", freshStr, line++, (remaining > 0) ? clrLightGreen : clrTomato, false);
     }

   if(discipline.IsSessionFilterEnabled())
     {
      int sh, sm, eh, em;
      discipline.GetSessionTimes(sh, sm, eh, em);

      MqlDateTime tm;
      TimeCurrent(tm);

      int cur = tm.hour * 60 + tm.min;
      int start = sh * 60 + sm;
      int end = eh * 60 + em;
      bool inSession = (start <= end) ? (cur >= start && cur <= end) : (cur >= start || cur <= end);

      string sessionStr = StringFormat("Session: %02d:%02d - %02d:%02d   %s",
                                       sh, sm, eh, em,
                                       inSession ? "ACTIVE" : "inactive");
      CreateLabel("session", sessionStr, line++, inSession ? clrLimeGreen : clrWhite, false);
     }

   string modeStr = "";
   switch(mode)
     {
      case MODE_ALERT_ONLY:
         modeStr = "ALERT ONLY";
         break;

      case MODE_AUTO_CLOSE:
         modeStr = "AUTO CLOSE";
         break;

      case MODE_AUTO_CLOSE_LOCK:
         modeStr = "AUTO CLOSE + LOCK";
         break;

      default:
         modeStr = "UNKNOWN";
         break;
     }

   CreateLabel("violation", "Guardian: " + modeStr, line++, clrCyan, true);

   m_lines = line;
   CreateBackground();
   ChartRedraw();
  }

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


Интеграция уровня разрешения торговли в советник

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

1. Подключение модулей контроля торговой дисциплины

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

#include <Trade Authorization/DisciplineLayer.mqh>
#include <Trade Authorization/DisciplineGuardian.mqh>
#include <Trade Authorization/DisciplinePanel.mqh>

Файл DisciplineLayer.mqh реализует логику разрешения торговли, DisciplineGuardian.mqh обрабатывает нарушения, а DisciplinePanel.mqh отображает текущее состояние на графике.

2. Определение входных параметров и объектов, используемых во время выполнения

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

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input string   inp_Section1 = "Discipline Settings";
input int      inp_signalFreshnessMinutes = 15;
input bool     inp_useSessionFilter       = true;
input int      inp_sessionStartHour       = 8;
input int      inp_sessionStartMin        = 0;
input int      inp_sessionEndHour         = 17;
input int      inp_sessionEndMin          = 0;
input int      inp_setupExpirySeconds     = 1800;

input string   inp_Section2 = "Guardian Settings";
input ENUM_VIOLATION_MODE inp_violationMode = MODE_AUTO_CLOSE;

input string   inp_Section3 = "Strategy Settings";
input int      inp_fastMAPeriod          = 20;
input int      inp_slowMAPeriod          = 50;
input double   inp_lotSize               = 0.1;
input int      inp_magicNumber           = 123456;
input int      inp_formingDistancePoints  = 100;   // distance used to mark a setup as forming

input string   inp_Section4 = "Dashboard Settings";
input bool     inp_showDashboard = true;
input int      inp_dashboardX     = 10;
input int      inp_dashboardY     = 30;

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

//+------------------------------------------------------------------+
//| Global objects and variables                                     |
//+------------------------------------------------------------------+
CDisciplineLayer     g_Discipline;
CDisciplineGuardian  g_Guardian;
CDisciplinePanel     g_Panel;

int                  g_fastMAHandle   = INVALID_HANDLE;
int                  g_slowMAHandle   = INVALID_HANDLE;
bool                 g_setupConfirmed = false;
int                  g_setupDirection  = 0;   // 1 = BUY, -1 = SELL
datetime             g_lastBarTime    = 0;

Кроме того, советник создаёт глобальные экземпляры классов CDisciplineLayer, CDisciplineGuardian и CDisciplinePanel, а также дескрипторы скользящих средних и локальные флаги сетапа, используемые во время выполнения.

3. Вспомогательные функции для управления торговыми операциями

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

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

//+------------------------------------------------------------------+
//| Helper: detect whether there is an open position on this symbol  |
//+------------------------------------------------------------------+
bool HasOpenPosition()
  {
   return PositionSelect(_Symbol);
  }

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

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

//+------------------------------------------------------------------+
//| Helper: choose a filling mode allowed by the symbol              |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING GetSymbolFillingType(const string symbol)
  {
   long filling = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);

   if((filling & SYMBOL_FILLING_FOK) != 0)
      return ORDER_FILLING_FOK;

   if((filling & SYMBOL_FILLING_IOC) != 0)
      return ORDER_FILLING_IOC;

   return ORDER_FILLING_RETURN;
  }

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

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

//+------------------------------------------------------------------+
//| Helper: close an open position on the current symbol             |
//+------------------------------------------------------------------+
bool CloseCurrentPosition()
  {
   if(!PositionSelect(_Symbol))
      return true;

   long   posType = PositionGetInteger(POSITION_TYPE);
   double volume  = PositionGetDouble(POSITION_VOLUME);

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};

   req.action       = TRADE_ACTION_DEAL;
   req.symbol       = _Symbol;
   req.volume       = volume;
   req.deviation    = 10;
   req.magic        = (ulong)inp_magicNumber;
   req.position     = (ulong)PositionGetInteger(POSITION_TICKET);
   req.type_filling = GetSymbolFillingType(_Symbol);

   if(posType == POSITION_TYPE_BUY)
     {
      req.type  = ORDER_TYPE_SELL;
      req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
     }
   else
     {
      req.type  = ORDER_TYPE_BUY;
      req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
     }

   if(OrderSend(req, res) && (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED))
     {
      Print("[TRADE] Existing position closed. Ticket=", res.order);
      return true;
     }

   Print("[TRADE] Failed to close position. Error=", GetLastError(),
         " retcode=", res.retcode,
         " comment=", res.comment);
   return false;
  }

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

4. Инициализация фреймворка

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

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_Discipline.SetSignalFreshnessMinutes(inp_signalFreshnessMinutes);

   if(inp_useSessionFilter)
      g_Discipline.EnableSessionFilter(inp_sessionStartHour, inp_sessionStartMin,
                                       inp_sessionEndHour, inp_sessionEndMin);
   else
      g_Discipline.DisableSessionFilter();

   g_Discipline.SetGlobalLockName("DISC_GLOBAL_LOCK");
   g_Discipline.Initialize();

   g_Guardian.SetGlobalLockPrefix("DISC_");
   g_Guardian.SetViolationMode(inp_violationMode);
   g_Guardian.Enable();

   if(inp_showDashboard)
     {
      g_Panel.Init("Disp", inp_dashboardX, inp_dashboardY, 380, clrDarkSlateGray, clrWhite);
      g_Panel.Show();
     }

   g_fastMAHandle = iMA(_Symbol, PERIOD_CURRENT, inp_fastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   g_slowMAHandle = iMA(_Symbol, PERIOD_CURRENT, inp_slowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);

   if(g_fastMAHandle == INVALID_HANDLE || g_slowMAHandle == INVALID_HANDLE)
      return INIT_FAILED;

   EventSetTimer(2);
   return INIT_SUCCEEDED;
  }

После этого советник создает два дескриптора SMA, используемые в торговой логике. В данном примере период быстрой SMA равен 20, а медленной — 50. Если не удается создать хотя бы один из дескрипторов, инициализация немедленно прерывается.

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

5. Освобождение ресурсов

При удалении советника функция OnDeinit() освобождает дескрипторы индикаторов, останавливает таймер и скрывает информационную панель. Это позволяет не загромождать график и не оставлять на нём объекты после выгрузки советника.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_fastMAHandle != INVALID_HANDLE)
      IndicatorRelease(g_fastMAHandle);

   if(g_slowMAHandle != INVALID_HANDLE)
      IndicatorRelease(g_slowMAHandle);

   EventKillTimer();
   g_Panel.Hide();
  }

6. Запуск уровня принудительного соблюдения правил

Функция OnTimer() поддерживает работу механизма контроля торговой дисциплины. Советник вызывает g_Discipline.CanTrade(), чтобы проверить, разрешена ли торговля в данный момент. Результат передается непосредственно в g_Guardian.Enforce(), что позволяет модулю контроля соблюдения правил применять заданную реакцию всякий раз, когда торговля не разрешена.

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   bool canTrade = g_Discipline.CanTrade();

   if(!canTrade)
      Print("[DISCIPLINE] Trade denied.");

   g_Guardian.Enforce(canTrade);

   if(inp_showDashboard)
      g_Panel.Update(g_Discipline, inp_violationMode);
  }

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

7. Выявление торговых сетапов

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

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

//+------------------------------------------------------------------+
//| Tick function                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(currentBarTime == g_lastBarTime)
      return;

   g_lastBarTime = currentBarTime;

   double fastMA[3];
   double slowMA[3];

   if(CopyBuffer(g_fastMAHandle, 0, 1, 3, fastMA) < 3 ||
      CopyBuffer(g_slowMAHandle, 0, 1, 3, slowMA) < 3)
      return;

   bool bullishCross =
      (fastMA[0] > slowMA[0] &&
       fastMA[1] <= slowMA[1]);

   bool bearishCross =
      (fastMA[0] < slowMA[0] &&
       fastMA[1] >= slowMA[1]);

   bool setupForming =
      (MathAbs(fastMA[0] - slowMA[0]) / _Point <= inp_formingDistancePoints);

   if(setupForming && !bullishCross && !bearishCross)
      g_Discipline.SetSetupState(SETUP_FORMING);

   if(bullishCross)
     {
      datetime expiry = TimeCurrent() + inp_setupExpirySeconds;

      g_Discipline.ConfirmSetup(expiry);
      g_Discipline.SetSetupState(SETUP_CONFIRMED);

      g_setupConfirmed = true;
      g_setupDirection = 1;

      Print("[SETUP] Bullish SMA crossover confirmed. Valid until ",
            TimeToString(expiry, TIME_DATE | TIME_MINUTES));
     }
   else if(bearishCross)
     {
      datetime expiry = TimeCurrent() + inp_setupExpirySeconds;

      g_Discipline.ConfirmSetup(expiry);
      g_Discipline.SetSetupState(SETUP_CONFIRMED);

      g_setupConfirmed = true;
      g_setupDirection = -1;

      Print("[SETUP] Bearish SMA crossover confirmed. Valid until ",
            TimeToString(expiry, TIME_DATE | TIME_MINUTES));
     }
   else if(!setupForming && !g_setupConfirmed)
     {
      g_Discipline.SetSetupState(NO_SETUP);
     }

   if(g_setupConfirmed && g_Discipline.CanTrade())
     {
      if(ExecuteTrade(g_setupDirection))
        {
         g_setupConfirmed = false;
         g_Discipline.SetSetupState(SETUP_ACTIVE);
        }
     }

   if(g_setupConfirmed && g_Discipline.GetExpiryTime() > 0 &&
      TimeCurrent() > g_Discipline.GetExpiryTime())
     {
      g_Discipline.ExpireSetup();
      g_setupConfirmed = false;
      g_setupDirection = 0;
     }
  }

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

Если условия сетапа перестают выполняться и нет сделки, ожидающей исполнения, состояние может вернуться к NO_SETUP. После исполнения сделки сетапу присваивается состояние SETUP_ACTIVE. По истечении срока действия сетап переводится в состояние SETUP_EXPIRED.

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

8. Разрешение на исполнение торговых операций

Подтверждение сетапа само по себе не приводит к исполнению сделки: сначала советник проверяет разрешение на торговлю — результат вызова g_Discipline.CanTrade().

//+------------------------------------------------------------------+
//| Trade authorization check                                        |
//+------------------------------------------------------------------+
if(g_setupConfirmed && g_Discipline.CanTrade())
  {
   ExecuteTrade(g_setupDirection);
   g_setupConfirmed = false;
  }

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

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

9. Исполнение сделки

Непосредственное выполнение торговой операции обеспечивает функция ExecuteTrade(int direction).

Эта функция поддерживает оба направления:

  • 1 — для покупки,
  • -1 — для продажи.

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

//+------------------------------------------------------------------+
//| Executes a market order                                          |
//+------------------------------------------------------------------+
bool ExecuteTrade(int direction)
  {
   if(direction != 1 && direction != -1)
      return false;

   if(PositionSelect(_Symbol))
     {
      long currentType = PositionGetInteger(POSITION_TYPE);

      if((direction == 1 && currentType == POSITION_TYPE_BUY) ||
         (direction == -1 && currentType == POSITION_TYPE_SELL))
        {
         Print("[TRADE] Position already aligned with signal. No new entry.");
         return false;
        }

      if(!CloseCurrentPosition())
         return false;
     }

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};

   req.action       = TRADE_ACTION_DEAL;
   req.symbol       = _Symbol;
   req.volume       = inp_lotSize;
   req.deviation    = 10;
   req.magic        = (ulong)inp_magicNumber;
   req.comment      = "Discipline SMA Crossover";
   req.type_time    = ORDER_TIME_GTC;
   req.type_filling = GetSymbolFillingType(_Symbol);

   if(direction > 0)
     {
      req.type  = ORDER_TYPE_BUY;
      req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
     }
   else
     {
      req.type  = ORDER_TYPE_SELL;
      req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
     }

   bool sent = OrderSend(req, res);

   if(sent && (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED))
     {
      Print("[TRADE] ", (direction > 0 ? "Buy" : "Sell"),
            " executed. Ticket=", res.order);
      return true;
     }

   Print("[TRADE] ", (direction > 0 ? "Buy" : "Sell"),
         " failed. Error=", GetLastError(),
         " retcode=", res.retcode,
         " comment=", res.comment);

   return false;
  }

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

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


Результаты

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

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


Заключение

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

Мы реализовали модель жизненного цикла сетапа в классе CDisciplineLayer, позволяющую сетапам проходить через заданные состояния и обеспечивающую проверку подтверждения, срока действия, актуальности сигнала, торговой сессии и блокировки через единую точку принятия решения CanTrade(). Затем мы добавили модуль CDisciplineGuardian для мониторинга активности на счете и реагирования на нарушения с помощью оповещений, автоматического закрытия позиций, удаления ордеров и, при необходимости, блокировки торговли. Для повышения прозрачности мы создали информационную панель CDisciplinePanel, которая отображает текущее состояние контроля торговой дисциплины непосредственно на графике.

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

Вложения

Имя файла Описание
DisciplineLayer.mqh Основной уровень разрешения торговли, отвечающий за проверку действительности сетапов и допустимости торговли.
DisciplineGuardian.mqh Модуль принудительного соблюдения правил во время выполнения программы, отвечающий за обработку нарушений и блокировку торговли.
DisciplinePanel.mqh Информационная панель на графике для отслеживания текущего состояния контроля торговой дисциплины.
ExampleTradingEA.mq5 Пример советника, демонстрирующий интеграцию фреймворка.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Oarabile Seboka
Oarabile Seboka | 1 июл. 2026 в 11:48
Это блестящий механизм, позволяющий структурно обеспечить надежное выполнение торговой стратегии.

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

Christian Benjamin
Christian Benjamin | 1 июл. 2026 в 14:20
Oarabile Seboka #:
Это блестящий механизм, позволяющий структурно обеспечить надежное выполнение торговой стратегии.

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

Спасибо, Oarabile. Я очень ценю ваш отзыв и рад, что вы сочли этот подход полезным.
Персистентное хранилище "ключ — значение" в MQL5: Плоские файлы как легковесная база данных для хранения состояния советника Персистентное хранилище "ключ — значение" в MQL5: Плоские файлы как легковесная база данных для хранения состояния советника
Благодаря облегченной архитектуре системы сохранения данных торговые советники (EA) могут сохранять счетчики, флаги и временные метки между перезапусками терминала. Используя только средства MQL5, CPersistentStore записывает удобочитаемый файл формата key=value в папку MQL5/Files и обслуживает запросы на чтение из кэша со сквозной записью на основе CHashMap через типизированный API. В статье анализируются операции со сложностью O(1) и O(n), риски неполной записи и отсутствие блокировки файлов, проводится сравнение с глобальными переменными терминала (GlobalVariables) и SQLite, а также приводится демонстрационный пример с детерминированной повторной загрузкой состояния.
Интерактивное управление зонами спроса и предложения на MQL5 (Часть III): Анализ зон и отслеживание взаимодействий цены с зоной Интерактивное управление зонами спроса и предложения на MQL5 (Часть III): Анализ зон и отслеживание взаимодействий цены с зоной
Мы расширяем программную основу управления зонами спроса и предложения с сохранением состояния для MetaTrader 5, добавляя количественную модель допуска зон в систему и специализированный механизм взаимодействия цены с зонами. Зоны-кандидаты получают балльную оценку по структурной симметрии, объемной активности участников рынка и смещению, нормированному по ATR, а затем распределяются по категориям качества на основе объективных критериев. Допущенные в систему зоны проходят детерминированный жизненный цикл, в ходе которого отслеживается первое касание, проверяются отскоки или подтверждаются пробои; при этом собирается полная телеметрия для анализа и обеспечения воспроизводимости.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Конструирование признаков для машинного обучения (Часть 8): Энтропийные признаки в MQL5 Конструирование признаков для машинного обучения (Часть 8): Энтропийные признаки в MQL5
Реализация на MQL5 четырех методов оценки энтропии — энтропии Шеннона, подстановочной оценки энтропии (Plug-In), сложности Лемпеля — Зива и энтропии Контоянниса — для последовательности, полученной по тиковому правилу внутри бара. CopyTicksRange() ограничивает доступные данные окном тикового кеша брокера, поэтому признаки можно рассчитывать только для недавних баров. В реализации тики из структуры MqlTick кодируются по направлению изменения цены Bid, операции, зависящие от NumPy, заменяются методами работы с массивами, а для использования в советниках и индикаторах предоставляются файлы CEntropyFeatures.mqh и EntropyViewer.mq5.