Разработка системы репликации (Часть 57): Анализируем тестовый сервис
Введение
В предыдущей статье Разработка системы репликации (Часть 56): Адаптация модулей, мы внесли некоторые изменения как в модуль индикатора управления, так и, главным образом, в модуль индикатора мыши.
Поскольку в статье уже содержалось довольно много информации, было решено никаких новых данных больше не добавлять, поскольку это скорее всего только запутало бы вас, уважаемый читатель, вместо того, чтобы помочь прояснить и объяснить, как вещи действительно работают на самом деле.
В приложении к той же статье содержится доступ к индикатору мыши и сервису. Когда вы его запустите, будет создан кастомный символ и добавлена панель с индикатором мыши и индикатором управления. Оба модуля размещаются на графике кастомного символа, и, хотя они не выполняют никаких действий, связанных с сервисом, между пользователем и обоими модулями можно заметить определенную активность взаимодействия.
Возможно, вы не представляете себе, как это делается, особенно если вы мало знакомы с MQL5. Наблюдая за кодом обоих индикаторов, вы не можете обнаружить или увидеть какую-либо активность, препятствующую потере данных, присутствующих в индикаторе, а точнее, в модуле управления.
Что ж, чтобы объяснить это подробно и с должным тщанием, в этой статье основное внимание будет уделено объяснению того, как на самом деле работает описываемый сервис. Это объяснение имеет первостепенное значение, поскольку правильное понимание принципов работы сервиса, в особенности этого, просто необходимо для того, чтобы понять, как работает система репликации/моделирования. Это связано с тем, что всегда легче создавать и объяснять код с меньшим количеством компонентов, чем пытаться сходу разобраться в коде с гораздо более сложной структурой.
Поэтому, несмотря на то, что код, который будет объяснен далее, на самом деле не будет нами использоваться, очень важно в нем детально разобраться. Вся основа взаимодействия модуля управления, модуля мыши и сервиса будет гораздо лучше усвоена, если хорошо понять этот более простой код.
Так что, без лишних слов, давайте посмотрим на исходный код сервиса, который был реализован и показан в предыдущей статье, чтобы понять содержание видео в конце той же статьи.
Анализ кода сервиса
Исходный код сервиса целиком приведен ниже:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. #property link "https://www.mql5.com/pt/articles/12000" 06. #property version "1.00" 07. //+------------------------------------------------------------------+ 08. #include <Market Replay\Defines.mqh> 09. //+------------------------------------------------------------------+ 10. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 11. #resource "\\" + def_IndicatorControl 12. //+------------------------------------------------------------------+ 13. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 14. //+------------------------------------------------------------------+ 15. void OnStart() 16. { 17. uCast_Double info; 18. long id; 19. int handle; 20. short iPos, iMode; 21. double Buff[]; 22. MqlRates Rate[1]; 23. 24. SymbolSelect(def_SymbolReplay, false); 25. CustomSymbolDelete(def_SymbolReplay); 26. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 27. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5); 28. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5); 29. Rate[0].close = 110; 30. Rate[0].open = 100; 31. Rate[0].high = 120; 32. Rate[0].low = 90; 33. Rate[0].tick_volume = 5; 34. Rate[0].time = D'06.01.2023 09:00'; 35. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 36. SymbolSelect(def_SymbolReplay, true); 37. id = ChartOpen(def_SymbolReplay, PERIOD_M30); 38. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE) 39. ChartIndicatorAdd(id, 0, handle); 40. IndicatorRelease(handle); 41. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE) 42. ChartIndicatorAdd(id, 0, handle); 43. IndicatorRelease(handle); 44. Print("Service maintaining sync state. Version Demo..."); 45. iPos = 0; 46. iMode = SHORT_MIN; 47. while (def_Loop) 48. { 49. while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50); 50. info.dValue = 0; 51. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0]; 52. IndicatorRelease(handle); 53. if ((short)(info._16b[0]) == SHORT_MIN) 54. { 55. info._16b[0] = (ushort)iPos; 56. info._16b[1] = (ushort)iMode; 57. EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, ""); 58. }else if (info._16b[1] != 0) 59. { 60. iPos = (short)info._16b[0]; 61. iMode = (short)info._16b[1]; 62. } 63. Sleep(250); 64. } 65. ChartClose(id); 66. SymbolSelect(def_SymbolReplay, false); 67. CustomSymbolDelete(def_SymbolReplay); 68. Print("Finished service..."); 69. } 70. //+------------------------------------------------------------------+
Исходный код тестового сервиса
Чтобы каждый мог реально понять, что происходит, даже те, у кого не так много опыта в MQL, и особенно те, кто пока не очень знаком с программированием сервисов для MetaTrader 5, давайте подробно разберемся в коде, начиная со строки 2.
Когда вы встречаете в коде MQL5 свойство, объявленное во второй строке, вы должны понимать, что это сервис. Если бы это свойство отсутствовало, код следовало бы рассматривать как скрипт. Основные различия между сервисом и скриптом заключаются, прежде всего, в наличии или отсутствии этого свойства в коде. А вот с точки зрения исполнения, основное отличие состоит в том, что скрипт всегда будет привязан к графику, в то время как сервис не зависит от его присутствия.
Остальные строки, между 3 и 6, хорошо знакомы тем, кто имеет хотя бы минимальные знания в программировании MQL5. В этом случае мы можем продолжить, не придавая им особого значения.
В строке 8 мы добавили директиву include, чтобы включить в этот код заголовочный файл. Обратите внимание, что в этом случае заголовочный файл будет находиться в папке «Include» внутри папки «Market Replay» под названием «Defines.mqh». Папка "Include" расположена в корневом каталоге основной директории MQL5, на который будет ссылаться MetaEditor в момент компиляции.
Теперь необходимо уделить особое внимание строкам 10 и 11. Эти строки находятся здесь для того, чтобы индикатор, а точнее модуль управления, стал внутренним ресурсом скомпилированного кода сервиса. Что это значит на самом деле? Это означает, что при переносе уже скомпилированного кода вам не потребуется переносить также код модуля управления, поскольку он уже будет встроен в качестве ресурса сервиса. По этой причине, в приложении к предыдущей статье было только два исполняемых файла, хотя на момент запуска сервиса на самом деле было запущено три файла.
Но почему мы также не включили индикатор, или скорее модуль мыши, как ресурс сервиса, так же, как мы сделали с модулем управления? Причина проста: дать возможность пользователю использовать модуль мыши и упростить к нему доступ. Если бы этот модуль был встроен в исполняемый файл сервиса, пользователю было бы сложно получить доступ к модулю мыши, чтобы поместить его на другой график, отличный от созданного сервисом.
По этой причине нам часто приходится принимать решения о том, какие именно элементы и, главным образом, почему, стоит делать внутренними ресурсами определенного исполняемого файла.
Далее мы видим строку 13. В этой строке мы объявляем определение с целью упрощения или, лучше сказать, стандартизации некоторых тестов, которые будем проводить. В сложных программах довольно часто приходится запускать один и тот же тип теста в различных местах. Создание определения для стандартизации таких тестов, кроме упрощения и облегчения поддержки кода, гарантирует, что мы всегда будем проводить одинаковый тип тестирования. Во многих случаях это является крайне желательным для большинства программистов, так как можно забыть изменить какую-то точку тестирования, и, когда по какой-то причине код будет выполнять этот конкретный тест, он будет несоответствовать другим точкам. Обычно это вызывает много головной боли и раздражения.
Очень хорошо. В строке 15 мы действительно приступаем к исполняемой части кода. Обратите внимание, что OnStart — это одна и та же точка входа как для скриптов, так и для сервисов. Это для кода на MQL5. Однако, из объявления в строке 2 мы знаем, что имеем дело с сервисом. MetaTrader 5 будет генерировать только один вызов события OnStart, каждый раз при выполнении кода, следовательно, потребуются некоторые маневры, чтобы код работал весь необходимый период времени. Однако, это произойдет только в конкретном случае, на строке 47. Но прежде нам нужно сделать еще несколько вещей.
А именно: произвести инициализацию и адаптацию сервиса, чтобы он мог делать для нас что-то полезное. Среди необходимых шагов — объявление и инициализация переменных и условий, которые будут налагаться нашим сервисом на MetaTrader 5. Все это будет выполнено после запуска сервиса. Учитывая это, между строками 17 и 22 мы объявляем переменные, которые будем использовать, и переходим к корректной инициализации правил, которые будут наложены на MetaTrader 5. Многое из того, что будет рассмотрено с этого момента, может показаться странным.
Речь идет о, казалось бы, простых вещах, которые изначально могли бы быть частью другого типа приложений, но поскольку мы хотим сосредоточить все в одном коде, нам приходится работать таким образом.
Далее, в строке 24, мы сообщаем MetaTrader 5, что символ, о котором идет речь, должен быть удален из окна обзора рынка. Сразу после этого, в строке 25, мы удаляем его из списка символов. Этот список содержит все инструменты, к которым у нас может быть какой-то доступ. Но то, что нас действительно интересует, находится в строке 26. В этой строке мы сообщаем MetaTrader 5, что хотим создать кастомный символ, и указываем, где он должен быть создан. Вам следует обратить на это внимание, потому что, если вы не будете осторожны, во время этого действия можно перезаписать рыночный символ, и, пытаясь получить доступ к реальному инструменту, вы фактически будете обращаться к кастомному. Но обычно мы всегда принимаем некоторые меры предосторожности, чтобы этого не произошло.
Строки 26 и 27 являются обязательными; без них модуль мыши не смог бы корректно устанавливать ценовую линию в правильное положение при перемещении мыши по графику.
Теперь, между строками 29 и 34, мы определяем бар, который будет первым баром, видимым на графике. По причинам, которые я не до конца понимаю, этот бар всегда располагается в такой позиции, которая не позволяет просмотреть максимумы и минимумы. Но поскольку наша цель здесь состоит в том, чтобы просто представить бар на графике и исключить генерацию модулями каких-либо ошибок диапазона, нам не очень важно, виден бар полностью или нет.
До этого момента, по сути, у нас на графике нет ровным счетом ничего, нет даже самого графика. В строке 35 мы сообщаем MetaTrader 5, что значения, определенные в предыдущих строках, где мы описываем бар, должны быть размещены в качестве первого бара создаваемого нами актива. В строке 36 мы указываем ему, что актив следует поместить в окно наблюдения за рынком. Если бы код заканчивался на этом этапе, мы могли бы вручную открыть кастомный символ и отобразить размещенный в нем бар. Но поскольку мы хотим еще больше автоматизации, у нас есть строка 37.
В момент выполнения строки 37, MetaTrader 5 откроет график с указанным символом и в том периоде, который мы указали. Хотя это уже объяснялось в других случаях, в каком-то месте этой серии я также объяснял причину информирования модулей об ID графика. Очень важно помнить, что теперь у нас открыт график, и поскольку мы не указываем, какой шаблон должен быть использован, график откроется с использованием стандартного шаблона. Эта мысль необходима для понимания следующих строк.
Давайте начнем со следующего: обратите внимание, что в строке 38 мы пытаемся сгенерировать «handle» или обработчик, чтобы попытаться разместить модуль мыши на графике. Особенно отметьте указанное местоположение и имя исполняемого файла. Если попытка создать "handle" по какой-либо причине окажется неудачной, строка 39 не будет выполнена. Обычно причина заключается в том, что исполняемый файл не находится в указанном месте, позже я подробнее это объясню. Но если "handle" был успешно создан, то в строке 39 мы добавим модуль на график. Таким образом, индикатор станет доступным, не обязательно видимым, но сможет присутствовать в списке индикаторов на графике.
Итак, в строке 40 мы сообщаем MetaTrader 5, что "handle" больше не нужен, поэтому выделенная под это память может быть возвращена в систему. Однако, поскольку модуль уже находится на графике, MetaTrader 5 не удалит его с графика, если только вы специально не сообщите терминалу о такой необходимости.
Этот же скрипт можно было бы удалить, и эффект был бы тем же, то есть, модуль мыши был бы помещен на график. Для этого было бы достаточно, чтобы он содержался в предустановленном шаблоне. Чтобы сделать это, можно было открыть любой график, добавить к нему индикатор мыши, а затем сохранить эту конфигурацию как Default.tpl. Или, чтобы было понятней, теперь можно создавать дефолтный шаблон, уже включающий в себя и индикатор мыши. Это устранило бы необходимость в наличии команд, видимых между строками 38 и 40, и в то же время, позволило бы разместить индикатор мыши в наиболее подходящем для нас месте.
В случае с модулем управления, ситуация немного иная. Это связано с тем, что модуль управления интегрирован в исполняемый файл сервиса. Тот простой факт, что это происходит, облегчает сервису размещение упомянутого модуля на графике. Это делается в строках 41 и 42. В строке 41 мы генерируем "handle" для доступа к модулю, а в строке 42 добавляем его на график. Заметьте, если мы не используем строку 42, то модуль не будет размещен на графике, он будет только загружен в память, но MetaTrader 5 не запустит его на нужном нам графике.
В строке 43 мы удалили "handle", так как он нам больше не нужен, так же, как это было сделано в строке 40.
До этого момента сервис ведет себя как программа, которая скоро завершится. Но перед этим мы используем строку 44 для вывода сообщения в окно сообщений, чтобы знать, как далеко мы продвинулись в выполнении. Затем, между строками 45 и 46, мы инициализируем последние переменные, которые фактически собираемся использовать. Обратите внимание на следующее: мы инициализируем эти переменные значениями, которые будут использоваться для запуска модуля управления. Помните, что он только был запущен на графике, но еще не был инициализирован, и по этой причине на графике не отображается.
Наконец, в строке 47 мы входим в цикл. Этот цикл завершится, если какое-либо из условий, определенных в строке 13, будет выполнено, что приведет к тому, что условие станет ложным и цикл прервется. С этого момента мы больше не будем делать вещи как попало. С этого момента сервис прекратит выполнение действий и будет отвечать за управление тем, что уже было запущено. Важно иметь в виду эту концепцию и знать, как проводить такое различие. В противном случае, не исключены попытки сделать то, чего в цикле быть не должно, и что снова сделает все очень нестабильным и проблематичным.
Теперь в строке 49 мы видим новый цикл. Этот цикл из строки 49 является опасным типом цикла, если он не спланирован должным образом. Причина в том, что если бы мы не использовали определение из строки 13, мы могли бы застрять в этом цикле на неопределенный срок. Это связано с тем, что модуль управления может отсутствовать на графике, и сервис будет ждать, пока MetaTrader 5 сообщит ему значение обработчика, прежде чем он сможет получить доступ к индикатору или модулю управления.
Однако, удаление модуля управления графиком приведет к тому, что MetaTrader 5 закроет график. Это делает определение в строке 13 ложным, поэтому циклы в строке 49 и строке 47 будут завершены.
Вот почему я объясняю, как все работает, на примере более простой системы. Заметить и разобрать подобные тонкости в более сложной системе было бы гораздо сложнее. Поэтому всякий раз, когда вы хотите что-то протестировать, делайте это с помощью какой-то простой программы, следующей той же логике работы, что и система, которая будет спроектирована позже.
Итак, предполагая, что MetaTrader 5 возвращает нам действительный "handle", в строке 50 мы обнуляем значение, которое определим, если на строке 51 чтение буфера индикатора управления будет успешным.
Обратите внимание на следующее: если чтение буфера по какой-либо причине не удастся, переменная, которую мы будем использовать, будет обнулена. Но если чтение пройдет успешно, мы будем иметь данные, которые находятся в буфере индикатора управления.
Как только мы получаем данные, мы можем удалить обработчик. Это делается в строке 52. Причина в том, что на следующих этапах может случиться что угодно, и мы не хотим получать ложные сигналы при повторении цикла. Может показаться, что это замедлит общую производительность системы, но лучше немного потерять в производительности, чем анализировать данные, которые могут быть недействительными или ненужными.
Теперь обратите внимание на следующее: индикатор, а точнее модуль управления, пока еще не инициализирован. Однако его буфер содержит значения, которые были помещены туда на первом этапе работы индикатора. Чтобы понять это, обратитесь к предыдущим статьям этой серии. Поэтому не следует ожидать, что при чтении буфера будут возвращены нулевые значения, этого не произойдет.
Для этого у нас есть два условных теста. Один — для инициализации модуля управления, чтобы тот знал, какие элементы управления отображать. И второй тест — чтобы мы могли хранить в сервисе всю информацию о том, что происходит в модуле управления. Это сделано для того, чтобы в момент, когда потребуется снова сообщить о последнем рабочем состоянии модуля, мы знали, какие значения ему передавать.
Таким образом, в строке 53 мы проверяем, был ли модуль добавлен на график. Это может произойти в двух моментах. Первый — когда мы все еще находимся в первом выполнении цикла, начатого в строке 47. В этом случае значения, которые должны быть сообщены модулю управления, извлекаются из строк 45 и 46. Второй момент — когда MetaTrader 5 приходится сбрасывать и заново размещать модуль управления на графике, поскольку время графика по какой-либо причине было изменено. В этом случае будут использоваться последние значения, которые были в индикаторе управления до того, как MetaTrader 5 снова поместил его на график.
Но в любом случае, в конце мы выполним строку 57, которая заставит MetaTrader 5 сгенерировать пользовательское событие на графике, за которым наблюдает сервис. Таким образом, индикатор управления получит новые значения или старые значения, создавая впечатление, что каким-то волшебным образом ничего не было потеряно. Это произойдет лишь потому, что в индикаторе, в модуле управления, мы поместили в буфер определенное значение, указывающее на то, что индикатор хочет и должен получать обновленные значения из внешнего источника. В данном случае, это сервис, который работает на платформе MetaTrader 5.
Теперь, если это не содержимое, поступающее из буфера индикатора, у нас есть новый тест, выполняемый в строке 58, где мы проверяем, отличается ли значение от нуля. Если это происходит, значит, индикатор управления находится в режиме «play» или на паузе. Однако здесь, в демонстрационном сервисе, обе ситуации указывают на то, что сервис должен абсорбировать то, что находится в буфере индикатора, сохраняя таким образом значение, которое будет использоваться, если MetaTrader 5 решит удалить и поместить индикатор обратно на график из-за изменения времени на графике.
Поскольку выполнение может произойти очень быстро, и сверхбыстрое обновление нам не нужно, мы используем строку 63, чтобы предоставить сервису некоторый отдых, в течение которого он не будет выполнять ничего важного. Эта строка также сообщает нам об окончании цикла, который начался в строке 47.
Кроме того, когда пользователь закрывает сервис, или график закрывается по какой-либо причине, мы выходим из цикла, начатого в строке 47, и сначала выполняем строку 65. В этой строке мы закрываем график, так как если пользователь завершит работу сервиса, график все равно будет оставаться открытым. Чтобы этого избежать, мы сообщаем MetaTrader 5, что он уже может закрыть используемый нами график.
После закрытия графика, мы пытаемся удалить созданный пользовательский символ из окна обзора рынка. Это делается в строке 66. Если никаких других открытых графиков с этим символом нет, мы сможем заставить MetaTrader 5 удалить его из обзора рынка. Тем не менее, даже после удаления, он все равно будет отображаться в том месте, где был создан, в строке 26.
Если символ действительно был удален из обзора рынка, мы воспользуемся строкой 67, чтобы удалить его из списка символов, опубликованных в системе. И наконец, в строке 68, если все прошло хорошо, и все процедуры были выполнены как положено, мы напечатаем сообщение в окне сообщений терминала, с информацией о том, что сервис завершил работу, и его поддержка больше не осуществляется.
Заключение
Ситуация такого типа, хотя и может показаться неважной или бесполезной в долгосрочной перспективе, на самом деле дает полезные и необходимые знания для того, что нам действительно понадобится впоследствии. Часто при разработке решения или приложения, нам приходится сталкиваться с неизвестными или неблагоприятными сценариями. Поэтому всякий раз, когда вам нужно спроектировать, запрограммировать или разработать что-то, что может оказаться намного сложнее, чем изначально предполагалось, создайте небольшую программу, которую легко понять и изучить, но которая, тем не менее, выполняет часть необходимой вам работы, предстоящей в долгосрочной перспективе.
Если вы все сделаете правильно, вы сможете взять часть созданной вами небольшой программы и использовать ее в своем настоящем большом и сложном проекте. По этой причине я решил написать эту статью. Чтобы вы поняли, что программа, порой чрезвычайно сложная и требующая разных уровней знаний, не создается просто так из ничего.
Она всегда создается поэтапно, функциональными частями, а затем часть задуманного используется в более крупном решении. И это именно то, что мы будем делать здесь, в системе репликации/моделирования. Тот способ работы сервиса, который был проанализирован в этой статье, дает нам именно то, что нам нужно для реализации нашей разрабатываемой системы.
Обратите внимание, что без надлежащего анализа и исследований, которые позволила нам провести эта статья, маловероятно, что нам удалось бы с минимальными усилиями добиться правильной работы модулей в системе репликации/моделирования. Нам пришлось бы внести множество корректировок и изменений в код, но все они сосредоточились бы на одном пункте: классе C_Replay. Но без знания, где, как и каким образом следует адаптировать класс C_Replay, мы бы в конечном итоге зашли в тупик, что сделало бы реализацию увиденного здесь чрезвычайно сложной.
Теперь, зная, как фактически будет вести себя система, все, что нам нужно сделать, это внести несколько небольших изменений (так я надеюсь) в класс C_Replay, чтобы цикл между строками 47 и 64 мог стать частью кода нашей системы. С небольшим нюансом: в отличие от того, что мы видим здесь, в системе репликации/моделирования мы будем выполнять расчеты, чтобы следовать тому, что уже было отображено на графике. Но это будет рассмотрено в следующих статьях. Поэтому, не торопясь изучите то, что было показано в этой статье, разберитесь и изучите, как работает система, чтобы лучше понять все то, что будет представлено далее. Теперь мы заставим сервис репликации/моделирования использовать те самые рабочие модули, показанные на видео в предыдущей статье.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12005
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо за ваш вклад.
Когда я попытался скомпилировать файлы, я столкнулся с несколькими ошибками компилятора из-за отсутствия файлов изображений.
Например, при попытке скомпилировать файл 'Services\Market Replay.mq5', я получил следующие ошибки:
Я столкнулся с дополнительными ошибками .bmp, но я смог найти эти изображения в предыдущих частях вашей серии и поместил их в обновленную структуру папок 'Images'. Однако другие компиляции не смогли найти 'Indicators.ico' и сообщили об отсутствии ошибок .tpl.
Не могли бы вы обновить zip-файл и включить в него папки 'Images' и ' Profiles ' (как вы делали в предыдущих частях) и убедиться, что они будут включены в последующие части серии? Это очень поможет нам следить за вашей работой.
Еще раз спасибо.
Очень интересно как это могло случиться, что статья опубликована сегодня
А реплики были уже в июле