English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Создание активных панелей управления  на MQL5 для торговли

Создание активных панелей управления на MQL5 для торговли

MetaTrader 5Эксперты | 15 апреля 2010, 08:34
10 018 2
Евгений
Евгений

Введение

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

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

Так каков же выход? Выход - в создании собственных элементов управления, ведь в MetaTrader 5 появились такие замечательные компоненты как "Кнопка", "Поле ввода" и "Графическая метка". Этим и займёмся.


2. Функции панели

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

  • Открытие сделки
  • Установка отложенного ордера
  • Модификация сделки/ордера
  • Закрытие сделки
  • Удаление отложенного ордера

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

2.1. Открытие позиции

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

Имя
Тип
Описание
 ActP_buy_button1  Кнопка
 Кнопка совершения сделки на покупку
 ActP_sell_button1
 Кнопка
 Кнопка совершения сделки на продажу
 ActP_DealLines_check1
 Флажок
 Флаг установки/сброса вспомогательных линий
 ActP_Exe_radio1
 Переключатели
 Группа переключателей для выбора типа исполнения сделки
 ActP_SL_edit1
 Поле ввода
 Поле для ввода стопа
 ActP_TP_edit1
 Поле ввода
 Поле для ввода тейка
 ActP_Lots_edit1
 Поле ввода
 Поле для ввода объёма
 ActP_dev_edit1
 Поле ввода
 Поле для ввода допустимого отклонения при открытии
 ActP_mag_edit1
 Поле ввода
 Поле для ввода magic номера
 ActP_comm_edit1  Поле ввода  Поле для ввода комментария

Таблица 1. Список элементов панели "Открытие сделки"

2.2. Установка отложенного ордера

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

Имя
Тип
Описание
 ActP_buy_button2  Кнопка
 Кнопка установки ордера на покупку
 ActP_sell_button2
 Кнопка
 Кнопка установки ордера на продажу
 ActP_DealLines_check2
 Флажок
 Флаг установки/сброса вспомогательных линий
 ActP_lim_check2  Флажок  Флаг установки/сброса ордера stoplimit 
 ActP_Exe_radio2
 Переключатели
 Группа переключателей для выбора типа исполнения ордера
 ActP_exp_radio2  Переключатели  Группа переключателей для выбора типа истечения ордера
 ActP_SL_edit2
 Поле ввода
 Поле для ввода стопа
 ActP_TP_edit2
 Поле ввода
 Поле для ввода тейка
 ActP_Lots_edit2
 Поле ввода
 Поле для ввода объёма
 ActP_limpr_edit2
 Поле ввода  Поле для ввода цены ордера stoplimit
 ActP_mag_edit2
 Поле ввода
 Поле для ввода magic номера
 ActP_comm_edit2  Поле ввода  Поле для ввода комментария
 ActP_exp_edit2  Поле ввода  Поле для ввода времени истечения
 ActP_Pr_edit2  Поле ввода  Поле для ввода цены исполнения ордера 

Таблица 2. Список элементов панели "Установка отложенного ордера"

2.3. Модификация/закрытие сделки

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

Имя
Тип
Описание
 ActP_ord_button5   Выпадающий список   Список для выбора торговой операции 
 ActP_mod_button4  Кнопка
 Кнопка модификации сделки 
 ActP_del_button4
 Кнопка
 Кнопка закрытия сделки 
 ActP_DealLines_check4
 Флажок
 Флаг установки/сброса вспомогательных линий
 ActP_SL_edit4
 Поле ввода
 Поле для ввода стопа
 ActP_TP_edit4
 Поле ввода
 Поле для ввода тейка
 ActP_Lots_edit4
 Поле ввода
 Поле для ввода объёма 
 ActP_dev_edit4
 Поле ввода
 Поле для ввода допустимого отклонения 
 ActP_mag_edit4
 Поле ввода
 Поле для отображения magic номера (только чтение)
 ActP_Pr_edit4  Поле ввода  Поле для отображения цены открытия (только чтение)

Таблица 3. Список элементов панели "Модификация/закрытие сделки"

2.4. Модификация/удаление ордера

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

Имя
Тип
Описание
 ActP_ord_button5  Выпадающий список  Список для выбора ордера
 ActP_mod_button3  Кнопка
 Кнопка модификации ордера 
 ActP_del_button3
 Кнопка
 Кнопка удаления ордера 
 ActP_DealLines_check3
 Флажок
 Флаг установки/сброса вспомогательных линий
 ActP_exp_radio3  Переключатели  Группа переключателей для выбора типа истечения ордера
 ActP_SL_edit3
 Поле ввода
 Поле для ввода стопа
 ActP_TP_edit3
 Поле ввода
 Поле для ввода тейка
 ActP_Lots_edit3
 Поле ввода
 Поле для отображения объёма (только чтение)
 ActP_limpr_edit3
 Поле ввода  Поле для ввода цены ордера stoplimit 
 ActP_mag_edit3
 Поле ввода
 Поле для отображения magic номера (только чтение)
 ActP_comm_edit3  Поле ввода  Поле для ввода комментария
 ActP_exp_edit3  Поле ввода  Поле для ввода времени истечения
 ActP_Pr_edit3  Поле ввода  Поле для ввода цены исполнения ордера 
 ActP_ticket_edit3  Поле ввода  Поле для отображения тикета ордера (только чтение) 

Таблица 4. Список элементов панели "Модификация/удаление ордера"

2.5. Настройки

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

Имя
Тип
Описание
 ActP_col1_button6  Выпадающий список
 Список для выбора цвета кнопок
 ActP_col2_button6
 Выпадающий список
 Список для выбора цвета меток
 ActP_col3_button6
 Выпадающий список
 Список для выбора цвета текста
 ActP_font_edit6
 Поле ввода
 Поле для ввода размера шрифта

Таблица 5. Список элементов панели "Настройки"

Также добавим кнопку для возможности сворачивания панели, если в ней нет нужды. Вы могли заметить наличие такого инструмента, как "вспомогательные линии", что они такое и зачем они нужны? С помощью этих линий мы сможем установить стоп, тейк, цену срабатывания отложенного ордера, цену ордера stoplimit (горизонтальные линии), а также время истечения отложенного ордера (вертикальная линия), просто путём перетаскивания этих линий мышью на нужную цену/время.

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


3. Общий подход к созданию интерфейса

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

А теперь представьте, мы долго, нудно и упорно высчитывали координаты объектов, чтобы он не перекрывали друг друга, были отчётливо видны, а потом появилась необходимость добавить новый объект, и вся наша схема летит в тартарары.

Те, кто знаком со средами быстрой разработки приложений (Delphi, C++ Builder и.т.д.) знают, насколько быстро там можно сверстать самый замысловатый интерфейс.

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

Код скрипта может выглядеть так:

//+------------------------------------------------------------------+
//|                                  Component properties writer.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

input int interfaceID=1; //входной параметр - идентификатор сохраняемого интерфейса
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //откроем файл для записи   
   int handle=FileOpen("Active_Panel_scheme_"+IntegerToString(interfaceID)+".bin", FILE_WRITE|FILE_BIN);
   if(handle!=INVALID_HANDLE)
     {
      //переберём все объекты на экране
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //и запишем их свойства в файл
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //закроем файл
      FileClose(handle);
      Alert("Done!");
     }
  }
//+------------------------------------------------------------------+

Как видите, код предельно прост, перебираются все объекты на графике, и их свойства записываются в бинарный файл. Главное, при считывании файла не забыть последовательность записанных свойств. 

Скрипт готов, займёмся созданием интерфейса.

И первое, с чего начнём, так это с главного меню, организуем его по типу вкладок. Зачем нам нужны вкладки? А затем, что количество объектов большое, и уместить их на одном экране будет проблематично. А раз объекты сгруппированы по назначению (см. таблицы выше), то удобнее всего будет поместить каждую группу на отдельную вкладку.

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

Рис. 1 Вкладки панелей

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

Особое внимание уделим именам объектов, не забываем, что все они должны начинаться с "ActP", также добавим в имя строку "main", это будет обозначать, что объект принадлежит главному меню панели.

Рис. 2 Список объектов (вкладки панелей)

Подобным образом "набросаем" на новые графики и содержимое вкладок. Содержимое каждой вкладки должно быть размещено на отдельном графике!

Вкладка "С рынка":

Рис. 3 Элементы вкладки "С рынка"

Вкладка "отложенный":

Рис. 4 Элементы вкладки "Отложенный"

Вкладка "Настройки":

Рис. 5 Элементы вкладки "Настройки"

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

Рис. 5 Элементы вкладки "Мод/закр"

После создадим подвкладки. Для работы со сделками:

Рис. 6 Подвкладка для работы со сделками

И для работы с ордерами:

Рис. 7 Подвкладка для работы с ордерами

Вот и всё, интерфейс создан.

Применим скрипт на каждый из графиков для сохранения каждой вкладки в отдельный файл. Для этого входной параметр "interfaceID" должен быть разным для каждой вкладки:

  • 0 - Главное меню
  • 1 - С рынка
  • 2 - Отложенный
  • 3 - Кнопка активации списка выбора сделки/ордера
  • 4 - Настройки
  • 6 - Подвкладка для работы со сделками
  • 7 - Подвкладка для работы с ордерами

Вкладка под номером 5 соответствует кнопке главного "Свернуть", т.к. никаких объектов на этой вкладке нет её пропустим.

После всех манипуляций в папке Папка терминала -> MQL5 -> Files появятся следующие файлы:

Рис. 8 Список файлов схем панелей


4. Загрузка элементов интерфейса

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

Создадим его:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window //разместим индикатор в отдельном окне

int OnInit()
  {
//--- indicator buffers mapping
   //установим короткое имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME, "AP");
//---
   return(0);
  }

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Код предельно прост, т.к. основная функция этого индикатора - создание подокна, никаких расчётов он производить не будет. Единственное что сделаем - это установим некоторое "короткое" имя индикатора, по которому сможем найти его подокно. Скомпилируем и набросим на индикатор график, появится его окно.

Теперь займёмся экспертом-панелью. Создадим нового эксперта.

В функции OnInit() разместим такие операторы:

double Bid,Ask;         //переменные для текущих цен
datetime time_current;  //время последнего тика
int wnd=-1;             //индекс окна с индикатором
bool last_loaded=false; //флаг указывающий, первая инициализация, или нет
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   //запустим таймер с периодичностью в 1 секунду
   EventSetTimer(1); 
   //получим последние цены
   get_prices();
   //определим окно с индикатором
   wnd=ChartWindowFind(0,"AP");
   //если первая инициализация - создадим интерфейс
   if(!last_loaded) create_interface();
//---
   return(0);
  }

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

Функция  create_interface() имеет вид:

//+------------------------------------------------------------------+
//| Функция создания интерфейса                                      |
//+------------------------------------------------------------------+
void create_interface()
  {
   //если выбран сброс настроек
   if(Reset_Expert_Settings)
     {
     //сбрасываем
      GlobalVariableDel("ActP_buttons_color");
      GlobalVariableDel("ActP_label_color");
      GlobalVariableDel("ActP_text_color");
      GlobalVariableDel("ActP_font_size");
     }

   //создадим интерфейс главного меню
   ApplyScheme(0);
   //создадим интерфейс вкладки "С рынка"
   ApplyScheme(1);
   //установим объекты как невыделяемые
   Objects_Selectable("ActP",false);
   //перерисуем график
   ChartRedraw();
  }

Первым делом проверим входной параметр "Сбросить_настройки", и если он установлен, удалим глобальные переменные отвечающие за настройки. Как это действие влияет на панель будет описано ниже. Далее функция ApplyScheme() создаст интерфейс из файла.

//+------------------------------------------------------------------+
//| Функция загрузки интерфейса                                      |
//| ID - идентификатор сохранённого интерфейса                       |
//+------------------------------------------------------------------+
bool ApplyScheme(int ID)
  {
   string fname="Active_Panel_scheme_custom_"+IntegerToString(ID)+".bin";
   //если не существует сохранённая схема загрузим стандартную
   if(!FileIsExist(fname)) fname="Active_Panel_scheme_"+IntegerToString(ID)+".bin";
   //откроем файл для чтения
   int handle=FileOpen(fname,FILE_READ|FILE_BIN);
   //файл открыт
   if(handle!=INVALID_HANDLE)
     {
      //считаем всё
      while(!FileIsEnding(handle))
        {
         string obj_name=FileReadString(handle,100);
         int _wnd=wnd;
         //вспомогательные линии забросим в главное окно
         if(StringFind(obj_name,"line")>=0) _wnd=0;
         ENUM_OBJECT obj_type=FileReadInteger(handle);
         //создаём объект
         ObjectCreate(0, obj_name, obj_type, _wnd, 0, 0);   
         //и применяем к нему свойства                                                   
         ObjectSetInteger(0,obj_name,OBJPROP_XDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_XSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_COLOR,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STYLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BACK,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_READONLY,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STATE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle));

         ObjectSetString(0,obj_name,OBJPROP_TEXT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_FONT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,0,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,1,FileReadString(handle,100));

         ObjectSetDouble(0,obj_name,OBJPROP_PRICE,FileReadDouble(handle));

         //установим цвета с объектов
         if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON)
            ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color"));
         if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL)
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color"));
         if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON))
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color"));
         if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL))
            ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size"));
         //установим глобальную переменную с размером шрифта
         if(obj_name=="ActP_font_edit6" && GlobalVariableCheck("ActP_font_size"))
            ObjectSetString(0,obj_name,OBJPROP_TEXT,IntegerToString(GlobalVariableGet("ActP_font_size")));
        }
      //закроем файл
      FileClose(handle);
      return(true);
     }
   return(false);
  }

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

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

//+------------------------------------------------------------------+
//| Функция установки объектов как невыделяемых                      |
//+------------------------------------------------------------------+
void  Objects_Selectable(string IDstr,bool flag)
  {
   //переберём все объекты
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //если объект принадлежит панели
      if(StringFind(n,IDstr)>=0)
        {
         //линии не трогаем
         if(!flag)
            if(StringFind(n,"line")>-1) continue; 
         //установим невыделяемыми все, кроме линий
         ObjectSetInteger(0,n,OBJPROP_SELECTABLE,flag); 
        }
     }
  }

Теперь займёмся функцией OnTick(). Она будет нам служить только лишь для получения последних цен на рынке.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //получим последние цены
   get_prices();
  }

Функция get_prices() имеет вид:

//+------------------------------------------------------------------+
//| Функция получения информации о тике                              |
//+------------------------------------------------------------------+
void get_prices()
  {
   MqlTick tick;
   //если тик был
   if(SymbolInfoTick(Symbol(),tick))
     {
      //получим информацию
      Bid=tick.bid;
      Ask=tick.ask;
      time_current=tick.time;
     }
  }

И не забудем про OnDeinit():

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   //если причина деинициализации - не смена таймфрейма или символа
   if(reason!=REASON_CHARTCHANGE)
     {
      //сбросим флаг инициализации
      last_loaded=false;  
      //удалим все объекты панели
      ObjectsDeleteAll_my("ActP");
      //удалим файлы с сохранённым состоянием вкладок
      FileDelete("Active_Panel_scheme_custom_1.bin");
      FileDelete("Active_Panel_scheme_custom_2.bin");
      FileDelete("Active_Panel_scheme_custom_3.bin");
      FileDelete("Active_Panel_scheme_custom_4.bin");
      FileDelete("Active_Panel_scheme_custom_5.bin");
     }
   //иначе установим флаг
   else last_loaded=true;
   //остановим таймер
   EventKillTimer();
  }

Сперва проверим причину деинициализации, если это смена таймфрейма и/или символа не будем удалять элементы панели. В иных случаях удалим всё с помощью функции ObjectsDeleteAll_my(). 

//+------------------------------------------------------------------+
//| Функция удаления всех объектов панели                            |
//| IDstr - строка-идентификатор                                     |
//+------------------------------------------------------------------+
void  ObjectsDeleteAll_my(string IDstr)
  {
   //переберём все объекты
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //если в имени есть идентификатор - удалим объект
      if(StringFind(n,IDstr)>=0) ObjectDelete(0,n);
     }
  }

Скомпилировав и запустив советника, получим такой результат:

Рис. 9 Пример работы советника

Но вот только толку от этой красоты пока мало, поэтому попытаемся заставить объекты реагировать на наши манипуляции с ними.


5. Обработка событий

Интерфейс создан, теперь нужно заставить его работать. Все наши действия с объектами генерируют определённые события. Функция OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam ) является обработчиком группы событий ChartEvent. Из всех событий нас интересуют следующие:

  • CHARTEVENT_CLICK - клик по графику
  • CHARTEVENT_OBJECT_ENDEDIT - окончание редактирования поля ввода
  • CHARTEVENT_OBJECT_CLICK - клик по графическому объекту

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

Итак, первое событие, с которого начнём - клик по кнопке главного меню.

5.1. Обработка событий главного меню

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

//+------------------------------------------------------------------+
//| Обработчики событий                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке главного меню
      if(sparam=="ActP_main_1") {Main_controls_click(1); ChartRedraw(); return;}
       //здесь выполним соответствующие операторы
      if(sparam=="ActP_main_2") {Main_controls_click(2); ChartRedraw(); return;}
      if(sparam=="ActP_main_3") {Main_controls_click(3); ChartRedraw(); return;}
      if(sparam=="ActP_main_4") {Main_controls_click(4); ChartRedraw(); return;}
      if(sparam=="ActP_main_5") {Main_controls_click(5); ChartRedraw(); return;}
   ...   
   }
...
}

Если произошёл клик по кнопке меню, то мы выполним функцию Main_controls_click(), перерисуем график с помощью ChartRedraw() и завершим выполнение функции. Завершать выполнение можно потому, что в один момент времени мы можем кликнуть только по одному объекту, и дальнейшее выполнение приведёт к пустой трате процессорного времени.

//+------------------------------------------------------------------+
//| Обработчик вкладки                                               |
//| ID - номер кликнутой вкладки                                     |
//+------------------------------------------------------------------+
void Main_controls_click(int ID)
  {
   int loaded=ID;
   //переберём все вкладки
   for(int i=1;i<6;i++)
     {
      //для всех, кроме выбранной установим неактивное состояние
      if(i!=ID) 
        {
         //также запомним последнюю активную вкладку
         if(ObjectGetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE)==1) loaded=i;
         ObjectSetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE,0);
        }
     }
//if(loaded==ID) return;
   //для выбранной установим активное состояние
   ObjectSetInteger(0,"ActP_main_"+IntegerToString(ID),OBJPROP_STATE,1);
   //удалим все выпадающие списки
   DeleteLists("ActP_orders_list_"); 
   DeleteLists("ActP_color_list_");
   //и установим кнопки активации списков в отжатое состояние
   ObjectSetInteger(0,"ActP_ord_button5",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col1_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col2_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col3_button6",OBJPROP_STATE,0);
   //сохраним состояние последней активной
   SaveScheme(loaded);
   //удалим старую вкладку
   DeleteScheme("ActP");
   //и загрузим новую
   ApplyScheme(ID);
   //установим все объекты в невыделяемое состояние
   Objects_Selectable("ActP",false);
  }

С функциями Objects_Selectable() и ApplyScheme() мы ознакомились выше, а DeleteLists() рассмотрим позже.

Функция SaveScheme() сохраняет интерфейс в файл, чтобы при новой его загрузке объекты сохраняли все свои свойства:

//+------------------------------------------------------------------+
//| Функция сохранения интерфейса                                    |
//+------------------------------------------------------------------+
void SaveScheme(int interfaceID)
  {
   //откроем файл для записи
   int handle=FileOpen("Active_Panel_scheme_custom_"+IntegerToString(interfaceID)+".bin",FILE_WRITE|FILE_BIN);
   //если файл открыт
   if(handle!=INVALID_HANDLE)
     {
      //переберём все объекты на экране
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //если объект принадлежит панели
         if(StringFind(name,"ActP")<0) continue;
         //и это не вкладка
         if(StringFind(name,"main")>=0) continue; 
         //запишем его свойства в файл
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //закроем файл  
      FileClose(handle);
     }
  }

Функция  DeleteScheme() удаляет объекты вкладки.

//+------------------------------------------------------------------+
//| Функция удаления всех объектов панели, кроме вкладки             |
//+------------------------------------------------------------------+
void  DeleteScheme(string IDstr)
  {
   //переберём все объекты
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //удалим всё, кроме вкладки
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n); 
     }
  }

Таким образом, выполнив функцию Main_controls_click(), мы удалим старую вкладку, предварительно сохранив её, и загрузим новую.

Скомпилировав советника, посмотрим результаты.

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

Рис. 10 Элементы вкладки "Отложенный"


Рис. 11 Элементы вкладки "Мод/закр"

Рис. 12 Элементы вкладки "Настройки"

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

5.2. Обработка событий компонента "Флажок"

Установка вспомогательных линий и ордера stoplimit осуществляется с помощью компонента "флажок", но такого нет в списке графических объектов MetaTrader 5, так создадим его. Есть объект "графическая метка", фактически это изображение, которое имеет состояния "включено" и "выключено". Состояние меняется кликом по объекту, на каждое состояние можно назначить отдельное изображение. Подберём картинки для каждого из состояний:

  •  Включено    
  •  Выключено  

Установим в свойствах объекта эти картинки:

Рис. 13. Установка свойства элемента "Флажок"

Рис. 13. Установка свойства элемента "Флажок"

Напомню, чтобы картинки были доступны в списке, они должны находится в папке "Папка терминала->MQL5->Images" и иметь расширение ".bmp".

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

       //клик по флажку установки вспомогательных линий при открытии сделки
      if(sparam=="ActP_DealLines_check1")
      {
          //проверим состояние флага
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //если флаг установлен
         if(selected)
         {
             //извлечём значения стопа и тейка из полей ввода
            string SL_txt=ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT);
            string TP_txt=ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT);
            double val_SL, val_TP;
            
              //если в поле стопа не пусто
              //запомним значение
            if(SL_txt!="")
               val_SL=StringToDouble(SL_txt);

              //если пусто
            else
            {
                 //возьмём макс. и мин. цену на графике
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
                 //установим стоп на уровне трети экрана 
               val_SL=pr_min+(pr_max-pr_min)*0.33;
            }   
            
              //аналогично обработаем тейк
            if(TP_txt!="")
               val_TP=StringToDouble(TP_txt);
            else
            {
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
               val_TP=pr_max-(pr_max-pr_min)*0.33;
            }      
            //переместим линии на новые позиции
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, val_SL);  
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, val_TP);  
         }
          //если флаг сброшен
         else
         {
             //уберём линии
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, 0);
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, 0);
         }
          //перерсуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }

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

Флажок установки ордера stoplimit на вкладке "Отложенный" имеет следующий обработчик:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...

   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //клик по флажку установки ордера stoplimit
      if(sparam=="ActP_limit_check2")
      {
          //проверим состояние флага
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)//если флаг установлен
         {
             //установим новый цвет поля ввода цены ордера
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, White);
             //также сделаем поле доступным для редактировния     
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, false);
             //установим в поле значение текущей цены
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, DoubleToString(Bid, _Digits));
             //если разрешены вспомогательные линии
             //переместим их
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, Bid);
         }  
          //если флаг сброшен
         else
         {
              //сделаем поле недоступным для редактирования
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, LavenderBlush);
              //установим цвет
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, true);
              //и "пустой" текст
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, "");
              //если разрешены вспомогательные линии
              //переместим их в нулевую точку с глаз долой
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, 0);
         }
      }       
   ...   
   }
...
}

С флажками покончено. Рассмотрим следующий объект собственного производства - "группа переключателей".

5.3. Обработка событий компонента "Группа переключателей"

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

  • Включено   
  • Выключено 

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

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по переключателю №1 типа исполнения ордера
      if(sparam=="ActP_Exe1_radio2")
      {
          //проверим состояние переключателя
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //установим переключатель в нужное положение
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
          //если установлен в положение 1 из 3-х
         if(selected)
         {
              //сборсим остальные переключатели
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
              //перерисуем график
            ChartRedraw();
              //закончим выполнение функции
            return;
         }
          //перерисуем график
         ChartRedraw();
          //закончим выполнение функции
         return;
      }
 
       //аналогично для переключателя №2
      if(sparam=="ActP_Exe2_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }   

       //аналогично для переключателя №3
      if(sparam=="ActP_Exe3_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

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

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту   
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по 3-му переключателю типа времени истечения ордера
      if(sparam=="ActP_exp3_radio2")
      {
          //проверим состояние
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
          //если активно
         if(selected)
         {
              //сбросим остальные переключатели
            ObjectSetInteger(0, "ActP_exp1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE, false);
              //установим новую дату в поле ввода
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, White);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, false);
            ObjectSetString(0, "ActP_exp_edit2", OBJPROP_TEXT, TimeToString(time_current));
              //если разрешены вспомогательные линии
              //установим новое значение линии времени
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, time_current);
            ChartRedraw();
            return;
         }

          //если не активно
         else
         {
             //сделаем поле ввода недоступным для редактирования
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, LavenderBlush);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, true);
             //уберём вспомогательную линию
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, 0);
         }      
         ChartRedraw();
         return;
   ...   
   }
...
}

С переключателями покончено.

5.4. Создание и обработка событий выпадающих списков

Из выпадающего списка будем производить выбор ордера/сделки для модификации/закрытия/удаления и выбор цветов панели. Начнём со списка сделок/ордеров.

Первое, что нас встречает на вкладке "Мод/закр" - это кнопка с надписью "Выберите ордер -->", это и будет нашей кнопкой активации списка. При клике на ней этот самый список должен из неё выпадать, после чего мы должны произвести выбор и список опять отправится в небытие.  Посмотрим на обработчик события CHARTEVENT_OBJECT_CLICK этой кнопки:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...

   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке активации выпадающего списка выбора ордера
      if(sparam=="ActP_ord_button5")
      {
          //проверим состояние
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);//проверим состояние
          //список активирован
         if(selected)//список активирован
         {
             //удалим интерфейс
            DeleteScheme("ActP", true);
             //массивы для хранения информации об ордерах
            string info[100];
             //массив для тикетов
            int tickets[100];
             //инициализируем его
            ArrayInitialize(tickets, -1);
             //получим информацию
            get_ord_info(info, tickets);
             //и создадим список
            create_list(info, tickets);
         }
          //список деактивирован
         else
         {
             //тогда удалим его
            DeleteLists("ActP_orders_list_");
         }
          //перерисуем график
         ChartRedraw();
          //завершим выполнение функции
         return;
      }      
   ...   
   }
...
}

Наша первоочередная задача - определить, есть ли сделки/ордера в рынке, и если есть, нужно извлечь из них информацию для отображения в списке. Этим занимается функция get_ord_info():

//+------------------------------------------------------------------+
//| Функция получения информации об ордерах                          |
//+------------------------------------------------------------------+
void get_ord_info(string &info[],int &tickets[])
  {
   //инициализируем счётчик
   int cnt=0;
   string inf;
   //если есть открытая позиция
   if(PositionSelect(Symbol()))
     {
      //соберём информацию о ней в строку
      double vol=PositionGetDouble(POSITION_VOLUME);
      int typ=PositionGetInteger(POSITION_TYPE);
      if(typ==POSITION_TYPE_BUY) inf+="BUY ";
      if(typ==POSITION_TYPE_SELL) inf+="SELL ";
      inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
      inf+=" at "+DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), Digits());
      //запишем результаты
      info[cnt]=inf;
      tickets[cnt]=0;
      //инкркментируем счётчик
      cnt++;
     }

   //переберём все ордера
   for(int i=0;i<OrdersTotal();i++)
     {
      //получим тикет
      int ticket=OrderGetTicket(i);
      //если символ ордера равен символу графика 
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         //соберём информацию об ордере в строку
         inf="#"+IntegerToString(ticket)+" ";
         int typ=OrderGetInteger(ORDER_TYPE);
         double vol=OrderGetDouble(ORDER_VOLUME_CURRENT);
         if(typ==ORDER_TYPE_BUY_LIMIT) inf+="BUY LIMIT ";
         if(typ==ORDER_TYPE_SELL_LIMIT) inf+="SELL LIMIT ";
         if(typ==ORDER_TYPE_BUY_STOP) inf+="BUY STOP ";
         if(typ==ORDER_TYPE_SELL_STOP) inf+="SELL STOP ";
         if(typ==ORDER_TYPE_BUY_STOP_LIMIT) inf+="BUY STOP LIMIT ";
         if(typ==ORDER_TYPE_SELL_STOP_LIMIT) inf+="SELL STOP LIMIT ";
         inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
         inf+=" at "+DoubleToString(OrderGetDouble(ORDER_PRICE_OPEN), Digits());
         //запишем результаты
         info[cnt]=inf;
         tickets[cnt]=ticket;
         //инкркментируем счётчик
         cnt++;
        }
     }
  }

Она запишет в массивы информацию и тикеты ордеров и сделки.

Далее функция create_list() на основании этой информации создаст список:

//+------------------------------------------------------------------+
//| Функция создания списка позиций                                  |
//| info - массив с информацией о позициях                           |
//| tickets - массив с тикетами                                      |
//+------------------------------------------------------------------+
void create_list(string &info[],int &tickets[])
  {
   //получим координаты кнопки активации списка
   int x=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YDISTANCE)+ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YSIZE);
   //и цвета
   color col=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_COLOR);
   color bgcol=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_BGCOLOR);
   //и ширину окна
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   int y_cnt=0;
   //переберём массивы
   for(int i=0;i<100;i++)
     {
      //если достигнут конец - завершим цикл
      if(tickets[i]==-1) break;
      //рассчитаем координаты элемента списка
      int y_pos=y+y_cnt*20;
      //если достигнуты пределы окна, начнём новый столбец
      if(y_pos+20>wnd_height) {x+=300; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      string name="ActP_orders_list_"+IntegerToString(i)+" $"+IntegerToString(tickets[i]);//создадим элемент
      create_button(name,info[i],x,y_pos,300,20);
      //и установим его свойства
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

Ну и наконец, функция DeleteLists() удаляет элементы списка:

//+------------------------------------------------------------------+
//| Функция удаления списков                                         |
//+------------------------------------------------------------------+
void  DeleteLists(string IDstr)
  {
   //переберём все объекты
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //удалим списки
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n);
     }
  }

Итак, теперь при клике на кнопке активации список создаётся. Заставим его работать, ведь при клике на каком-либо элементе списка должны выполняться некоторые действия. А именно: загрузка интерфейса для работы с ордером, и его заполнение информацией об ордере/сделке.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик не по элементу списка выбора ордера
      if(StringFind(sparam, "ActP_orders_list_")<0)
      {
          //удалим его 
         DeleteLists("ActP_orders_list_");
          //установим кнопку активации в положение "отжата"
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //перерисуем график
         ChartRedraw();
      }     
       //клик по элементу списка выбора ордера
      else
      {
          //установим новое имя для кнопки активации
         ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, ObjectGetString(0, sparam, OBJPROP_TEXT));
          //установим кнопку активации в положение "отжата"
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //извлечём тикет из описания элемента списка
         int ticket=StringToInteger(StringSubstr(sparam, StringFind(sparam, "$")+1));
          //загрузим интерфейс
         SetScheme(ticket);
          //и удалим список
         DeleteLists("ActP_orders_list_");
          //перерисуем график
         ChartRedraw();
      }
   ...   
   }
...
}

Здесь пришлось пойти на хитрость, т.к. мы не знаем заранее размера списка и имён его объектов, то придётся выжимать из него информацию. путём разбора имени элемента списка. Функция SetScheme() установит нужный интерфейс - либо для работы со сделкой, либо для работы с отложенным ордером:

//+------------------------------------------------------------------+
//| Функция установки интерфейса в зависимости от типа позиции       |
//| (сделки или отложенный ордер)                                    |
//| t - тикет                                                        |
//+------------------------------------------------------------------+
void SetScheme(int t)
  {
   //если сделка
   if(t==0)
     {
      //проверим её наличие
      if(PositionSelect(Symbol()))
        {
         //удалим старый интерфейс
         DeleteScheme("ActP",true);
         //и установим новый
         ApplyScheme(6);
         //установим значения объектов
         SetPositionParams();
         //сделаем объекты недоступными для выделения
         Objects_Selectable("ActP",false);
        }
     }
   //если ордер  
   if(t>0)
     {
      //проверим его наличие
      if(OrderSelect(t))
        {
         //удалим старый интерфейс
         DeleteScheme("ActP",true);
         //и установим новый
         ApplyScheme(7);
         //установим значения объектов
         SetOrderParams(t);
         //сделаем объекты недоступными для выделения
         Objects_Selectable("ActP",false);
        }
     }
  }

Функции SetPositionParams() и SetOrderParams() выполняют установку нужных свойств загруженного интерфейса:

//+------------------------------------------------------------------+
//| Функция установки значений объектов при модификации сделки       |
//+------------------------------------------------------------------+
void SetPositionParams()
  {
   //если сделка существует
   if(PositionSelect(Symbol()))
     {
      //извлечём её параметры
      double pr=PositionGetDouble(POSITION_PRICE_OPEN);
      double lots=PositionGetDouble(POSITION_VOLUME);
      double sl=PositionGetDouble(POSITION_SL);
      double tp=PositionGetDouble(POSITION_TP);
      double mag=PositionGetInteger(POSITION_MAGIC);
      //и установим новые значения в объекты
      ObjectSetString(0,"ActP_Pr_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit4",OBJPROP_TEXT,IntegerToString(mag));
      //перерисуем график
      ChartRedraw();
     }
   //если сделки нет, выдадим сообщение  
   else MessageBox("Нет открытых сделок по инструменту "+Symbol());
  }
//+------------------------------------------------------------------+
//| Функция установки значений объектов при модификации ордера       |
//| ticket - тикет ордера                                            |
//+------------------------------------------------------------------+
void SetOrderParams(int ticket)
  {
   //если ордер существует
   if(OrderSelect(ticket) && OrderGetString(ORDER_SYMBOL)==Symbol())
     {
      //извлечём его параметры
      double pr=OrderGetDouble(ORDER_PRICE_OPEN);
      double lots=OrderGetDouble(ORDER_VOLUME_CURRENT);
      double sl=OrderGetDouble(ORDER_SL);
      double tp=OrderGetDouble(ORDER_TP);
      double mag=OrderGetInteger(ORDER_MAGIC);
      double lim=OrderGetDouble(ORDER_PRICE_STOPLIMIT);
      datetime expir=OrderGetInteger(ORDER_TIME_EXPIRATION);
      ENUM_ORDER_TYPE type=OrderGetInteger(ORDER_TYPE);
      ENUM_ORDER_TYPE_TIME expir_type=OrderGetInteger(ORDER_TYPE_TIME);
      
      //если ордер stoplimit подкорректируем интерфейс
      if(type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT)
        {
         //установим в поле для ввода цены ордера новую цену
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,DoubleToString(lim,_Digits));
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,White);
         //установим поле для ввода цены ордера доступным для чтения
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,false);
        }
      //если ордер не stoplimit подкорректируем интерфейс      
      else
        {
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,"");
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,LavenderBlush);
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,true);
        }

      //проверим тип истечения ордера   
      //и установим переключатели в нужное положение
      switch(expir_type)
        {
         case ORDER_TIME_GTC:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_DAY:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_SPECIFIED:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,1);
            //здесь дополнительно установим новое значение в поле ввода
            ObjectSetString(0,"ActP_exp_edit3",OBJPROP_TEXT,TimeToString(expir));
            break;
           }
        }
      //установим новые значения в объекты
      ObjectSetString(0,"ActP_Pr_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      ObjectSetString(0,"ActP_ticket_edit3",OBJPROP_TEXT,IntegerToString(ticket));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit3",OBJPROP_TEXT,IntegerToString(mag));
      ChartRedraw();
     }
   //если ордера нет, выдадим сообщение  
   else MessageBox("Нет ордера с тикетом "+IntegerToString(ticket)+" по инструменту "+Symbol());
  }

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

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графику
   if(id==CHARTEVENT_CLICK)
   {
       //удалим все выпадающие списки
      DeleteLists("ActP_orders_list_");
      DeleteLists("ActP_color_list_"); 
       //и установим кнопки активации списков в отжатое состояние
      ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
      ChartRedraw(); 
      return;
   }
...
}

В итоге имеем симпатичный выпадающий список:

Рис. 14 Пример выпадающего списка панели "Мод/закр"

Осталось создать список выбора цвета на вкладке "Настройки". 

Рассмотрим обработчики кнопок активации:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту   
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке активации выпадающего списка выбора цвета кнопок
      if(sparam=="ActP_col1_button6")
      {
          //проверим состояние
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //список активирован
         if(selected)//список активирован
         {
             //создадим список
            create_color_list(100, "ActP_col1_button6", 1);
             //установим положение остальных кнопок в положение "отжаты"
            ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
            ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
             //удалим другие списки
            DeleteLists("ActP_color_list_2");
            DeleteLists("ActP_color_list_3");                 
         }
          //список деактивирован
         else
         {
             //удалим его
            DeleteLists("ActP_color_list_");
         }
          //перерисуем график
         ChartRedraw();
          //завершим выполнение функции
         return;
      }
   ...   
   }
...
}

Здесь действуем по аналогии со списком выбора ордера.

Отличается функция создания списка:

//+------------------------------------------------------------------+
//| Функция создания списка цветов                                   |
//| y_max - макс. ширина списка                                      |
//| ID - идентификатор списка                                        |
//| num - номер интерфейса                                           |
//+------------------------------------------------------------------+
void create_color_list(int y_max,string ID,int num)
  {
  //получим координаты кнопки активации списка
   int x=ObjectGetInteger(0,ID,OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, ID, OBJPROP_YDISTANCE)+ObjectGetInteger(0, ID, OBJPROP_YSIZE);
   //и цвета
   color col=ObjectGetInteger(0,ID,OBJPROP_COLOR);
   //и ширину окна
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   y_max+=y;
   int y_cnt=0;
   //переберём массив с цветами
   for(int i=0;i<132;i++)
     {
      color bgcol=colors[i];
      //рассчитаем координаты элемента списка
      int y_pos=y+y_cnt*20;
      //если достигнуты пределы окна, начнём новый столбец
      if(y_pos+20>wnd_height || y_pos+20>y_max) {x+=20; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      //создадим элемент
      string name="ActP_color_list_"+IntegerToString(num)+ID+IntegerToString(i);
      create_button(name,"",x,y_pos,20,20);
      //и установим его свойства
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

Далее обработаем клик по элементу списка: 

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик не по элементу списка цвета кнопок
      if(StringFind(sparam, "ActP_color_list_1")<0)
      {
          //удалим список
         DeleteLists("ActP_color_list_1");
          //установим кнопку активации в положение "отжата"
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //перерисуем график
         ChartRedraw();
      }     
       //клик по элементу списка цвета кнопок
      else
      {
          //извлечём цвет из элемента списка
         color col=ObjectGetInteger(0, sparam, OBJPROP_BGCOLOR);
          //и установим его для всех кнопок
         SetButtonsColor(col);
          //установим кнопку активации в положение "отжата"
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //и удалим список
         DeleteLists("ActP_color_list_1");
          //перерисуем график
         ChartRedraw();
      }
   ...   
   }
...
}

Функция SetButtonsColor() устанавливает цвет кнопок:

//+------------------------------------------------------------------+
//| Функция установки цвета кнопок                                   |
//| col - цвет для кнопок                                            |
//+------------------------------------------------------------------+
void SetButtonsColor(color col)
  {
   //переберём все объекты
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //если объект принадлежит панели и он кнопка
      //установим цвет
      if(StringFind(n,"ActP")>=0 && ObjectGetInteger(0,n,OBJPROP_TYPE)==OBJ_BUTTON) 
         ObjectSetInteger(0,n,OBJPROP_BGCOLOR,col); 
     }
   //установим глобальную переменную
   GlobalVariableSet("ActP_buttons_color",col);
  }

Оценим результат:

Рис. 15 Установка цвета кнопок

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

Рис. 16 Измененные цвета панели, кнопок и текста

Со списками покончено. Примемся за поля ввода.

5.5. Обработка событий поля ввода

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

Рассмотрим это на примере установки линии стопа:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - окончание редактирования поля ввода
   if(id==CHARTEVENT_OBJECT_ENDEDIT)//событие - окончание редактирования поля ввода
   {
   ...
       //если редактируемое поле - поле для ввода стопа для открытия с рынка
      if(sparam=="ActP_SL_edit1")
      {
          //и при этом разрешены вспомогательные линии
         if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
         {
              //возьмём текст из поля ввода
            double sl_val=StringToDouble(ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT));
              //передвинем линии на новое положение
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, sl_val);
         }
          //перерисуем график
         ChartRedraw();
          //т.к. мы можем одновременно кликнуть только по одному объекту - дальше можно не выполнять
         return;
      }
   ...   
   }
...
}

Аналогично обрабатываются и другие поля ввода.

5.6 Обработка событий таймера

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

Рассмотрим пример установки слежения за линиями стопа и тейка при активной вкладке "С рынка":

void OnTimer()//обработчик таймера
{
   //выбрана вкладка №1
   if(ObjectGetInteger(0, "ActP_main_1", OBJPROP_STATE)==1)
   {  
      //если разрешены вспомогательные линии
      if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
      {
          //то установим в поля ввода новые значения
         double sl_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_SL_line1", OBJPROP_PRICE), _Digits);
          //стопа
         ObjectSetString(0, "ActP_SL_edit1", OBJPROP_TEXT, DoubleToString(sl_pr, _Digits));
         //и тейка
         double tp_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_TP_line1", OBJPROP_PRICE), _Digits);
         ObjectSetString(0, "ActP_TP_edit1", OBJPROP_TEXT, DoubleToString(tp_pr, _Digits));
      }   
   }
   ...
   //перерисуем график
   ChartRedraw();
}
//+------------------------------------------------------------------+

Аналогично выполняется слежение и за другими линиями.


6. Совершение торговых операций

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

6.1. Открытие сделки

У нас есть кнопки "Buy" и "Sell" на вкладке "С рынка". При клике по ним при правильно заполненных полях должна совершиться сделка.

Посмотрим на обработчики этих кнопок:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке покупки
      if(sparam=="ActP_buy_button1")
      {
          //проверим состояние кнопки
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //если она в позиции "нажата"
         if(selected)
         {
             //попытаемся совершить сделку
            deal(ORDER_TYPE_BUY);
             //и установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }

          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }
      //******************************************
       //аналогично для кнопки продажи
      if(sparam=="ActP_sell_button1")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            deal(ORDER_TYPE_SELL);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

Как видим, выполняется функция deal():

//+------------------------------------------------------------------+
//| Функция открытия сделки                                          |
//+------------------------------------------------------------------+
int deal(ENUM_ORDER_TYPE typ)
  {
   //получим необходимые данные с объектов  
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit1",OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit1",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_Magic_edit1", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0, "ActP_Dev_edit1", OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit1",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0,"ActP_Exe2_radio1",OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;

   //оформим запрос
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=Ask;
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.deviation=dev;
   req.type=typ;
   req.type_filling=filling;
   req.magic=mag;
   req.comment=comm;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата
   return(res.retcode);
  }

Ничего сверхъестественного, сначала считываем необходимые данные с объектов и на их основании создаём торговый запрос.

Проверим работу:

Рис. 17 Торговые операции - результат совершения сделки на покупку

Как видите, сделка на покупку совершена успешно.

6.2. Установка отложенного ордера

Кнопки "Buy" и "Sell" на вкладке "Отложенный" отвечают за установку отложенных ордеров.

Рассмотрим обработчики:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту  
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке установки отложенного ордера на покупку
      if(sparam=="ActP_buy_button2")
      {
          //проверим состояние кнопки
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);//проверим состояние кнопки
          //если нажата
         if(selected)
         {
            ENUM_ORDER_TYPE typ; 
              //возьмём цену, на которую хотим установить ордер
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
              //если не предполагается установка ордера stoplimit
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
                 //если цена установки ниже текущей установим limit ордер
               if(Ask>pr) typ=ORDER_TYPE_BUY_LIMIT; 
                 //иначе - stop ордер
               else typ=ORDER_TYPE_BUY_STOP;
            }
              //если предполагается установка ордера stoplimit
            else
            {
                 //установим нужный тип операции
               typ=ORDER_TYPE_BUY_STOP_LIMIT;
            }   
              //попробуем установить ордер
            order(typ);
              //установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }     
      //******************************************  

       //аналогично при установке отложенного ордера на продажу
      if(sparam=="ActP_sell_button2")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            ENUM_ORDER_TYPE typ;
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
               if(Bid<pr) typ=ORDER_TYPE_SELL_LIMIT;
               else typ=ORDER_TYPE_SELL_STOP;
            }
            else
            {
               typ=ORDER_TYPE_SELL_STOP_LIMIT;
            }   
            order(typ);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }        
   ...   
   }
...
}

Здесь определяется тип будущего ордера на основании положения текущей цены относительно цены установки, после чего функция order() устанавливает ордер:

//+------------------------------------------------------------------+
//| Функция установки ордера                                         |
//+------------------------------------------------------------------+
int order(ENUM_ORDER_TYPE typ)
  {
   //получим необходимые данные с объектов
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit2",OBJPROP_TEXT));
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit2",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit2", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit2", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit2",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit2",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0,"ActP_Magic_edit2",OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit2",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;
   if(ObjectGetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_RETURN;
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //оформим запрос
   MqlTradeRequest req;
   MqlTradeResult res; 
   req.action=TRADE_ACTION_PENDING;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type=typ;
   req.type_filling=filling;
   req.type_time=expir_type;
   req.expiration=expir;
   req.comment=comm;
   req.magic=mag;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата
   return(res.retcode);
  }

Проверим работу:

Рис. 18 Торговые операции - результат выставления ордера на покупку

Ордер buy stoplimit установлен успешно.

6.3. Модификация сделки

Кнопка "Изменить" на вкладке "Мод/закр" при выбранной сделке из списка отвечает за модификацию этой сделки:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке модификации открытой сделки
      if(sparam=="ActP_mod_button4")
      {
          //проверим состояние кнопки
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //если нажата
         if(selected)//если нажата
         {
             //модифицируем
            modify_pos();
             //удалим элементы интерфейса 
            DeleteScheme("ActP" ,true);
             //и установим заново (иными словами обновим интерфейс)
            SetScheme(0);
             //установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }     
   ...   
   }
...
}

Функция modify_pos() непосредственно отвечает за модификацию:

//+------------------------------------------------------------------+
//| Функция модификации сделки                                       |
//+------------------------------------------------------------------+
int modify_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("Нет открытых сделок по символу "+Symbol(),"Message");
   //получим необходимые данные с объектов
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit4",OBJPROP_TEXT));  
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit4", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0,"ActP_dev_edit4",OBJPROP_TEXT));

   //оформим запрос
   MqlTradeRequest req;
   MqlTradeResult res;  
   req.action=TRADE_ACTION_SLTP;
   req.symbol=Symbol();
   req.sl=NormalizeDouble(SL, _Digits);
   req.tp=NormalizeDouble(TP, _Digits);
   req.deviation=dev;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата
   return(res.retcode);

  }

Результат:


Рис. 19 Торговые операции - результат модификации свойств сделки (установка TP и SL)

Стоп и тейк уровни изменены успешно.

6.4. Закрытие сделки

Кнопка "Закрыть" на вкладке "Мод/закр" при выбранной сделку из списка отвечает за закрытие(возможно частичное) этой сделки:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке закрытия открытой сделки
      if(sparam=="ActP_del_button4")
      {
          //проверим состояние кнопки
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //если нажата
         if(selected)
         {
              //пробуем закрыть
            int retcode=close_pos();
              //если удачно
            if(retcode==10009)
            {
                 //удалим элементы интерфейса
               DeleteScheme("ActP" ,true);
                 //установим новый текст кнопки активации списка
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Выберите ордер -->");
            }
             //установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }     
   ...   
   }
...
}

Функция close_pos() отвечает за закрытие:

//+------------------------------------------------------------------+
//| Функция закрытия сделки                                          |
//+------------------------------------------------------------------+
int close_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("Нет открытых сделок по символу "+Symbol(),"Message");
   //получим необходимые данные с объектов
   double lots=StringToDouble(ObjectGetString(0,"ActP_lots_edit4",OBJPROP_TEXT));
   if(lots>PositionGetDouble(POSITION_VOLUME)) lots=PositionGetDouble(POSITION_VOLUME);
   int dev=StringToInteger(ObjectGetString(0, "ActP_dev_edit4", OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_mag_edit4", OBJPROP_TEXT));

   //оформим запрос
   MqlTradeRequest req;
   MqlTradeResult res;

   //в завсисмости от типа выберем тип контрсделки
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {
      req.price=Bid;
      req.type=ORDER_TYPE_SELL;
     }
   else
     {
      req.price=Ask;
      req.type=ORDER_TYPE_BUY;
     }

   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.sl=0;
   req.tp=0;
   req.deviation=dev;
   req.type_filling=ORDER_FILLING_FOK;
   req.magic=mag;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата 
   return(res.retcode);
  }

Результат - закрыто 1.5 лота из трёх по выбранной сделке:

Рис. 20 Торговые операции - частичное закрытие позиции

6.5. Модификация отложенного ордера

Кнопка "Изменить" на вкладке "Мод/закр" при выбранном ордере из списка отвечает за модификацию этого ордера:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке модификации ордера
      if(sparam=="ActP_mod_button3")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {     
             //извлечём из подписи кнопки списка тикет ордера
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
              //модифицируем ордер
            modify_order(ticket);
              //обновим интерфейс
            DeleteScheme("ActP" ,true);
            SetScheme(ticket);
              //установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }     
   ...   
   }
...
}

Функция modify_order() отвечает за модификацию:

//+------------------------------------------------------------------+
//| Функция модификации ордера                                       |
//| ticket - тикет ордера                                            |
//+------------------------------------------------------------------+
int modify_order(int ticket)
  {
   //получим необходимые данные с объектов
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit3",OBJPROP_TEXT)); 
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit3",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit3", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit3", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit3",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit3",OBJPROP_TEXT));
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //оформим запрос на модификацию
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_MODIFY;
   req.order=ticket;
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type_time=expir_type;
   req.expiration=expir;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата
   return(res.retcode);
  }

Посмотрим результат - ордер модифицирован успешно:

Рис. 21 Торговые операции - модификация отложенного ордера

6.6. Удаление отложенного ордера

Кнопка "Удалить" на вкладке "Мод/закр" при выбранном ордере из списка отвечает за удаление этого ордера: 

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //событие - клик по графическому объекту  
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //клик по кнопке удаления ордера
      if(sparam=="ActP_del_button3")
      {
          //проверим состояние кнопки
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //если нажата
         if(selected)
         {
              //извлечём из подписи кнопки списка тикет ордера
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
              //пробуем удалить ордер
            int retcode=del_order(ticket);
              //если удачно
            if(retcode==10009)
            {
                 //удалим элементы интерфейса
               DeleteScheme("ActP" ,true);
                 //установим новый текст кнопки активации списка
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Выберите ордер -->");
            }
             //установим кнопку в положение "отжата"
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //перерисуем график
         ChartRedraw();
          //и закончим выполнение функции
         return;
      }     
   ...   
   }
...
}

Функция del_order() отвечает за удаление ордера:

//+------------------------------------------------------------------+
//| Функция удаления ордера                                          |
//| ticket - тикет ордера                                            |
//+------------------------------------------------------------------+
int del_order(int ticket)
  {
   //оформим запрос на удаление
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_REMOVE;
   req.order=ticket;
   //отошлём запрос
   OrderSend(req,res);
   //покажем сообщение с результатом
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //вернём код возврата
   return(res.retcode);
  }

Посмотрим результат - ордер удалён:

Рис. 22 Торговые операции - удаление отложенного ордера


Заключение

Итак, все функции панели протестированы и работают успешно.

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

Для начала работы с панелью необходимо распаковать архив в папку с терминалом, потом набросить индикатор AP на график, и только после этого запустить эксперт Active Panel.


Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Sergey Pavlov
Sergey Pavlov | 15 апр. 2010 в 10:45

Взял на заметку несколько интересных решений.

Dmitrij Isaenko
Dmitrij Isaenko | 2 нояб. 2014 в 17:50
Полезно и интересно для ознакомления. Спасибо автору!
Практическое применение баз данных для анализа рынков Практическое применение баз данных для анализа рынков
Работа с данными стала главной задачей современного программного обеспечения, как автономных, так и сетевых прикладных программ. Для ее решения было создано специализированное программное обеспечение - системы управления базами данных (СУБД), которые позволяют структурировать, систематизировать и организовывать данные для их компьютерного хранения и обработки. Что касается трейдинга, то основная масса аналитиков не прибегает к использованию баз данных (БД) в своей работе. Но бывают задачи, где такое решение пришлось бы кстати. В данной статье приводится пример индикатора, который может сохранять и загружать данные из баз как с клиент-серверной, так и с файл-серверной архитектурами.
MQL5 для "чайников": Как проектировать и конструировать классы объектов MQL5 для "чайников": Как проектировать и конструировать классы объектов
На примере создания программы визуального программирования показано, как проектировать и конструировать классы на MQL5. Статья предназначена для начинающих разработчиков приложений МТ5. Предлагается простая и понятная технология создания собственных классов без глубокого погружения в теорию объектно-ориентированного программирования.
Пример разработки торговой системы, основанной на различиях  часовых поясов на разных континентах Пример разработки торговой системы, основанной на различиях часовых поясов на разных континентах
Листая страницы Интернета, можно найти множество стратегий, которые вам советуют делать то или иное. Давайте заглянем внутрь и посмотрим на сам процесс составления стратегии, основанной на различиях часовых поясов на разных континентах.
Пример написания игры "Змейка" на MQL5 Пример написания игры "Змейка" на MQL5
В статье рассматривается пример написания игры "Змейка". Создание игр в 5-ой версии языка MQL стало возможным, в первую очередь, благодаря обработке событий. Поддержка объектно-ориентированного программирования значительно упрощает данный процесс. Также вы узнаете особенности обработки событий, примеры работы со стандартной библиотекой MQL5 и способы периодического вызова функций.