Разработка торгового советника с нуля (Часть 31): Навстречу будущему (IV)

Daniel Jose | 16 декабря, 2022

Введение

После того, как мы удалили Chart Trade внутри советника, в статье "Разработка торгового советника с нуля (Часть 29)" мы сделали из той же панели Chart Trade индикатор. Как это сделать, со всеми сопутствующими моментами, включая функции и уход, необходимые для поддержания индикатора в рабочем состоянии, мы рассказали в части 30. Это один из возможных подходов, хотя на самом деле есть и другие, со своими преимуществами и недостатками, но мы рассмотрим их в другой раз.

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

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

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


Внедрение звукового сервиса

От всех этих изменений можно сойти с ума. Но поверьте, идея все же не в том, чтобы свести вас с ума (разве что немного удивить 😁), а в том, чтобы показать, как иногда небольшие изменения могут иметь большое значение и сделать использование платформы MetaTrader 5 гораздо более приятным. Одновременно с этим посмотрим, как это позволяет модулировать вещи.

Такое решение позволяет выбирать, что нам нужно, а что нет. Если что-то понравится, можно будет добавить улучшения к этой функции, чтобы сделать ее еще более полезной и приятной. И при этом не нужно будет вносить большие изменения или перепрограммировать то, что мы давно настроили. В целом, идея заключается в том, чтобы ВСЕГДА ИСПОЛЬЗОВАТЬ РЕСУРСЫ ПОВТОРНО.

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

Не имеет смысла размещать систему оповещения только в советнике. Может быть очень полезно иметь систему звуковых сигналов в индикаторах или даже в скриптах, которые запускаются в определенное время. Это поможет анализировать вместе с советником. Таким образом, платформа MetaTrader 5 может стать настоящим ТИТАНОМ в плане анализа, с огромным количеством вычислений, которые помогут нам анализировать рынок в очень конкретные моменты, либо для входа в позиции, либо для их закрытия. И это при минимальных усилиях.

Здесь кто-то может возразить: "Но я могу добавить в файл MQH (Header File) все звуки, включить их в исполняемые файлы и получить требуемое поведение". Да, так можно сделать, но давайте рассмотрим следующий сценарий: со временем этот файл MQH будет расти, и, по мере того, как это происходит, некоторые старые программы могут стать несовместимыми с этим заголовочным файлом (MQH). При перекомпиляции этих старых файлов мы столкнемся с проблемами. А если мы создадим модульную систему, в которой существует протокол связи между процессами, мы сможем расширить функциональность платформы, сохранив при этом совместимость со старыми программами.

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

В предыдущей статье я показал, как воссоздать Chart Trade, чтобы он вел себя так же, как и во время его интеграции в советник. Однако, удалив его из советника, нужно было создать способ, чтобы он продолжал работать в том же режиме. Тот способ, который я вам показал, является одним из многих возможных, и хоть он не самый лучший, но он работает. Каждое решение требует правильного понимания общей работы вещей. Иногда, ограничивая себя одной моделью идеи, мы не можем решить какие-то конкретные ситуации, а наоборот, усугубляем их. Именно из-за недостатка знаний многие люди думают, что ничего нельзя сделать, или просто говорят, что система ограничена, когда на самом деле ограничение заключается в недостатке знаний у того человека, который планирует и реализует решения.

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

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

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableAlert "Sound Alert"
//+------------------------------------------------------------------+

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

После этого мы сможем создать файл, который будет звуковым сервисом, как можно увидеть в приведенном ниже коде:

#property service
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Sounds.mqh>
#include <NanoEA-SIMD\Interprocess\Sound.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        union u00
        {
                double value;
                struct s00
                {
                        uint    i0,
                                i1;
                }info;
        }u_local;
        C_Sounds Sound;
        
        while (!IsStopped())
        {
                Sleep(500);
                if (GlobalVariableGet(def_GlobalVariableAlert, u_local.value))
                {
                        GlobalVariableDel(def_GlobalVariableAlert);
                        if (u_local.info.i1 == 0) Sound.PlayAlert((C_Sounds::eTypeSound)u_local.info.i0);
                        else Sound.PlayAlert(u_local.info.i1);
                }
        }
}
//+------------------------------------------------------------------+

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

Всё, что нам нужно сделать — это указать индекс звука для воспроизведения. Основываясь на вышеуказанной структуре, можно воспроизвести в общей сложности 4 294 967 295 различных звуков, и это количество только из внешних файлов. Можно иметь такое же количество внутренних звуков, то есть это означает, что можно сделать много чего.

Чтобы система узнала, какой тип звука воспроизвести, проверяется значение переменной u_local.info.i1: если значение равно 0, то воспроизводимый звук будет встроен в служебный файл, а индекс звука будет указан переменной u_local.info.i0, но это значение представляет собой перечисление в классе C_Sound.

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

Прежде чем идти дальше, давайте немного подумаем. В отличие от индикатора Chart Trade, который будет общаться только с советником, звуковая система может держать связь с любым типом программы в платформе MetaTrader 5. И чтобы воспроизвести нужный звук, нужно установить значение переменной, которая всегда будет значением double, и сделать это соответствующим образом.

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

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


Создание библиотеки для доступа к звуковому сервису

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

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

При создании библиотеки есть только 2 проблемы:

  1. Четко сформулировать экспортируемые функции.
  2. Максимально скрыть сложность внутреннего моделирования, чтобы пользователи библиотеки не знали, что и как происходит, а только видели входящие данные и выходящий результат.

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

Итак, давайте посмотрим на нашу библиотеку, которая будет скрывать моделирование данных, использованных в звуковом сервисе. Каждая программа должна сообщать о двух моментах: первый из них — является ли звук внутренним или внешним; второй — какой индекс у звука. Звучит сложно? Давайте взглянем на код этих вызовов внутри библиотеки:

void Sound_WAV(uint index) export { Sound(0, index); }
void Sound_Alert(uint index) export { Sound(index, 0); }

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

Но если посмотреть весь код, то не найдем ни одной функции с именем Sound. Так где же она? Она находится в самой библиотеке, но не будет видна за ее пределами. Давайте просмотрим полный код библиотеки ниже:

//+------------------------------------------------------------------+
#property library
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Interprocess\Sound.mqh>
//+------------------------------------------------------------------+
void Sound_WAV(uint index) export { Sound(0, index); }
//+------------------------------------------------------------------+
void Sound_Alert(uint index) export { Sound(index, 0); }
//+------------------------------------------------------------------+
void Sound(uint value00, uint value01)
{
        union u00
        {
                double value;
                struct s00
                {
                        uint    i0,
                                i1;
                }info;
        }u_local;
        
        u_local.info.i0 = value00;
        u_local.info.i1 = value01;
        GlobalVariableTemp(def_GlobalVariableAlert);
        GlobalVariableSet(def_GlobalVariableAlert, u_local.value);
}
//+------------------------------------------------------------------+

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

Чтобы понять, как это работает, давайте рассмотрим пример скрипта:

#property copyright "Daniel Jose"
#property script_show_inputs
#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import
//+------------------------------------------------------------------+
input uint value00 = 1;         //Внутренний звуковой сервис...
input uint value01 = 10016;     //Звук записанный в WAV-файле...
//+------------------------------------------------------------------+
void OnStart()
{
        Sound_WAV(value01);
        Sound_Alert(value00);
}
//+------------------------------------------------------------------+

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

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

void OnStart()
{
        Sound_WAV(value01);
        Sound_Alert(value00);
}

Во втором случае код будет выглядеть так:

void OnStart()
{
        Sound_Alert(value00);
        Sound_WAV(value01);
}

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

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

#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import

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

// ...

#import "Service_Sound.ex5"
        void Sound_WAV(uint);
        void Sound_Alert(uint);
#import
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#include <NanoEA-SIMD\Interprocess\Sound.mqh>

// ...

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalVariableAlert "Sound Alert"
//+------------------------------------------------------------------+
enum eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END};
//+------------------------------------------------------------------+

Таким образом, мы можем получить код, подобный такому:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound_Alert(TRADE_ALLOWED);
                return INIT_FAILED;
        }

// ... Остальная функция

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

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound_Alert(0);
                return INIT_FAILED;
        }

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

А теперь ответьте честно: такой вариант легче для понимания?

В конце всей этой работы информация внутри платформы будет идти, как показано на следующем рисунке:

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


Заключение

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

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

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

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

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