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

Несколько индикаторов на графике (Часть 04): Начинаем работу с советником

MetaTrader 5Трейдинг | 5 мая 2022, 09:21
1 234 2
Daniel Jose
Daniel Jose

Введение

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


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

У нас уже работает пользовательский индикатор, то есть наш объектный класс уже функционирует. Поскольку это объектный класс, мы можем легко перенести его в другие модели. Однако простое объявление и попытка использовать класс в советнике не заставит работать вещи так же, как в нашем пользовательском индикаторе. Все потому, что в советнике нет возможности такой возможности как подокно. А потом подумалось: "А что если использовать уже скомпилированный и работающий пользовательский индикатор и вызвать его из советника с помощью команды iCustom? Что ж, это действительно может сработать, поскольку в подокне нет необходимости, а команда будет выглядеть следующим образом:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
input string user01 = "";                //Используемые индикаторы
input string user02 = "";                //Сопровождаемые активы
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//... Код советника ...

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), 0, m_handleSub)) return INIT_FAILED;
//... Код советника ...

        ChartRedraw();
        
        return(INIT_SUCCEEDED);
}
//...Остальной код советника ...

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

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

Исходя из этого, нам необходимо выполнить несколько задач:

Задание Цель
1 => Создать индикатор общего назначения, т.е. общий. Иметь возможность создавать и использовать команду iCustom, совершенно не загрязняя при этом график.
2 => Включить этот индикатор в советник каким-либо образом.  Это позволит вам без проблем перенести советник с полной функциональностью
3 => Создавать общий класс объекта для подокна  Позволяет добавлять подокна через наш советник
4 => Добиться привязки нашего класса C_TemplateChart к классу окна. Это позволит управлять содержимым подокон, ничего не меняя в уже работающем коде.

Хотя это может показаться трудоемким, трудности решаются довольно просто. Итак, давайте разберемся с каждым из пунктов.


Реализация: Создание индикатора общего назначения

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

#property copyright "Daniel Jose"
#property version   "1.00"
#property description "Этот файл служит только для поддержки индикаторов в SubWin."
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Только это и ничего больше. Сохраним этот файл как SubSupport.mq5, только он не будет находиться вместе с другими индикаторами, а перенесем его в директорию RESOURCE нашего советника, тогда структура файла будет выглядеть как на картинке ниже:


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


Реализация: Включение общего показателя в советник

Для этого нам нужно добавить следующий код в начало нашего советника.

//+------------------------------------------------------------------+
#define def_Resource "Resources\\SubSupport.ex5"
//+------------------------------------------------------------------+
#resource def_Resource
//+------------------------------------------------------------------+

Это включит скомпилированный код общего индикатора в наш советник, после этого общий индикатор может удалить файл .ex5, так как в нем нет необходимости. Теперь следует иметь в виду, что если во время компиляции кода советника не будет найден исполняемый файл SubSupport.ex5, то компилятор автоматически скомпилирует код нашего общего индикатора SubSupport.mq5 и добавит этот новый скомпилированный исполняемый файл в наш советник, т.е. если мы по какой-либо причине изменим файл SubSupport.mq5 и захотим добавить изменения в советник, нужно удалить файл SubSupport.ex5, иначе изменения не будут добавлены.

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

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


Реализация: Создание класса объекта подокна.

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

Функция Описание
Init Позволяет добавлять подокна через наш советник
Close Позволяет добавлять подокна через наш советник

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


Реализация: Наследование класса C_TemplateChart

Когда я решил создавать что-то новое, используя ООП (Объектно-ориентированное программирование), я сделал это, потому что уже знал, что есть большие преимущества в использовании такого подхода, среди которых безопасность и наследование, хотя у нас также есть полиморфизм, но мы будем использовать его позже, когда будем создавать систему кросс-ордеров, а здесь мы будем использовать одно из преимуществ ООП — наследование. Наш класс C_TemplateChart уже полностью функционален, и, видя это, мы не хотим перепрограммировать его заново или рисковать, добавляя код в класс, поотому что этот код не позволит использовать класс в других местах. Решением является использование наследования, что позволяет добавлять новый код или функции, никак не изменяя исходный код.

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


Класс-прародитель — это самый базовый класс, где мы имеем меньший уровень манипуляции данными, но когда родительский класс наследует что-то от прародительского класса, все вещи, объявленные как public в прародителе, могут быть видны и использованы родителем. Но при этом также можно добавлять новые вещи в родительский класс, и это не повлияете на то, что наследуется и поддерживается при наследовании. Если родительский класс уже закончен и работает, а нам нужно расширить его, не меняя ничего в классах ниже, то мы создаем класс-наследник, и он будет обладать всеми возможностями предыдущих классов. Также можно изменить операцию, и это интересная вещь о наследовании, потому что делая эти изменения, другие классы не будут затронуты. Однако здесь есть ограничение, в отличие от C++, который позволяет множественное наследование. Если ребенок может наследовать функции как от отцовской, так и от материнской стороны, в MQL5 это невозможно, поэтому структура всегда немного лагает, но вы все равно получаете некоторую выгоду от наследования. Пример множественного наследования можно увидеть ниже:

Хорошо, но как же это сделать в MQL5? Как объявить наследование, чтобы можно было воспользоваться им? Самый точный способ понять это — прочитать содержание объектно-ориентированного программирования (ООП), но здесь мы сразу перейдем к делу. Наследование будет осуществляться через следующие строки:

#include "C_TemplateChart.mqh"
//+------------------------------------------------------------------+
class C_SubWindow : public C_TemplateChart
{
// ... Код класса
};

Видите, что класс C_SubWindow будет публично наследовать класс C_TemplateChart, поэтому теперь мы можем использовать класс C_SubWindow для доступа к функциональности класса C_TemplateChart.

В приведенном выше фрагменте кода я выделил одну вещь, обратите внимание, что она находится в кавычках ( " ), а не как обычно используется ( < > ). Так почему же я это сделал? Как и в языке C++, в MQL5 есть несколько очень интересных вещей, но некоторые сбивают с толку тех, кто только начинает изучать искусство программирования. Когда мы помещаем заголовочный файл внутри угловых скобок ( < > ), мы имеем в виду абсолютный путь, то есть компилятор будет следовать точно по указанному нами пути, но когда мы используем кавычки (как мы уже сделали), то компилятор будет использовать относительный путь, или, чтобы вы поняли это лучше, он сначала начнет с текущего каталога, где находится рабочий файл. Это может показаться странным, но бывают случаи, когда мы имеем одно и то же имя для файлов, содержимое которых отличается, и они находятся в разных каталогах, но мы все равно хотим ссылаться на текущий каталог, поэтому мы используем кавычки для этого.

Две функции, которые мы думали использовать раньше, INIT и CLOSE, показаны ниже:

//+------------------------------------------------------------------+
bool Init(void)
{
        if (m_handleSub != INVALID_HANDLE) return true;
        if ((m_handleSub = iCustom(NULL, 0, "::" + def_Resource)) == INVALID_HANDLE) return false;
        m_IdSub = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL);
        if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) return false;
                
        return true;
}
//+------------------------------------------------------------------+
void Close(void)
{
        ClearTemplateChart();
        if (m_handleSub == INVALID_HANDLE) return;
        IndicatorRelease(m_IdSub);
        ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, 0));
        ChartRedraw();
        m_handleSub = INVALID_HANDLE;
}
//+------------------------------------------------------------------+

Видите, код очень простой и короткий, но есть кое-что, с чем мы должны быть осторожны, обратите внимание на выделенную часть. Вы должны быть внимательны, чтобы не ошибиться при добавлении этой части, потому что если вы не оставите всё именно так, исполняемый файл SubSupport.ex5, который мы попросили добавить в советник, будет виден не внутри советника, а вне его. Можно прочитать о Ресурсах, чтобы понять это, но в основном это выглядит следующим образом: если использовать ( :: ), это укажет, что советник должен использовать внутренний ресурс, присутствующий в нем, но если просто назвать ресурс, то советник будет искать его внутри каталога MQL5, поэтому если файл не существует в указанном месте, функция завершится неудачей, даже если файл был добавлен как ресурс советника.

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

Что на самом деле делает этот код, можно увидеть ниже:

input string user01 = "";               //Используемые индикаторы
input string user02 = "";               //Сопровождаемые активы
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//...   

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL), m_handleSub)) return INIT_FAILED;

//...

        ChartRedraw();
        
   return(INIT_SUCCEEDED);
}
//...Остальной код советника ...

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


Заключение:

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

В прикрепленном файле содержатся все улучшения, разработанные на данный момент, но скоро в этом коде будет еще больше интересных вещей. 😁👍


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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Mateus Cerqueira Lopes
Mateus Cerqueira Lopes | 25 февр. 2022 в 17:50
Привет, Даниил, я слежу за вашими статьями, но что насчет части 03?
Daniel Jose
Daniel Jose | 26 февр. 2022 в 13:28
Mateus Lopes #:
Здравствуйте Даниил, я слежу за вашими статьями, но что насчет части 03?

Была небольшая проблема в то время, когда просили выпустить для публикации, но уже разрешили публикацию части 03, скоро она тоже будет доступна, эта проблема была вызвана скорее количеством статей, которые я уже отправил ... В настоящее время у них есть еще 15 статей для анализа, все они связаны с разработкой этого советника, и в каждой из них все сложнее .... но спасибо, что следите за серией ... ожидайте огромных новостей от статьи 05, с этого момента вещь действительно будет стоить того, потому что она станет чем-то большим для людей, эти первые просто представляют то, что будет впереди ...😁👍

Как разработать торговую систему на основе индикатора Envelopes Как разработать торговую систему на основе индикатора Envelopes
В этой статье я поделюсь с вами еще одним методом торговли по полосам. На этот раз мы будем работать с индикатором Envelopes (Конверты, Огибающие линии). Научимся создавать стратегии на основе этого индикатора.
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock
В статье реализуем работу таких параметров панели как Padding (внутренние отступы/поля со всех сторон элемента) и Dock (способ расположения объекта внутри контейнера).
Несколько индикаторов на графике (Часть 05): Превращаем MetaTrader 5 в систему RAD (I) Несколько индикаторов на графике (Часть 05): Превращаем MetaTrader 5 в систему RAD (I)
Несмотря на то, что многие люди не умеют программировать, они достаточно креативны и имеют отличные идеи, но отсутствие знаний или понимания программирования мешает им сделать некоторые вещи. Давайте посмотрим вместе, как создать Chart Trade, но используя саму платформу MT5, как будто это IDE.
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
В статье разберём создание подчинённых элементов управления, привязанных к базовому элементу, создаваемых непосредственно при помощи функционала базового элемента управления. Помимо поставленной выше задачи, немного поработаем над объектом-тенью графического элемента, так как при её использовании для любого из объектов, позволяющих иметь тень, до сих пор есть неисправленные ошибки логики