English
preview
Встраивание торговой дисциплины в код (Часть 6): Построение единого фреймворка дисциплины на MQL5

Встраивание торговой дисциплины в код (Часть 6): Построение единого фреймворка дисциплины на MQL5

MetaTrader 5Примеры |
45 0
Christian Benjamin
Christian Benjamin

Введение

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

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

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


Обзор системы

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

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

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

Фреймворк организован в четыре основных уровня:

  • Уровень конфигурации (Configuration Layer)
  • Движок дисциплины (Discipline Engine)
  • Визуальный дашборд (Visual Dashboard)
  • Интеграция советника (Expert Advisor Integration)

1. Уровень конфигурации

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

2. Движок дисциплины

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

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

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

3. Визуальный дашборд

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

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

4. Интеграция советника

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

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

Процесс обеспечения соблюдения правил

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

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



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

Фреймворк соблюдения дисциплины построен на основе единого движка управления и разделен на три части: движок, дашборд и советник-принудитель. Данная реализация объединяет ранее разработанные модули в единый дисциплинарный слой, управляемый файлом _CDisciplineEngine.mqh_. Эти модули включают в себя систему дневных лимитов на сделки, менеджер белого списка символов и фильтр торговых часов/новостей. Затем движок используется и индикатором дашборда, и советником, что гарантирует применение одних и тех же правил дисциплины всеми компонентами.

Шаг 1: Создание движка дисциплины — построение основного уровня управления

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

Интеграция вспомогательных модулей

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

#property copyright "Copyright 2026, Christian Benjamin"
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.0"

#include "DisciplineFramework/DailyTradeLimit.mqh"
#include "DisciplineFramework/SymbolWhitelist.mqh"
#include "DisciplineFramework/TradingHoursNews.mqh"

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

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

Проектирование класса основного движка

Следующий фрагмент демонстрирует основной интерфейс проверки, используемый CDisciplineEngine.

//+------------------------------------------------------------------+
//| Unified trading gate                                             |
//+------------------------------------------------------------------+
class CDisciplineEngine
  {
private:
   bool m_initialized;

public:
   CDisciplineEngine();

   bool Init();

   bool IsTradeAllowed(string symbol);

   bool IsInitialized() const;
   bool IsSymbolAllowed(string s);
   bool IsTradingHoursAllowed();
   bool IsDailyLimitAllowed();

   void RecordTrade();

   int GetTradesToday();
   int GetDailyLimit();
   int GetAmberZone();

   string GetNextSession();
   string GetNextNews();
  };

Конструктор инициализирует внутреннее состояние m_initialized значением false, предотвращая проверку сделок движком до завершения подготовки. Функция Init() выступает в качестве процедуры запуска фреймворка. Во время инициализации движок актуализирует данные системы дневных лимитов и фильтра торговых часов/новостей. Это гарантирует, что все торговые решения основаны на текущих рыночных условиях и обновленных данных о дисциплине до начала торговли. После успешного завершения инициализации движок переводится в рабочее состояние: m_initialized устанавливается в true.

Создание единого шлюза проверки сделок

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

//--- pre‑trade gate: whitelist + hours/news + daily limit
   bool              IsTradeAllowed(string symbol)
     {
      if(!m_initialized)
         return false;
      if(!SWL::IsSymbolAllowed(symbol))
         return false;
      THN::Refresh();
      if(!THN::IsAllowedNow())
         return false;
      DTL::Refresh();
      if(!DTL::IsTradingAllowed())
         return false;
      return true;
     }

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

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

Запись и мониторинг торговой активности

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

//--- dashboard getters
bool IsInitialized() const
  {
   return m_initialized;
  }

bool IsSymbolAllowed(string s)
  {
   return SWL::IsSymbolAllowed(s);
  }

bool IsTradingHoursAllowed()
  {
   THN::Refresh();
   return THN::IsAllowedNow();
  }

bool IsDailyLimitAllowed()
  {
   DTL::Refresh();
   return DTL::IsTradingAllowed();
  }

void RecordTrade()
  {
   DTL::ForceRefresh();
  }

int GetTradesToday()
  {
   DTL::Refresh();
   return DTL::TradesToday();
  }

int GetDailyLimit()
  {
   return DTL::GetParamLimit();
  }

int GetAmberZone()
  {
   return DTL::GetParamAmber();
  }

string GetNextSession()
  {
   THN::Refresh();
   return THN::GetNextSession();
  }

string GetNextNews()
  {
   THN::Refresh();
   return THN::GetNextNews();
  }

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

Шаг 2: Создание индикатора дашборда — визуализация системы контроля дисциплины

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

Интеграция движка дисциплины

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

#property copyright "Copyright 2026, Christian Benjamin"
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.0"
#property indicator_chart_window
#property indicator_plots 0

#include <DisciplineFramework/CDisciplineEngine.mqh>

//--- input parameters
input int      RefreshSeconds    = 2;
input color    PanelBackColor    = C'25,30,45';
input color    PanelBorderColor  = C'80,90,110';
input color    TextMainColor     = clrWhite;
input color    TextLabelColor    = C'160,170,190';
input color    AllowedColor      = C'50,205,50';
input color    BlockedColor      = C'255,70,70';
input color    CautionColor      = C'255,180,50';
input int      PanelX            = 20;
input int      PanelY            = 40;
input int      PanelWidth        = 340;

CDisciplineEngine g_engine;
string            g_prefix = "DisciplinePanel_";

Настройки #property определяют, как индикатор регистрируется в MetaTrader 5. Используя indicator_chart_window, дашборд отображается непосредственно на основном ценовом графике, а не в отдельном подокне, что позволяет отображать панель дисциплины прямо на основном графике рядом с торговой активностью. Директива indicator_plots 0 подтверждает, что традиционные буферы индикатора не используются, поскольку индикатор функционирует только как панель мониторинга. Подключение к основной системе контроля дисциплины осуществляется через #include _<DisciplineFramework/CDisciplineEngine.mqh>_. Она обеспечивает логику проверки для фильтрации символов, проверки торговых сессий и дневных лимитов сделок. Сам дашборд не рассчитывает эти правила; он только считывает состояние движка.

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

Инициализация и настройка среды выполнения

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!g_engine.Init())
     {
      Print("Dashboard: Engine init failed.");
      return INIT_FAILED;
     }
   EventSetTimer(RefreshSeconds);
   CreatePanel();
   UpdateDashboard();
   return INIT_SUCCEEDED;
  }

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

Далее EventSetTimer(RefreshSeconds) запускает цикл обновления по расписанию для OnTimer(), позволяя дашборду обновляться независимо от активности тиков. После этого функция CreatePanel() создает визуальный интерфейс, а UpdateDashboard() загружает текущее состояние торговли, чтобы панель отображала точную информацию сразу после добавления индикатора.

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

Деинициализация и управление объектами

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   for(int i = ObjectsTotal(0)-1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, g_prefix) == 0)
         ObjectDelete(0, name);
     }
   Comment("");
  }

Функция OnDeinit() обеспечивает надлежащую очистку при удалении индикатора с графика. Вызывая EventKillTimer(), она останавливает все запланированные обновления, предотвращая дальнейшее выполнение OnTimer() после завершения работы системы. Далее цикл ObjectsTotal() удаляет все графические объекты, созданные дашбордом. В этом процессе используется соглашение об именовании g_prefix для удаления только объектов, созданных дашбордом. Наконец, Comment("") удаляет весь оставшийся текст на графике, возвращая его в чистое состояние.

Создание интерфейса дашборда (уровень построения панелей)

Функция _CreatePanel()_ создает визуальную компоновку дашборда и начинается с определения значений локального позиционирования, используемых для управления интервалами и выравниванием. Переменные prefix, yOff, rowH и y управляют именованием, размещением и интервалами. Эти значения обеспечивают согласованное выравнивание дашборда по различным размерам и разрешениям графика. Далее создается основной фон с помощью OBJ_RECTANGLE_LABEL, который обеспечивает как точное позиционирование, так и заливку фона. Его фиксированная ширина и высота помогают поддерживать стабильность компоновки панели по различным символам и таймфреймам.

//+------------------------------------------------------------------+
//| Create static panel elements                                     |
//+------------------------------------------------------------------+
void CreatePanel()
  {
   string prefix = g_prefix;
   int yOff = PanelY;
   int rowH = 24;
   int y = yOff + 32;

//--- background (taller to fit news)
   ObjectCreate(0, prefix+"Bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_XDISTANCE, PanelX);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_YDISTANCE, yOff);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_XSIZE, PanelWidth);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_YSIZE, 230);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_BGCOLOR, PanelBackColor);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_BORDER_COLOR, PanelBorderColor);
   ObjectSetInteger(0, prefix+"Bg", OBJPROP_WIDTH, 2);

//--- title bar
   ObjectCreate(0, prefix+"TitleBar", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, prefix+"TitleBar", OBJPROP_XDISTANCE, PanelX);
   ObjectSetInteger(0, prefix+"TitleBar", OBJPROP_YDISTANCE, yOff);
   ObjectSetInteger(0, prefix+"TitleBar", OBJPROP_XSIZE, PanelWidth);
   ObjectSetInteger(0, prefix+"TitleBar", OBJPROP_YSIZE, 28);
   ObjectSetInteger(0, prefix+"TitleBar", OBJPROP_BGCOLOR, C'45,55,75');

//--- title text
   ObjectCreate(0, prefix+"TitleText", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, prefix+"TitleText", OBJPROP_XDISTANCE, PanelX+10);
   ObjectSetInteger(0, prefix+"TitleText", OBJPROP_YDISTANCE, yOff+6);
   ObjectSetInteger(0, prefix+"TitleText", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, prefix+"TitleText", OBJPROP_FONTSIZE, 12);
   ObjectSetString(0, prefix+"TitleText", OBJPROP_FONT, "Arial Bold");
   ObjectSetString(0, prefix+"TitleText", OBJPROP_TEXT, "TRADING GATE");

//--- labels
   CreateLabel(prefix+"LblSymbol",    PanelX+10, y,    "Symbol:", TextLabelColor, 9);
   CreateLabel(prefix+"LblTime",      PanelX+180, y,   "Time:", TextLabelColor, 9);
   CreateLabel(prefix+"LblWhitelist", PanelX+10, y+rowH*1, "Whitelist:", TextLabelColor, 9);
   CreateLabel(prefix+"LblHours",     PanelX+10, y+rowH*2, "Trading Hours:", TextLabelColor, 9);
   CreateLabel(prefix+"LblDaily",     PanelX+10, y+rowH*3, "Daily Limit:", TextLabelColor, 9);
   CreateLabel(prefix+"LblNextSess",  PanelX+10, y+rowH*4, "Next Session:", TextLabelColor, 9);
   CreateLabel(prefix+"LblNextNews",  PanelX+10, y+rowH*5, "Next News:", TextLabelColor, 9);

//--- value fields
   CreateLabel(prefix+"SymbolVal",    PanelX+70,  y,    "", TextMainColor, 9);
   CreateLabel(prefix+"TimeVal",      PanelX+220, y,    "", TextMainColor, 9);
   CreateLabel(prefix+"WhitelistVal", PanelX+100, y+rowH*1, "", TextMainColor, 9);
   CreateLabel(prefix+"HoursVal",     PanelX+120, y+rowH*2, "", TextMainColor, 9);
   CreateLabel(prefix+"DailyVal",     PanelX+100, y+rowH*3, "", TextMainColor, 9);
   CreateLabel(prefix+"NextSessVal",  PanelX+100, y+rowH*4, "", TextMainColor, 9);
   CreateLabel(prefix+"NextNewsVal",  PanelX+100, y+rowH*5, "", TextMainColor, 9);

//--- overall status bar
   int overallY = yOff + 32 + rowH*6 + 10;
   ObjectCreate(0, prefix+"OverallBg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, prefix+"OverallBg", OBJPROP_XDISTANCE, PanelX);
   ObjectSetInteger(0, prefix+"OverallBg", OBJPROP_YDISTANCE, overallY);
   ObjectSetInteger(0, prefix+"OverallBg", OBJPROP_XSIZE, PanelWidth);
   ObjectSetInteger(0, prefix+"OverallBg", OBJPROP_YSIZE, 38);
   ObjectSetInteger(0, prefix+"OverallBg", OBJPROP_BGCOLOR, C'45,55,75');

   ObjectCreate(0, prefix+"OverallText", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, prefix+"OverallText", OBJPROP_XDISTANCE, PanelX+PanelWidth/2-60);
   ObjectSetInteger(0, prefix+"OverallText", OBJPROP_YDISTANCE, overallY+10);
   ObjectSetInteger(0, prefix+"OverallText", OBJPROP_FONTSIZE, 12);
   ObjectSetString(0, prefix+"OverallText", OBJPROP_FONT, "Arial Bold");
   ObjectSetString(0, prefix+"OverallText", OBJPROP_TEXT, "TRADING BLOCKED");
   ObjectSetInteger(0, prefix+"OverallText", OBJPROP_COLOR, BlockedColor);
  }

PanelBackColor и PanelBorderColor визуально отделяют дашборд от графика, а полоса заголовка обеспечивает четкий заголовок для панели. Более темный фон отличает область заголовка от основной панели, а текст заголовка отображает ТОРГОВЫЙ ШЛЮЗ (TRADING GATE). Для заголовка используется объект типа OBJ_LABEL, поскольку он отображается без фона, а стиль шрифта и позиционирование обеспечивают центрирование текста внутри полосы заголовка.

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

//+------------------------------------------------------------------+
//| Create dashboard label                                           |
//+------------------------------------------------------------------+
void CreateLabel(string name,
                 int x,
                 int y,
                 string text,
                 color clr=colorWhite,
                 int fontSize=9)
  {
   ObjectCreate(0,name,OBJ_LABEL,0,0,0);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

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

Синхронизация в реальном времени между движком и дашбордом

//+------------------------------------------------------------------+
//| Update dashboard values                                          |
//+------------------------------------------------------------------+
void UpdateDashboard()
  {
   if(!g_engine.IsInitialized())
      return;

   string symbol = Symbol();
   datetime now = TimeCurrent();

   bool symAllowed = g_engine.IsSymbolAllowed(symbol);
   bool hoursAllowed = g_engine.IsTradingHoursAllowed();
   int trades = g_engine.GetTradesToday();
   int limit = g_engine.GetDailyLimit();
   bool dailyAllowed = (trades < limit);
   string nextSession = g_engine.GetNextSession();
   string nextNews = g_engine.GetNextNews();
   if(nextNews == "")
      nextNews = "None";

//--- update text
   ObjectSetString(0, g_prefix+"SymbolVal", OBJPROP_TEXT, symbol);
   ObjectSetString(0, g_prefix+"TimeVal", OBJPROP_TEXT, TimeToString(now, TIME_MINUTES));

   ObjectSetString(0, g_prefix+"WhitelistVal", OBJPROP_TEXT, symAllowed ? "ALLOWED" : "BLOCKED");
   ObjectSetInteger(0, g_prefix+"WhitelistVal", OBJPROP_COLOR, symAllowed ? AllowedColor : BlockedColor);

   ObjectSetString(0, g_prefix+"HoursVal", OBJPROP_TEXT, hoursAllowed ? "OPEN" : "BLOCKED");
   ObjectSetInteger(0, g_prefix+"HoursVal", OBJPROP_COLOR, hoursAllowed ? AllowedColor : BlockedColor);

   string dailyText = StringFormat("%d/%d", trades, limit);
   color dailyColor = dailyAllowed ? ((limit - trades) <= g_engine.GetAmberZone() ? CautionColor : AllowedColor) : BlockedColor;
   ObjectSetString(0, g_prefix+"DailyVal", OBJPROP_TEXT, dailyText);
   ObjectSetInteger(0, g_prefix+"DailyVal", OBJPROP_COLOR, dailyColor);

   ObjectSetString(0, g_prefix+"NextSessVal", OBJPROP_TEXT, (nextSession!="" ? nextSession : "None"));
   ObjectSetString(0, g_prefix+"NextNewsVal", OBJPROP_TEXT, nextNews);

//--- overall status
   bool overallAllowed = symAllowed && hoursAllowed && dailyAllowed;
   string overallText = overallAllowed ? "TRADING ALLOWED" : "TRADING BLOCKED";
   color overallColor = overallAllowed ? AllowedColor : BlockedColor;
   ObjectSetString(0, g_prefix+"OverallText", OBJPROP_TEXT, overallText);
   ObjectSetInteger(0, g_prefix+"OverallText", OBJPROP_COLOR, overallColor);
   ObjectSetInteger(0, g_prefix+"OverallBg", OBJPROP_BGCOLOR, overallAllowed ? C'30,70,30' : C'70,30,30');
  }

Функция UpdateDashboard() является связующим звеном между CDisciplineEngine и визуальной панелью во время выполнения. Сначала она проверяет значение IsInitialized(), поэтому никакие значения не отображаются до тех пор, пока движок не загрузит свои внутренние модули. После этого она считывает текущий символ и серверное время с помощью функций Symbol() и TimeCurrent(), а затем запрашивает у движка информацию о текущем состоянии дисциплины, включая статус допуска символа к торговле, статус торговых часов, текущее количество сделок за день, дневной лимит, а также следующую торговую сессию и новостное событие. Это позволяет напрямую привязать дашборд к состоянию движка, а не к каким-либо отдельным вычислениям внутри индикатора.

Далее отображаемые значения обновляются с помощью ObjectSetString() и ObjectSetInteger(). Статус символа и торговых часов отображается как РАЗРЕШЕНО (ALLOWED) или ЗАБЛОКИРОВАНО (BLOCKED), ежедневное использование форматируется с помощью StringFormat(), а GetAmberZone() добавляет предупреждение, когда лимит приближается к исчерпанию. GetNextSession() и GetNextNews() предоставляют контекст на ближайшую перспективу, а итоговая булева проверка объединяет условия белого списка, торговых часов и дневного лимита в один общий результат. Когда все условия выполнены, на панели отображается сообщение ТОРГОВЛЯ РАЗРЕШЕНА (TRADING ALLOWED); в противном случае — ТОРГОВЛЯ ЗАБЛОКИРОВАНА (TRADING BLOCKED).

Шаг 3: Создание советника-принудителя по сделкам

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

//+------------------------------------------------------------------+
//|                                                TradeEnforcer.mq5 |
//|                               Copyright 2026, Christian Benjamin |
//|                          https://www.mql5.com/ru/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Christian Benjamin"
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.00"
#property strict

#include <Trade/Trade.mqh>
#include <DisciplineFramework/CDisciplineEngine.mqh>

//--- input
input int EnforceIntervalSec = 5;

CDisciplineEngine g_enforcer;

Функция #property strict обеспечивает более строгую проверку типов и более надежную валидацию компилятором, что помогает уменьшить количество скрытых логических ошибок в советнике-принудителе. Файл также включает <Trade/Trade.mqh> для операций закрытия позиций через CTrade и <DisciplineFramework/CDisciplineEngine.mqh> для подключения советника к централизованному фреймворку обеспечения соблюдения дисциплины, вместо дублирования логики валидации внутри самого советника.

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

Инициализация

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

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

//+------------------------------------------------------------------+
//| Initialization                                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!g_enforcer.Init())
     {
      Print("[ENFORCER] Init failed.");
      return INIT_FAILED;
     }
   if(!EventSetTimer(EnforceIntervalSec))
     {
      Print("[ENFORCER] Timer failed.");
      return INIT_FAILED;
     }
   Print("[ENFORCER] Started. Monitoring all new trades.");
   return INIT_SUCCEEDED;
  }

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

Деинициализация

Функция OnDeinit() отвечает за безопасное завершение работы системы обеспечения соблюдения правил.

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   Print("[ENFORCER] Stopped.");
  }

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

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

Периодический мониторинг позиций

Функция OnTimer() действует как уровень непрерывного сканирования советника. Ее задача — периодически проверять все открытые позиции и убедиться, что они соответствуют правилам фреймворка дисциплины. Процесс мониторинга перебирает все открытые позиции с помощью PositionsTotal(). Для каждой позиции советник получает тикет, выбирает позицию по тикету и считывает символ с помощью PositionGetString(POSITION_SYMBOL).

//+------------------------------------------------------------------+
//| Timer – periodic scan for any missed positions                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   for(int i = PositionsTotal()-1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket))
         continue;
      string symbol = PositionGetString(POSITION_SYMBOL);
      if(!IsPositionValid(symbol))
        {
         Print("[ENFORCER] Periodic close of ", symbol, " ticket ", ticket);
         ClosePosition(ticket);
        }
     }
  }

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

Фиксация новой торговой активности

Функция OnTradeTransaction() служит уровнем обеспечения соблюдения правил, управляемым событиями, в рамках фреймворка. В отличие от OnTimer(), которая выполняет запланированные сканирования, эта функция реагирует немедленно при возникновении новой торговой транзакции. Обработка начинается с фильтрации по TRADE_TRANSACTION_DEAL_ADD, чтобы оценивались только вновь созданные сделки. Далее советник получает подробную информацию о сделках из истории счета, используя функции HistoryDealSelect(), HistoryDealGetInteger() и HistoryDealGetString().

//+------------------------------------------------------------------+
//| OnTradeTransaction                                               |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- correct constant: TRADE_TRANSACTION_DEAL_ADD
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
     {
      ulong dealTicket = trans.deal;
      if(HistoryDealSelect(dealTicket))
        {
         long entryType = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
         if(entryType == DEAL_ENTRY_IN)
           {
            string symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
            if(!IsPositionValid(symbol))
              {
               ulong positionTicket = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
               if(positionTicket > 0 && PositionSelectByTicket(positionTicket))
                 {
                  Print("[ENFORCER] Violation detected. Closing new trade on ", symbol, " ticket ", positionTicket);
                  ClosePosition(positionTicket);
                 }
              }
            else
              {
               g_enforcer.RecordTrade();
              }
           }
        }
     }
  }

Условие DEAL_ENTRY_IN гарантирует, что проверяются только вновь открытые позиции, исключая выходы из сделок, их изменения или частичное закрытие. Как только обнаруживается удовлетворяющая условиям сделка, система немедленно оценивает её с помощью функции IsPositionValid(). Если позиция нарушает какое-либо правило дисциплины, советник получает соответствующий тикет позиции, выбирает активную позицию и закрывает её с помощью функции ClosePosition(). Если сделка допустима, g_enforcer.RecordTrade() обновляет дневной счётчик. Такая событийно-ориентированная структура позволяет советнику немедленно реагировать на сделки, открытые вручную, и позиции, открытые внешними системами.

Централизованная проверка позиций

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

//+------------------------------------------------------------------+
//| Check if a position is valid under all rules                     |
//+------------------------------------------------------------------+
bool IsPositionValid(string symbol)
  {
   if(!g_enforcer.IsSymbolAllowed(symbol))
      return false;
   if(!g_enforcer.IsTradingHoursAllowed())
      return false;
   int tradesNow = g_enforcer.GetTradesToday();
   int limit = g_enforcer.GetDailyLimit();
   if(tradesNow >= limit)
      return false;
   return true;
  }

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

Автоматическое закрытие позиций

Функция ClosePosition() выполняет фактическое действие по обеспечению соблюдения правил при обнаружении нарушения. Исполнение сделок осуществляется через класс CTrade , предоставляемый <Trade/Trade.mqh>. Метод PositionClose() отправляет запрос на закрытие непосредственно на торговую платформу.

//+------------------------------------------------------------------+
//| Close a position                                                 |
//+------------------------------------------------------------------+
void ClosePosition(ulong ticket)
  {
   CTrade trade;
   trade.PositionClose(ticket);
   if(trade.ResultRetcode() == TRADE_RETCODE_DONE)
      Print("[ENFORCER] Closed position ", ticket);
   else
      Print("[ENFORCER] Failed to close ", ticket, " error ", trade.ResultRetcode());
  }

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



Результаты

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

  • 2026.05.12 23:11:59.368 DisciplineDashboard (EURUSD,M5) [THN] Loaded news: 2026.05.14 14:30 pre=30 post=45
  • 2026.05.12 23:12:03.372 DisciplineDashboard (EURUSD,M5) [THN] Loaded news: 2026.05.15 16:00 pre=20 post=20
  • 2026.05.12 23:12:03.372 DisciplineDashboard (EURUSD,M5) [THN] Total news events loaded: 3
  • 2026.05.12 23:12:05.362 DisciplineDashboard (EURUSD,M5) [THN] Total news events loaded: 3
  • 2026.05.12 23:12:57.552 DisciplineDashboard (EURUSD,M5) [THN] Next news: 2026.05.13 10:00

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

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

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

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



Заключение

Данная реализация объединяет модули дисциплины в рамках файла _CDisciplineEngine.mqh_ для централизованного управления сделками. Система объединяет проверку белого списка символов, ограничения по торговым часам и новостям, а также ежедневные лимиты сделок в единый рабочий процесс, который непрерывно оценивает торговые условия.

Реализация разделена на три скоординированных уровня: движок дисциплины для централизованной проверки, дашборд для мониторинга и визуализации в реальном времени и советник-принудитель для контроля за сделками в реальном времени. Фреймворк проверяет сделки с помощью _OnTradeTransaction()_ и периодических сканирований _OnTimer()_. Он проверяет как вновь открытые, так и существующие позиции, независимо от того, были ли они исполнены вручную или с помощью внешних советников.

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

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

Название файла   
Тип
Описание  
Include/DisciplineFramework/CDisciplineEngine.mqh
Включаемый файл MQL5
Центральный движок дисциплины, координирующий проверку символов, проверки по торговым часам, новостные ограничения и дневные лимиты сделок.
Include/DisciplineFramework/DailyTradeLimit.mqh
Включаемый файл MQL5 
Обрабатывает ежедневный подсчет сделок, проверку лимитов и статистику использования дневного лимита сделок.
Include/DisciplineFramework/SymbolWhitelist.mqh
Включаемый файл MQL5
Ограничивает торговлю разрешенными символами, определенными в рамках данного фреймворка.
Include/DisciplineFramework/TradingHoursNews.mqh
Включаемый файл MQL5
Управляет торговыми сессиями, периодами новостной блокировки и временными ограничениями на совершение сделок.
Indicators/DisciplineDashboard.mq5
Индикатор MQL5
Дашборд в реальном времени отображает статус белого списка, состояние торговых часов, дневные лимиты, следующую сессию и предстоящие новостные события.
Experts/TradeEnforcer.mq5
Советник MQL5
Уровень обеспечения соблюдения правил, который отслеживает позиции, проверяет сделки с помощью движка дисциплины и блокирует или закрывает сделки, нарушающие правила.
Files/NewsEvents.csv
Файл данных CSV
Содержит информацию о запланированных экономических новостях и периодах новостных блокировок.
Files/TradingSessions.txt
Текстовый файл данных
Хранит разрешенные торговые сессии, используемые системой проверки торговых часов.

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

Прикрепленные файлы |
Автоматизация торговых стратегий в MQL5 (Часть 32): Создание системы распознавания гармонического паттерна "5 Drives" с использованием Price Action Автоматизация торговых стратегий в MQL5 (Часть 32): Создание системы распознавания гармонического паттерна "5 Drives" с использованием Price Action
В этой статье мы разрабатываем систему распознавания паттернов "5 Drives" на языке MQL5, которая определяет бычьи и медвежьи гармонические паттерны "5 Drives" с использованием точек разворота и коэффициентов Фибоначчи, совершая сделки с настраиваемыми уровнями входа, стоп-лосса и тейк-профита на основе выбранных пользователем параметров. Мы улучшим работу трейдеров с помощью визуальной обратной связи, используя графические объекты, такие как треугольники, линии тренда и подписи, для наглядного отображения структуры паттерна A-B-C-D-E-F.
Создание индикатора ZigZag со статистикой волн в MQL5 Создание индикатора ZigZag со статистикой волн в MQL5
Индикатор ZigZag дополняется количественной оценкой длин волн: для восходящих и нисходящих движений считаются текущая длина, максимум, минимум, среднее и медиана, обновляемые на каждом новом баре. Разбираются ключевые элементы реализации: поиск экстремумов в скользящем окне, конечный автомат пивотов, инкрементальный пересчёт и отключение визуального слоя в тестере. Практическая польза — объективные численные ориентиры вместо визуальной оценки на глаз.
Алгоритм бактериальной эволюции Нумаоки для быстрой адаптации (NBE) Алгоритм бактериальной эволюции Нумаоки для быстрой адаптации (NBE)
Рассматриваем модель Нумаоки как агентную колонию с энергией, гибелью-делением и плазмидным переносом, ориентированную на быструю адаптацию. Переносим её в каркас C_AO: энергобаланс с популяционно-относительным приходом, хемотаксис-поиск, рулетка по энергии; приводим псевдокод и MQL5-реализацию. Проводим тестирование на Hilly, Forest и Megacity, показывая поведение и ограничения модели на статике для практического использования.
Создание пользовательских индикаторов в MQL5 (Часть 11): Улучшение футпринт-графика с помощью слоёв рыночной структуры и ордер-флоу Создание пользовательских индикаторов в MQL5 (Часть 11): Улучшение футпринт-графика с помощью слоёв рыночной структуры и ордер-флоу
В статье MQL5-индикатор футпринт-графика расширяется слоями рыночной структуры и ордер-флоу: барами объёмного профиля, точкой контроля (Point of Control, POC), подсветкой зоны стоимости (value area), выявлением последовательных дисбалансов, зонами поглощения, а также маркерами single print (одиночных принтов) и unfinished business (незавершённой аукционной отработки экстремума). Мы расширим структуры данных бара, добавим функции для POC/зоны стоимости, дисбаланса и поглощения, а также построим конвейер отрисовки с фиксированным порядком слоёв. Вы получите готовые входные параметры, метаданные и утилиты отрисовки для интеграции и настройки этих слоёв в индикаторе.