
Разработка системы репликации (Часть 72): Неожиданный способ оповещений (I)
Введение
В предыдущей статье, "Разработка системы репликации (Часть 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. Пример реализации можно увидеть на следующем изображении:
Результат внедрения
Обратите внимание на кое-что интересное. В колонке 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. //+------------------------------------------------------------------+
Исходный код тестового индикатора
Прежде чем анализировать, что делает эта новая строка, давайте посмотрим на следующее изображение, чтобы увидеть результат ее выполнения.
Результат внедрения
Теперь обратите внимание на следующий момент. Раньше требовалось 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 байт. Теперь давайте поразмыслим. Отлично. Указатель мыши можно использовать на реальном рынке. Т.е. мы можем использовать его в символе, который получает данные с торгового сервера в режиме реального времени. Однако проблема, которую мы решаем, возникает при использовании сервиса репликации/моделирования. Пока что единственным индикатором, действительно необходимым для репликации/моделирования, является индикатор управления. Поэтому имеет смысл обратить внимание на то, как настроен буфер данных управляющего индикатора. Мы можем увидеть это ниже:
Нужно кое-что понять: 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования