English Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 72): Неожиданный способ оповещений (I)

Разработка системы репликации (Часть 72): Неожиданный способ оповещений (I)

MetaTrader 5Примеры |
305 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье, "Разработка системы репликации (Часть 71): Настройка времени (IV)", мы показали, как нужно приступить к добавлению в систему репликации/моделирования того, что было представлено в другой статье.

В статье "Разработка системы репликации (Часть 70): Настройка времени (III)" мы используем тестовый сервис, чтобы более наглядно понять, как работать с событиями стакана цен. Это происходит, когда акцент оказывается на пользовательском символе.

В этих двух последних статьях всё оказалось довольно интересным. Это связано с тем, как нам необходимо работать, чтобы добиться желаемых результатов. Думаю, что многие смогли понять и запомнить, как правильно использовать стакан цен в MetaTrader 5. И снова в центре внимания - индивидуальный символ. Никогда не забывайте об этом.

Было довольно интересно посмотреть, что мы можем позволить указателю мыши использовать функцию OnCalculate в MetaTrader 5, просто добавив стакан цен, поскольку он размещает данные в массивах. Это значительно ускоряет работу, так как нам не нужно использовать функцию iSpread для получения спреда бара.

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

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


Освежаем память

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

Если сменим таймфрейм в MetaTrader 5, то информация, предоставляемая указателем мыши, изменится с "аукциона" на "закрытый рынок". Кроме того, индикатор управления исчезнет с графика, и взаимодействовать с ним будет невозможно.

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

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

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

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

Честно говоря, мне всё равно, удалось ли вам решить проблему или нет. Я просто хотел, чтобы вы хотя бы попытались это сделать. Решать проблемы и придумывать решения - это действительно искусство. А написание кода - это просто формальность. Но давайте посмотрим, как решить эту проблему.


Первое альтернативное решение

Если вы действительно поняли суть проблемы, то вам уже пришло в голову следующее решение: реализовать способ проверки изменения таймфрейма. Как только это произойдет, мы сделаем так, чтобы индикаторы можно было вернуть в состояние, в котором они находились до удаления с графика. Таким образом, когда MetaTrader 5 вернет их на график, то индикаторы (управления и мыши) будут знать свое последнее состояние и смогут возобновить работу с этой точки. Отличная идея, но в этом случае есть и недостаток. Любая форма, которую мы используем для записи последнего состояния индикатора, ни при каких обстоятельствах не может быть связана непосредственно с индикатором. И это означает, что данная информация должна быть доступна индикатору сразу же после запуска кода OnInit. В противном случае будут проблемы.

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

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

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

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

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


Второе альтернативное решение

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

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


Третье альтернативное решение

Хорошо, а что скажете, если мы попробуем что-нибудь более экзотичное? Давайте объединим две альтернативы в одну, но без использования глобальных переменных терминала или файлов. Вместо этого мы будем использовать буфер данных как средство передачи нужной нам информации, в данном случае значения таймфрейма. Тогда сервис сможет следить за происходящим. Таким образом, при изменении таймфрейма сервис сможет это обнаружить. Когда это произойдет, мы можем попросить сервис указать индикаторам, что их необходимо обновить.

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


Приступаем к тестированию реализации

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

Мы создадим очень простой индикатор для проверки изменений на таймфрейме. Полный исходный код этого индикатора можно найти ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. int OnInit()
08. {
09.    Print(_Period);
10. 
11.    return
     INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Исходный код тестового индикатора

Что делает этот маленький код? Всё это происходит на девятой строке. То есть он выводит на терминал информацию, которая содержится во внутренней переменной _Period. Пример реализации можно увидеть на следующем изображении:

Рисунок 01

Результат внедрения

Обратите внимание на кое-что интересное. В колонке Source этого изображения мы видим название индикатора, но нас интересует то, что заключено в скобки, а точнее, то, что находится после запятой. Обратите внимание, что мы несколько раз изменяли таймфрейм, и каждый раз в столбце сообщений выводилось другое значение. Это может показаться очевидным, но забудьте на минуту о том, что значения бывают разные. Забудьте тоже о попытках найти хоть какую-то логику. На самом деле нас интересует, какое максимальное значение может содержать _Period. Хорошо. MetaTrader 5 позволяет использовать месячный период. На данном изображении можно увидеть это значение на предпоследней строке, то есть наибольшее значение - 49153. Почему важно знать это значение? Потому что нам нужен способ заставить сервис обнаружить, что значение изменилось. Но мы не можем позволить себе использовать любую длину бита: нам нужно, чтобы количество используемых битов было как можно меньше. Скоро вы поймете причину.

Внесем небольшое изменение в приведенный выше код. Результат будет таким:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. int OnInit()
08. {
09.    Print(_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))));
10. 
11.    return INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Исходный код тестового индикатора

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

Рисунок 02

Результат внедрения

Теперь обратите внимание на следующий момент. Раньше требовалось 16 бит, чтобы зарегистрировать нужное значение и определить, что таймфрейм изменился. Теперь посмотрите на максимально возможное значение: 96. Невероятно! Разве можно так делать? Да, можно и именно так будем делать. Это связано с тем, что MetaTrader 5, очевидно, уже консолидирует данные таймфреймы. Так что мы можем изменить ситуацию, чтобы использовать это новое значение.

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


Начинаем передавать информацию сервису

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider         400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. //+------------------------------------------------------------------+
18. union uCast_Double
19. {
20.    double    dValue;
21.    long      _long;                                 // 1 Information
22.    datetime _datetime;                              // 1 Information
23.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
24.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
25.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
26. };
27. //+------------------------------------------------------------------+
28. enum EnumEvents    {
29.          evHideMouse,               //Hide mouse price line
30.          evShowMouse,               //Show mouse price line
31.          evHideBarTime,             //Hide bar time
32.          evShowBarTime,             //Show bar time
33.          evHideDailyVar,            //Hide daily variation
34.          evShowDailyVar,            //Show daily variation
35.          evHidePriceVar,            //Hide instantaneous variation
36.          evShowPriceVar,            //Show instantaneous variation
37.          evCtrlReplayInit           //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Исходный код файла Defines.mqh

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

Буфер данных индикатора всегда имеет тип double, то есть его длина составляет 8 байт. По причинам, о которых я рассказывал в предыдущих статьях, любая информация, которую мы хотим передать, должна умещаться в этих 8 байтах. Мы не можем позволить себе роскошь использовать более 8 байт для передачи информации от индикатора к сервису. Всё должно поместиться в эти 8 байт. Теперь давайте поразмыслим. Отлично. Указатель мыши можно использовать на реальном рынке. Т.е. мы можем использовать его в символе, который получает данные с торгового сервера в режиме реального времени. Однако проблема, которую мы решаем, возникает при использовании сервиса репликации/моделирования. Пока что единственным индикатором, действительно необходимым для репликации/моделирования, является индикатор управления. Поэтому имеет смысл обратить внимание на то, как настроен буфер данных управляющего индикатора. Мы можем увидеть это ниже:

Рисунок 3

Нужно кое-что понять: QWORD - это определение из Assembly. Оно указывает на то, что мы работаем с 64-битным значением. Правый байт равен нулю, а левый равен семи. В данном случае индикатор управления в настоящее время использует 4 байта. Чтобы понять это, давайте посмотрим на фрагмент кода в заголовочном файле C_Controls.mqh, показанный ниже:

168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             if (rates_total > 0)
176.                Buff[rates_total - 1] = info.dValue;
177.          }
178. //+------------------------------------------------------------------+

Фрагмент файла C_Controls.mqh

Обратите внимание, что в строке 171 объявлено объединение, которое также присутствует в вышеупомянутом файле Defines.mqh. Прошу заметить, что мы используем два матричных значения, каждое из которых использует 16 бит. Это дает нам в общей сложности 32 бита. Хотя не все биты используются полностью, будем считать, что все они используются. Таким образом, четыре из восьми доступных байтов уже заняты. Однако, поскольку наша система моделирования позволяет передавать таймфрейм в одном байте, мы будем использовать пять из восьми доступных байтов. Самое главное, на что следует обратить внимание, - это используемый индекс. Подобные моменты часто очень смущают новичков, поскольку они склонны забывать, что при использовании объединения некоторые байты уже заняты полезной информацией. Если использовать неправильный индекс, можно перезаписать информацию и получить неверные значения. Давайте теперь вернемся к изображению QWORD. В нем мы разметим четыре байта, начиная с нулевого индекса. Это означает, что индексы с первого по третий заняты и не должны использоваться для каких-либо других целей. Следовательно, первый свободный индекс равен 4.

Таким образом, мы добавляем новую запись в заголовочный файл Defines.mqh, который теперь будет выглядеть так:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider         400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame       4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.    double    dValue;
22.    long      _long;                                 // 1 Information
23.    datetime _datetime;                              // 1 Information
24.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents    {
30.          evHideMouse,               //Hide mouse price line
31.          evShowMouse,               //Show mouse price line
32.          evHideBarTime,             //Hide bar time
33.          evShowBarTime,             //Show bar time
34.          evHideDailyVar,            //Hide daily variation
35.          evShowDailyVar,            //Show daily variation
36.          evHidePriceVar,            //Hide instantaneous variation
37.          evShowPriceVar,            //Show instantaneous variation
38.          evCtrlReplayInit           //Initialize replay control
39.                   };
40. //+------------------------------------------------------------------+

Исходный код файла Defines.mqh

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonCtrl        def_PathBMP + "Ctrl.bmp"
011. #define def_ButtonCtrlBlock   def_PathBMP + "Ctrl_Block.bmp"
012. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
013. #resource "\\" + def_ButtonPlay
014. #resource "\\" + def_ButtonPause
015. #resource "\\" + def_ButtonCtrl
016. #resource "\\" + def_ButtonCtrlBlock
017. #resource "\\" + def_ButtonPin
018. //+------------------------------------------------------------------+
019. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
020. #define def_PosXObjects         120
021. //+------------------------------------------------------------------+
022. #define def_SizeButtons         32
023. #define def_ColorFilter         0xFF00FF
024. //+------------------------------------------------------------------+
025. #include "..\Auxiliar\C_Mouse.mqh"
026. //+------------------------------------------------------------------+
027. class C_Controls : private C_Terminal
028. {
029.    protected:
030.    private   :
031. //+------------------------------------------------------------------+
032.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
033.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
034. //+------------------------------------------------------------------+
035.       struct st_00
036.       {
037.          string   szBarSlider,
038.                   szBarSliderBlock;
039.          ushort   Minimal;
040.       }m_Slider;
041.       struct st_01
042.       {
043.          C_DrawImage *Btn;
044.          bool         state;
045.          short        x, y, w, h;
046.       }m_Section[eObjectControl::eNull];
047.       C_Mouse   *m_MousePtr;
048. //+------------------------------------------------------------------+
049. inline void CreteBarSlider(short x, short size)
050.          {
051.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
052.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
053.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
054.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
055.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
060.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
066.          }
067. //+------------------------------------------------------------------+
068.       void SetPlay(bool state)
069.          {
070.             if (m_Section[ePlay].Btn == NULL)
071.                m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
072.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start");
073.             if (!state) CreateCtrlSlider();
074.          }
075. //+------------------------------------------------------------------+
076.       void CreateCtrlSlider(void)
077.          {
078.             if (m_Section[ePin].Btn != NULL) return;
079.             CreteBarSlider(77, 436);
080.             m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock);
081.             m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true);
082.             m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL);
083.             PositionPinSlider(m_Slider.Minimal);
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveCtrlSlider(void)
087.          {         
088.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
089.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
090.             {
091.                delete m_Section[c0].Btn;
092.                m_Section[c0].Btn = NULL;
093.             }
094.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
095.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
096.          }
097. //+------------------------------------------------------------------+
098. inline void PositionPinSlider(ushort p)
099.          {
100.             int iL, iR;
101.             string szMsg;
102.             
103.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
104.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
105.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
106.             m_Section[ePin].x += def_PosXObjects;
107.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
108.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)   if (m_Section[c0].Btn != NULL)
109.              {
110.                 switch (c0)
111.                 {
112.                    case eLeft  : szMsg = "Previous Position";            break;
113.                    case eRight : szMsg = "Next Position";                break;
114.                    case ePin   : szMsg = "Go To: " + IntegerToString(p); break;
115.                    default     : szMsg = "\n";
116.                 }
117.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg);
118.             }
119. 
120.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
121.          }
122. //+------------------------------------------------------------------+
123. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
124.          {
125.             C_Mouse::st_Mouse InfoMouse;
126.             
127.             InfoMouse = (*m_MousePtr).GetInfoMouse();
128.             x = (short) InfoMouse.Position.X_Graphics;
129.             y = (short) InfoMouse.Position.Y_Graphics;
130.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
131.             {   
132.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
133.                   return c0;
134.             }
135.             
136.             return eNull;
137.          }
138. //+------------------------------------------------------------------+
139.    public   :
140. //+------------------------------------------------------------------+
141.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
142.          :C_Terminal(Arg0),
143.           m_MousePtr(MousePtr)
144.          {
145.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
148.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
149.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
150.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
151.             {
152.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
153.                m_Section[c0].y = 25;
154.                m_Section[c0].Btn = NULL;
155.             }
156.             m_Section[ePlay].x = def_PosXObjects;
157.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
158.             m_Section[eRight].x = m_Section[ePlay].x + 511;
159.             m_Slider.Minimal = eTriState;
160.          }
161. //+------------------------------------------------------------------+
162.       ~C_Controls()
163.          {
164.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
166.             delete m_MousePtr;
167.          }
168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             info._8b[def_IndexTimeFrame] = (uchar) def_IndicatorTimeFrame;
176.             if (rates_total > 0)
177.                Buff[rates_total - 1] = info.dValue;
178.          }
179. //+------------------------------------------------------------------+
180.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
181.          {
182.             short x, y;
183.             static ushort iPinPosX = 0;
184.             static short six = -1, sps;
185.             uCast_Double info;
186.             
187.             switch (id)
188.             {
189.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
190.                   info.dValue = dparam;
191.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
192.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
193.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
194.                   break;
195.                case CHARTEVENT_OBJECT_DELETE:
196.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
197.                   {
198.                      if (sparam == def_ObjectCtrlName(ePlay))
199.                      {
200.                         delete m_Section[ePlay].Btn;
201.                         m_Section[ePlay].Btn = NULL;
202.                         SetPlay(m_Section[ePlay].state);
203.                      }else
204.                      {
205.                         RemoveCtrlSlider();
206.                         CreateCtrlSlider();
207.                      }
208.                   }
209.                   break;
210.                case CHARTEVENT_MOUSE_MOVE:
211.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
212.                   {
213.                      case ePlay:
214.                         SetPlay(!m_Section[ePlay].state);
215.                         if (m_Section[ePlay].state)
216.                         {
217.                            RemoveCtrlSlider();
218.                            m_Slider.Minimal = iPinPosX;
219.                         }else CreateCtrlSlider();
220.                         break;
221.                      case eLeft:
222.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
223.                         break;
224.                      case eRight:
225.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
226.                         break;
227.                      case ePin:
228.                         if (six == -1)
229.                         {
230.                            six = x;
231.                            sps = (short)iPinPosX;
232.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
233.                         }
234.                         iPinPosX = sps + x - six;
235.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
236.                         break;
237.                   }else if (six > 0)
238.                   {
239.                      six = -1;
240.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
241.                   }
242.                   break;
243.             }
244.             ChartRedraw(GetInfoTerminal().ID);
245.          }
246. //+------------------------------------------------------------------+
247. };
248. //+------------------------------------------------------------------+
249. #undef def_PosXObjects
250. #undef def_ButtonPlay
251. #undef def_ButtonPause
252. #undef def_ButtonCtrl
253. #undef def_ButtonCtrlBlock
254. #undef def_ButtonPin
255. #undef def_PathBMP
256. //+------------------------------------------------------------------+

Исходный код файла C_Controls.mqh

Из-за некоторых модификаций, сделанных в предыдущих статьях, нам необходимо внести некоторые изменения в заголовочный файл C_Controls.mqh, в дополнение к другим изменениям, о которых мы расскажем ниже. Посмотрите на строку 10: данная строка ссылается на изображение Ctrl.bmp. Какое это изображение? Это старое изображение LEFT.BMP. Аналогично, строка 11 ссылается на изображение LEFT_BLOCK.BMP. Теперь мы будем использовать новое название для вот этих изображений. В любом случае, мы приложим изображения, чтобы в случае сомнений включить их в проект.

Еще одно изменение внесли в конструктор класса C_Controls. Обратите внимание на строку 146: теперь конструктор будет возвращать результат до завершения своей работы только в том случае, если указанная ошибка является той, которую мы, как программисты, определили в коде. Другие виды ошибок будут игнорироваться, по крайней мере, на данный момент.

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


Заключительные идеи

Поскольку для работы предлагаемой системы необходимо объяснить некоторые изменения, которые нужно будет внести в код сервиса и код индикатора, я не буду включать эти коды в данную статью. Я извиняюсь перед теми, у кого больше опыта в MQL5. Однако за всё время публикации этой серии, я заметил, что многие читатели являются новичками в MQL5 и стремятся программировать на этом языке. Поэтому я стараюсь направлять их и объяснять всё специально для них. Но, несмотря на это, в приложении вы найдете два изображения. На эти изображения ссылается заголовочный файл C_Controls.mqh.

Если хотите продолжать его, не забудьте внести эту корректировку в проект.

Для следующей статьи осталось выполнить следующие задачи: показать и объяснить изменения, внесенные в исходный код индикатора управления. В данном случае мы уже увидели изменения в заголовочном файле, но код индикатора всё еще нуждается в модификации. Кроме того, мы покажем как корректировать и изменять исходный код заголовочного файла C_Replay.mqh. Без этих изменений сервис не сможет определить, что таймфрейм был изменен, просто «взглянув» на индикатор на графике.

Увидимся в следующий раз. Не забудьте внимательно изучить представленные здесь материалы.

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Реализация торговой стратегии Rapid-Fire с использованием индикаторов Parabolic SAR и простой скользящей средней (SMA) на MQL5 Реализация торговой стратегии Rapid-Fire с использованием индикаторов Parabolic SAR и простой скользящей средней (SMA) на MQL5
В настоящей статье мы разрабатываем торговый советник Rapid-Fire на MQL5, используя индикаторы Parabolic SAR и простую скользящую среднюю (SMA) для создания гибкой торговой стратегии. Мы подробно описываем реализацию стратегии, включая использование индикаторов, генерацию сигналов, а также процесс тестирования и оптимизации.
Возможности Мастера MQL5, которые вам нужно знать (Часть 36): Q-обучение с цепями Маркова Возможности Мастера MQL5, которые вам нужно знать (Часть 36): Q-обучение с цепями Маркова
Обучение с подкреплением — один из трех основных принципов машинного обучения, наряду с обучением с учителем и без учителя. Поэтому возникает необходимость в оптимальном управлении или изучении наилучшей долгосрочной политики, которая наилучшим образом соответствует целевой функции. Именно на этом фоне мы исследуем его возможную роль в информировании процесса обучения MLP советника, собранного в Мастере.
Возможности Мастера MQL5, которые вам нужно знать (Часть 37): Регрессия гауссовских процессов с линейными ядрами и ядрами Матерна Возможности Мастера MQL5, которые вам нужно знать (Часть 37): Регрессия гауссовских процессов с линейными ядрами и ядрами Матерна
Линейные ядра — простейшая матрица, используемая в машинном обучении для линейной регрессии и опорных векторных машин. Ядро Матерна (Matérn) представляет собой более универсальную версию радиальной базисной функции (Radial Basis Function, RBF), которую мы рассматривали в одной из предыдущих статей, и оно отлично подходит для отображения функций, которые не настолько гладкие, как предполагает RBF. Создадим специальный класс сигналов, который использует оба ядра для прогнозирования условий на покупку и продажу.
Визуализация стратегий в MQL5: раскладываем результаты оптимизации по графикам критериев Визуализация стратегий в MQL5: раскладываем результаты оптимизации по графикам критериев
В этой статье мы напишем пример визуализации процесса оптимизации и сделаем отображение трёх лучших проходов для четырёх критериев оптимизации. А также обеспечим возможность выбора одного из трёх лучших проходов для вывода его данных в таблицы и на график.