MQL5 Drag-and-Drop: Перетаскивание торговых уровней на графике

MQL5 Drag-and-Drop: Перетаскивание торговых уровней на графике КРЕДИТЫ, ИПОТЕКА и БАНКИ
Подробная инструкция по реализации кастомного OnChartEvent для перетаскивания торговых уровней (Stop Loss, Take Profit) мышью на графике в MQL5. Включает пример кода и разбор параметров.
Суть: Реализация кастомного Drag-and-Drop в MQL5 для торговых уровней требует перехвата событий мыши (CHARTEVENT_MOUSEDOWN, CHARTEVENT_MOUSE_MOVE, CHARTEVENT_MOUSEUP) в функции OnChartEvent. Необходимо идентифицировать объект под курсором, отслеживать его перемещение, конвертируя экранные координаты в ценовые, и обновлять свойства объекта, а затем, при отпускании кнопки мыши, фиксировать новое положение и инициировать модификацию соответствующего торгового ордера или позиции.

Исходный код

В MQL5 нет встроенной функции Drag-and-Drop для произвольных графических объектов, поэтому для реализации интерактивного перетаскивания торговых уровней (например, Stop Loss или Take Profit) необходимо использовать обработчик событий графика OnChartEvent. Этот подход позволяет полностью контролировать логику взаимодействия пользователя с графиком.

Представленный ниже код демонстрирует, как создать две горизонтальные линии на графике и реализовать их перетаскивание мышью. После отпускания кнопки мыши в консоль выводится новая цена объекта, что является отправной точкой для интеграции с торговой логикой, например, для модификации реальных ордеров или позиций. Для более сложных сценариев, например, для работы с виртуальными уровнями, которые не видны брокеру, вы можете ознакомиться с нашей статьей о Виртуальных Stop Loss и Take Profit в MQL4.


//+------------------------------------------------------------------+
//|                                              DragDropLevels.mq5 |
//|                                                  FinFluct.com   |
//|                                                                 |
//+------------------------------------------------------------------+
#property copyright "FinFluct.com"
#property link      "https://finfluct.com"
#property version   "1.00"
#property description "Реализация Drag-and-Drop для торговых уровней в MQL5"
#property indicator_chart_window
#property strict

//--- Глобальные переменные для отслеживания состояния перетаскивания
bool   g_is_dragging = false;         // Флаг, указывающий, что объект перетаскивается
string g_dragged_object_name = "";    // Имя перетаскиваемого объекта
double g_drag_start_price = 0.0;      // Цена объекта в начале перетаскивания
long   g_drag_start_time = 0;         // Время объекта в начале перетаскивания
int    g_drag_start_x = 0;            // X-координата мыши в начале перетаскивания
int    g_drag_start_y = 0;            // Y-координата мыши в начале перетаскивания

//--- Имена объектов для демонстрации
#define SL_LINE_NAME "FinFluct_SL_Line"
#define TP_LINE_NAME "FinFluct_TP_Line"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Создаем тестовые линии Stop Loss и Take Profit
   CreateLevel(SL_LINE_NAME, ChartOpenPrice(0) - 50 * _Point, clrRed);
   CreateLevel(TP_LINE_NAME, ChartOpenPrice(0) + 100 * _Point, clrGreen);
   
   //--- Включаем события графика для обработки мыши
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   
   Print("Drag-and-Drop уровни инициализированы.");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   //--- Удаляем созданные объекты при деинициализации
   ObjectsDeleteAll(0, SL_LINE_NAME);
   ObjectsDeleteAll(0, TP_LINE_NAME);
   
   Print("Drag-and-Drop уровни деинициализированы.");
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   //--- Обработка события нажатия кнопки мыши
   if (id == CHARTEVENT_MOUSEDOWN)
   {
      int x = (int)lparam;
      int y = (int)dparam;
      
      //--- Проверяем, был ли клик по одному из наших объектов
      string clicked_object = GetObjectAtXY(x, y);
      if (clicked_object == SL_LINE_NAME || clicked_object == TP_LINE_NAME)
      {
         g_is_dragging = true;
         g_dragged_object_name = clicked_object;
         
         //--- Сохраняем начальные координаты объекта и мыши
         g_drag_start_price = ObjectGetDouble(0, g_dragged_object_name, OBJPROP_PRICE);
         g_drag_start_time = ObjectGetInteger(0, g_dragged_object_name, OBJPROP_TIME);
         g_drag_start_x = x;
         g_drag_start_y = y;
         
         Print("Начато перетаскивание объекта: ", g_dragged_object_name);
      }
   }
   //--- Обработка события перемещения мыши
   else if (id == CHARTEVENT_MOUSE_MOVE)
   {
      if (g_is_dragging)
      {
         int x = (int)lparam;
         int y = (int)dparam;
         
         datetime new_time;
         double new_price;
         
         //--- Конвертируем экранные координаты в координаты графика
         if (ChartXYToTimePrice(0, x, y, new_time, new_price))
         {
            //--- Обновляем только цену объекта, время остается прежним
            ObjectSetDouble(0, g_dragged_object_name, OBJPROP_PRICE, new_price);
            
            //--- Перерисовываем график для мгновенного обновления
            ChartRedraw();
         }
      }
   }
   //--- Обработка события отпускания кнопки мыши
   else if (id == CHARTEVENT_MOUSEUP)
   {
      if (g_is_dragging)
      {
         g_is_dragging = false;
         
         double final_price = ObjectGetDouble(0, g_dragged_object_name, OBJPROP_PRICE);
         Print("Завершено перетаскивание объекта: ", g_dragged_object_name, 
               ", новая цена: ", DoubleToString(final_price, _Digits));
         
         //--- Здесь должна быть логика для модификации реального ордера/позиции
         //--- Например:
         // if (g_dragged_object_name == SL_LINE_NAME)
         // {
         //    // Модифицировать Stop Loss для открытой позиции
         //    // Trade.PositionModify(...) - требуется класс CTrade
         // }
         // else if (g_dragged_object_name == TP_LINE_NAME)
         // {
         //    // Модифицировать Take Profit для открытой позиции
         //    // Trade.PositionModify(...) - требуется класс CTrade
         // }
         
         g_dragged_object_name = ""; // Сбрасываем имя перетаскиваемого объекта
      }
   }
}

//+------------------------------------------------------------------+
//| Создает горизонтальную линию на графике                          |
//+------------------------------------------------------------------+
void CreateLevel(const string name, const double price, const color clr)
{
   if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
   {
      Print("Ошибка создания объекта ", name, ": ", GetLastError());
      return;
   }
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true); // Важно: объект должен быть выбираемым
   ObjectSetInteger(0, name, OBJPROP_DRAGGABLE, false); // Мы управляем перетаскиванием вручную
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   ObjectSetString(0, name, OBJPROP_TOOLTIP, name + " (перетащите)");
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0); // Поверх других объектов
   
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| Возвращает имя объекта, находящегося в заданных координатах      |
//+------------------------------------------------------------------+
string GetObjectAtXY(const int x, const int y)
{
   string object_name = "";
   int objects_total = ObjectsTotal(0, -1, -1); // Все объекты на текущем графике
   
   for (int i = 0; i < objects_total; i++)
   {
      string name = ObjectName(0, i);
      ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, name, OBJPROP_TYPE);
      
      //--- Проверяем только наши линии
      if (name == SL_LINE_NAME || name == TP_LINE_NAME)
      {
         //--- Получаем координаты объекта
         long obj_time;
         double obj_price;
         ObjectGetTimeByValue(0, name, 0, obj_time); // Для HLINE только одна точка
         ObjectGetDouble(0, name, OBJPROP_PRICE, 0, obj_price);
         
         int obj_x, obj_y;
         if (ChartTimePriceToXY(0, 0, obj_time, obj_price, obj_x, obj_y))
         {
            //--- Проверяем, находится ли курсор достаточно близко к линии
            //--- Добавим небольшой "запас" для удобства клика
            int tolerance = 5; // Пикселей
            if (y >= obj_y - tolerance && y <= obj_y + tolerance)
            {
               return name;
            }
         }
      }
   }
   return "";
}

Разбор параметров

Для понимания работы механизма Drag-and-Drop в MQL5, важно разобраться в ключевых переменных и функциях:

  • g_is_dragging: Глобальный булевый флаг, который устанавливается в true, когда пользователь начинает перетаскивать объект, и в false, когда отпускает кнопку мыши.
  • g_dragged_object_name: Глобальная строковая переменная, хранящая имя графического объекта, который в данный момент перетаскивается.
  • CHARTEVENT_MOUSEDOWN: Идентификатор события, возникающего при нажатии любой кнопки мыши на графике. Используется для определения, был ли клик по нашему объекту, и для инициации процесса перетаскивания.
  • CHARTEVENT_MOUSE_MOVE: Идентификатор события, возникающего при перемещении курсора мыши по графику. Это событие активно используется для непрерывного обновления положения перетаскиваемого объекта.
  • CHARTEVENT_MOUSEUP: Идентификатор события, возникающего при отпускании любой кнопки мыши. Используется для завершения процесса перетаскивания и фиксации нового положения объекта.
  • ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true): Эта функция критически важна. Она включает отслеживание событий перемещения мыши для текущего графика. Без нее событие CHARTEVENT_MOUSE_MOVE не будет генерироваться, и перетаскивание не будет работать.
  • ObjectCreate(): Функция для создания графических объектов на графике. В нашем примере используется для создания горизонтальных линий (OBJ_HLINE).
  • ObjectSetDouble(), ObjectSetInteger(), ObjectGetString(): Функции для установки различных свойств графического объекта, таких как цена, цвет, стиль, возможность выбора (OBJPROP_SELECTABLE) и, что важно, отключение встроенного перетаскивания (OBJPROP_DRAGGABLE = false), так как мы реализуем его вручную.
  • ObjectGetDouble(), ObjectGetInteger(): Функции для получения текущих свойств графического объекта, например, его цены или времени.
  • ChartXYToTimePrice(0, x, y, new_time, new_price): Одна из ключевых функций для Drag-and-Drop. Она конвертирует экранные координаты курсора мыши (в пикселях) в соответствующие координаты времени и цены на графике. Это позволяет точно позиционировать объект по мере перемещения мыши.
  • ChartTimePriceToXY(0, 0, obj_time, obj_price, obj_x, obj_y): Обратная функция, конвертирующая координаты времени/цены объекта в экранные координаты. Используется в GetObjectAtXY для определения, находится ли курсор достаточно близко к линии.
  • ChartRedraw(): Принудительная перерисовка графика. Вызывается после каждого обновления положения объекта во время перетаскивания, чтобы изменения отображались мгновенно.
  • GetObjectAtXY(x, y): Вспомогательная функция, которая перебирает все объекты на графике и определяет, какой из них находится в заданных экранных координатах (с учетом небольшого допуска для удобства клика).

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

Как запустить

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

  1. Откройте MetaEditor: В MetaTrader 5 нажмите F4 или выберите "Сервис" -> "Редактор MetaQuotes Language".

  2. Создайте новый советник: В MetaEditor выберите "Файл" -> "Создать" -> "Советник (шаблон)". Назовите его, например, DragDropLevels.

  3. Вставьте код: Полностью скопируйте предоставленный выше исходный код и вставьте его в файл DragDropLevels.mq5, заменив содержимое по умолчанию.

  4. Скомпилируйте советник: Нажмите кнопку "Компилировать" (или F7). Убедитесь, что в окне "Ошибки" (Errors) нет ошибок.

  5. Прикрепите к графику: Откройте любой график в MetaTrader 5. В окне "Навигатор" (Navigator) найдите ваш советник DragDropLevels в разделе "Советники" (Expert Advisors).

  6. Перетащите советник на график: Перетащите DragDropLevels на график. В появившемся окне настроек советника перейдите на вкладку "Общие" (Common) и убедитесь, что установлен флажок "Разрешить алгоритмическую торговлю" (Allow Algo Trading).

  7. Начните перетаскивание: После прикрепления советника на графике появятся две горизонтальные линии: красная (FinFluct_SL_Line) и зеленая (FinFluct_TP_Line). Нажмите левую кнопку мыши на одной из этих линий и, удерживая ее, перемещайте курсор. Вы увидите, как линия следует за курсором.

  8. Завершите перетаскивание: Отпустите кнопку мыши, чтобы зафиксировать линию в новом положении. В журнале экспертов (вкладка "Эксперты" в окне "Инструменты" MetaTrader 5) вы увидите сообщение о новой цене объекта.

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

Оцените статью
FinFluct