English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Несколько индикаторов на графике (Часть 06): Превращаем MetaTrader 5 в систему RAD (II)

Несколько индикаторов на графике (Часть 06): Превращаем MetaTrader 5 в систему RAD (II)

MetaTrader 5Торговые системы | 13 мая 2022, 16:06
1 256 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье я показал, как создать Chart Trade с использованием объектов MetaTrader 5 и превратить платформу в систему RAD. Система работает очень хорошо, и наверняка многие задумывались о создании библиотеки — она позволит иметь всё больше и больше функциональности в предлагаемой системе, и можно будет разработать более интуитивно понятный советник с более приятный и простым в использовании интерфейсом.

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


Планирование

Изменения нашей IDE будут такими, как показано на следующих изображениях:

         

Как видите, небольшие изменения внесены в сам дизайн, но также добавилось две новых области: одна будет получать название актива, а другая — накопленную стоимость за день. Мы поработаем с вещами, без которых, конечно, можно обойтись и которые не будут влиять на наши решения, но они могут быть интересны пользователю. Я покажу самый простой и правильный способ добавить функциональность в нашу IDE. Итак, открываем список объектов в новом интерфейсе и видим следующее:


Два отмеченных объекта не имеют событий, связанных с ними, что означает, что они не функционируют в IDE. Все остальные объекты уже правильно связаны с определенными событиями, и MetaTrader 5 может заставить эти события правильно выполняться, когда они происходят в советнике. То есть мы можем изменить интерфейс IDE по своему вкусу, но если функциональность еще не реализована, MetaTrader 5 не сделает ничего, кроме отображения объекта на графике. Теперь нам нужно, чтобы объект EDIT 00 получал название торгуемого актива, и это название должно быть в центре объекта. Объект EDIT 01 будет получать накопленное значение за определенный период — в данном случае мы будем использовать дневной период, чтобы знать, в прибыли мы или в убытке в течение дня. Если значение отрицательное, то оно будет одного цвета, а если положительное — другого.

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


Однако следует учитывать, что невозможно указать, как будет представлена информация, то есть мы нельзя выровнять текст, чтобы он отображался в центре объекта. При желании это можно сделать с помощью кода, так как есть свойство, позволяющее выравнивать текст по центру. Более подробно смотрите "Свойства объектов", обратите внимание на ENUM_ALIGN_MODE в таблице — там указано, на каких объектах можно использовать текст, выровненный по центру.

Итак, какое бы изменение мы ни собирались создать, для начала надо подготовить план: какими будут новые возможности, как они будут представлены и как пользователь сможет с ними взаимодействовать. Мы выберем нужный объект и настроим его, насколько это возможно, используя сам интерфейс MetaTrader 5 так, что в итоге у нас будет уже готовая IDE, а нам останется только подкорректировать ее через код MQL5, так что IDE в итоге будет на 100% функциональной. Итак, давайте приступим к внесению изменений.


Изменения

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


Изменение 1. Добавляем имя актива

Для реализации этой части нам не потребуются большие изменения, но их нужно сделать в правильных местах. Для начала добавим новое значение в перечисление, исходный код показан ниже:

enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};

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

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};

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

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

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };

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

Теперь давайте вернемся в MetaTrader 5 и сделаем изменения, как показано ниже:

         

Ну вот, теперь MetaTrader 5 уже распознает объект в нашей IDE как объект для получения сообщения, осталось создать процедуру для отправки сообщения, поскольку это сообщение, к которому мы добавим текст, и это будет сделано только один раз, мы сможем отправить его, как только MetaTrader 5 поместит нашу IDE на график. Это можно было бы сделать, просто добавив необходимый код в конец функции Create нашего класса объектов, но опять же, чтобы код не превратился в Франкенштейна, полного исправлений, мы добавим новую процедуру внутрь функции DispatchMessage. Оригинальная функция выглядит следующим образом:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... Остальной код ...

        }
}

После изменения она будет выглядеть следующим образом:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }else if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... Остальной код

        }
}

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

bool Create(int nSub)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                                
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
        m_SubWindow = nSub;
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}

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


Изменение 2: добавляем суммарный результат за день (точка покрытия)

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

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eROOF_DIARY, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};

// ... Остальной код

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_ROOF_DIARY",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };

После этого давайте изменим IDE с помощью нового сообщения:

         

Наша новая IDE готова. Теперь мы реализуем код, который будет создавать сообщение, содержащее суммарное значение за день. Но надо решить, в каком классе реализовать эту функцию. Многие, возможно, создали бы эту функцию здесь же, в классе C_Chart_IDE, но по организационным причинам будет лучше поместить ее вместе с функциями, которые работают с ордерами. Так что код фактически реализован в классе C_OrderView, код можно увидеть ниже:

double UpdateRoof(void)
{
        ulong   ticket;
        int     max;
        string  szSymbol = Terminal.GetSymbol();
        double  Accumulated = 0;
                                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}

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

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        static double AccumulatedRoof = 0.0;
        bool    b0;
        double  d0;

        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if ((b0 = (szArg == szMsgIDE[eRESULT])) || (szArg == szMsgIDE[eROOF_DIARY]))
                        {
                                if (b0)
                                {
                                        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                                }else
                                {
                                        AccumulatedRoof = dValue;
                                        dValue = 0;
                                }
                                d0 = AccumulatedRoof + dValue;
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_TEXT, DoubleToString(MathAbs(d0), 2));
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_BGCOLOR, (d0 >= 0 ? clrForestGreen : clrFireBrick));
                        }else   if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// .... Остальной код....

        }
}

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

Еще одно изменение в советнике касается функции OnTrade. Выглядит оно так:

void OnTrade()
{
        SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY], NanoEA.UpdateRoof());
        NanoEA.UpdatePosition();
}

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

double UpdateRoof(void)
{
        ulong           ticket;
        string  szSymbol = Terminal.GetSymbol();
        int             max;
        static int      memMax = 0;
        static double   Accumulated = 0;
                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        if (memMax == max) return Accumulated; else memMax = max;
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}

Выделенные строки — это код, добавленный в исходную функцию. Хоть и кажется, что они не имеют большого значения, они все же его имеют. Давайте разберемся почему. При первом обращении к коду как статическая переменная memMax, так и Accumulated будут установлены в ноль, если в истории заказов не было значений за указанный период, тест отобразит их и функция их вернет, но если есть какие-либо данные, они будут протестированы, и переменные memMax и Accumulated будут отображать новое состояние. Тот факт, что эти переменные статичны, означает, что их значения сохраняются между вызовами. Поэтому когда значение позиции изменяется в результате естественного движения актива, MetaTrader 5 генерирует событие, которое вызовет функцию OnTrade. В этот момент мы имеем новый вызов функции UpdateRoof, и если позиция не была закрыта, функция вернется в контрольную точку, что ускорит процесс возврата.


Заключение

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


Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10301

Прикрепленные файлы |
EA_1.05.zip (3274.9 KB)
Разработка торговой системы на основе индикатора RSI Разработка торговой системы на основе индикатора RSI
В этой статье мы поговорим об еще одном популярном и часто используемом индикаторе — RSI. Узнаем, как разработать торговую систему на основе показателей от этого индикатора.
Разработка торговой системы на основе Импульса (Momentum) Разработка торговой системы на основе Импульса (Momentum)
В предыдущей статье я упоминал о важности определения тренда, то есть определения направления движения цены. В этой статье мы поговорим еще об одном важном понятии в трейдинге, которое также существует в виде индикатора — импульсе цен, или индикаторе Momentum. Мы разработаем собственную торговую систему на основе этого индикатора.
Нейросети — это просто (Часть 14): Кластеризация данных Нейросети — это просто (Часть 14): Кластеризация данных
Должен признаться, что с момента публикации последней статьи прошло уже больше года. За столь длительное время можно многое переосмыслить, выработать новые подходы. И в новой статье я хотел бы немного отойти от используемого ранее метода обучения с учителем, и предложить немного окунуться в алгоритмы обучения без учителя. И, в частности, рассмотреть один из алгоритмов кластеризации — k-средних.
Несколько индикаторов на графике (Часть 05): Превращаем MetaTrader 5 в систему RAD (I) Несколько индикаторов на графике (Часть 05): Превращаем MetaTrader 5 в систему RAD (I)
Несмотря на то, что многие люди не умеют программировать, они достаточно креативны и имеют отличные идеи, но отсутствие знаний или понимания программирования мешает им сделать некоторые вещи. Давайте посмотрим вместе, как создать Chart Trade, но используя саму платформу MT5, как будто это IDE.