English
preview
От простых кнопок закрытия до панели управления рисками на основе правил в MQL5

От простых кнопок закрытия до панели управления рисками на основе правил в MQL5

MetaTrader 5Торговые системы |
34 0
Amanda Vitoria De Paula Pereira
Amanda Vitoria De Paula Pereira

От простых кнопок закрытия до панели управления рисками на основе правил в MQL5

В короткие периоды экстремальной волатильности управление несколькими открытыми позициями в MetaTrader 5 становится серьёзным узким местом в исполнении для любого трейдера, полагающегося исключительно на ручные интерфейсы. В стандартной панели Toolbox для закрытия позиций по очереди необходимо выделять строку, щёлкать по ней правой кнопкой мыши и подтверждать операцию. Каждый щелчок мышью и каждое взаимодействие с окном добавляют задержку, измеряемую секундами, что приводит к измеримому проскальзыванию и дополнительной экспозиции. Например, последовательное закрытие десяти позиций может добавить несколько секунд ненужной рыночной экспозиции и привести к существенному отрицательному P/L при быстрых движениях рынка. Следовательно, требование вполне конкретно: предоставить размещённый на графике элемент управления в один щелчок, который отображает плавающую прибыль/убыток в реальном времени, выполняет массовые и условные закрытия позиций с графика без внешних DLL и полностью работает в среде встроенной Standard Library MQL5 и CTrade.

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



Аргументы в пользу нативных библиотек интерфейса

Создание графических интерфейсов на MQL5 обычно ставит разработчиков перед выбором одного из двух разных путей. Первый способ — использовать класс Canvas для рисования пользовательской графики с пиксельной точностью; он обеспечивает чрезвычайную визуальную гибкость, но требует сотен строк кода даже для обработки простого состояния при наведении курсора мыши или события перетаскивания окна. Второй способ — использовать нативные элементы управления Standard Library, в частности классы диалоговых окон и кнопок.

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


Архитектурная настройка и безопасность исполнения

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

//+------------------------------------------------------------------+
//| Include standard library files and declare execution constraints |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Label.mqh>
#include <Controls\Edit.mqh>
#include <Trade\Trade.mqh>

//--- User Inputs for Filtering
input long   InpMagicNumber       = 0;     // Target Magic Number (0 = All)
input bool   InpCurrentSymbolOnly = true;  // Filter by Current Symbol Only

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

//+------------------------------------------------------------------+
//| Class: CRiskPanel                                                |
//| Purpose: Interactive on-chart risk management dashboard          |
//+------------------------------------------------------------------+
class CRiskPanel : public CAppDialog
  {
private:
   CButton           m_btn_close_all;     // Button to close all filtered positions
   CButton           m_btn_close_winners; // Button to close only profitable positions
   CButton           m_btn_close_losers;  // Button to close only losing positions
   CLabel            m_lbl_drawdown;      // Label to display net floating P/L
   CEdit             m_edit_target;       // Input field for automated target profit
   CLabel            m_lbl_target_info;   // Label for the target input field
   CTrade            m_trade;             // Trade execution object

public:
                     CRiskPanel(void);
                    ~CRiskPanel(void);
   
   //--- Create the panel and its elements
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   bool              CreateButtons(void);
   bool              CreateLabels(void);
   bool              CreateInputFields(void);
   
   //--- Logic and event handlers
   void              UpdatePanel(void);
   double            CalculateRisk(void);
   void              ExecuteBasketClose(int filter_type); 
   void              CheckAutoTarget(double current_profit);
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRiskPanel::CRiskPanel(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CRiskPanel::~CRiskPanel(void)
  {
  }


Построение графической топологии

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

//+------------------------------------------------------------------+
//| Creates interactive buttons on the panel                         |
//+------------------------------------------------------------------+
bool CRiskPanel::CreateButtons(void)
  {
   // Close All Button
   if(!m_btn_close_all.Create(m_chart_id,m_name+"CloseAll",m_subwin,20,40,110,60)) return(false);
   if(!m_btn_close_all.Text("Close All")) return(false);
   if(!Add(m_btn_close_all)) return(false);

   // Close Winners Button
   if(!m_btn_close_winners.Create(m_chart_id,m_name+"CloseWinners",m_subwin,130,40,220,60)) return(false);
   if(!m_btn_close_winners.Text("Close Winners")) return(false);
   if(!Add(m_btn_close_winners)) return(false);
      
   // Close Losers Button
   if(!m_btn_close_losers.Create(m_chart_id,m_name+"CloseLosers",m_subwin,240,40,330,60)) return(false);
   if(!m_btn_close_losers.Text("Close Losers")) return(false);
   if(!Add(m_btn_close_losers)) return(false);

   return(true);
  }
Мы реализуем поле пользовательского ввода с помощью класса CEdit. Именно это превращает утилиту из инструмента для ручного управления в автоматизированный механизм, работающий на основе правил. Трейдер задаёт денежную цель непосредственно в этом поле интерфейса на графике. Затем алгоритм преобразует эту строку в значение с плавающей запятой и непрерывно отслеживает рынок. Когда динамическая эквити пересекает этот числовой порог, система самостоятельно закрывает всю корзину позиций без необходимости ручного вмешательства со стороны трейдера.
//+------------------------------------------------------------------+
//| Creates input fields for automated rules                         |
//+------------------------------------------------------------------+
bool CRiskPanel::CreateInputFields(void)
  {
   // Target Profit Input
   if(!m_edit_target.Create(m_chart_id,m_name+"TargetProfit",m_subwin,130,95,220,115)) return(false);
   if(!m_edit_target.Text("100.00")) return(false);
   if(!Add(m_edit_target)) return(false);
   return(true);
  }


Расширенная фильтрация позиций и изоляция портфеля

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

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

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

//+------------------------------------------------------------------+
//| Calculates absolute net exposure based on strict filters         |
//+------------------------------------------------------------------+
double CRiskPanel::CalculateRisk(void)
  {
   double net_profit = 0.0;
   int total = PositionsTotal();
   
   // Loop backwards to safely evaluate positions
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         // Apply symbol and magic number isolation
         if(InpCurrentSymbolOnly && PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
         if(InpMagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
         
         // Aggregate raw profit and swap
         net_profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
        }
     }
   return(net_profit);
  }


Коды возврата торгового сервера и диагностический аудит

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

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

//+------------------------------------------------------------------+
//| Executes the liquidation sequence and handles server errors      |
//+------------------------------------------------------------------+
void CRiskPanel::ExecuteBasketClose(int filter_type)
  {
   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         // Apply security filters
         if(InpCurrentSymbolOnly && PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
         if(InpMagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
         
         double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         
         // Apply profitability condition (1=Winners, -1=Losers)
         if(filter_type == 1 && profit <= 0) continue;
         if(filter_type == -1 && profit >= 0) continue;
         
         // Execute trade and intercept server rejections
         if(!m_trade.PositionClose(ticket))
           {
            PrintFormat("Close failed for ticket %I64u. Code: %d, Desc: %s", ticket, m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription());
           }
        }
     }
  }


Обход конфликтов маршрутизации событий

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

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

//+------------------------------------------------------------------+
//| Event handler for chart and library events                       |
//+------------------------------------------------------------------+
bool CRiskPanel::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   // Captures both native chart clicks and the library's internal click events
   if(id == CHARTEVENT_OBJECT_CLICK || id == (CHARTEVENT_CUSTOM+ON_CLICK))
     {
      // Route to Close All
      if(sparam == m_btn_close_all.Name() || lparam == m_btn_close_all.Id())
        {
         ExecuteBasketClose(0);
         return(true);
        }
      // Route to Close Winners
      if(sparam == m_btn_close_winners.Name() || lparam == m_btn_close_winners.Id())
        {
         ExecuteBasketClose(1);
         return(true);
        }
      // Route to Close Losers
      if(sparam == m_btn_close_losers.Name() || lparam == m_btn_close_losers.Id())
        {
         ExecuteBasketClose(-1);
         return(true);
        }
     }
   // Call the base class method for default dialog events
   return(CAppDialog::OnEvent(id,lparam,dparam,sparam));
  }


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

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

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

//+------------------------------------------------------------------+
//| Autonomously closes basket if floating reality exceeds target    |
//+------------------------------------------------------------------+
void CRiskPanel::CheckAutoTarget(double current_profit)
  {
   double target = StringToDouble(m_edit_target.Text());
   
   // Trigger closure if target is valid and breached
   if(target > 0 && current_profit >= target)
     {
      PrintFormat("Auto-Target reached! Profit: %.2f >= Target: %.2f", current_profit, target);
      ExecuteBasketClose(0); // 0 = Close all filtered positions
     }
  }

//--- Global Environment
CRiskPanel ExtPanel;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Instantiate the panel object
   if(!ExtPanel.Create(0,"Risk Dashboard PRO",0,50,50,410,210))
      return(INIT_FAILED);
      
   ExtPanel.Run();
   
   // Establish polling frequency
   EventSetTimer(1); 
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Destroy the panel and release resources
   ExtPanel.Destroy(reason);
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   // Route chart interactions to the panel event listener
   ExtPanel.ChartEvent(id,lparam,dparam,sparam);
  }

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   // Trigger the panel update sequence
   ExtPanel.UpdatePanel(); 
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // The panel is primarily event and timer driven.
   // OnTick is formally included to define the program as an Expert Advisor.
  }


Заключение и перспективы дальнейшего совершенствования

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

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

Имя файла
Описание
RiskPanel.mq5
Полный исходный код панели управления рисками на основе правил (версия 2.0)

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

Прикрепленные файлы |
RiskPanel.mq5 (12.31 KB)
Нейросети в трейдинге: Управление риском через распределение результатов (Объект верхнего уровня) Нейросети в трейдинге: Управление риском через распределение результатов (Объект верхнего уровня)
Продолжаем адаптацию AC-SRM для задач финансовых рынков и вводим нейронный слой для риск-чувствительной оценки распределения возможных результатов. CNeuronACSRM упорядочивает прогнозируемые исходы, корректно выделяет нижний хвост распределения и возвращает градиенты в исходную индексацию FQF. В статье разбираются архитектура слоя, прямой и обратный проходы, а также влияние вероятностей на итоговую спектральную оценку риска.
Нейронная сеть на практике: Пример реализации элемента XOR Нейронная сеть на практике: Пример реализации элемента XOR
В этой статье я постараюсь показать вам, уважаемый читатель, что не всё так, как кажется. Часто мы склонны думать, что всё устроено определённым образом, хотя на самом деле в итоге можем поверить в то, что не обязательно соответствует действительности. Нейронные сети, безусловно, — одна из самых интересных тем. И с математической точки зрения, и с точки зрения электроники, и даже программного обеспечения. Вопреки тому, что многие считают или утверждают, нейронные сети — это далеко не проблема и не окончательное решение. Это лишь одно из направлений исследований, в котором нам следует продолжать разбираться и изучать, что происходит за кулисами.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Реализация алгоритма усечённого метода Ньютона с сопряжёнными градиентами в MQL5 Реализация алгоритма усечённого метода Ньютона с сопряжёнными градиентами в MQL5
В статье реализован оптимизатор на основе усечённого метода Ньютона с сопряжёнными градиентами (TNC) с покомпонентными ограничениями на переменные, а также подробно описаны его основные компоненты: масштабирование, проекция на границы, поиск по направлению и произведения матрицы Гессе на вектор с помощью конечных разностей. Она предоставляет обёртку целевой функции, поддерживающую аналитические или численные производные, и проверяет работоспособность решателя на тестовой функции Розенброка. Пример логистической регрессии демонстрирует, как использовать TNC в качестве непосредственной замены LBFGS.