Рецепты MQL5 - обработка типичных событий графика

Denis Kirichenko | 26 сентября, 2014

Введение

В своей статье я хотел бы описать возможности и прикладной аспект обработчика OnChartEvent() относительно типичных (стандартных), уже определенных разработчиком MQL5, событий. В статейном материале форума MQL5 и в Code Base уже есть примеры использования этого обработчика.

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


1. Событие ChartEvent

Итак, для начала давайте разберемся, что представляет собой данный вид события.

Согласно документации событие ChartEvent может появиться при работе с графиком, а именно:

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

Разработчик классифицирует событие ChartEvent по видам, которые задаются перечислением ENUM_CHART_EVENT.

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

В части пользовательских событий программист вооружен специальной функцией-генератором EventChartCustom(). Но в данной статье тема пользовательских событий не затрагивается.


2. Обработчик и генератор ChartEvent

Всю работу по обработке ChartEvent берет на себя специальный обработчик — функция OnChartEvent(). И это вполне сообразуется с концепцией языка MQL5, согласно которой, например, событие Trade обрабатывается функцией OnTrade(), событие Init обрабатывается функцией OnInit() и т.д.

Функция OnChartEvent() имеет следующий заголовок:

void OnChartEvent(const int id,         // идентификатор события  
                  const long& lparam,   // параметр события типа long
                  const double& dparam, // параметр события типа double
                  const string& sparam  // параметр события типа string
                  )

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

Так, по значению параметра id можно узнать, что за событие вызвало обработчик. Остальные могут иметь значения "своих" типов: long, double и string. Предполагается, что таким образом можно выудить еще какую-то информацию о событии.

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

Пользовательская часть события ChartEvent, которая отдана на откуп программисту, связана с функцией EventChartCustom(). Именно она может породить это самое событие. Заголовок функции представлен так:

bool  EventChartCustom(long    chart_id,        // идентификатор графика-получателя события
                       ushort  custom_event_id, // идентификатор события
                       long    lparam,          // параметр типа long
                       double  dparam,          // параметр типа double
                       string  sparam           // строковый параметр события
                       )

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

Функции OnChartEvent() и EventChartCustom() — это мощный инструмент, который является ярким образчиком преимуществ событийно-ориентированного программирования.


3. Шаблон обработки стандартных событий

Предлагаю поработать в таком режиме: будут рассматриваться типы событий графика, и для каждого я приведу свой пример. Для каждого события будет написана своя версия советника EventProcessor.mq5, в коде которого будет иметь место обработка события графика. Таких типовых событий в MQL5 имеется 10 штук.

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

Тогда блок, обрабатывающий события графика, примет такой вид:

void OnChartEvent(const int id, 
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   string comment="Последнее событие: ";

//--- выбор события графика
   switch(id)
     {
      //--- 1
      case CHARTEVENT_KEYDOWN:
        {
         comment+="1) нажатие клавиатуры";
         break;
        }
      //--- 2
      case CHARTEVENT_MOUSE_MOVE:
        {
         comment+="2) мыши";
         break;
        }
      //--- 3
      case CHARTEVENT_OBJECT_CREATE:
        {
         comment+="3) создание графического объекта";
         break;
        }
      //--- 4
      case CHARTEVENT_OBJECT_CHANGE:
        {
         comment+="4) изменение свойств объекта через диалог свойств";
         break;
        }
      //--- 5
      case CHARTEVENT_OBJECT_DELETE:
        {
         comment+="5) удаление графического объекта";
         break;
        }
      //--- 6
      case CHARTEVENT_CLICK:
        {
         comment+="6) щелчок мыши на графике";
         break;
        }
      //--- 7
      case CHARTEVENT_OBJECT_CLICK:
        {
         comment+="7) щелчок мыши на графическом объекте";
         break;
        }
      //--- 8
      case CHARTEVENT_OBJECT_DRAG:
        {
         comment+="8) перемещение графического объекта при помощи мыши";
         break;
        }
      //--- 9
      case CHARTEVENT_OBJECT_ENDEDIT:
        {
         comment+="9) окончание редактирования текста";
         break;
        }
      //--- 10
      case CHARTEVENT_CHART_CHANGE:
        {
         comment+="10) изменение графика";
         break;
        }
     }
//---
   Comment(comment);
  }

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

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


4. Примеры обработки стандартных событий


4.1. Событие нажатия клавиатуры

Возьмем первый case-модуль и поработаем с кнопками клавиатуры — научим советник реагировать на сигналы клавиатуры. Пускай он будет открывать позицию на покупку при нажатии на кнопку "стрелка вверх" и на продажу при нажатии на кнопку "стрелка вниз". Тогда код case-модуля может выглядеть так:

//--- 1
      case CHARTEVENT_KEYDOWN:
        {
         //--- стрелка вверх
         if(lparam==38)
            TryToBuy();

         //--- стрелка вниз
         else if(lparam==40)
            TryToSell();

         comment+="1) нажатие клавиатуры";
         //---         
         break;
        }

Код функций TryToBuy() и TryToSell() можно найти в файле советника. Торговые параметры (лот, Stop Loss, Take Profit и т.п.) задаются в виде input-переменных (InpLot, InpStopLoss, InpTakeProfit, etc). Отметил бы еще, что параметр lparam как раз получает код нажатой клавиши.



Назовем обновленную версию советника EventProcessor1.mq5.


4.2. Событие мыши

Этот тип события будет обрабатываться, только если для графика задано свойство CHART_EVENT_MOUSE_MOVE. Для этого в блоке инициализации советника я написал такие строки кода:

//--- mouse move
bool is_mouse=false;
if(InpIsEventMouseMove)
   is_mouse=true;
ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,is_mouse);

Нужно сказать, что если вы работаете с помощью мыши, то событие мыши, что вполне естественно, будет очень часто иметь место. Поэтому стоит сделать возможность отключения обработки этого события. Параметры обработчика lparam и dparam сообщают координаты X и Y соответственно.

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

Для начала нужно определиться с самим отступом. Создадим input-переменную для определения размера отступа нулевого бара от правого края в процентах (InpChartShiftSize).

Рис.1 Окно торговой операции

Рис.1 Окно торговой операции

Затем воспользуемся функциями, которые включают и задают размер отступа ChartShiftSet() и ChartShiftSizeSet(). Потом определим, была ли ранее Х-координата мыши слева от границы или теперь она справа. Если это так, то появится окно с предложением купить\продать (рис.1).

Код, реализующий поставленную задачу, выглядит так:

//--- 2
      case CHARTEVENT_MOUSE_MOVE:
        {
         comment+="2) мыши";
         //--- если обрабатывать событие мыши
         if(InpIsEventMouseMove)
           {
            long static last_mouse_x;

            //--- включить отступ
            if(ChartShiftSet(true))
               //--- задать размер отступа 
               if(ChartShiftSizeSet(InpChartShiftSize))
                 {
                  //--- ширина графика
                  int chart_width=ChartWidthInPixels();

                  //--- определить X-координату границы отступа
                  int chart_shift_x=(int)(chart_width-chart_width*InpChartShiftSize/100.);

                  //--- условие пересечения границы
                  if(lparam>chart_shift_x && last_mouse_x<chart_shift_x)
                    {
                     int res=MessageBox("Да: купить / Нет: продать","Торговая операция",MB_YESNO);
                     //--- если купить
                     if(res==IDYES)
                        TryToBuy();
                     //--- если продать
                     else if(res==IDNO)
                        TryToSell();
                    }

                  //--- запомнить X-координату мыши
                  last_mouse_x=lparam;
                 }
           }

         //---
         break;
        }

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


4.3. Событие создания графического объекта

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

//--- object create
bool is_obj_create=false;
if(InpIsEventObjectCreate)
   is_obj_create=true;
ChartSetInteger(0,CHART_EVENT_OBJECT_CREATE,is_obj_create);

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

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

Вот код для решения поставленной задачи:

//--- 3
      case CHARTEVENT_OBJECT_CREATE:
        {
         comment+="3) создание графического объекта";
         //--- если обрабатывать событие создания графического объекта
         if(InpIsEventObjectCreate)
           {
            //--- "поймать" создание горизонтали
            int all_hor_lines=ObjectsTotal(0,0,OBJ_HLINE);

            //--- если это единственная линия
            if(all_hor_lines==1)
              {
               string hor_line_name1=sparam;

               //--- рассчитать уровни
               int visible_bars_num=ChartVisibleBars();

               //--- массивы high и low
               double highs[],lows[];
               //---
               int copied=CopyHigh(_Symbol,_Period,0,visible_bars_num-1,highs);
               if(copied!=visible_bars_num-1)
                 {
                  Print("Failed to copy highs!");
                  return;
                 }
               copied=CopyLow(_Symbol,_Period,0,visible_bars_num-1,lows);
               if(copied!=visible_bars_num-1)
                 {
                  Print("Failed to copy lows!");
                  return;
                 }
               //--- high и low цены
               double ch_high_pr,ch_low_pr,ch_mid_pr;
               //---
               ch_high_pr=NormalizeDouble(highs[ArrayMaximum(highs)],_Digits);
               ch_low_pr=NormalizeDouble(lows[ArrayMinimum(lows)],_Digits);
               ch_mid_pr=NormalizeDouble((ch_high_pr+ch_low_pr)/2.,_Digits);

               //--- созданную линию разместить на максимуме
               if(ObjectFind(0,hor_line_name1)>-1)
                  if(!ObjectMove(0,hor_line_name1,0,0,ch_high_pr))
                    {
                     Print("Failed to move!");
                     return;
                    }
               //--- создать линию на минимуме
               string hor_line_name2="Hor_line_min";
               //---
               if(!ObjectCreate(0,hor_line_name2,OBJ_HLINE,0,0,ch_low_pr))
                 {
                  Print("Failed to create the 2nd horizontal line!");
                  return;
                 }
               //--- создать линию между high и low 
               string hor_line_name3="Hor_line_mid";
               //---
               if(!ObjectCreate(0,hor_line_name3,OBJ_HLINE,0,0,ch_mid_pr))
                 {
                  Print("Failed to create the 3rd horizontal line!");
                  return;
                 }
              }
           }
         break;
        }

Назовем обновленную версию советника EventProcessor3.mq5.

Рис.2 Результат обработки события создания графического объекта

Рис. 2. Результат обработки события создания графического объекта

После проделанной операции у меня получилась такая картинка (рис. 2). Таким образом, можно научить свой советник реагировать и что-то потом выполнять, когда был создан графический объект.


4.4. Событие изменения свойств графического объекта через диалог свойств

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

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

//--- 4
      case CHARTEVENT_OBJECT_CHANGE:
        {
         comment+="4) изменение свойств объекта через диалог свойств";
         //---
         string curr_obj_name=sparam;
         //--- найти измененный объект
         if(ObjectFind(0,curr_obj_name)>-1)
           {
            //--- получить цвет объекта
            color curr_obj_color=(color)ObjectGetInteger(0,curr_obj_name,OBJPROP_COLOR);
            //--- всего объектов на графике
            int all_other_objects=ObjectsTotal(0);
            //--- найти другие объекты
            for(int obj_idx=0;obj_idx<all_other_objects;obj_idx++)
              {
               string other_obj_name=ObjectName(0,obj_idx);
               if(StringCompare(curr_obj_name,other_obj_name)!=0)
                  if(!ObjectSetInteger(0,other_obj_name,OBJPROP_COLOR,curr_obj_color))
                    {
                     Print("Failed to change the object color!");
                     return;
                    }
              }
            //--- перерисовать график
            ChartRedraw();
           }
         //---
         break;

Пусть есть набор линий на графике (рис.3).

Рис.3 Разноцветные динамические линии

Рис.3. Разноцветные динамические линии

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

Рис.4 Изменение цвета линии

Рис.4. Изменение цвета линии

Новая версия советника получит название EventProcessor4.mq5.


4.5. Событие удаления графического объекта

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

//--- object delete
   bool is_obj_delete=false;
   if(InpIsEventObjectDelete)
      is_obj_delete=true;
   ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,is_obj_delete);

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

Рис.5 Пять вертикалей и другие линии

Рис.5. Пять вертикалей и другие линии

Тогда вручную удалим только одну вертикаль, а четыре остальные исчезнут с помощью советника (рис.6).

Рис.6 Оставшиеся линии

Рис.6. Оставшиеся линии

В журнале "Эксперты" появятся такие записи:

NS      0       10:31:17.937    EventProcessor5 (EURUSD.e,W1)   Вертикальных линий до удаления: 4
MD      0       10:31:17.937    EventProcessor5 (EURUSD.e,W1)   Удалено вертикальных линий с графика: 4
QJ      0       10:31:18.078    EventProcessor5 (EURUSD.e,W1)   Вертикальных линий после удаления: 0

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

Последнюю версию эксперта назовем EventProcessor5.mq5.


4.6. Событие щелчка мыши на графике

Это событие возникнет, если на графике произвести щелчок левой кнопки. Если кликнуть правой — откроется контекстное меню, средней — появится перекрестие. Параметры обработчика lparam и dparam сообщают координаты X и Y соответственно.

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

Код для данного примера:

//--- 6
      case CHARTEVENT_CLICK:
        {
         comment+="6) щелчок мыши на графике";
         //--- счетчик объектов 
         static uint sign_obj_cnt;
         string buy_sign_name="buy_sign_"+IntegerToString(sign_obj_cnt+1);
         //--- координаты 
         int mouse_x=(int)lparam;
         int mouse_y=(int)dparam;
         //--- время и цена
         datetime obj_time;
         double obj_price;
         int sub_window;
         //--- преобразовать координаты X и Y в значения время и цена
         if(ChartXYToTimePrice(0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
           {
            //--- создание объекта
            if(!ObjectCreate(0,buy_sign_name,OBJ_ARROW_BUY,0,obj_time,obj_price))
              {
               Print("Failed to create buy sign!");
               return;
              }
            //--- перерисовать график
            ChartRedraw();
            //--- увеличить счетчик объектов
            sign_obj_cnt++;
           }
         //---
         break;
        }

Текущая версия эксперта получит название EventProcessor6.mq5.


4.7. Событие щелчка мыши на графическом объекте

Данный тип события графика отличается от предыдущего лишь тем, что щелчок мыши происходит на каком-либо графическом объекте. Строковой параметр sparam укажет на имя "кликнутого" объекта. В последнем примере создавались стрелки buy. Давайте теперь сделаем так, что клик на объекте этого типа заменит его на стрелку sell.

Код этого блока обработчика может выглядеть примерно следующим образом:

//--- 7
      case CHARTEVENT_OBJECT_CLICK:
        {
         comment+="7) щелчок мыши на графическом объекте";
         //---
         string sign_name=sparam;

         //--- удалить стрелку buy
         if(ObjectDelete(0,sign_name))
           {
            //--- перерисовать график
            ChartRedraw();
            //---
            static uint sign_obj_cnt;
            string sell_sign_name="sell_sign_"+IntegerToString(sign_obj_cnt+1);

            //--- координаты 
            int mouse_x=(int)lparam;
            int mouse_y=(int)dparam;
            //--- время и цена
            datetime obj_time;
            double obj_price;
            int sub_window;
            //--- преобразовать координаты X и Y в значения время и цена
            if(ChartXYToTimePrice(0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
              {
               //--- создание объекта
               if(!ObjectCreate(0,sell_sign_name,OBJ_ARROW_SELL,0,obj_time,obj_price))
                 {
                  Print("Failed to create sell sign!");
                  return;
                 }
               //--- перерисовать график
               ChartRedraw();
               //--- увеличить счетчик объектов
               sign_obj_cnt++;
              }
           }
         //---
         break;
        }

Для целей примера я оставил case-модуль обработки щелчка мыши на графике нетронутым. При запуске советника я кликнул левой кнопкой мыши 3 раза и получил 3 стрелки на покупку (рис.7). Их местоположение выделил желтым.

Рис.7 Стрелки buy

Рис.7. Стрелки buy

И если теперь кликнуть на каждую стрелку buy, то получим такую картинку (рис.8).

Рис.8 Стрелки buy и sell

Рис.8. Стрелки buy и sell

Появились, как и задумывалось, стрелки sell, но также появились, как не задумывалось, стрелки buy. Я специально привожу список объектов на графике, где выделил желтым имена стрелок buy.

Легко заметить, что советник создал 4-ую, 5-ую и 6-ую buy-стрелки. Почему это произошло? Наверное, потому, что первый щелчок на объекте вызвал два события: первое — сам щелчок на объекте, второе — щелчок на графике. Именно последнее событие и порождает создание buy-стрелки. Тогда возникает необходимость добавить какой-то механизм, который отсечет обработку второго события — щелчка на графике. Мне представляется, что таким механизмом может стать контроль по времени.

Добавим глобальную переменную gLastTime. С ее помощью будем контролировать время создания buy-стрелки. Если обработчик обычного клика вызывается менее чем через 250 мсек после создания sell-стрелки, то будем считать, что нужно его вызов пропускать.

Перед перерисовкой графика нужно добавить строку в блок обработки щелчка по объекту:

//--- запомнить момент создания
gLastTime=GetTickCount();

А в блок обработки щелчка по графику нужно добавить проверку по времени:

uint lastTime=GetTickCount();
if((lastTime-gLastTime)>250)
  {
   //--- здесь обработки щелчка
  }

Создадим снова три стрелки на графике типа buy (рис.9).

Рис.9 Стрелки buy

Рис.9. Стрелки buy

Несмотря на их небольшой размер, постараемся щелкнуть по ним. Стрелки были заменены на тип sell (рис.10).

Рис.10 Стрелки sell

Рис.10. Стрелки sell

По традиции новую версию советника назовем EventProcessor7.mq5.


4.8. Событие перемещения графического объекта при помощи мыши

Событие имеет место тогда, когда происходит перемещение объекта в пределах графика. Обработчик получает в виде строкового параметра sparam имя перемещаемого объекта.

Рассмотрим на примере. Очень часто интрадей-трейдер торгует в пределах какого-то временного диапазона. Его границы обозначим парой вертикалей. Картинка получится примерно такой (рис.11). Период, представляющий интерес, выделен светлым.

Рис.11 Границы временного диапазона

Рис.11. Границы временного диапазона

Диапазон можно изменять вручную. И тогда наш полуавтомат должен будет отреагировать на такое изменение.

Создадим на глобальном уровне переменные, описывающие имена двух вертикалей — gTimeLimit1_name и gTimeLimit2_name. Также нужно создать пару переменных для имен прямоугольников, которые затемняют неторговое время на графике. И придется еще создавать глобальные переменные для точек привязки. Раз у нас два прямоугольника, то точек будет четыре.

Приведу код case-модуля обработчика события CHARTEVENT_OBJECT_DRAG:

//--- 8
      case CHARTEVENT_OBJECT_DRAG:
        {
         comment+="8) перемещение графического объекта при помощи мыши";
         string curr_obj_name=sparam;
         //--- если перемещается одна из вертикалей
         if(!StringCompare(curr_obj_name,gTimeLimit1_name) || 
            !StringCompare(curr_obj_name,gTimeLimit2_name))
           {
            //--- координата времени вертикалей
            datetime time_limit1=0;
            datetime time_limit2=0;
            //--- найти 1-ую вертикаль
            if(ObjectFind(0,gTimeLimit1_name)>-1)
               time_limit1=(datetime)ObjectGetInteger(0,gTimeLimit1_name,OBJPROP_TIME);
            //--- найти 2-ую вертикаль
            if(ObjectFind(0,gTimeLimit2_name)>-1)
               time_limit2=(datetime)ObjectGetInteger(0,gTimeLimit2_name,OBJPROP_TIME);

            //--- если вертикали найдены
            if(time_limit1>0 && time_limit2>0)
               if(time_limit1<time_limit2)
                 {
                  //--- обновить параметры прямоугольников
                  datetime start_time=time_limit1;
                  datetime finish_time=time_limit2;
                  //---
                  if(RefreshRecPoints(start_time,finish_time))
                    {
                     //---
                     if(!ObjectMove(0,gRectLimit1_name,0,gRec1_time1,gRec1_pr1))
                       {
                        Print("Failed to move the 1st point!");
                        return;
                       }
                     if(!ObjectMove(0,gRectLimit1_name,1,gRec1_time2,gRec1_pr2))
                       {
                        Print("Failed to move the 2nd point!");
                        return;
                       }
                     //---
                     if(!ObjectMove(0,gRectLimit2_name,0,gRec2_time1,gRec2_pr1))
                       {
                        Print("Failed to move the 1st point!");
                        return;
                       }
                     if(!ObjectMove(0,gRectLimit2_name,1,gRec2_time2,gRec2_pr2))
                       {
                        Print("Failed to move the 2nd point!");
                        return;
                       }
                    }
                 }
           }
         //---
         break;
        }

Тут есть пользовательская функция RefreshRecPoints(). Она как раз и занимается обновлением значений точек привязки для двух прямоугольников. Как создаются графические объекты, можно узнать из блока инициализации советника. Обновленную версию назову EventProcessor8.mq5.


4.9. Событие окончания редактирования текста в поле ввода

Данный тип события носит узкоспециализированный характер и появляется тогда, когда в поле ввода редактируется текст. Параметр sparam сообщит имя объекта, с которым идет работа.

Давайте создадим такой пример: в поле ввода будем записывать ту торговую операцию, которую собираемся выполнить. Пускай будет всего 2 операции — продажа и покупка по рынку. Если ввести в поле ввода слово "Buy", то советник купит актив, слово "Sell" — продаст. Сделаем так, чтобы не зависеть от регистра, т.е. можно вводить "buy" и "sell". Также текст и граница поля будут окрашены при продаже в красный цвет, при покупке — в синий (рис.12).

Рис.12 Покупка через поле ввода

Рис.12. Покупка через поле ввода

Вот код в case-модуле CHARTEVENT_OBJECT_ENDEDIT:

//--- 9
      case CHARTEVENT_OBJECT_ENDEDIT:
        {
         comment+="9) окончание редактирования текста в поле ввода";
         //---
         string curr_obj_name=sparam;
         //--- если редактируется заданное поле ввода
         if(!StringCompare(curr_obj_name,gEdit_name))
           {
            //--- получить описание объекта
            string obj_text=NULL;
            if(ObjectGetString(0,curr_obj_name,OBJPROP_TEXT,0,obj_text))
              {
               //--- проверить значение
               if(!StringCompare(obj_text,"Buy",false))
                 {
                  if(TryToBuy())
                     //--- установить цвет текста
                     ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrBlue);
                 }
               else if(!StringCompare(obj_text,"Sell",false))
                 {
                  if(TryToSell())
                     //--- установить цвет текста
                     ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrRed);
                 }
               else
                 {
                  //--- установить цвет текста
                  ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrGray);
                 }
               //--- перерисовать график
               ChartRedraw();
              }
           }
         //---
         break;
        }

Версия советника называется EventProcessor9.mq5. В исходном файле также можно посмотреть блок создания самого поля ввода.


4.10. Событие изменения графика

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

Рассмотрим следующий пример: пусть имеется запрет на изменение некоторых настроек графика. Тогда все попытки их изменить будут игнорироваться. На самом деле советник будет просто возвращать предыдущие значения. Зафиксируем следующие параметры графика:

Код для данного case-модуля:

//--- 10
      case CHARTEVENT_CHART_CHANGE:
        {
         //--- текущие высота и ширина графика         
         int curr_ch_height=ChartHeightInPixelsGet();
         int curr_ch_width=ChartWidthInPixels();
         //--- если высота и ширина графика не изменились
         if(gChartHeight==curr_ch_height && gChartWidth==curr_ch_width)
           {
            //--- зафиксировать свойства:
            //--- отображение сетки
            if(!ChartShowGridSet(InpToShowGrid))
              {
               Print("Failed to show grid!");
               return;
              }
            //--- тип отображения графика
            if(!ChartModeSet(InpMode))
              {
               Print("Failed to set mode!");
               return;
              }
            //--- цвет фона
            if(!ChartBackColorSet(InpBackColor))
              {
               Print("Failed to set background сolor!");
               return;
              }
           }
         //--- запомнить размеры окна
         else
           {
            gChartHeight=curr_ch_height;
            gChartWidth=curr_ch_width;
           }
         //---
         comment+="10) изменение графика";
         //---
         break;
        }

Последнюю версию назовем EventProcessor10.mq5.


Заключение

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