
Создание активных панелей управления на MQL5 для торговли
Введение
Удобство имеет большое значение в работе, а тем более в работе трейдера, где скорость и точность решают многое. При подготовке терминала к работе каждый настраивает своё рабочее место максимально комфортно для себя, чтобы в максимально короткий срок можно было совершить анализ и войти в рынок. Но реальность такова, что разработчики не могут угодить всем и некоторый функционал невозможно тонко настроить "под себя".
Например, для пипсовщика важна каждая доля секунды и нажатие на кнопку "Новый ордер" и последующей установкой всех параметров может оказаться критическим по времени.
Так каков же выход? Выход - в создании собственных элементов управления, ведь в 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. Установка свойства элемента "Флажок"
Напомню, чтобы картинки были доступны в списке, они должны находится в папке "Папка терминала->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.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Взял на заметку несколько интересных решений.
... как для создания "альтернативных Панелей В окне индикатора = так вообще тема ТОП (!) :)
... но а для классического подхода --> когда Панель делается для ОСНОВНОГО окна Графика - подскажите пожалуйста как переписать код ?!
----------------
короче , Уважаемые Знатоки (или вдруг сам Автор @space_cowboy увидит) -->> просьба ПЕРЕПИСАТЬ пол-статьи для "чайников" -->> как вписать свою Панель в ГЛАВНОЕ окно Графика (?!) :))
----------------
Благодарю _/\_ :)
... ну и вопрос к Автору :
а почему Скрипт сохраняет свойства объектов не в .CSV файл , а в .BIN (??!!)
с CSV ж ведь проще работать (!)
... ну и вопрос к Автору :
а почему Скрипт сохраняет свойства объектов не в .CSV файл , а в .BIN (??!!)
с CSV ж ведь проще работать (!)
нужно взять за правило - смотреть даты последних сообщений, там 2014г