От начального до среднего уровня: События (II)
Введение
В предыдущей статье "От начального до среднего уровня: События (I)", мы начали говорить о событийно-ориентированном программировании. Да, этот подход знаком многим программистам именно под таким названием: событийно-ориентированное программирование. Многие считают его сложным и трудным. Это связано с тем, что они не понимают механизмов и концепций.
Однако событийно-ориентированное программирование освобождает нас от различных проблем и задач, связанных с графическим интерфейсом. Поскольку MetaTrader 5 - это графический интерфейс, использование событийно-ориентированного программирования значительно упрощает разработку событийно-ориентированных приложений.
В предыдущей статье мы сделали первые шаги, чтобы почувствовать, как всё будет происходить дальше. Мы увидели, как обойти некоторые места, чтобы вести учет произошедших событий с помощью довольно простого индикатора. Однако для тех, кто хочет проверить, насколько хорошо он понимает и применяет на практике определенные концепции программирования, предстоит решить небольшую задачу.
Поскольку предложенная задача была не очень сложной, я считаю, что те, кто потратил на нее немного больше времени и усилий, смогли бы придумать способ ее решения, поскольку, судя по всему, ее действительно можно решить относительно просто.
Далее я покажу два способа решения одной и той же задачи, и расскажу о плюсах и минусах каждого из способов решения проблемы. Так что пора сосредоточиться на сегодняшнем материале. И, как обычно, мы начнем новую тему, чтобы лучше всё организовать.
Возможное первое решение
Чтобы правильно начать объяснение, давайте начнем с того, каким был код индикатора, как это было показано в предыдущей статье:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. uint gl_Counter; 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. gl_Counter = 0; 09. 10. Print(__FUNCTION__); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. if (!gl_Counter) 18. { 19. uint arr[]; 20. 21. if (FileLoad(_Symbol, arr) > 0) 22. gl_Counter = arr[0]; 23. } 24. 25. Print(__FUNCTION__, " :: ", ++gl_Counter); 26. 27. return rates_total; 28. }; 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. uint arr[1]; 33. 34. arr[0] = gl_Counter; 35. FileSave(_Symbol, arr); 36. 37. Print(__FUNCTION__); 38. }; 39. //+------------------------------------------------------------------+
Код 01
Предложенная задача заключалась в том, чтобы создать способ заставить переменную, объявленную в строке 04 этого кода, сохранять свое текущее значение счета только тогда, когда изменяется графический период. В любом другом случае значение данной переменной должно быть равно нулю. Таким образом, счет запускался заново.
Несмотря на то, что эта задача относительно проста, она может показать, насколько читатель готов к более сложному. Однако если вы всё-таки застряли при решении проблемы, не переживайте. Главное, что вы искали способ решить проблему. Именно это имеет значение. Решение задачи с вашей стороны было как "получение бонуса", но это не доказывало бы, что вы уже умеете программировать. Вам удалось бы найти только одно решение. В таком случае примите мои самые теплые поздравления.
Ладно, давайте немного подумаем. Каждый раз, когда мы изменяем время графика, MetaTrader 5 запускает событие Deinit. Это, в свою очередь, генерирует вызов, направленный на определенный график. В будущем мы продолжим изучение данного механизма. Пока что вам просто нужно понять, насколько MetaTrader 5 будет работать за вас и как мы можем и должны с этим работать. Когда это событие приходит в графическом окне, оно направляется ко ВСЕМ ПРИЛОЖЕНИЯМ, ПРИСУТСТВУЮЩИМ НА ГРАФИКЕ. КО ВСЕМ. Каждое приложение должно уловить это событие Deinit и обработать его наилучшим образом.
Хорошо, а кто получает данное событие? Что ж, это событие будет перехвачено только и исключительно процедурой OnDeinit, и вот тут-то всё и становится интересным. Если посмотреть на строку 30 кода 01, мы увидим, что данная процедура получает значение. Это значение заполняется MetaTrader 5, чтобы сообщить причину удаления приложения с графика. "Но подождите секунду, что значит "удаление приложения с графика"? На первый взгляд, это не имеет особого смысла, так как мы просим только изменить период графика, а не удалить индикатор". В каком-то смысле вы правы, дорогие читатели. Однако по разным причинам, в основном связанным с реализацией самой платформы MetaTrader 5, гораздо проще удалить все элементы с графика, запросить его перерисовку и только потом поместить удаленные элементы обратно на график. Конечно, из-за этого скрипты не переставляются, поскольку они не могут обрабатывать события. Однако индикаторы и советники, умеющие работать с событиями, автоматически перекидываются обратно на график. Это то же самое, что и при запуске платформы. Если на открытом графике есть индикатор или советник, он будет автоматически размещен на графике MetaTrader 5. Сразу после размещения всех этих элементов на графике MetaTrader 5 выдает новое событие для этого графика. Событием, о котором идет речь, является Init, которое, в свою очередь, будет перехвачено функцией OnInit. Данную функцию можно увидеть в строке 06 кода 01. Вот и вся работа, которую MetaTrader 5 сделает за нас. Дальше мы сами решаем, как наше приложение будет реагировать на такие события.
Это очень важно, поэтому, пожалуйста, поймите и усвойте это как следует. В этом заключается основа событийно-ориентированного программирования на MQL5 для создания приложений, работающих на MetaTrader 5. Поскольку нашему приложению знакомо только значение подсчета, присутствующее в gl_Counter во время выполнения, мы не можем знать, каким оно было до появления события Init. И это происходит так, потому что в процессе удаления графика из платформы и последующего его воссоздания, MetaTrader 5 очищает всю область памяти, которая использовалась нашим приложением на графике. Единственное, что (в зависимости от ситуации) может поддерживаться, - это статические переменные. Но статические переменные в приложениях, предназначенных для работы в качестве индикаторов, создают некоторые проблемы. Их можно использовать, но с определенными предосторожностями. Мы расскажем об этом в будущем. Пока забудем немного о статических переменных в кодах, предназначенных для работы в качестве индикаторов. В случае с советниками ситуация иная, поскольку здесь действуют другие механизмы, которые необходимо понять, чтобы уметь их использовать. Поэтому давайте сначала сосредоточимся на вопросе индикаторов - именно потому, что они проще.
Правда, на данном этапе мы не можем работать со статическими переменными и значение gl_Counter действительно только до тех пор, пока индикатор есть на графике. Кроме того, когда мы попросим MetaTrader 5 изменить период графика, будет вызвано событие, которое будет захвачено процедурой в строке 30, значение параметра reason которой указывает на причину события Deinit. Теперь нам остается обратиться к документации и проверить, какое значение сообщает о типе события, которое нам нужно сохранить gl_Counter. Таким образом, мы получим решение.
Хорошо, тогда, обратившись к документации, мы видим, что константа REASON_CHARTCHANGE удовлетворяет нашему критерию, который заключается именно в изменении графического периода. Однако если символ также изменен, тогда будет передана та же константа. Но сначала давайте остановимся на вопросе с графическим периодом. Таким образом, добавив небольшой тест в процедуру OnDeinit, мы получим наш новый код:
. . . 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. uint arr[1]; 33. 34. arr[0] = (reason == REASON_CHARTCHANGE ? gl_Counter : 0); 35. FileSave(_Symbol, arr); 36. 37. Print(__FUNCTION__); 38. }; 39. //+------------------------------------------------------------------+
Код 02
Поскольку весь остальной код остался идентичным тому, что можно увидеть в коде 01, мы сосредоточимся только на том, что изменилось. Изменение очень понятное: мне просто нужно было изменить строку 34, чтобы проверить, является ли константа той, которую мы хотим использовать. Если да, то значение подсчета сохранится. В противном случае значение обнулится, что приведет к сбросу подсчета при повторном использовании индикатора на том же символе, где происходил подсчет.
Теперь мы рассмотрим преимущества и недостатки такой реализации решения. И да, это не все преимущества и недостатки, связанные с внедрением. Поэтому понимание концепций и практика - это часть обучения.
Для краткости мы приведем только одно преимущество и один недостаток. Остальное зависит от вас, и знания будут появляться по мере того, как вы будете нарабатывать практику и изучать каждый конкретный сценарий использования той или иной формы реализации.
Преимуществом является то, что процесс относительно "чистый" и легко контролируемый по нескольким параметрам. Например, вы можете захотеть узнать, сколько раз определенное событие срабатывало в течение заданного периода времени.
С другой стороны, использование файлов для хранения значений заставляет нас анализировать их, что во многих случаях становится проблематичным. Например, если по одной и той же сессии MetaTrader 5 открыт один и тот же символ с одним и тем же индикатором, то при постоянном изменении периода графика значения могут не совпадать. В программировании этот тип проблемы известен как состояние гонки и является довольно сложным, фактически, на эту тему были написаны докторские диссертации и целые книги. Поверьте мне, как новичку, вам совсем не захочется находится в состоянии гонки в своем коде.
Что ж, мой дорогой читатель, это было бы первым решением, именно потому, что оно не требует особых изменений в коде: лишь беглый поиск в документации и заимствование элементов, уже показанных в предыдущих статьях.
Сейчас мы рассмотрим несколько иное решение, которое делегирует MetaTrader 5 часть работы по отслеживанию значения подсчета. Но, как вы понимаете, мы сделаем это в новой теме.
Возможное второе решение
Второе решение гораздо интереснее как с практической точки зрения, так и с точки зрения того, что ответственность за поддержание стоимости счетчика остается в руках MetaTrader 5. Однако прежде, чем вы подумаете о том, почему мы не рассматривали это решение раньше, мы сделаем всё наоборот. Сначала мы обсудим преимущества и недостатки использования второго предложенного решения, а затем посмотрим, как оно реализовано. Как и в предыдущем решении, мы перечислим по одному преимуществу и недостатку, чтобы объяснение не получилось слишком длинным.
В качестве преимущества второго решения можно отметить тот факт, что оно снимает с нас часть ответственности за сохранение значения переменной в каком-либо месте, скорее всего, используя для этого файл. С другой стороны, следует отметить, что делегирование ответственности за поддержание значения переменной немного усложняет задачу в зависимости от того, что мы пытаемся сделать. Это связано с тем, что ответственность за это лежит на MetaTrader 5, а не на нас.
"Но как это возможно? Как одна и та же вещь может считаться преимуществом и недостатком одновременно?" Чтобы понять, почему я отношу это к преимуществам и недостаткам, нам нужно посмотреть, из чего состоит данное решение, для которого преимущество и недостаток - практически одно и то же, и в то же время производит впечатление чего-то невероятного. Это решение - почти невиданная особенность MetaTrader 5. В прошлом я использовал его для других целей, причем разработчики MetaTrader 5, конечно, не думали, что кто-то будет использовать его именно для этого.
Можно посмотреть это в статье "Разработка торгового советника с нуля (Часть 17): Доступ к данным в Интернете (III)". В то время я представлял, что ресурсы MetaTrader 5 гораздо более широко изучены большим кругом программистов. Однако в итоге я заметил, что стало много людей с очень поверхностными знаниями, которые пытаются вынуждать MQL5 делать то, к чему не приспособлено. Именно поэтому я взял паузу, поразмыслил и созрел для создания этих статей, которыми вы теперь пользуетесь для изучения MQL5 в приятной форме. Я хотел поощрить эту жаждущую знаний публику, но у нас очень мало ссылок, ориентированных на новичков.
Я надеюсь, что эти статьи послужат вам хорошим началом. Любые вопросы и предложения по улучшению данного контента приветствуются.
Хорошо, давайте вернемся к нашему вопросу. Идея состоит в том, чтобы использовать глобальные переменные терминала. Это очень интересный ресурс, доступ к которому можно получить с помощью функций и процедур, определенных в библиотеке MQL5. Он прост, практичен и очень полезен в самых разных ситуациях. Давайте теперь посмотрим, как выглядит код индикатора для целей использования данного ресурса. Вот он:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union u_01 05. { 06. double d_value; 07. uint u_Counter; 08. }gl_Union; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. ZeroMemory(gl_Union); 13. 14. if (!GlobalVariableGet(_Symbol, gl_Union.d_value)) 15. if (!GlobalVariableTemp(_Symbol)) 16. return INIT_FAILED; 17. 18. GlobalVariableSet(_Symbol, gl_Union.d_value); 19. 20. Print(__FUNCTION__); 21. 22. return INIT_SUCCEEDED; 23. }; 24. //+------------------------------------------------------------------+ 25. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 26. { 27. Print(__FUNCTION__, " :: ", ++gl_Union.u_Counter); 28. 29. return rates_total; 30. }; 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. switch(reason) 35. { 36. case REASON_CHARTCHANGE: 37. GlobalVariableSet(_Symbol, gl_Union.d_value); 38. break; 39. default: 40. GlobalVariableDel(_Symbol); 41. } 42. 43. Print(__FUNCTION__); 44. }; 45. //+------------------------------------------------------------------+
Код 03
Когда запускаем код 03, мы видим нечто похожее на анимацию, показанную ниже:

Анимация 01
Прошу заметить, что в какой-то момент мы изменили период графика и при этом сгенерировали ту же последовательность событий, о которой говорили в предыдущей теме. Об этом можно судить по информации, нанесенной на терминал. Но это не всё, что здесь происходит. Обратите внимание, что, в отличие от предыдущего кода 03, мы не используем файл для временного хранения информации (в данном случае значения счетчика), а просим MetaTrader 5 хранить данную информацию очень специфическим образом.
Чтобы проверить это, мы можем посмотреть, существует ли она, и если да, то какие глобальные переменные терминала присутствуют в MetaTrader 5, еще до объяснения кода 03. Для этого просто воспользуйтесь горячей клавишей F3 или следуйте по пути, показанному на изображении ниже:

Изображение 01
Если индикатор кода 03 запущен, то в открывшемся окне мы увидим такую переменную:

Изображение 02
Если индикатор из кода 03 не запущен, то вполне возможно, что это же окно, показанное на изображении 02, не содержит никакой информации, так как в целом, насколько я знаю, мало кто использует глобальные переменные терминала, по каким-либо причинам. Но если ресурс существует и доступен, почему бы нам не воспользоваться им творчески?
Благодаря информации, которую мы видели на изображениях и в анимации, у нас достаточно элементов, чтобы понять, как работает код 03. В некотором смысле, я не вижу необходимости объяснять, как это работает, ведь это всего лишь простая модификация кода 02. Однако я не буду так поступать, поскольку глобальные переменные терминала - это тип ресурса, использование которого очень специфично. Пользуясь случаем, я расскажу, каким образом можно придумать, как использовать их в других ситуациях.
Начнем с того, что если вы действительно хотите использовать глобальную терминальную переменную, то должны знать, что они могут содержать только значения типа double. То есть мы не можем поместить в такую переменную текстовые или целочисленные значения. Это, если обобщить. Однако, поскольку MQL5 позволяет создавать объединения, которые, как мы уже видели, могут быть очень полезны в повседневной работе, мы создаем объединение в строке 04, чтобы поместить значение без плавающей точки в глобальную переменную терминала.
Таким образом, мы открываем несколько возможностей для использования этих переменных, поскольку в этом случае мы сможем включать в них небольшие фрагменты текста, если только текст может быть включен в тип double. Если вы не совсем понимаете, о чем я говорю, то я вам рекомендую ознакомиться с предыдущими статьями, чтобы узнать больше об этой теме.
Чтобы избежать ненужного доступа к глобальным переменным терминала, мы объявляем объединение как глобальную переменную. Это видно из строки 08. Прошу заметить: то, что мы делаем, не является обязательным. Мы можем реализовать данный код так, чтобы у нас не было глобальной переменной, но это заставит нас вынести эту глобальную переменную, которая будет объявлена в нашем коде, за пределы нашего кода, превратив ее в глобальную переменную терминала. В определенном смысле это может снизить общую производительность. Однако в некоторых очень конкретных сценариях может быть интересно и необходимо использовать этот тип реализации. Каждый случай - это отдельный случай. Поэтому не стоит обобщать или думать, что мы должны делать всё так, а не иначе. Ведь так будет не всегда.
Для практических целей мы используем строку 12, чтобы очистить область памяти, в которой будет находиться наш счетчик. Мы можем делать это и другими способами, поэтому придумайте иные способы и воспользуйтесь ими, чтобы увидеть результат. Сразу после этого, в строке 14, мы пытаемся прочитать переменную, которую можно увидеть на изображении 02. Если такой переменной не существует, то в строке 15 мы попытаемся ее создать. Обратите внимание, что имя переменной будет названием символа, в котором используется индикатор. Если попытка не удалась, индикатор вернет MetaTrader 5 значение ошибки, что вызовет новое событие, в данном случае событие Deinit, поскольку инициализация не удалась. И это приведет к тому, что процедура в строке 32 будет выполнена и индикатор исчезнет с графика.
Забавно, как всё работает и может быть реализовано очень просто, если понимать, что делать и как всё генерируется. Однако я хочу, чтобы вы поняли, что данная функция в строке 15 попытается создать временную глобальную переменную терминала, и важно, чтобы это было сделано ДО вызова в строке 18. В противном случае переменная не будет временной. Но даже в этом случае переменная всё равно будет глобальной по другим критериям, но не по тому, который мы хотим использовать.
Когда мы создаем временную глобальную переменную терминала, она будет существовать только до тех пор, пока платформа MetaTrader 5 открыта. Если она закрывается по какой-либо причине, все глобальные переменные терминала, созданные функцией в строке 15, будут удалены. Эта ситуация очень интересна, если мы хотим хранить значения до тех пор, пока MetaTrader 5 открыт (независимо от того, что запущен) и эта информация забывается, когда платформа закрывается. Однако это происходит только в том случае, если глобальная переменная терминала была создана функцией GlobalVariableTemp.
Мы также можем создать такую же переменную, но без условия ее удаления, если воспользоваться функцией в строке 18. В этом случае MetaTrader 5 удалит её через определенный промежуток времени. Обратитесь к документации, чтобы узнать больше об этом, ведь бывают случаи когда с одной стороны нам нужно, чтобы информация присутствовала в течение длительного периода времени, но с другой стороны мы не хотим хранить ее в файле. При такой ситуации глобальные переменные терминала могут оказаться весьма интересными.
Хорошо, что касается функции OnCalculate, то нам нечего добавить. Однако ещё скажем пару слов по поводу процедуры OnDeinit. Обратите внимание на следующий момент: в строке 34 мы проверяем, какая ситуация вызвала событие Deinit. Если это изменение периода графика, как и ожидалось, мы будем использовать строку 37 для хранения текущего значения нашего счетчика. В любой другой ситуации мы удалим созданную глобальную переменную терминала с помощью строки 40. Но зачем это делать, если создаваемая переменная является временной? Причина исключительно образовательная. Вы можете поэкспериментировать с кодом 03 и создать свои собственные виды ситуаций. Нет ничего более справедливого, чем показать, как мы можем создавать и удалять глобальную переменную терминала прямо через код. Вот почему существует строка 40.
Идея заключается в том, чтобы показать, что мы можем добиться одного и того же результата разными способами. Выбор средств зависит от типа сценария или ситуации, с которой мы сталкиваемся.
Заключительные идеи
Что ж, мы подошли к концу очередной статьи. Она является дополнением к предыдущей, поэтому вы должны их рассматривать в комплексе. Изучение того, что было показано в предыдущей статье, и применение знаний, полученных в других статьях этой серии, позволит вам глубже понять то, что объяснялось и демонстрировалось в этой статье. Хотя я не слишком подробно рассказал о том, как работает каждая строка кода, но это и не было моей целью. Я хочу, чтобы вы научились "ловить рыбу". Я предоставляю вам знания и материалы, чтобы вы могли практиковаться и учиться наилучшим образом для достижения своих целей. Вы должны практиковаться и изучать каждую показанную деталь.
Поэтому в следующей статье мы рассмотрим, как можно разработать настоящий индикатор, чтобы применить всё, что мы показывали до настоящего момента. Однако для того, чтобы правильно понять суть следующей статьи, необходимо разобраться в том, как генерируются события и насколько MetaTrader 5 может помочь нам, иначе многие двери останутся для вас закрытыми.
В приложении вы получите доступ к кодам, о которых рассказали в этой статье. Удачи вам и до встречи в следующей статье!
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/15733
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Обучение глубоких спайкинговых моделей (Интеграция спайков)
Автоматизация торговых стратегий на MQL5 (Часть 9): Создаем советник для стратегии прорыва азиатской сессии
От начального до среднего уровня: Индикатор (I)
Создание вероятностного рыночно-нейтрального робота на основе распределения доходностей
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования