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

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

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

Содержание


Введение

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

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

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

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


Понимание логики дисциплины

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

Факторы, влияющие на торговую дисциплину

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

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

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

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

Автоматизация торговой дисциплины

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

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

Логика дисциплины в этой системе

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

В процессе работы система выполняет следующие функции:

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

  • Разрешено — торговля находится в пределах лимита.
  • Предупреждение — количество оставшихся сделок упало до порогового значения или ниже него.
  • Лимит достигнут — выполнено максимальное количество сделок.

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

5. Контролирует соблюдение лимита, активно блокируя любые новые сделки после перехода в состояние “Лимит достигнут”:

  • Любой новый отложенный ордер, размещенный после достижения лимита, немедленно удаляется.
  • Любая вновь открытая позиция (будь то советником или вручную) немедленно закрывается.
  • Существующие позиции, открытые до достижения лимита, остаются без изменений — система лишь предотвращает дальнейшее увеличение риска.

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

Рисунок 1: Поток принятия решений от начала дня до обеспечения соблюдения лимита

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

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

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

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


Обзор архитектуры системы

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

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

Вот три компонента:

1. Общая библиотека—DailyTradeLimit.mqh

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

  • Подсчет сделок, совершенных в течение текущего торгового дня
  • Определение активного торгового дня на основе заданного времени начала
  • Расчет оставшихся сделок
  • Классификация торгового состояния (Разрешено / Предупреждение / Лимит достигнут).

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

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

2. Индикатор дашборда — DailyTradeLimitDashboard.mq5

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

  1. Считывает заданные пользователем параметры (дневной лимит, время начала, пороговое значение желтого индикатора, настройки оповещений)
  2. Записывает эти настройки в глобальные переменные
  3. Отображает цветовую панель, показывающую:
    • Сделки, совершенные сегодня
    • Оставшиеся сделки
    • Текущее состояние

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

3. Советник-принудитель — DailyTradeLimitEnforcer.mq5

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

Если лимит превышен, советник немедленно

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

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

Взаимодействие через глобальные переменные

Все три компонента используют следующие общие глобальные переменные:

  1. DTL_LIMIT — Максимальное количество сделок, разрешенных в день (хранится как число double, но обрабатывается как целое число).
  2. DTL_START_TIME — Время начала торгового дня, закодированное в формате HHMM (например, 900 для 09:00).
  3. DTL_AMBER — Пороговое значение для состояния предупреждения (количество оставшихся сделок ≤ этому значению).

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

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

Рисунок 2: Поток взаимодействия


Почему используется такая структура?

Разделение системы на три взаимодействующих модуля обеспечивает ряд структурных преимуществ:

Преимущество Объяснение
Возможность повторного использования Любой советник может включать общую библиотеку и добровольно проверять IsTradingAllowed(). Даже если он этого не делает, советник-принудитель все равно гарантирует соответствие требованиям.
Независимость Дашборд и советник-принудитель могут работать на отдельных графиках. Ни один из них не мешает работе других советников или сценариям ручного исполнения сделок.
Ясность Каждый компонент следует принципу единственной ответственности, что упрощает понимание, тестирование и обслуживание системы.
Расширяемость Дополнительные ограничения, такие как дневные лимиты прибыли, лимиты просадки или временные торговые окна, могут быть реализованы путем расширения общей библиотеки при сохранении того же архитектурного подхода.

Заложив эту архитектурную основу, мы переходим к пошаговой реализации каждого компонента.


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

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

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

A) DailyTradeLimit.mqh—Общая библиотека

  • Утилиты преобразования времени

Библиотека предоставляет TimeToInt и IntToTime для преобразования удобного для человека ввода времени в формат хранения терминала. TimeToInt преобразует строку, например, "09:00", в целое число 900, что удобно для сохранения в глобальной переменной терминала. IntToTime выполняет обратное преобразование, превращая 900 обратно в "09:00" для отображения или обработки. Такое кодирование делает конфигурацию легковесной, при этом она остается простой для интерпретации.

//+------------------------------------------------------------------+
//| Convert "HH:MM" to HHMM (e.g., "09:00" -> 900)                   |
//+------------------------------------------------------------------+
int TimeToInt(string timeStr)
  {
   string parts[];
   if(StringSplit(timeStr,':',parts)!=2)
      return 0;
   int h=(int)StringToInteger(parts[0]);
   int m=(int)StringToInteger(parts[1]);
   return h*100+m;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Convert HHMM to "HH:MM"                                           |
//+------------------------------------------------------------------+
string IntToTime(int value)
  {
   int h=value/100;
   int m=value%100;
   return StringFormat("%02d:%02d",h,m);
  }
//+------------------------------------------------------------------+

  • Получение параметров

Конфигурация считывается из глобальных переменных терминала с помощью функций GetParamLimit, GetParamStartTime и GetParamAmber. Каждая функция считывает соответствующую глобальную переменную (DTL_LIMIT, DTL_START_TIME, DTL_AMBER) и возвращается к разумному значению по умолчанию, если глобальная переменная не задана. Это гарантирует, что все компоненты — дашборд и советник-принудитель — используют один и тот же источник конфигурации без дублирования логики.

//+------------------------------------------------------------------+
//| Get the daily trade limit (max trades per day)                   |
//+------------------------------------------------------------------+
int GetParamLimit()
  {
   if(GlobalVariableCheck("DTL_LIMIT"))
      return (int)GlobalVariableGet("DTL_LIMIT");
   return DEFAULT_LIMIT;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get the configured start time as a string                        |
//+------------------------------------------------------------------+
string GetParamStartTime()
  {
   if(GlobalVariableCheck("DTL_START_TIME"))
     {
      int t=(int)GlobalVariableGet("DTL_START_TIME");
      return IntToTime(t);
     }
   return DEFAULT_START_TIME;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get the amber threshold (remaining trades before caution)        |
//+------------------------------------------------------------------+
int GetParamAmber()
  {
   if(GlobalVariableCheck("DTL_AMBER"))
      return (int)GlobalVariableGet("DTL_AMBER");
   return DEFAULT_AMBER;
  }
//+------------------------------------------------------------------+

  • Расчет времени

Функция ParseTime преобразует строку "HH:MM" в отдельные целочисленные значения часов и минут, которые функция GetDayStart использует для привязки к текущему торговому дню. GetDayStart сравнивает время брокера с заданным временем начала, и, если текущее время раньше начала сегодняшнего дня, использует вчерашний день в качестве привязки; в противном случае используется сегодняшний день. Этот точный расчет гарантирует, что счетчик сделок за день обнуляется в правильный момент времени в разных часовых поясах брокеров.

//+------------------------------------------------------------------+
//| Parse "HH:MM" into hour and minute integers                      |
//+------------------------------------------------------------------+
bool ParseTime(string txt,int &h,int &m)
  {
   string parts[];
   if(StringSplit(txt,':',parts)<2)
      return false;
   h=(int)StringToInteger(parts[0]);
   m=(int)StringToInteger(parts[1]);
   return (h>=0 && h<=23 && m>=0 && m<=59);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Calculate the start datetime of the current trading day          |
//+------------------------------------------------------------------+
datetime GetDayStart(datetime now,string startTime)
  {
   int h=0,m=0;
   if(!ParseTime(startTime,h,m))
     {
      h=0;
      m=0;
     }
   MqlDateTime t;
   TimeToStruct(now,t);

   //--- If current time is before today's start, use yesterday
   if(t.hour<h || (t.hour==h && t.min<m))
      now-=86400;

   TimeToStruct(now,t);
   t.hour=h;
   t.min=m;
   t.sec=0;
   return StructToTime(t);
  }
//+------------------------------------------------------------------+

  • Подсчет сделок

Функция CountTrades подсчитывает все сделки с DEAL_ENTRY_IN с момента вычисленного начала дня, сканируя историю в пределах дневного периода. Она использует HistorySelect для ограничения области поиска и перебирает сделки, чтобы подсчитать те, которые имеют DEAL_ENTRY_IN.

//+------------------------------------------------------------------+
//| Count trades (DEAL_ENTRY_IN) since a given datetime              |
//+------------------------------------------------------------------+
int CountTrades(datetime from)
  {
   datetime to=from+86400;
   if(!HistorySelect(from,to))
      return 0;

   int total=HistoryDealsTotal();
   int cnt=0;
   for(int i=0; i<total; i++)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket>0 && HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_IN)
         cnt++;
     }
   return cnt;
  }
//+------------------------------------------------------------------+

  • Расчет состояния

Функция CalcState сравнивает количество сделок с дневным лимитом и предупредительным порогом, возвращая STATE_ALLOWED, STATE_CAUTION или STATE_LIMIT. Это обеспечивает простое и согласованное сопоставление числового подсчета с четким состоянием торговли.

//+------------------------------------------------------------------+
//| Map the trade count to a state (allowed/caution/limit)           |
//+------------------------------------------------------------------+
ENUM_DTL_STATE CalcState(int trades,int limit,int amber)
  {
   if(trades>=limit)
      return STATE_LIMIT;
   if(limit-trades<=amber)
      return STATE_CAUTION;
   return STATE_ALLOWED;
  }
//+------------------------------------------------------------------+

  • Управление кэшем

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

//+------------------------------------------------------------------+
//| Cached refresh – only performs a full update every 1 second      |
//+------------------------------------------------------------------+
bool Refresh()
  {
   datetime now=TimeCurrent();
   if(now-s_lastRefresh<REFRESH_INTERVAL)
      return false;
   return ForceRefresh();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Force a full recount and update all cache values                 |
//+------------------------------------------------------------------+
bool ForceRefresh()
  {
   datetime now=TimeCurrent();
   int limit=GetParamLimit();
   string st=GetParamStartTime();
   int amber=GetParamAmber();

   datetime newDayStart=GetDayStart(now,st);
   int newTrades=0;

   if(newDayStart!=s_dayStart)
     {
      s_dayStart=newDayStart;
      newTrades=CountTrades(s_dayStart);
     }
   else
     {
      newTrades=CountTrades(s_dayStart);
     }

   ENUM_DTL_STATE newState=CalcState(newTrades,limit,amber);
   bool changed=(newState!=s_state) || (newTrades!=s_tradesToday);

   s_tradesToday=newTrades;
   s_state=newState;
   s_lastRefresh=now;

   return changed;
  }
//+------------------------------------------------------------------+

  • Настройка глобальных параметров

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

//+------------------------------------------------------------------+
//| Store configuration into terminal globals                        |
//+------------------------------------------------------------------+
bool SetParameters(int limit,string startTime,int amber)
  {
   if(!GlobalVariableSet("DTL_LIMIT",limit))
      return false;
   int t=TimeToInt(startTime);
   if(!GlobalVariableSet("DTL_START_TIME",t))
      return false;
   if(!GlobalVariableSet("DTL_AMBER",amber))
      return false;
   s_lastRefresh=0; // force refresh next time
   return true;
  }
//+------------------------------------------------------------------+

B) DailyTradeLimitDashboard.mq5 — Визуальный дашборд

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

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Propagate user inputs into globals
   DTL::SetParameters(InpDailyTradeLimit,InpDayStartTime,InpAmberRemainingTrades);

   //--- Build UI and prime data
   CreatePanel();
   DTL::Refresh();
   prevState=DTL::GetState();

   //--- Start timer for updates
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

  • Функция CreatePanel()

Функция CreatePanel создает компактную, самодостаточную панель на графике. Сначала она удаляет все существующие объекты, имеющие тот же префикс, что и панель, чтобы избежать загромождения, а затем рисует фоновый прямоугольник и четыре метки: TITLE, L1, L2 и L3. Компоновка панели разработана таким образом, чтобы быть понятной и читаемой, а цвета выбраны в соответствии с текущим состоянием торговли.

//+------------------------------------------------------------------+
//| Create the entire dashboard panel                                |
//+------------------------------------------------------------------+
void CreatePanel()
  {
   DeleteByPrefix(PREFIX);

   ObjectCreate(0,PREFIX+"BG",OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_CORNER,InpCorner);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_XDISTANCE,InpX);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_YDISTANCE,InpY);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_XSIZE,InpWidth);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_YSIZE,InpHeight);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_BACK,false);
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_SELECTABLE,false);

   string labels[4]={"TITLE","L1","L2","L3"};
   int y[4]={8,32,52,78};

   for(int i=0; i<4; i++)
     {
      ObjectCreate(0,PREFIX+labels[i],OBJ_LABEL,0,0,0);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_CORNER,InpCorner);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_XDISTANCE,InpX+10);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_YDISTANCE,InpY+y[i]);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_COLOR,clrWhite);
      ObjectSetInteger(0,PREFIX+labels[i],OBJPROP_SELECTABLE,false);
     }

   ObjectSetInteger(0,PREFIX+"TITLE",OBJPROP_FONTSIZE,11);
   ObjectSetString(0,PREFIX+"TITLE",OBJPROP_TEXT,"DAILY TRADE LIMIT");

   ObjectSetInteger(0,PREFIX+"L1",OBJPROP_FONTSIZE,10);
   ObjectSetInteger(0,PREFIX+"L2",OBJPROP_FONTSIZE,10);
   ObjectSetInteger(0,PREFIX+"L3",OBJPROP_FONTSIZE,10);
  }
//+------------------------------------------------------------------+

  • Функция UpdatePanel()

Функция UpdatePanel считывает данные в реальном времени из библиотеки, используя функции TradesToday, GetParamLimit, Remaining и GetState, и соответствующим образом обновляет текстовые строки панели. Она также меняет цвет фона на зеленый для STATE_ALLOWED, оранжевый для STATE_CAUTION и бордовый для STATE_LIMIT, обеспечивая мгновенную визуальную подсказку о текущем состоянии.

//+------------------------------------------------------------------+
//| Refresh the panel's content with the latest state                |
//+------------------------------------------------------------------+
void UpdatePanel()
  {
   int trades=DTL::TradesToday();
   int limit=DTL::GetParamLimit();
   int remaining=DTL::Remaining();
   DTL::ENUM_DTL_STATE state=DTL::GetState();

   ObjectSetString(0,PREFIX+"L1",OBJPROP_TEXT,
                   StringFormat("Trades today: %d / %d",trades,limit));
   ObjectSetString(0,PREFIX+"L2",OBJPROP_TEXT,
                   StringFormat("Remaining today: %d",remaining));
   ObjectSetString(0,PREFIX+"L3",OBJPROP_TEXT,
                   StringFormat("STATUS: %s",DTL::StateToString(state)));

   color bg=clrNONE;
   switch(state)
     {
      case DTL::STATE_ALLOWED:
         bg=clrDarkGreen;
         break;
      case DTL::STATE_CAUTION:
         bg=clrOrange;
         break;
      case DTL::STATE_LIMIT:
         bg=clrMaroon;
         break;
     }
   ObjectSetInteger(0,PREFIX+"BG",OBJPROP_BGCOLOR,bg);
  }
//+------------------------------------------------------------------+

  • Обработка таймера (OnTimer)

Функция OnTimer служит основным циклом периодического обновления дашборда. Она вызывает Refresh для обновления внутреннего состояния, а затем UpdatePanel для обновления отображения. Если состояние изменилось и включены оповещения, она выводит сообщение в терминал, push-уведомление (если включено) и при необходимости воспроизводит звуковой сигнал, чтобы привлечь внимание к изменению.

//+------------------------------------------------------------------+
//| Timer event – updates the panel and triggers alerts              |
//+------------------------------------------------------------------+
void OnTimer()
  {
   bool changed=DTL::Refresh();
   UpdatePanel();

   if(changed && InpShowAlerts)
     {
      string msg;
      switch(DTL::GetState())
        {
         case DTL::STATE_ALLOWED:
            msg="Trading allowed: within daily trade limit.";
            break;
         case DTL::STATE_CAUTION:
            msg="Caution: approaching daily trade limit.";
            break;
         case DTL::STATE_LIMIT:
            msg="Trading limit reached: no trades remaining today.";
            break;
        }
      Alert(msg);
      if(InpUsePushNotifications)
         SendNotification(msg);
      if(InpPlaySound)
         PlaySound(InpSoundFile);
     }
  }
//+------------------------------------------------------------------+

C) DailyTradeLimitEnforcer.mq5 — Советник-принудитель

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

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("DailyTradeLimitEnforcer started. AutoTrading must be enabled.");
   EventSetTimer(1);
   DTL::ForceRefresh(); // initial read
   s_limitJustReached=(DTL::GetState()==DTL::STATE_LIMIT);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   Print("DailyTradeLimitEnforcer stopped.");
  }
//+------------------------------------------------------------------+

  • Процедура таймера (OnTimer)

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

//+------------------------------------------------------------------+
//| Timer – checks for limit state and cancels pending orders        |
//+------------------------------------------------------------------+
void OnTimer()
  {
   DTL::ForceRefresh();
   bool nowLimited=(DTL::GetState()==DTL::STATE_LIMIT);

   //--- If we just entered the limit, cancel all pending orders
   if(nowLimited && !s_limitJustReached)
     {
      Print("Daily limit reached – cancelling all pending orders.");
      CancelAllPendingOrders();
     }

   s_limitJustReached=nowLimited;
  }
//+------------------------------------------------------------------+

  • Реакция на транзакцию (OnTradeTransaction)

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

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

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

//+------------------------------------------------------------------+
//| Trade transaction handler – blocks any disallowed trade          |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   //--- Ensure we have latest state
   DTL::ForceRefresh();

   //--- If still allowed, nothing to do
   if(DTL::IsTradingAllowed())
      return;

   //--- Case: new pending order added
   if(trans.type==TRADE_TRANSACTION_ORDER_ADD)
     {
      ulong orderTicket=trans.order;
      if(orderTicket==0)
         return;

      if(!OrderSelect(orderTicket))
        {
         Print("Failed to select order ",orderTicket);
         return;
        }

      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
         return; // market order – will be handled if filled

      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action   =TRADE_ACTION_REMOVE;
      req.order    =orderTicket;
      req.comment  ="Daily limit reached – order cancelled immediately";

      if(OrderSend(req,res))
         Print("Immediately cancelled pending order ",orderTicket);
      else
         Print("Immediate cancel failed for order ",orderTicket,": ",res.comment);
     }

   //--- Case: new deal added (opening trade)
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ulong dealTicket=trans.deal;
      if(dealTicket==0)
         return;

      if(HistoryDealGetInteger(dealTicket,DEAL_ENTRY)!=DEAL_ENTRY_IN)
         return;

      ulong posTicket=HistoryDealGetInteger(dealTicket,DEAL_POSITION_ID);
      if(posTicket==0)
         return;

      if(!PositionSelectByTicket(posTicket))
        {
         Print("Position ",posTicket," no longer open.");
         return;
        }

      //--- Close newly opened position
      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action    =TRADE_ACTION_DEAL;
      req.position  =posTicket;
      req.symbol    =PositionGetString(POSITION_SYMBOL);
      req.volume    =PositionGetDouble(POSITION_VOLUME);
      req.deviation =5;
      req.comment   ="Daily limit reached – new position closed immediately";

      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
        {
         req.price=SymbolInfoDouble(req.symbol,SYMBOL_BID);
         req.type=ORDER_TYPE_SELL;
        }
      else
        {
         req.price=SymbolInfoDouble(req.symbol,SYMBOL_ASK);
         req.type=ORDER_TYPE_BUY;
        }

      if(OrderSend(req,res))
         Print("Immediately closed new position ",posTicket);
      else
         Print("Immediate close failed for position ",posTicket,": ",res.comment);
     }
  }
//+------------------------------------------------------------------+

  • Вспомогательная функция (CancelAllPendingOrders)

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

//+------------------------------------------------------------------+
//| Cancel all pending orders (market orders are ignored)            |
//+------------------------------------------------------------------+
void CancelAllPendingOrders()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      ulong ticket=OrderGetTicket(i);
      if(ticket==0)
         continue;

      if(!OrderSelect(ticket))
        {
         Print("Failed to select order ",ticket);
         continue;
        }

      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
         continue; // market order – ignore

      MqlTradeRequest req={};
      MqlTradeResult  res={};
      req.action   =TRADE_ACTION_REMOVE;
      req.order    =ticket;
      req.comment  ="Daily limit reached – order cancelled";

      if(OrderSend(req,res))
         Print("Cancelled pending order ",ticket);
      else
         Print("Failed to cancel order ",ticket,": ",res.comment);
     }
  }
//+------------------------------------------------------------------+

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

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

Создание файлов в MetaEditor

Чтобы реализовать систему на своей платформе, выполните следующие шаги для создания трех необходимых файлов в MetaEditor:

Создайте общую библиотеку (.mqh)

  • В MetaEditor, нажмите "Создать" → выберите "Включаемый файл" (.mqh).
  • Назовите его DailyTradeLimit.mqh и сохраните в папке MQL5\Include\.
  • Скопируйте и вставьте весь код общей библиотеки в этот файл и сохраните его. (Включаемые файлы используются другими программами и не требуют отдельной компиляции).

Создайте индикатор дашборда (.mq5)

  • Нажмите "Создать" → выберите "Пользовательский индикатор".
  • Назовите его DailyTradeLimitDashboard.mq5 и сохраните в папке MQL5\Indicators\.
  • В мастере примите настройки по умолчанию (нет необходимости добавлять буферы индикаторов вручную — код уже обрабатывает их).
  • Замените сгенерированный код предоставленным полным кодом дашборда, затем скомпилируйте (Ctrl+F7).

Создайте советник-принудитель (.mq5)

  • Нажмите "Создать" → выберите "Советник".
  • Назовите его DailyTradeLimitEnforcer.mq5 и сохраните в папке MQL5\Experts\.
  • Замените код шаблона полным кодом советника-принудителя, затем скомпилируйте.

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



Тестирование

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

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

Обзор тестовой среды

Система оценивалась в следующих условиях:

Категория Описание
Платформа
MetaTrader 5
Тип счета
Демосчет
Тип исполнения
Ручное размещение сделок
Активные компоненты
1. Общая библиотека (DailyTradeLimit.mqh)

2. Индикатор дашборда (DailyTradeLimitDashboard.mq5)
3. Советник-принудитель (DailyTradeLimitEnforcer.mq5)

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

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

Этап 1 — Начальное состояние и постепенный переход

Рисунок 3: Начальное состояние и постепенный переход

На первой иллюстрации показано начало торговой сессии.

Наблюдения:

  • Сегодня сделок: 0 / 5
  • Оставшиеся сделки: 5
  • Статус: ТОРГОВЛЯ РАЗРЕШЕНА
  • Цвет дашборда: Зеленый

Это подтверждает следующее:

  1. Торговый день инициализирован корректно.
  2. Счетчик сделок начал отсчет с нуля.
  3. Система корректно классифицировала состояние как STATE_ALLOWED.
  4. Глобальные переменные были корректно считаны и синхронизированы.

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

Разрешенное состояние

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

Состояние предупреждения

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

Переход в состояние “Лимит достигнут”

После совершения пятой сделки количество оставшихся сделок упало до нуля, и система перешла в состояние STATE_LIMIT. Дашборд немедленно стал бордового/красного цвета, и статус обновился до ДОСТИГНУТ ТОРГОВЫЙ ЛИМИТ (TRADING LIMIT REACHED), четко сигнализируя о достижении дневной границы. Этот переход подтверждает точный подсчет сделок посредством сканирования истории, корректную привязку времени начала торгового дня, немедленную переклассификацию состояния при превышении лимита и полную синхронизацию между дашбордом и логикой общей библиотеки.

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

Этап 2 — Обеспечение соблюдения лимита после его превышения

На втором графике показан критический этап обеспечения соблюдения лимита.

Рисунок 4: Обеспечение соблюдения лимита после его превышения

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

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

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


Заключение

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

Готовый пакет состоит из трех скоординированных компонентов:

  • DailyTradeLimit.mqh — общая библиотека, которая централизует логику времени торговой сессии, подсчет DEAL_ENTRY_IN и управление состоянием.
  • DailyTradeLimitDashboard.mq5 — уровень конфигурации и мониторинга, обеспечивающий визуализацию состояния в реальном времени и оповещения.
  • DailyTradeLimitEnforcer.mq5 — независимый советник, отвечающий за вмешательство и обеспечение соблюдения лимита.

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

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

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

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

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

Прикрепленные файлы |
От начального до среднего уровня: Навигация по песочнице От начального до среднего уровня: Навигация по песочнице
В данной статье мы рассмотрим два способа наблюдения за содержимым песочницы и даже взаимодействия с ним, используя MetaTrader 5 в качестве базовой платформы. Понимание содержания этой статьи имеет основополагающее значение для понимания того, что будет рассмотрено в последующих статьях.
Теория графов: Применение алгоритма поиска в ширину (BFS) в торговой системе Теория графов: Применение алгоритма поиска в ширину (BFS) в торговой системе
Поиск в ширину (BFS) использует обход по уровням для моделирования структуры рынка как ориентированного графа свингов цен, развивающихся во времени. Анализируя исторические бары или сессии слой за слоем, BFS отдает приоритет недавнему поведению цен, при этом учитывая более глубокую рыночную память.
Моделирование рынка: Position View (VII) Моделирование рынка: Position View (VII)
В данной статье мы начнем вносить некоторые улучшения в индикатор позиции, чтобы иметь возможность взаимодействовать с ним и изменять ценовые линии или закрывать позицию напрямую через взаимодействие с индикатором позиции. Прежде чем мы перейдем к реализации, стоит кое-что уточнить, особенно для тех, кто об этом не знает. Индикатор никак не может использоваться для изменения чего-либо на торговом сервере. Это связано с тем, что в MetaTrader 5 действует система безопасности, которая позволяет воздействовать на ордера и позиции только советникам. Никакое другое приложение, кроме советника, не сможет манипулировать ордерами или позициями.
Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль прогнозирования) Нейросети в трейдинге: Когнитивная инерция в анализе финансовых рынков (модуль прогнозирования)
В статье продолжается адаптация фреймворка CogDriver к анализу финансовых рынков. После построения рыночной сцены и временно согласованной памяти переходим к созданию Forecast Head — модуля прогнозирования. Рассматривается механизм рекуррентного уточнения прогноза, сочетание Cross- и Self-Attention, проблема позиционного кодирования и диагностические признаки, помогающие оценивать устойчивость будущей торговой гипотезы.