Советник для размещения ордеров на основе риска с графическим интерфейсом на графике (Часть 2): Добавление интерактивности и логики
Введение
В первой части этой серии мы сосредоточились на проектировании статической панели управления на графике для нашего инструмента размещения ордеров с учетом риска. Целью было создать аккуратный и структурированный пользовательский интерфейс, позволяющий трейдерам вводить параметры сделки, такие как тип ордера и цена входа, прямо с графика. На том этапе интерфейс был только визуальным и не мог реагировать на действия пользователя.
Во второй части мы сделаем интерфейс интерактивным и функциональным. Наша главная задача — связать графические компоненты с рабочей логикой, чтобы при нажатии пользователем кнопки расчета лота программа считывала поля ввода, рассчитывала корректный размер лота на основе заданного риска и сразу выводила результат на график. Аналогично, при нажатии кнопки отправки ордера система будет отправлять торговый ордер, используя рассчитанный размер лота и параметры, заданные пользователем.
К концу статьи у вас будет полностью рабочий калькулятор размера лота на графике, который реагирует на действия пользователя и выполняет расчеты в реальном времени. Этот практический шаг превращает нашу статическую панель в интеллектуального торгового помощника, повышая точность и контроль при планировании сделок.
Подготовка к интерактивности
В первой части серии наша панель управления была статической. Она могла отображать кнопки и поля ввода, но не реагировала на щелчки пользователя. В этой части мы сделаем панель интерактивной, обрабатывая действия пользователя, например нажатия кнопок.
В MQL5 интерактивность реализуется с помощью специальной функции обработки событий, которая называется OnChartEvent. Эта функция автоматически вызывается терминалом при возникновении определенного события на графике, например щелчка мыши по графическому объекту, нажатия клавиши или пользовательского события, сгенерированного программой.
Ниже показана структура этой функции, уже определенная в нашем исходном коде:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { }
У этой функции четыре параметра:
- id - идентифицирует тип произошедшего события. Например, CHARTEVENT_OBJECT_CLICK обозначает щелчок по объекту на графике.
- lparam - хранит дополнительные данные в зависимости от типа события, например координату X мыши или код клавиши.
- dparam - обычно содержит числовые данные, связанные с событием, например координату Y мыши.
- sparam - строковый параметр, который часто содержит имя объекта, участвующего в событии. Именно на него мы будем опираться, чтобы понять, какая кнопка или метка была нажата.
Теперь давайте определим события щелчка внутри этой функции. Поскольку нас интересуют только щелчки пользователя по нашим графическим компонентам, мы сосредоточимся на событии CHARTEVENT_OBJECT_CLICK. Для этого добавим следующий условный оператор в тело функции:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ } }
Так программа будет реагировать только тогда, когда пользователь щелкает по объекту на графике. Далее мы можем использовать параметр sparam, чтобы точно определить, какой объект был нажат, и выполнить соответствующее действие.
Перехват и обработка действий пользователя
Первое действие, которое нужно обработать, — закрытие GUI при щелчке пользователя по значку X в правом верхнем углу панели управления.

Для этого мы определим новую функцию DESTROY_GUI. Она удалит все объекты, составляющие пользовательский интерфейс, и обновит график.
Ниже приведено определение функции:
//+------------------------------------------------------------------+ //| Function to destroy the main GUI | //+------------------------------------------------------------------+ void DESTROY_GUI(){ ObjectsDeleteAll(0, SmartRiskTrader); ObjectDelete(0, BTN_CLOSE_GUI); ObjectDelete(0, BTN_ORDER_TYPES); ObjectDelete(0, FIELD_ENTRY_PRICE); ObjectDelete(0, FIELD_STOP_LOSS); ObjectDelete(0, FIELD_TAKE_PROFIT); ObjectDelete(0, RISK); ObjectDelete(0, BTN_CALC_LOT); ObjectDelete(0, BTN_SEND_ORDER); ObjectDelete(0, RESULTS_TEXT); ObjectsDeleteAll(0, "ORDER_TYPE_GROUP"); ChartRedraw(); }
Эта функция гарантирует, что при закрытии GUI с графика будут удалены все графические компоненты, и график останется чистым. Вызов ChartRedraw немедленно обновляет график, чтобы отразить эти изменения.
Далее нужно определить, когда пользователь действительно щелкает по значку X . Мы делаем это внутри функции обработки событий OnChartEvent. Достаточно проверить, было ли событие щелчком по объекту и совпадает ли имя нажатого объекта с BTN_CLOSE_GUI. Если совпадает, вызываем функцию DESTROY_GUI, чтобы удалить интерфейс.
Вот как мы это обрабатываем:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ //--- To close GUI if(sparam == BTN_CLOSE_GUI){ DESTROY_GUI(); } } }
После компиляции и запуска кода щелчок по значку X мгновенно удалит весь GUI с графика. Так пользователь получает простой и отзывчивый способ закрывать панель, как в обычном интерфейсе приложения.
После закрытия панели управления было бы неудобно, если бы у пользователя не было способа открыть ее снова. Для решения этой задачи мы создадим небольшую кнопку открытия GUI, которая появится в левом нижнем углу графика. При щелчке по этой кнопке основная панель будет возвращаться на график.

Начнем с определения макроса, который задаст кнопке уникальное имя для удобного обращения к ней в дальнейшем:
//+------------------------------------------------------------------+ //| Macros | //+------------------------------------------------------------------+ ... #define BTN_GUI_OPEN "BTN_GUI_OPEN"
Затем определим функцию CREATE_GUI_OPEN_BUTTON. Она нарисует кнопку открытия GUI на графике и аккуратно разместит ее в левом нижнем углу:
//+------------------------------------------------------------------+ //| Function to create the GUI open button | //+------------------------------------------------------------------+ void CREATE_GUI_OPEN_BUTTON(){ CREATE_OBJ_BUTTON(BTN_GUI_OPEN, 20, 60, 120, 40, "Open GUI", clrWhite, 12, 2, clrDarkGreen, clrBlack); ObjectSetInteger(0, BTN_GUI_OPEN, OBJPROP_CORNER, CORNER_LEFT_LOWER); ChartRedraw(); }
Вызов ObjectSetInteger гарантирует, что позиция кнопки будет привязана к левому нижнему углу графика. Команда ChartRedraw немедленно обновляет график, чтобы кнопка появилась без задержки.
Наконец, нужно показывать эту кнопку сразу после закрытия GUI. Для этого просто вызываем функцию CREATE_GUI_OPEN_BUTTON внутри логики обработки событий при щелчке по значку X:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ //--- To close GUI if(sparam == BTN_CLOSE_GUI){ ... CREATE_GUI_OPEN_BUTTON(); } } }
Теперь, когда кнопка открытия GUI создана, нужно сделать ее рабочей. При щелчке пользователя по этой кнопке основная панель управления должна снова отображаться на графике. Для этого добавим небольшой фрагмент логики в функцию обработки событий.
Идея проста: при нажатии кнопки открытия GUI мы сначала удаляем эту кнопку с графика, чтобы избежать дубликатов, а затем заново создаем основной GUI с помощью уже существующей функции CREATE_GUI. Вот как выглядит обновленная функция обработки событий:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To open GUI if(sparam == BTN_GUI_OPEN){ DESTROY_GUI_OPEN_BUTTON(); CREATE_GUI(); } } }
В этом коде функция DESTROY_GUI_OPEN_BUTTON отвечает за удаление кнопки открытия GUI перед повторным созданием основной панели.
//+------------------------------------------------------------------+ //| Function to destroy the GUI open button | //+------------------------------------------------------------------+ void DESTROY_GUI_OPEN_BUTTON (){ ObjectDelete(0, BTN_GUI_OPEN); ChartRedraw(); }
Это сохраняет график чистым и предотвращает наложение графических элементов друг на друга.
После компиляции и тестирования вы увидите, что щелчок по кнопке открытия GUI аккуратно возвращает основную панель управления в прежнем виде. Такая простая функция делает интерфейс гораздо более интерактивным и завершенным.
Далее автоматизируем выпадающий список типов ордеров. Мы сделаем его функциональным: когда трейдер нажимает кнопку, список появляется, а при повторном нажатии исчезает. Для этого сначала определим функцию, которая будет удалять выпадающий список, когда он не нужен.
//+------------------------------------------------------------------+ //| Function to destroy the order type dropdown | //+------------------------------------------------------------------+ void DESTROY_ORDER_TYPE_DROPDOWN(){ ObjectsDeleteAll(0, "ORDER_TYPE_GROUP"); ChartRedraw(); }
Приведенная выше функция удаляет все объекты графика, относящиеся к группе "ORDER_TYPE_GROUP". Каждый объект, создаваемый при построении выпадающего списка, будет будет принадлежать этой группе. После удаления функция ChartRedraw обновляет график, чтобы сразу отразить изменения.
Теперь нужно управлять тем, когда выпадающий список должен появляться и когда он должен скрываться.

Это делается через функцию OnChartEvent, которая обрабатывает все взаимодействия пользователя с графиком. Мы изменим ее так, чтобы она определяла щелчок по кнопке Order Types и в зависимости от состояния показывала или скрывала выпадающий список.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To handle order types dropdown if(sparam == BTN_ORDER_TYPES){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); if(state == true) { CREATE_ORDER_TYPE_DROPDOWN(); } if(state == false){ DESTROY_ORDER_TYPE_DROPDOWN(); } } } }
Свойство OBJPROP_STATE сообщает, нажата кнопка (true) или отпущена (false). Когда кнопка нажата, вызывается функция CREATE_ORDER_TYPE_DROPDOWN для отображения выпадающего списка. Когда она отпущена, вызывается функция DESTROY_ORDER_TYPE_DROPDOWN, скрывающая список. Этот простой механизм делает кнопку отзывчивой: теперь трейдер может щелкнуть по ней, чтобы показать список типов ордеров, и щелкнуть еще раз, чтобы скрыть его.
В первой части серии мы создали статический GUI, в котором поля цены входа, Stop Loss и Take Profit отображали жестко заданные значения. Это было удобно на этапе проектирования, но не подходит для реальной торговли. В реальной торговле цены постоянно меняются, и эти поля должны показывать значения, отражающие текущую рыночную ситуацию.
Для этого сначала создадим две вспомогательные функции: одну для получения текущей цены Ask, другую — для получения цены Bid символа, на график которого загружен наш интерфейс. Эти функции позволят легко обращаться к актуальным ценам в любом месте кода без повторения одной и той же логики.
//+------------------------------------------------------------------+ //| Function to get Ask Price | //+------------------------------------------------------------------+ double Ask(){ double askPrc = SymbolInfoDouble(_Symbol, SYMBOL_ASK); return NormalizeDouble(askPrc, _Digits); } //+------------------------------------------------------------------+ //| Function to get Bid Price | //+------------------------------------------------------------------+ double Bid(){ double bidPrc = SymbolInfoDouble(_Symbol, SYMBOL_BID); return NormalizeDouble(bidPrc, _Digits); }
Обе функции используют встроенную функцию SymbolInfoDouble для получения ценовых данных текущего символа графика. Константы SYMBOL_ASK и SYMBOL_BID задают тип получаемой цены. Затем мы используем функцию NormalizeDouble, чтобы округлить результат до количества десятичных знаков, используемого символом и представленного предопределенной переменной _Digits. С этими двумя функциями теперь эти функции можно вызывать из любого места кода: Ask или Bid из любого места нашего кода, и они всегда будут возвращать точные и актуальные цены.
Далее изменим функцию CREATE_GUI так, чтобы поля цены входа, Stop Loss и Take Profit отображали значения на основе текущей цены Ask вместо фиксированного текстового значения. Изначально эти поля создавались с использованием жестко заданных чисел, например:
//+------------------------------------------------------------------+ //| Function to render the main GUI | //+------------------------------------------------------------------+ void CREATE_GUI(){ ... //--- Entry Price CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, "1.14030", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Stop Loss CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, "1.13302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Take Profit CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, "1.16302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); ... }
Визуально это работало, но не отражало реальные рыночные цены просматриваемого финансового инструмента. Чтобы сделать интерфейс полезнее, заменим эти значения выражениями, использующими актуальные ценовые данные. Ниже обновленная версия этого раздела:
//+------------------------------------------------------------------+ //| Function to render the main GUI | //+------------------------------------------------------------------+ void CREATE_GUI(){ ... //--- Entry Price CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, DoubleToString(Ask(), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Stop Loss CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, DoubleToString(Ask() - (200 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); //--- Take Profit CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20'); CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, DoubleToString(Ask() + (400 * Point()), Digits()), C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue); ... }
Разберем это подробнее:
- Поле цены входа теперь напрямую отображает текущую цену Ask с помощью DoubleToString(Ask(), Digits()). Так мы получаем корректную строку цены, соответствующую шкале цены инструмента.
- Поле Stop Loss рассчитывается как цена Ask минус 200 пунктов, а поле Take Profit задается как цена Ask плюс 400 пунктов. Это дает трейдеру логичный ориентир для настройки торговых уровней без ручного ввода значений.
- Функция Point обеспечивает корректную подстройку смещений под ценовой формат символа, будь то валютная пара Forex, CFD на акцию или товар.
Так GUI автоматически адаптируется к торгуемому инструменту. Сразу после загрузки интерфейса трейдер получает реалистичные уровни входа и выхода, что делает калькулятор практичнее и интуитивнее.
На этом этапе мы построили выпадающее меню, которое появляется при щелчке по кнопке Order Type и исчезает при повторном щелчке. Однако само меню пока ничего не делает: выбор типа ордера не обновляет интерфейс. В этом разделе мы обработаем это взаимодействие так, чтобы при выборе трейдером конкретного типа ордера, например Market Buy, основная кнопка обновлялась и показывала выбранный вариант, выпадающий список автоматически закрывался, а поля цены входа, Stop Loss и Take Profit заполнялись разумными значениями.
Ниже код, который выполняет это при выборе пользователем варианта 'Market Buy'.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the market buy order if(sparam == "ORDER_TYPE_GROUP_MARKET_BUY"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits())); ChartRedraw(); } } }
Разберем его пошагово.
bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_BUY", OBJPROP_TEXT);
Здесь мы делаем две вещи:
- Проверяем текущее состояние кнопки типа ордера: нажата она или нет.
- Считываем текст из нажатого элемента выпадающего списка; в данном случае это должно быть "Market Buy".
ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text);
Эта строка обновляет надпись на основной кнопке типа ордера, и теперь на ней отображается “Market Buy”. Пользователь сразу получает обратную связь и ясно видит выбранный тип ордера на основной кнопке.
DESTROY_ORDER_TYPE_DROPDOWN();
После выбора вызываем ранее созданную функцию DESTROY_ORDER_TYPE_DROPDOWN, которая удаляет все объекты выпадающего списка с графика.
Затем, чтобы основная кнопка снова выглядела ненажатой, сбрасываем ее состояние:
if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); }
Далее автоматически заполняем ключевые поля ввода реалистичными значениями по умолчанию:
ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (400 * Point()), Digits()));
Вот что делает каждая строка:
- Entry Price: устанавливается на текущую цену Ask, поскольку именно по этой цене исполняется рыночная покупка.
- Stop Loss: размещается на 200 пунктов ниже цены Ask, что дает логичный запас безопасности.
- Take Profit: размещается на 400 пунктов выше цены Ask, формируя благоприятное соотношение риска и прибыли.
Наконец, мы вызываем:
ChartRedraw();
Эта команда принудительно обновляет график, чтобы последние изменения объектов и их текста мгновенно появились на экране.
Теперь нужно сделать рабочим каждый тип ордера в выпадающем списке. Когда трейдер выбирает один из типов ордеров, автоматически должны происходить три действия:
- Текст выбранного типа ордера появляется на кнопке Order Type.
- Выпадающий список закрывается, а кнопка возвращается в нормальное состояние.
- Поля цены входа, Stop Loss и Take Profit обновляются разумными значениями по умолчанию для выбранного типа ордера.
Когда трейдер выбирает Market Sell, советник выполняет следующее:
- Обновляет основную кнопку типа ордера, отображая “Market Sell”.
- Закрывает выпадающий список и восстанавливает состояние кнопки типов ордеров.
- Автоматически заполняет цену входа, Stop Loss и Take Profit значениями, подходящими для ордера на продажу.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the market sell order if(sparam == "ORDER_TYPE_GROUP_MARKET_SELL"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_MARKET_SELL", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (400 * Point()), Digits())); ChartRedraw(); } } }
Поскольку это Market Sell, ордер исполняется по цене Bid. Stop Loss устанавливается выше входа на 200 пунктов, а Take Profit — ниже входа на 400 пунктов. Это формирует реалистичный базовый сценарий для типичной сделки на продажу.
Для Buy Limit логика похожа, но цены корректируются с учетом отложенного ордера ниже текущей рыночной цены.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the buy limit order if(sparam == "ORDER_TYPE_GROUP_BUY_LIMIT"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_LIMIT", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask() - (400 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits())); ChartRedraw(); } } }
Ордер Buy Limit размещается ниже текущей цены Ask. Stop Loss задается еще ниже, а Take Profit — выше Ask.
Sell Limit — это противоположность Buy Limit: он размещается выше текущей цены Bid.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the sell limit order if(sparam == "ORDER_TYPE_GROUP_SELL_LIMIT"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_LIMIT", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid() + (400 * Point()), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits())); ChartRedraw(); } } }
Цена входа размещается выше текущего рыночного уровня. Stop Loss находится еще выше, а Take Profit — ниже, что соответствует типичной структуре ордеров Sell Limit.
Ордер Buy Stop размещается выше рынка в ожидании продолжения роста цены.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the buy stop order if(sparam == "ORDER_TYPE_GROUP_BUY_STOP"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_BUY_STOP", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Ask() + (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Ask(), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Ask() + (600 * Point()), Digits())); ChartRedraw(); } } }
Здесь вход размещается немного выше цены Ask. Stop Loss задается рядом с текущей Ask, а Take Profit располагается выше, чтобы захватить потенциальное движение вверх.
Наконец, ордер Sell Stop размещается ниже текущей цены в ожидании дальнейшего снижения.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- To select the sell stop order if(sparam == "ORDER_TYPE_GROUP_SELL_STOP"){ bool state = ObjectGetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE); string text = ObjectGetString (0, "ORDER_TYPE_GROUP_SELL_STOP", OBJPROP_TEXT); ObjectSetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT, text); DESTROY_ORDER_TYPE_DROPDOWN(); if(state == true){ ObjectSetInteger(0, BTN_ORDER_TYPES, OBJPROP_STATE, false); } //--- Set reasonable values to start with ObjectSetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT, DoubleToString(Bid() - (200 * Point()), Digits())); ObjectSetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT, DoubleToString(Bid(), Digits())); ObjectSetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT, DoubleToString(Bid() - (600 * Point()), Digits())); ChartRedraw(); } } }
Такой ордер предполагает пробой вниз. Вход расположен ниже Bid, Stop Loss размещается выше, а Take Profit — значительно ниже, чтобы использовать возможное продолжение снижения.
На этом этапе каждый тип ордера в выпадающем списке интерактивен и работает ожидаемым образом. При щелчке трейдера по любому из них интерфейс мгновенно реагирует:
- Выбранный тип появляется на основной кнопке типов ордеров.
- Выпадающий список исчезает.
- Цены входа, Stop Loss и Take Profit автоматически заполняются значениями по умолчанию.
В результате получается динамичный и удобный торговый интерфейс, в котором трейдер может интуитивно выбирать разные типы ордеров без необходимости каждый раз вводить значения вручную.
Теперь добавим интерактивность к кнопкам, чтобы при нажатии пользователем управляющих элементов исполнения советник рассчитывал и показывал размер лота либо действительно отправлял ордер на сервер. Но прежде чем подключать кнопки, нам нужно несколько вспомогательных элементов. Они дадут коду торговые возможности, уникальный идентификатор сделок и небольшие вспомогательные процедуры для открытия конкретных типов ордеров.
Сначала подключим стандартную торговую библиотеку. Разместите эту строку под макросами в верхней части файла.
//+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh>
Эта библиотека предоставляет класс CTrade и готовые торговые методы. Использование библиотеки делает торговый код простым и надежным.
Затем объявим входной параметр пользователя для magic number советника. Разместите этот input после секции include.
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input ulong magicNumber = 254700680002;
Magic number уникально идентифицирует сделки, созданные этим советником. Если сделать его входным параметром, пользователь сможет менять его без редактирования исходного кода.
Теперь определим несколько глобальных переменных. Их следует разместить сразу под секцией входных параметров пользователя.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ CTrade Trade; double accountBalance; double contractSize;
Поле объект Trade дает нам простые методы для размещения рыночных и отложенных ордеров. Две другие переменные служат заготовками для информации о счете и контракте, которую мы можем использовать позже.
Инициализируйте объект Trade в OnInit установив для советника magic number. Добавьте эту строку в OnInit.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ... //--- Set Expert Magic Number Trade.SetExpertMagicNumber(magicNumber); return(INIT_SUCCEEDED); }
Теперь добавим небольшие вспомогательные функции для размещения каждого типа ордера. Каждая функция-обертка содержит один торговый вызов и возвращает true при успехе или false при ошибке. Такая обертка делает основной рабочий процесс понятным и удобным для сопровождения.
Поле market buy вспомогательная функция вызывает метод Trade.Buy с переданными значениями объема, цены входа, Stop Loss и Take Profit. Если вызов завершается ошибкой, функция выводит код ошибки и информацию о результате торговой операции, затем возвращает false. При успехе возвращается true.
//+------------------------------------------------------------------+ //| Function to open a market buy position | //+------------------------------------------------------------------+ bool OPEN_MARKET_BUY(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
Поле market sell вспомогательная функция делает то же самое с помощью Trade.Sell. Она обрабатывает ошибки таким же образом и возвращает вызывающему коду булев результат
//+------------------------------------------------------------------+ //| Function to open a market sell position | //+------------------------------------------------------------------+ bool OPEN_MARKET_SELL(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, takeProfit)){ Print("Error while executing a market sell order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
Также добавим вспомогательные функции для отложенных ордеров. Функция buy limit использует Trade.BuyLimit а функция sell limit использует Trade.SellLimit. Обе функции принимают цену входа, Stop Loss, Take Profit и размер лота. При ошибке они сообщают о ней и возвращают false.
//+------------------------------------------------------------------+ //| Function to open a buy limit order | //+------------------------------------------------------------------+ bool OPEN_BUY_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.BuyLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a buy limit order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } //+------------------------------------------------------------------+ //| Function to open a sell limit order | //+------------------------------------------------------------------+ bool OPEN_SELL_LIMIT(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.SellLimit(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a sell limit order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
Наконец, добавим вспомогательные функции для ордеров buy stop и sell stop с использованием Trade.BuyStop и Trade.SellStop. Каждая функция работает по одной схеме: пытается выполнить торговую операцию, при ошибке выводит диагностическую информацию и возвращает булев результат.
//+------------------------------------------------------------------+ //| Function to open a buy stop order | //+------------------------------------------------------------------+ bool OPEN_BUY_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.BuyStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a buy stop order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } //+------------------------------------------------------------------+ //| Function to open a sell stop order | //+------------------------------------------------------------------+ bool OPEN_SELL_STOP(double entryPrice, double stopLoss, double takeProfit, double lotSize){ if(!Trade.SellStop(lotSize, entryPrice, _Symbol, stopLoss, takeProfit)){ Print("Error while executing a sell stop order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }
Использование таких небольших оберток упрощает логику отправки ордера. Позже мы будем вызывать подходящую вспомогательную функцию в зависимости от выбранного типа ордера. Сообщения об ошибках, выводимые этими функциями, помогут диагностировать проблемы, например недостаточную маржу или некорректные параметры.
Теперь добавим интерактивность к кнопкам расчета лота и отправки ордера, чтобы советник мог реагировать на щелчки пользователя по ним.

Эти две кнопки представляют финальный этап нашего графического интерфейса. Первая рассчитывает корректный размер лота на основе заданного процента риска, а вторая отправляет выбранный тип ордера на рынок или сервер. Чтобы это стало возможным, мы должны определить нажатие каждой кнопки и выполнить соответствующее действие.
Начнем с расширения функции OnChartEvent. Как вы знаете, она автоматически вызывается при взаимодействии пользователя с графиком. Здесь мы специально обрабатываем CHARTEVENT_OBJECT_CLICK, которое срабатывает при щелчке пользователя по кнопке. Внутри этого события проверяем имя объекта, чтобы понять, какая кнопка была нажата.
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- When the lot calculation button is clicked if(sparam == BTN_CALC_LOT) { } //--- When the send order button is clicked if(sparam == BTN_SEND_ORDER){ } } }
Если имя объекта совпадает с BTN_CALC_LOT, это означает, что пользователь нажал кнопку расчета лота. Аналогично, если имя совпадает с BTN_SEND_ORDER, значит была нажата кнопка отправки ордера. На этом этапе событие только обнаруживает щелчки, но еще не выполняет фактическую работу. Чтобы правильно обработать каждое действие, определим две вспомогательные функции: одну для расчета лота и одну для исполнения ордера.
Обработка расчета размера лота
//+------------------------------------------------------------------+ //| Function to handles lot calculation process | //+------------------------------------------------------------------+ void HandleLotCalculation(){ string order_type = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT); string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT); string stop_loss = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT); string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT); string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT); accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); if(order_type == "Select Order Type"){ ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!"); ChartRedraw(); return; } if(order_type == "Market Buy "){ double stopDistance = Ask() - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Market Sell"){ double stopDistance = StringToDouble(stop_loss) - Bid(); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Buy Limit "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Sell Limit "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Buy Stop "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } if(order_type == "Sell Stop "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); ChartRedraw(); } }
Функция HandleLotCalculation управляет всем, что связано с вычислением корректного размера лота. При вызове она сначала получает все необходимые данные из полей GUI: тип ордера, цену входа, Stop Loss, Take Profit и процент риска. Она также считывает текущий баланс счета и размер контракта из торговой среды.
Первая проверка убеждается, что пользователь выбрал тип ордера. Если нет, на графике появляется понятное сообщение с просьбой выбрать тип ордера перед продолжением. Это предотвращает лишние расчеты.
После подтверждения допустимого типа ордера функция рассчитывает размер лота по-разному в зависимости от того, является ли ордер Market Buy, Market Sell, Buy Limit, Sell Limit, Buy Stop или Sell Stop. В каждом варианте рассчитывается расстояние между ценой входа и Stop Loss, чтобы измерить риск в ценовом выражении.
Затем функция вычисляет сумму под риском, умножая процент риска на баланс счета. Деление этой суммы риска на произведение размера контракта и расстояния до стопа дает корректный размер лота для заданного риска. Результат нормализуется до двух десятичных знаков и аккуратно отображается в поле результатов GUI.
Это позволяет трейдерам мгновенно видеть размер лота, необходимый для риска, например, 2% или 3% от баланса, без ручных вычислений.
Обработка исполнения ордера
//+------------------------------------------------------------------+ //| Function to handles order execution process. | //+------------------------------------------------------------------+ void HandleOrderExecution(){ string order_type = ObjectGetString(0, BTN_ORDER_TYPES, OBJPROP_TEXT); string entry_price = ObjectGetString(0, FIELD_ENTRY_PRICE, OBJPROP_TEXT); string stop_loss = ObjectGetString(0, FIELD_STOP_LOSS, OBJPROP_TEXT); string take_profit = ObjectGetString(0, FIELD_TAKE_PROFIT, OBJPROP_TEXT); string riskPercent = ObjectGetString(0, RISK, OBJPROP_TEXT); accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE); if(order_type == "Select Order Type"){ ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Please Select Order Type!"); ChartRedraw(); return; } if(order_type == "Market Buy "){ double stopDistance = Ask() - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_MARKET_BUY(Ask(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Market Sell"){ double stopDistance = StringToDouble(stop_loss) - Bid(); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_MARKET_SELL(Bid(), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Buy Limit "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_BUY_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Sell Limit "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_SELL_LIMIT(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Buy Stop "){ double stopDistance = StringToDouble(entry_price) - StringToDouble(stop_loss); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_BUY_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } if(order_type == "Sell Stop "){ double stopDistance = StringToDouble(stop_loss) - StringToDouble(entry_price); double riskPercentValue = StringToDouble(riskPercent); double amountAtRisk = (riskPercentValue / 100.0) * accountBalance; double lotSize = amountAtRisk / (contractSize * stopDistance); lotSize = lotSize = NormalizeDouble(lotSize, 2); ObjectSetString(0, RESULTS_TEXT, OBJPROP_TEXT, "Result: Lot Size = " + DoubleToString(lotSize, 2)); OPEN_SELL_STOP(StringToDouble(entry_price), StringToDouble(stop_loss), StringToDouble(take_profit), lotSize); ChartRedraw(); } }
Вторая вспомогательная функция, HandleOrderExecution, выполняет те же шаги, что и предыдущая, но не только рассчитывает параметры, а еще и отправляет торговый ордер. После подтверждения типа ордера и расчета корректного размера лота она вызывает соответствующую функцию исполнения сделки, определенную ранее, например OPEN_MARKET_BUY(), OPEN_SELL_LIMIT() и т. д.
Как и функция расчета лота, эта функция также проверяет, выбран ли тип ордера. Если нет, сообщение предлагает пользователю сделать выбор. Затем она таким же образом рассчитывает расстояние до стопа, сумму под риском и итоговый размер лота. Главное отличие в том, что после определения размера лота она использует соответствующую вспомогательную функцию для отправки ордера на торговый сервер.
Каждая вспомогательная функция обрабатывает свой тип ордера и при возникновении ошибки, например некорректных цен или недостаточной маржи, выводит полезные сообщения на вкладке Experts. После размещения сделки функция обновляет график, показывая результат и использованный размер лота.
Наконец, мы вызываем эти две функции из обработчика событий. Внутри функции OnChartEvent просто проверяем, какая кнопка была нажата, и вызываем соответствующий обработчик:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle click events if(id == CHARTEVENT_OBJECT_CLICK){ ... //--- When the lot calculation button is clicked if(sparam == BTN_CALC_LOT) { HandleLotCalculation(); } //--- When the send order button is clicked if(sparam == BTN_SEND_ORDER){ HandleOrderExecution(); } } }
Это делает интерфейс полностью интерактивным. При нажатии кнопки расчета лота советник мгновенно рассчитывает и отображает правильный размер лота. При нажатии кнопки отправки ордера он отправляет соответствующий ордер с использованием рассчитанных параметров.
Такой подход делает GUI не только визуально завершенным, но и функциональным, практичным. Теперь трейдер может управлять сделками на основе риска прямо с графика, не вводя команды и не переключаясь между разными панелями.
Заключение
Мы подошли к концу части 2, завершая наш проект. Во второй и заключительной части мы превратили GUI из статического в полностью интерактивный и функциональный. Мы связали каждую кнопку, выпадающий список и поле ввода с реальными торговыми действиями, позволив советнику рассчитывать размеры лота на основе риска и исполнять сделки прямо с графика.
На этом пути вы узнали не только, как проектировать визуально привлекательный интерфейс, но и как заставить его разумно реагировать на действия пользователя с помощью OnChartEvent. Теперь вы понимаете, как структурировать вспомогательные функции для чистой и модульной логики исполнения сделок — основы любого советника профессионального уровня.
С этими новыми знаниями вы можете расширить созданное нами решение и разработать еще более сложные и функциональные GUI — от продвинутых панелей ордеров до полноценных торговых дашбордов управления позициями. Фундамент уже заложен, а единственное ограничение здесь — ваши задачи и ваша фантазия.
В качестве бонуса мы прикрепили полный исходный код проекта, чтобы вы могли изучать, изменять и адаптировать его под собственные торговые идеи. Создаете ли вы инструменты для личной торговли или для клиентов, этот проект дает мощную отправную точку для разработки интерактивных торговых интерфейсов с учетом риска полностью на MQL5.
На этом завершается наша серия о создании советника для размещения ордеров с учетом риска и панелью управления на графике — практического инструмента, который можно начать использовать сразу, и важного шага вперед в освоении разработки GUI на MQL5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20159
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Автоматизация торговых стратегий с помощью MQL5 (Часть 47): Торговая система Nick Rypock Trailing Reverse (NRTR) с поддержкой хеджирования
От начального до среднего уровня: События в объектах (I)
От начального до среднего уровня: События в объектах (II)
Разрабатываем пользовательский индикатор рыночных настроений
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования